Skip to content
Merged

Deploy #1731

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ any help with getting started, I'm available most days.

1. Install Docker and Docker Compose - [Windows install instructions](https://docs.docker.com/docker-for-windows/install/)

1. Install the dotnet SDK, currently 8.x
1. Install the dotnet SDK, currently 9.x

1. Log in or sign up for a [Battle.Net Developer](https://develop.battle.net) account

Expand Down Expand Up @@ -119,7 +119,7 @@ any help with getting started, I'm available most days.
1. Start the initial data import/build, this will take a while:

```bash
cd app/tool/
cd apps/tool/
dotnet run all
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ApiCharacterRaiderIoSeason
{ "season-tww-2", 14 },
{ "season-tww-3", 15 },
{ "season-tww-3-legion-remix", 1001 },
{ "season-mn-1", 17 },
};

public string Season { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sortBy from 'lodash/sortBy';
import { router } from 'svelte-spa-router';

import { setCharacterTableContext } from './context';
import { browserState } from '@/shared/state/browser.svelte';
import { settingsState } from '@/shared/state/settings.svelte';
import { newNavState } from '@/stores/local-storage';
Expand Down Expand Up @@ -115,6 +116,8 @@
return [characters, groups];
});

setCharacterTableContext(() => ({ characters }));

const paddingMap: Record<string, number> = {
small: 1,
medium: 2,
Expand Down
9 changes: 9 additions & 0 deletions apps/frontend/components/character-table/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from 'svelte';
import type { Character } from '@/types/character/character.svelte';

interface CharacterTableContext {
characters: Character[];
}

export const [getCharacterTableContext, setCharacterTableContext] =
createContext<() => CharacterTableContext>();
Original file line number Diff line number Diff line change
@@ -1,60 +1,55 @@
<script lang="ts">
import { getCharacterTableContext } from '@/components/character-table/context';
import { Constants } from '@/data/constants';
import { imageStrings } from '@/data/icons';
import { expansionProfessionConcentration } from '@/data/professions/cooldowns';
import { professionMoxie } from '@/data/professions/moxie';
import { ProfessionType } from '@/enums/profession-type';
import { Region } from '@/enums/region';
import { settingsState } from '@/shared/state/settings.svelte';
import { wowthingData } from '@/shared/stores/data';
import { timeStore } from '@/shared/stores/time';
import { componentTooltip } from '@/shared/utils/tooltips/component-tooltip.svelte';
import { getCurrencyData } from '@/utils/characters/get-currency-data';
import { getProfessionSortKey } from '@/utils/professions/get-profession-sort-key';
import type { StaticDataProfession } from '@/shared/stores/static/types/profession';
import type { CharacterSubProfession } from '@/types/character/profession.svelte';
import type { CharacterProps } from '@/types/props';

import Tooltip from '@/components/tooltips/professions/TooltipProfessions.svelte';
import WowthingImage from '@/shared/components/images/sources/WowthingImage.svelte';

let { character }: CharacterProps = $props();

let concentrationData = $derived(expansionProfessionConcentration[Constants.expansion]);
let professions = $derived.by(() => {
const ret: [StaticDataProfession, CharacterSubProfession, boolean][] = [];

for (const staticProfession of wowthingData.static.professionById.values()) {
if (staticProfession?.type !== ProfessionType.Primary) {
continue;
}

let best: [CharacterSubProfession, number];
for (const expansion of settingsState.expansions) {
const subProfession = staticProfession.expansionSubProfession[expansion.id];
if (subProfession) {
const characterSubProfession =
character.professions[staticProfession.id]?.subProfessions?.[
subProfession.id
];
if (characterSubProfession && expansion.id >= (best?.[1] || 0)) {
best = [characterSubProfession, expansion.id];
}
}
}

if (best) {
ret.push([staticProfession, best[0], best[1] === Constants.expansion]);
}
}
const { characters: visibleCharacters } = getCharacterTableContext()();
let [anyConcentration, anyMoxie] = $derived.by(() => {
const professionIds = visibleCharacters.map((char) =>
char.professionData.map(([prof, , isCurrent]) => [prof.id, isCurrent])
) as [number, boolean][][];

ret.sort((a, b) => getProfessionSortKey(a[0]).localeCompare(getProfessionSortKey(b[0])));
const retConcentration = [
professionIds.some((data) => data[0]?.[1] && !!concentrationData[data[0][0]]),
professionIds.some((data) => data[1]?.[1] && !!concentrationData[data[1][0]]),
];
const retMoxie = [
professionIds.some((data) => data[0]?.[1] && !!professionMoxie[data[0][0]]),
professionIds.some((data) => data[1]?.[1] && !!professionMoxie[data[1][0]]),
];

return ret;
return [retConcentration, retMoxie];
});

let concentrationData = $derived(expansionProfessionConcentration[Constants.expansion]);
let professions = $derived(character.professionData);

// TODO: derive from settings state
let fields = $derived(['concentration', 'moxie']);

let columnCounts = $derived([
1 +
(fields.includes('concentration') && anyConcentration[0] ? 1 : 0) +
(fields.includes('moxie') && anyMoxie[0] ? 1 : 0),
1 +
(fields.includes('concentration') && anyConcentration[1] ? 1 : 0) +
(fields.includes('moxie') && anyMoxie[1] ? 1 : 0),
]);

function statusClass(fullIsBad: boolean, percent: number) {
if (percent >= 100) {
return fullIsBad ? 'status-fail' : 'status-success';
Expand All @@ -70,6 +65,8 @@

<style lang="scss">
td {
--profession-width: 3.5rem;

padding-left: 0;
padding-right: 0;
}
Expand All @@ -80,7 +77,6 @@
}
.professions {
display: grid;
grid-template-columns: 1fr 1fr;
width: 100%;
}
.profession {
Expand All @@ -89,8 +85,12 @@
display: grid;
flex-wrap: nowrap;
gap: 0.4rem;
grid-template-columns: repeat(var(--columns), 1fr);
grid-template-columns: repeat(var(--columns), var(--profession-width));
padding: 0 0.3rem;

> * {
width: var(--profession-width);
}
}
a {
--image-margin-top: 0;
Expand All @@ -114,13 +114,13 @@
</style>

<td class="b-l">
<div class="professions">
<div class="professions" style:grid-template-columns="{columnCounts[0]}fr {columnCounts[1]}fr">
{#each professions as [profession, charProfession, current], index (profession.id)}
{@const currentSkill = charProfession?.skillCurrent || 0}
<div
class="profession"
class:b-l={index > 0}
style:--columns={1 + fields.length}
style:--columns={columnCounts[index]}
data-id={profession.id}
>
<a
Expand All @@ -147,7 +147,7 @@

{#if current}
{#each fields as field (field)}
{#if field === 'concentration'}
{#if field === 'concentration' && anyConcentration[index]}
{@const concCurrency = wowthingData.static.currencyById.get(
concentrationData[profession.id]
)}
Expand All @@ -172,7 +172,7 @@
{:else}
<div class="concentration"></div>
{/if}
{:else if field === 'moxie'}
{:else if field === 'moxie' && anyMoxie[index]}
{@const moxieCurrency = wowthingData.static.currencyById.get(
professionMoxie[profession.id]
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
<code>level=60 | &lt;=,&lt;,=,&gt;,&gt;=</code>
</dd>

<dt>Last seen (days)</dt>
<dd>
<code>seen&lt;=7 | &lt;=,&lt;,=,&gt;,&gt;=</code>
</dd>

<dt>Item Level</dt>
<dd>
<code>ilevel&gt;=400 | &lt;=,&lt;,=,&gt;,&gt;=</code>
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/data/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export abstract class Constants {
static readonly maxRenown: number = 80;
static readonly restedDuration: number = 10 * 24 * 60 * 60; // 10 days

static readonly mythicPlusSeason: number = 15;
static readonly mythicPlusSeason: number = 17;

static readonly defaultUnixTime = 946684800;
static readonly defaultTime = DateTime.fromSeconds(Constants.defaultUnixTime);
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/data/mythic-plus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ const orderRemixLegion: number[] = [
export const seasonMap: Record<number, MythicPlusSeason> = Object.fromEntries(
[
new MythicPlusSeason({
id: 16,
id: 17,
name: '[Mid] Season 1',
slug: 'midnight-1',
minLevel: 90,
orders: [orderMidnightS1],
portalLevel: 10,
scoreType: MythicPlusScoreType.WarWithin,
startPeriod: 1055, // 2026-03-17
startPeriod: 1056, // 2026-03-24
}),
new MythicPlusSeason({
id: 1001,
Expand Down
40 changes: 38 additions & 2 deletions apps/frontend/types/character/character.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { getNumberKeyedEntries } from '@/utils/get-number-keyed-entries';
import { initializeContainsItems } from '@/utils/items/initialize-contains-items';
import { getDungeonScores } from '@/utils/mythic-plus';
import type { Region } from '@/enums/region';
import type { StaticDataRealm } from '@/shared/stores/static/types';
import type { StaticDataProfession, StaticDataRealm } from '@/shared/stores/static/types';
import type { Guild } from '@/types/guild';

import type { CharacterConfiguration } from './configuration';
Expand All @@ -46,7 +46,11 @@ import {
type CharacterMythicPlusAddonMapArray,
type CharacterMythicPlusAddonRunArray,
} from './mythic-plus';
import { CharacterProfession, type CharacterProfessionRaw } from './profession.svelte';
import {
CharacterProfession,
CharacterSubProfession,
type CharacterProfessionRaw,
} from './profession.svelte';
import type { CharacterRaiderIoSeason } from './raider-io-season';
import type {
CharacterReputation,
Expand All @@ -72,6 +76,8 @@ import type { CharacterAura } from './aura';
import type { CharacterPatronOrder } from './patron-order';
import type { CharacterMovementSpeed } from './movement-speed';
import type { CharacterQuests } from '@/user-home/state/user/types';
import { ProfessionType } from '@/enums/profession-type';
import { getProfessionSortKey } from '@/utils/professions/get-profession-sort-key';

export class Character implements ContainsItems, HasNameAndRealm {
// Static
Expand Down Expand Up @@ -622,6 +628,36 @@ export class Character implements ContainsItems, HasNameAndRealm {
return this.specializations[this.activeSpecId]?.find((loadout) => loadout.active);
});

public professionData = $derived.by(() => {
const ret: [StaticDataProfession, CharacterSubProfession, boolean][] = [];

for (const staticProfession of wowthingData.static.professionById.values()) {
if (staticProfession?.type !== ProfessionType.Primary) {
continue;
}

let best: [CharacterSubProfession, number];
for (const expansion of settingsState.expansions) {
const subProfession = staticProfession.expansionSubProfession[expansion.id];
if (subProfession) {
const characterSubProfession =
this.professions[staticProfession.id]?.subProfessions?.[subProfession.id];
if (characterSubProfession && expansion.id >= (best?.[1] || 0)) {
best = [characterSubProfession, expansion.id];
}
}
}

if (best) {
ret.push([staticProfession, best[0], best[1] === Constants.expansion]);
}
}

ret.sort((a, b) => getProfessionSortKey(a[0]).localeCompare(getProfessionSortKey(b[0])));

return ret;
});

public allProfessionAbilities = $derived.by(() => {
const allKnown = new Set<number>();
for (const profession of Object.values(this.professions)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
{ id: 'gold', name: 'Gold' },
{ id: 'itemlevel', name: 'Item level' },
{ id: 'level', name: 'Level' },
{ id: 'seen', name: 'Last seen' },
];

let sortByChoices: SettingsChoice[] = $derived([
Expand Down
13 changes: 13 additions & 0 deletions apps/frontend/utils/characters/use-character-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ export function useCharacterFilter(
);
}

// Last seen (in days)
match = part.match(/^seen(<|<=|=|>=|>)(\d+)$/);
if (match) {
const now = Math.floor(Date.now() / 1000);
const days = parseInt(match[2]);
const diff = (now - char.lastSeenAddonUnix) / 86400;
return compareValues(
match[1].toString(),
diff,
days
);
}

// Item level
match = part.match(/^(itemlevel|ilevel|ilvl)(<|<=|=|>=|>)(\d+)$/);
if (match) {
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/utils/get-character-sort-func.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export const getCharacterSortFunc = (prefixFunc?: SortValueFunction, viewSortBy?
'0'
)
);
} else if (thing === 'seen') {
out.push(leftPad(2000000000 - char.lastSeenAddonUnix, 10, '0'));
} else if (thing === 'level') {
// in descending order
const levelData = getCharacterLevel(char);
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/utils/home/home-sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { getDungeonLevel } from '@/utils/mythic-plus/get-dungeon-level';
import { getWorldTier } from '@/utils/vault/get-world-tier';
import type { Character } from '@/types';

const MAX_TIME = 2 ** 31 * 1000;

export function homeSort(char: Character, sortBy: string): string {
if (sortBy === 'gold') {
return leftPad(10_000_000 - char.gold, 8, '0');
Expand All @@ -36,7 +38,7 @@ export function homeSort(char: Character, sortBy: string): string {
'0'
);
} else if (sortBy === 'lastSeenAddon') {
return leftPad(2_000_000_000_000 - (char.lastSeenAddon?.toMillis() || 0), 13, '0');
return leftPad(MAX_TIME - (char.lastSeenAddon?.toMillis() || 0), 13, '0');
} else if (sortBy === 'currentLocation') {
// adding two spaces makes it sort before " > blah"
return char.currentLocation + ' ' || 'ZZZZZ';
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/utils/mythic-plus/get-dungeon-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export function getDungeonLevel(prog: CharacterWeeklyProgress): number {

let level = prog.level;
// Mythic
if ([3, 11, 14, 33, 69].includes(prog.tier)) {
if ([3, 11, 14, 33, 69, 102].includes(prog.tier)) {
level = 1;
}
// Heroic
else if ([2, 10, 13, 32, 68].includes(prog.tier)) {
else if ([2, 10, 13, 32, 68, 101].includes(prog.tier)) {
level = 0;
}
return level;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ await _context.Users

var characterQuery = _context.PlayerCharacter
.Where(c => c.Account.UserId == apiResult.User.Id)
.Where(c => c.Level > 0);
.Where(c => c.ClassId > 0 && c.RaceId > 0 && c.Level > 0)
if (apiResult.Public)
{
characterQuery = characterQuery.Where(c => c.Level >= 11);
Expand Down
Loading
Loading