Compare commits
11 Commits
b41c01e6f7
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3325c63631 | |||
| 3a12b6a4ab | |||
| 9ea2278dfb | |||
| a4391c84d0 | |||
| ee6dc58a21 | |||
| 0d3995764b | |||
| f30c41afba | |||
| 2ef9968920 | |||
| d8db9f650b | |||
| 349510db56 | |||
| b44834343a |
@@ -20,7 +20,7 @@ export interface LoginTokenViewModel {
|
||||
avatarImage: string;
|
||||
role: Role;
|
||||
cellGroup: PastoralDomain;
|
||||
signalRSessionId;
|
||||
signalRConnectionId;
|
||||
sessionTabId: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface IGamePlayer {
|
||||
isPlayer: boolean;
|
||||
signalRClientId: string;
|
||||
tabId: string;
|
||||
isDisconnected: boolean;
|
||||
}
|
||||
|
||||
export class GamePlayer implements IGamePlayer {
|
||||
@@ -14,4 +15,5 @@ export class GamePlayer implements IGamePlayer {
|
||||
isPlayer: boolean;
|
||||
signalRClientId: string;
|
||||
tabId: string;
|
||||
isDisconnected: boolean;
|
||||
}
|
||||
@@ -50,8 +50,11 @@ import { MD2MobInfoEditorComponent } from './massive-darkness2/md2-mob-info-main
|
||||
import { MD2MobInfoDetailComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-detail/md2-mob-info-detail.component';
|
||||
import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-skill-editor/md2-mob-skill-editor.component';
|
||||
import { MD2MobLevelEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-level-editor/md2-mob-level-editor.component';
|
||||
import { MD2BossFightEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component';
|
||||
import { MD2PhaseBuffEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-phase-buff-editor/md2-phase-buff-editor.component';
|
||||
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
|
||||
import { MD2HeroProfileEditorComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-editor/md2-hero-profile-editor.component';
|
||||
import { GameInitDlgComponent } from './massive-darkness2/game-init-dlg/game-init-dlg.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -87,8 +90,11 @@ import { MD2HeroProfileEditorComponent } from './massive-darkness2/md2-hero-prof
|
||||
MD2MobInfoDetailComponent,
|
||||
MD2MobSkillEditorComponent,
|
||||
MD2MobLevelEditorComponent,
|
||||
MD2BossFightEditorComponent,
|
||||
MD2PhaseBuffEditorComponent,
|
||||
MD2HeroProfileMaintenanceComponent,
|
||||
MD2HeroProfileEditorComponent
|
||||
MD2HeroProfileEditorComponent,
|
||||
GameInitDlgComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -7,6 +7,8 @@ import { SignalRMessage } from "../../services/signal-r.service";
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model";
|
||||
import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model";
|
||||
import { LoginUserService } from "../../services/login-user.service";
|
||||
import { GamePlayer } from "../games.model";
|
||||
|
||||
@Injectable()
|
||||
export abstract class MD2Base {
|
||||
@@ -58,14 +60,14 @@ export abstract class MD2Base {
|
||||
}
|
||||
|
||||
imgUrl(imgPath: string) {
|
||||
return this.md2Service.stateService.imgUrl(imgPath);
|
||||
return this.md2Service.imgUrl(imgPath);
|
||||
}
|
||||
fileList(folderPath: string) {
|
||||
return this.md2Service.fileList(folderPath);
|
||||
}
|
||||
|
||||
iconHtml(icon: MD2Icon, cssClass = '') {
|
||||
return this.md2Service.stateService.iconHtml(icon, cssClass);
|
||||
return this.md2Service.iconHtml(icon, cssClass);
|
||||
}
|
||||
|
||||
imgHtml(imgFile: string, cssClass = '') {
|
||||
@@ -81,12 +83,18 @@ export abstract class MD2Base {
|
||||
}
|
||||
abstract refreshUI();
|
||||
handleSignalRCallback(message: SignalRMessage): void {
|
||||
// if (message.from.isGroup) {
|
||||
// if (!this.isHeroDashboard) return;
|
||||
// } else {
|
||||
// if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return;
|
||||
// }
|
||||
console.log('handleSignalRCallback', message);
|
||||
if (message.from) {
|
||||
if (message.from.isGroup) {
|
||||
if (!this.isHeroDashboard) return;
|
||||
} else {
|
||||
if (this.isHeroDashboard && this.md2Service.playerHero?.playerInfo?.signalRClientId == message.from.connectionId) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isHeroDashboard) {
|
||||
|
||||
}
|
||||
switch (message.actionType) {
|
||||
case 'hero':
|
||||
let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
|
||||
@@ -95,43 +103,12 @@ export abstract class MD2Base {
|
||||
this.md2Service.heros.push(heroInfo);
|
||||
break;
|
||||
case 'update':
|
||||
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.signalRClientId == heroInfo.playerInfo.signalRClientId);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateHeroInfo(heroInfo);
|
||||
//Object.assign(heroInfo, exitingHero);
|
||||
break;
|
||||
case 'updateMyHero':
|
||||
if (this.isHeroDashboard) {
|
||||
this.md2Service.stateService.playerHero = heroInfo;
|
||||
this.md2Service.playerHero = heroInfo;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -140,15 +117,54 @@ export abstract class MD2Base {
|
||||
}
|
||||
this.detectChanges();
|
||||
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':
|
||||
switch (message.actionName) {
|
||||
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();
|
||||
break;
|
||||
case 'update':
|
||||
if (this.isHeroDashboard) {
|
||||
//Before update game info check the current Hero level
|
||||
let playerHeroLevel = this.md2Service.playerHero?.level;
|
||||
this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo);
|
||||
let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId);
|
||||
if (playerHero) {
|
||||
|
||||
playerHero.playerInfo = this.md2Service.gameRoomService.currentPlayer();
|
||||
playerHero.playerInfo.isDisconnected = false;
|
||||
this.md2Service.playerHero = playerHero;
|
||||
this.md2Service.broadcastMyHeroInfo();
|
||||
//When fetch game info, if the hero level is changed, show the level up message
|
||||
if (playerHeroLevel && playerHeroLevel != playerHero.level) {
|
||||
//do i--
|
||||
for (let i = playerHero.level; i > playerHeroLevel; i--) {
|
||||
this.md2Service.msgBoxService.show(`Level Up Lv.${i}`, { text: 'Please do a skill level up!', icon: ADIcon.INFO });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.detectChanges();
|
||||
}
|
||||
break;
|
||||
@@ -158,10 +174,19 @@ export abstract class MD2Base {
|
||||
this.detectChanges();
|
||||
}
|
||||
break;
|
||||
case 'getGameInfo':
|
||||
|
||||
if (!this.isHeroDashboard) {
|
||||
|
||||
this.md2Service.broadcastGameInfo();
|
||||
}
|
||||
break;
|
||||
case 'sendJoinInfo':
|
||||
//When hero join the game, or reconnect to the game will receive this message
|
||||
if (this.isHeroDashboard && this.md2Service.playerHero) {
|
||||
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
|
||||
this.md2Service.broadcastService.broadcastMyHeroInfo();
|
||||
//Send fetch game info to the hero
|
||||
this.md2Service.broadcastFetchGameInfo();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -229,6 +254,41 @@ export abstract class MD2Base {
|
||||
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);
|
||||
}
|
||||
@@ -260,14 +320,14 @@ export abstract class MD2ComponentBase {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
imgUrl(imgPath: string) {
|
||||
return this.md2Service.stateService.imgUrl(imgPath);
|
||||
return this.md2Service.imgUrl(imgPath);
|
||||
}
|
||||
fileList(folderPath: string) {
|
||||
return this.md2Service.fileList(folderPath);
|
||||
}
|
||||
|
||||
iconHtml(icon: MD2Icon, cssClass = '') {
|
||||
return this.md2Service.stateService.iconHtml(icon, cssClass);
|
||||
return this.md2Service.iconHtml(icon, cssClass);
|
||||
}
|
||||
detectChanges() {
|
||||
if (!this.cdRef['destroyed']) {
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
<nb-card-body class="g-overflow-hidden">
|
||||
<div class="row form-group">
|
||||
<div class="col-md-5 g-height-700px">
|
||||
<md2-mob-stand-info [mob]="boss.info" [mode]="mode"></md2-mob-stand-info>
|
||||
<md2-mob-stand-info [mob]="boss" [mode]="mode"></md2-mob-stand-info>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
@@ -17,11 +17,11 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
|
||||
<md2-mob-attack-info [mob]="boss.info"></md2-mob-attack-info>
|
||||
<md2-mob-attack-info [mob]="boss"></md2-mob-attack-info>
|
||||
</div>
|
||||
<div class="col-md-8 MD2IconContainer-lg">
|
||||
|
||||
<md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
|
||||
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+4
-3
@@ -6,10 +6,11 @@ import { takeUntil } from 'rxjs/operators';
|
||||
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||
import { StateService } from '../../../../services/state.service';
|
||||
import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase } from '../../massive-darkness2.model';
|
||||
import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase, MobInfo } from '../../massive-darkness2.model';
|
||||
import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss';
|
||||
import { MD2ComponentBase } from '../../MD2Base';
|
||||
import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
|
||||
import { MD2MobInfo, MD2MobSkill } from '../../massive-darkness2.db.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-boss-activation',
|
||||
@@ -17,8 +18,8 @@ import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.com
|
||||
styleUrls: ['./boss-activation.component.scss']
|
||||
})
|
||||
export class BossActivationComponent implements OnInit {
|
||||
boss: IBossFight;
|
||||
bossAction: MobSkill;
|
||||
boss: MobInfo;
|
||||
bossAction: MD2MobSkill;
|
||||
currentAction: number;
|
||||
allActions: number;
|
||||
MobDlgType = MobDlgType;
|
||||
|
||||
@@ -3,34 +3,50 @@
|
||||
{{boss.name}}
|
||||
<button nbButton hero status="primary" (click)="activate()">Action</button>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<nb-card-body class="g-overflow-hidden">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<img src="{{boss.standUrl}}" class="w-100 g-max-height-80vh">
|
||||
<!-- <img src="{{boss.standUrl}}" class="w-100 bossStandImg"> -->
|
||||
|
||||
<md2-mob-stand-info [mob]="boss" [mode]="MobDlgType.PreView"></md2-mob-stand-info>
|
||||
<!-- HP and Mana Bars -->
|
||||
<div class="hero-stats-overlay">
|
||||
<div class="stat-bar-overlay hp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||
<span class="stat-value-overlay">{{boss.unitRemainHp}}/{{boss.hp}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay hp-fill-overlay"
|
||||
[style.width.%]="(boss.unitRemainHp / boss.hp) * 100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.unitRemainHp" minimum="0"
|
||||
<adj-number-input name="mob{{boss.name}}" [(ngModel)]="boss.unitRemainHp" minimum="0"
|
||||
class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
|
||||
</adj-number-input>
|
||||
<md2-mob-attack-info [mob]="boss.info">
|
||||
<md2-mob-attack-info [mob]="boss">
|
||||
</md2-mob-attack-info>
|
||||
<md2-mob-def-info [mob]="boss.info"></md2-mob-def-info>
|
||||
<md2-mob-def-info [mob]="boss"></md2-mob-def-info>
|
||||
</div>
|
||||
<div class="col-md-9 h6" *ngIf="boss.extraRules">
|
||||
<div [innerHtml]="boss.extraRules"></div>
|
||||
<div class="col-md-9 bossSpecialRules" *ngIf="boss.bossFightProfile.specialRules">
|
||||
<div [innerHtml]="boss.bossFightProfile.specialRules"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
|
||||
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
|
||||
<!--
|
||||
<button nbButton hero status="danger" size="small" (click)="attack(boss.info)">Attack It</button> -->
|
||||
<button nbButton hero status="danger" size="small" (click)="attack(boss)">Attack It</button> -->
|
||||
|
||||
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.info.combatSkill.skillName">
|
||||
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.combatSkill.skillName">
|
||||
</label>
|
||||
<label class="MD2Text" [innerHtml]="boss.info.combatInfo.skillDescription">
|
||||
<label class="MD2Text" [innerHtml]="boss.combatInfo.skillDescription">
|
||||
</label> -->
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,3 +2,136 @@ nb-card {
|
||||
height: 80vh;
|
||||
//width: 80vw;
|
||||
}
|
||||
.bossStandImg {
|
||||
max-height: 67vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
::ng-deep .bossSpecialRules {
|
||||
.MD2Icon {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
// HP and Mana Bars Overlay
|
||||
.hero-stats-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
z-index: 2;
|
||||
width: 95%;
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-bar-overlay {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-bar-label-overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
md2-icon {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.15rem;
|
||||
gap: 0.3rem;
|
||||
|
||||
md2-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-bar-overlay {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-fill-overlay {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
transition: width 0.5s ease-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
&.full-stat {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hp-fill-overlay {
|
||||
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||
}
|
||||
|
||||
.mp-fill-overlay {
|
||||
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import { SpawnMobDlgComponent } from '../mobs/spawn-mob-dlg/spawn-mob-dlg.compon
|
||||
styleUrls: ['./boss-fight.component.scss']
|
||||
})
|
||||
export class BossFightComponent extends MD2ComponentBase {
|
||||
|
||||
|
||||
MobDlgType = MobDlgType;
|
||||
MD2Icon = MD2Icon;
|
||||
public get boss() {
|
||||
return this.md2Service.info.boss;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class BossFightComponent extends MD2ComponentBase {
|
||||
super.ngOnInit();
|
||||
this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||
if (this.md2Service.info.isBossFight) {
|
||||
this.attack(this.boss.info);
|
||||
this.attack(this.boss);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -49,10 +49,14 @@ export class BossFightComponent extends MD2ComponentBase {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
activate() {
|
||||
this.boss.activating();
|
||||
this.md2Service.activateBoss();
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -61,7 +65,10 @@ export class BossFightComponent extends MD2ComponentBase {
|
||||
if (mobResult) {
|
||||
let attackDamage = mobResult.uiWounds;
|
||||
if (attackDamage) {
|
||||
this.boss.info.hp -= attackDamage;
|
||||
this.boss.unitRemainHp -= attackDamage;
|
||||
if (this.boss.unitRemainHp <= 0) {
|
||||
this.WIN();
|
||||
}
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,99 +8,6 @@ import { MobSkillType } from "../../massive-darkness2.model.boss";
|
||||
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 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 {
|
||||
abstract mobName: string;
|
||||
@@ -192,7 +99,7 @@ export class RMUndeadQueenFactory extends CoreGameRMFactory {
|
||||
}
|
||||
});
|
||||
}
|
||||
});;
|
||||
});
|
||||
}
|
||||
|
||||
this.mob.skills = [
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<nb-card status="info" size="large">
|
||||
<nb-card-header>
|
||||
<img src="{{md2Service.imgUrl('HeroIcon.png')}}" width="40px">
|
||||
<span class="ml-2 g-font-size-17">Initialize Game</span>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="form-group">
|
||||
<label class="label">Select Game Bundles:</label>
|
||||
<div class="form-group" *ngFor="let bundle of bundleOptions">
|
||||
<nb-checkbox [checked]="isBundleEnabled(bundle.value)" (checkedChange)="toggleBundle(bundle.value)">
|
||||
{{ bundle.label }}
|
||||
</nb-checkbox>
|
||||
</div>
|
||||
<small class="form-text text-muted" *ngIf="enabledBundles.length === 0">
|
||||
At least one bundle must be selected.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<nb-checkbox [(checked)]="enableMobSpecialRule">
|
||||
Enable Mob Special Rules
|
||||
</nb-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<nb-checkbox [(checked)]="enableHeroBetrayal">
|
||||
Enable Hero Betrayal
|
||||
</nb-checkbox>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
<nb-card-footer>
|
||||
<button class="float-right" nbButton hero status="warning" size="small" (click)="cancel()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="float-right mr-2" nbButton hero status="primary" size="small"
|
||||
[disabled]="enabledBundles.length === 0" (click)="submit()">
|
||||
Initialize
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
@@ -0,0 +1,12 @@
|
||||
nb-card {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
nb-checkbox {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { NbDialogRef } from '@nebular/theme';
|
||||
import { GameBundle } from '../massive-darkness2.db.model';
|
||||
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||
import { StringUtils } from '../../../utilities/string-utils';
|
||||
|
||||
export interface GameInitConfig {
|
||||
enabledBundles: GameBundle[];
|
||||
enableMobSpecialRule: boolean;
|
||||
enableHeroBetrayal: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-game-init-dlg',
|
||||
templateUrl: './game-init-dlg.component.html',
|
||||
styleUrls: ['./game-init-dlg.component.scss']
|
||||
})
|
||||
export class GameInitDlgComponent implements OnInit {
|
||||
GameBundle = GameBundle;
|
||||
|
||||
@Input() initialConfig: GameInitConfig;
|
||||
|
||||
enabledBundles: GameBundle[] = [GameBundle.CoreGame];
|
||||
enableMobSpecialRule: boolean = false;
|
||||
enableHeroBetrayal: boolean = false;
|
||||
|
||||
bundleOptions = [];
|
||||
|
||||
constructor(
|
||||
private dlgRef: NbDialogRef<GameInitDlgComponent>,
|
||||
public md2Service: MD2Service
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//For each GameBundle, create a new option
|
||||
this.bundleOptions = Object.values(GameBundle).filter(b => !isNaN(Number(b))).map(b => ({
|
||||
value: b as GameBundle, label: StringUtils.camelToTitle(GameBundle[b as GameBundle] as string)
|
||||
}));
|
||||
// Initialize from context if provided
|
||||
if (this.initialConfig) {
|
||||
this.enabledBundles = [...this.initialConfig.enabledBundles];
|
||||
this.enableMobSpecialRule = this.initialConfig.enableMobSpecialRule;
|
||||
this.enableHeroBetrayal = this.initialConfig.enableHeroBetrayal;
|
||||
}
|
||||
}
|
||||
|
||||
toggleBundle(bundle: GameBundle): void {
|
||||
const index = this.enabledBundles.indexOf(bundle);
|
||||
if (index > -1) {
|
||||
this.enabledBundles.splice(index, 1);
|
||||
} else {
|
||||
this.enabledBundles.push(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
isBundleEnabled(bundle: GameBundle): boolean {
|
||||
return this.enabledBundles.includes(bundle);
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
const config: GameInitConfig = {
|
||||
enabledBundles: this.enabledBundles,
|
||||
enableMobSpecialRule: this.enableMobSpecialRule,
|
||||
enableHeroBetrayal: this.enableHeroBetrayal
|
||||
};
|
||||
this.dlgRef.close(config);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dlgRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +126,10 @@
|
||||
</div>
|
||||
<div class="col-6 hero-skills-col">
|
||||
<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="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"
|
||||
[innerHTML]="hero.shadowSkillHtml"></div>
|
||||
</div>
|
||||
@@ -138,7 +139,7 @@
|
||||
<!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> -->
|
||||
|
||||
<!-- 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">
|
||||
<button nbButton hero class="action-btn" status="info" (click)="moveAction()"
|
||||
*ngIf="!showMoveAction">
|
||||
@@ -269,16 +270,24 @@
|
||||
*ngIf="hero.uiActivating">
|
||||
</adj-number-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="col-6" *ngIf="hero.class==HeroClass.Berserker">
|
||||
<adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
|
||||
title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage"
|
||||
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.class==HeroClass.Berserker">
|
||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||
</adj-number-input>
|
||||
|
||||
|
||||
<adj-number-input name="heroCorruption" [(ngModel)]="hero.corruptionToken" minimum="0"
|
||||
title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption"
|
||||
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.uiShowCorruptionToken">
|
||||
</div>
|
||||
<div class="col-6" *ngIf="hero.uiShowExtraToken">
|
||||
<adj-number-input name="heroExtraToken" [(ngModel)]="hero.extraToken" minimum="0"
|
||||
title="{{hero.uiExtraTokenHtml}} {{hero.uiExtraTokenName}}"
|
||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||
</adj-number-input>
|
||||
</div>
|
||||
<div class="col-6" *ngIf="hero.uiShowExtraToken2">
|
||||
<adj-number-input name="heroExtraToken2" [(ngModel)]="hero.extraToken2" minimum="0"
|
||||
title="{{hero.uiExtraTokenHtml2}} {{hero.uiExtraTokenName2}}"
|
||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||
</adj-number-input>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -810,3 +810,6 @@
|
||||
-ms-transform: rotateY(-180deg);
|
||||
transform: rotateY(-180deg);
|
||||
}
|
||||
::ng-deep .skill-content .MD2Icon {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { DebounceTimer } from '../../../utilities/timer-utils';
|
||||
import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model';
|
||||
import { MD2Base } from '../MD2Base';
|
||||
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
|
||||
import { SignalRService } from '../../../services/signal-r.service';
|
||||
import { NbToastrService } from '@nebular/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-hero-dashboard',
|
||||
@@ -45,7 +47,9 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
|
||||
}
|
||||
|
||||
heroUpdateDebounceTimer = new DebounceTimer(1000, () => { this.broadcastHeroInfo(); })
|
||||
heroUpdateDebounceTimer = new DebounceTimer(1000, () => {
|
||||
this.broadcastHeroInfo();
|
||||
})
|
||||
|
||||
classOptions: DropDownOption[] = [
|
||||
new DropDownOption(HeroClass.Berserker, 'Berserker'),
|
||||
@@ -55,6 +59,10 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
new DropDownOption(HeroClass.Wizard, 'Wizard'),
|
||||
new DropDownOption(HeroClass.Shaman, 'Shaman'),
|
||||
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[];
|
||||
heroProfiles: MD2HeroProfile[] = [];
|
||||
@@ -66,6 +74,15 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
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 {
|
||||
return this.heros[this.currentHeroIndex];
|
||||
}
|
||||
@@ -78,6 +95,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
protected route: ActivatedRoute,
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
private msgBoxService: MsgBoxService,
|
||||
private signalRService: SignalRService,
|
||||
private toastrService: NbToastrService
|
||||
) {
|
||||
super(md2Service, stateService, route, cdRef);
|
||||
this.isHeroDashboard = true;
|
||||
@@ -89,6 +108,16 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
|
||||
this.gameRoomService.gameRoomId = this.roomId;
|
||||
this.gameRoomService.joinGameRoom(this.roomId);
|
||||
//this.fetchGameInfo();
|
||||
this.signalRService.signalRMessageConnSubject.subscribe(state => {
|
||||
//fetchGameInfo is called in MD2Base.handleSignalRCallback sendJoinInfo message
|
||||
// if (state.status == 'connected') {
|
||||
// this.fetchGameInfo();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
override signalRInitialized() {
|
||||
@@ -99,8 +128,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
// }
|
||||
}
|
||||
initHero() {
|
||||
this.gameRoomService.gameRoomId = this.roomId;
|
||||
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) {
|
||||
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRConnectionId)) {
|
||||
|
||||
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
|
||||
.pipe(first()).subscribe(heroClass => {
|
||||
@@ -122,10 +150,11 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
});
|
||||
}
|
||||
}
|
||||
className: string;
|
||||
|
||||
|
||||
|
||||
initClassHeroList(heroClass: HeroClass) {
|
||||
this.heros = [];
|
||||
this.className = HeroClass[heroClass];
|
||||
this.selectedHeroClass = heroClass;
|
||||
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
|
||||
|
||||
@@ -139,7 +168,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
hp: heroProfile.hp,
|
||||
mp: heroProfile.mana,
|
||||
skillHtml: heroProfile.skillHtml,
|
||||
shadowSkillHtml: heroProfile.shadowSkillHtml,
|
||||
shadowSkillHtml: heroProfile.shadowSkillHtml.replace("<p>", '<p>' + this.iconHtml(MD2Icon.Shadow) + ' : '),
|
||||
class: heroClass
|
||||
});
|
||||
heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg');
|
||||
@@ -156,12 +185,18 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
selectCurrentHero() {
|
||||
if (this.currentSelectingHero) {
|
||||
this.md2Service.playerJoin(this.currentSelectingHero);
|
||||
this.md2Service.broadcastMyHeroInfo();
|
||||
this.isSelectingHero = false;
|
||||
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() {
|
||||
this.currentHeroIndex++;
|
||||
if (this.currentHeroIndex >= this.heros.length) {
|
||||
@@ -178,8 +213,12 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
this.detectChanges();
|
||||
}
|
||||
|
||||
fetchGameInfo() {
|
||||
this.md2Service.broadcastFetchGameInfo();
|
||||
}
|
||||
|
||||
broadcastHeroInfo() {
|
||||
this.md2Service.broadcastService.broadcastMyHeroInfo();
|
||||
this.md2Service.broadcastMyHeroInfo();
|
||||
this.heroUpdateDebounceTimer.clearOut();
|
||||
}
|
||||
increaseRage() {
|
||||
@@ -188,8 +227,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
}
|
||||
}
|
||||
openDoor() {
|
||||
this.md2Service.broadcastService.broadcastHeroAction('openDoor');
|
||||
this.showMoveAction = false;
|
||||
this.md2Service.broadcastHeroAction('openDoor');
|
||||
//this.showMoveAction = false;
|
||||
this.detectChanges();
|
||||
}
|
||||
moveAction() {
|
||||
@@ -213,7 +252,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.md2Service.broadcastService.broadcastHeroAction(action);
|
||||
this.md2Service.broadcastHeroAction(action);
|
||||
this.reduceAction();
|
||||
}
|
||||
reduceAction() {
|
||||
@@ -250,7 +289,6 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
this.hero.uiActivating = false;
|
||||
}
|
||||
this.broadcastHeroInfo();
|
||||
|
||||
}
|
||||
endActivation() {
|
||||
if (this.hero.remainActions > 0) {
|
||||
|
||||
@@ -57,25 +57,44 @@
|
||||
({{md2Service.highestPlayerLevel}})</label>
|
||||
</div> -->
|
||||
<div class="col-12" *ngFor="let hero of md2Service.heros">
|
||||
<label class='label mr-1'>{{hero.playerInfo.name}} ({{heroClassName(hero)}} -
|
||||
<label class='label mr-1'
|
||||
(click)="adjustHeroValue(hero,'remainActions')">{{hero.playerInfo.name}}
|
||||
({{heroClassName(hero)}} -
|
||||
{{hero.name}})</label>
|
||||
<span class="badge badge-primary mr-1">Lv.:{{hero.level}}</span>
|
||||
<span class="badge badge-primary mr-1">HP: {{hero.hp}}/{{hero.hpMaximum}}</span>
|
||||
<span class="badge badge-primary mr-1">Mana: {{hero.mp}}/{{hero.mpMaximum}}</span>
|
||||
<span class="badge badge-success mr-1">Exp: {{hero.exp}}</span>
|
||||
<span class="badge badge-danger mr-1" *ngIf="hero.fireToken">
|
||||
<md2-icon icon="fire" size="sm"></md2-icon> {{hero.fireToken}}
|
||||
<span class="badge badge-primary mr-1"
|
||||
(click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span>
|
||||
<span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'hp')">HP:
|
||||
{{hero.hp}}/{{hero.hpMaximum}}</span>
|
||||
<span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'mp')">Mana:
|
||||
{{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 class="badge badge-info mr-1" *ngIf="hero.frozenToken">
|
||||
<md2-icon icon="frozen" size="sm"></md2-icon>{{hero.frozenToken}}
|
||||
<span class="badge mr-1" *ngIf="hero.frozenToken">
|
||||
<md2-icon [icon]="MD2Icon.FrozenToken" size="sm"></md2-icon>{{hero.frozenToken}}
|
||||
</span>
|
||||
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Actions:
|
||||
|
||||
<span class="badge mr-1" *ngIf="hero.extraToken">
|
||||
<span [innerHtml]="hero.uiExtraTokenHtml"></span> {{hero.extraToken}}
|
||||
</span>
|
||||
<span class="badge mr-1" *ngIf="hero.extraToken2">
|
||||
<span [innerHtml]="hero.uiExtraTokenHtml2"></span> {{hero.extraToken2}}
|
||||
</span>
|
||||
|
||||
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0"
|
||||
(click)="adjustHeroValue(hero,'remainActions')">Actions:
|
||||
{{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-warning mr-1"
|
||||
*ngIf="hero.playerInfo.isDisconnected">Disconnected</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>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.badge {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { MsgBoxService } from '../../services/msg-box.service';
|
||||
import { ArrayUtils } from '../../utilities/array-utils';
|
||||
import { ObjectUtils } from '../../utilities/object-utils';
|
||||
import { first, map, take, takeUntil } from 'rxjs/operators';
|
||||
import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType } from './massive-darkness2.model';
|
||||
import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType, MD2Icon } from './massive-darkness2.model';
|
||||
import { MD2Service } from '../../services/MD2/md2.service';
|
||||
import { GameRoomService } from '../../services/game-room.service';
|
||||
import { MD2Base } from './MD2Base';
|
||||
@@ -17,6 +17,8 @@ import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.compone
|
||||
import { BossMicheal } from './massive-darkness2.model.boss';
|
||||
import { MD2InitService } from '../../services/MD2/md2-init.service';
|
||||
import { NumberUtils } from '../../utilities/number-utils';
|
||||
import { GameInitDlgComponent, GameInitConfig } from './game-init-dlg/game-init-dlg.component';
|
||||
import { GameBundle } from './massive-darkness2.db.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-massive-darkness2',
|
||||
@@ -25,6 +27,7 @@ import { NumberUtils } from '../../utilities/number-utils';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
||||
MD2Icon = MD2Icon;
|
||||
HeroClass = HeroClass;
|
||||
constructor(
|
||||
private fileService: FileService,
|
||||
@@ -42,20 +45,100 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
|
||||
this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||
this.showEnemyPhaseAction(0);
|
||||
});
|
||||
this.showGameInitDialog();
|
||||
}
|
||||
|
||||
private showGameInitDialog() {
|
||||
// Only show dialog if game hasn't been initialized yet
|
||||
if (!this.md2Service.initialized) {
|
||||
const dialogRef = this.msgBoxService.dlgService.open(GameInitDlgComponent, {
|
||||
closeOnBackdropClick: false,
|
||||
closeOnEsc: false,
|
||||
context: {
|
||||
initialConfig: {
|
||||
enabledBundles: this.md2Service.info.enabledBundles?.length > 0
|
||||
? this.md2Service.info.enabledBundles
|
||||
: [GameBundle.CoreGame],
|
||||
enableMobSpecialRule: this.md2Service.info.enableMobSpecialRule || false,
|
||||
enableHeroBetrayal: this.md2Service.info.enableHeroBetrayal || false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.onClose.pipe(first()).subscribe((config: GameInitConfig) => {
|
||||
if (config) {
|
||||
this.initGameBundles(config);
|
||||
} else {
|
||||
// User cancelled, use defaults
|
||||
this.initGameBundles({
|
||||
enabledBundles: [GameBundle.CoreGame],
|
||||
enableMobSpecialRule: false,
|
||||
enableHeroBetrayal: false
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Game already initialized, just use current settings
|
||||
this.initGameBundles({
|
||||
enabledBundles: this.md2Service.info.enabledBundles,
|
||||
enableMobSpecialRule: this.md2Service.info.enableMobSpecialRule,
|
||||
enableHeroBetrayal: this.md2Service.info.enableHeroBetrayal
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private initGameBundles(config: GameInitConfig) {
|
||||
this.md2Service.info.enabledBundles = config.enabledBundles;
|
||||
this.md2Service.info.enableMobSpecialRule = config.enableMobSpecialRule;
|
||||
this.md2Service.info.enableHeroBetrayal = config.enableHeroBetrayal;
|
||||
|
||||
if (this.md2Service.initialized == false) {
|
||||
this.gameRoomService.createGameRoom('MD2');
|
||||
this.md2Service.initialized = true;
|
||||
}
|
||||
|
||||
this.initService.initMobDecks();
|
||||
this.initService.initTreasureBag();
|
||||
}
|
||||
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 (![false, null, undefined].includes(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() {
|
||||
|
||||
if (this.md2Service.initialized == false) {
|
||||
this.gameRoomService.createGameRoom('MD2');
|
||||
this.md2Service.initialized = true;
|
||||
}
|
||||
|
||||
let initUrl = `${window.location.origin}/games/MD2_Hero/${this.gameRoomService.gameRoomId}`;
|
||||
this.msgBoxService.show("Scan To Join", { text: `<img src='${this.qrCodeService.QRCodeUrl(initUrl, 5)}'><br><a href='${initUrl}' target='_blank'>Link</a>` });
|
||||
}
|
||||
@@ -145,16 +228,21 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
||||
this.md2Service.heros.forEach(hero => {
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public get round(): string {
|
||||
if (this.md2Service.info.isBossFight) {
|
||||
return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.boss.rounds)} Round`;
|
||||
return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.bossRound)} Round`;
|
||||
} else {
|
||||
return NumberUtils.Ordinal(this.md2Service.info.round) + ' Round';
|
||||
}
|
||||
|
||||
@@ -8,14 +8,19 @@ export enum MobSkillTarget {
|
||||
HighestHp = 70,
|
||||
HighestMp = 80,
|
||||
LowestLevel = 90,
|
||||
MostCorruption = 200,
|
||||
LeastCorruption = 201
|
||||
HighestLevel = 100,
|
||||
MostExtraToken = 200,
|
||||
LeastExtraToken,
|
||||
MostExtraToken2,
|
||||
LeastExtraToken2
|
||||
}
|
||||
|
||||
export enum GameBundle {
|
||||
CoreGame,
|
||||
HeavenFallen,
|
||||
Zombiecide
|
||||
Zombicide,
|
||||
ZombicideWhiteDeath,
|
||||
DarkBringerPack
|
||||
}
|
||||
export interface MD2MobInfo {
|
||||
id: string;
|
||||
@@ -26,6 +31,7 @@ export interface MD2MobInfo {
|
||||
minionImgUrl: string;
|
||||
mobLevelInfos: MD2MobLevelInfo[];
|
||||
skills: MD2MobSkill[];
|
||||
bossFightProfile?: BossFightProfile;
|
||||
}
|
||||
|
||||
export interface MD2MobLevelInfo {
|
||||
@@ -44,7 +50,10 @@ export interface MD2MobLevelInfo {
|
||||
defenceInfo: MD2DiceSet;
|
||||
}
|
||||
|
||||
export interface MD2MobSkill {
|
||||
export class MD2MobSkill {
|
||||
constructor(config: Partial<MD2MobSkill> = {}) {
|
||||
Object.assign(this, config);
|
||||
}
|
||||
id: string;
|
||||
seq: number;
|
||||
level: number;
|
||||
@@ -67,4 +76,30 @@ export interface MD2DiceSet {
|
||||
blue: number | null;
|
||||
green: number | null;
|
||||
black: number | null;
|
||||
}
|
||||
export interface BossFightProfile {
|
||||
mobInfoId: string;
|
||||
id: string;
|
||||
prerequisite: string;
|
||||
objective: string;
|
||||
specialRules: string;
|
||||
extraTokenName: string;
|
||||
extraTokenHtml: string;
|
||||
extraTokenName2: string;
|
||||
extraTokenHtml2: string;
|
||||
phaseBuffs: BossFightPhaseBuff[];
|
||||
}
|
||||
|
||||
export interface BossFightPhaseBuff {
|
||||
id: string;
|
||||
bossFightProfileId: string;
|
||||
phase: number;
|
||||
extraAction: number;
|
||||
extraAttackDice: MD2DiceSet;
|
||||
extraDefenceDice: MD2DiceSet;
|
||||
extraHp: number;
|
||||
extraTokenCount: number;
|
||||
extraTokenCount2: number;
|
||||
enableExtraBuffDescription: boolean;
|
||||
extraBuffDescription: string;
|
||||
}
|
||||
@@ -6,49 +6,65 @@ import { StringUtils } from "../../utilities/string-utils";
|
||||
import { GamePlayer } from "../games.model";
|
||||
import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model";
|
||||
import { MobSkill } from "./massive-darkness2.model.boss";
|
||||
import { MobSkillTarget } from "./massive-darkness2.db.model";
|
||||
export class MD2Logic {
|
||||
public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: AttackTarget) {
|
||||
public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget) {
|
||||
return this.getTargetHerosByFilter(heros, targetType, true)[0];
|
||||
}
|
||||
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) {
|
||||
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget, onlyOne: boolean = false) {
|
||||
let beenAttackedHero = [] as MD2HeroInfo[];
|
||||
switch (targetType) {
|
||||
case AttackTarget.LeastHp:
|
||||
case MobSkillTarget.LeastHp:
|
||||
let lowestHp = Math.min(...heros.map(h => h.hp));
|
||||
beenAttackedHero = heros.filter(h => h.hp == lowestHp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||
break;
|
||||
case AttackTarget.LeastMp:
|
||||
case MobSkillTarget.LeastMp:
|
||||
let lowestMp = Math.min(...heros.map(h => h.mp));
|
||||
beenAttackedHero = heros.filter(h => h.hp == lowestMp);
|
||||
beenAttackedHero = heros.filter(h => h.mp == lowestMp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||
break;
|
||||
|
||||
case AttackTarget.HighestHp:
|
||||
case MobSkillTarget.HighestHp:
|
||||
let highestHp = Math.max(...heros.map(h => h.hp));
|
||||
beenAttackedHero = heros.filter(h => h.hp == highestHp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
||||
break;
|
||||
case AttackTarget.HighestMp:
|
||||
case MobSkillTarget.HighestMp:
|
||||
let highestMp = Math.max(...heros.map(h => h.mp));
|
||||
beenAttackedHero = heros.filter(h => h.mp == highestMp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
|
||||
break;
|
||||
case AttackTarget.LowestLevel:
|
||||
let lowestLevel = Math.max(...heros.map(h => h.level));
|
||||
case MobSkillTarget.LowestLevel:
|
||||
let lowestLevel = Math.min(...heros.map(h => h.level));
|
||||
beenAttackedHero = heros.filter(h => h.level == lowestLevel);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||
break;
|
||||
case AttackTarget.LeastCorruption:
|
||||
case MobSkillTarget.HighestLevel:
|
||||
let highestLevel = Math.max(...heros.map(h => h.level));
|
||||
beenAttackedHero = heros.filter(h => h.level == highestLevel);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||
break;
|
||||
case MobSkillTarget.LeastExtraToken:
|
||||
|
||||
let leastCor = Math.min(...heros.map(h => h.corruptionToken));
|
||||
beenAttackedHero = heros.filter(h => h.corruptionToken == leastCor);
|
||||
let leastExtraToken = Math.min(...heros.map(h => h.extraToken));
|
||||
beenAttackedHero = heros.filter(h => h.extraToken == leastExtraToken);
|
||||
break;
|
||||
case AttackTarget.MostCorruption:
|
||||
let mostCor = Math.max(...heros.map(h => h.corruptionToken));
|
||||
beenAttackedHero = heros.filter(h => h.corruptionToken == mostCor);
|
||||
case MobSkillTarget.MostExtraToken:
|
||||
let mostExtraToken = Math.max(...heros.map(h => h.extraToken));
|
||||
beenAttackedHero = heros.filter(h => h.extraToken == mostExtraToken);
|
||||
break;
|
||||
case AttackTarget.Random:
|
||||
|
||||
case MobSkillTarget.LeastExtraToken2:
|
||||
|
||||
let leastExtraToken2 = Math.min(...heros.map(h => h.extraToken2));
|
||||
beenAttackedHero = heros.filter(h => h.extraToken2 == leastExtraToken2);
|
||||
break;
|
||||
case MobSkillTarget.MostExtraToken2:
|
||||
let mostExtraToken2 = Math.max(...heros.map(h => h.extraToken2));
|
||||
beenAttackedHero = heros.filter(h => h.extraToken2 == mostExtraToken2);
|
||||
break;
|
||||
case MobSkillTarget.Random:
|
||||
default:
|
||||
beenAttackedHero = [heros[Math.round(Math.random() * (heros.length - 1))]];
|
||||
//this.otherAttackTarget = 'Just act like normal.';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { StringUtils } from "../../utilities/string-utils"
|
||||
import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
|
||||
import { TreasureType, AttackInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
|
||||
import { RollingBlackDice } from "./massive-darkness2.model.dice"
|
||||
import { MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model"
|
||||
import { MD2DiceSet, MD2MobSkill, MobSkillTarget } from "./massive-darkness2.db.model"
|
||||
|
||||
|
||||
export enum MobSkillType {
|
||||
@@ -15,6 +15,7 @@ export enum MobSkillType {
|
||||
Combat,
|
||||
Passive,
|
||||
ConditionalSkill,
|
||||
OtherWiseSkill,
|
||||
ActiveSkill,
|
||||
MeleeAttack = 15,
|
||||
RangeAttack,
|
||||
@@ -52,6 +53,7 @@ export interface IBossFight {
|
||||
imgUrl: string
|
||||
standUrl: string
|
||||
extraRules: string
|
||||
md2Service: MD2Service
|
||||
activating(): boolean
|
||||
prepareForBossFight(): void
|
||||
darknessPhase(): void
|
||||
@@ -72,7 +74,7 @@ export abstract class BossFight implements IBossFight {
|
||||
extraRules: string
|
||||
protected subscription: Subscription
|
||||
|
||||
constructor(protected md2Service: MD2Service) {
|
||||
constructor(public md2Service: MD2Service) {
|
||||
this.rounds = 1;
|
||||
}
|
||||
activating(): boolean {
|
||||
@@ -104,26 +106,26 @@ export abstract class BossFight implements IBossFight {
|
||||
|
||||
}
|
||||
export class BossMicheal extends BossFight {
|
||||
constructor(protected md2Service: MD2Service) {
|
||||
constructor(public md2Service: 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.imgUrl = md2Service.stateService.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
|
||||
this.standUrl = md2Service.stateService.imgUrl('/Boss/Michael.png');
|
||||
this.imgUrl = md2Service.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
|
||||
this.standUrl = md2Service.imgUrl('/Boss/Michael.png');
|
||||
|
||||
this.info = new MobInfo({
|
||||
description: this.name,
|
||||
type: MobType.Boss,
|
||||
hpPerHero: 15,
|
||||
level: 10,
|
||||
imageUrl: md2Service.stateService.imgUrl('/Boss/Michael.png')
|
||||
imageUrl: md2Service.imgUrl('/Boss/Michael.png')
|
||||
});
|
||||
if (!this.info.skills) {
|
||||
this.info.skills = [];
|
||||
}
|
||||
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).`,
|
||||
type: MobSkillType.Combat,
|
||||
skillRoll: 1
|
||||
@@ -134,9 +136,9 @@ export class BossMicheal extends BossFight {
|
||||
this.actionBlackDice = 2;
|
||||
|
||||
this.extraRules = `Archangel Michael can’t be the target of any attack, skill, ability or take Wounds until there are no Corruption tokens in the whole Tile.<br><br>` +
|
||||
`Any Hero on a Zone with a ${this.corruptionTokenHtml} may spend 1 action to remove it. Each time a Hero removes a ${this.corruptionTokenHtml} from a Zone they must roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.BlackDice)}.` +
|
||||
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)} the Hero takes 1 Wound.<br>If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} place 1 ${this.corruptionTokenHtml} on their Dashboard.<br>` +
|
||||
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
|
||||
`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.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.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
|
||||
}
|
||||
activatedTimes: number
|
||||
acted: number
|
||||
@@ -154,14 +156,14 @@ export class BossMicheal extends BossFight {
|
||||
|
||||
bossAction(): Observable<boolean> {
|
||||
|
||||
let actionResult = new RollingBlackDice().roll(this.actionBlackDice);
|
||||
let actionResult = new RollingBlackDice().roll(2);
|
||||
let actionHtml = '';
|
||||
let beenAttackedHero = [] as MD2HeroInfo[];
|
||||
let bossAction: MobSkill;
|
||||
switch (actionResult.claws) {
|
||||
case 0:
|
||||
//Justice From Above
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.MostCorruption, true);
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.MostExtraToken, true);
|
||||
bossAction = new MobSkill(
|
||||
{
|
||||
name: 'Justice From Above',
|
||||
@@ -171,7 +173,7 @@ export class BossMicheal extends BossFight {
|
||||
break;
|
||||
case 1:
|
||||
//Lance Dash
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true);
|
||||
bossAction = new MobSkill({
|
||||
name: 'Lance Dash',
|
||||
description:
|
||||
@@ -191,12 +193,13 @@ export class BossMicheal extends BossFight {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||
return null;
|
||||
//return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||
|
||||
}
|
||||
prepareForBossFight(): void {
|
||||
this.md2Service.heros.forEach(hero => {
|
||||
hero.uiShowCorruptionToken = true;
|
||||
hero.uiShowExtraToken = true;
|
||||
});
|
||||
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
||||
text: `<h6>Place ${this.md2Service.heros.length * 2} ${this.corruptionTokenHtml} on the Corruption Stone Zones (Shadow
|
||||
@@ -231,26 +234,26 @@ export class BossMicheal extends BossFight {
|
||||
|
||||
export class BossReaper extends BossFight {
|
||||
|
||||
constructor(protected md2Service: MD2Service) {
|
||||
constructor(public md2Service: 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.imgUrl = md2Service.stateService.imgUrl('/Boss/The Reaper.jpg');
|
||||
this.standUrl = md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png');
|
||||
this.imgUrl = md2Service.imgUrl('/Boss/The Reaper.jpg');
|
||||
this.standUrl = md2Service.imgUrl('/Boss/The Reaper-Stand.png');
|
||||
|
||||
this.info = new MobInfo({
|
||||
description: this.name,
|
||||
type: MobType.Boss,
|
||||
hpPerHero: 25,
|
||||
level: 10,
|
||||
imageUrl: md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png')
|
||||
imageUrl: md2Service.imgUrl('/Boss/The Reaper-Stand.png')
|
||||
});
|
||||
if (!this.info.skills) {
|
||||
this.info.skills = [];
|
||||
}
|
||||
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,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill);
|
||||
@@ -280,7 +283,7 @@ export class BossReaper extends BossFight {
|
||||
switch (actionResult.claws) {
|
||||
case 0:
|
||||
//Justice From Above
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastMp, true);
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastMp, true);
|
||||
bossAction = new MobSkill(
|
||||
{
|
||||
name: 'Soul Drain',
|
||||
@@ -290,7 +293,7 @@ export class BossReaper extends BossFight {
|
||||
break;
|
||||
case 1:
|
||||
//Lance Dash
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
|
||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true);
|
||||
bossAction = new MobSkill({
|
||||
name: 'Time Ticking',
|
||||
description:
|
||||
@@ -303,15 +306,16 @@ export class BossReaper extends BossFight {
|
||||
name: 'Death Is Coming',
|
||||
description:
|
||||
`Place The Reaper in the central Zone.<br>` +
|
||||
`Roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.YellowDice)}. Remove ${this.timeTokenHtml} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled from both <b>Hourglass Zone</b>.<br>` +
|
||||
`Each Hero discards ${this.md2Service.stateService.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled.`
|
||||
`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.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.iconHtml(MD2Icon.Melee)} rolled.`
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||
return null;
|
||||
//return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||
}
|
||||
prepareForBossFight(): void {
|
||||
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ObjectUtils } from "../../utilities/object-utils";
|
||||
import { GamePlayer } from "../games.model";
|
||||
import { MD2Clone } from "./factorys/md2-clone";
|
||||
import { MobSkill } from "./massive-darkness2.model.boss";
|
||||
import { MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";
|
||||
import { BossFightProfile, GameBundle, MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
|
||||
export enum MobDlgType {
|
||||
@@ -37,6 +37,10 @@ export enum HeroClass {
|
||||
Shaman,
|
||||
Paladin,
|
||||
Druid,
|
||||
Necromancer,
|
||||
Monk,
|
||||
Thinker,
|
||||
Bard
|
||||
}
|
||||
export enum MobType {
|
||||
Mob,
|
||||
@@ -303,6 +307,8 @@ export class MobInfo implements IDrawingItem {
|
||||
this.drawingWeight = 1;
|
||||
this.unitRemainHp = config.hp
|
||||
}
|
||||
id: string;
|
||||
from: GameBundle;
|
||||
type: MobType = MobType.Mob;
|
||||
imageUrl: string
|
||||
standUrl: string
|
||||
@@ -329,12 +335,15 @@ export class MobInfo implements IDrawingItem {
|
||||
fireToken: number = 0;
|
||||
frozenToken: number = 0;
|
||||
corruptionToken: number = 0;
|
||||
uiExtraTokenCount: number = 0;
|
||||
uiExtraTokenCount2: number = 0;
|
||||
uiWounds: number;
|
||||
uiFireTokens: number;
|
||||
uiFrozenTokens: number;
|
||||
uiCorruptionTokens: number;
|
||||
uiAttackedBy: string;
|
||||
extraRule: string;
|
||||
bossFightProfile?: BossFightProfile;
|
||||
get identifyName(): string {
|
||||
return `${this.name}_${this.level}`;
|
||||
}
|
||||
@@ -377,7 +386,6 @@ export class MobInfo implements IDrawingItem {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public actionSubject: Subject<string>;
|
||||
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
|
||||
}
|
||||
export interface MD2HeroProfile {
|
||||
@@ -406,7 +414,8 @@ export class MD2HeroInfo {
|
||||
level: number = 1;
|
||||
fireToken: number = 0;
|
||||
frozenToken: number = 0;
|
||||
corruptionToken: number = 0;
|
||||
extraToken: number = 0;
|
||||
extraToken2: number = 0;
|
||||
playerInfo: GamePlayer;
|
||||
imgUrl: string;
|
||||
skillHtml: string;
|
||||
@@ -414,10 +423,15 @@ export class MD2HeroInfo {
|
||||
remainActions: number = 3;
|
||||
rage: number = 0;
|
||||
uiActivating = false;
|
||||
uiShowCorruptionToken = false;
|
||||
uiExtraTokenHtml: string = '';
|
||||
uiExtraTokenHtml2: string = '';
|
||||
uiExtraTokenName: string = '';
|
||||
uiExtraTokenName2: string = '';
|
||||
uiShowExtraToken = false;
|
||||
uiShowExtraToken2 = false;
|
||||
uiBossFight = false;
|
||||
uiShowAttackBtn = false;
|
||||
|
||||
uiBetrayal = false;
|
||||
public get heroFullName(): string {
|
||||
return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
|
||||
}
|
||||
|
||||
+9
-2
@@ -18,10 +18,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||
MD2Icon = MD2Icon;
|
||||
public gridData: GridDataResult = { data: [], total: 0 };
|
||||
private allData: MD2HeroProfile[] = [];
|
||||
private lastSelectedHero: MD2HeroProfile;
|
||||
private lastSelectedHeroClass: HeroClass;
|
||||
public gridState: State = {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
take: 1000,
|
||||
sort: [{
|
||||
field: 'title',
|
||||
dir: 'asc'
|
||||
@@ -77,6 +78,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||
|
||||
public addHandler(): void {
|
||||
const editorData = { heroClass: this.lastSelectedHeroClass || HeroClass.Berserker } as MD2HeroProfile;
|
||||
if (this.lastSelectedHero) {
|
||||
editorData.heroClass = this.lastSelectedHero.heroClass;
|
||||
editorData.skillHtml = this.lastSelectedHero.skillHtml;
|
||||
editorData.shadowSkillHtml = this.lastSelectedHero.shadowSkillHtml;
|
||||
}
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: 'Add New Hero Profile',
|
||||
content: MD2HeroProfileEditorComponent,
|
||||
@@ -95,6 +101,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||
|
||||
dialogRef.result.subscribe((result: MD2HeroProfile) => {
|
||||
if (result) {
|
||||
this.lastSelectedHero = result;
|
||||
this.lastSelectedHeroClass = result.heroClass;
|
||||
this.loadData();
|
||||
}
|
||||
@@ -108,7 +115,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||
width: '90vw',
|
||||
height: 600
|
||||
});
|
||||
|
||||
this.lastSelectedHero = dataItem;
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = false;
|
||||
editor.data = JSON.parse(JSON.stringify(dataItem));
|
||||
|
||||
@@ -6,7 +6,6 @@ import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||
import { MD2Icon } from '../massive-darkness2.model';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
||||
@@ -59,7 +58,6 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
|
||||
|
||||
constructor(
|
||||
private msgBoxService: MsgBoxService,
|
||||
private md2StateService: MD2StateService,
|
||||
private dialogService: DialogService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {
|
||||
@@ -147,46 +145,6 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
|
||||
}).result.subscribe((html: string) => {
|
||||
if (html && this.editor) {
|
||||
this.insertAfterSelection(html, true);
|
||||
return;
|
||||
// Insert the HTML content at the current cursor position
|
||||
// Use ProseMirror's dispatch method to insert content at cursor
|
||||
const view = this.editor.view;
|
||||
if (view && view.state) {
|
||||
try {
|
||||
// Parse the HTML content
|
||||
const dom = document.createElement('div');
|
||||
dom.innerHTML = html;
|
||||
|
||||
// Use ProseMirror's DOMParser to parse HTML
|
||||
// Access it from the view's state
|
||||
const pmState = view.state;
|
||||
|
||||
// Use the editor's exec method or manual dispatch
|
||||
if ((this.editor as any).exec) {
|
||||
// Try using exec with 'insertHTML' command if available
|
||||
try {
|
||||
(this.editor as any).exec('insertHTML', html);
|
||||
} catch (e) {
|
||||
throw new Error('insertHTML not supported');
|
||||
}
|
||||
} else {
|
||||
throw new Error('exec method not available');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error inserting HTML:', e);
|
||||
// Fallback: append to the end
|
||||
const currentValue = this.editor.value || '';
|
||||
const newValue = currentValue + ' ' + html;
|
||||
this.value = newValue;
|
||||
this.onChange(newValue);
|
||||
}
|
||||
} else {
|
||||
// Fallback: append to the end
|
||||
const currentValue = this.editor.value || '';
|
||||
const newValue = currentValue + ' ' + html;
|
||||
this.value = newValue;
|
||||
this.onChange(newValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -447,7 +405,7 @@ export const rbjTagNodeSpec: NodeSpec = {
|
||||
"span",
|
||||
{
|
||||
class: classValue,
|
||||
// "rbj-tag-id": node.attrs["rbj-tag-id"],
|
||||
"md2-icon": md2IconText,
|
||||
// "tag-marker": node.attrs["tag-marker"],
|
||||
// "tag-value": node.attrs["tag-value"],
|
||||
// "tag-preview": node.attrs["tag-preview"],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NbDialogRef } from '@nebular/theme';
|
||||
import { MD2Icon } from '../massive-darkness2.model';
|
||||
import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
import { DialogRef } from '@progress/kendo-angular-dialog';
|
||||
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'md2-icon-picker-dlg',
|
||||
@@ -37,7 +37,7 @@ import { DialogRef } from '@progress/kendo-angular-dialog';
|
||||
gap: 10px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
width:400px;
|
||||
//width:400px;
|
||||
}
|
||||
.icon-item {
|
||||
cursor: pointer;
|
||||
@@ -63,7 +63,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private dlgRef: DialogRef,
|
||||
private md2StateService: MD2StateService
|
||||
private md2Service: MD2Service
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -72,7 +72,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
|
||||
this.iconList.push({
|
||||
icon: icon,
|
||||
name: MD2Icon[icon],
|
||||
html: this.md2StateService.iconHtml(icon)
|
||||
html: this.md2Service.iconHtml(icon)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { MD2Icon, TreasureType } from '../massive-darkness2.model';
|
||||
import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'md2-icon',
|
||||
@@ -44,7 +44,7 @@ export class MD2IconComponent implements OnInit {
|
||||
}
|
||||
@Input() size: string = 'sm';
|
||||
iconName: string;
|
||||
constructor(private md2StateService: MD2StateService) { }
|
||||
constructor(private md2Service: MD2Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
@@ -52,10 +52,10 @@ export class MD2IconComponent implements OnInit {
|
||||
private initIcon(icon: MD2Icon): void {
|
||||
if (icon < MD2Icon.TreasureToken) {
|
||||
this.isImageIcon = false;
|
||||
this.iconHtml = this.md2StateService.iconHtml(icon);
|
||||
this.iconHtml = this.md2Service.iconHtml(icon);
|
||||
} else {
|
||||
this.isImageIcon = true;
|
||||
this.imgUrl = this.md2StateService.iconHtml(icon);
|
||||
this.imgUrl = this.md2Service.iconHtml(icon);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<div class="k-dialog-content">
|
||||
<kendo-tabstrip tabPosition="top">
|
||||
<kendo-tabstrip-tab title="Boss Fight Info" [selected]="true">
|
||||
<ng-template kendoTabContent>
|
||||
<form class="k-form" style="padding: 5px;">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="k-label g-cursor-pointer"
|
||||
(click)="showInsertMD2Icon('extraTokenHtml')">Extra Token Name<span
|
||||
class="tokenIconDiv" [innerHTML]="model.extraTokenHtml"></span></label>
|
||||
<input kendoTextBox [(ngModel)]="model.extraTokenName" name="extraTokenName"
|
||||
class="k-input" placeholder="Enter extra token name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="k-label g-cursor-pointer"
|
||||
(click)="showInsertMD2Icon('extraTokenHtml2')">Extra Token Name 2<span
|
||||
class="tokenIconDiv" [innerHTML]="model.extraTokenHtml2"></span></label>
|
||||
<input kendoTextBox [(ngModel)]="model.extraTokenName2" name="extraTokenName2"
|
||||
class="k-input" placeholder="Enter extra token name 2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Prerequisite</label>
|
||||
<md2-html-editor [(ngModel)]="model.prerequisite" name="prerequisite"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Objective</label>
|
||||
<md2-html-editor [(ngModel)]="model.objective" name="objective"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Special Rules</label>
|
||||
<md2-html-editor [(ngModel)]="model.specialRules" name="specialRules"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</ng-template>
|
||||
</kendo-tabstrip-tab>
|
||||
|
||||
<kendo-tabstrip-tab title="Phase Buffs">
|
||||
<ng-template kendoTabContent>
|
||||
<div style="padding: 20px;">
|
||||
<div class="phase-buffs-toolbar" style="margin-bottom: 10px;">
|
||||
<button kendoButton (click)="addPhaseBuffHandler()" [primary]="true">
|
||||
<span class="k-icon k-i-plus"></span> Add Phase Buff
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<kendo-grid #phaseBuffsGrid [data]="phaseBuffsData" [loading]="isLoading"
|
||||
[pageSize]="phaseBuffsState.take" [skip]="phaseBuffsState.skip" [sortable]="true"
|
||||
[filterable]="true" [pageable]="true" [height]="400" (remove)="removePhaseBuffHandler($event)"
|
||||
(dataStateChange)="phaseBuffsState = $event; loadPhaseBuffs()">
|
||||
|
||||
<kendo-grid-column field="phase" title="Phase" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.phase }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="extraAction" title="Extra Action" [width]="100">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.extraAction }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="extraHp" title="Extra HP" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.extraHp }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="extraTokenCount" title="Extra Token Count" [width]="120">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.extraTokenCount }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="extraTokenCount2" title="Extra Token Count 2" [width]="140">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.extraTokenCount2 }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="extraBuffDescription" title="Extra Buff Description" [width]="300">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
<div *ngIf="dataItem.enableExtraBuffDescription"
|
||||
[innerHTML]="dataItem.extraBuffDescription">
|
||||
</div>
|
||||
<span *ngIf="!dataItem.enableExtraBuffDescription">-</span>
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-command-column title="Actions" [width]="133">
|
||||
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem"
|
||||
let-rowIndex="rowIndex">
|
||||
<button kendoButton [primary]="true"
|
||||
(click)="editPhaseBuffHandler(dataItem)">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
||||
</div>
|
||||
</ng-template>
|
||||
</kendo-tabstrip-tab>
|
||||
</kendo-tabstrip>
|
||||
</div>
|
||||
|
||||
<kendo-dialog-actions>
|
||||
<button kendoButton (click)="close()">Cancel</button>
|
||||
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||
{{ processing ? 'Saving...' : 'Save' }}
|
||||
</button>
|
||||
</kendo-dialog-actions>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Boss Fight Editor styles
|
||||
.tokenIconDiv {
|
||||
margin-left: 5px;
|
||||
font-size: 30px;
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase, DialogService } from '@progress/kendo-angular-dialog';
|
||||
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||
import { State } from '@progress/kendo-data-query';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { BossFightProfile, BossFightPhaseBuff } from '../../massive-darkness2.db.model';
|
||||
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||
import { MD2BossFightProfileService, MD2PhaseBuffService } from '../../service/massive-darkness2.service';
|
||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||
import { MD2PhaseBuffEditorComponent } from '../md2-phase-buff-editor/md2-phase-buff-editor.component';
|
||||
import { MD2IconPickerDlgComponent } from '../../md2-html-editor/md2-icon-picker-dlg.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-boss-fight-editor',
|
||||
templateUrl: './md2-boss-fight-editor.component.html',
|
||||
styleUrls: ['./md2-boss-fight-editor.component.scss']
|
||||
})
|
||||
export class MD2BossFightEditorComponent extends DialogContentBase implements OnInit {
|
||||
@Input() public data: BossFightProfile;
|
||||
@Input() public mobInfoId: string;
|
||||
@ViewChild('phaseBuffsGrid') phaseBuffsGrid: GridComponent;
|
||||
|
||||
public model: BossFightProfile;
|
||||
public phaseBuffs: BossFightPhaseBuff[] = [];
|
||||
public phaseBuffsData: GridDataResult = { data: [], total: 0 };
|
||||
public phaseBuffsState: State = {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
sort: [],
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: []
|
||||
}
|
||||
};
|
||||
public isLoading: boolean = false;
|
||||
public processing: boolean = false;
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private dialogService: DialogService,
|
||||
private bossFightProfileService: MD2BossFightProfileService,
|
||||
private phaseBuffService: MD2PhaseBuffService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
super(dialog);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initializeModel();
|
||||
}
|
||||
|
||||
public initializeModel(): void {
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
|
||||
prerequisite: this.data?.prerequisite || '',
|
||||
objective: this.data?.objective || '',
|
||||
specialRules: this.data?.specialRules || '',
|
||||
extraTokenName: this.data?.extraTokenName || '',
|
||||
extraTokenHtml: this.data?.extraTokenHtml || '',
|
||||
extraTokenName2: this.data?.extraTokenName2 || '',
|
||||
extraTokenHtml2: this.data?.extraTokenHtml2 || '',
|
||||
phaseBuffs: this.data?.phaseBuffs || []
|
||||
};
|
||||
|
||||
this.phaseBuffs = this.model.phaseBuffs || [];
|
||||
this.loadPhaseBuffs();
|
||||
}
|
||||
|
||||
public loadPhaseBuffs(): void {
|
||||
this.phaseBuffsData = {
|
||||
data: this.phaseBuffs.sort((a, b) => a.phase - b.phase),
|
||||
total: this.phaseBuffs.length
|
||||
};
|
||||
}
|
||||
|
||||
public addPhaseBuffHandler(): void {
|
||||
if (!this.model) return;
|
||||
|
||||
const lastPhaseBuff = this.phaseBuffs.length > 0
|
||||
? this.phaseBuffs.reduce((prev, current) => (prev.phase > current.phase) ? prev : current)
|
||||
: null;
|
||||
|
||||
const nextPhase = lastPhaseBuff ? lastPhaseBuff.phase + 1 : 1;
|
||||
|
||||
const newPhaseBuff: BossFightPhaseBuff = {
|
||||
id: this.generatePhaseBuffId(),
|
||||
bossFightProfileId: this.model.id,
|
||||
phase: nextPhase,
|
||||
extraAction: 0,
|
||||
extraAttackDice: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
extraDefenceDice: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
extraHp: 0,
|
||||
extraTokenCount: 0,
|
||||
extraTokenCount2: 0,
|
||||
enableExtraBuffDescription: false,
|
||||
extraBuffDescription: ''
|
||||
};
|
||||
|
||||
this.openPhaseBuffEditor(newPhaseBuff, true);
|
||||
}
|
||||
|
||||
public editPhaseBuffHandler(dataItem: BossFightPhaseBuff): void {
|
||||
if (!this.model) return;
|
||||
|
||||
const phaseBuffCopy: BossFightPhaseBuff = JSON.parse(JSON.stringify(dataItem));
|
||||
this.openPhaseBuffEditor(phaseBuffCopy, false);
|
||||
}
|
||||
|
||||
private openPhaseBuffEditor(phaseBuff: BossFightPhaseBuff, isNew: boolean): void {
|
||||
if (!this.model) return;
|
||||
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: isNew ? 'Add New Phase Buff' : 'Edit Phase Buff',
|
||||
content: MD2PhaseBuffEditorComponent,
|
||||
width: '80vw',
|
||||
height: 700
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance as MD2PhaseBuffEditorComponent;
|
||||
editor.isAdding = isNew;
|
||||
editor.data = phaseBuff;
|
||||
editor.bossFightProfileId = this.model.id;
|
||||
|
||||
setTimeout(() => {
|
||||
editor.initializeModel();
|
||||
}, 0);
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result && typeof result === 'object' && 'id' in result) {
|
||||
this.handlePhaseBuffSaved(result as BossFightPhaseBuff, isNew);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handlePhaseBuffSaved(result: BossFightPhaseBuff, isNew: boolean): void {
|
||||
if (!this.model) return;
|
||||
|
||||
if (isNew) {
|
||||
if (!this.phaseBuffs) {
|
||||
this.phaseBuffs = [];
|
||||
}
|
||||
this.phaseBuffs.push(result);
|
||||
} else {
|
||||
const index = this.phaseBuffs.findIndex(p => p.id === result.id);
|
||||
if (index !== -1) {
|
||||
this.phaseBuffs[index] = result;
|
||||
}
|
||||
}
|
||||
|
||||
this.model.phaseBuffs = this.phaseBuffs;
|
||||
this.loadPhaseBuffs();
|
||||
}
|
||||
|
||||
public removePhaseBuffHandler({ dataItem }: { dataItem: BossFightPhaseBuff }): void {
|
||||
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||
if (answer === true) {
|
||||
this.isLoading = true;
|
||||
this.phaseBuffService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||
const index = this.phaseBuffs.findIndex(p => p.id === dataItem.id);
|
||||
if (index !== -1) {
|
||||
this.phaseBuffs.splice(index, 1);
|
||||
this.model.phaseBuffs = this.phaseBuffs;
|
||||
this.loadPhaseBuffs();
|
||||
}
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private generatePhaseBuffId(): string {
|
||||
return 'phasebuff_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (!this.processing && this.model) {
|
||||
this.processing = true;
|
||||
|
||||
this.model.phaseBuffs = this.phaseBuffs;
|
||||
this.model.mobInfoId = this.mobInfoId || this.model.mobInfoId;
|
||||
|
||||
this.bossFightProfileService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
|
||||
this.processing = false;
|
||||
this.dialog.close(result);
|
||||
}, error => {
|
||||
this.processing = false;
|
||||
console.error('Error saving boss fight profile:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
showInsertMD2Icon(attributeName: string) {
|
||||
this.dialogService.open({
|
||||
title: 'Select MD2 Icon',
|
||||
content: MD2IconPickerDlgComponent,
|
||||
width: '800px',
|
||||
height: 600
|
||||
}).result.subscribe((html: string) => {
|
||||
if (html && typeof html === 'string') {
|
||||
this.model[attributeName] = html;
|
||||
} else {
|
||||
this.model[attributeName] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
public get isValid(): boolean {
|
||||
if (!this.model) {
|
||||
return false;
|
||||
}
|
||||
return this.model.mobInfoId !== '';
|
||||
}
|
||||
}
|
||||
+1
@@ -183,5 +183,6 @@
|
||||
</div>
|
||||
|
||||
<kendo-dialog-actions>
|
||||
<button kendoButton *ngIf="mobInfo?.type === MobType.Boss" (click)="editBossFightHandler()">Edit Boss Fight</button>
|
||||
<button kendoButton [primary]="true" (click)="close()">Close</button>
|
||||
</kendo-dialog-actions>
|
||||
+68
-7
@@ -3,13 +3,15 @@ import { DialogRef, DialogContentBase, DialogService } from '@progress/kendo-ang
|
||||
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||
import { State } from '@progress/kendo-data-query';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill, MobSkillTarget, GameBundle } from '../../massive-darkness2.db.model';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill, MobSkillTarget, GameBundle, BossFightProfile } from '../../massive-darkness2.db.model';
|
||||
import { MobType } from '../../massive-darkness2.model';
|
||||
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||
import { MD2MobLevelInfoService, MD2MobSkillService } from '../../service/massive-darkness2.service';
|
||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||
import { MD2MobSkillEditorComponent } from '../md2-mob-skill-editor/md2-mob-skill-editor.component';
|
||||
import { MD2MobLevelEditorComponent } from '../md2-mob-level-editor/md2-mob-level-editor.component';
|
||||
import { MD2BossFightEditorComponent } from '../md2-boss-fight-editor/md2-boss-fight-editor.component';
|
||||
import { MD2MobInfoService } from '../../service/massive-darkness2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-mob-info-detail',
|
||||
@@ -18,6 +20,7 @@ import { MD2MobLevelEditorComponent } from '../md2-mob-level-editor/md2-mob-leve
|
||||
})
|
||||
export class MD2MobInfoDetailComponent extends DialogContentBase implements OnInit {
|
||||
MobSkillType = MobSkillType;
|
||||
MobType = MobType;
|
||||
@Input() public mobInfo: MD2MobInfo;
|
||||
@ViewChild('levelsGrid') levelsGrid: GridComponent;
|
||||
@ViewChild('skillsGrid') skillsGrid: GridComponent;
|
||||
@@ -54,10 +57,10 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
private dialogService: DialogService,
|
||||
private mobLevelInfoService: MD2MobLevelInfoService,
|
||||
private mobSkillService: MD2MobSkillService,
|
||||
private mobInfoService: MD2MobInfoService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
super(dialog);
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -68,6 +71,7 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
}
|
||||
this.loadSkills();
|
||||
}
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
private initializeEnums(): void {
|
||||
@@ -140,6 +144,9 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
attackInfo: lastLevel.attackInfo
|
||||
? { ...lastLevel.attackInfo }
|
||||
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
alterAttackInfo: lastLevel.alterAttackInfo
|
||||
? { ...lastLevel.alterAttackInfo }
|
||||
: null,
|
||||
defenceInfo: lastLevel.defenceInfo
|
||||
? { ...lastLevel.defenceInfo }
|
||||
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
|
||||
@@ -158,6 +165,7 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
hpPerHero: 0,
|
||||
actions: actions,
|
||||
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 }
|
||||
};
|
||||
}
|
||||
@@ -186,14 +194,20 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
case 1:
|
||||
newLevel.rewardTokens = 2;
|
||||
newLevel.fixedRareTreasure = 1;
|
||||
newLevel.fixedEpicTreasure = 0;
|
||||
newLevel.fixedLegendTreasure = 0;
|
||||
break;
|
||||
case 3:
|
||||
newLevel.rewardTokens = 2;
|
||||
newLevel.fixedRareTreasure = 0;
|
||||
newLevel.fixedEpicTreasure = 1;
|
||||
newLevel.fixedLegendTreasure = 0;
|
||||
break;
|
||||
case 5:
|
||||
newLevel.rewardTokens = 0;
|
||||
newLevel.fixedRareTreasure = 0;
|
||||
newLevel.fixedEpicTreasure = 3;
|
||||
newLevel.fixedLegendTreasure = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -309,11 +323,11 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
level: level,
|
||||
mobInfoId: this.mobInfo.id,
|
||||
type: type,
|
||||
skillTarget: MobSkillTarget.Random,
|
||||
clawRoll: 0,
|
||||
skillRoll: 1,
|
||||
name: 'Basic Skill',
|
||||
skillCondition: '',
|
||||
skillTarget: lastLevel?.skillTarget || MobSkillTarget.Random,
|
||||
clawRoll: lastLevel?.clawRoll || 0,
|
||||
skillRoll: lastLevel?.skillRoll || 1,
|
||||
name: lastLevel?.name || 'Combat Skill',
|
||||
skillCondition: lastLevel?.skillCondition || '',
|
||||
description: lastLevel?.description || ''
|
||||
};
|
||||
|
||||
@@ -424,6 +438,53 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
|
||||
return GameBundle[bundle] || '';
|
||||
}
|
||||
|
||||
public editBossFightHandler(): void {
|
||||
if (!this.mobInfo || this.mobInfo.type !== MobType.Boss) return;
|
||||
|
||||
// Ensure bossFightProfile is initialized
|
||||
if (!this.mobInfo.bossFightProfile) {
|
||||
this.mobInfo.bossFightProfile = {
|
||||
id: 'bossfight_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||
mobInfoId: this.mobInfo.id,
|
||||
prerequisite: '',
|
||||
objective: '',
|
||||
specialRules: '',
|
||||
extraTokenName: '',
|
||||
extraTokenHtml: '',
|
||||
extraTokenName2: '',
|
||||
extraTokenHtml2: '',
|
||||
phaseBuffs: []
|
||||
};
|
||||
}
|
||||
|
||||
// Create a copy of the boss fight profile for editing
|
||||
const bossFightProfileCopy: BossFightProfile = JSON.parse(JSON.stringify(this.mobInfo.bossFightProfile));
|
||||
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: `Edit Boss Fight: ${this.mobInfo.name}`,
|
||||
content: MD2BossFightEditorComponent,
|
||||
width: '90vw',
|
||||
height: '90vh'
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance as MD2BossFightEditorComponent;
|
||||
editor.data = bossFightProfileCopy;
|
||||
editor.mobInfoId = this.mobInfo.id;
|
||||
|
||||
setTimeout(() => {
|
||||
editor.initializeModel();
|
||||
}, 0);
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result && typeof result === 'object' && 'id' in result) {
|
||||
// Reload mob info to get updated boss fight profile
|
||||
this.mobInfoService.getById(this.mobInfo.id).pipe(first()).subscribe(mobInfo => {
|
||||
this.mobInfo = mobInfo;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close(true);
|
||||
}
|
||||
|
||||
+36
-4
@@ -2,7 +2,7 @@ import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular
|
||||
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2MobInfo, GameBundle } from '../../massive-darkness2.db.model';
|
||||
import { MD2MobInfo, GameBundle, BossFightProfile } from '../../massive-darkness2.db.model';
|
||||
import { MobType } from '../../massive-darkness2.model';
|
||||
import { MD2MobInfoService } from '../../service/massive-darkness2.service';
|
||||
|
||||
@@ -48,9 +48,15 @@ export class MD2MobInfoEditorComponent extends DialogContentBase implements OnIn
|
||||
leaderImgUrl: this.data?.leaderImgUrl || '',
|
||||
minionImgUrl: this.data?.minionImgUrl || '',
|
||||
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||
skills: this.data?.skills || []
|
||||
skills: this.data?.skills || [],
|
||||
bossFightProfile: this.data?.bossFightProfile
|
||||
};
|
||||
|
||||
// Initialize bossFightProfile for Boss type mobs if not already set
|
||||
if (typeValue === MobType.Boss && !this.model.bossFightProfile) {
|
||||
this.model.bossFightProfile = this.createDefaultBossFightProfile(this.model.id);
|
||||
}
|
||||
|
||||
// Set selected objects for dropdowns
|
||||
this.selectedMobType = this.mobTypes.find(t => t.value === typeValue) || this.mobTypes[0] || null;
|
||||
this.selectedGameBundle = this.gameBundles.find(b => b.value === fromValue) || this.gameBundles[0] || null;
|
||||
@@ -58,6 +64,21 @@ export class MD2MobInfoEditorComponent extends DialogContentBase implements OnIn
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
private createDefaultBossFightProfile(mobInfoId: string): BossFightProfile {
|
||||
return {
|
||||
id: 'bossfight_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||
mobInfoId: mobInfoId || '',
|
||||
prerequisite: '',
|
||||
objective: '',
|
||||
specialRules: '',
|
||||
extraTokenName: '',
|
||||
extraTokenHtml: '',
|
||||
extraTokenName2: '',
|
||||
extraTokenHtml2: '',
|
||||
phaseBuffs: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private initializeEnums(): void {
|
||||
// Initialize MobType options
|
||||
@@ -85,13 +106,24 @@ export class MD2MobInfoEditorComponent extends DialogContentBase implements OnIn
|
||||
if (this.model.name && !this.processing) {
|
||||
this.processing = true;
|
||||
|
||||
const mobType = this.selectedMobType?.value ?? MobType.Mob;
|
||||
|
||||
// Ensure bossFightProfile is initialized for Boss type
|
||||
let bossFightProfile = this.model.bossFightProfile;
|
||||
if (mobType === MobType.Boss && !bossFightProfile) {
|
||||
bossFightProfile = this.createDefaultBossFightProfile(this.model.id);
|
||||
} else if (mobType !== MobType.Boss) {
|
||||
bossFightProfile = undefined;
|
||||
}
|
||||
|
||||
// Extract enum values from selected objects
|
||||
const mobInfo: MD2MobInfo = {
|
||||
...this.model,
|
||||
type: this.selectedMobType?.value ?? MobType.Mob,
|
||||
type: mobType,
|
||||
from: this.selectedGameBundle?.value ?? GameBundle.CoreGame,
|
||||
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||
skills: this.data?.skills || []
|
||||
skills: this.data?.skills || [],
|
||||
bossFightProfile: bossFightProfile
|
||||
};
|
||||
|
||||
this.mobInfoService.createOrUpdate(mobInfo).pipe(first()).subscribe(result => {
|
||||
|
||||
+9
-3
@@ -11,7 +11,8 @@
|
||||
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
|
||||
[group]="gridState.group" [filter]="gridState.filter" [sort]="gridState.sort" [sortable]="true"
|
||||
[filterable]="true" [pageable]="true" [selectable]="true" [groupable]="true"
|
||||
(dataStateChange)="gridState = $event; processGridData()">
|
||||
(dataStateChange)="gridState = $event; processGridData()" (edit)="editHandler($event)"
|
||||
(remove)="removeHandler($event)" (add)="addHandler()">
|
||||
|
||||
<kendo-grid-toolbar>
|
||||
<button kendoGridAddCommand>Add new</button>
|
||||
@@ -20,9 +21,14 @@
|
||||
<kendo-grid-column field="name" title="Name" [width]="200">
|
||||
</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>
|
||||
{{ 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>
|
||||
</kendo-grid-column>
|
||||
|
||||
|
||||
+73
@@ -119,6 +119,22 @@
|
||||
<div class="col-md-12">
|
||||
<h5>Attack Info</h5>
|
||||
</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="form-group">
|
||||
<label class="k-label">
|
||||
@@ -157,6 +173,63 @@
|
||||
</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>
|
||||
</div>
|
||||
|
||||
|
||||
+24
-3
@@ -5,6 +5,7 @@ import { MD2MobLevelInfo } from '../../massive-darkness2.db.model';
|
||||
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||
import { MD2MobLevelInfoService } from '../../service/massive-darkness2.service';
|
||||
import { MD2Icon, MobType } from '../../massive-darkness2.model';
|
||||
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-mob-level-editor',
|
||||
@@ -20,21 +21,35 @@ export class MD2MobLevelEditorComponent extends DialogContentBase implements OnI
|
||||
@Input() public mobInfoId: string;
|
||||
@Input() public isAdding: boolean = false;
|
||||
|
||||
public selectedAttackType: { value: MobSkillType; text: string } | null = null;
|
||||
public model: MD2MobLevelInfo;
|
||||
public processing: boolean = false;
|
||||
|
||||
public attackTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||
public selectedAlterAttackType: { value: MobSkillType; text: string } | null = null;
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private mobLevelInfoService: MD2MobLevelInfoService,
|
||||
private cdr: ChangeDetectorRef
|
||||
private cdr: ChangeDetectorRef,
|
||||
private md2Service: MD2Service
|
||||
) {
|
||||
super(dialog);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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 {
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
@@ -50,6 +65,9 @@ export class MD2MobLevelEditorComponent extends DialogContentBase implements OnI
|
||||
attackInfo: this.data?.attackInfo
|
||||
? { ...this.data.attackInfo }
|
||||
: { 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
|
||||
? { ...this.data.defenceInfo }
|
||||
: { 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) {
|
||||
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.processing = false;
|
||||
this.dialog.close(result);
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<div class="k-dialog-content">
|
||||
<form #form="ngForm" class="k-form">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Phase *</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.phase" name="phase" [min]="1" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Extra Action</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraAction" name="extraAction" [min]="0" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">
|
||||
<md2-icon [icon]="MD2Icon.HP"></md2-icon>Extra HP</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraHp" name="extraHp" [min]="0" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Extra Token Count</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraTokenCount" name="extraTokenCount" [min]="0"
|
||||
[decimals]="0" [format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Extra Token Count 2</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraTokenCount2" name="extraTokenCount2" [min]="0"
|
||||
[decimals]="0" [format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Enable Extra Buff Description</label>
|
||||
<input type="checkbox" [(ngModel)]="model.enableExtraBuffDescription" name="enableExtraBuffDescription" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="model.enableExtraBuffDescription">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Extra Buff Description</label>
|
||||
<textarea [(ngModel)]="model.extraBuffDescription" name="extraBuffDescription" rows="4" class="k-input" style="width: 100%;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h5>Extra Attack Dice</h5>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">
|
||||
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Attack Type</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedAttackType" name="extraAttackDice.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.extraAttackDice.yellow" name="extraAttackDice.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.extraAttackDice.orange" name="extraAttackDice.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.extraAttackDice.red" name="extraAttackDice.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.extraAttackDice.black" name="extraAttackDice.black"
|
||||
[min]="0" [decimals]="0" [format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h5>Extra Defence Dice</h5>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">
|
||||
<md2-icon [icon]="MD2Icon.Defense"></md2-icon>Defence Type</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedDefenceType" name="extraDefenceDice.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.BlueDice"></md2-icon>Blue Dice</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraDefenceDice.blue" name="extraDefenceDice.blue"
|
||||
[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.GreenDice"></md2-icon>Green Dice</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.extraDefenceDice.green" name="extraDefenceDice.green"
|
||||
[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.extraDefenceDice.black" name="extraDefenceDice.black"
|
||||
[min]="0" [decimals]="0" [format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kendo-dialog-actions>
|
||||
<button kendoButton (click)="close()">Cancel</button>
|
||||
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||
{{ processing ? 'Saving...' : 'Save' }}
|
||||
</button>
|
||||
</kendo-dialog-actions>
|
||||
+1
@@ -0,0 +1 @@
|
||||
// Phase Buff Editor styles
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { BossFightPhaseBuff, MD2DiceSet } from '../../massive-darkness2.db.model';
|
||||
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||
import { MD2PhaseBuffService } from '../../service/massive-darkness2.service';
|
||||
import { MD2Icon } from '../../massive-darkness2.model';
|
||||
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-phase-buff-editor',
|
||||
templateUrl: './md2-phase-buff-editor.component.html',
|
||||
styleUrls: ['./md2-phase-buff-editor.component.scss']
|
||||
})
|
||||
export class MD2PhaseBuffEditorComponent extends DialogContentBase implements OnInit {
|
||||
MD2Icon = MD2Icon;
|
||||
MobSkillType = MobSkillType;
|
||||
@Input() public data: BossFightPhaseBuff;
|
||||
@Input() public bossFightProfileId: string;
|
||||
@Input() public isAdding: boolean = false;
|
||||
@ViewChild('form') form: NgForm;
|
||||
|
||||
public model: BossFightPhaseBuff;
|
||||
public processing: boolean = false;
|
||||
public attackTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||
public selectedAttackType: { value: MobSkillType; text: string } | null = null;
|
||||
public selectedDefenceType: { value: MobSkillType; text: string } | null = null;
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private phaseBuffService: MD2PhaseBuffService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private md2Service: MD2Service
|
||||
) {
|
||||
super(dialog);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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' },
|
||||
{ value: MobSkillType.Defense, text: this.md2Service.iconHtml(MD2Icon.Defense) + ' Defense' }
|
||||
];
|
||||
this.selectedAttackType = this.attackTypes.find(t => t.value === this.model.extraAttackDice?.type) || this.attackTypes[0] || null;
|
||||
this.selectedDefenceType = this.attackTypes.find(t => t.value === this.model.extraDefenceDice?.type) || this.attackTypes[4] || null;
|
||||
}
|
||||
|
||||
public initializeModel(): void {
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
bossFightProfileId: this.bossFightProfileId || this.data?.bossFightProfileId || '',
|
||||
phase: this.data?.phase ?? 1,
|
||||
extraAction: this.data?.extraAction ?? 0,
|
||||
extraAttackDice: this.data?.extraAttackDice
|
||||
? { ...this.data.extraAttackDice }
|
||||
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
extraDefenceDice: this.data?.extraDefenceDice
|
||||
? { ...this.data.extraDefenceDice }
|
||||
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
extraHp: this.data?.extraHp ?? 0,
|
||||
extraTokenCount: this.data?.extraTokenCount ?? 0,
|
||||
extraTokenCount2: this.data?.extraTokenCount2 ?? 0,
|
||||
enableExtraBuffDescription: this.data?.enableExtraBuffDescription ?? false,
|
||||
extraBuffDescription: this.data?.extraBuffDescription || ''
|
||||
};
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (!this.processing) {
|
||||
this.processing = true;
|
||||
|
||||
// Ensure required objects exist
|
||||
if (!this.model.extraAttackDice) {
|
||||
this.model.extraAttackDice = { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||
}
|
||||
if (!this.model.extraDefenceDice) {
|
||||
this.model.extraDefenceDice = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||
}
|
||||
|
||||
this.model.extraAttackDice.type = this.selectedAttackType?.value ?? MobSkillType.Attack;
|
||||
this.model.extraDefenceDice.type = this.selectedDefenceType?.value ?? MobSkillType.Defense;
|
||||
this.model.bossFightProfileId = this.bossFightProfileId || this.model.bossFightProfileId;
|
||||
|
||||
this.phaseBuffService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
|
||||
this.processing = false;
|
||||
this.dialog.close(result);
|
||||
}, error => {
|
||||
this.processing = false;
|
||||
console.error('Error saving phase buff:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
if (!this.model) {
|
||||
return false;
|
||||
}
|
||||
return this.model.phase > 0 && this.model.bossFightProfileId !== '';
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -20,13 +20,12 @@
|
||||
<md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md">
|
||||
</md2-icon>
|
||||
</label> -->
|
||||
<div *ngFor="let skill of mob.skills" class=" g-brd-bottom--dashed g-brd-gray-light-v2">
|
||||
<div *ngIf="skill.uiDisplay">
|
||||
<ng-container *ngFor="let skill of mob.skills">
|
||||
<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'>
|
||||
{{MobSkillType[skill.type]}} {{skill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon>
|
||||
</label>
|
||||
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="skill.description"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
+4
-2
@@ -22,12 +22,13 @@ export class MobCombatInfoComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
@Input() mode: MobDlgType = MobDlgType.PreView;
|
||||
showSkill: boolean = false;
|
||||
showAllSkill: boolean = false;
|
||||
showBlackDice: boolean
|
||||
skillTriggerHtml: string = '';
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.showAllSkill = [MobDlgType.PreView, MobDlgType.Dashboard].includes(this.mode);
|
||||
if (this.mob.skills && this.mob.skills.length > 0) {
|
||||
this.mob.skills.forEach(element => {
|
||||
switch (this.mode) {
|
||||
@@ -38,7 +39,8 @@ export class MobCombatInfoComponent implements OnInit {
|
||||
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Defense].includes(element.type);
|
||||
break;
|
||||
case MobDlgType.PreView:
|
||||
element.uiDisplay = true;
|
||||
case MobDlgType.Dashboard:
|
||||
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Attack, MobSkillType.Defense].includes(element.type);
|
||||
break;
|
||||
case MobDlgType.Spawn:
|
||||
element.uiDisplay = false;
|
||||
|
||||
+18
-8
@@ -8,8 +8,8 @@
|
||||
<div class="pl-2 col-md-6" *ngIf="mob.mobAmount">
|
||||
|
||||
<ng-container>
|
||||
<label class='label g-text-nowrap'>Alive Units <b
|
||||
class="MD2text g-font-size-18">{{mob.mobAmount}}</b></label><br>
|
||||
<label class='label g-text-nowrap'>Minions <b
|
||||
class="MD2text g-font-size-18">{{mob.mobAmount-1}}</b></label><br>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -34,11 +34,21 @@
|
||||
|
||||
|
||||
<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"
|
||||
title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
|
||||
(hitMaximum)="adjustMobHp(1)">
|
||||
</adj-number-input>
|
||||
<adj-number-input name="mob{{mob.name}}" [ngModel]="mob.unitRemainHp" [maximum]="mob.hp" minimum="1"
|
||||
title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
|
||||
(hitMaximum)="adjustMobHp(1)">
|
||||
</adj-number-input>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -50,8 +60,8 @@
|
||||
<md2-mob-attack-info [mob]="mob" [mode]="mode">
|
||||
</md2-mob-attack-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>
|
||||
<div *ngIf="mob.extraRule">
|
||||
<md2-mob-combat-info *ngIf="!hideCombatInfo" [mob]="mob" [mode]="mode"></md2-mob-combat-info>
|
||||
<div *ngIf="!hideCombatInfo && mob.extraRule">
|
||||
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,7 +12,7 @@ import { MD2ComponentBase } from '../../MD2Base';
|
||||
styleUrls: ['./mob-detail-info.component.scss']
|
||||
})
|
||||
export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
|
||||
|
||||
MobDlgType = MobDlgType;
|
||||
MD2Icon = MD2Icon;
|
||||
private _mob: MobInfo;
|
||||
public get mob(): MobInfo {
|
||||
@@ -33,6 +33,10 @@ export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
|
||||
return this.mode == MobDlgType.Spawn;
|
||||
}
|
||||
|
||||
public get hideCombatInfo(): boolean {
|
||||
return this.mode == MobDlgType.Dashboard;
|
||||
}
|
||||
|
||||
|
||||
showAttackingInfo: boolean = false;
|
||||
@Input("showAttackingInfo")
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
<img class="g-width-95x img-thumbnail mobBg" src="{{imgUrl('/Mobs/BG.png')}}" />
|
||||
<img class="mobImg roamingMonster" src="{{getMobImageUrl(mob)}}" (click)="showMobImage(mob)" *ngIf="!isMob" />
|
||||
<div *ngIf="isMob">
|
||||
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)" />
|
||||
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" />
|
||||
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)"
|
||||
[ngClass]="{'noMinions': mob.mobAmount==1}" />
|
||||
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" *ngIf="mob.mobAmount>1" />
|
||||
|
||||
</div>
|
||||
|
||||
<!-- HP and Mana Bars -->
|
||||
<div class="hero-stats-overlay">
|
||||
<div class="stat-bar-overlay hp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||
<span class="stat-value-overlay">{{mob.unitRemainHp}}/{{mob.hp}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay hp-fill-overlay" [style.width.%]="(mob.unitRemainHp / mob.hp) * 100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,23 +1,153 @@
|
||||
.mobImg {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
object-fit: contain;
|
||||
&.roamingMonster {
|
||||
width: 95%;
|
||||
max-height: 80%;
|
||||
left: 0;
|
||||
}
|
||||
&.mobLeader {
|
||||
z-index: 3;
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
&.noMinions {
|
||||
width: 95%;
|
||||
max-height: 90%;
|
||||
}
|
||||
}
|
||||
&.mobMinion {
|
||||
width: 60%;
|
||||
max-height: 80%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.mobBg {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// HP and Mana Bars Overlay
|
||||
.hero-stats-overlay {
|
||||
position: relative;
|
||||
bottom: 60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
z-index: 1;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.stat-bar-overlay {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-bar-label-overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
md2-icon {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.15rem;
|
||||
gap: 0.3rem;
|
||||
|
||||
md2-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-bar-overlay {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-fill-overlay {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
transition: width 0.5s ease-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
&.full-stat {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hp-fill-overlay {
|
||||
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||
}
|
||||
|
||||
.mp-fill-overlay {
|
||||
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
<div class="row no-gutters">
|
||||
<div class="col-12 col-md-8">
|
||||
<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 class=" col-12 col-md-4">
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ import { StateService } from '../../../services/state.service';
|
||||
import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
|
||||
import { NumberUtils } from '../../../utilities/number-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 { MD2Base, MD2ComponentBase } from '../MD2Base';
|
||||
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
|
||||
import { GameBundle } from '../massive-darkness2.db.model';
|
||||
|
||||
@Component({
|
||||
selector: 'md2-mobs',
|
||||
@@ -89,20 +89,24 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
||||
}
|
||||
|
||||
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.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`)) :
|
||||
this.md2Service.allMobInfos.map(f => new DropDownOption(f.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`));
|
||||
mobOptions = mobOptions.sort((a, b) => a.value1.localeCompare(b.value1));
|
||||
|
||||
this.msgBoxService.showInputbox('Spawn', '',
|
||||
{
|
||||
inputType: 'dropdown', dropDownOptions: mobOptions,
|
||||
buttons: ADButtons.YesNoCancel,
|
||||
confirmButtonText: 'Spawn',
|
||||
cancelButtonText: 'Random'
|
||||
}).pipe(first()).subscribe(mobName => {
|
||||
}).pipe(first()).subscribe(mobId => {
|
||||
|
||||
|
||||
if (mobName || mobName === false) {
|
||||
if (mobId || mobId === false) {
|
||||
|
||||
if (!mobName) { mobName = null; }
|
||||
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobName);
|
||||
if (!mobId) { mobId = null; }
|
||||
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobId);
|
||||
let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`;
|
||||
let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
|
||||
let mob = result.exitingMob == null ? result.mob : result.exitingMob;
|
||||
@@ -120,7 +124,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
||||
|
||||
afterSpawn() {
|
||||
this.cdRef.detectChanges();
|
||||
this.md2Service.broadcastService.broadcastMobsInfo();
|
||||
this.md2Service.broadcastMobsInfo();
|
||||
if (this.showRoundMessage) {
|
||||
this.msgBoxService.show(`${NumberUtils.Ordinal(this.md2Service.info.round)} Hero Phase`, { icon: ADIcon.INFO });
|
||||
}
|
||||
@@ -151,7 +155,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
||||
if (attacker) {
|
||||
attacker.exp += 1;
|
||||
this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
|
||||
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
|
||||
this.md2Service.broadcastHeroInfoToOwner(attacker);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,33 @@
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<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"> -->
|
||||
<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 class="col-md-5">
|
||||
|
||||
<md2-mob-detail-info [mob]="mob" [mode]="mode">
|
||||
</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">
|
||||
<div class="row form-group mt-2">
|
||||
|
||||
@@ -2,3 +2,122 @@
|
||||
width: 885px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
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 { MD2Service } from '../../../../services/MD2/md2.service';
|
||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||
@@ -9,6 +9,13 @@ import { StateService } from '../../../../services/state.service';
|
||||
import { StringUtils } from '../../../../utilities/string-utils';
|
||||
import { AttackInfo, AttackTarget, AttackType, MD2HeroInfo, MD2Icon, MobDlgType, MobInfo, MobType } from '../../massive-darkness2.model';
|
||||
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, MobSkillTarget } from '../../massive-darkness2.db.model';
|
||||
import { MD2Logic } from '../../massive-darkness2.logic';
|
||||
|
||||
type SkillResolutionResult = { result: boolean; skill: MD2MobSkill | null };
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-spawn-mob-dlg',
|
||||
@@ -25,7 +32,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
||||
MD2Icon = MD2Icon;
|
||||
mob: MobInfo;
|
||||
actionInfoHtml: string;
|
||||
beenAttackedHero = [] as MD2HeroInfo[];
|
||||
beenAttackedHero = null as MD2HeroInfo;
|
||||
attackTarget: string;
|
||||
otherAttackTarget: string;
|
||||
constructor(
|
||||
@@ -38,16 +45,34 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
||||
) {
|
||||
super(md2Service, stateService, route, cdRef);
|
||||
}
|
||||
activeSkill: MD2MobSkill = null;;
|
||||
ngOnInit(): void {
|
||||
//this.mob = new MobInfo(this.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)];
|
||||
} else if (this.mode == MobDlgType.Activating) {
|
||||
if (this.mob.actionSubject) {
|
||||
this.mob.actionSubject.pipe(first()).subscribe(result => {
|
||||
this.actionInfoHtml = result;
|
||||
if (this.mob.skills) {
|
||||
|
||||
//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;
|
||||
@@ -55,6 +80,52 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
||||
this.mob.uiFrozenTokens = 0;
|
||||
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() {
|
||||
if (this.mode == MobDlgType.Spawn) {
|
||||
|
||||
@@ -87,50 +158,45 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
||||
if (this.mode == MobDlgType.Spawn) {
|
||||
htmlText = `${this.mob.description} Shows Up`;
|
||||
} else if (this.mode == MobDlgType.Activating) {
|
||||
let targetType = null as AttackTarget;
|
||||
let randomAttack = Math.random() * AttackTarget.LowestLevel;
|
||||
const values = Object.values(AttackTarget);
|
||||
let targetType = null as MobSkillTarget;
|
||||
let randomAttack = Math.random() * MobSkillTarget.HighestLevel;
|
||||
const values = Object.values(MobSkillTarget);
|
||||
|
||||
const stringKeys = Object
|
||||
.keys(AttackTarget)
|
||||
.keys(MobSkillTarget)
|
||||
.filter((v) => isNaN(Number(v)))
|
||||
for (let i = 0; i < stringKeys.length; i++) {
|
||||
const element = AttackTarget[stringKeys[i]];
|
||||
const element = MobSkillTarget[stringKeys[i]];
|
||||
|
||||
if (element >= randomAttack) {
|
||||
targetType = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.beenAttackedHero = MD2Logic.getTargetHeroByFilter(this.md2Service.heros, targetType);
|
||||
switch (targetType) {
|
||||
case AttackTarget.LeastHp:
|
||||
let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp));
|
||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||
case MobSkillTarget.LeastHp:
|
||||
this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||
break;
|
||||
case AttackTarget.HighestHp:
|
||||
let highestHp = Math.max(...this.md2Service.heros.map(h => h.hp));
|
||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == highestHp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
||||
case MobSkillTarget.HighestHp:
|
||||
this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
||||
break;
|
||||
case AttackTarget.HighestMp:
|
||||
let highestMp = Math.max(...this.md2Service.heros.map(h => h.mp));
|
||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.mp == highestMp);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
|
||||
case MobSkillTarget.HighestMp:
|
||||
this.otherAttackTarget = 'attacking the other <b>Highest Mana</b> hero.';
|
||||
break;
|
||||
case AttackTarget.LowestLevel:
|
||||
let lowestLevel = Math.max(...this.md2Service.heros.map(h => h.level));
|
||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.level == lowestLevel);
|
||||
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||
case MobSkillTarget.LowestLevel:
|
||||
this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||
break;
|
||||
case AttackTarget.Random:
|
||||
case MobSkillTarget.HighestLevel:
|
||||
this.otherAttackTarget = 'attacking the other <b>Highest Level</b> hero.';
|
||||
break;
|
||||
case MobSkillTarget.Random:
|
||||
default:
|
||||
this.beenAttackedHero = [this.md2Service.heros[Math.round(Math.random() * (this.md2Service.heros.length - 1))]];
|
||||
//this.otherAttackTarget = 'Just act like normal.';
|
||||
this.otherAttackTarget = 'Just act like normal.';
|
||||
break;
|
||||
}
|
||||
let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
|
||||
this.attackTarget = `Attacking <b>${attackedHeros}</b> first if possible.`;
|
||||
//let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
|
||||
this.attackTarget = `Attacking <b>${this.beenAttackedHero.heroFullName}</b> first if possible, otherwise ${this.otherAttackTarget}`;
|
||||
htmlText = `${this.title}`;
|
||||
} else {
|
||||
htmlText = `${this.mob.description}`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CrudService } from '../../../services/crudServices/crud.service';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill } from '../massive-darkness2.db.model';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill, BossFightProfile, BossFightPhaseBuff } from '../massive-darkness2.db.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MD2HeroProfile } from '../massive-darkness2.model';
|
||||
|
||||
@@ -47,4 +47,26 @@ export class MD2HeroProfileService extends CrudService<MD2HeroProfile> {
|
||||
super(http, (action: string = null) => { return `MD2HeroProfile${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2BossFightProfileService extends CrudService<BossFightProfile> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2BossFightProfile${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2PhaseBuffService extends CrudService<BossFightPhaseBuff> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2BossFightPhaseBuff${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreGameRMFactories } from '../../games/massive-darkness2/factorys/roamingMonsters/CoreGame';
|
||||
import { DrawingBag, MobInfo, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { MD2StateService } from './md2-state.service';
|
||||
import { DrawingBag, MobInfo, MobType, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { MD2MobInfoService } from '../../games/massive-darkness2/service/massive-darkness2.service';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2Service } from './md2.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -11,28 +10,18 @@ import { first } from 'rxjs/operators';
|
||||
export class MD2InitService {
|
||||
|
||||
constructor(
|
||||
private stateService: MD2StateService,
|
||||
private md2Service: MD2Service,
|
||||
private mobInfoService: MD2MobInfoService,
|
||||
) { }
|
||||
|
||||
public initMobDecks() {
|
||||
this.stateService.mobDeck = new DrawingBag();
|
||||
this.stateService.roamingMobDeck = new DrawingBag();
|
||||
this.md2Service.mobDeck = new DrawingBag();
|
||||
this.md2Service.roamingMobDeck = new DrawingBag();
|
||||
|
||||
this.initCoreGameMobs();
|
||||
this.initCoreGameRoamingMonsters();
|
||||
this.initCoreGameMobAndRoamingMonsters();
|
||||
}
|
||||
|
||||
private initCoreGameRoamingMonsters() {
|
||||
CoreGameRMFactories.forEach(factory => {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
this.stateService.roamingMobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
|
||||
i++;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
private initCoreGameMobs() {
|
||||
private initCoreGameMobAndRoamingMonsters() {
|
||||
// CoreGameMobFactories.forEach(factory => {
|
||||
// for (let i = 1; i <= 5; i++) {
|
||||
// this.stateService.mobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
|
||||
@@ -40,13 +29,21 @@ export class MD2InitService {
|
||||
// }
|
||||
// });
|
||||
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);
|
||||
this.md2Service.bossInfos = result.filter(m => m.type == MobType.Boss);
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const mobInfo = result[i];
|
||||
for (let j = 0; j < mobInfo.mobLevelInfos.length; j++) {
|
||||
const levelInfo = mobInfo.mobLevelInfos[j];
|
||||
|
||||
this.stateService.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 }));
|
||||
switch (mobInfo.type) {
|
||||
case MobType.Mob:
|
||||
this.md2Service.mobDeck.AddItem(new MobInfo({ id: mobInfo.id, name: mobInfo.name, from: mobInfo.from, level: levelInfo.level, drawingWeight: 1 }));
|
||||
break;
|
||||
case MobType.RoamingMonster:
|
||||
this.md2Service.roamingMobDeck.AddItem(new MobInfo({ id: mobInfo.id, name: mobInfo.name, from: mobInfo.from, level: levelInfo.level, drawingWeight: 1 }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +54,12 @@ export class MD2InitService {
|
||||
}
|
||||
|
||||
public initTreasureBag() {
|
||||
this.stateService.treasureBag.ClearAllItems();
|
||||
this.md2Service.treasureBag.ClearAllItems();
|
||||
this.addTreasure(TreasureType.Common, 15);
|
||||
this.addTreasure(TreasureType.Rare, 5);
|
||||
}
|
||||
|
||||
public addTreasure(type: TreasureType, amount: number = 1) {
|
||||
this.stateService.treasureBag.AddItem(new TreasureItem(type, amount));
|
||||
this.md2Service.treasureBag.AddItem(new TreasureItem(type, amount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { first } from 'rxjs/operators';
|
||||
import { MobInfo, MobType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { ADIcon } from '../../ui/alert-dlg/alert-dlg.model';
|
||||
import { MsgBoxService } from '../msg-box.service';
|
||||
import { MD2StateService } from './md2-state.service';
|
||||
import { MD2Service } from './md2.service';
|
||||
|
||||
@Injectable({
|
||||
@@ -16,14 +15,13 @@ export class MD2MobService {
|
||||
attackingAttackerReward: string = '';
|
||||
constructor(
|
||||
private md2Service: MD2Service,
|
||||
private stateService: MD2StateService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) { }
|
||||
public get roamingMonsters() {
|
||||
return this.stateService.info.roamingMonsters;
|
||||
return this.md2Service.info.roamingMonsters;
|
||||
}
|
||||
public get mobs() {
|
||||
return this.stateService.info.mobs;
|
||||
return this.md2Service.info.mobs;
|
||||
}
|
||||
|
||||
addOneUnit(mob: MobInfo) {
|
||||
@@ -120,14 +118,14 @@ export class MD2MobService {
|
||||
}
|
||||
this.attackingAllExp = 0;
|
||||
this.attackingAttackerExp = 0;
|
||||
this.md2Service.broadcastService.broadcastAllHeroInfoToAll();
|
||||
this.md2Service.broadcastAllHeroInfoToAll();
|
||||
this.md2Service.refreshUI$.next();
|
||||
});
|
||||
} else if (this.attackingAttackerExp > 0 && attacker) {
|
||||
this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
|
||||
attacker.exp += this.attackingAttackerExp;
|
||||
this.attackingAttackerExp = 0;
|
||||
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
|
||||
this.md2Service.broadcastHeroInfoToOwner(attacker);
|
||||
this.md2Service.refreshUI$.next();
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
+536
-111
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2EnemyPhaseSpecialInfo, MD2EnemyPhaseSpecialRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { first, map, reduce } from "rxjs/operators";
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BossMicheal, BossReaper, IBossFight } from '../../games/massive-darkness2/massive-darkness2.model.boss';
|
||||
import { NbDialogService, NbThemeService } from '@nebular/theme';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { BossFight, MobSkillType } from '../../games/massive-darkness2/massive-darkness2.model.boss';
|
||||
import { ADIcon, MessageBoxConfig } from '../../ui/alert-dlg/alert-dlg.model';
|
||||
import { NumberUtils } from '../../utilities/number-utils';
|
||||
import { StringUtils } from '../../utilities/string-utils';
|
||||
@@ -11,16 +11,16 @@ import { FileService } from '../file.service';
|
||||
import { GameRoomService } from '../game-room.service';
|
||||
import { LoginUserService } from '../login-user.service';
|
||||
import { MsgBoxService } from '../msg-box.service';
|
||||
import { SignalRService, SignalRSession, SignalRMessage } from '../signal-r.service';
|
||||
import { MD2StateService } from './md2-state.service';
|
||||
import { MD2BroadcastService } from './md2-broadcast.service';
|
||||
import { SignalRService, SignalRClient, SignalRMessage } from '../signal-r.service';
|
||||
import { MD2Logic } from '../../games/massive-darkness2/massive-darkness2.logic';
|
||||
import { CoreGameRMFactories } from '../../games/massive-darkness2/factorys/roamingMonsters/CoreGame';
|
||||
import { MD2InitService } from './md2-init.service';
|
||||
import { ArrayUtils } from '../../utilities/array-utils';
|
||||
import { DropDownOption } from '../../entity/dropDownOption';
|
||||
import { GameBundle, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
|
||||
import { GameBundle, MD2DiceSet, MD2MobInfo, MD2MobSkill, MobSkillTarget } from '../../games/massive-darkness2/massive-darkness2.db.model';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { RollingBlackDice } from '../../games/massive-darkness2/massive-darkness2.model.dice';
|
||||
import { BossActivationComponent } from '../../games/massive-darkness2/boss-fight/boss-activation/boss-activation.component';
|
||||
import { NbToastrService } from '@nebular/theme';
|
||||
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` };
|
||||
@@ -30,6 +30,18 @@ const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files
|
||||
export class MD2Service {
|
||||
// #region Properties (24)
|
||||
|
||||
private _highestPlayerLevel: number = 1;
|
||||
private _playerAmount: number = 2;
|
||||
public specialRule: MD2EnemyPhaseSpecialInfo;
|
||||
public info: MD2GameInfo;
|
||||
public playerHero: MD2HeroInfo;
|
||||
public bossInfos: MD2MobInfo[] = [];
|
||||
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 enemyPhaseMobs: MobInfo[];
|
||||
public enemyPhaseSubject = new Subject<MobInfo>();
|
||||
@@ -43,45 +55,40 @@ export class MD2Service {
|
||||
|
||||
|
||||
public get heros() {
|
||||
return this.stateService.info.heros;
|
||||
return this.info.heros;
|
||||
}
|
||||
public get roamingMonsters() {
|
||||
return this.stateService.info.roamingMonsters;
|
||||
return this.info.roamingMonsters;
|
||||
}
|
||||
public get mobs() {
|
||||
return this.stateService.info.mobs;
|
||||
return this.info.mobs;
|
||||
}
|
||||
public get allRoamingMonsterInfos() {
|
||||
return this.roamingMobInfos;
|
||||
}
|
||||
public get allMobInfos() {
|
||||
return this.stateService.mobInfos;
|
||||
}
|
||||
|
||||
|
||||
public get info(): MD2GameInfo {
|
||||
return this.stateService.info;
|
||||
}
|
||||
public set info(v: MD2GameInfo) {
|
||||
this.stateService.info = v;
|
||||
return this.mobInfos;
|
||||
}
|
||||
|
||||
// #endregion Properties (24)
|
||||
|
||||
// #region Constructors (1)
|
||||
|
||||
constructor(
|
||||
public fileService: FileService,
|
||||
public msgBoxService: MsgBoxService,
|
||||
private gameRoomService: GameRoomService,
|
||||
private loginUserService: LoginUserService,
|
||||
public stateService: MD2StateService,
|
||||
public gameRoomService: GameRoomService,
|
||||
public loginUserService: LoginUserService,
|
||||
public signalRService: SignalRService,
|
||||
public dlgService: NbDialogService,
|
||||
public broadcastService: MD2BroadcastService
|
||||
public themeService: NbThemeService,
|
||||
public toastrService: NbToastrService
|
||||
) {
|
||||
this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
|
||||
this.stateService.info = new MD2GameInfo();
|
||||
this.info = new MD2GameInfo();
|
||||
this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => {
|
||||
this.addTreasure(treasureType, 1);
|
||||
});
|
||||
this.specialRule = new MD2EnemyPhaseSpecialInfo();
|
||||
}
|
||||
|
||||
// #endregion Constructors (1)
|
||||
@@ -103,11 +110,8 @@ export class MD2Service {
|
||||
}
|
||||
}
|
||||
|
||||
public get playerHero(): MD2HeroInfo {
|
||||
return this.stateService.playerHero;
|
||||
}
|
||||
public get currentActivateHero(): MD2HeroInfo {
|
||||
return this.stateService.info.heros.find(h => h.uiActivating);
|
||||
return this.info.heros.find(h => h.uiActivating);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,9 +121,6 @@ export class MD2Service {
|
||||
|
||||
// #region Public Methods (27)
|
||||
|
||||
public get treasureBag() {
|
||||
return this.stateService.treasureBag
|
||||
}
|
||||
|
||||
public addTreasure(type: TreasureType, amount: number = 1) {
|
||||
this.treasureBag.AddItem(new TreasureItem(type, amount));
|
||||
@@ -129,23 +130,35 @@ export class MD2Service {
|
||||
public darknessPhase() {
|
||||
this.heros.forEach(hero => {
|
||||
hero.remainActions = 3;
|
||||
this.broadcastService.broadcastHeroInfoToOwner(hero);
|
||||
this.broadcastHeroInfoToOwner(hero);
|
||||
});
|
||||
this.stateService.info.roundPhase = RoundPhase.HeroPhase;
|
||||
if (!this.stateService.info.isBossFight) {
|
||||
this.stateService.info.round++;
|
||||
this.info.roundPhase = RoundPhase.HeroPhase;
|
||||
if (!this.info.isBossFight) {
|
||||
this.info.round++;
|
||||
if (this.info.round == 5 && this.info.enableHeroBetrayal) {
|
||||
let betrayalHero = this.getTargetHerosByFilter(MobSkillTarget.LowestLevel, true)[0];
|
||||
this.sendMsgboxMsg(betrayalHero.playerInfo.signalRClientId,
|
||||
{
|
||||
title: 'Dark Lord’s Invitation', text: 'Child of light… why pretend?<br>' +
|
||||
'I have seen the hunger within you, the one your allies fear you will one day unleash.<br>' +
|
||||
'Serve me openly, and the world shall kneel before you.<br>' +
|
||||
'One act of betrayal—one moment of truth—and you will become the legend the others only pretend to be.', icon: ADIcon.WARNING
|
||||
});
|
||||
betrayalHero.uiBetrayal = true;
|
||||
this.broadcastGameInfo();
|
||||
}
|
||||
if (this.darknessPhaseRule.runDarknessPhase()) {
|
||||
this.msgBoxService.show(`${NumberUtils.Ordinal(this.stateService.info.round)} Hero Phase`, { icon: ADIcon.INFO });
|
||||
this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
|
||||
}
|
||||
} else {
|
||||
this.stateService.info.boss.darknessPhase();
|
||||
this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.stateService.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO });
|
||||
this.bossDarknessPhase();
|
||||
this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.info.bossRound)} Hero Phase`, { icon: ADIcon.INFO });
|
||||
}
|
||||
|
||||
//this.runNextPhase();
|
||||
}
|
||||
|
||||
public spawnMob(isRoamingMonster: boolean, mobName: string = null) {
|
||||
public spawnMob(isRoamingMonster: boolean, mobId: string = null) {
|
||||
let mobDeck = null as DrawingBag<MobInfo>;
|
||||
let level = 1;
|
||||
if (this.highestPlayerLevel < 3) {
|
||||
@@ -155,22 +168,22 @@ export class MD2Service {
|
||||
level = 5;
|
||||
}
|
||||
if (isRoamingMonster) {
|
||||
mobDeck = this.stateService.roamingMobDeck;
|
||||
mobDeck = this.roamingMobDeck;
|
||||
} 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 && this.info.enabledBundles.includes((m as MobInfo).from))
|
||||
.map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) {
|
||||
mobDeck.RestoreRemoveItems();
|
||||
}
|
||||
|
||||
let newSpawnMob = null as MobInfo;
|
||||
|
||||
if (mobName) {
|
||||
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.name == mobName)[0]);
|
||||
if (mobId) {
|
||||
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.id == mobId)[0]);
|
||||
} else {
|
||||
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]);
|
||||
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && this.info.enabledBundles.includes(m.from))[0]);
|
||||
}
|
||||
|
||||
let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name);
|
||||
@@ -184,13 +197,10 @@ export class MD2Service {
|
||||
exitingMob.skills = newSpawnMob.skills;
|
||||
} else {
|
||||
if (isRoamingMonster) {
|
||||
newSpawnMob = CoreGameRMFactories.find(f => f.mobName == newSpawnMob.name).generate(level);
|
||||
newSpawnMob.hp = newSpawnMob.hpPerHero * this.playerAmount;
|
||||
newSpawnMob.unitRemainHp = newSpawnMob.hp;
|
||||
newSpawnMob.mobAmount = 0;
|
||||
newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
|
||||
this.roamingMonsters.push(newSpawnMob);
|
||||
} else {
|
||||
newSpawnMob = this.generateMob(newSpawnMob.name, level);
|
||||
newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
|
||||
this.mobs.push(newSpawnMob);
|
||||
}
|
||||
|
||||
@@ -202,16 +212,12 @@ export class MD2Service {
|
||||
|
||||
return { exitingMob, mob: newSpawnMob };
|
||||
}
|
||||
private generateMob(mobName: string, level: number) {
|
||||
let dbMobInfo = this.allMobInfos.find(m => m.name == mobName);
|
||||
private generateMob(isRoamingMonster: boolean, mobName: string, level: number) {
|
||||
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 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let mobInfo = new MobInfo({
|
||||
name: dbMobInfo.name,
|
||||
hp: levelInfo.hpPerHero,
|
||||
@@ -219,6 +225,7 @@ export class MD2Service {
|
||||
level: level,
|
||||
rewardTokens: levelInfo.rewardTokens,
|
||||
defenseInfo: levelInfo.defenceInfo,
|
||||
type: dbMobInfo.type,
|
||||
skills: []
|
||||
});
|
||||
|
||||
@@ -229,22 +236,74 @@ export class MD2Service {
|
||||
}
|
||||
}
|
||||
mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq);
|
||||
|
||||
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`);
|
||||
|
||||
switch (dbMobInfo.type) {
|
||||
case MobType.Mob:
|
||||
mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
|
||||
mobInfo.minionImgUrl = !!dbMobInfo.minionImgUrl ? this.imgUrl(dbMobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
|
||||
break;
|
||||
case MobType.RoamingMonster:
|
||||
mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`);
|
||||
mobInfo.minionImgUrl = null;
|
||||
break;
|
||||
case MobType.Boss:
|
||||
mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : this.imgUrl(`/Boss/${mobInfo.name}-Stand.png`);
|
||||
mobInfo.minionImgUrl = null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mobInfo.fixedCarriedTreasure = [];
|
||||
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.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;
|
||||
}
|
||||
public enemyPhase() {
|
||||
private getAttackInfo(attackInfo: MD2DiceSet): AttackInfo {
|
||||
return new AttackInfo(attackInfo.type as unknown as MD2Icon, attackInfo.yellow, attackInfo.orange, attackInfo.red, attackInfo.black);
|
||||
}
|
||||
public enemyPhase(triggerSpecialRule: boolean = true) {
|
||||
//this.msgBoxService
|
||||
//Draw a special rule
|
||||
|
||||
this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs);
|
||||
|
||||
if (this.info.enableMobSpecialRule && this.mobs.length > 0 && triggerSpecialRule) {
|
||||
let specialRuleDrawingBag = new DrawingBag<MD2EnemyPhaseSpecialRule>(this.specialRule.specialRules);
|
||||
let specialRule = specialRuleDrawingBag.Draw(1)[0];
|
||||
this.specialRule.specialRule = specialRule;
|
||||
if (specialRule.description) {
|
||||
this.themeService.changeTheme('dark');
|
||||
this.msgBoxService.show(specialRule.title, { text: specialRule.description, icon: ADIcon.WARNING })
|
||||
.pipe(first()).subscribe(result => {
|
||||
this.enemyPhase(false);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.specialRule.specialRule = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.enemyPhaseMobs.length > 0) {
|
||||
this.enemyPhaseMobs = ArrayUtils.Shuffle(this.enemyPhaseMobs);
|
||||
//this.showEnemyPhaseAction();
|
||||
@@ -255,44 +314,65 @@ export class MD2Service {
|
||||
}
|
||||
|
||||
public enterBossFight() {
|
||||
|
||||
this.msgBoxService.showInputbox('Boss Fight', 'Choose the boss', {
|
||||
inputType: 'dropdown',
|
||||
dropDownOptions: [new DropDownOption('The Reaper', 'The Reaper'), new DropDownOption('Michael - The Corrupted Archangel', 'Michael - The Corrupted Archangel')]
|
||||
dropDownOptions: this.bossInfos.map(b => new DropDownOption(b.id, b.name))
|
||||
}).pipe(first()).subscribe(result => {
|
||||
if (result) {
|
||||
this.info.mobs = [];
|
||||
this.info.roamingMonsters = [];
|
||||
let bossInfo = this.bossInfos.find(b => b.id == result);
|
||||
this.info.boss = new MobInfo(bossInfo);
|
||||
|
||||
if (result == 'The Reaper') {
|
||||
this.info.boss = new BossReaper(this);
|
||||
} else {
|
||||
this.info.boss = new BossMicheal(this);
|
||||
this.info.boss.leaderImgUrl = !!this.info.boss.leaderImgUrl ? this.imgUrl(this.info.boss.leaderImgUrl) : this.imgUrl(`/Boss/${this.info.boss.name}-Stand.png`);
|
||||
|
||||
let bossLevelInfo = bossInfo.mobLevelInfos.sort((a, b) => b.level - a.level)
|
||||
.find(l => l.level <= Math.max(...this.info.heros.map(h => h.level)));
|
||||
if (bossLevelInfo) {
|
||||
this.info.boss.hp = bossLevelInfo.hpPerHero * this.info.heros.length;
|
||||
this.info.boss.unitRemainHp = this.info.boss.hp;
|
||||
this.info.boss.actions = bossLevelInfo.actions;
|
||||
this.info.boss.attackInfos = [this.getAttackInfo(bossLevelInfo.attackInfo)];
|
||||
let altAttackInfo = bossLevelInfo.alterAttackInfo;
|
||||
if (altAttackInfo
|
||||
&& (altAttackInfo.black > 0 || altAttackInfo.blue > 0 || altAttackInfo.green > 0 || altAttackInfo.orange > 0 || altAttackInfo.red > 0 || altAttackInfo.yellow > 0)
|
||||
) {
|
||||
this.info.boss.attackInfos.push(this.getAttackInfo(altAttackInfo));
|
||||
}
|
||||
this.info.boss.defenseInfo = bossLevelInfo.defenceInfo;
|
||||
}
|
||||
this.stateService.info.roamingMonsters = [];
|
||||
this.stateService.info.mobs = [];
|
||||
this.stateService.info.isBossFight = true;
|
||||
this.info.roamingMonsters = [];
|
||||
this.info.mobs = [];
|
||||
this.info.isBossFight = true;
|
||||
this.info.boss.info.hp = this.info.boss.info.hpPerHero * this.info.heros.length;
|
||||
this.info.boss.info.unitRemainHp = this.info.boss.info.hp;
|
||||
this.refreshUI$.next();
|
||||
this.info.boss.prepareForBossFight();
|
||||
this.prepareForBossFight();
|
||||
this.levelUpPhase(false);
|
||||
|
||||
//Reset all heroes
|
||||
let extraTokenName = this.info.boss.bossFightProfile.extraTokenName;
|
||||
let extraTokenHtml = this.info.boss.bossFightProfile.extraTokenHtml;
|
||||
let extraTokenName2 = this.info.boss.bossFightProfile.extraTokenName2;
|
||||
let extraTokenHtml2 = this.info.boss.bossFightProfile.extraTokenHtml2;
|
||||
this.heros.forEach(hero => {
|
||||
hero.hp = hero.hpMaximum;
|
||||
hero.mp = hero.mpMaximum;
|
||||
hero.remainActions = 3;
|
||||
hero.uiActivating = false;
|
||||
hero.uiBossFight = true;
|
||||
hero.uiExtraTokenHtml = extraTokenHtml;
|
||||
hero.uiExtraTokenName = extraTokenName;
|
||||
hero.uiShowExtraToken = !!extraTokenName;
|
||||
hero.uiShowExtraToken2 = !!extraTokenName2;
|
||||
hero.uiExtraTokenHtml2 = extraTokenHtml2;
|
||||
hero.uiExtraTokenName2 = extraTokenName2;
|
||||
});
|
||||
this.broadcastService.broadcastAllHeroInfoToAll();
|
||||
this.broadcastGameInfo();
|
||||
}
|
||||
|
||||
});
|
||||
//this.sendMsgboxMsg
|
||||
}
|
||||
public activateBoss() {
|
||||
this.stateService.info.boss.activating();
|
||||
}
|
||||
public fileList(folderPath: string) {
|
||||
return this.fileService.FileList('Images/MD2/' + folderPath);
|
||||
}
|
||||
@@ -312,8 +392,8 @@ export class MD2Service {
|
||||
hero.mp += levelUpInfo.extraMp;
|
||||
hero.mpMaximum += levelUpInfo.extraMp;
|
||||
hero.exp = levelUpInfo.currentExp;
|
||||
this.broadcastService.broadcastHeroInfoToOwner(hero);
|
||||
this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: 'Level Up', text: 'Please do a skill level up!', icon: ADIcon.INFO });
|
||||
this.broadcastHeroInfoToOwner(hero);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -332,9 +412,9 @@ export class MD2Service {
|
||||
level = 3;
|
||||
}
|
||||
if (isRoamingMonsters) {
|
||||
return this.stateService.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
|
||||
return this.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
|
||||
} else {
|
||||
return this.stateService.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
|
||||
return this.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +424,7 @@ export class MD2Service {
|
||||
hero.hp = hero.hpMaximum;
|
||||
hero.mp = hero.mpMaximum;
|
||||
hero.exp = 0;
|
||||
this.stateService.playerHero = hero;
|
||||
this.playerHero = hero;
|
||||
// let message = {
|
||||
// receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
|
||||
// from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
|
||||
@@ -359,13 +439,14 @@ export class MD2Service {
|
||||
|
||||
|
||||
public runNextPhase() {
|
||||
this.stateService.info.roundPhase++;
|
||||
switch (this.stateService.info.roundPhase) {
|
||||
this.info.roundPhase++;
|
||||
switch (this.info.roundPhase) {
|
||||
case RoundPhase.HeroPhase:
|
||||
this.heros.forEach(hero => {
|
||||
hero.remainActions = 3;
|
||||
this.broadcastService.broadcastHeroInfoToOwner(hero);
|
||||
});
|
||||
//HeroPhase will be handled in the darkness phase
|
||||
// this.heros.forEach(hero => {
|
||||
// hero.remainActions = 3;
|
||||
// this.broadcastHeroInfoToOwner(hero);
|
||||
// });
|
||||
break;
|
||||
case RoundPhase.EnemyPhase:
|
||||
this.enemyPhase();
|
||||
@@ -375,18 +456,19 @@ export class MD2Service {
|
||||
break;
|
||||
case RoundPhase.DarknessPhase:
|
||||
this.darknessPhase();
|
||||
this.themeService.changeTheme('default');
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
let parameters = {};
|
||||
parameters['phase'] = this.stateService.info.roundPhase;
|
||||
this.broadcastService.broadcastMessage('roundPhase', '', parameters);
|
||||
parameters['phase'] = this.info.roundPhase;
|
||||
this.broadcastMessage('roundPhase', '', parameters);
|
||||
}
|
||||
|
||||
public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) {
|
||||
let message = {
|
||||
receiver: { isGroup: false, sessionId: playerSessionId } as SignalRSession,
|
||||
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
|
||||
receiver: { isGroup: false, connectionId: playerSessionId } as SignalRClient,
|
||||
from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
|
||||
actionType: 'message',
|
||||
actionName: 'popup',
|
||||
} as SignalRMessage;
|
||||
@@ -395,8 +477,8 @@ export class MD2Service {
|
||||
});
|
||||
}
|
||||
|
||||
public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) {
|
||||
return MD2Logic.getTargetHerosByFilter(this.stateService.info.heros, targetType, onlyOne);
|
||||
public getTargetHerosByFilter(targetType: MobSkillTarget, onlyOne: boolean = false) {
|
||||
return MD2Logic.getTargetHerosByFilter(this.info.heros, targetType, onlyOne);
|
||||
}
|
||||
|
||||
public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
|
||||
@@ -419,34 +501,371 @@ export class MD2Service {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.broadcastService.broadcastHeroInfoToAll(this.currentActivateHero);
|
||||
this.broadcastService.broadcastHeroInfoToOwner(this.currentActivateHero);
|
||||
this.broadcastHeroInfoToAll(this.currentActivateHero, true);
|
||||
//this.broadcastHeroInfoToOwner(this.currentActivateHero);
|
||||
this.msgBoxService.show(
|
||||
this.stateService.imgHtml('/Fountain/Cover.png', '')
|
||||
this.imgHtml('/Fountain/Cover.png', '')
|
||||
, { text: `${this.currentActivateHero.heroFullName} Recovered ${result.description}.` });
|
||||
}
|
||||
|
||||
public openTreasureChest() {
|
||||
let result = new DrawingBag<DrawingItem>([
|
||||
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Common)} x 2`, '', 10),
|
||||
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5),
|
||||
new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Common)} x 2`, '', 10),
|
||||
new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5),
|
||||
])
|
||||
.Draw(1)[0];
|
||||
|
||||
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() {
|
||||
let result = new DrawingBag<DrawingItem>([
|
||||
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3),
|
||||
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1),
|
||||
new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3),
|
||||
new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1),
|
||||
]).Draw(1)[0];
|
||||
|
||||
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 = 'MD2IconImg g-height-25 mr-1';
|
||||
}
|
||||
//image based icons
|
||||
switch (icon) {
|
||||
|
||||
case MD2Icon.HP_Color:
|
||||
return this.imgHtml('HeartIcon.png', cssClass, 'HP');
|
||||
case MD2Icon.Mana_Color:
|
||||
return this.imgHtml('ManaIcon.png', cssClass, 'Mana');
|
||||
case MD2Icon.CorruptToken:
|
||||
return this.imgHtml('Tokens/CorruptToken.png', cssClass, 'Corruption Token');
|
||||
case MD2Icon.TimeToken:
|
||||
return this.imgHtml('Tokens/TimeToken.png', cssClass, 'Time Token');
|
||||
case MD2Icon.FireToken:
|
||||
return this.imgHtml('Tokens/FireToken.png', cssClass, 'Fire Token');
|
||||
case MD2Icon.FrozenToken:
|
||||
return this.imgHtml('Tokens/FrozenToken.png', cssClass, 'Frozen Token');
|
||||
case MD2Icon.TreasureToken:
|
||||
return this.imgHtml('TreasureToken/Cover.png', cssClass, 'Treasure Token');
|
||||
case MD2Icon.TreasureToken_Common:
|
||||
return this.imgHtml('TreasureToken/Common.png', cssClass, 'Common Treasure Token');
|
||||
case MD2Icon.TreasureToken_Rare:
|
||||
return this.imgHtml('TreasureToken/Rare.png', cssClass, 'Rare Treasure Token');
|
||||
case MD2Icon.TreasureToken_Epic:
|
||||
return this.imgHtml('TreasureToken/Epic.png', cssClass, 'Epic Treasure Token');
|
||||
case MD2Icon.TreasureToken_Legendary:
|
||||
return this.imgHtml('TreasureToken/Legendary.png', cssClass, 'Legendary Treasure Token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1', imgTitle = '') {
|
||||
return `<img src='${this.imgUrl(imgPath)}' class='${cssClass}' title='${imgTitle}'>`;
|
||||
}
|
||||
|
||||
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 = {};
|
||||
parameters['gameInfo'] = JSON.stringify(this.info);
|
||||
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)
|
||||
|
||||
//#region Boss Fight
|
||||
private bossActivatedTimes: number = 0;
|
||||
activateBoss(): boolean {
|
||||
this.bossActivatedTimes = this.info.boss.actions;
|
||||
this.runBossAction();
|
||||
return true;
|
||||
}
|
||||
|
||||
runBossAction() {
|
||||
this.bossAction().pipe(first()).subscribe(result => {
|
||||
this.bossActivatedTimes--;
|
||||
if (this.bossActivatedTimes) {
|
||||
this.runBossAction();
|
||||
} else {
|
||||
if (false == this.heros.some(h => h.remainActions > 0)) {
|
||||
this.darknessPhase();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bossAction(): Observable<boolean> {
|
||||
let actionBlackDice = 2;
|
||||
let actionResult = new RollingBlackDice().roll(actionBlackDice);
|
||||
let actionHtml = '';
|
||||
let beenAttackedHero = [] as MD2HeroInfo[];
|
||||
let bossAction: MD2MobSkill;
|
||||
bossAction = new MD2MobSkill(this.info.boss.skills.find(s => s.type == MobSkillType.ActiveSkill && s.skillRoll == actionResult.claws));
|
||||
|
||||
bossAction.description = this.skillParse(bossAction.description, bossAction.skillTarget);
|
||||
|
||||
return this.dlgService.open(BossActivationComponent, { context: { boss: this.info.boss, bossAction: bossAction, currentAction: this.bossActivatedTimes, allActions: this.info.boss.actions } }).onClose;
|
||||
|
||||
}
|
||||
prepareForBossFight(): void {
|
||||
this.heros.forEach(hero => {
|
||||
hero.uiShowExtraToken = !!this.info.boss.bossFightProfile.extraTokenName;
|
||||
hero.uiShowExtraToken2 = !!this.info.boss.bossFightProfile.extraTokenName2;
|
||||
});
|
||||
//init boss first round buff
|
||||
this.bossDarknessPhase();
|
||||
let prerequisiteHtml = this.info.boss.bossFightProfile.prerequisite;
|
||||
//Detect prerequisiteHtml is empty or not,like <p></p>
|
||||
if (prerequisiteHtml.trim() == '<p></p>') {
|
||||
prerequisiteHtml = '';
|
||||
}
|
||||
if (prerequisiteHtml) {
|
||||
prerequisiteHtml = this.skillParse(prerequisiteHtml, MobSkillTarget.Random);
|
||||
this.msgBoxService.show('Prepare Boss Fight', {
|
||||
text: prerequisiteHtml
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
bossDarknessPhase(): void {
|
||||
this.info.bossRound++;
|
||||
let roundBuff = this.info.boss.bossFightProfile.phaseBuffs.find(p => p.phase == this.info.bossRound);
|
||||
if (roundBuff) {
|
||||
this.info.boss.actions += roundBuff.extraAction || 0;
|
||||
for (let i = 0; i < this.info.boss.attackInfos.length; i++) {
|
||||
this.info.boss.attackInfos[i].black += roundBuff.extraAttackDice.black || 0;
|
||||
this.info.boss.attackInfos[i].yellow += roundBuff.extraAttackDice.yellow || 0;
|
||||
this.info.boss.attackInfos[i].orange += roundBuff.extraAttackDice.orange || 0;
|
||||
this.info.boss.attackInfos[i].red += roundBuff.extraAttackDice.red || 0;
|
||||
}
|
||||
this.info.boss.defenseInfo.black += roundBuff.extraDefenceDice.black || 0;
|
||||
this.info.boss.defenseInfo.yellow += roundBuff.extraDefenceDice.yellow || 0;
|
||||
this.info.boss.defenseInfo.orange += roundBuff.extraDefenceDice.orange || 0;
|
||||
this.info.boss.defenseInfo.red += roundBuff.extraDefenceDice.red || 0;
|
||||
this.info.boss.hp += roundBuff.extraHp || 0;
|
||||
this.info.boss.unitRemainHp += roundBuff.extraHp || 0;
|
||||
this.info.boss.uiExtraTokenCount += roundBuff.extraTokenCount || 0;
|
||||
this.info.boss.uiExtraTokenCount2 += roundBuff.extraTokenCount2 || 0;
|
||||
}
|
||||
|
||||
}
|
||||
//#endregion Boss Fight
|
||||
|
||||
public skillParse(skillHtmlContent: string, targetRule: MobSkillTarget, onlyOneTarget: boolean = true) {
|
||||
let targetHeros = MD2Logic.getTargetHerosByFilter(this.info.heros, targetRule, onlyOneTarget);
|
||||
//if the skillHtmlContent contains ${Expression} replace it with the expression result
|
||||
//Example: ${1+1} will be replaced with 2
|
||||
//bossExtraTokenCount will be replaced with this.info.boss.uiExtraTokenCount
|
||||
//bossExtraTokenCount2 will be replaced with this.info.boss.uiExtraTokenCount2
|
||||
//targetHero.level will be replaced with the hero level
|
||||
//targetHero.hp will be replaced with the hero hp
|
||||
//targetHero.mp will be replaced with the hero mp
|
||||
//targetHero.ap will be replaced with the hero ap
|
||||
//targetHero.fireToken will be replaced with the hero fire token
|
||||
//targetHero.frozenToken will be replaced with the hero frozen token
|
||||
//targetHero.uiExtraTokenCount will be replaced with the hero extra token count
|
||||
//targetHero.uiExtraTokenCount2 will be replaced with the hero extra token count2
|
||||
|
||||
//For example:
|
||||
//${targetHero.level+bossExtraTokenCount} will be replaced with the number
|
||||
//then calculate the result and replace the ${Expression} with the result
|
||||
//Example: ${1+1} will be replaced with 2
|
||||
//${3-1}
|
||||
//${2*3}
|
||||
//${2/3}
|
||||
//${2%3}
|
||||
|
||||
|
||||
// No target heroes found, still process boss tokens
|
||||
let result = skillHtmlContent;
|
||||
|
||||
result = result.replace(/\bheroes.length\b/g, String(this.info?.heros?.length || 0));
|
||||
|
||||
if (result.includes('boss.') && this.info?.boss) {
|
||||
// Replace boss tokens even if no heroes
|
||||
result = result.replace(/\bboss.ExtraTokenCount\b/g, String(this.info?.boss?.uiExtraTokenCount || 0));
|
||||
result = result.replace(/\bboss.ExtraTokenCount2\b/g, String(this.info?.boss?.uiExtraTokenCount2 || 0));
|
||||
}
|
||||
// Evaluate remaining expressions (if any numbers remain)
|
||||
|
||||
|
||||
|
||||
if (result.includes('targetHero') && targetHeros && targetHeros.length > 0) {
|
||||
|
||||
// Use the first hero as targetHero for replacements
|
||||
const targetHero = targetHeros[0];
|
||||
|
||||
// Replace targetHero properties
|
||||
result = result.replace(/\btargetHero\.name\b/g, String(targetHero?.heroFullName || 0));
|
||||
result = result.replace(/\btargetHero\.level\b/g, String(targetHero?.level || 0));
|
||||
result = result.replace(/\btargetHero\.hp\b/g, String(targetHero?.hp || 0));
|
||||
result = result.replace(/\btargetHero\.mp\b/g, String(targetHero?.mp || 0));
|
||||
result = result.replace(/\btargetHero\.ap\b/g, String(targetHero?.ap || 0));
|
||||
result = result.replace(/\btargetHero\.fireToken\b/g, String(targetHero?.fireToken || 0));
|
||||
result = result.replace(/\btargetHero\.frozenToken\b/g, String(targetHero?.frozenToken || 0));
|
||||
// Map uiExtraTokenCount to extraToken and uiExtraTokenCount2 to extraToken2
|
||||
result = result.replace(/\btargetHero\.uiExtraTokenCount\b/g, String(targetHero?.extraToken || 0));
|
||||
result = result.replace(/\btargetHero\.uiExtraTokenCount2\b/g, String(targetHero?.extraToken2 || 0));
|
||||
|
||||
}
|
||||
|
||||
// Evaluate expressions in ${...} format
|
||||
result = this.evaluateExpressions(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private evaluateExpressions(content: string): string {
|
||||
// Match ${expression} pattern
|
||||
const expressionPattern = /\$\{([^}]+)\}/g;
|
||||
|
||||
return content.replace(expressionPattern, (match, expression) => {
|
||||
try {
|
||||
const sanitizedExpression = expression.trim();
|
||||
|
||||
// After placeholder replacement, expression should only contain numbers, operators, and spaces
|
||||
// Check if expression still contains letters (variables that weren't replaced)
|
||||
if (/[a-zA-Z_]/.test(sanitizedExpression)) {
|
||||
// Variables weren't replaced, return original match
|
||||
return match.replace('${', '').replace('}', '');
|
||||
}
|
||||
|
||||
// Validate expression contains only safe mathematical characters
|
||||
// Allow: numbers, decimal points, operators (+, -, *, /, %), parentheses, and spaces
|
||||
if (!/^[0-9+\-*/().%\s]+$/.test(sanitizedExpression)) {
|
||||
// Contains unsafe characters, return original
|
||||
return match;
|
||||
}
|
||||
|
||||
// Evaluate the expression safely using Function constructor
|
||||
// This is safer than eval() as it runs in strict mode
|
||||
const result = Function('"use strict"; return (' + sanitizedExpression + ')')();
|
||||
|
||||
// Check if result is a valid number
|
||||
if (typeof result === 'number' && !isNaN(result) && isFinite(result)) {
|
||||
return String(result);
|
||||
}
|
||||
|
||||
// Invalid result, return original match
|
||||
return match;
|
||||
} catch (error) {
|
||||
// If evaluation fails for any reason, return the original match
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class MD2GameInfo {
|
||||
@@ -461,8 +880,8 @@ export class MD2GameInfo {
|
||||
|
||||
this.mobs = this.mobs.map(m => new MobInfo(m));
|
||||
this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m));
|
||||
if (this.boss.info) {
|
||||
this.boss.info = new MobInfo(this.boss.info);
|
||||
if (this.boss) {
|
||||
this.boss = new MobInfo(this.boss);
|
||||
}
|
||||
this.heros = this.heros.map(h => new MD2HeroInfo(h));
|
||||
}
|
||||
@@ -472,8 +891,14 @@ export class MD2GameInfo {
|
||||
public mobs: MobInfo[] = [];
|
||||
public roamingMonsters: MobInfo[] = [];
|
||||
public heros: MD2HeroInfo[] = [];
|
||||
public disconnectedHeroes: MD2HeroInfo[] = [];
|
||||
public round = 1;
|
||||
public bossRound = 0;
|
||||
public roundPhase: RoundPhase = RoundPhase.HeroPhase;
|
||||
public showAttackBtn: boolean = false;
|
||||
public boss: IBossFight;
|
||||
public boss: MobInfo;
|
||||
|
||||
public enabledBundles: GameBundle[] = [GameBundle.CoreGame];
|
||||
public enableMobSpecialRule: boolean = false;
|
||||
public enableHeroBetrayal: boolean = false;
|
||||
}
|
||||
@@ -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,) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -23,18 +23,19 @@ export class GameRoomService {
|
||||
return {
|
||||
name: this.loginUserService.userAccess.firstName,
|
||||
id: this.loginUserService.userAccess.memberId,
|
||||
signalRClientId: this.loginUserService.userAccess.signalRSessionId,
|
||||
signalRClientId: this.loginUserService.userAccess.signalRConnectionId,
|
||||
isPlayer: true,
|
||||
gameRoomId: this.gameRoomId,
|
||||
tabId: this.loginUserService.sessionTabId
|
||||
} as IGamePlayer;
|
||||
}
|
||||
createGameRoom(name: string) {
|
||||
let gameRoom = this.currentPlayer();
|
||||
gameRoom.gameRoomId = gameRoom.id;
|
||||
this.gameRoomId = gameRoom.id;
|
||||
//Using current player to create game room
|
||||
let currentPlayer = this.currentPlayer();
|
||||
currentPlayer.gameRoomId = currentPlayer.id;
|
||||
this.gameRoomId = currentPlayer.id;
|
||||
this.http.post<boolean>(GAME_ROOM_URL,
|
||||
JSON.stringify(gameRoom), {
|
||||
JSON.stringify(currentPlayer), {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -14,14 +14,14 @@ export class LoginUserService {
|
||||
}
|
||||
|
||||
setSignalRConnectionId(id: string) {
|
||||
this.userAccess.signalRSessionId = id;
|
||||
this.userAccess.signalRConnectionId = id;
|
||||
}
|
||||
|
||||
public get sessionTabId(): string {
|
||||
return sessionStorage.getItem('tabId');
|
||||
return localStorage.getItem('tabId');
|
||||
}
|
||||
public set sessionTabId(v: string) {
|
||||
sessionStorage.setItem('tabId', v);
|
||||
localStorage.setItem('tabId', v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const SIGNAL_R_URL = (id: string = null) => { return `${environment.apiUrl}/${id
|
||||
})
|
||||
export class SignalRService {
|
||||
ReceivedSignalRMessageSubject = new Subject<SignalRMessage>();
|
||||
signalRMessageConnSubject = new Subject<any>();
|
||||
signalRMessageConnSubject = new Subject<SignalRConnectionState>();
|
||||
private hubConnection: signalR.HubConnection
|
||||
constructor(
|
||||
private loginUserService: LoginUserService,
|
||||
@@ -42,7 +42,10 @@ export class SignalRService {
|
||||
|
||||
}
|
||||
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([], {
|
||||
queryParams: {
|
||||
@@ -63,13 +66,21 @@ export class SignalRService {
|
||||
.withUrl(`${SIGNAL_R_URL('GameRoomHub')}?${params.toString()}`)
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
this.registerHubConnectionLifecycleHandlers();
|
||||
let me = this
|
||||
this.hubConnection
|
||||
.start()
|
||||
.then(() => {
|
||||
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.BroadcastAPIMessageReceive(JSON.parse(jsonString));
|
||||
});
|
||||
@@ -77,12 +88,37 @@ export class SignalRService {
|
||||
}
|
||||
public setSignalRConnectionId(connectionId: string) {
|
||||
|
||||
this.loginUserService.userAccess.signalRSessionId = connectionId;
|
||||
this.loginUserService.userAccess.signalRConnectionId = connectionId;
|
||||
this.loginUserService.signalRInitialized.next(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) {
|
||||
@@ -100,16 +136,23 @@ export class SignalRService {
|
||||
|
||||
}
|
||||
export interface SignalRMessage {
|
||||
from: SignalRSession;
|
||||
receiver: SignalRSession;
|
||||
from: SignalRClient;
|
||||
receiver: SignalRClient;
|
||||
actionType: string;
|
||||
actionName: string;
|
||||
parameters: { [key: string]: string; };
|
||||
value: any;
|
||||
jsonValue: string;
|
||||
}
|
||||
|
||||
export interface SignalRSession {
|
||||
sessionId: string;
|
||||
export interface SignalRClient {
|
||||
connectionId: string;
|
||||
name: string;
|
||||
isGroup: boolean;
|
||||
}
|
||||
|
||||
export interface SignalRConnectionState {
|
||||
status: 'connecting' | 'connected' | 'reconnecting' | 'reconnected' | 'closed' | 'error';
|
||||
connectionId?: string;
|
||||
error?: any;
|
||||
}
|
||||
@@ -12,6 +12,6 @@
|
||||
// background: nb-theme(color-primary-200) !important;
|
||||
// }
|
||||
|
||||
.label {
|
||||
color: #736f6f;
|
||||
}
|
||||
// .label {
|
||||
// color: #736f6f;
|
||||
// }
|
||||
|
||||
@@ -24,3 +24,12 @@ p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
k-editor-content p {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const urls = [
|
||||
'https://api.golife.love'
|
||||
];
|
||||
const LINE_CLIENT_ID = '1657422139';
|
||||
const dockerDebug = urls[0];
|
||||
const dockerDebug = urls[2];
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: dockerDebug,
|
||||
|
||||
+2
-1
@@ -6,8 +6,9 @@
|
||||
<title>真幸福的幸福小組</title>
|
||||
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<script defer
|
||||
|
||||
Reference in New Issue
Block a user