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
9 changes: 4 additions & 5 deletions docs/book/parts/part-1/02-your-first-room.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ initializeWorld(world: WorldModel): void {
aliases: ['sign', 'wooden sign'],
article: 'a',
}));
sign.add(new SceneryTrait());

const booth = world.createEntity('ticket booth', EntityType.SCENERY);
booth.add(new IdentityTrait({
Expand All @@ -181,7 +180,6 @@ initializeWorld(world: WorldModel): void {
aliases: ['booth', 'ticket booth', 'window'],
article: 'a',
}));
booth.add(new SceneryTrait());

world.moveEntity(sign.id, entrance.id);
world.moveEntity(booth.id, entrance.id);
Expand All @@ -191,9 +189,10 @@ initializeWorld(world: WorldModel): void {
}
```

The ticket booth is built exactly like the sign: an entity, an `IdentityTrait` for
its name and description, and a `SceneryTrait` so it stays put. Both are placed in
the entrance, and now `examine booth` in the "Try it" list has something to find.
The ticket booth is built exactly like the sign: an entity and an `IdentityTrait`
for its name and description. Both are typed `EntityType.SCENERY`, so they stay
put, and both are placed in the entrance; now `examine booth` in the "Try it" list
has something to find.

## Placing things

Expand Down
9 changes: 2 additions & 7 deletions docs/book/parts/part-2/04-rooms-and-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,15 @@ initializeWorld(world: WorldModel): void {

// Step 3: scenery. The welcome sign and ticket booth from Chapter 2 stay
// in the entrance. The three new rooms get scenery of their own. Each is the
// same pattern you already know which includes: an entity, an IdentityTrait,
// a SceneryTrait (so it can't be taken), and a moveEntity to place it.
// same pattern you already know: an entity, an IdentityTrait, and a moveEntity
// to place it. The EntityType.SCENERY type makes each one fixed.
const sign = world.createEntity('welcome sign', EntityType.SCENERY);
sign.add(new IdentityTrait({
name: 'welcome sign',
description: 'A brightly painted wooden sign welcomes you to the zoo.',
aliases: ['sign', 'welcome sign', 'wooden sign'],
article: 'a',
}));
sign.add(new SceneryTrait());
world.moveEntity(sign.id, entrance.id);

const booth = world.createEntity('ticket booth', EntityType.SCENERY);
Expand All @@ -181,7 +180,6 @@ initializeWorld(world: WorldModel): void {
aliases: ['booth', 'ticket booth', 'window'],
article: 'a',
}));
booth.add(new SceneryTrait());
world.moveEntity(booth.id, entrance.id);

const directionSigns = world.createEntity('direction signs', EntityType.SCENERY);
Expand All @@ -195,7 +193,6 @@ initializeWorld(world: WorldModel): void {
article: 'some',
grammaticalNumber: 'plural',
}));
directionSigns.add(new SceneryTrait());
world.moveEntity(directionSigns.id, mainPath.id);

const goats = world.createEntity('pygmy goats', EntityType.SCENERY);
Expand All @@ -208,7 +205,6 @@ initializeWorld(world: WorldModel): void {
article: 'some',
grammaticalNumber: 'plural',
}));
goats.add(new SceneryTrait());
world.moveEntity(goats.id, pettingZoo.id);

const toucan = world.createEntity('toucan', EntityType.SCENERY);
Expand All @@ -220,7 +216,6 @@ initializeWorld(world: WorldModel): void {
aliases: ['toucan', 'bird', 'toco toucan'],
article: 'a',
}));
toucan.add(new SceneryTrait());
world.moveEntity(toucan.id, aviary.id);

// Step 4: place the player at the entrance, as in Chapter 2.
Expand Down
66 changes: 33 additions & 33 deletions docs/book/parts/part-2/05-scenery-and-portable-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ That's a complete, takeable object. `EntityType.ITEM` is the type label for a
generic portable thing; the `IdentityTrait` gives it a name, description, and
aliases. No trait is needed to make it carryable; that's the default.

## SceneryTrait takes portability away
## EntityType.SCENERY makes a thing fixed

Most of the things in a room are *not* meant to be carried. You don't want the
player stuffing a park bench into a backpack or wandering off with the iron
fence. `SceneryTrait` is how you say "this is part of the world, not a
collectible." It does exactly one thing: it blocks the taking action.
fence. Create a fixed thing as `EntityType.SCENERY` and it comes with a
`SceneryTrait` already attached. That trait does exactly one thing: it blocks the
taking action.

```typescript
const fence = world.createEntity('iron fence', EntityType.SCENERY);
Expand All @@ -47,39 +48,38 @@ fence.add(new IdentityTrait({
description: 'A tall wrought-iron fence with animal silhouettes.',
aliases: ['fence', 'iron fence', 'railing'],
}));
fence.add(new SceneryTrait());
world.moveEntity(fence.id, entrance.id);
```

Now `take fence` gives the player *"iron fence is fixed in place."* But
`examine fence` still works: scenery blocks *taking*, not *looking*. The entity
keeps its `IdentityTrait`, so its description is always readable.

> **The mistake everyone makes once:** forgetting `SceneryTrait`. Because items
> are portable by default, an animal or a wall you created without it can be
> picked up and carried away. If the player can pocket your waterfall, you left
> off the scenery trait.
> **The mistake everyone makes once:** a fixed thing that *isn't* typed
> `EntityType.SCENERY`. The scenery type pins it for you, but a container, a
> supporter, or an animal you made an `ACTOR` is portable by default. If the player
> can pocket your feed dispenser, it has no `SceneryTrait` and you need to add one
> by hand.

## The label and the trait are two different things
## When you still add SceneryTrait by hand

This trips people up, because two things share the same word:
Typing a thing `EntityType.SCENERY` is the usual way to fix it, and it is enough
on its own. You reach for an explicit `SceneryTrait` in only two cases:

- **`EntityType.SCENERY`** is a *label*. It tells the engine "this entity
represents scenery." On its own it does **not** prevent taking.
- **`SceneryTrait`** is the *mechanism*. It's the thing that actually blocks the
taking action.
- **A fixed thing of another type.** A feed dispenser is a `CONTAINER` and a park
bench is a `SUPPORTER`; those types don't arrive fixed, so you add a
`SceneryTrait` to pin them in place.
- **A custom refusal.** A plain `SceneryTrait` gives the standard "fixed in place"
line; construct it with your own message to say something specific.

You want both for a proper fixed object. `EntityType.SCENERY` without
`SceneryTrait` is scenery the player can still pick up — almost never what you
mean. The pair to keep straight:

| Entity type | Portable by default | Example |
| Entity type | Fixed by default | Example |
|---|---|---|
| `EntityType.ITEM` | Yes | Maps, keys, coins |
| `EntityType.SCENERY` | No (with `SceneryTrait`) | Fences, benches, animals |
| `EntityType.ITEM` | No (portable) | Maps, keys, coins |
| `EntityType.SCENERY` | Yes (gets `SceneryTrait`) | Fences, benches, animals |
| `EntityType.CONTAINER` / `SUPPORTER` | No (add `SceneryTrait` to fix) | Dispensers, shelves |

A rule of thumb for which to reach for: if you'd find it strange for the player
to put a thing in their pocket, it's scenery.
A rule of thumb: if you'd find it strange for the player to put a thing in their
pocket, make it `EntityType.SCENERY`.

## Aliases make objects findable

Expand Down Expand Up @@ -111,7 +111,7 @@ library handles the whole inventory vocabulary without a line of code from you:
| `drop all` | Drops everything the player is holding |

When the player carries an item and walks to a new room, the item travels with
them carried things live inside the player's own container, so they move
them: carried things live inside the player's own container, so they move
wherever the player goes. And loose portable objects on the floor are listed
after the room description:

Expand All @@ -122,7 +122,7 @@ A wide gravel path winds through the heart of the zoo...
You can see a souvenir penny here.
```

Scenery is *not* listed this way it's expected to be named in the room's
Scenery is *not* listed this way; it's expected to be named in the room's
description prose, where it belongs.

## How taking actually decides
Expand All @@ -138,24 +138,25 @@ types `take map`:
4. The player sees *"Taken."*

That's the entire rule. Portable or not is simply: *does it have `SceneryTrait`?*
Creating a thing as `EntityType.SCENERY` is just the quickest way to give it one.

## Putting it together

Fill each room with scenery for atmosphere, then scatter a few takeable items.
Scenery gets `SceneryTrait`; items get nothing extra:
Scenery is typed `EntityType.SCENERY`, which fixes it in place; items get nothing
extra:

```typescript
// Scenery — fixed in place, examinable, mentioned in room prose.
// Scenery: the SCENERY type fixes it in place, examinable, mentioned in room prose.
const fence = world.createEntity('iron fence', EntityType.SCENERY);
fence.add(new IdentityTrait({
name: 'iron fence',
description: 'A tall wrought-iron fence with animal silhouettes.',
aliases: ['fence', 'iron fence', 'railing'],
}));
fence.add(new SceneryTrait());
world.moveEntity(fence.id, entrance.id);

// More scenery a pair of rabbits in the Petting Zoo, beside the goats.
// More scenery: a pair of rabbits in the Petting Zoo, beside the goats.
const rabbits = world.createEntity('rabbits', EntityType.SCENERY);
rabbits.add(new IdentityTrait({
name: 'rabbits',
Expand All @@ -166,10 +167,9 @@ rabbits.add(new IdentityTrait({
article: 'some',
grammaticalNumber: 'plural',
}));
rabbits.add(new SceneryTrait());
world.moveEntity(rabbits.id, pettingZoo.id);

// A takeable item no SceneryTrait, so it's portable by default.
// A takeable item: no SceneryTrait, so it's portable by default.
const zooMap = world.createEntity('zoo map', EntityType.ITEM);
zooMap.add(new IdentityTrait({
name: 'zoo map',
Expand All @@ -184,7 +184,7 @@ animalFeed.add(new IdentityTrait({
name: 'bag of animal feed',
description:
'A small brown paper bag of dried corn and pellets. The label reads ' +
'"ZOO SNACKS Safe for goats, rabbits, and birds." It rustles invitingly.',
'"ZOO SNACKS: Safe for goats, rabbits, and birds." It rustles invitingly.',
aliases: ['feed', 'animal feed', 'bag of feed', 'bag', 'corn', 'pellets'],
}));
world.moveEntity(animalFeed.id, pettingZoo.id);
Expand Down Expand Up @@ -216,7 +216,7 @@ penny are portable, the feed waits in the Petting Zoo, and the goats stay put.
> look Map is now on the ground in Main Path
> east Go to the Petting Zoo
> take feed Pick up the bag of animal feed
> take goats Can't they're scenery!
> take goats Can't: they're scenery!
```

## Key takeaway
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ shelves.add(new IdentityTrait({
description: 'Industrial steel shelving stacked with feed sacks and supplies.',
aliases: ['shelves', 'metal shelves', 'shelf', 'shelving'],
}));
shelves.add(new SceneryTrait());
world.moveEntity(shelves.id, supplyRoom.id);

// The key: an ordinary item, placed at the entrance for the player to find.
Expand Down
3 changes: 0 additions & 3 deletions docs/book/parts/part-2/08-light-and-dark.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ sugarGliders.add(new IdentityTrait({
aliases: ['sugar gliders', 'gliders', 'sugar glider'],
article: 'some',
}));
sugarGliders.add(new SceneryTrait());
world.moveEntity(sugarGliders.id, nocturnalExhibit.id);

const bushBabies = world.createEntity('bush babies', EntityType.SCENERY);
Expand All @@ -180,7 +179,6 @@ bushBabies.add(new IdentityTrait({
aliases: ['bush babies', 'bush baby', 'galagos'],
article: 'some',
}));
bushBabies.add(new SceneryTrait());
world.moveEntity(bushBabies.id, nocturnalExhibit.id);

const barnOwl = world.createEntity('barn owl', EntityType.SCENERY);
Expand All @@ -190,7 +188,6 @@ barnOwl.add(new IdentityTrait({
aliases: ['barn owl', 'owl'],
article: 'a',
}));
barnOwl.add(new SceneryTrait());
world.moveEntity(barnOwl.id, nocturnalExhibit.id);
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ pettingPlaque.add(new ReadableTrait({
'temperament. Our pair, Biscuit and Marmalade, were born right ' +
'here at Willowbrook in 2023.',
}));
pettingPlaque.add(new SceneryTrait());
world.moveEntity(pettingPlaque.id, pettingZoo.id);
```

Expand Down Expand Up @@ -111,8 +110,8 @@ world.moveEntity(brochure.id, entrance.id);
```

Readable scenery (plaques, warning signs) and readable items (brochures,
letters, books) are the same trait; the only difference is whether you also add
`SceneryTrait`.
letters, books) are the same trait; the only difference is whether the thing is
fixed in place.

