This commit is contained in:
Chris Chen 2025-11-12 18:22:33 -08:00
parent d8db9f650b
commit 2ef9968920
15 changed files with 238 additions and 61 deletions

View File

@ -7,6 +7,7 @@ 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";
@Injectable()
export abstract class MD2Base {
@ -95,38 +96,7 @@ 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':
@ -140,6 +110,18 @@ export abstract class MD2Base {
}
this.detectChanges();
break;
case 'heroes':
switch (message.actionName) {
case 'updateAll':
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);
});
break;
}
break;
case 'GameRoom':
switch (message.actionName) {
case 'Leaving':
@ -229,6 +211,40 @@ export abstract class MD2Base {
break;
}
}
updateHeroInfo(heroInfo: MD2HeroInfo) {
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;
//My hero update
if (this.isHeroDashboard && this.md2Service.loginUserService.sessionTabId == 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();
}
}
}
}
}
}
abstract heroAction(hero: MD2HeroInfo, action: string);
}

View File

@ -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>

View File

@ -55,6 +55,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[] = [];
@ -100,6 +104,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
}
initHero() {
this.gameRoomService.gameRoomId = this.roomId;
this.gameRoomService.joinGameRoom(this.roomId);
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) {
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
@ -156,12 +161,18 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
selectCurrentHero() {
if (this.currentSelectingHero) {
this.md2Service.playerJoin(this.currentSelectingHero);
this.md2Service.broadcastService.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) {
@ -189,7 +200,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
}
openDoor() {
this.md2Service.broadcastService.broadcastHeroAction('openDoor');
this.showMoveAction = false;
//this.showMoveAction = false;
this.detectChanges();
}
moveAction() {

View File

@ -63,19 +63,20 @@
<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 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:
{{hero.remainActions}}</span>
<span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating">Inactive</span>
<span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</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>

View File

@ -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';
@ -25,6 +25,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,
@ -148,7 +149,12 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
this.md2Service.broadcastService.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();
}
});
}

View File

@ -37,6 +37,10 @@ export enum HeroClass {
Shaman,
Paladin,
Druid,
Necromancer,
Monk,
Thinker,
Bard
}
export enum MobType {
Mob,

View File

@ -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>

View File

@ -10,10 +10,22 @@
<md2-mob-stand-info [mob]="mob" [mode]="mode"></md2-mob-stand-info>
<div *ngIf="activeSkill">
<div class="alert alert-warning" role="alert">
<!-- <div class="alert alert-warning" role="alert">
<h4>{{activeSkill.name}}</h4>
<div [innerHtml]="activeSkill.description"></div>
</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">

View File

@ -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;
}
}

View File

@ -66,7 +66,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
this.mob.actions = 1;
} else {
this.mob.actions = 2;
this.activeSkill = { name: 'Normal Action', description: '${this.mob.name} Gains 2 Actions.' } as MD2MobSkill;
this.activeSkill = { name: 'Normal Action', description: `${this.mob.name} Gains 2 Actions.` } as MD2MobSkill;
//this.actionInfoHtml = `${this.mob.name} Gains 2 Actions.`;
}
}
@ -113,8 +113,8 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
return of({ result: true, skill } as SkillResolutionResult);
}
const title = skill.name || 'Resolve Skill?';
const prompt = skill.skillCondition || skill.description || 'Resolve this skill?';
const title = 'Resolve Skill?';
const prompt = skill.skillCondition;
return this.msgBoxService.show(title, {
text: prompt,

View File

@ -26,8 +26,14 @@ export class MD2BroadcastService {
return this.stateService.playerHero;
}
public broadcastAllHeroInfoToAll() {
this.stateService.info.heros.forEach(element => {
this.broadcastHeroInfoToAll(element);
let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'heroes',
actionName: 'updateAll',
} as SignalRMessage;
message.parameters = { heros: JSON.stringify(this.stateService.info.heros) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
this.broadcastMobsInfo();
}

View File

@ -73,7 +73,7 @@ export class MD2Service {
public fileService: FileService,
public msgBoxService: MsgBoxService,
private gameRoomService: GameRoomService,
private loginUserService: LoginUserService,
public loginUserService: LoginUserService,
public stateService: MD2StateService,
public signalRService: SignalRService,
public dlgService: NbDialogService,

View File

@ -18,10 +18,10 @@ export class LoginUserService {
}
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);
}
}

View File

@ -42,7 +42,9 @@ export class SignalRService {
}
public startSignalRConnection(gameRoomId: string = '') {
this.loginUserService.sessionTabId = UuidUtils.generate();
if (!this.loginUserService.sessionTabId) {
this.loginUserService.sessionTabId = UuidUtils.generate();
}
const tree = this.router.createUrlTree([], {
queryParams: {

View File

@ -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,