Merge branch 'master' into slice/0.9-first-quest-and-bag-tutorial
master turned the gate watch into painted scenery with its own portrait and result image; Slice 0.9 turns that same hotspot into a real NPC. Kept the NPC doorway and dropped the result text and image with it -- a hotspot carries one or the other, never both. The warden now uses master's `graufurt-gate-watch.png` instead of the raw copy this branch added: same figure, already optimized. Borin keeps master's optimized portrait for the same reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -278,13 +278,13 @@ function entry(
|
||||
* rather than seeding an item that does nothing.
|
||||
*/
|
||||
export const LOOT_TABLE_ENTRIES: SeedLootTableEntry[] = [
|
||||
entry(ASH_RAT_LOOT_TABLE_ID, 'ash-pelt', 1, '1.0000'),
|
||||
entry(ASH_RAT_LOOT_TABLE_ID, 'ash-pelt', 1, '0.6000'),
|
||||
entry(ASH_RAT_LOOT_TABLE_ID, 'worn-short-sword', 2, '0.0800'),
|
||||
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-blade', 1, '0.1800'),
|
||||
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-hood', 2, '0.1200'),
|
||||
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'raider-gloves', 3, '0.0800'),
|
||||
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-insignia', 4, '1.0000'),
|
||||
entry(WILD_ROAD_DOG_LOOT_TABLE_ID, 'tough-hide', 1, '1.0000'),
|
||||
entry(ROAD_BANDIT_LOOT_TABLE_ID, 'bandit-insignia', 4, '0.6000'),
|
||||
entry(WILD_ROAD_DOG_LOOT_TABLE_ID, 'tough-hide', 1, '0.6000'),
|
||||
entry(WILD_ROAD_DOG_LOOT_TABLE_ID, 'ash-boots', 2, '0.0800'),
|
||||
entry(CHARRED_LOOTER_LOOT_TABLE_ID, 'charred-raider-insignia', 1, '1.0000'),
|
||||
entry(CHARRED_LOOTER_LOOT_TABLE_ID, 'reinforced-leather-jacket', 2, '0.1000'),
|
||||
|
||||
@@ -158,6 +158,7 @@ export const SOUTH_GATE_LOCAL_CONTENT: LocalLocationContent = {
|
||||
resultTitle: 'Notice Board',
|
||||
resultText:
|
||||
'Weathered notices flutter in the wind. A fresh one warns of raiders on the Burned Road and promises silver for every bandit killed.',
|
||||
resultImg: '/images/environment/notice-board.png',
|
||||
},
|
||||
// Was authored scenery with a line of result text until Slice 0.9 made the
|
||||
// warden a real NPC with a quest to give. A hotspot carries result text or
|
||||
|
||||
@@ -144,7 +144,9 @@ export const NPC_DEFINITIONS: SeedNpcDefinition[] = [
|
||||
'They have watched the road long enough to stop expecting good news from it. Whatever comes back through the gate, they want it counted.',
|
||||
locationKey: 'south-gate',
|
||||
factionKey: 'border-guard',
|
||||
portraitPath: '/images/npcs/south-gate-warden.png',
|
||||
// The gate watch's own portrait, painted before this slice made them a
|
||||
// person. Reused rather than duplicated: it is the same figure.
|
||||
portraitPath: '/images/npcs/graufurt-gate-watch.png',
|
||||
artworkPath: null,
|
||||
capabilities: [
|
||||
NpcCapability.DIALOGUE,
|
||||
|
||||
@@ -63,6 +63,7 @@ const SOUTH_GATE_POIS: LocationPointOfInterestContent[] = [
|
||||
enabled: true,
|
||||
resultTitle: 'Gate Watch',
|
||||
resultText: 'Only heard at the South Gate.',
|
||||
resultImg: '/images/npcs/graufurt-gate-watch.png',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -114,6 +115,19 @@ describe('WorldService.runLocalInteraction', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the authored image when the interaction carries one', async () => {
|
||||
const service = createService(location(SOUTH_GATE_ID, SOUTH_GATE_POIS));
|
||||
|
||||
await expect(
|
||||
service.runLocalInteraction(CHARACTER_ID, 'gate-watch'),
|
||||
).resolves.toEqual({
|
||||
interactionKey: 'gate-watch',
|
||||
title: 'Gate Watch',
|
||||
text: 'Only heard at the South Gate.',
|
||||
img: '/images/npcs/graufurt-gate-watch.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('settles travel before resolving which location the character stands at', async () => {
|
||||
const completeTravelIfDue = jest.fn().mockResolvedValue({ status: 'IDLE' });
|
||||
const service = createService(
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface LocationPointOfInterestContent {
|
||||
enabled: boolean;
|
||||
resultTitle?: string;
|
||||
resultText?: string;
|
||||
/** Authored portrait shown alongside the result text, e.g. an NPC's face. */
|
||||
resultImg?: string;
|
||||
/**
|
||||
* Names a real NPC (NPC spec §24), which turns this hotspot into a doorway
|
||||
* to that person's screen instead of an authored one-shot text reveal. A
|
||||
@@ -117,6 +119,7 @@ export interface LocationInteractionResultDto {
|
||||
interactionKey: string;
|
||||
title: string;
|
||||
text: string;
|
||||
img?: string;
|
||||
}
|
||||
|
||||
/** Strips server-only result text before a POI is sent to the client. */
|
||||
|
||||
@@ -156,6 +156,7 @@ export class WorldService {
|
||||
interactionKey: poi.key,
|
||||
title: poi.resultTitle ?? poi.title,
|
||||
text: poi.resultText,
|
||||
...(poi.resultImg === undefined ? {} : { img: poi.resultImg }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user