# A store from a document

> Load a pet whose state and actions are defined in a document. Change its values and see the scene react.

Example `store-document`, running at https://nacatamalon.com/en/examples/store-document. The whole catalogue: https://nacatamalon.com/llms/examples.md.

Load a pet whose state and actions are defined in a document. Change its values and see the scene react.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import * as Settings from './settings';
import { Room } from './scenes/room';
import { Hud } from './scenes/hud';
import { Start } from './scenes/start';
import { loadPet } from './stores/load_pet';
import { addActions } from './stores/add_actions';
import { registerFeed } from './scripts/feed';

loadPet();
registerFeed();
addActions();

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#14121c',
});

// Declare Scenes
game({
    Start,
    Room,
    Hud
}, 'Start');
```

`data/pet_store.ts`:

```ts
export const PET_FILE = {
    format: 'nacatamalon-store',
    version: 1,
    key: 'pet',
    fields: [
        {
            name: 'hunger',
            type: 'number',
            value: 42
        },
        {
            name: 'name',
            type: 'string',
            value: 'GAMMIX'
        },
    ],
};
```

`data/room.ts`:

```ts
const place = (x: number, y: number) => ({
    x,
    y,
    z: 0,
    rotation: 0,
    rotationX: 0,
    rotationY: 0,
    scaleX: 1,
    scaleY: 1,
    scaleZ: 1
});

// The scene as the editor would have saved it. The two components worth looking at are the last
// ones: a link to a store by name, and a behaviour by name. Neither carries any code.
export const ROOM = {
    format: 'nacatamalon-scene',
    version: 1,
    name: 'Room',
    assets: [],
    root: {
        id: 'room',
        name: 'Room',
        transform: null,
        components: [],
        children: [
            {
                id: 'pet',
                name: 'Pet',
                transform: place(0, 0),
                components: [
                    {
                        type: 'sprite',
                        id: 'pet-art',
                        texture: null,
                        tint: {
                            r: 0.36,
                            g: 0.84,
                            b: 0.36,
                            a: 1
                        },
                        width: 56,
                        height: 56,
                        transform: {
                            x: 152,
                            y: 60
                        }
                    },
                    {
                        type: 'store',
                        id: 'pet:store:pet',
                        ref: 'pet'
                    },
                    // The setting the file authors lands over the behaviour's own default, which is 9.

                    {
                        type: 'script',
                        id: 'pet-feed',
                        ref: 'feed',
                        props: {
                            decay: 4
                        }
                    },
                ],
                children: [],
            }
        ],
    },
};
```

`scenes/hud.ts`:

```ts
import { createScene, createText, getGameStore, useLoadBitmapFont, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';

export const Hud = () => {
    const font = useLoadBitmapFont(Settings.FONT);
    const pet = getGameStore('pet');

    if (pet === null) {
        throw new Error('Load the pet store before starting the HUD.');
    }

    const label = createText({
        text: '',
        font,
        style: {
            fontSize: 8
        },
        tint: '#f2f6fa',
        transform: {
            x: 20,
            y: 140
        }
    });

    createText({
        text: 'F FEEDS IT',
        font,
        style: {
            fontSize: 8
        },
        tint: '#8fa4c4',
        transform: {
            x: 20,
            y: 164
        },
    });

    useUpdate(() => {
        // The name comes from the file and the number from the running game: same store.
        label.text = pet.state.name + '   HUNGER ' + Math.round(Number(pet.state.hunger));
    });

    return createScene();
};
```

`scenes/room.ts`:

```ts
import { parseSceneDoc, sceneFromDoc } from 'nacatamalon/files';
import { ROOM } from '../data/room';

export const Room = sceneFromDoc(parseSceneDoc(ROOM, 'scenes/room.scene'));
```

`scenes/start.ts`:

```ts
import { createScene, useScene } from 'nacatamalon';

export const Start = () => {
    const scene = useScene();

    scene.launch('Room');
    scene.launch('Hud');

    return createScene();
};
```

`scripts/feed.ts`:

```ts
import type { TPetState } from '../stores/types';
import { getColor, registerScript, storeOf, useKeyboard, useUpdate } from 'nacatamalon';

const FULL = getColor('#5bd75b');
const HUNGRY = getColor('#e5533b');

export const registerFeed = () => {
    registerScript<{ decay: number; }>('feed', (self, props) => {
        // The object was given a store; the behaviour asks the object for it. No import naming a file
        // this behaviour cannot know the path of, which is the whole point: it was attached by a tool.
        const pet = storeOf<TPetState, { feed: () => void; }>(self);
        const [sprite] = self.drawables;

        if (pet === null || sprite === undefined || sprite.type !== 'sprite') {
            return;
        }

        const keys = useKeyboard();

        useUpdate((delta) => {
            pet.set((s) => {
                s.hunger = Math.max(0, s.hunger - props.decay * delta);
            });

            // An action the FILE knows nothing about: it arrives with the code, further down, and the
            // store this is holding is the same object by then. If it were not, this would be undefined
            // and the F key would do nothing.
            if (keys.justPressed('f')) {
                pet.actions.feed();
            }

            sprite.tint = pet.state.hunger > 30 ? FULL : HUNGRY;
        });
    }, [
        {
            key: 'decay',
            label: 'Hunger per second',
            type: 'number',
            default: 9,
            min: 0,
            step: 1
        }
    ]);
};
```

`settings.ts`:

```ts
export const WIDTH = 360;
export const HEIGHT = 200;
export const FONT = {
    json: '/example-assets/fonts/bitmaps/nacatamal-arcade/nacatamal-arcade.json',
    atlas: '/example-assets/fonts/bitmaps/nacatamal-arcade/nacatamal-arcade.png'
};
```

`stores/add_actions.ts`:

```ts
import { createGameStore } from 'nacatamalon';

export const addActions = () => {
    createGameStore({
        key: 'pet',
        state: {
            hunger: 100
        },
        actions: (set) => ({
            feed: () => set((s) => {
                s.hunger = Math.min(100, s.hunger + 18);
            }),
        }),
    });
};
```

`stores/load_pet.ts`:

```ts
import { applyStoreDocs, parseStoreDoc } from 'nacatamalon/files';
import { clearGameStores } from 'nacatamalon/authoring';
import { PET_FILE } from '../data/pet_store';

export const loadPet = () => {
    // The index belongs to the page and this example is re-run on every edit, so the last run's stores
    // are forgotten first. That is what this is for outside a test too: a tool closing one project
    // before it opens another.
    clearGameStores();

    // Reading is the front door and applying is the front door, because a published game does both on
    // the way up: it reads its .store files and lays them over what its code declared.
    applyStoreDocs([parseStoreDoc(PET_FILE, 'stores/pet.store')]);
};
```

`stores/types.ts`:

```ts
export type TPetState = { name: string; hunger: number; };
```
