371 lines
13 KiB
HTML
371 lines
13 KiB
HTML
@if (store.loading()) {
|
||
<p class="merchant__status">Approaching…</p>
|
||
} @else if (store.error()) {
|
||
<div class="merchant__status merchant__status--error" role="alert">
|
||
<p>{{ store.error() }}</p>
|
||
<button type="button" class="merchant__button" (click)="leave()">
|
||
Back to the gate
|
||
</button>
|
||
</div>
|
||
} @else if (store.interaction(); as interaction) {
|
||
<article class="merchant">
|
||
<header class="merchant__identity">
|
||
<img
|
||
class="merchant__portrait"
|
||
[src]="interaction.npc.portraitPath"
|
||
[alt]="interaction.npc.name"
|
||
(error)="($any($event.target).style.visibility = 'hidden')"
|
||
/>
|
||
<div class="merchant__naming">
|
||
<h1 class="merchant__name">{{ interaction.npc.name }}</h1>
|
||
@if (interaction.npc.title) {
|
||
<p class="merchant__title">{{ interaction.npc.title }}</p>
|
||
}
|
||
@if (interaction.npc.description) {
|
||
<p class="merchant__description">{{ interaction.npc.description }}</p>
|
||
}
|
||
</div>
|
||
</header>
|
||
|
||
@if (interaction.dialogue) {
|
||
<blockquote class="merchant__dialogue" data-dialogue>
|
||
{{ interaction.dialogue.text }}
|
||
</blockquote>
|
||
}
|
||
|
||
<nav class="merchant__actions" aria-label="Interactions">
|
||
@for (action of interaction.availableActions; track action.type) {
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
[class.merchant__button--active]="
|
||
(action.type === 'OPEN_EXCHANGE' && isPanel('EXCHANGE')) ||
|
||
(action.type === 'OPEN_SHOP' && isPanel('SHOP')) ||
|
||
(action.type === 'VIEW_QUESTS' && isPanel('QUESTS')) ||
|
||
(action.type === 'TALK' && isPanel('DIALOGUE'))
|
||
"
|
||
[attr.data-action]="action.type"
|
||
(click)="activate(action.type, action.key)"
|
||
>
|
||
{{ action.label }}
|
||
</button>
|
||
}
|
||
<button type="button" class="merchant__button" (click)="leave()">
|
||
Leave
|
||
</button>
|
||
</nav>
|
||
|
||
@if (store.actionError()) {
|
||
<p class="merchant__error" role="alert">{{ store.actionError() }}</p>
|
||
}
|
||
|
||
@if (store.questLine(); as line) {
|
||
<blockquote class="merchant__dialogue" data-quest-line aria-live="polite">
|
||
{{ line }}
|
||
</blockquote>
|
||
}
|
||
|
||
@if (store.grantedBag(); as bag) {
|
||
<section class="bag-granted" data-granted-bag aria-live="polite">
|
||
<h2 class="bag-granted__heading">New Loot Bag</h2>
|
||
<p class="bag-granted__name">{{ bag.name }}</p>
|
||
<p class="bag-granted__capacity">{{ capacityLabel(bag) }}</p>
|
||
<button
|
||
type="button"
|
||
class="merchant__link"
|
||
(click)="store.dismissGrantedBag()"
|
||
>
|
||
Dismiss
|
||
</button>
|
||
</section>
|
||
}
|
||
|
||
@if (isPanel('QUESTS')) {
|
||
<section class="merchant__panel" aria-label="Quests">
|
||
<h2 class="merchant__panel-title">Matters at Hand</h2>
|
||
|
||
@for (quest of store.quests(); track quest.key) {
|
||
<article class="npc-quest" [attr.data-quest]="quest.key">
|
||
<h3 class="npc-quest__title">{{ quest.title }}</h3>
|
||
<p class="npc-quest__description">{{ quest.description }}</p>
|
||
|
||
@if (currentObjective(quest); as objective) {
|
||
<app-quest-objective-line
|
||
[objective]="objective"
|
||
[hint]="quest.hint"
|
||
/>
|
||
}
|
||
|
||
@if (quest.status === 'AVAILABLE') {
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
data-quest-accept
|
||
[disabled]="store.pending() !== null"
|
||
(click)="store.acceptQuest(quest.key)"
|
||
>
|
||
Accept
|
||
</button>
|
||
} @else if (quest.status === 'ACTIVE' && isCurrentStepHere(quest)) {
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
data-quest-advance
|
||
[disabled]="store.pending() !== null"
|
||
(click)="store.advanceQuest(quest.key)"
|
||
>
|
||
Continue
|
||
</button>
|
||
}
|
||
</article>
|
||
} @empty {
|
||
<p class="merchant__empty">There is nothing to discuss.</p>
|
||
}
|
||
</section>
|
||
}
|
||
|
||
@if (isPanel('EXCHANGE') && store.exchange(); as exchange) {
|
||
<section class="merchant__panel" aria-label="Trade in goods">
|
||
<h2 class="merchant__panel-title">{{ exchange.profileName }}</h2>
|
||
|
||
<app-loot-capacity-strip [capacities]="exchange.capacities" />
|
||
|
||
@if (exchange.offers.length === 0) {
|
||
<p class="merchant__empty">There is nothing here he will take.</p>
|
||
} @else {
|
||
<ul class="trade-list">
|
||
@for (offer of exchange.offers; track offer.itemKey) {
|
||
<li
|
||
class="trade-row"
|
||
[class.trade-row--empty]="offer.quantityCarried === 0"
|
||
[attr.data-trade-item]="offer.itemKey"
|
||
>
|
||
<img
|
||
class="trade-row__icon"
|
||
[src]="offer.iconPath"
|
||
[alt]=""
|
||
aria-hidden="true"
|
||
/>
|
||
<div class="trade-row__naming">
|
||
<span class="trade-row__name">{{ offer.itemName }}</span>
|
||
<span class="trade-row__carried" data-carried>
|
||
Carrying {{ offer.quantityCarried }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="trade-row__value" data-value>
|
||
<span class="trade-row__silver"
|
||
>{{ offer.silverPerStep }} Silver</span
|
||
>
|
||
<span class="trade-row__rep"
|
||
>+{{ offer.reputationPerStep }}
|
||
{{ offer.factionName }}</span
|
||
>
|
||
</div>
|
||
|
||
<div class="trade-row__picker">
|
||
<button
|
||
type="button"
|
||
class="trade-row__step"
|
||
aria-label="Trade fewer"
|
||
[disabled]="
|
||
offer.quantityCarried === 0 || store.pending() !== null
|
||
"
|
||
(click)="step(offer.itemKey, -1, offer.inputQuantity)"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
class="trade-row__quantity"
|
||
type="number"
|
||
min="0"
|
||
[max]="offer.quantityCarried"
|
||
[step]="offer.inputQuantity"
|
||
[value]="quantityFor(offer.itemKey)"
|
||
[disabled]="
|
||
offer.quantityCarried === 0 || store.pending() !== null
|
||
"
|
||
[attr.aria-label]="'Quantity of ' + offer.itemName"
|
||
(input)="
|
||
onQuantityInput(offer.itemKey, $any($event.target).value)
|
||
"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="trade-row__step"
|
||
aria-label="Trade more"
|
||
[disabled]="
|
||
offer.quantityCarried === 0 || store.pending() !== null
|
||
"
|
||
(click)="step(offer.itemKey, 1, offer.inputQuantity)"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</li>
|
||
}
|
||
</ul>
|
||
|
||
<footer class="trade-footer">
|
||
<p class="trade-footer__preview" data-preview>
|
||
Selected:
|
||
<strong>{{ store.preview().silver }} Silver</strong>
|
||
and
|
||
<strong>{{ store.preview().reputation }} Reputation</strong>
|
||
</p>
|
||
<div class="trade-footer__buttons">
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
[disabled]="store.pending() !== null"
|
||
(click)="store.selectAll()"
|
||
>
|
||
Select All
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="merchant__button merchant__button--primary"
|
||
data-trade-confirm
|
||
[disabled]="!store.hasSelection() || store.pending() !== null"
|
||
(click)="store.tradeSelected()"
|
||
>
|
||
{{ store.pending() === 'trade' ? 'Handing over…' : 'Trade Selected' }}
|
||
</button>
|
||
</div>
|
||
</footer>
|
||
}
|
||
|
||
@if (store.lastTrade(); as trade) {
|
||
<section class="trade-summary" data-trade-summary aria-live="polite">
|
||
<h3 class="trade-summary__title">Trade Complete</h3>
|
||
<ul class="trade-summary__consumed">
|
||
@for (entry of trade.consumed; track entry.itemKey) {
|
||
<li>{{ entry.quantity }} × {{ entry.itemName }} handed in</li>
|
||
}
|
||
</ul>
|
||
<ul class="trade-summary__rewards">
|
||
<li>+{{ trade.rewards.silver }} Silver</li>
|
||
<li>+{{ trade.rewards.regionalReputation }} Border Watch Reputation</li>
|
||
@if (trade.rewards.worldRenown > 0) {
|
||
<li class="trade-summary__renown" data-renown>
|
||
+{{ trade.rewards.worldRenown }} World Renown
|
||
</li>
|
||
}
|
||
</ul>
|
||
@if (trade.reputationRankChanged) {
|
||
<p class="trade-summary__rank" data-rank-changed>
|
||
The Border Watch now regards you differently.
|
||
</p>
|
||
}
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
(click)="store.dismissTradeSummary()"
|
||
>
|
||
Done
|
||
</button>
|
||
</section>
|
||
}
|
||
</section>
|
||
}
|
||
|
||
@if (isPanel('SHOP') && store.shop(); as shop) {
|
||
<section class="merchant__panel" aria-label="Wares for sale">
|
||
<h2 class="merchant__panel-title">{{ shop.shopName }}</h2>
|
||
<p class="merchant__purse" data-purse>{{ shop.silver }} Silver</p>
|
||
|
||
@if (store.newlyUnlocked().length > 0) {
|
||
<p class="shop-unlocked" data-unlocked aria-live="polite">
|
||
@for (name of store.newlyUnlocked(); track name) {
|
||
<span>New merchant offer unlocked: {{ name }}</span>
|
||
}
|
||
<button
|
||
type="button"
|
||
class="merchant__link"
|
||
(click)="store.dismissUnlocked()"
|
||
>
|
||
Dismiss
|
||
</button>
|
||
</p>
|
||
}
|
||
|
||
<ul class="shop-list">
|
||
@for (offer of shop.offers; track offer.itemKey) {
|
||
<li class="shop-row" [attr.data-shop-item]="offer.itemKey">
|
||
<img
|
||
class="shop-row__icon"
|
||
[src]="offer.iconPath"
|
||
[alt]=""
|
||
aria-hidden="true"
|
||
/>
|
||
<div class="shop-row__naming">
|
||
<span class="shop-row__name">{{ offer.itemName }}</span>
|
||
@if (offer.itemDescription) {
|
||
<span class="shop-row__description">{{
|
||
offer.itemDescription
|
||
}}</span>
|
||
}
|
||
@if (offer.effectSummary) {
|
||
<span class="shop-row__effect" data-effect>{{
|
||
offer.effectSummary
|
||
}}</span>
|
||
}
|
||
@for (
|
||
requirement of offer.requirements;
|
||
track requirement.label
|
||
) {
|
||
<span
|
||
class="shop-row__requirement"
|
||
[class.shop-row__requirement--met]="requirement.met"
|
||
data-requirement
|
||
>
|
||
{{ requirement.label }}
|
||
@if (!requirement.met && requirement.current !== null) {
|
||
<span class="shop-row__current"
|
||
>· Current: {{ requirement.current }}</span
|
||
>
|
||
}
|
||
</span>
|
||
}
|
||
</div>
|
||
<span class="shop-row__price">{{ offer.price }} Silver</span>
|
||
<button
|
||
type="button"
|
||
class="merchant__button"
|
||
[disabled]="
|
||
!offer.unlocked ||
|
||
!offer.affordable ||
|
||
store.pending() !== null
|
||
"
|
||
(click)="store.buy(offer.itemKey)"
|
||
>
|
||
@if (!offer.unlocked) {
|
||
Locked
|
||
} @else if (!offer.affordable) {
|
||
Too costly
|
||
} @else {
|
||
Buy
|
||
}
|
||
</button>
|
||
</li>
|
||
}
|
||
</ul>
|
||
|
||
@if (store.lastPurchase(); as purchase) {
|
||
<p class="shop-receipt" data-purchase aria-live="polite">
|
||
Bought {{ purchase.quantity }} × {{ purchase.itemName }} for
|
||
{{ purchase.silverSpent }} Silver.
|
||
<button
|
||
type="button"
|
||
class="merchant__link"
|
||
(click)="store.dismissPurchase()"
|
||
>
|
||
Dismiss
|
||
</button>
|
||
</p>
|
||
}
|
||
</section>
|
||
}
|
||
</article>
|
||
}
|