openvibers/openvibe.games Commits
First steps: a Craft a Stone Pickaxe step after the axe, and the gather hint asks for five stones (nothing said to make a pickaxe before the quarry, and one stone finished gathering) (#128)
Changes
@@@ -8,6 +8,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).### Fixed++- **No step said to make a pickaxe.** Scraplandia's first steps went from the Stone Axe to "Mine at the quarry", whose++ hint only said a Stone Pickaxe breaks the deposits, and the gather step finished after a single stone. A new++ "Craft a Stone Pickaxe" step follows the axe (2 Wood Planks and 3 Stones), and the gather hint says a log and five++ stones make the first two tools: one pile and one log are exactly both recipes.- **Townsfolk in the Salvage Rush arena.** The 90-second scrap round ran the content's seven NPC spawns, laid out forthe 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@@@ -44,6 +44,9 @@ describe('first steps', () => {expect(current()).toContain('Craft a Stone Axe')items = { stone_axe: 1 }fs.update()++ expect(current()).toContain('Craft a Stone Pickaxe')++ items = { stone_axe: 1, stone_pickaxe: 1 }++ fs.update()expect(current()).toContain('Chop an oak')levels = { woodcutting: 2 }fs.update()@@@ -52,7 +55,7 @@ describe('first steps', () => {levels = {}fs.update()expect(current()).toContain('Mine at the quarry')-- expect(root.querySelector('.first-steps-head')?.textContent).toContain('3/6')++ expect(root.querySelector('.first-steps-head')?.textContent).toContain('4/7')})it('finishes with a haul and then hides', () => {@@@ -83,7 +86,7 @@ describe('first steps', () => {it('progress() unions what was done with what is done now', () => {const view = { count: () => 0, level: (s: string) => (s === 'mining' ? 2 : 1), hauled: false }-- expect(progress(view, { done: ['gather'] })).toEqual(['gather', 'mine'])++ expect(progress(view, { done: ['gather'] })).toEqual(['gather', 'pickaxe', 'mine'])})})@@@ -110,6 +113,22 @@ describe('the waypoint hooks', () => {expect(steps.birdAt).toBeNull()})++ it('makes the pickaxe a step of its own, right after the axe, from the same gate piles', () => {++ const ids = FIRST_STEPS.map((s) => s.id)++ expect(ids.indexOf('pickaxe')).toBe(ids.indexOf('axe') + 1)++ const pickaxe = FIRST_STEPS.find((s) => s.id === 'pickaxe')!++ expect(pickaxe.hint).toMatch(/2 Wood Planks and 3 Stones/)++ expect(++ pickaxe.done({++ count: (i) => (i === 'stone_pickaxe' ? 1 : 0),++ level: () => 1,++ hauled: false,++ }),++ ).toBe(true)++ expect(pickaxe.done({ count: () => 0, level: () => 1, hauled: false })).toBe(false)++ expect(FIRST_STEPS[0]?.hint).toMatch(/five stones/)++ })++it('gives the outdoor steps somewhere to point', () => {const toward = Object.fromEntries(FIRST_STEPS.map((s) => [s.id, s.toward]))expect(toward.gather?.nodes).toEqual(['branch_pile', 'loose_stones'])@@@ -3,7 +3,7 @@* used to arrive with an empty hotbar and nothing to say what Scraplandia is about (owner, 2026-10-19: "so boring").** The steps follow Scrap Island (apps/server/src/maps/scrapIsland.ts) and the tool chain: branches and loose stones-- * need no tool → planks → a Stone Axe → an oak in the west forest → a Stone Pickaxe → the quarry → the scrapyard →++ * need no tool → planks → a Stone Axe and a Stone Pickaxe → an oak in the west forest → the quarry → the scrapyard →* a haul at the recovery bird. Each step reads what the client already knows (inventory, skill levels, the* extraction announce); progress only moves forward and is kept in this browser. The panel can be closed for good.*/@@@ -33,7 +33,9 @@ export const FIRST_STEPS: FirstStep[] = [{id: 'gather',title: 'Gather branches and stones',-- hint: 'Piles just inside every city gate. Look at one and press E.',++ // A pile holds five stones and a log makes four planks: exactly the axe (2 planks, 2 stones) and the pickaxe++ // (2 planks, 3 stones). The hint said nothing of how many, and nothing said to make a pickaxe.++ hint: 'Piles just inside every city gate. Look at one and press E: a log and five stones make your first two tools.',done: (v) =>(v.count('wood_log') > 0 || v.count('wood_plank') > 0 || v.count('stone_axe') > 0) &&(v.count('stone') > 0 || v.count('stone_axe') > 0),@@@ -45,6 +47,12 @@ export const FIRST_STEPS: FirstStep[] = [hint: 'Tab → Crafting: turn a log into Wood Planks, then make the axe.',done: (v) => v.count('stone_axe') > 0 || v.level('woodcutting') >= 2,},++ {++ id: 'pickaxe',++ title: 'Craft a Stone Pickaxe',++ hint: 'Tab → Crafting: 2 Wood Planks and 3 Stones. Short of stones? The gate piles refill every two minutes.',++ done: (v) => v.count('stone_pickaxe') > 0 || v.level('mining') >= 2,++ },{id: 'chop',title: 'Chop an oak',@@@ -55,7 +63,7 @@ export const FIRST_STEPS: FirstStep[] = [{id: 'mine',title: 'Mine at the quarry',-- hint: 'South-west of town. A Stone Pickaxe breaks the stone deposits.',++ hint: 'South-west of town. Hold the pickaxe and swing at the stone deposits.',done: (v) => v.level('mining') >= 2,toward: { nodes: ['stone_deposit'], zone: 'quarry' },},
@@ -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
+- **No step said to make a pickaxe.** Scraplandia's first steps went from the Stone Axe to "Mine at the quarry", whose
+ hint only said a Stone Pickaxe breaks the deposits, and the gather step finished after a single stone. A new
+ "Craft a Stone Pickaxe" step follows the axe (2 Wood Planks and 3 Stones), and the gather hint says a log and five
+ stones make the first two tools: one pile and one log are exactly both recipes.
- **Townsfolk in the Salvage Rush arena.** The 90-second scrap round ran the content's seven NPC spawns, laid out for
- **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.
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
A world definition can now name its own NPCs (`npcSpawns`; `[]` is none), and the arena has none. A map's own NPCs
@@ -44,6 +44,9 @@ describe('first steps', () => {
@@ -44,6 +44,9 @@ describe('first steps', () => {
expect(current()).toContain('Craft a Stone Axe')
expect(current()).toContain('Craft a Stone Axe')
items = { stone_axe: 1 }
items = { stone_axe: 1 }
fs.update()
fs.update()
+ expect(current()).toContain('Craft a Stone Pickaxe')
+ items = { stone_axe: 1, stone_pickaxe: 1 }
+ fs.update()
expect(current()).toContain('Chop an oak')
expect(current()).toContain('Chop an oak')
levels = { woodcutting: 2 }
levels = { woodcutting: 2 }
fs.update()
fs.update()
@@ -52,7 +55,7 @@ describe('first steps', () => {
@@ -52,7 +55,7 @@ describe('first steps', () => {
levels = {}
levels = {}
fs.update()
fs.update()
expect(current()).toContain('Mine at the quarry')
expect(current()).toContain('Mine at the quarry')
- expect(root.querySelector('.first-steps-head')?.textContent).toContain('3/6')
+ expect(root.querySelector('.first-steps-head')?.textContent).toContain('4/7')
})
})
it('finishes with a haul and then hides', () => {
it('finishes with a haul and then hides', () => {
@@ -83,7 +86,7 @@ describe('first steps', () => {
@@ -83,7 +86,7 @@ describe('first steps', () => {
it('progress() unions what was done with what is done now', () => {
it('progress() unions what was done with what is done now', () => {
const view = { count: () => 0, level: (s: string) => (s === 'mining' ? 2 : 1), hauled: false }
const view = { count: () => 0, level: (s: string) => (s === 'mining' ? 2 : 1), hauled: false }
- expect(progress(view, { done: ['gather'] })).toEqual(['gather', 'mine'])
+ expect(progress(view, { done: ['gather'] })).toEqual(['gather', 'pickaxe', 'mine'])
})
})
})
})
@@ -110,6 +113,22 @@ describe('the waypoint hooks', () => {
@@ -110,6 +113,22 @@ describe('the waypoint hooks', () => {
expect(steps.birdAt).toBeNull()
expect(steps.birdAt).toBeNull()
})
})
+ it('makes the pickaxe a step of its own, right after the axe, from the same gate piles', () => {
+ const ids = FIRST_STEPS.map((s) => s.id)
+ expect(ids.indexOf('pickaxe')).toBe(ids.indexOf('axe') + 1)
+ const pickaxe = FIRST_STEPS.find((s) => s.id === 'pickaxe')!
+ expect(pickaxe.hint).toMatch(/2 Wood Planks and 3 Stones/)
+ expect(
+ pickaxe.done({
+ count: (i) => (i === 'stone_pickaxe' ? 1 : 0),
+ level: () => 1,
+ hauled: false,
+ }),
+ ).toBe(true)
+ expect(pickaxe.done({ count: () => 0, level: () => 1, hauled: false })).toBe(false)
+ expect(FIRST_STEPS[0]?.hint).toMatch(/five stones/)
+ })
+
it('gives the outdoor steps somewhere to point', () => {
it('gives the outdoor steps somewhere to point', () => {
const toward = Object.fromEntries(FIRST_STEPS.map((s) => [s.id, s.toward]))
const toward = Object.fromEntries(FIRST_STEPS.map((s) => [s.id, s.toward]))
expect(toward.gather?.nodes).toEqual(['branch_pile', 'loose_stones'])
expect(toward.gather?.nodes).toEqual(['branch_pile', 'loose_stones'])
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
* used to arrive with an empty hotbar and nothing to say what Scraplandia is about (owner, 2026-10-19: "so boring").
* used to arrive with an empty hotbar and nothing to say what Scraplandia is about (owner, 2026-10-19: "so boring").
*
*
* The steps follow Scrap Island (apps/server/src/maps/scrapIsland.ts) and the tool chain: branches and loose stones
* The steps follow Scrap Island (apps/server/src/maps/scrapIsland.ts) and the tool chain: branches and loose stones
- * need no tool → planks → a Stone Axe → an oak in the west forest → a Stone Pickaxe → the quarry → the scrapyard →
+ * need no tool → planks → a Stone Axe and a Stone Pickaxe → an oak in the west forest → the quarry → the scrapyard →
* a haul at the recovery bird. Each step reads what the client already knows (inventory, skill levels, the
* a haul at the recovery bird. Each step reads what the client already knows (inventory, skill levels, the
* extraction announce); progress only moves forward and is kept in this browser. The panel can be closed for good.
* extraction announce); progress only moves forward and is kept in this browser. The panel can be closed for good.
*/
*/
@@ -33,7 +33,9 @@ export const FIRST_STEPS: FirstStep[] = [
@@ -33,7 +33,9 @@ export const FIRST_STEPS: FirstStep[] = [
{
{
id: 'gather',
id: 'gather',
title: 'Gather branches and stones',
title: 'Gather branches and stones',
- hint: 'Piles just inside every city gate. Look at one and press E.',
+ // A pile holds five stones and a log makes four planks: exactly the axe (2 planks, 2 stones) and the pickaxe
+ // (2 planks, 3 stones). The hint said nothing of how many, and nothing said to make a pickaxe.
+ hint: 'Piles just inside every city gate. Look at one and press E: a log and five stones make your first two tools.',
done: (v) =>
done: (v) =>
(v.count('wood_log') > 0 || v.count('wood_plank') > 0 || v.count('stone_axe') > 0) &&
(v.count('wood_log') > 0 || v.count('wood_plank') > 0 || v.count('stone_axe') > 0) &&
(v.count('stone') > 0 || v.count('stone_axe') > 0),
(v.count('stone') > 0 || v.count('stone_axe') > 0),
@@ -45,6 +47,12 @@ export const FIRST_STEPS: FirstStep[] = [
@@ -45,6 +47,12 @@ export const FIRST_STEPS: FirstStep[] = [
hint: 'Tab → Crafting: turn a log into Wood Planks, then make the axe.',
hint: 'Tab → Crafting: turn a log into Wood Planks, then make the axe.',
done: (v) => v.count('stone_axe') > 0 || v.level('woodcutting') >= 2,
done: (v) => v.count('stone_axe') > 0 || v.level('woodcutting') >= 2,
},
},
+ {
+ id: 'pickaxe',
+ title: 'Craft a Stone Pickaxe',
+ hint: 'Tab → Crafting: 2 Wood Planks and 3 Stones. Short of stones? The gate piles refill every two minutes.',
+ done: (v) => v.count('stone_pickaxe') > 0 || v.level('mining') >= 2,
+ },
{
{
id: 'chop',
id: 'chop',
title: 'Chop an oak',
title: 'Chop an oak',
@@ -55,7 +63,7 @@ export const FIRST_STEPS: FirstStep[] = [
@@ -55,7 +63,7 @@ export const FIRST_STEPS: FirstStep[] = [
{
{
id: 'mine',
id: 'mine',
title: 'Mine at the quarry',
title: 'Mine at the quarry',
- hint: 'South-west of town. A Stone Pickaxe breaks the stone deposits.',
+ hint: 'South-west of town. Hold the pickaxe and swing at the stone deposits.',
done: (v) => v.level('mining') >= 2,
done: (v) => v.level('mining') >= 2,
toward: { nodes: ['stone_deposit'], zone: 'quarry' },
toward: { nodes: ['stone_deposit'], zone: 'quarry' },
},
},