Skip to content
Merged
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
41 changes: 35 additions & 6 deletions src/app/archipelago-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,32 @@ export async function initializeClient(initializeClientOptions: InitializeClient
}
}));

let prevRatHints = List<Hint>();
const itemNetworkIdToRatItem: Partial<Record<number, number>> = { };
for (const [id, item] of defs.allItems.entries()) {
if (item.ratCount === 0) {
continue;
}

itemNetworkIdToRatItem[itemNetworkNameLookup[itemName(item)]] = id;
}
const ratHints = computed(() => prevRatHints = prevRatHints.withMutations((rh) => {
const { team: myTeam, slot: mySlot } = client.players.self;
let seenCount = 0;
for (const hint of reactiveHints()) {
if (hint.item.id in itemNetworkIdToRatItem && hint.item.receiver.slot === mySlot && hint.item.receiver.team === myTeam) {
if (seenCount < rh.size) {
rh.set(seenCount, hint);
}
else {
rh.push(hint);
}

++seenCount;
}
}
}));

return {
connectScreenState,
client,
Expand All @@ -192,6 +218,7 @@ export async function initializeClient(initializeClientOptions: InitializeClient
slotData,
hintedLocations,
hintedItems,
ratHints,
locationIsProgression,
locationIsTrap,
storedData,
Expand Down Expand Up @@ -224,24 +251,26 @@ export type Message =
function createReactiveHints(client: Client, destroyRef?: DestroyRef): Signal<List<Hint>> {
const hints = signal(List<Hint>(client.items.hints));
function onHint(hint: Hint) {
hints.update(hints => hints.push(hint));
}
function onHintUpdated(hint: Hint) {
hints.update(hints => hints.update(hints.findIndex(h => h.uniqueKey === hint.uniqueKey), hint, () => hint));
hints.update((hints) => {
const hintIndex = hints.findIndex(h => h.uniqueKey === hint.uniqueKey);
return hintIndex < 0
? hints.push(hint)
: hints.set(hintIndex, hint);
});
}
function onHints(newHints: readonly Hint[]) {
hints.update(hints => hints.push(...newHints));
}
client.items.on('hintsInitialized', onHints);
client.items.on('hintReceived', onHint);
client.items.on('hintFound', onHint);
client.items.on('hintUpdated', onHintUpdated);
client.items.on('hintUpdated', onHint);
if (destroyRef) {
destroyRef.onDestroy(() => {
client.items.off('hintsInitialized', onHints);
client.items.off('hintReceived', onHint);
client.items.off('hintFound', onHint);
client.items.off('hintUpdated', onHintUpdated);
client.items.off('hintUpdated', onHint);
});
}
return hints.asReadonly();
Expand Down
1 change: 1 addition & 0 deletions src/app/data/slot-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface AutopelagoClientAndData {
messageLog: Signal<List<Message>>;
hintedLocations: Signal<List<Hint | null>>;
hintedItems: Signal<List<Hint | null>>;
ratHints: Signal<List<Hint>>;
slotData: AutopelagoSlotData;
storedData: AutopelagoStoredData;
storedDataKey: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,17 @@ import { watchAnimations } from './watch-animations';
[cdkConnectedOverlayScrollStrategy]="tooltipScrollStrategy()">
@if (tooltipOrigin(); as origin) {
@if (origin.location; as location) {
<app-location-tooltip [locationKey]="location" />
<app-location-tooltip
[locationKey]="location"
(mouseenter)="tooltipContext.notifyMouseEnterTooltip(origin.uid)"
(mouseleave)="tooltipContext.notifyMouseLeaveTooltip(origin.uid)"
/>
}
@else {
<app-player-tooltip />
<app-player-tooltip
(mouseenter)="tooltipContext.notifyMouseEnterTooltip(origin.uid)"
(mouseleave)="tooltipContext.notifyMouseLeaveTooltip(origin.uid)"
/>
}
}
</ng-template>
Expand Down Expand Up @@ -468,6 +475,7 @@ interface LandmarkProps extends LocationProps {
}

interface CurrentTooltipOriginProps {
uid: symbol;
location: number | null;
element: HTMLElement;
notifyDetached: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ import { RequestHint } from './request-hint';
appTooltip [tooltipContext]="tooltipContext" (tooltipOriginChange)="setTooltipOrigin($index, item.id, $event, true)">
<!--suppress AngularNgOptimizedImage -->
<img class="item"
src="/assets/images/items.webp"
[class.rats]="item.id === 'rats'"
[src]="item.id === 'rats' ? '/assets/images/players/pack_rat.webp' : '/assets/images/items.webp'"
[alt]="item.name"
[style.object-position]="-item.offsetX() + 'px ' + -item.offsetY + 'px'">
[style.--ap-object-position]="-item.offsetX() + 'px ' + -item.offsetY + 'px'">
@if (item.id === 'rats') {
<span class="rat-count-corner">{{ ratCount() }}</span>
}
</div>
}
</div>
Expand All @@ -39,26 +43,60 @@ import { RequestHint } from './request-hint';
[cdkConnectedOverlayOpen]="tooltipOrigin() !== null"
[cdkConnectedOverlayUsePopover]="'inline'"
(detach)="setTooltipOrigin(0, 0, null, false)">
@let item = items()[tooltipOrigin()!.index];
<div class="tooltip">
<h1 class="box header">{{item.name}}</h1>
<div class="box flavor-text" [hidden]="!item.flavorText">“{{item.flavorText}}”</div>
@if (hintForTooltipItem(); as hint) {
<div class="box hint">
At
<span class="location-text">{{ hint.item.locationName }}</span>
in
<span class="player-text" [class.own-player-text]="isSelf(hint.item.sender)">{{ hint.item.sender }}</span>'s world (<span
class="hint-text"
[class.unspecified]="hint.status === HINT_STATUS_UNSPECIFIED"
[class.no-priority]="hint.status === HINT_STATUS_NO_PRIORITY"
[class.avoid]="hint.status === HINT_STATUS_AVOID"
[class.priority]="hint.status === HINT_STATUS_PRIORITY"
[class.found]="hint.status === HINT_STATUS_FOUND"
>{{ hintStatusText() }}</span>).
</div>
}
</div>
@if (tooltipOrigin(); as origin) {
@let item = items()[origin.index];
<div
class="tooltip"
(mouseenter)="tooltipContext.notifyMouseEnterTooltip(origin.uid)"
(mouseleave)="tooltipContext.notifyMouseLeaveTooltip(origin.uid)"
>
<h1 class="box header">{{item.name}}</h1>
<div class="box flavor-text" [hidden]="!item.flavorText">“{{item.flavorText}}”</div>
@if (item.id === 'rats') {
@if (ratHints().size > 0) {
<div class="box hint rat-hint">
Hints:
<ul>
@for (hint of ratHints(); track $index) {
<li>
<span class="item-text" [class.progression]="hint.item.progression" [class.filler]="hint.item.filler"
[class.useful]="hint.item.useful" [class.trap]="hint.item.trap">
{{ hint.item.name }}
</span>
at
<span class="location-text">{{ hint.item.locationName }}</span>
in
<span class="player-text" [class.own-player-text]="isSelf(hint.item.sender)">{{ hint.item.sender }}</span>'s world (<span
class="hint-text"
[class.unspecified]="hint.status === HINT_STATUS_UNSPECIFIED"
[class.no-priority]="hint.status === HINT_STATUS_NO_PRIORITY"
[class.avoid]="hint.status === HINT_STATUS_AVOID"
[class.priority]="hint.status === HINT_STATUS_PRIORITY"
[class.found]="hint.status === HINT_STATUS_FOUND"
>{{ statusText(hint) }}</span>).
</li>
}
</ul>
</div>
}
}
@else if (hintForTooltipItem(); as hint) {
<div class="box hint">
At
<span class="location-text">{{ hint.item.locationName }}</span>
in
<span class="player-text" [class.own-player-text]="isSelf(hint.item.sender)">{{ hint.item.sender }}</span>'s world (<span
class="hint-text"
[class.unspecified]="hint.status === HINT_STATUS_UNSPECIFIED"
[class.no-priority]="hint.status === HINT_STATUS_NO_PRIORITY"
[class.avoid]="hint.status === HINT_STATUS_AVOID"
[class.priority]="hint.status === HINT_STATUS_PRIORITY"
[class.found]="hint.status === HINT_STATUS_FOUND"
>{{ statusText(hint) }}</span>).
</div>
}
</div>
}
</ng-template>
`,
styles: `
Expand All @@ -71,6 +109,7 @@ import { RequestHint } from './request-hint';
.item-container {
margin: 5px;
padding: 5px;
display: inline-grid;

border: 2px solid black;
border-radius: 8px;
Expand All @@ -81,9 +120,21 @@ import { RequestHint } from './request-hint';
}

.item {
object-fit: none;
grid-row: 1;
grid-column: 1;
width: 64px;
height: 64px;
&:not(.rats) {
object-fit: none;
object-position: var(--ap-object-position);
}
}

.rat-count-corner {
grid-row: 1;
grid-column: 1;
align-self: end;
justify-self: end;
}

.box {
Expand All @@ -98,6 +149,7 @@ import { RequestHint } from './request-hint';
gap: 10px;
padding: 4px;
background-color: theme.$region-color;
pointer-events: initial;

.header {
margin: 0;
Expand All @@ -113,6 +165,10 @@ import { RequestHint } from './request-hint';
.hint {
font-size: 8pt;
}

.rat-hint {
white-space: nowrap;
}
}
`,
})
Expand All @@ -121,17 +177,19 @@ export class ProgressionItemStatus {
readonly #dialog = inject(Dialog);
readonly #gameStore = inject(GameStore);
readonly #performanceInsensitiveAnimatableState = inject(PerformanceInsensitiveAnimatableState);
protected readonly ratCount = this.#performanceInsensitiveAnimatableState.ratCount.asReadonly();
protected readonly items: Signal<readonly ItemModel[]>;
readonly #tooltipOrigin = signal<CurrentTooltipOriginProps | null>(null);
protected readonly tooltipOrigin = this.#tooltipOrigin.asReadonly();
// all tooltips here should use the same context, so that the user can quickly switch between them
// without having to sit through the whole delay.
protected readonly tooltipContext = createEmptyTooltipContext();
readonly #hintedItems = computed(() => this.#gameStore.game()?.hintedItems() ?? List(Repeat(null, BAKED_DEFINITIONS_FULL.allItems.length)));
protected readonly ratHints = computed(() => this.#gameStore.game()?.ratHints() ?? List<Hint>());
protected readonly hintForTooltipItem = computed(() => {
const item = this.tooltipOrigin()?.item ?? null;
const hintedItems = [...this.#hintedItems()];
return item === null
return item === null || item === 'rats'
? null
: hintedItems[item] ?? null;
});
Expand All @@ -141,27 +199,24 @@ export class ProgressionItemStatus {
protected readonly HINT_STATUS_AVOID: Hint['status'] = 20;
protected readonly HINT_STATUS_PRIORITY: Hint['status'] = 30;
protected readonly HINT_STATUS_FOUND: Hint['status'] = 40;
protected readonly hintStatusText = computed(() => {
// https://github.com/ArchipelagoMW/Archipelago/blob/0.6.5/kvui.py#L1195-L1201
switch (this.hintForTooltipItem()?.status) {
case this.HINT_STATUS_FOUND: return 'Found';
case this.HINT_STATUS_UNSPECIFIED: return 'Unspecified';
case this.HINT_STATUS_NO_PRIORITY: return 'No Priority';
case this.HINT_STATUS_AVOID: return 'Avoid';
case this.HINT_STATUS_PRIORITY: return 'Priority';
default: return null;
}
});

constructor() {
this.items = computed(() => {
const victoryLocationYamlKey = this.#gameStore.victoryLocationYamlKey();
const lactoseIntolerant = this.#gameStore.lactoseIntolerant();
return PROGRESSION_ITEMS_BY_VICTORY_LOCATION[victoryLocationYamlKey].map((itemYamlKey, index) => {
return [{
index: 0,
id: 'rats',
collected: computed(() => this.ratCount() > 0),
name: 'Rats',
flavorText: null,
offsetX: computed(() => 0),
offsetY: BAKED_DEFINITIONS_FULL.progressionItemsByYamlKey.size * 65,
}, ...PROGRESSION_ITEMS_BY_VICTORY_LOCATION[victoryLocationYamlKey].map((itemYamlKey, index) => {
const item = BAKED_DEFINITIONS_FULL.progressionItemsByYamlKey.get(itemYamlKey) ?? -1;
const collected = computed(() => this.#performanceInsensitiveAnimatableState.receivedItemCountLookup()[item] > 0);
return {
index,
index: index + 1,
id: item,
name: lactoseIntolerant
? BAKED_DEFINITIONS_FULL.allItems[item].lactoseIntolerantName
Expand All @@ -171,11 +226,11 @@ export class ProgressionItemStatus {
offsetX: computed(() => collected() ? 0 : 65),
offsetY: index * 65,
};
});
})];
});
}

protected setTooltipOrigin(index: number, item: number, props: TooltipOriginProps | null, fromDirective: boolean) {
protected setTooltipOrigin(index: number, item: number | 'rats', props: TooltipOriginProps | null, fromDirective: boolean) {
this.#tooltipOrigin.update((prev) => {
if (prev !== null && !fromDirective) {
prev.notifyDetached();
Expand Down Expand Up @@ -220,11 +275,23 @@ export class ProgressionItemStatus {
const { team, slot } = game.client.players.self;
return player.team === team && player.slot === slot;
}

protected statusText(hint: Hint) {
// https://github.com/ArchipelagoMW/Archipelago/blob/0.6.5/kvui.py#L1195-L1201
switch (hint.status) {
case this.HINT_STATUS_FOUND: return 'Found';
case this.HINT_STATUS_UNSPECIFIED: return 'Unspecified';
case this.HINT_STATUS_NO_PRIORITY: return 'No Priority';
case this.HINT_STATUS_AVOID: return 'Avoid';
case this.HINT_STATUS_PRIORITY: return 'Priority';
default: return null;
}
}
}

interface ItemModel {
index: number;
id: number;
id: number | 'rats';
name: string;
flavorText: string | null;
collected: Signal<boolean>;
Expand All @@ -233,8 +300,9 @@ interface ItemModel {
}

interface CurrentTooltipOriginProps {
uid: symbol;
index: number;
item: number;
item: number | 'rats';
element: HTMLElement;
notifyDetached: () => void;
}
Loading