Some checks failed
CI / Get Changed Files (pull_request) Successful in 31s
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Successful in 23s
CI / prettier (pull_request) Failing after 27s
CI / eslint (pull_request) Successful in 31s
CI / test-build (pull_request) Successful in 49s
CI / Docker frontend validation (pull_request) Successful in 1m34s
216 lines
5.9 KiB
TypeScript
216 lines
5.9 KiB
TypeScript
import { ChangeDetectorRef, Component } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { LootboxService } from '../services/lootbox.service';
|
|
import { LootBox, Reward } from 'app/model/LootBox';
|
|
import { UserService } from '@service/user.service';
|
|
import { User } from 'app/model/User';
|
|
import { AuthService } from '@service/auth.service';
|
|
|
|
@Component({
|
|
selector: 'app-lootbox-opening',
|
|
standalone: true,
|
|
imports: [CommonModule],
|
|
templateUrl: './lootbox-opening.component.html',
|
|
styleUrls: ['./lootbox-opening.component.css'],
|
|
})
|
|
export default class LootboxOpeningComponent {
|
|
lootbox: LootBox | null = null;
|
|
isLoading = true;
|
|
error = '';
|
|
isOpening = false;
|
|
isOpen = false;
|
|
wonReward: Reward | null = null;
|
|
prizeList: Reward[] = [];
|
|
animationCompleted = false;
|
|
currentUser: User | null = null;
|
|
private winSound: HTMLAudioElement;
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private lootboxService: LootboxService,
|
|
private userService: UserService,
|
|
private authService: AuthService,
|
|
private cdr: ChangeDetectorRef
|
|
) {
|
|
this.winSound = new Audio('/sounds/win.mp3');
|
|
this.loadLootbox();
|
|
this.authService.userSubject.subscribe((user) => {
|
|
this.currentUser = user;
|
|
this.cdr.detectChanges();
|
|
});
|
|
}
|
|
|
|
private loadLootbox(): void {
|
|
const idParam = this.route.snapshot.paramMap.get('id');
|
|
if (!idParam) {
|
|
this.error = 'Invalid lootbox ID';
|
|
this.isLoading = false;
|
|
return;
|
|
}
|
|
|
|
const lootboxId = parseInt(idParam, 10);
|
|
this.lootboxService.getAllLootBoxes().subscribe({
|
|
next: (lootboxes) => {
|
|
this.lootbox = lootboxes.find((box) => box.id === lootboxId) || null;
|
|
this.isLoading = false;
|
|
this.cdr.detectChanges();
|
|
},
|
|
error: () => {
|
|
this.error = 'Failed to load lootbox data';
|
|
this.isLoading = false;
|
|
this.cdr.detectChanges();
|
|
},
|
|
});
|
|
}
|
|
|
|
openLootbox(): void {
|
|
if (!this.lootbox || this.isOpening) return;
|
|
|
|
// Check if user has enough balance
|
|
if (!this.hasEnoughBalance()) {
|
|
this.error = 'Nicht genug Guthaben, um diese Lootbox zu öffnen.';
|
|
window.scrollTo(0, 0);
|
|
this.cdr.detectChanges();
|
|
setTimeout(() => {
|
|
this.error = '';
|
|
this.cdr.detectChanges();
|
|
}, 5000);
|
|
return;
|
|
}
|
|
|
|
this.resetState(true);
|
|
|
|
if (this.lootbox.price) {
|
|
this.userService.updateLocalBalance(-this.lootbox.price);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
this.lootboxService.purchaseLootBox(this.lootbox!.id).subscribe({
|
|
next: this.handleRewardSuccess.bind(this),
|
|
error: this.handleRewardError.bind(this),
|
|
});
|
|
}, 100);
|
|
}
|
|
|
|
private handleRewardSuccess(reward: Reward): void {
|
|
this.wonReward = reward;
|
|
this.generateCasePrizes(reward);
|
|
this.isOpening = false;
|
|
this.isOpen = true;
|
|
|
|
this.cdr.detectChanges();
|
|
}
|
|
|
|
private handleRewardError(): void {
|
|
if (!this.lootbox) return;
|
|
|
|
const rewards = this.lootbox.rewards;
|
|
const fallback = rewards[Math.floor(Math.random() * rewards.length)];
|
|
|
|
this.wonReward = fallback;
|
|
this.generateCasePrizes(fallback);
|
|
this.isOpening = false;
|
|
this.isOpen = true;
|
|
this.cdr.detectChanges();
|
|
}
|
|
|
|
private resetState(isOpening = false): void {
|
|
this.isOpening = isOpening;
|
|
this.isOpen = false;
|
|
this.wonReward = null;
|
|
this.prizeList = [];
|
|
this.animationCompleted = false;
|
|
this.cdr.detectChanges();
|
|
}
|
|
|
|
generateCasePrizes(wonReward: Reward): void {
|
|
if (!this.lootbox) return;
|
|
|
|
const prizeCount = 120;
|
|
const winningPosition = Math.floor(prizeCount / 2);
|
|
const possibleRewards = this.lootbox.rewards;
|
|
const items: Reward[] = [];
|
|
|
|
for (let i = 0; i < prizeCount; i++) {
|
|
if (i === winningPosition) {
|
|
items.push({ ...wonReward });
|
|
} else {
|
|
items.push(this.getWeightedRandomReward(possibleRewards));
|
|
}
|
|
}
|
|
|
|
this.prizeList = items;
|
|
|
|
setTimeout(() => {
|
|
this.animationCompleted = true;
|
|
|
|
if (this.wonReward) {
|
|
this.winSound.play();
|
|
this.userService.updateLocalBalance(this.wonReward.value);
|
|
}
|
|
|
|
this.userService.refreshCurrentUser();
|
|
this.cdr.detectChanges();
|
|
}, 10000);
|
|
}
|
|
|
|
getWeightedRandomReward(rewards: Reward[]): Reward {
|
|
const totalProbability = rewards.reduce((sum, reward) => sum + reward.probability, 0);
|
|
const randomValue = Math.random() * totalProbability;
|
|
let cumulativeProbability = 0;
|
|
|
|
for (const reward of rewards) {
|
|
cumulativeProbability += reward.probability;
|
|
if (randomValue <= cumulativeProbability) {
|
|
return { ...reward };
|
|
}
|
|
}
|
|
|
|
return { ...rewards[0] };
|
|
}
|
|
|
|
openAgain(): void {
|
|
this.resetState();
|
|
this.openLootbox();
|
|
}
|
|
|
|
getBoxImage(id: number): string {
|
|
return `/images/${id}-box.png`;
|
|
}
|
|
|
|
goBack(): void {
|
|
this.router.navigate(['/game/lootboxes']);
|
|
}
|
|
|
|
isWonReward(reward: Reward): boolean {
|
|
if (!this.wonReward || !this.prizeList.length) return false;
|
|
|
|
const middleIndex = Math.floor(this.prizeList.length / 2);
|
|
return this.prizeList.indexOf(reward) === middleIndex;
|
|
}
|
|
|
|
getRewardRarityClass(reward: Reward): string {
|
|
if (!reward) return 'text-common';
|
|
|
|
const probability = reward.probability;
|
|
|
|
if (probability < 0.01) return 'text-mythic';
|
|
if (probability < 0.05) return 'text-legendary';
|
|
if (probability < 0.1) return 'text-epic';
|
|
if (probability < 0.2) return 'text-rare';
|
|
if (probability < 0.4) return 'text-uncommon';
|
|
return 'text-common';
|
|
}
|
|
|
|
getRewardClass(): string {
|
|
if (!this.wonReward || !this.lootbox) return '';
|
|
return this.wonReward.value > (this.lootbox.price || 0) ? 'text-emerald' : 'text-accent-red';
|
|
}
|
|
|
|
hasEnoughBalance(): boolean {
|
|
if (!this.currentUser || !this.lootbox) return false;
|
|
return this.currentUser.balance >= this.lootbox.price;
|
|
}
|
|
}
|