## SwitchableTrait — a device with on/off state

Expand Down Expand Up @@ -185,8 +184,8 @@ match how a person would actually talk about the object.
## Key takeaway

`ReadableTrait` separates what an object *says* (`read`) from what it *looks
like* (`examine`); add `SceneryTrait` for fixed plaques and signs, leave it off
for portable brochures and books, and use `\n` to shape the text. `SwitchableTrait`
like* (`examine`); type fixed plaques and signs `EntityType.SCENERY` and leave
portable brochures and books as plain items, and use `\n` to shape the text. `SwitchableTrait`
gives any device an on/off toggle through the `switch`/`turn` verbs, alone for a
plain device like the radio, or paired with another trait when the switch should
drive something. It's the sibling of `OpenableTrait`: same shape, different verbs,
Expand Down
24 changes: 12 additions & 12 deletions tutorials/familyzoo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@
"clean": "sharpee clean"
},
"dependencies": {
"@sharpee/core": "^1.0.8",
"@sharpee/engine": "^1.0.8",
"@sharpee/event-processor": "^1.0.8",
"@sharpee/helpers": "^1.0.8",
"@sharpee/if-domain": "^1.0.8",
"@sharpee/lang-en-us": "^1.0.8",
"@sharpee/media": "^1.0.8",
"@sharpee/parser-en-us": "^1.0.8",
"@sharpee/core": "^1.1.1",
"@sharpee/engine": "^1.1.1",
"@sharpee/event-processor": "^1.1.1",
"@sharpee/helpers": "^1.1.1",
"@sharpee/if-domain": "^1.1.1",
"@sharpee/lang-en-us": "^1.2.0",
"@sharpee/media": "^1.1.1",
"@sharpee/parser-en-us": "^1.1.1",
"@sharpee/platform-browser": "^1.1.2",
"@sharpee/plugin-npc": "^1.0.8",
"@sharpee/plugin-scheduler": "^1.0.8",
"@sharpee/stdlib": "^1.0.8",
"@sharpee/world-model": "^1.0.8"
"@sharpee/plugin-npc": "^1.1.1",
"@sharpee/plugin-scheduler": "^1.1.1",
"@sharpee/stdlib": "^1.2.0",
"@sharpee/world-model": "^1.2.0"
},
"devDependencies": {
"@sharpee/devkit": "^1.1.4",
Expand Down
Loading
Loading