openvibers/openvibe.games Commits
Salvage Rush runs no NPCs: a world definition can name its own (npcSpawns, [] for none), and the arena did get the procedural world's townsfolk, warden and Rustjaw thugs (#126)
Changes
@@@ -8,6 +8,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).### Fixed++- **Townsfolk in the Salvage Rush arena.** The 90-second scrap round ran the content's seven NPC spawns, laid out for++ the procedural world: townsfolk and a warden wandered inside the arena and Rustjaw thugs stood just past its edge.++ A world definition can now name its own NPCs (`npcSpawns`; `[]` is none), and the arena has none. A map's own NPCs++ still win.- **Touch play's HUD was buried under the controls.** On a phone, the stick (bottom left) and the button grid (bottomright) covered the first-steps panel, and the HP, food, water and stamina bars sat on the hotbar. While the touchcontrols show:@@@ -124,6 +124,7 @@ export interface NpcSpawn {export function npcSpawnsFor(world?: WorldDef): NpcSpawn[] {const mapped = getMapOverride(world)?.npcs ?? []if (mapped.length) return mapped.map((n) => ({ archetype: n.npc, pos: n.pos }))++ if (world?.npcSpawns) return world.npcSpawnsreturn NPC_SPAWNS}@@@ -12,6 +12,8 @@ import {} from './mapFileV2.js'import { NPC_SPAWNS, npcSpawnsFor } from './defs/npcs.js'import { setMapOverride } from './terrain.js'++import { SALVAGE_RUSH_PACK } from './packs/salvageRush.js'++import type { WorldDef } from './schema/world.js'const SUB = 8@@@ -473,6 +475,19 @@ describe('map-authored NPCs', () => {expect(npcSpawnsFor()).toBe(NPC_SPAWNS)})++ it("a world with its own spawns (none, in Salvage Rush's arena) is not given the content's", () => {++ const arena = SALVAGE_RUSH_PACK.map as WorldDef++ expect(arena.npcSpawns).toEqual([])++ expect(npcSpawnsFor(arena)).toEqual([])++ // A map that places NPCs still wins.++ setMapOverride({ terrains: [], npcs: [{ id: 'n', npc: 'dust_hare', pos: [1, 0, 1] }] })++ try {++ expect(npcSpawnsFor(arena)).toEqual([{ archetype: 'dust_hare', pos: [1, 0, 1] }])++ } finally {++ setMapOverride(null)++ }++ })++it('an NPC id may not repeat another object id', () => {const map: MapFileV2 = {...emptyMapV2(),@@@ -17,6 +17,9 @@ export const SALVAGE_RUSH_PACK: ContentPackV2 = {],resourceNodes: [],initialProps: [],++ // A 90-second scrap round: no NPCs. The content's spawns put townsfolk and a warden inside the arena and Rustjaw++ // thugs just past its edge.++ npcSpawns: [],zones: [{id: 'arena',@@@ -155,6 +155,11 @@ export const WorldDefSchema = z.object({resourceNodes: z.array(ResourceNodeSpawnSchema),initialProps: z.array(PropSpawnSchema),zones: z.array(ZoneDefSchema),++ /**++ * The world's own NPCs, when it has an opinion: `[]` for none (a timed arena), else archetypes and ground++ * positions. Absent, it gets the content's NPC_SPAWNS. A map's own NPCs win over either (npcSpawnsFor).++ */++ npcSpawns: z.array(z.object({ archetype: z.string(), pos: vec3 })).optional(),})export type WorldDef = z.infer<typeof WorldDefSchema>
@@ -8,6 +8,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -8,6 +8,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixed
### Fixed
+- **Townsfolk in the Salvage Rush arena.** The 90-second scrap round ran the content's seven NPC spawns, laid out for
+ the procedural world: townsfolk and a warden wandered inside the arena and Rustjaw thugs stood just past its edge.
+ A world definition can now name its own NPCs (`npcSpawns`; `[]` is none), and the arena has none. A map's own NPCs
+ still win.
- **Touch play's HUD was buried under the controls.** On a phone, the stick (bottom left) and the button grid (bottom
- **Touch play's HUD was buried under the controls.** On a phone, the stick (bottom left) and the button grid (bottom
right) covered the first-steps panel, and the HP, food, water and stamina bars sat on the hotbar. While the touch
right) covered the first-steps panel, and the HP, food, water and stamina bars sat on the hotbar. While the touch
controls show:
controls show:
@@ -124,6 +124,7 @@ export interface NpcSpawn {
@@ -124,6 +124,7 @@ export interface NpcSpawn {
export function npcSpawnsFor(world?: WorldDef): NpcSpawn[] {
export function npcSpawnsFor(world?: WorldDef): NpcSpawn[] {
const mapped = getMapOverride(world)?.npcs ?? []
const mapped = getMapOverride(world)?.npcs ?? []
if (mapped.length) return mapped.map((n) => ({ archetype: n.npc, pos: n.pos }))
if (mapped.length) return mapped.map((n) => ({ archetype: n.npc, pos: n.pos }))
+ if (world?.npcSpawns) return world.npcSpawns
return NPC_SPAWNS
return NPC_SPAWNS
}
}
@@ -12,6 +12,8 @@ import {
@@ -12,6 +12,8 @@ import {
} from './mapFileV2.js'
} from './mapFileV2.js'
import { NPC_SPAWNS, npcSpawnsFor } from './defs/npcs.js'
import { NPC_SPAWNS, npcSpawnsFor } from './defs/npcs.js'
import { setMapOverride } from './terrain.js'
import { setMapOverride } from './terrain.js'
+import { SALVAGE_RUSH_PACK } from './packs/salvageRush.js'
+import type { WorldDef } from './schema/world.js'
const SUB = 8
const SUB = 8
@@ -473,6 +475,19 @@ describe('map-authored NPCs', () => {
@@ -473,6 +475,19 @@ describe('map-authored NPCs', () => {
expect(npcSpawnsFor()).toBe(NPC_SPAWNS)
expect(npcSpawnsFor()).toBe(NPC_SPAWNS)
})
})
+ it("a world with its own spawns (none, in Salvage Rush's arena) is not given the content's", () => {
+ const arena = SALVAGE_RUSH_PACK.map as WorldDef
+ expect(arena.npcSpawns).toEqual([])
+ expect(npcSpawnsFor(arena)).toEqual([])
+ // A map that places NPCs still wins.
+ setMapOverride({ terrains: [], npcs: [{ id: 'n', npc: 'dust_hare', pos: [1, 0, 1] }] })
+ try {
+ expect(npcSpawnsFor(arena)).toEqual([{ archetype: 'dust_hare', pos: [1, 0, 1] }])
+ } finally {
+ setMapOverride(null)
+ }
+ })
+
it('an NPC id may not repeat another object id', () => {
it('an NPC id may not repeat another object id', () => {
const map: MapFileV2 = {
const map: MapFileV2 = {
...emptyMapV2(),
...emptyMapV2(),
@@ -17,6 +17,9 @@ export const SALVAGE_RUSH_PACK: ContentPackV2 = {
@@ -17,6 +17,9 @@ export const SALVAGE_RUSH_PACK: ContentPackV2 = {
],
],
resourceNodes: [],
resourceNodes: [],
initialProps: [],
initialProps: [],
+ // A 90-second scrap round: no NPCs. The content's spawns put townsfolk and a warden inside the arena and Rustjaw
+ // thugs just past its edge.
+ npcSpawns: [],
zones: [
zones: [
{
{
id: 'arena',
id: 'arena',
@@ -155,6 +155,11 @@ export const WorldDefSchema = z.object({
@@ -155,6 +155,11 @@ export const WorldDefSchema = z.object({
resourceNodes: z.array(ResourceNodeSpawnSchema),
resourceNodes: z.array(ResourceNodeSpawnSchema),
initialProps: z.array(PropSpawnSchema),
initialProps: z.array(PropSpawnSchema),
zones: z.array(ZoneDefSchema),
zones: z.array(ZoneDefSchema),
+ /**
+ * The world's own NPCs, when it has an opinion: `[]` for none (a timed arena), else archetypes and ground
+ * positions. Absent, it gets the content's NPC_SPAWNS. A map's own NPCs win over either (npcSpawnsFor).
+ */
+ npcSpawns: z.array(z.object({ archetype: z.string(), pos: vec3 })).optional(),
})
})
export type WorldDef = z.infer<typeof WorldDefSchema>
export type WorldDef = z.infer<typeof WorldDefSchema>