Ajout de composants Hero Wars

This commit is contained in:
2025-11-27 10:35:38 +01:00
parent 8e3bd16b60
commit 8a1c19e011
66 changed files with 4137 additions and 7 deletions
+1 -1
View File
@@ -1 +1 @@
<app-full-layout></app-full-layout> <app-full-layout [class]="appClass"></app-full-layout>
+8
View File
@@ -0,0 +1,8 @@
.grayscale {
filter: grayscale(1);
transition: all 2s ease;
}
.colored {
filter: grayscale(0);
transition: all 5s ease;
}
+7 -2
View File
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, AfterContentInit } from '@angular/core';
import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, provideNativeDateAdapter } from '@angular/material/core'; import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, provideNativeDateAdapter } from '@angular/material/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
@@ -31,8 +31,9 @@ export const MY_FORMATS = {
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss' styleUrl: './app.component.scss'
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit, AfterContentInit {
public title = 'Ad Astra'; public title = 'Ad Astra';
public appClass = 'grayscale';
constructor(private userService: UserService, private titleService: Title) { } constructor(private userService: UserService, private titleService: Title) { }
@@ -40,4 +41,8 @@ export class AppComponent implements OnInit {
this.userService.populate(); this.userService.populate();
this.titleService.setTitle(this.title); this.titleService.setTitle(this.title);
} }
ngAfterContentInit() {
this.appClass = 'colored';
}
} }
@@ -0,0 +1,114 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<span class="fs-5 text-primary">{{ now | date: 'dd/MM/yyyy' }}</span>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
<div class="d-flex">
<p class="fs-3">{{description}}</p>
</div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<mat-card class="my-3">
<mat-card-header class="d-flex rounded-top bg-navy-light text-bg-navy-light py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="table-responsive p-0">
<mat-stepper headerPosition="top" #stepper>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 1</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 1</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">Power limit: 320k</h4>
</div>
</div>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 2</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 2</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">Power limit: 500k</h4>
</div>
</div>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 3</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 3</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">No power limit</h4>
</div>
</div>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="search">
<ng-template matStepLabel>Statistics</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Statistics</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">variation ≥ {{ minVarPct }}%</h4>
<mat-form-field appearance="outline">
<mat-label>Variation</mat-label>
<input matInput [(ngModel)]="minVarPct" type="number" min="{{ minValue }}">
<mat-icon matSuffix>percent</mat-icon>
<mat-hint>excess power in %</mat-hint>
</mat-form-field>
</div>
</div>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
<ng-template matStepperIcon="timeline">
<mat-icon>timeline</mat-icon>
</ng-template>
<ng-template matStepperIcon="search">
<mat-icon>manage_search</mat-icon>
</ng-template>
</mat-stepper>
</mat-card-content>
</mat-card>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HerowarsGuildraidComponent } from './herowars-guildraid.component';
describe('HerowarsGuildraidComponent', () => {
let component: HerowarsGuildraidComponent;
let fixture: ComponentFixture<HerowarsGuildraidComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HerowarsGuildraidComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HerowarsGuildraidComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,94 @@
import { DatePipe } from '@angular/common';
import { Title } from '@angular/platform-browser';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, Injectable, inject, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatStepperIntl, MatStepperModule } from '@angular/material/stepper';
import { ListErrorsComponent } from 'src/app/components/shared';
import { Errors } from 'src/app/core/models';
import { HWGuildRaid, HWGuildRaidAttacker, HWGuildRaidAttackers, HWGuildRaidStage, HWMember } from 'src/app/core/models/herowars';
import guildRaids from 'src/files-data/hw-guild-raids.json'; // page Asgard -> Guild Raid -> Log
@Injectable()
export class GuildRaidStepperIntl extends MatStepperIntl {
// the default optional label text, if unspecified is "Optional"
override optionalLabel = 'Optional Label';
}
@Component({
selector: 'app-herowars-guildraid',
standalone: true,
providers: [
{
provide: STEPPER_GLOBAL_OPTIONS,
useValue: { displayDefaultIndicatorType: false },
},
],
imports: [
DatePipe, FormsModule, ListErrorsComponent,
MatButtonModule, MatCardModule, MatDividerModule, MatFormFieldModule,
MatIconModule, MatInputModule, MatStepperModule
],
templateUrl: './herowars-guildraid.component.html',
styleUrl: './herowars-guildraid.component.scss',
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateY(0)' })),
transition('void => *', [
style({ transform: 'translateY(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateY(100%)' }),
animate(200)
])
])
]
})
export class HerowarsGuildraidComponent implements OnInit {
public title = 'Guild Raids';
public subtitle = 'Log';
public description = 'Tools in working progress.';
public errors: Errors = { errors: {} };
public destroyRef = inject(DestroyRef);
public minVarPct = 3;
public minValue = 0;
public now = new Date();
private _guildMembers: HWMember[] = [];
private _matStepperIntl = inject(MatStepperIntl);
private _guildRaids: HWGuildRaid[] = [];
private _raidStages: HWGuildRaidStage[] = [
{ num: 1, name: 'Stage 1', maxPower: 320000, duration: 8, startTime: 0, endTime: 0 },
{ num: 2, name: 'Stage 2', maxPower: 500000, duration: 8, startTime: 0, endTime: 0 },
{ num: 3, name: 'Stage 3', maxPower: 0, duration: 0, startTime: 0, endTime: 0 }
];
constructor(
private _titleService: Title
) { }
ngOnInit() {
this._titleService.setTitle(this.title);
this._guildMembers.map((data) => {
data.raids = [];
data.raidsInfo = {
variationAvg: 0,
variationSum: 0
};
return data;
});
}
getMembers(): HWMember[] {
return this._guildMembers;
}
}
@@ -0,0 +1,209 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<span class="fs-5 text-primary">{{ now | date: 'dd/MM/yyyy' }}</span>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
<div class="d-flex">
<p class="fs-3">{{description}}</p>
</div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<mat-tab-group [selectedIndex]="0" color="warn">
<mat-tab label="Clan">
<div class="row g-0">
<div class="col-12 pe-md-2 border-1 border-top">
<!--<app-guildwar-attack [members]="getMembers()"></app-guildwar-attack>-->
<div class="d-flex mt-3">
<div class="flex-fill">
<h2>
@if (clanLoaded) {
<span class="border-1 border-bottom">{{ getClan().title }} <span class="fs-4"> Server {{ getClan().serverId }}</span></span>
}
</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<span class="fs-4">Members: {{ getClan().membersCount }}/30</span>
</div>
</div>
<div class="row g-0 my-3">
<div class="col-2">
<div class="d-flex align-items-start flex-column me-1" role="group" aria-label="Vertical button group">
@if (clanLoaded) {
<button mat-stroked-button color="warn" (click)="syncClan()" class="mb-1">Sync clan</button>
}
@else {
<button mat-stroked-button color="megna" (click)="loadClan()" class="mb-1">Load clan</button>
}
@if (membersLoaded) {
<button mat-stroked-button color="warn" (click)="syncMembers()" class="mb-1">Sync members</button>
}
@else {
<button mat-stroked-button color="megna" (click)="loadMembers()" [disabled]="!clanLoaded" class="mb-1">Load members</button>
}
</div>
</div>
<div class="col-5">
<dl class="dl-horizontal m-0">
<dt>country</dt>
<dd>{{ getClan().country }}</dd>
<dt>serverId</dt>
<dd>{{ getClan().serverId }}</dd>
<dt>description</dt>
<dd>{{ getClan().description }}</dd>
<dt>level</dt>
<dd>{{ getClan().level }}</dd>
<dt>minLevel</dt>
<dd>{{ getClan().minLevel }}</dd>
</dl>
</div>
<div class="col-5">
<dl class="dl-horizontal m-0">
<dt>topActivity</dt>
<dd>{{ getClan().topActivity | number : '1.0' }}</dd>
<dt>topDungeon</dt>
<dd>{{ getClan().topDungeon | number : '1.0' }}</dd>
<dt>disbanding</dt>
<dd>{{ getClan().disbanding }}</dd>
<dt>frameId</dt>
<dd>{{ getClan().frameId }}</dd>
<dt>ownerId</dt>
<dd>{{ getClan().ownerId }}</dd>
</dl>
</div>
</div>
<div class="table-responsive p-0">
<table class="table table-striped table-condensed border-top border-light">
<thead>
<tr role="row" class="text-bg-raspberry bg-opacity-60">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
ID
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Name
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Clan
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Server
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Last Login
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Level
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Heroes
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-end">
Titans
</div>
</th>
</tr>
</thead>
<tbody>
@for (member of getMembers(); track member; let index = $index) {
<tr role="row" class="align-middle">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell font-monospace text-start">
<span class="me-3">{{ member.id }}</span>
</td>
<td class="cell text-start">
{{ member.name }}
</td>
<td class="cell text-start">
{{ member.clanTitle }}
</td>
<td class="cell text-start">
{{ member.serverId }}
</td>
<td class="cell font-monospace text-start">
<span class="me-3">{{ member.lastLoginTime + '000' | date: 'dd/MM HH:mm' }}</span>
</td>
<td class="cell font-monospace text-start">
{{ member.level }}
</td>
<td class="cell font-monospace text-end">
{{ member.heroes.power | number : '1.0' }}
</td>
<td class="cell font-monospace text-end pe-4">
{{ member.titans.power | number : '1.0' }}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</mat-tab>
<mat-tab label="Attack">
<div class="row g-0">
<div class="col col-md-6 pe-md-2">
<!--<app-guildwar-defence [members]="getMembers()" type="heroes"></app-guildwar-defence>-->
</div>
<div class="col col-md-6 ps-md-2">
<!--<app-guildwar-defence [members]="getMembers()" type="titans"></app-guildwar-defence>-->
</div>
</div>
</mat-tab>
<mat-tab label="Defense">
<div class="row g-0">
<div class="col">
<mat-card class="my-3">
<mat-card-header class="d-flex rounded-top text-bg-raspberry py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="table-responsive p-0">
</mat-card-content>
</mat-card>
</div>
</div>
</mat-tab>
<mat-tab label="Info">
<div class="row g-0">
<div class="col">
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
@@ -0,0 +1,8 @@
.btn-group-vertical {
flex-direction: column;
align-items: flex-end;
justify-content: center;
button {
margin: 2px 0;
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HerowarsGuildwarComponent } from './herowars-guildwar.component';
describe('HerowarsGuildwarComponent', () => {
let component: HerowarsGuildwarComponent;
let fixture: ComponentFixture<HerowarsGuildwarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HerowarsGuildwarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HerowarsGuildwarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,207 @@
import { DatePipe, DecimalPipe } from '@angular/common';
import { Title } from '@angular/platform-browser';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatTabsModule } from '@angular/material/tabs';
import { ListErrorsComponent, GuildwarAttackComponent, GuildwarDefenceComponent } from 'src/app/components/shared';
import { Errors, HWActivityStat, HWGuildClan, HWMember, HWMemberStat, HWWeekStat } from 'src/app/core/models';
import { UtilitiesService } from 'src/app/core/services';
import guildData from 'src/files-data/hw-guild-data.json'; // page Membres
import guildStatistics from 'src/files-data/hw-guild-statistics.json'; // page Overview -> Statistics
@Component({
selector: 'app-herowars-guildwar',
standalone: true,
imports: [
DatePipe, DecimalPipe, FormsModule, ListErrorsComponent,
MatButtonModule, MatCardModule, MatDividerModule, MatFormFieldModule,
MatIconModule, MatInputModule, MatTabsModule,
GuildwarAttackComponent, GuildwarDefenceComponent
],
templateUrl: './herowars-guildwar.component.html',
styleUrl: './herowars-guildwar.component.scss',
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateY(0)' })),
transition('void => *', [
style({ transform: 'translateY(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateY(100%)' }),
animate(200)
])
])
]
})
export class HerowarsGuildwarComponent implements OnInit {
public title = 'Guild War';
public subtitle = 'Log';
public description = 'Tools in working progress.';
public errors: Errors = { errors: {} };
public destroyRef = inject(DestroyRef);
public now = new Date();
public daysToWarn = 1;
public clanLoaded = false;
public membersLoaded = false;
private _guildClan: HWGuildClan = {} as HWGuildClan;
private _guildMembers: HWMember[] = [];
private _guildInfo: HWActivityStat = {} as HWActivityStat;
constructor(
private _titleService: Title,
private _utilitiesService: UtilitiesService
) { }
ngOnInit() {
this._titleService.setTitle(this.title);
}
private _resetActivity(): HWActivityStat {
const activity: HWActivityStat = { activity: 0, prestige: 0, titanite: 0, war: 0, adventure: 0, gifts: 0, score: 0, scoreGifts: 0, warGifts: 0, rewards: 0 };
return activity;
}
private _importMembers() {
try {
const oneDayInSec = (24 * 60 * 60);
const daysToKick = parseInt(guildData.clan.daysToKick);
this.daysToWarn = (parseInt(guildData.clan.daysToKick) / 2);
for (const data of Object.entries(guildData.clan.members)) {
const member: HWMember = {} as HWMember;
Object.assign(member, data[1]);
if (guildData.clan.warriors.indexOf(parseInt(member.id)) !== -1) {
member.champion = true;
} else {
member.champion = false;
}
member.heroes = { power: 0, teams: []};
member.titans = { power: 0, teams: []};
this._guildMembers.push(member);
}
this._guildMembers.map((data) => {
const last = new Date((parseInt(data.lastLoginTime) * 1000));
const exp = new Date(last.getTime() + ((daysToKick * oneDayInSec) * 1000));
const diff = Math.floor(((exp.getTime() - this.now.getTime()) / 1000));
const daysLeft = Math.floor((diff / oneDayInSec));
let timeLeft = diff;
if (daysLeft >= 1) {
timeLeft = (diff - (daysLeft * oneDayInSec));
}
data.inactivity = {
daysLeft: daysLeft,
timeLeft: timeLeft,
dropDelay: `${daysLeft}d, ${this._utilitiesService.secondsToDuration(timeLeft)}`,
dropDate: Math.floor((exp.getTime() / 1000))
};
data.raids = [];
data.raidsInfo = {
variationAvg: 0,
variationSum: 0
};
data.stat = {} as HWMemberStat;
let index = guildData.membersStat.findIndex(stat => stat.userId === data.id);
if (index !== -1) {
delete (guildData.membersStat[index] as {userId?: string}).userId;
Object.assign(data.stat, guildData.membersStat[index]);
//data.stat = guildData.membersStat[index];
}
index = guildStatistics.stat.findIndex(stat => stat.id === data.id);
if (index !== -1) {
delete (guildStatistics.stat[index] as {id?: string}).id;
const stat: HWWeekStat = {} as HWWeekStat;
Object.assign(stat, guildStatistics.stat[index]);
data.weekStat = stat;
data.adventureSum = guildStatistics.stat[index].adventureStat.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
data.clanGiftsSum = guildStatistics.stat[index].clanGifts.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
data.clanWarSum = guildStatistics.stat[index].clanWarStat.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
this._guildClan.sumTotal.adventure += data.adventureSum;
this._guildClan.sumTotal.gifts += data.clanGiftsSum;
this._guildClan.sumTotal.war += data.clanWarSum;
}
data.score = (data.stat.dungeonActivitySum + data.stat.activitySum + data.stat.prestigeSum);
data.warGifts = (((this._guildClan.giftsCount / 2) * ((data.clanWarSum * 10) / 100)) / 20);
this._guildClan.todayTotal.activity += data.stat.todayActivity;
this._guildClan.todayTotal.prestige += data.stat.todayPrestige;
this._guildClan.todayTotal.titanite += data.stat.todayDungeonActivity;
this._guildClan.todayTotal.score += (data.stat.todayDungeonActivity + data.stat.todayActivity + data.stat.todayPrestige);
this._guildClan.sumTotal.activity += data.stat.activitySum;
this._guildClan.sumTotal.prestige += data.stat.prestigeSum;
this._guildClan.sumTotal.titanite += data.stat.dungeonActivitySum;
this._guildClan.sumTotal.score += data.score;
this._guildClan.sumTotal.warGifts += data.warGifts;
data.scoreGifts = 0;
data.rewards = 0;
return data;
});
this._guildClan.sumTotal.scoreGifts = (this._guildClan.giftsCount - Math.floor(this._guildClan.sumTotal.warGifts));
this._guildClan.todayAverages.activity = (this._guildClan.todayTotal.activity / this._guildMembers.length);
this._guildClan.todayAverages.prestige = (this._guildClan.todayTotal.prestige / this._guildMembers.length);
this._guildClan.todayAverages.titanite = (this._guildClan.todayTotal.titanite / this._guildMembers.length);
this._guildClan.todayAverages.score = (this._guildClan.todayTotal.score / this._guildMembers.length);
this._guildClan.sumAverages.activity = (this._guildClan.sumTotal.activity / this._guildMembers.length);
this._guildClan.sumAverages.prestige = (this._guildClan.sumTotal.prestige / this._guildMembers.length);
this._guildClan.sumAverages.titanite = (this._guildClan.sumTotal.titanite / this._guildMembers.length);
this._guildClan.sumAverages.war = (this._guildClan.sumTotal.war / 20);
this._guildClan.sumAverages.adventure = (this._guildClan.sumTotal.adventure / this._guildMembers.length);
this._guildClan.sumAverages.gifts = (this._guildClan.sumTotal.gifts / this._guildMembers.length);
this._guildClan.sumAverages.score = (this._guildClan.sumTotal.score / this._guildMembers.length);
this._guildMembers.map((data) => {
data.scoreGifts = ((data.score / this._guildClan.sumTotal.score) * this._guildClan.sumTotal.scoreGifts);
data.rewards = Math.floor((data.scoreGifts + data.warGifts));
this._guildClan.sumTotal.rewards += data.rewards;
return data;
});
this._guildClan.sumAverages.rewards = (this._guildClan.sumTotal.rewards / this._guildMembers.length);
console.log(this._guildClan);
console.log(this._guildMembers);
} catch (error) {
console.error(error);
}
}
getClan(): HWGuildClan {
return this._guildClan;
}
loadClan(): void {
Object.assign(this._guildClan, guildData.clan);
this._guildClan.sumAverages = this._resetActivity();
this._guildClan.sumTotal = this._resetActivity();
this._guildClan.todayAverages = this._resetActivity();
this._guildClan.todayTotal = this._resetActivity();
this.clanLoaded = true;
}
loadMembers(): void {
this._importMembers();
this.membersLoaded = true;
}
getMembers(): HWMember[] {
return this._guildMembers;
}
syncClan(): void {
console.log(this._guildClan);
}
syncMembers(): void {
console.log(this._guildMembers);
}
}
@@ -0,0 +1,66 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<span class="fs-5 text-primary">{{ now | date: 'dd/MM/yyyy' }}</span>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
<div class="d-flex">
<p class="fs-3">{{description}}</p>
</div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<mat-tab-group [selectedIndex]="3">
<mat-tab label="Guild War">
<div class="row g-0">
<div class="col col-md-6 pe-md-2">
<app-guildwar-defence [members]="getMembers()" type="heroes"></app-guildwar-defence>
</div>
<div class="col col-md-6 ps-md-2">
<app-guildwar-defence [members]="getMembers()" type="titans"></app-guildwar-defence>
</div>
</div>
<div class="row g-0">
<div class="col-12 pe-md-2">
<!--<app-guildwar-attack [members]="getMembers()"></app-guildwar-attack>-->
</div>
</div>
</mat-tab>
<mat-tab label="Champions">
<div class="row g-0">
<div class="col col-md-6 pe-md-2">
<div class="row">
<div class="col col-md-6 pe-md-2">
<app-guildwar-champions [members]="getMembers()" type="heroes"></app-guildwar-champions>
</div>
<div class="col col-md-6 ps-md-2">
<app-guildwar-champions [members]="getMembers()" type="titans"></app-guildwar-champions>
</div>
</div>
<app-guild-card></app-guild-card>
</div>
<div class="col col-md-6 ps-md-2">
<app-guildwar-teams [members]="getMembers()"></app-guildwar-teams>
</div>
</div>
</mat-tab>
<mat-tab label="Members">
<div class="row g-0">
<div class="col">
<app-members-statistics [members]="getMembers()"></app-members-statistics>
</div>
</div>
</mat-tab>
<mat-tab label="Raids">
<div class="row g-0">
<div class="col">
<app-guildraids-log [members]="getMembers()"></app-guildraids-log>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
@@ -0,0 +1,37 @@
/* herowars */
//@use 'variable' as var;
.icon-xxsmall {
transform: scale(0.25);
}
.icon-xsmall {
font-size: 16px;
height: 16px;
width: 16px;
}
.icon-small {
font-size: 18px;
height: 18px;
width: 18px;
}
.icon-medium {
font-size: 20px;
height: 20px;
width: 20px;
}
.hstack .value {
min-width: 56px;
}
/*
#FF41F8
#F0060B
#F0070C
#CC26D5
#7702FF
#35A2EB
#B7DDD0
#B0D9CB
#D9CAAE
#9788C7
*/
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HerowarsComponent } from './herowars.component';
describe('HerowarsComponent', () => {
let component: HerowarsComponent;
let fixture: ComponentFixture<HerowarsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HerowarsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HerowarsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,84 @@
import { DatePipe } from '@angular/common';
import { Title } from '@angular/platform-browser';
import { trigger, state, style, animate, transition } from '@angular/animations';
//import { Component, DestroyRef, inject, OnInit, computed, signal, Signal } from '@angular/core';
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import {
ListErrorsComponent,
GuildCardComponent, GuildraidsLogComponent, GuildwarChampionsComponent,
GuildwarAttackComponent, GuildwarDefenceComponent, GuildwarTeamsComponent, MembersStatisticsComponent
} from 'src/app/components/shared';
import { Errors, HWMember } from 'src/app/core/models';
import guildData from 'src/files-data/hw-guild-data.json'; // page Membres
//import guildWarData from 'src/files-data/hw-guild-war.json'; // page Overview -> Statistics | page Guild War -> Guild War
//import guildStatistics from 'src/files-data/hw-guild-statistics.json'; // page Overview -> Statistics
//import guildWarSlots from 'src/files-data/hw-guild-war-slots.json'; // no pages
@Component({
selector: 'app-herowars',
standalone: true,
imports: [
DatePipe,
MatCardModule, MatDividerModule, MatIconModule, MatTabsModule,
ListErrorsComponent,
GuildCardComponent, GuildraidsLogComponent, GuildwarChampionsComponent,
GuildwarAttackComponent, GuildwarDefenceComponent, GuildwarTeamsComponent, MembersStatisticsComponent
],
templateUrl: './herowars.component.html',
styleUrl: './herowars.component.scss',
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateY(0)' })),
transition('void => *', [
style({ transform: 'translateY(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateY(100%)' }),
animate(200)
])
])
]
})
export class HerowarsComponent implements OnInit {
public title = 'HeroWars GM Tools';
public description = 'Tools in working progress.';
public errors: Errors = { errors: {} };
public destroyRef = inject(DestroyRef);
public now = new Date();
private _guildMembers: HWMember[] = [];
constructor(
private _titleService: Title
) { }
ngOnInit() {
try {
this._titleService.setTitle(this.title);
for (const data of Object.entries(guildData.clan.members)) {
const member: HWMember = {} as HWMember;
Object.assign(member, data[1]);
if (guildData.clan.warriors.indexOf(parseInt(member.id)) !== -1) {
member.champion = true;
} else {
member.champion = false;
}
member.heroes = { power: 0, teams: []};
member.titans = { power: 0, teams: []};
this._guildMembers.push(member);
}
} catch (error) {
console.error(error);
}
}
getMembers(): HWMember[] {
return this._guildMembers;
}
}
+2
View File
@@ -6,6 +6,8 @@ export * from './dashboard/dashboard.component';
export * from './demo/demo.component'; export * from './demo/demo.component';
export * from './dropzones/dropzones.component'; export * from './dropzones/dropzones.component';
export * from './herowars/herowars.component'; export * from './herowars/herowars.component';
export * from './herowars-guildwar/herowars-guildwar.component';
export * from './herowars-guildraid/herowars-guildraid.component';
export * from './home/home.component'; export * from './home/home.component';
export * from './jump/jump.component'; export * from './jump/jump.component';
export * from './jumps/jumps.component'; export * from './jumps/jumps.component';
@@ -0,0 +1,37 @@
<mat-card-subtitle>
<span class="border-bottom border-navy text-navy fw-normal fs-5">{{ data.name }}</span>
<small class="font-monospace text-navy float-end">[ {{ data.count | number : '1.0' }} ]</small>
</mat-card-subtitle>
<ul class="list-unstyled mt-2">
@for (champion of data.teams; track champion; let index = $index) {
<li [class]="(champion.id !== data.positions[data.slots[(index)]].toString()) ? 'text-red' : ''">
<span class="slot">slot {{data.slots[(index)]}}</span>
@if (champion.id !== data.positions[data.slots[(index)]].toString()) {
<span class="text-decoration-line-through">{{ getMemberNameById(data.positions[data.slots[(index)]].toString()) }}</span>
<mat-icon fontIcon="swap_horiz" class="mx-3 align-middle icon-xsmall"></mat-icon>
}
<span>{{ champion.name }}</span>
<span class="font-monospace float-end">
@switch (teamType) {
@case ('heroes') {
@if (champion.id !== data.positions[data.slots[(index)]].toString()) {
<span class="text-decoration-line-through">{{ getMemberHeroesPowerById(data.positions[data.slots[(index)]].toString()) | number : '1.0' }}</span>
<mat-icon fontIcon="swap_horiz" class="mx-3 align-middle icon-xsmall"></mat-icon>
}
{{ champion.heroes.power | number : '1.0' }}
}
@case ('titans') {
@if (champion.id !== data.positions[data.slots[(index)]].toString()) {
<span class="text-decoration-line-through">{{ getMemberTitansPowerById(data.positions[data.slots[(index)]].toString()) | number : '1.0' }}</span>
<mat-icon fontIcon="swap_horiz" class="mx-3 align-middle icon-xsmall"></mat-icon>
}
{{ champion.titans.power | number : '1.0' }}
}
@default {
0
}
}
</span>
</li>
}
</ul>
@@ -0,0 +1,13 @@
.slot {
min-width: 60px;
display: inline-block;
}
.mat-mdc-standard-chip {
--mdc-chip-container-height: 28px;
--mdc-chip-container-shape-radius: 8px 8px 8px 8px;
--mdc-chip-label-text-color: inherit;
--mdc-chip-label-text-line-height: 20px;
--mdc-chip-label-text-size: 13px;
--mdc-chip-label-text-weight: 400;
--mdc-chip-with-avatar-avatar-shape-radius: 6px 6px 6px 6px;
}
@@ -0,0 +1,83 @@
import { Component, Input } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
//import { Errors } from 'src/app/core/models';
//import { HWMember, HWGuildWarFortification, HWGuildWarHeroTeam, HWGuildWarTitanTeam } from 'src/app/core/models';
import { HWGuildWarFortification, HWMember } from 'src/app/core/models';
@Component({
selector: 'app-fortification-card-content',
templateUrl: './fortification-card-content.component.html',
styleUrl: './fortification-card-content.component.scss',
standalone: true,
imports: [
DecimalPipe,
MatCardModule, MatChipsModule, MatIconModule
]
})
export class FortificationCardContentComponent {
public data: HWGuildWarFortification = {} as HWGuildWarFortification;
public teamType: string = '';
public guildMembers: HWMember[] = [];
//public errorList: string[] = [];
@Input() set fortification(data: HWGuildWarFortification) {
this.data = data;
}
@Input() set type(value: string) {
this.teamType = value;
}
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
getMemberNameById(id: string): string {
let name = '';
this.guildMembers.some((item) => {
if (item.id === id) {
name = item.name;
return true;
}
return false;
});
return name;
}
getMemberHeroesPowerById(id: string): number {
let power = 0;
this.guildMembers.some((item) => {
if (item.id === id) {
power = item.heroes.power;
return true;
}
return false;
});
return power;
}
getMemberTitansPowerById(id: string): number {
let power = 0;
this.guildMembers.some((item) => {
if (item.id === id) {
power = item.titans.power;
return true;
}
return false;
});
return power;
}
/*
@Input() set errors(errorList: Errors | null) {
this.errorList = errorList
? Object.keys(errorList.errors || {}).map(
(key) => `${key} ${errorList.errors[key]}`,
)
: [];
}
*/
}
@@ -0,0 +1,72 @@
<mat-card class="mb-3">
<mat-card-header class="bg-raspberry text-bg-primary d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ guildData.clan.title }} <span class="ms-2 me-2 fw-light opacity-75">|</span><small class="fs-5 fw-light opacity-75">server {{ guildData.clan.serverId }}</small>
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ guildData.clan.membersCount }}/30</span>
</div>
</mat-card-header>
<mat-card-content>
<div class="row mt-2">
<div class="col-xs-12 col-md-6">
<span class="border-bottom border-raspberry text-raspberry fs-5">Server/Season Dates</span>
<dl class="dl-horizontal dl-large mt-3 mb-0">
<dt>Server Reset Time</dt>
<dd class="font-monospace">{{ (guildData.serverResetTime*1000) | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
<dt>War End Season Time</dt>
<dd class="font-monospace">{{ (guildData.clanWarEndSeasonTime*1000) | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
<dt>Free Clan Change Start</dt>
<dd class="font-monospace">{{ (guildData.freeClanChangeInterval.start * 1000) | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
<dt>Free Clan Change End</dt>
<dd class="font-monospace">{{ (guildData.freeClanChangeInterval.end * 1000) | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
</dl>
</div>
<div class="col-xs-12 col-md-6">
<span class="border-bottom border-raspberry text-raspberry fs-5">Guild Overview</span>
<dl class="dl-horizontal dl-large mt-3 mb-0">
<dt>Members</dt>
<dd class="font-monospace text-end">{{ guildData.clan.membersCount }}/30</dd>
<dt>Champions</dt>
<dd class="font-monospace text-end">{{ guildData.clan.warriors.length }}/20</dd>
<dt>Top Activity</dt>
<dd class="font-monospace text-end">{{ guildData.clan.topActivity | number : '1.0' }}</dd>
<dt>Top Dungeon</dt>
<dd class="font-monospace text-end">{{ guildData.clan.topDungeon | number : '1.0' }}</dd>
</dl>
</div>
</div>
<mat-divider class="my-2"></mat-divider>
<div class="row">
<div class="col-xs-12 col-md-6">
<span class="border-bottom border-raspberry text-raspberry fs-5">Guild Settings</span>
<dl class="dl-horizontal dl-large mt-2 mb-0">
<dt>Guild Min. Level</dt>
<dd class="font-monospace">{{ guildData.clan.minLevel }}</dd>
<dt>Dismissal Delay</dt>
<dd class="font-monospace">{{ guildData.clan.daysToKick }} days</dd>
<dt>Guild Master Gifts</dt>
<dd class="font-monospace">{{ guildData.clan.giftsCount }}</dd>
<dt>Adventure Stat</dt>
<dd class="font-monospace">{{ guildData.stat.adventureStat }}</dd>
</dl>
</div>
<div class="col-xs-12 col-md-6">
<span class="border-bottom border-raspberry text-raspberry fs-5">Solide Overview</span>
<dl class="dl-horizontal dl-large mt-2 mb-0">
<dt>Today Activity</dt>
<dd class="font-monospace text-end">{{ guildData.stat.todayActivity }}</dd>
<dt>Today Titanite</dt>
<dd class="font-monospace text-end">{{ guildData.stat.todayDungeonActivity }}</dd>
<dt>Activity Sum</dt>
<dd class="font-monospace text-end">{{ guildData.stat.activitySum }}</dd>
<dt>Titanite Sum</dt>
<dd class="font-monospace text-end">{{ guildData.stat.dungeonActivitySum }}</dd>
</dl>
</div>
</div>
</mat-card-content>
</mat-card>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildCardComponent } from './guild-card.component';
describe('GuildCardComponent', () => {
let component: GuildCardComponent;
let fixture: ComponentFixture<GuildCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildCardComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { DatePipe, DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { HWGuildData, HWGuildClan, HWGuildStat } from 'src/app/core/models';
import guildData from 'src/files-data/hw-guild-data.json'; // page Membres
@Component({
selector: 'app-guild-card',
standalone: true,
imports: [
DatePipe, DecimalPipe,
MatCardModule, MatDividerModule, MatIconModule
],
templateUrl: './guild-card.component.html',
styleUrl: './guild-card.component.scss'
})
export class GuildCardComponent implements OnInit {
public guildData: HWGuildData = {} as HWGuildData;
private _guildClan: HWGuildClan = {} as HWGuildClan;
private _guildStat: HWGuildStat = {} as HWGuildStat;
ngOnInit() {
Object.assign(this._guildClan, guildData.clan);
this.guildData.clan = this._guildClan;
Object.assign(this._guildStat, guildData.stat);
this.guildData.stat = this._guildStat;
this.guildData.serverResetTime = guildData.serverResetTime;
this.guildData.clanWarEndSeasonTime = guildData.clanWarEndSeasonTime;
this.guildData.freeClanChangeInterval = guildData.freeClanChangeInterval;
this.guildData.giftUids = guildData.giftUids;
}
}
@@ -0,0 +1,436 @@
<mat-card class="my-3">
<mat-card-header class="d-flex rounded-top bg-navy-light text-bg-navy-light py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="table-responsive p-0">
<form class="d-flex" [formGroup]="raidsForm" (ngSubmit)="submitForm()">
<mat-form-field appearance="outline" class="mt-2 me-3" color="warn">
<mat-label>Choisissez une date</mat-label>
<input matInput [matDatepicker]="datepicker" formControlName="startTime" (dateChange)="onDateChange()" required
cdkFocusInitial />
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
<mat-datepicker #datepicker>
<mat-datepicker-actions>
<button mat-button matDatepickerCancel>Annuler</button>
<button mat-raised-button color="warn" matDatepickerApply>Appliquer</button>
</mat-datepicker-actions>
</mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="mt-2 me-2" color="warn">
<mat-label>Duration</mat-label>
<input matInput formControlName="duration" required />
<mat-hint>Stage duration in hour</mat-hint>
</mat-form-field>
</form>
<mat-stepper headerPosition="top" #stepper>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 1</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 1</h2>
<h4 class="mb-1">
From {{ getStagesTime(0).startTime | date: 'dd/MM HH:mm' }}
to {{ getStagesTime(0).endTime | date: 'dd/MM HH:mm' }}
</h4>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">Power limit: 320k</h4>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr role="row" class="bg-gray-500 text-bg-primary">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center justify-content-end">
Date
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Member
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Level
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Damage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Stage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Power
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-end">
Variation
</div>
</th>
</tr>
</thead>
<tbody>
@for (raid of getRaidsLog(1); track raid; let index = $index) {
<tr role="row" class="align-middle text-{{ raid.variation.color }}">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell font-monospace text-end">
<span class="me-3">{{ raid.startTime + '000' | date: 'dd/MM HH:mm:ss' }}</span>
</td>
<td class="cell font-monospace text-end">
{{ raid.userName }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
{{ raid.level | number : '1.0' }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
<div class="d-flex align-items-center justify-content-end">
@if (raid.damage["2"] > 0) {
<mat-icon fontIcon="arrow_upward" color="purple" class="me-2 icon-xxsmall" matTooltip="{{ raid.damage['1'] | number : '1.0' }} + {{ raid.damage['2'] | number : '1.0' }}"></mat-icon>
}
{{ (raid.damage["1"] + raid.damage["2"]) | number : '1.0' }}
</div>
</td>
<td class="cell font-monospace text-end">
{{ raid.stage.name }}: {{ (raid.stage.maxPower / 1000) | number : '1.0' }}k
</td>
<td class="cell font-monospace text-end">
{{ raid.power | number : '1.0' }}
</td>
<td class="cell font-monospace text-end pe-4">
<span class="ms-3" [matTooltip]="raid.tooltip">
{{ raid.variation.percent | number : '1.0-2' }}%
</span>
</td>
</tr>
}
</tbody>
</table>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 2</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 2</h2>
<h4 class="mb-1">
From {{ getStagesTime(1).startTime | date: 'dd/MM HH:mm' }}
to {{ getStagesTime(1).endTime | date: 'dd/MM HH:mm' }}
</h4>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">Power limit: 500k</h4>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr role="row" class="bg-gray-500 text-bg-primary">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center justify-content-end">
Date
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Member
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Level
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Damage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Stage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Power
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-end">
Variation
</div>
</th>
</tr>
</thead>
<tbody>
@for (raid of getRaidsLog(2); track raid; let index = $index) {
<tr role="row" class="align-middle text-{{ raid.variation.color }}">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell font-monospace text-end">
<span class="me-3">{{ raid.startTime + '000' | date: 'dd/MM HH:mm:ss' }}</span>
</td>
<td class="cell font-monospace text-end">
{{ raid.userName }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
{{ raid.level | number : '1.0' }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
<div class="d-flex align-items-center justify-content-end">
@if (raid.damage["2"] > 0) {
<mat-icon fontIcon="arrow_upward" color="purple" class="me-2 icon-xxsmall" matTooltip="{{ raid.damage['1'] | number : '1.0' }} + {{ raid.damage['2'] | number : '1.0' }}"></mat-icon>
}
{{ (raid.damage["1"] + raid.damage["2"]) | number : '1.0' }}
</div>
</td>
<td class="cell font-monospace text-end">
{{ raid.stage.name }}: {{ (raid.stage.maxPower / 1000) | number : '1.0' }}k
</td>
<td class="cell font-monospace text-end">
{{ raid.power | number : '1.0' }}
</td>
<td class="cell font-monospace text-end pe-4">
<span class="ms-3" [matTooltip]="raid.tooltip">
{{ raid.variation.percent | number : '1.0-2' }}%
</span>
</td>
</tr>
}
</tbody>
</table>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="timeline">
<ng-template matStepLabel>Stage 3</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Stage 3</h2>
<h4 class="mb-1">
From {{ getStagesTime(2).startTime | date: 'dd/MM HH:mm' }}
to {{ getStagesTime(2).endTime | date: 'dd/MM HH:mm' }}
</h4>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">No power limit</h4>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr role="row" class="bg-gray-500 text-bg-primary">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center justify-content-end">
Date
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Member
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Level
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Damage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Stage
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Power
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-end">
Variation
</div>
</th>
</tr>
</thead>
<tbody>
@for (raid of getRaidsLog(3); track raid; let index = $index) {
<tr role="row" class="align-middle text-{{ raid.variation.color }}">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell font-monospace text-end">
<span class="me-3">{{ raid.startTime + '000' | date: 'dd/MM HH:mm:ss' }}</span>
</td>
<td class="cell font-monospace text-end">
{{ raid.userName }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
{{ raid.level | number : '1.0' }}
</td>
<td class="cell font-monospace text-end {{ raid.damage['2'] > 0 ? 'text-purple' : '' }}">
<div class="d-flex align-items-center justify-content-end">
@if (raid.damage["2"] > 0) {
<mat-icon fontIcon="arrow_upward" color="purple" class="me-2 icon-xxsmall" matTooltip="{{ raid.damage['1'] | number : '1.0' }} + {{ raid.damage['2'] | number : '1.0' }}"></mat-icon>
}
{{ (raid.damage["1"] + raid.damage["2"]) | number : '1.0' }}
</div>
</td>
<td class="cell font-monospace text-end">
{{ raid.stage.name }}: {{ (raid.stage.maxPower / 1000) | number : '1.0' }}k
</td>
<td class="cell font-monospace text-end">
{{ raid.power | number : '1.0' }}
</td>
<td class="cell font-monospace text-end pe-4">
<span class="ms-3" [matTooltip]="raid.tooltip">
{{ raid.variation.percent | number : '1.0-2' }}%
</span>
</td>
</tr>
}
</tbody>
</table>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step state="search">
<ng-template matStepLabel>Statistics</ng-template>
<div class="d-flex mt-3">
<div class="flex-fill">
<h2 class="mb-1">Statistics</h2>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end">
<h4 class="mb-1">variation ≥ {{ minVarPct }}%</h4>
<mat-form-field appearance="outline">
<mat-label>Variation</mat-label>
<input matInput [(ngModel)]="minVarPct" type="number" min="0">
<mat-icon matSuffix>percent</mat-icon>
<mat-hint>excess power in %</mat-hint>
</mat-form-field>
</div>
</div>
<table class="table table-striped table-condensed">
<thead>
<tr role="row" class="bg-gray-500 text-bg-primary">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Name
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Fights
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center justify-content-end">
Variation moyenne
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-start ps-2">
[]
</div>
</th>
</tr>
</thead>
<tbody>
@for (member of getMembers(); track member; let index = $index) {
<tr role="row" class="align-middle">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell font-monospace text-end">
{{ member.name }}
</td>
<td class="cell font-monospace text-end">
{{ member.raids.length }}
</td>
<td class="cell font-monospace text-end">
{{ member.raidsInfo.variationAvg | number : '1.0-2' }}%
</td>
<td class="cell font-monospace text-end">
<div class="d-flex justify-content-start">
@for (raid of member.raids; track raid) {
<div class="px-2 text-{{ raid.variation.color }}">
{{ raid.variation.percent | number : '1.2-2' }}%
</div>
}
</div>
</td>
</tr>
}
</tbody>
</table>
<div class="border-top border-light pt-2">
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
<ng-template matStepperIcon="timeline">
<mat-icon>timeline</mat-icon>
</ng-template>
<ng-template matStepperIcon="search">
<mat-icon>manage_search</mat-icon>
</ng-template>
</mat-stepper>
</mat-card-content>
</mat-card>
@@ -0,0 +1 @@
/* guildraids-log */
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildraidsLogComponent } from './guildraids-log.component';
describe('GuildraidsLogComponent', () => {
let component: GuildraidsLogComponent;
let fixture: ComponentFixture<GuildraidsLogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildraidsLogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildraidsLogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,214 @@
import { Component, Injectable, inject, Input, OnInit } from '@angular/core';
import { DatePipe, DecimalPipe } from '@angular/common';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatStepperIntl, MatStepperModule } from '@angular/material/stepper';
import { MatTooltipModule } from '@angular/material/tooltip';
import { HWGuildRaid, HWGuildRaidAttacker, HWGuildRaidAttackers, HWGuildRaidStage, HWMember } from 'src/app/core/models';
import guildRaids from 'src/files-data/hw-guild-raids.json'; // page Asgard -> Guild Raid -> Log
@Injectable()
export class StepperIntl extends MatStepperIntl {
// the default optional label text, if unspecified is "Optional"
override optionalLabel = 'Optional Label';
}
@Component({
selector: 'app-guildraids-log',
standalone: true,
providers: [
{
provide: STEPPER_GLOBAL_OPTIONS,
useValue: {displayDefaultIndicatorType: false},
},
],
imports: [
DatePipe, DecimalPipe, FormsModule, ReactiveFormsModule,
MatButtonModule, MatCardModule, MatDatepickerModule, MatDividerModule,
MatFormFieldModule, MatIconModule, MatInputModule, MatTooltipModule,
MatStepperModule
],
templateUrl: './guildraids-log.component.html',
styleUrl: './guildraids-log.component.scss'
})
export class GuildraidsLogComponent implements OnInit {
public now: Date;
public raidsForm: FormGroup;
public guildMembers: HWMember[] = [];
public title = 'Guild Raids';
public subtitle = 'Log';
public minVarPct = 3;
private _matStepperIntl = inject(MatStepperIntl);
private _guildRaids: HWGuildRaid[] = [];
private _raidStages: HWGuildRaidStage[] = [
{num: 1, name: 'Stage 1', maxPower: 350000, duration: 8, startTime: 0, endTime: 0},
{num: 2, name: 'Stage 2', maxPower: 550000, duration: 8, startTime: 0, endTime: 0},
{num: 3, name: 'Stage 3', maxPower: 0, duration: 0, startTime: 0, endTime: 0}
];
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
constructor(
private fb: FormBuilder
) {
this.now = new Date();
const raidsDate = new Date();
const startHour = 4;
raidsDate.setHours(startHour, 0, 0, 0);
const controlsConfig = {
num: 1,
name: 'Stage 1',
maxPower: 350,
duration: 8,
startTime: [raidsDate.getTime(), Validators.required],
endTime: 0
};
this.raidsForm = this.fb.group(controlsConfig);
}
_setStagesTime(startTime: number): void {
const raidsDate = new Date(startTime);
const startHour = 4;
raidsDate.setHours(startHour, 0, 0, 0);
this._raidStages[0].startTime = raidsDate.getTime();
raidsDate.setHours((startHour + this._raidStages[0].duration), 0, 0, 0);
this._raidStages[0].endTime = raidsDate.getTime();
this._raidStages[1].startTime = raidsDate.getTime();
raidsDate.setHours((startHour + this._raidStages[0].duration + this._raidStages[1].duration), 0, 0, 0);
this._raidStages[1].endTime = raidsDate.getTime();
this._raidStages[2].startTime = raidsDate.getTime();
raidsDate.setHours(startHour, 0, 0, 0);
raidsDate.setDate(raidsDate.getDate() + 4);
this._raidStages[2].endTime = raidsDate.getTime();
this._matStepperIntl.optionalLabel = this._raidStages[1].duration.toString() + 'h';
}
ngOnInit() {
this.guildMembers.map((data) => {
data.raids = [];
data.raidsInfo = {
variationAvg: 0,
variationSum: 0
};
return data;
});
for (const member of Object.entries(guildRaids)) {
const index = this.guildMembers.findIndex(item => item.id === member[0]);
const memberRaids: HWGuildRaid[] = [];
if (index !== -1) {
for (const data of Object.entries(member[1])) {
const raid: HWGuildRaid = {} as HWGuildRaid;
Object.assign(raid, data[1]);
const startTime: number = (parseInt(raid.startTime) * 1000);
if (this._guildRaids.length === 0) {
this._setStagesTime(startTime);
}
const raidsDate = new Date(startTime);
if (raidsDate.getTime() > this._raidStages[1].endTime) {
raid.stage = this._raidStages[2];
} else if (raidsDate.getTime() > this._raidStages[0].endTime) {
raid.stage = this._raidStages[1];
} else {
raid.stage = this._raidStages[0];
}
raid.damage = data[1].result.damage;
raid.level = data[1].result.level;
raid.userName = this.guildMembers[index].name;
const attackers: HWGuildRaidAttackers = {} as HWGuildRaidAttackers;
Object.assign(attackers, data[1].attackers);
//Object.assign(raid.attackers, data[1].attackers);
raid.attackers = [];
raid.power = 0;
for (const item of Object.entries(attackers)) {
const attacker: HWGuildRaidAttacker = {
id: item[1].id,
power: item[1].power,
type: item[1].type
} as HWGuildRaidAttacker;
raid.power += attacker.power;
raid.attackers.push(attacker);
}
let percent = 0;
if (raid.stage.maxPower > 0) {
percent = (((raid.power / raid.stage.maxPower) - 1) * 100);
}
let color = 'default';
let value = 0;
if (raid.stage.maxPower > 0) {
if (percent > 15) {
color = 'raspberry';
} else if (percent > 10) {
color = 'red';
} else if (percent > 5) {
color = 'orange';
}
value = (raid.power - raid.stage.maxPower)
}
raid.variation = {
value: value,
percent: percent,
color: color
};
raid.tooltip = Math.floor(raid.variation.value / 1000) + 'k';
//console.log(raid.attackers);
memberRaids.push(raid);
this._guildRaids.push(raid);
}
this.guildMembers[index].raids = memberRaids;
const varSum = memberRaids.reduce((accumulator, currentValue) => accumulator + currentValue.variation.percent, 0);
this.guildMembers[index].raidsInfo = {
variationAvg: (varSum / memberRaids.length),
variationSum: varSum
};
}
}
this._guildRaids.sort((a, b) => (a.startTime < b.startTime ? -1 : 1)).map((data) => {
return data;
});
this.guildMembers.sort((a, b) => (a.raidsInfo.variationAvg > b.raidsInfo.variationAvg ? -1 : 1));/*.map((data) => {
console.log(data.name, data.raidsInfo.variationAvg, data.raids.length);
return data;
});*/
}
public getMembers(): HWMember[] {
//return this.guildMembers;
return this.minVarPct ? this.guildMembers.filter(member => (member.raidsInfo.variationAvg >= this.minVarPct) === true) : this.guildMembers;
}
public getRaidsLog(stageNum: number): HWGuildRaid[] {
return this._guildRaids.filter(raid => (raid.stage.num === stageNum) === true);
}
public getStagesTime(stageNum: number): HWGuildRaidStage {
return this._raidStages[stageNum];
}
public onDateChange(): void {
const timestamp: number = Date.parse(this.raidsForm.controls['startTime'].value);
if (isNaN(timestamp) == false) {
this._setStagesTime(timestamp);
}
}
submitForm(): void {
}
}
@@ -0,0 +1,149 @@
<mat-card class="my-3">
<mat-card-header class="text-bg-warn d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
Guild War Day {{ warInfo.day }} VS {{ warInfo.clanEnemyName }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light"> {{ warInfo.endTime | date: 'dd/MM/yyyy HH:mm:ss' }}</span>
</div>
</mat-card-header>
<mat-card-content>
<mat-divider class="mb-3"></mat-divider>
<div class="d-flex align-items-center ps-2 mb-3">
<dl class="dl-horizontal dl-large mt-3 mb-0">
<dt>Server End Time</dt>
<dd class="font-monospace">{{ warInfo.endTime | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
<dt>Next War Time</dt>
<dd class="font-monospace">{{ warInfo.nextWarTime| date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
<dt>Next Lock Time</dt>
<dd class="font-monospace">{{ warInfo.nextLockTime | date: 'dd/MM/yyyy HH:mm:ss' }}</dd>
</dl>
</div>
</mat-card-content>
</mat-card>
<mat-divider class="my-3"></mat-divider>
<mat-card class="my-3">
<mat-card-header class="text-bg-danger d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ warInfo.clanEnemyName }} Members
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }} {{ warInfo.endTime | date: 'dd/MM/yyyy HH:mm:ss' }}</span>
</div>
</mat-card-header>
<mat-card-content class="p-0">
<table class="table table-striped table-condensed border-top border-light">
<thead>
<tr role="row" class="text-bg-danger bg-opacity-50">
<th class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center text-start">
Name
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Last Login
</div>
</th>
<th class="align-middle w-auto">
<div class="text-end">
Heroes
</div>
</th>
<th class="align-middle w-auto pe-4">
<div class="text-end">
Titans
</div>
</th>
</tr>
</thead>
<tbody>
@for (enemy of getEnemies(); track enemy; let index = $index) {
<tr role="row" class="align-middle{{ enemy.champion ? '' : ' text-muted' }}">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell text-start">
<div class="d-flex{{ enemy.champion ? ' text-green' : '' }}">
<mat-icon [fontIcon]="enemy.champion ? 'gpp_good' : 'gpp_bad'" class="me-2 icon-small"></mat-icon>
{{ enemy.name }}
</div>
</td>
<td class="cell font-monospace text-start">
<span class="me-3">{{ enemy.lastLoginTime + '000' | date: 'dd/MM HH:mm' }}</span>
</td>
<td class="cell font-monospace text-end">
{{ enemy.heroes.power | number : '1.0' }}
</td>
<td class="cell font-monospace text-end">
{{ enemy.titans.power | number : '1.0' }}
</td>
</tr>
}
</tbody>
</table>
</mat-card-content>
</mat-card>
<mat-divider class="my-3"></mat-divider>
<div class="row g-0">
<div class="col col-md-6 pe-md-2">
<mat-card>
<mat-card-header class="bg-accent text-navy d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ warInfo.clanEnemyName }} Heroes {{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="pt-3">
<app-fortification-card-content type="heroes" [fortification]="getLighthouse()" [members]="guildEnemies"></app-fortification-card-content>
<app-fortification-card-content type="heroes" [fortification]="getBarracks()" [members]="guildEnemies"></app-fortification-card-content>
<app-fortification-card-content type="heroes" [fortification]="getMageAcademy()" [members]="guildEnemies"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content type="heroes" [fortification]="getFoundry()" [members]="guildEnemies"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content type="heroes" [fortification]="getCitadel()" [members]="guildEnemies"></app-fortification-card-content>
</mat-card-content>
</mat-card>
</div>
<div class="col col-md-6 ps-md-2">
<mat-card>
<mat-card-header class="bg-accent text-navy d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ warInfo.clanEnemyName }} Titans {{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="pt-3">
<app-fortification-card-content type="titans" [fortification]="getBridge()" [members]="guildEnemies"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content type="titans" [fortification]="getGatesOfNature()" [members]="guildEnemies"></app-fortification-card-content>
<app-fortification-card-content type="titans" [fortification]="getBastionOfFire()" [members]="guildEnemies"></app-fortification-card-content>
<app-fortification-card-content type="titans" [fortification]="getBastionOfIce()" [members]="guildEnemies"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content type="titans" [fortification]="getSpringOfElements()" [members]="guildEnemies"></app-fortification-card-content>
</mat-card-content>
</mat-card>
</div>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildwarAttackComponent } from './guildwar-attack.component';
describe('GuildwarAttackComponent', () => {
let component: GuildwarAttackComponent;
let fixture: ComponentFixture<GuildwarAttackComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildwarAttackComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildwarAttackComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,285 @@
import { Component, Input, OnInit } from '@angular/core';
import { DatePipe, DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { FortificationCardContentComponent } from 'src/app/components/shared';
import {
HWGuildWarEnemySlot, HWGuildWarEnemyTeam, HWGuildWarFortification,
HWGuildWarHeroTeam, HWGuildWarTitanTeam, HWGuildWarSlots, HWMember
} from 'src/app/core/models';
/* JSON data */
import guildWarData from 'src/files-data/hw-guild-war.json'; // page Overview -> Statistics | page Guild War -> Guild War
import guildWarInfo from 'src/files-data/hw-guild-war-info.json'; // page Guild War -> Guild War
//import guildWarLog from 'src/files-data/hw-guild-war-log.json'; // page Guild War -> Guild War
import guildWarSlots from 'src/files-data/hw-guild-war-slots.json'; // no pages
@Component({
selector: 'app-guildwar-attack',
standalone: true,
imports: [
DatePipe, DecimalPipe,
MatCardModule, MatDividerModule, MatIconModule,
FortificationCardContentComponent
],
templateUrl: './guildwar-attack.component.html',
styleUrl: './guildwar-attack.component.scss'
})
export class GuildwarAttackComponent implements OnInit {
private _championsByPower: HWMember[] = [];
private _enemySlots: number[] = [];
public guildEnemies: HWMember[] = [];
public guildMembers: HWMember[] = [];
public title = 'Fortifications';
public subtitle = 'Guild War Attack';
public now = new Date();
public warInfo: { day: number, clanEnemyName: string, endTime: number, nextWarTime: number, nextLockTime: number } = {
day: 0,
clanEnemyName: '',
endTime: 0,
nextWarTime: 0,
nextLockTime: 0
};
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
ngOnInit() {
this.warInfo.day = parseInt(guildWarInfo.day);
this.warInfo.clanEnemyName = guildWarInfo.enemyClan.title;
this.warInfo.endTime = (guildWarInfo.endTime * 1000);
this.warInfo.nextWarTime = (guildWarInfo.nextWarTime * 1000);
this.warInfo.nextLockTime = (guildWarInfo.nextLockTime * 1000);
this._setChampionsByPower();
this._setEnemies();
}
private _getFortification(indexes: number[], type: string, name: string): HWGuildWarFortification {
const teams: HWMember[] = [];
const slots: HWGuildWarSlots = guildWarSlots.slots;
let count = 0;
indexes.forEach((data) => {
switch (type) {
case 'heroes':
teams.push(this.guildEnemies[data]);
count += this.guildEnemies[data].heroes.power;
break;
case 'titans':
teams.push(this.guildEnemies[data]);
count += this.guildEnemies[data].titans.power;
break;
default:
break;
}
});
return {
name: name,
type: type,
count: count,
teams: teams,
positions: this._enemySlots,
slots: slots[name]
};
}
private _getDefense(type: string, name: string): HWGuildWarFortification {
const teams: HWMember[] = [];
const slots: HWGuildWarSlots = guildWarSlots.slots;
let count = 0;
slots[name].forEach(slot => {
let enemies: HWMember[] = [];
let team: HWMember = {} as HWMember;
enemies = this.guildEnemies.filter(enemy => parseInt(enemy.id) === this._enemySlots[slot]);
if (enemies.length) {
team = enemies[0];
teams.push(team);
switch (type) {
case 'heroes':
count += team.heroes.power;
break;
case 'titans':
count += team.titans.power;
break;
default:
break;
}
}
});
return {
name: name,
type: type,
count: count,
teams: teams,
positions: this._enemySlots,
slots: slots[name]
};
}
private _setEnemies(): void {
const warriors: number[] = [];
for (const data of Object.entries(guildWarInfo.enemyClanTries)) {
warriors.push(parseInt(data[0]));
}
for (const data of Object.entries(guildWarInfo.enemyClanMembers)) {
const enemy: HWMember = {} as HWMember;
Object.assign(enemy, data[1]);
if (warriors.indexOf(parseInt(enemy.id)) !== -1) {
enemy.champion = true;
} else {
enemy.champion = false;
}
enemy.heroes = { power: 0, teams: []};
enemy.titans = { power: 0, teams: []};
//console.log(guildWarInfo.enemySlots);
this.guildEnemies.push(enemy);
}
for (const data of Object.entries(guildWarInfo.enemySlots)) {
const ennemySlot: HWGuildWarEnemySlot = {} as HWGuildWarEnemySlot;
Object.assign(ennemySlot, data[1]);
//console.log(ennemySlot.slotId, ennemySlot.user.name, ennemySlot.user.id); //, ennemySlot.team["1"].power);
ennemySlot.teams = [];
let totalHeroPower = 0;
let totalTitanPower = 0;
if (ennemySlot.user !== null) {
this._enemySlots[ennemySlot.slotId] = parseInt(ennemySlot.user.id);
for (const team of Object.entries(data[1].team[0])) {
/*
ennemySlot.teams.push(enemyTeam);
console.log(enemyTeam);
*/
const enemyTeam: HWGuildWarEnemyTeam = {} as HWGuildWarEnemyTeam;
Object.assign(enemyTeam, team[1]);
switch (enemyTeam.type) {
case 'hero':
case 'pet':
totalHeroPower += enemyTeam.power;
break;
case 'titan':
totalTitanPower += enemyTeam.power;
break;
default:
break;
}
ennemySlot.teams.push(team[1]);
//console.log(team[1]);
}
//console.log(totalHeroPower, totalTitanPower, ennemySlot.teams);
this.guildEnemies.some((item) => {
if (item.id === ennemySlot.user.id) {
if (totalHeroPower) {
item.heroes.power = totalHeroPower;
item.heroes.teams = ennemySlot.teams;
}
if (totalTitanPower) {
item.titans.power = totalTitanPower;
item.titans.teams = ennemySlot.teams;
}
return true;
}
return false;
});
}
this.guildEnemies.sort((a, b) => ((a.heroes.power + a.titans.power) > (b.heroes.power + b.titans.power) ? -1 : 1)); // Descending
}
}
private _setChampionsByPower(): void {
for (const [teamId, teamValues] of Object.entries(guildWarData.teams)) {
const index = this.guildMembers.findIndex(member => member.id === teamId);
let totalHeroPower = 0;
let totalTitanPower = 0;
const heroes: HWGuildWarHeroTeam[] = [];
const titans: HWGuildWarTitanTeam[] = [];
for (const unit of Object.entries(teamValues.clanDefence_heroes.units)) {
heroes.push(unit[1]);
totalHeroPower += unit[1].power;
}
for (const unit of Object.entries(teamValues.clanDefence_titans.units)) {
titans.push(unit[1]);
totalTitanPower += unit[1].power;
}
this.guildMembers[index].heroes = { power: totalHeroPower, teams: heroes };
this.guildMembers[index].titans = { power: totalTitanPower, teams: titans };
}
this.guildMembers.sort((a, b) => ((a.heroes.power + a.titans.power) > (b.heroes.power + b.titans.power) ? -1 : 1)); // Descending
this._championsByPower = this.guildMembers.filter(member => member.champion === true);
}
getEnemies(): HWMember[] {
return this.guildEnemies;
}
getMembers(): HWMember[] {
return this.guildMembers;
}
getLighthouse(): HWGuildWarFortification {
//const indexes: number[] = [0, 4, 8];
//return this._getFortification(indexes, 'heroes', 'Lighthouse');
return this._getDefense('heroes', 'Lighthouse');
}
getBarracks(): HWGuildWarFortification {
//const indexes: number[] = [1, 5, 6];
//return this._getFortification(indexes, 'heroes', 'Barracks');
return this._getDefense('heroes', 'Barracks');
}
getMageAcademy(): HWGuildWarFortification {
//const indexes: number[] = [2, 3, 7];
//return this._getFortification(indexes, 'heroes', 'Mage Academy');
return this._getDefense('heroes', 'Mage Academy');
}
getFoundry(): HWGuildWarFortification {
//const indexes: number[] = [9, 11, 12, 13];
//return this._getFortification(indexes, 'heroes', 'Foundry');
return this._getDefense('heroes', 'Foundry');
}
getCitadel(): HWGuildWarFortification {
//const indexes: number[] = [10, 14, 15, 16, 17, 18, 19];
//return this._getFortification(indexes, 'heroes', 'Citadel');
return this._getDefense('heroes', 'Citadel');
}
getBridge(): HWGuildWarFortification {
//const indexes: number[] = [0, 1, 2, 3];
//return this._getFortification(indexes, 'titans', 'Bridge');
return this._getDefense('titans', 'Bridge');
}
getGatesOfNature(): HWGuildWarFortification {
//const indexes: number[] = [4, 9, 14, 19];
//return this._getFortification(indexes, 'titans', 'Gates Of Nature');
return this._getDefense('titans', 'Gates Of Nature');
}
getBastionOfFire(): HWGuildWarFortification {
//const indexes: number[] = [5, 10, 15, 16];
//return this._getFortification(indexes, 'titans', 'Bastion Of Fire');
return this._getDefense('titans', 'Bastion Of Fire');
}
getBastionOfIce(): HWGuildWarFortification {
//const indexes: number[] = [6, 11, 12, 17];
//return this._getFortification(indexes, 'titans', 'Bastion Of Ice');
return this._getDefense('titans', 'Bastion Of Ice');
}
getSpringOfElements(): HWGuildWarFortification {
//const indexes: number[] = [7, 8, 13, 18];
//return this._getFortification(indexes, 'titans', 'Spring Of Elements');
return this._getDefense('titans', 'Spring Of Elements');
}
}
@@ -0,0 +1,57 @@
<mat-card class="my-3">
<mat-card-header class="bg-{{color}}-light text-bg-primary d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="p-0">
<table class="table table-striped table-condensed border-top border-light">
<thead>
<tr role="row" class="bg-{{color}}-light text-bg-primary bg-opacity-50">
<th class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th class="align-middle w-auto">
<div class="text-start">
Name
</div>
</th>
<th class="align-middle w-auto pe-4">
<div class="d-flex align-items-center justify-content-end">
Power
<mat-icon fontIcon="arrow_downward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
</tr>
</thead>
<tbody>
@for (champion of getChampionsByPower(); track champion; let index = $index) {
<tr role="row" class="align-middle">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell text-start">
{{ champion.name }}
</td>
<td class="cell font-monospace text-end pe-4">
@if (teamType === 'heroes') {
{{ champion.heroes.power | number : '1.0' }}
}
@else {
{{ champion.titans.power | number : '1.0' }}
}
</td>
</tr>
}
</tbody>
</table>
</mat-card-content>
</mat-card>
@@ -0,0 +1 @@
/* guildwar-champions */
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildwarChampionsComponent } from './guildwar-champions.component';
describe('GuildwarChampionsComponent', () => {
let component: GuildwarChampionsComponent;
let fixture: ComponentFixture<GuildwarChampionsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildwarChampionsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildwarChampionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,58 @@
import { Component, Input, OnInit } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { HWMember } from 'src/app/core/models';
//import { HWGuildWarFortification, HWMember } from 'src/app/core/models';
@Component({
selector: 'app-guildwar-champions',
standalone: true,
imports: [
DecimalPipe,
MatCardModule, MatDividerModule, MatIconModule
],
templateUrl: './guildwar-champions.component.html',
styleUrl: './guildwar-champions.component.scss'
})
export class GuildwarChampionsComponent implements OnInit {
private _championsByPower: HWMember[] = [];
public guildMembers: HWMember[] = [];
public color: string = 'primary';
public teamType: string = '';
public title = 'Champions';
public subtitle = 'by power';
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
@Input() set type(value: string) {
this.teamType = value;
}
ngOnInit() {
switch (this.teamType) {
case 'heroes':
this.color = 'navy';
this.subtitle = 'heroes power';
this.guildMembers.sort((a, b) => (a.heroes.power > b.heroes.power ? -1 : 1)); // Descending
this._championsByPower = this.guildMembers.filter(member => member.champion === true);
break;
case 'titans':
this.color = 'purple';
this.subtitle = 'titans power';
this.guildMembers.sort((a, b) => (a.titans.power > b.titans.power ? -1 : 1)); // Descending
this._championsByPower = this.guildMembers.filter(member => member.champion === true);
break;
default:
break;
}
}
getChampionsByPower(): HWMember[] {
return this._championsByPower;
}
}
@@ -0,0 +1,36 @@
<mat-card class="my-3">
<mat-card-header class="bg-accent text-navy d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="pt-3">
@if (teamType === 'heroes') {
<app-fortification-card-content [type]="teamType" [fortification]="getLighthouse()" [members]="guildMembers"></app-fortification-card-content>
<app-fortification-card-content [type]="teamType" [fortification]="getBarracks()" [members]="guildMembers"></app-fortification-card-content>
<app-fortification-card-content [type]="teamType" [fortification]="getMageAcademy()" [members]="guildMembers"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content [type]="teamType" [fortification]="getFoundry()" [members]="guildMembers"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content [type]="teamType" [fortification]="getCitadel()" [members]="guildMembers"></app-fortification-card-content>
}
@else {
<app-fortification-card-content [type]="teamType" [fortification]="getBridge()" [members]="guildMembers"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content [type]="teamType" [fortification]="getGatesOfNature()" [members]="guildMembers"></app-fortification-card-content>
<app-fortification-card-content [type]="teamType" [fortification]="getBastionOfFire()" [members]="guildMembers"></app-fortification-card-content>
<app-fortification-card-content [type]="teamType" [fortification]="getBastionOfIce()" [members]="guildMembers"></app-fortification-card-content>
<mat-divider class="my-3"></mat-divider>
<app-fortification-card-content [type]="teamType" [fortification]="getSpringOfElements()" [members]="guildMembers"></app-fortification-card-content>
}
</mat-card-content>
</mat-card>
<div class="d-flex align-items-center ps-2 mb-3">
<mat-icon fontIcon="fiber_manual_record" class="me-2 icon-small text-red"></mat-icon> <span class="d-inline-block">Position to check</span>
</div>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildwarDefenceComponent } from './guildwar-defence.component';
describe('GuildwarDefenceComponent', () => {
let component: GuildwarDefenceComponent;
let fixture: ComponentFixture<GuildwarDefenceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildwarDefenceComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildwarDefenceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,191 @@
import { Component, Input, OnInit } from '@angular/core';
//import { DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { FortificationCardContentComponent } from 'src/app/components/shared';
//import { HWMember } from 'src/app/core/models';
import { HWGuildWarFortification, HWGuildWarHeroTeam, HWGuildWarSlots, HWGuildWarTitanTeam, HWMember } from 'src/app/core/models';
/* JSON data */
import guildWarData from 'src/files-data/hw-guild-war.json'; // page Overview -> Statistics | page Guild War -> Guild War
import guildWarSlots from 'src/files-data/hw-guild-war-slots.json'; // no pages
@Component({
selector: 'app-guildwar-defence',
standalone: true,
imports: [
MatCardModule, MatDividerModule, MatIconModule,
FortificationCardContentComponent
],
templateUrl: './guildwar-defence.component.html',
styleUrl: './guildwar-defence.component.scss'
})
export class GuildwarDefenceComponent implements OnInit {
private _championsByPower: HWMember[] = [];
private _championSlots: number[] = [];
public guildMembers: HWMember[] = [];
public teamType: string = '';
public title = 'Fortifications';
public subtitle = 'Guild War Defence';
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
@Input() set type(value: string) {
this.teamType = value;
}
ngOnInit() {
for (const [teamId, teamValues] of Object.entries(guildWarData.teams)) {
const index = this.guildMembers.findIndex(member => member.id === teamId);
let totalHeroPower = 0;
let totalTitanPower = 0;
const heroes: HWGuildWarHeroTeam[] = [];
const titans: HWGuildWarTitanTeam[] = [];
this.guildMembers[index].heroes = { power: totalHeroPower, teams: heroes };
this.guildMembers[index].titans = { power: totalTitanPower, teams: titans };
for (const unit of Object.entries(teamValues.clanDefence_heroes.units)) {
heroes.push(unit[1]);
totalHeroPower += unit[1].power;
}
for (const unit of Object.entries(teamValues.clanDefence_titans.units)) {
titans.push(unit[1]);
totalTitanPower += unit[1].power;
}
this.guildMembers[index].heroes = { power: totalHeroPower, teams: heroes };
this.guildMembers[index].titans = { power: totalTitanPower, teams: titans };
}
for (const data of Object.entries(guildWarData.slots)) {
this._championSlots[parseInt(data[0])] = data[1];
}
switch (this.teamType) {
case 'heroes':
this.title = 'Heroes Fortifications';
this.guildMembers.sort((a, b) => (a.heroes.power > b.heroes.power ? -1 : 1)); // Descending
break;
case 'titans':
this.title = 'Titans Fortifications';
this.guildMembers.sort((a, b) => (a.titans.power > b.titans.power ? -1 : 1)); // Descending
break;
default:
break;
}
this._championsByPower = this.guildMembers.filter(member => member.champion === true);
}
private _getFortification(indexes: number[], type: string, name: string): HWGuildWarFortification {
const teams: HWMember[] = [];
const slots: HWGuildWarSlots = guildWarSlots.slots;
let count = 0;
indexes.forEach((data) => {
switch (type) {
case 'heroes':
//console.log(name, data, this._championsByPower[data]);
teams.push(this._championsByPower[data]);
count += this._championsByPower[data].heroes.power;
break;
case 'titans':
teams.push(this._championsByPower[data]);
count += this._championsByPower[data].titans.power;
break;
default:
break;
}
});
return {
name: name,
type: type,
count: count,
teams: teams,
positions: this._championSlots,
slots: slots[name]
};
}
/*
--------------------------------
0 - 3 - 6
1 - 4 - 7
2 - 5 - 8
--------------------------------
9 - 10 - 11 - 12
--------------------------------
13 - 14 - 15 - 16 - 17 - 18 - 19
--------------------------------
Mage Academy 1-2-31
Lighthouse 3-4-32
Barracks 5-6-33
Foundry 13-14-15-36
Citadel 25-26-28-28-29-30-40
*/
getLighthouse(): HWGuildWarFortification {
const indexes: number[] = [0, 4, 8];
return this._getFortification(indexes, 'heroes', 'Lighthouse');
}
getBarracks(): HWGuildWarFortification {
const indexes: number[] = [1, 5, 6];
return this._getFortification(indexes, 'heroes', 'Barracks');
}
getMageAcademy(): HWGuildWarFortification {
const indexes: number[] = [2, 3, 7];
return this._getFortification(indexes, 'heroes', 'Mage Academy');
}
getFoundry(): HWGuildWarFortification {
const indexes: number[] = [9, 11, 12, 13];
return this._getFortification(indexes, 'heroes', 'Foundry');
}
getCitadel(): HWGuildWarFortification {
const indexes: number[] = [10, 14, 15, 16, 17, 18, 19];
return this._getFortification(indexes, 'heroes', 'Citadel');
}
/*
--------------------------------
0 - 1 - 2 - 3
--------------------------------
4 - 8 - 12 - 16
5 - 9 - 13 - 17
6 - 10 - 14 - 18
7 - 11 - 15 - 19
--------------------------------
Bridge 7-8-9-34
Spring of Elements 10-11-12-35
Gates of Nature 16-17-18-37
Bastion of Fire 19-20-21-38
Bastion of Ice 22-23-24-39
*/
getBridge(): HWGuildWarFortification {
const indexes: number[] = [0, 1, 2, 3];
return this._getFortification(indexes, 'titans', 'Bridge');
}
getGatesOfNature(): HWGuildWarFortification {
const indexes: number[] = [4, 9, 14, 19];
return this._getFortification(indexes, 'titans', 'Gates Of Nature');
}
getBastionOfFire(): HWGuildWarFortification {
const indexes: number[] = [5, 10, 15, 16];
return this._getFortification(indexes, 'titans', 'Bastion Of Fire');
}
getBastionOfIce(): HWGuildWarFortification {
const indexes: number[] = [6, 11, 12, 17];
return this._getFortification(indexes, 'titans', 'Bastion Of Ice');
}
getSpringOfElements(): HWGuildWarFortification {
const indexes: number[] = [7, 8, 13, 18];
return this._getFortification(indexes, 'titans', 'Spring Of Elements');
}
}
@@ -0,0 +1,101 @@
<mat-card class="my-3">
<mat-card-header class="text-bg-teal d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="p-0">
<table class="table table-striped table-condensed border-top border-light">
<thead>
<tr role="row" class="text-bg-teal bg-opacity-50">
<th class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th class="align-middle w-auto">
<div class="text-start">
Name
</div>
</th>
<th class="align-middle w-auto">
<div class="d-flex align-items-center justify-content-end">
Avg
<mat-icon fontIcon="arrow_downward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th class="align-middle w-auto">
<div class="text-end">
Heroes
</div>
</th>
<th class="align-middle w-auto pe-4">
<div class="text-end">
Titans
</div>
</th>
</tr>
</thead>
<tbody>
@for (member of getChampionsByPower(); track member; let index = $index) {
<tr role="row" class="align-middle">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell text-start">
<div class="d-flex {{ member.champion ? 'text-green' : 'text-red' }}">
<mat-icon [fontIcon]="member.champion ? 'gpp_good' : 'gpp_bad'" class="me-2 icon-small"></mat-icon>
{{ member.name }}
</div>
</td>
<td class="cell font-monospace text-end">
{{ ((member.heroes.power + member.titans.power) / 2) | number : '1.0-0' }}
</td>
<td class="cell font-monospace text-end">
<span [class]="member.heroes.power < guildAverageHeroesPower ? 'text-orange' : 'text-megna'">{{ member.heroes.power | number : '1.0' }}</span>
</td>
<td class="cell font-monospace text-end">
<span [class]="member.titans.power < guildAverageTitansPower ? 'text-orange' : 'text-megna'">{{ member.titans.power | number : '1.0' }}</span>
</td>
</tr>
}
</tbody>
<tfoot>
<tr role="row" class="bg-light fw-medium align-middle">
<td colspan="3" class="cell text-end fw-medium pe-sm-2">
Totals
</td>
<td class="cell font-monospace text-end">
{{ guildTotalHeroesPower | number : '1.0' }}
</td>
<td class="cell font-monospace text-end">
{{ guildTotalTitansPower | number : '1.0' }}
</td>
</tr>
<tr role="row" class="bg-teal text-bg-primary fw-medium align-middle">
<td colspan="3" class="cell text-end pe-sm-2">
Averages
</td>
<td class="cell font-monospace text-end">
{{ guildAverageHeroesPower | number : '1.0' }}
</td>
<td class="cell font-monospace text-end">
{{ guildAverageTitansPower | number : '1.0' }}
</td>
</tr>
</tfoot>
</table>
</mat-card-content>
</mat-card>
<div class="d-flex align-items-center ps-2 mb-3">
<mat-icon fontIcon="gpp_good" class="me-2 icon-small text-megna"></mat-icon> <span class="d-inline-block">Champion signet</span>
<mat-icon fontIcon="gpp_bad" class="ms-3 me-2 icon-small text-red"></mat-icon> <span class="d-inline-block">No Champion signet</span>
<mat-icon fontIcon="fiber_manual_record" class="ms-3 me-2 icon-small text-orange"></mat-icon> <span class="d-inline-block">Below average</span>
<mat-icon fontIcon="fiber_manual_record" class="ms-3 me-2 icon-small text-megna"></mat-icon> <span class="d-inline-block">Above average</span>
</div>
@@ -0,0 +1 @@
/* guildwar-teams */
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GuildwarTeamsComponent } from './guildwar-teams.component';
describe('GuildwarTeamsComponent', () => {
let component: GuildwarTeamsComponent;
let fixture: ComponentFixture<GuildwarTeamsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GuildwarTeamsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GuildwarTeamsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,78 @@
import { Component, Input, OnInit } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { HWGuildWarHeroTeam, HWGuildWarTitanTeam, HWMember } from 'src/app/core/models';
import guildWarData from 'src/files-data/hw-guild-war.json'; // page Overview -> Statistics | page Guild War -> Guild War
@Component({
selector: 'app-guildwar-teams',
standalone: true,
imports: [
DecimalPipe,
MatCardModule, MatDividerModule, MatIconModule
],
templateUrl: './guildwar-teams.component.html',
styleUrl: './guildwar-teams.component.scss'
})
export class GuildwarTeamsComponent implements OnInit {
private _championsByPower: HWMember[] = [];
public guildMembers: HWMember[] = [];
public title = 'Members';
public subtitle = 'guild war teams power';
public guildAverageHeroesPower: number = 0;
public guildAverageTitansPower: number = 0;
public guildTotalHeroesPower: number = 0;
public guildTotalTitansPower: number = 0;
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
ngOnInit() {
this.guildMembers.sort((a, b) => (((a.heroes.power + a.titans.power) / 2) > ((b.heroes.power + b.titans.power) / 2) ? -1 : 1))
.map((data) => {
/*
this.guildTotalHeroesPower += data.heroes.power;
this.guildTotalTitansPower += data.titans.power;
this.guildAverageHeroesPower = Math.floor((this.guildTotalHeroesPower / this.guildMembers.length));
this.guildAverageTitansPower = Math.floor((this.guildTotalTitansPower / this.guildMembers.length));
*/
this._championsByPower.push(data);
return data;
});
for (const [teamId, teamValues] of Object.entries(guildWarData.teams)) {
const index = this._championsByPower.findIndex(member => member.id === teamId);
let totalHeroPower = 0;
let totalTitanPower = 0;
const heroes: HWGuildWarHeroTeam[] = [];
const titans: HWGuildWarTitanTeam[] = [];
for (const unit of Object.entries(teamValues.clanDefence_heroes.units)) {
heroes.push(unit[1]);
totalHeroPower += unit[1].power;
}
for (const unit of Object.entries(teamValues.clanDefence_titans.units)) {
titans.push(unit[1]);
totalTitanPower += unit[1].power;
}
this._championsByPower[index].heroes = { power: totalHeroPower, teams: heroes };
this._championsByPower[index].titans = { power: totalTitanPower, teams: titans };
this.guildTotalHeroesPower += totalHeroPower;
this.guildTotalTitansPower += totalTitanPower;
this._championsByPower[index].rewards = Math.floor((this._championsByPower[index].scoreGifts + this._championsByPower[index].warGifts));
}
this.guildAverageHeroesPower = Math.floor((this.guildTotalHeroesPower / this._championsByPower.length));
this.guildAverageTitansPower = Math.floor((this.guildTotalTitansPower / this._championsByPower.length));
}
getChampionsByPower(): HWMember[] {
return this._championsByPower;
}
}
@@ -0,0 +1,8 @@
export * from './fortification-card-content/fortification-card-content.component';
export * from './guild-card/guild-card.component';
export * from './guildraids-log/guildraids-log.component';
export * from './guildwar-attack/guildwar-attack.component';
export * from './guildwar-champions/guildwar-champions.component';
export * from './guildwar-defence/guildwar-defence.component';
export * from './guildwar-teams/guildwar-teams.component';
export * from './members-statistics/members-statistics.component';
@@ -0,0 +1,270 @@
<mat-card class="my-3">
<mat-card-header class="bg-megna text-bg-primary d-flex rounded-top py-2 px-3">
<div class="flex-fill">
<mat-card-title>
{{ title }}
</mat-card-title>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center text-end">
<span class="fs-5 font-light">{{ subtitle }}</span>
</div>
</mat-card-header>
<mat-card-content class="table-responsive p-0">
<table class="table table-striped table-condensed border-top border-light">
<thead>
<tr role="row" class="bg-megna text-bg-primary bg-opacity-50">
<th scope="col" class="align-middle w-auto ps-4">
<div class="text-start">
#
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="d-flex align-items-center text-start">
Name
<mat-icon fontIcon="arrow_upward" class="ms-1 icon-xsmall"></mat-icon>
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
ID
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Last Login
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-start">
Dismissal Delay
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Dungeon
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Activity
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Prestige
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
War Fights
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Adventures
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Score
</div>
</th>
<th scope="col" class="align-middle w-auto">
<div class="text-end">
Name
</div>
</th>
<th scope="col" class="align-middle w-auto pe-4">
<div class="text-end">
Rewards
</div>
</th>
</tr>
</thead>
<tbody>
@for (member of getMembersByName(); track member; let index = $index) {
<tr role="row" class="align-middle">
<td class="cell text-start ps-4">
{{ (index+1) }}
</td>
<td class="cell text-start">
<div class="d-flex">
<mat-icon [fontIcon]="member.champion ? 'gpp_good' : 'gpp_bad'" class="me-2 icon-small {{ member.champion ? 'text-green' : 'text-muted' }}"></mat-icon>
<span [class]="member.champion ? 'text-green' : ''">{{ member.name }}</span>
</div>
</td>
<td class="cell font-monospace text-start">
<span class="me-3">{{ member.id }}</span>
</td>
<td class="cell font-monospace text-start">
<span class="me-3">{{ member.lastLoginTime + '000' | date: 'dd/MM HH:mm' }}</span>
</td>
<td class="cell font-monospace text-start">
<span [class]="member.inactivity.daysLeft < daysToWarn ? 'text-red' : ''">{{ member.inactivity.dropDelay }}</span>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ member.stat.todayDungeonActivity | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
<span [class]="member.stat.dungeonActivitySum < guildSumAverages.titanite ? 'text-orange' : 'text-megna'">{{ member.stat.dungeonActivitySum | number : '1.0' }}</span>
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ member.stat.todayActivity | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
<span [class]="member.stat.activitySum < guildSumAverages.activity ? 'text-orange' : 'text-megna'">{{ member.stat.activitySum | number : '1.0' }}</span>
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ member.stat.todayPrestige | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
<span [class]="member.stat.prestigeSum < guildSumAverages.prestige ? 'text-orange' : 'text-megna'">{{ member.stat.prestigeSum | number : '1.0' }}</span>
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<span [class]="!member.champion ? 'text-muted' : member.clanWarSum < guildSumAverages.war ? 'text-orange' : 'text-megna'">{{ member.clanWarSum }}</span>
</td>
<td class="cell font-monospace text-end">
<span [class]="member.adventureSum < guildSumAverages.adventure ? 'text-orange' : 'text-megna'">{{ member.adventureSum }}</span>
</td>
<td class="cell font-monospace text-end">
<span [class]="member.score < guildSumAverages.score ? 'text-orange' : 'text-megna'">{{ member.score | number : '1.0' }}</span>
</td>
<td class="cell text-start">
<div class="d-flex">
<span [class]="member.champion ? 'text-green' : ''">{{ member.name }}</span>
</div>
</td>
<td class="cell font-monospace text-end pe-4">
<span [class]="member.rewards < guildSumAverages.rewards ? 'text-orange' : 'text-megna'">{{ member.rewards | number : '1.0' }}</span>
</td>
</tr>
}
</tbody>
<tfoot>
<tr role="row" class="bg-light fw-medium align-middle">
<td colspan="5" class="cell text-end fw-medium pe-sm-2">
Totals
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayTotal.titanite | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumTotal.titanite | number : '1.0' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayTotal.activity | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumTotal.activity | number : '1.0' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayTotal.prestige | number : '1.0' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumTotal.prestige | number : '1.0' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
{{ guildSumTotal.war | number : '1.0' }}
</td>
<td class="cell font-monospace text-end">
{{ guildSumTotal.adventure | number : '1.0' }}
</td>
<td class="cell font-monospace text-end">
{{ guildSumTotal.score | number : '1.0' }}
</td>
<td colspan="2" class="cell font-monospace text-end pe-4">
{{ guildSumTotal.rewards }}
</td>
</tr>
<tr role="row" class="bg-megna text-bg-primary fw-medium align-middle">
<td colspan="5" class="cell text-end pe-sm-2">
Averages
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayAverages.titanite | number : '1.0-1' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumAverages.titanite | number : '1.0-1' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayAverages.activity | number : '1.0-1' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumAverages.activity | number : '1.0-1' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
<div class="hstack gap-2">
<div class="value px-1 ms-auto">
{{ guildTodayAverages.prestige | number : '1.0-1' }}
</div>
<div class="vr"></div>
<div class="value px-1">
{{ guildSumAverages.prestige | number : '1.0-1' }}
</div>
</div>
</td>
<td class="cell font-monospace text-end">
{{ guildSumAverages.war | number : '1.0-1' }}
</td>
<td class="cell font-monospace text-end">
{{ guildSumAverages.adventure | number : '1.0-1' }}
</td>
<td class="cell font-monospace text-end">
{{ guildSumAverages.score | number : '1.0-1' }}
</td>
<td colspan="2" class="cell font-monospace text-end pe-4">
{{ guildSumAverages.rewards | number : '1.0-1' }}
</td>
</tr>
</tfoot>
</table>
</mat-card-content>
</mat-card>
<div class="d-flex align-items-center ps-2 mb-3">
<mat-icon fontIcon="gpp_good" class="me-2 icon-small text-megna"></mat-icon> <span class="d-inline-block">Champion signet</span>
<mat-icon fontIcon="gpp_bad" class="ms-3 me-2 icon-small text-muted"></mat-icon> <span class="d-inline-block">No Champion signet</span>
<mat-icon fontIcon="fiber_manual_record" class="ms-3 me-2 icon-small text-red"></mat-icon> <span class="d-inline-block">Inactivity &gt; {{ daysToWarn }} days</span>
<mat-icon fontIcon="fiber_manual_record" class="ms-3 me-2 icon-small text-orange"></mat-icon> <span class="d-inline-block">Below average</span>
<mat-icon fontIcon="fiber_manual_record" class="ms-3 me-2 icon-small text-megna"></mat-icon> <span class="d-inline-block">Above average</span>
</div>
@@ -0,0 +1,5 @@
/* members-statistics */
.hstack .value {
min-width: 56px;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MembersStatisticsComponent } from './members-statistics.component';
describe('MembersStatisticsComponent', () => {
let component: MembersStatisticsComponent;
let fixture: ComponentFixture<MembersStatisticsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MembersStatisticsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(MembersStatisticsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,159 @@
import { Component, Input, OnInit } from '@angular/core';
import { DatePipe, DecimalPipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { HWActivityStat, HWGuildClan, HWGuildData, HWMember, HWMemberStat, HWWeekStat } from 'src/app/core/models/herowars';
import { UtilitiesService } from 'src/app/core/services';
import guildData from 'src/files-data/hw-guild-data.json'; // page Membres
import guildStatistics from 'src/files-data/hw-guild-statistics.json'; // page Overview -> Statistics
@Component({
selector: 'app-members-statistics',
standalone: true,
imports: [
DatePipe, DecimalPipe,
MatCardModule, MatDividerModule, MatIconModule
],
templateUrl: './members-statistics.component.html',
styleUrl: './members-statistics.component.scss'
})
export class MembersStatisticsComponent implements OnInit {
public guildMembers: HWMember[] = [];
public title = 'Members';
public subtitle = 'statistics';
public daysToWarn = 1;
public guildSumAverages: HWActivityStat = { activity: 0, prestige: 0, titanite: 0, war: 0, adventure: 0, gifts: 0, score: 0, scoreGifts: 0, warGifts: 0, rewards: 0 };
public guildTodayAverages: HWActivityStat = { activity: 0, prestige: 0, titanite: 0, war: 0, adventure: 0, gifts: 0, score: 0, scoreGifts: 0, warGifts: 0, rewards: 0 };
public guildSumTotal: HWActivityStat = { activity: 0, prestige: 0, titanite: 0, war: 0, adventure: 0, gifts: 0, score: 0, scoreGifts: 0, warGifts: 0, rewards: 0 };
public guildTodayTotal: HWActivityStat = { activity: 0, prestige: 0, titanite: 0, war: 0, adventure: 0, gifts: 0, score: 0, scoreGifts: 0, warGifts: 0, rewards: 0 };
public guildData: HWGuildData = {} as HWGuildData;
private _guildClan: HWGuildClan = {} as HWGuildClan;
private _membersByName: HWMember[] = [];
private _deviations: { activity: number[], prestige: number[], titanite: number[] } = { activity: [], prestige: [], titanite: [] };
@Input() set members(value: HWMember[]) {
this.guildMembers = value;
}
constructor(
private _utilitiesService: UtilitiesService
) { }
ngOnInit() {
const oneDayInSec = (24 * 60 * 60);
const today = new Date();
const daysToKick = parseInt(guildData.clan.daysToKick);
this.daysToWarn = (parseInt(guildData.clan.daysToKick) / 2);
this.guildMembers.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1)).map((data) => {
const last = new Date((parseInt(data.lastLoginTime) * 1000));
const exp = new Date(last.getTime() + ((daysToKick * oneDayInSec) * 1000));
const diff = Math.floor(((exp.getTime() - today.getTime()) / 1000));
const daysLeft = Math.floor((diff / oneDayInSec));
let timeLeft = diff;
if (daysLeft >= 1) {
timeLeft = (diff - (daysLeft * oneDayInSec));
}
data.inactivity = {
daysLeft: daysLeft,
timeLeft: timeLeft,
dropDelay: `${daysLeft}d, ${this._utilitiesService.secondsToDuration(timeLeft)}`,
dropDate: Math.floor((exp.getTime() / 1000))
};
this._membersByName.push(data);
return data;
});
Object.assign(this._guildClan, guildData.clan);
this.guildData.clan = this._guildClan;
this.guildData.membersStat = guildData.membersStat;
for (const item of Object.entries(guildStatistics.stat)) {
const index = this._membersByName.findIndex(member => member.id === item[1].id);
if (index !== -1) {
this._membersByName[index].adventureSum = item[1].adventureStat.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
this._membersByName[index].clanGiftsSum = item[1].clanGifts.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
this._membersByName[index].clanWarSum = item[1].clanWarStat.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
this.guildSumTotal.adventure += this._membersByName[index].adventureSum;
this.guildSumTotal.gifts += this._membersByName[index].clanGiftsSum;
this.guildSumTotal.war += this._membersByName[index].clanWarSum;
const stat: HWWeekStat = {} as HWWeekStat;
Object.assign(stat, item[1]);
this._membersByName[index].weekStat = stat;
} else {
console.log(item[1].id);
}
}
for (const data of Object.entries(guildData.membersStat)) {
const stat: HWMemberStat = {} as HWMemberStat;
Object.assign(stat, data[1]);
const index = this._membersByName.findIndex(member => member.id === data[1].userId);
if (index !== -1) {
this._membersByName[index].stat = stat;
}
this._membersByName[index].score = (this._membersByName[index].stat.dungeonActivitySum + this._membersByName[index].stat.activitySum + this._membersByName[index].stat.prestigeSum);
this._membersByName[index].warGifts = (((guildData.clan.giftsCount / 2) * ((this._membersByName[index].clanWarSum * 10) / 100)) / 20);
this.guildTodayTotal.activity += stat.todayActivity;
this.guildTodayTotal.prestige += stat.todayPrestige;
this.guildTodayTotal.titanite += stat.todayDungeonActivity;
this.guildTodayTotal.score += (this._membersByName[index].stat.todayDungeonActivity + this._membersByName[index].stat.todayActivity + this._membersByName[index].stat.todayPrestige);
this.guildSumTotal.activity += stat.activitySum;
this.guildSumTotal.prestige += stat.prestigeSum;
this.guildSumTotal.titanite += stat.dungeonActivitySum;
this.guildSumTotal.score += this._membersByName[index].score;
this.guildSumTotal.warGifts += this._membersByName[index].warGifts;
this._deviations.activity.push(stat.activitySum);
this._deviations.prestige.push(stat.prestigeSum);
this._deviations.titanite.push(stat.dungeonActivitySum);
//this.guildSumTotal.warGifts += ((this._membersByName[index].score / this.guildSumTotal.score) * (guildData.clan.giftsCount / 2))
}
this.guildSumTotal.scoreGifts = (guildData.clan.giftsCount - Math.floor(this.guildSumTotal.warGifts));
this.guildTodayAverages.activity = (this.guildTodayTotal.activity / this._membersByName.length);
this.guildTodayAverages.prestige = (this.guildTodayTotal.prestige / this._membersByName.length);
this.guildTodayAverages.titanite = (this.guildTodayTotal.titanite / this._membersByName.length);
this.guildTodayAverages.score = (this.guildTodayTotal.score / this._membersByName.length);
this.guildSumAverages.activity = (this.guildSumTotal.activity / this._membersByName.length);
this.guildSumAverages.prestige = (this.guildSumTotal.prestige / this._membersByName.length);
this.guildSumAverages.titanite = (this.guildSumTotal.titanite / this._membersByName.length);
this.guildSumAverages.war = (this.guildSumTotal.war / 20);
this.guildSumAverages.adventure = (this.guildSumTotal.adventure / this._membersByName.length);
this.guildSumAverages.gifts = (this.guildSumTotal.gifts / this._membersByName.length);
this.guildSumAverages.score = (this.guildSumTotal.score / this._membersByName.length);
this._membersByName.map((data) => {
data.scoreGifts = ((data.score / this.guildSumTotal.score) * this.guildSumTotal.scoreGifts);
data.rewards = Math.floor((data.scoreGifts + data.warGifts));
this.guildSumTotal.rewards += data.rewards;
return data;
});
this.guildSumAverages.rewards = (this.guildSumTotal.rewards / this._membersByName.length);
}
getMembersByName(): HWMember[] {
return this._membersByName;
}
private _standardDeviation(array: number[]): number {
const n = array.length;
const mean = array.reduce((a, b) => a + b) / n;
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
}
getStandardDeviationActivity(): number {
return this._standardDeviation(this._deviations.activity);
}
getStandardDeviationAPrestige(): number {
return this._standardDeviation(this._deviations.prestige);
}
getStandardDeviationTitanite(): number {
return this._standardDeviation(this._deviations.titanite);
}
}
-1
View File
@@ -1,6 +1,5 @@
export * from './accordion'; export * from './accordion';
export * from './dashboard-components'; export * from './dashboard-components';
export * from './fortification-card-content/fortification-card-content.component';
export * from './helpers-chart'; export * from './helpers-chart';
export * from './helpers-jump'; export * from './helpers-jump';
export * from './herowars-components'; export * from './herowars-components';
+3 -1
View File
@@ -53,7 +53,9 @@ const MENUITEMS: Menu[] = [
{ parent: 'page', state: 'les-fleurs', type: 'link', name: 'Fleurs', icon: 'marijuana', desc: '' }, { parent: 'page', state: 'les-fleurs', type: 'link', name: 'Fleurs', icon: 'marijuana', desc: '' },
{ parent: 'page', state: 'les-resines', type: 'link', name: 'Résines', icon: 'butter', desc: '' }, { parent: 'page', state: 'les-resines', type: 'link', name: 'Résines', icon: 'butter', desc: '' },
{ parent: 'page', state: 'les-molecules', type: 'link', name: 'Molécules', icon: 'molecule', desc: '' }, { parent: 'page', state: 'les-molecules', type: 'link', name: 'Molécules', icon: 'molecule', desc: '' },
{ state: 'herowars', type: 'link', name: 'Hero Wars', icon: 'military_tech', icontype: 'mat', desc: '' } { state: 'herowars', type: 'menu', name: 'Hero Wars', icon: 'military_tech', icontype: 'mat', desc: '' },
{ parent: 'herowars', state: 'guildwar', type: 'link', name: 'Guild War', icon: 'military_tech', icontype: 'mat', desc: '' },
{ parent: 'herowars', state: 'guildraid', type: 'link', name: 'Guild Raid', icon: 'military_tech', icontype: 'mat', desc: '' }
]; ];
const MENUPJUMP: Menu[] = [ const MENUPJUMP: Menu[] = [
@@ -0,0 +1,16 @@
export interface HWClanListConfig {
type: string;
filters: HWClanListFilters;
}
export interface HWClanListFilters {
id?: string;
title?: string;
country?: string;
level?: string;
minLevel?: string;
ownerId?: string;
serverId?: string;
topActivity?: string;
topDungeon?: string;
}
@@ -0,0 +1,6 @@
import { HWClan } from './clan.model';
export interface HWClanList {
clans: Array<HWClan>;
clansCount: number;
}
@@ -0,0 +1,84 @@
import { HWMember } from './member.model';
import { HWActivityStat, HWGuildStat, HWMemberStat } from './stat.model';
import { PageLinks, PageMeta } from '../shared.model';
export interface HWGuildClan {
id: string;
ownerId: string;
level: string;
title: string;
description: string;
icon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
country: string;
minLevel: string;
serverId: string;
membersCount: string;
disbanding: boolean;
topActivity: string;
topDungeon: string;
roleNames: never[];
frameId: number;
members: {[key: string]: HWMember};
news: string;
activityPoints: number;
dungeonPoints: number;
blackList: {[key: string]: number};
warriors: number[];
giftsCount: number;
daysToKick: string;
league: string;
sumAverages: HWActivityStat;
sumTotal: HWActivityStat;
todayAverages: HWActivityStat;
todayTotal: HWActivityStat;
}
export interface HWGuildData {
clan: HWGuildClan;
slots: {[key: string]: number};
membersStat: HWMemberStat[];
stat: HWGuildStat;
serverResetTime: number;
clanWarEndSeasonTime: number;
freeClanChangeInterval: {
start: number;
end: number;
};
giftUids: string[];
}
export interface HWClan {
id: string;
country: string;
description: string;
disbanding: boolean;
frameId: number;
icon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
level: string;
members: {[key: string]: HWMember};
membersCount: string;
minLevel: string;
ownerId: string;
/*roleNames: never[];*/
serverId: string;
title: string;
topActivity: string;
topDungeon: string;
}
export interface HWClanPageData {
clan: HWClan;
_links: PageLinks;
_meta: PageMeta;
}
@@ -0,0 +1,10 @@
import { HWMember } from './member.model';
export interface HWGuildWarFortification {
name: string;
type: string;
count: number;
teams: HWMember[];
positions: number[];
slots: number[];
}
@@ -0,0 +1,80 @@
export interface HWGuildWarHeroTeam {
id: number;
level: number;
color: number;
star: number;
power: number;
}
export interface HWGuildWarTitanTeam {
id: number;
level: number;
star: number;
element: string;
elementSpiritLevel: number;
elementSpiritStar: number;
elementSpiritSkills: {
skillId: number;
level: number;
tierScale: number;
}[];
elementAffinityPower: number;
power: number;
}
export interface HWGuildWarEnemyTeam {
element: string;
elementSpiritLevel: number;
elementSpiritStar: number;
elementSpiritSkills: {
skillId: number;
level: number;
tierScale: number;
}[];
elementAffinityPower: number;
state: {
isDead: boolean;
hp: number;
energy: number;
maxHp: number;
};
id: number;
star: number;
color: number;
level: number;
power: number;
type: string;
}
export interface HWGuildWarSlots {
[key: string]: number[];
}
export interface HWGuildWarEnemySlot {
attackerId: number;
status: string;
user: {
id: string;
name: string;
lastLoginTime: string;
serverId: string;
level: string;
clanId: string;
clanRole: string;
commander: boolean;
avatarId: string;
isChatModerator: boolean;
frameId: number;
leagueId: number;
allowPm: string;
clanTitle: string;
clanIcon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
};
pointsFarmed: number;
slotId: number;
totalPoints: number;
teams: HWGuildWarEnemyTeam[];
}
+264
View File
@@ -0,0 +1,264 @@
export interface HWMember {
id: string;
name: string;
lastLoginTime: string;
serverId: string;
level: string;
clanId: string;
clanRole: string;
commander: boolean;
avatarId: string;
isChatModerator: boolean;
frameId: number;
leagueId: number;
allowPm: string;
showProfile: boolean;
clanTitle: string;
clanIcon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
champion: boolean;
heroes: {
power: number;
teams: HWGuildWarHeroTeam[];
};
titans: {
power: number;
teams: HWGuildWarTitanTeam[];
};
stat: HWMemberStat;
inactivity: {
daysLeft: number,
timeLeft: number,
dropDate: number;
dropDelay: string;
}
adventureSum: number;
clanGiftsSum: number;
clanWarSum: number;
weekStat: HWWeekStat;
score: number;
scoreGifts: number;
warGifts: number;
rewards: number;
raids: HWGuildRaid[];
raidsInfo: {
variationAvg: number;
variationSum: number;
}
}
export interface HWGuildWarHeroTeam {
id: number;
level: number;
color: number;
star: number;
power: number;
}
export interface HWGuildWarTitanTeam {
id: number;
level: number;
star: number;
element: string;
elementSpiritLevel: number;
elementSpiritStar: number;
elementSpiritSkills: {
skillId: number;
level: number;
tierScale: number;
}[];
elementAffinityPower: number;
power: number;
}
export interface HWGuildWarFortification {
name: string;
type: string;
count: number;
teams: HWMember[];
positions: number[];
slots: number[];
}
export interface HWGuildStat {
todayActivity: number;
activitySum: number;
dungeonActivitySum: number;
todayRaid: never[];
todayItemsActivity: number;
todayDungeonActivity: number;
activityForRuneAvailable: boolean;
adventureStat: number;
clanWarStat: number;
}
export interface HWGuildClan {
id: string;
ownerId: string;
level: string;
title: string;
description: string;
icon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
country: string;
minLevel: string;
serverId: string;
membersCount: string;
disbanding: boolean;
topActivity: string;
topDungeon: string;
roleNames: never[];
frameId: number;
members: {[key: string]: HWMember};
news: string;
activityPoints: number;
dungeonPoints: number;
blackList: {[key: string]: number};
warriors: number[];
giftsCount: number;
daysToKick: string;
league: string;
}
export interface HWGuildData {
clan: HWGuildClan;
slots: {[key: string]: number};
membersStat: HWMemberStat[];
stat: HWGuildStat;
serverResetTime: number;
clanWarEndSeasonTime: number;
freeClanChangeInterval: {
start: number;
end: number;
};
giftUids: string[];
}
export interface HWActivityStat {
activity: number;
prestige: number;
titanite: number;
war: number;
adventure: number;
gifts: number;
score: number;
scoreGifts: number;
warGifts: number;
rewards: number;
}
export interface HWWeekStat {
activity: number[];
dungeonActivity: number[];
adventureStat: number[];
clanWarStat: number[];
prestigeStat: number[];
clanGifts: number[];
}
export interface HWMemberStat {
activitySum: number;
todayActivity: number;
dungeonActivitySum: number;
todayDungeonActivity: number;
raidSum: number;
wasChampion: boolean;
todayPrestige: number;
prestigeSum: number;
}
export interface HWGuildRaidAttacker {
id: number;
power: number;
type: string;
}
export interface HWGuildRaidStage {
num: number;
name: string;
maxPower: number;
duration: number;
startTime: number;
endTime: number;
}
export interface HWGuildRaid {
userId: string;
userName: string;
power: number;
variation: {
value: number;
percent: number;
color: string;
};
tooltip: string;
startTime: string;
endTime: string;
damage: {
"1": number;
"2": number;
};
level: string;
attackers: HWGuildRaidAttacker[];
stage: HWGuildRaidStage;
}
export interface HWGuildRaidAttackers {
[key: string]: HWGuildRaidAttacker;
}
export interface HWGuildWarEnemySlot {
attackerId: number;
status: string;
user: {
id: string;
name: string;
lastLoginTime: string;
serverId: string;
level: string;
clanId: string;
clanRole: string;
commander: boolean;
avatarId: string;
isChatModerator: boolean;
frameId: number;
leagueId: number;
allowPm: string;
clanTitle: string;
clanIcon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
};
pointsFarmed: number;
slotId: number;
totalPoints: number;
teams: HWGuildWarEnemyTeam[];
}
export interface HWGuildWarEnemyTeam {
element: string;
elementSpiritLevel: number;
elementSpiritStar: number;
elementSpiritSkills: {
skillId: number;
level: number;
tierScale: number;
}[];
elementAffinityPower: number;
state: {
isDead: boolean;
hp: number;
energy: number;
maxHp: number;
};
id: number;
star: number;
color: number;
level: number;
power: number;
type: string;
}
export interface HWGuildWarSlots {
[key: string]: number[];
}
+9
View File
@@ -0,0 +1,9 @@
export * from './clan-list-config.model';
export * from './clan-list.model';
export * from './clan.model';
export * from './fortification.model';
export * from './guildwar.model';
//export * from './hw.model';
export * from './member.model';
export * from './raid.model';
export * from './stat.model';
@@ -0,0 +1,57 @@
import { HWGuildRaid } from './raid.model';
import { HWMemberStat, HWWeekStat } from './stat.model';
import { HWGuildWarHeroTeam, HWGuildWarTitanTeam } from './guildwar.model';
export interface HWMember {
id: string;
allowPm: string;
avatarId: string;
clanIcon: {
flagColor1: number;
flagColor2: number;
flagShape: number;
iconColor: number;
iconShape: number;
};
clanId: string;
clanRole: string;
clanTitle: string;
commander: boolean;
frameId: number;
isChatModerator: boolean;
lastLoginTime: string;
leagueId: number;
level: string;
name: string;
serverId: string;
showProfile: boolean;
champion: boolean;
heroes: {
power: number;
teams: HWGuildWarHeroTeam[];
};
titans: {
power: number;
teams: HWGuildWarTitanTeam[];
};
stat: HWMemberStat;
inactivity: {
daysLeft: number,
timeLeft: number,
dropDate: number;
dropDelay: string;
}
adventureSum: number;
clanGiftsSum: number;
clanWarSum: number;
weekStat: HWWeekStat;
score: number;
scoreGifts: number;
warGifts: number;
rewards: number;
raids: HWGuildRaid[];
raidsInfo: {
variationAvg: number;
variationSum: number;
}
}
@@ -0,0 +1,38 @@
export interface HWGuildRaidAttacker {
id: number;
power: number;
type: string;
}
export interface HWGuildRaidStage {
num: number;
name: string;
maxPower: number;
duration: number;
startTime: number;
endTime: number;
}
export interface HWGuildRaid {
userId: string;
userName: string;
power: number;
variation: {
value: number;
percent: number;
color: string;
};
tooltip: string;
startTime: string;
endTime: string;
damage: {
"1": number;
"2": number;
};
level: string;
attackers: HWGuildRaidAttacker[];
stage: HWGuildRaidStage;
}
export interface HWGuildRaidAttackers {
[key: string]: HWGuildRaidAttacker;
}
@@ -0,0 +1,42 @@
export interface HWGuildStat {
todayActivity: number;
activitySum: number;
dungeonActivitySum: number;
todayRaid: never[];
todayItemsActivity: number;
todayDungeonActivity: number;
activityForRuneAvailable: boolean;
adventureStat: number;
clanWarStat: number;
}
export interface HWActivityStat {
activity: number;
prestige: number;
titanite: number;
war: number;
adventure: number;
gifts: number;
score: number;
scoreGifts: number;
warGifts: number;
rewards: number;
}
export interface HWWeekStat {
activity: number[];
dungeonActivity: number[];
adventureStat: number[];
clanWarStat: number[];
prestigeStat: number[];
clanGifts: number[];
}
export interface HWMemberStat {
activitySum: number;
todayActivity: number;
dungeonActivitySum: number;
todayDungeonActivity: number;
raidSum: number;
wasChampion: boolean;
todayPrestige: number;
prestigeSum: number;
}
+1 -1
View File
@@ -13,7 +13,7 @@ export * from './category.model';
export * from './chart.model'; export * from './chart.model';
export * from './dropzone.model'; export * from './dropzone.model';
export * from './errors.model'; export * from './errors.model';
export * from './hw.model'; export * from './herowars/';
export * from './jump.model'; export * from './jump.model';
export * from './jump-list.model'; export * from './jump-list.model';
export * from './jump-list-config.model'; export * from './jump-list-config.model';
+60
View File
@@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import { HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import {
HWClan, HWClanList, HWClanListConfig, HWClanListFilters, HWClanPageData
} from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class HWClanService {
constructor(
private apiService: ApiService
) { }
query(config: HWClanListConfig): Observable<HWClanList> {
// Convert any filters over to Angular's URLSearchParams
const params: HWClanListFilters = {} as HWClanListFilters;
Object.assign(params, config.filters);
return this.apiService
.get(
'/clans',
new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params })
);
}
get(id: string): Observable<HWClanPageData> {
return this.apiService.get(`/clans/${id}`);
}
getAll(): Observable<Array<HWClan>> {
return this.apiService.get('/clans').pipe(map(data => data.clans));
}
create(clan: HWClan): Observable<HWClan> {
return this.apiService.post('/clans/', { clan: clan }).pipe(map(data => data.clan));
}
update(clan: HWClan): Observable<HWClan> {
return this.apiService.put(`/clans/${clan.id}`, { clan: clan }).pipe(map(data => data.clan));
}
destroy(id: string): Observable<boolean> {
return this.apiService.delete(`/clans/${id}`).pipe(map(data => data.deleted));
}
save(clan: HWClan): Observable<HWClan> {
// If we're updating an existing clan
if (clan.id) {
return this.apiService.put(`/clans/${clan.id}`, { clan: clan }).pipe(map(data => data.clan));
// Otherwise, create a new clan
} else {
return this.apiService.post('/clans/', { clan: clan }).pipe(map(data => data.clan));
}
}
}
+60
View File
@@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import { HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import {
HWClan, HWClanList, HWClanListConfig, HWClanListFilters, HWClanPageData
} from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class HWClanService {
constructor(
private apiService: ApiService
) { }
query(config: HWClanListConfig): Observable<HWClanList> {
// Convert any filters over to Angular's URLSearchParams
const params: HWClanListFilters = {} as HWClanListFilters;
Object.assign(params, config.filters);
return this.apiService
.get(
'/clans',
new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params })
);
}
get(id: string): Observable<HWClanPageData> {
return this.apiService.get(`/clans/${id}`);
}
getAll(): Observable<Array<HWClan>> {
return this.apiService.get('/clans').pipe(map(data => data.clans));
}
create(clan: HWClan): Observable<HWClan> {
return this.apiService.post('/clans/', { clan: clan }).pipe(map(data => data.clan));
}
update(clan: HWClan): Observable<HWClan> {
return this.apiService.put(`/clans/${clan.id}`, { clan: clan }).pipe(map(data => data.clan));
}
destroy(id: string): Observable<boolean> {
return this.apiService.delete(`/clans/${id}`).pipe(map(data => data.deleted));
}
save(clan: HWClan): Observable<HWClan> {
// If we're updating an existing clan
if (clan.id) {
return this.apiService.put(`/clans/${clan.id}`, { clan: clan }).pipe(map(data => data.clan));
// Otherwise, create a new clan
} else {
return this.apiService.post('/clans/', { clan: clan }).pipe(map(data => data.clan));
}
}
}
+12 -1
View File
@@ -2,7 +2,8 @@ import { Routes } from '@angular/router';
import { import {
AeronefsComponent, CalculatorComponent, CanopiesComponent, CredentialsComponent, AeronefsComponent, CalculatorComponent, CanopiesComponent, CredentialsComponent,
DashboardComponent, DemoComponent, DropzonesComponent, HerowarsComponent, JumpComponent, JumpsComponent, HerowarsComponent, HerowarsGuildraidComponent, HerowarsGuildwarComponent,
DashboardComponent, DemoComponent, DropzonesComponent, JumpComponent, JumpsComponent,
LogbookComponent, PageComponent, PagesComponent, ProductComponent, ProductsComponent, ProfileComponent, QcmComponent, SettingsComponent LogbookComponent, PageComponent, PagesComponent, ProductComponent, ProductsComponent, ProfileComponent, QcmComponent, SettingsComponent
} from 'src/app/components'; } from 'src/app/components';
import { authGuard, adminGuard } from 'src/app/core/guards'; import { authGuard, adminGuard } from 'src/app/core/guards';
@@ -62,6 +63,16 @@ export const AUTH_ROUTES: Routes = [
component: HerowarsComponent, component: HerowarsComponent,
canActivate: [ authGuard ] canActivate: [ authGuard ]
}, },
{
path: 'herowars/guildwar',
component: HerowarsGuildwarComponent,
canActivate: [ authGuard ]
},
{
path: 'herowars/guildraid',
component: HerowarsGuildraidComponent,
canActivate: [ authGuard ]
},
{ {
path: 'jumps', path: 'jumps',
component: JumpsComponent, component: JumpsComponent,
+34
View File
@@ -580,6 +580,40 @@ $md-custom-pink: (
A700 : #000000, A700 : #000000,
) )
); );
$md-custom-jinza: (
50 : #ffecef,
100 : #ffd0d4,
200 : #f3a09d,
300 : #eb7a77,
400 : #f65e54,
500 : #fa5039,
600 : #ed4739,
700 : #da3d33,
800 : #cd372c,
900 : #be2c20,
A100 : #fdf1f1,
A200 : #fbe4e4,
A400 : #f7cac9,
A700 : #f1a19f,
contrast: (
50 : #000000,
100 : #000000,
200 : #000000,
300 : #000000,
400 : #000000,
500 : #000000,
600 : #000000,
700 : #ffffff,
800 : #ffffff,
900 : #ffffff,
A100 : #000000,
A200 : #000000,
A400 : #000000,
A700 : #000000,
)
);
$md-custom-red: ( $md-custom-red: (
50: #ffebee, 50: #ffebee,
100: #ffcdd2, 100: #ffcdd2,
+3
View File
@@ -259,6 +259,9 @@ dl.dl-horizontal {
.text-error { .text-error {
color: $error !important; color: $error !important;
} }
.text-muted {
color: $muted !important;
}
/**************************** /****************************
* Extra Background Colors * * Extra Background Colors *