Neat! Pickled Muskrat!

    discoteque
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.5 • Public • Published

    Discoteque

    Text-based interactive ficiton with RPG elements

    Allows you to create simple RPG-esque text stories that follow basic principles.

    1. You can read text that is spoken by characters or just presented as is ("narration").
    2. You can navigate branching dialogue.
    3. You can distribute and use skills to pass dialogue checks and gain them.
    4. State can be used to track progress / remember decisions / set "VN-like" flags.
    5. Time passes with each line

    Full Demo

    Usage

    With seed

    Just clone example seed project, then edit game.ts, skills.ts and store.ts to develop your game.

    git clone https://code.gensokyo.social/Gensokyo.social/discoteque-seed.git my-game
    cd my-game
    yarn
    yarn start

    With create-react-app as template

    (Optional, but provides for stable webpack set up that we're not covering here)

    Start with https://create-react-app.dev/docs/adding-typescript/

    npx create-react-app my-app --template typescript

    Remove all contents from src/

    Install following pre-requisits

    yarn add discoteque react-toastify redux
    yarn add -D @types/react-redux

    Follow these steps

    Create game store & reducer

    Create a store and reducer to process in-game actions and change state

    // store.ts
    import { createStore, Action } from 'redux';
    import { resetGame } from 'discoteque/lib/engine/lib/utils';
    
    export interface IGameState {
        myCheck: boolean;
    }
    
    const defaultState: IGameState = {
      myCheck: false,
    };
    
    type ACTION = 'INIT' | 'set-check';
    interface IGameAction extends Action {
        type: ACTION;
    }
    
    interface ISetCheckAction extends IGameAction {
        check: boolean;
    }
    
    interface InitAction extends IGameAction {
        data: { gameState: IGameState };
    }
    
    export const setCheck = (check: boolean): ISetCheckAction => ({
        type: 'set-check', check,
    });
    
    export const reducer = (state: IGameState = defaultState, action: IGameAction): IGameState => {
      switch (action.type) {
          case 'set-check':
            const checkAction = action as ISetCheckAction;
            return ({ ...state, myCheck: checkAction.check });
          // 'INIT' is a reserved Engine action type, used to initialize both game and engine state from save
          // Since game state is defined along with game's script and not included with engine, we have to perform reseting ourselves
          // `resetGame` is a supplied utility function that lets us perform necessary reduce without cluttering the rest of reducer
          case 'INIT':
            const initAction = action as InitAction;
            return resetGame(state, initAction);
          default:
            return state;
      }
    }
    
    export default createStore(reducer);

    Create skills

    Create default skills

    // skills.ts
    import { SkillSet } from "discoteque/lib/engine/types";
    
    export type GameSkills = 'test';
    
    export const skills: SkillSet<GameSkills> = {
      test: 5,
    };
    
    export const skillDescriptions: Record<GameSkills, string> = {
      test: 'This is a test skill',
    }
    
    export default skills;

    Create script

    // game.ts
    import { INode, IActor, ILocation } from 'discoteque/lib/engine/types';
    import { setState } from 'discoteque/lib/engine/lib/store';
    import { IGameState, setCheck } from './store';
    import { awardSkill, toast } from 'discoteque/lib/engine/lib/utils';
    
    // Create Nodes
    export const nodes: INode<IGameState>[] = [
        { id: 'beginning', kind: 'node', next: 'choice', lines: [
            // Lines are basically objects which specify how line should look
            { actorId: 'char_exampler', text: 'Hi! This is an example of Discoteque!' },
            { actorId: 'char_exampler', text: 'Let\'s try picking options' },
        ] },
        { id: 'choice', kind: 'node', next: 'choice', lines: [
            // Make player pick an answer
            { text: 'Your choice?', options: [
                // This answer is gated behind a skill check (dice throw)
                // name and difficulty are self-explanatory, failTo specifies node to fallback to if check is failed
                // failTo is REQUIRED
                { text: 'Let\'s test a skill', value: 'test_success', skill: { name: 'test', difficulty: 15, failTo: 'test_fail' } },
                // This one has no costs or prerequisites!
                { text: 'Let me out!', value: 'exit' },
            ] }
        ] },
        { id: 'test_success', kind: 'node', next: 'choice', lines: [
            // Line can be a funciton which takes a number of parameters and returns a line object
            (_, { myCheck }, dispatch) => {
                if (!myCheck) {
                    // We can dispatch redux events to modify game's store!
                    dispatch(setCheck(true))
                    return { actorId: 'char_exampler', text: "Yay! You passed a check!" }
                } else {
                    return { text: 'You think you\'ve already passed this check, so no need to do it again' }
                }
            },
        ]},
        { id: 'test_fail', kind: 'node', next: 'choice', lines: [
            ({ skillPoints }, _, dispatch) => {
                // This is a helper function from discoteque lib
                // It awards one skill point to player and dispatches a toast popup
                awardSkill(dispatch, skillPoints);
                return { actorId: 'char_exampler', text: 'You\'ve failed... Not a problem! Take a skill point and I\'ll give another if you fail again!' };
            }
        ] },
        { id: 'exit', kind: 'node', next: 'choice', lines: [
            (_, _gameState, dispatch) => {
                dispatch(setState({ ui: { isOver: true } }));
                return { actorId: 'char_exampler' , text: 'Bye-bye!' };
            },
        ] }
    ];
    
    // Create actors
    export const actors: IActor<IGameState>[] = [
        { id: 'char_exampler', kind: 'actor', name: 'Exampler!', lines: [] },
    ];
    
    // Create locations
    export const locations: ILocation<IGameState>[] = [
        { id: 'loc_discoville', kind: 'location', name: 'Discoville!', next: 'beginning', lines: [
            { text: 'It\'s a beautiful day at Discoville today!' },
            { text: 'A friendly feller is approaching you.' }
        ] },
    ];

    Create game config

    // config.ts
    import { EngineConfig } from "discoteque/lib/engine/types";
    import { IGameState, reducer } from "./store";
    import skills, { GameSkills, skillDescriptions } from "./skills";
    import { nodes, actors, locations } from "./game";
    
    const config: EngineConfig<IGameState, undefined, GameSkills> = {
        // Pass the initial skills
        skills: skills,
        // Define how many points player can spend on start
        skillPointsOnStart: 3,
        // Define starting node
        startNode: 'loc_discoville',
        // Define time object
        chrono: {
            time: 750,
        },
        // Give skills some desctiptions
        skillDescriptoins: skillDescriptions,
        // Provide game's reducer
        reducer: reducer,
        // Supply the actual script (nodes)
        nodes: [
            ...nodes,
            ...actors,
            ...locations,
        ],
    };
    
    export default config;

    Use config in your app

    Replace default project's index.tsx with this

    // index.tsx
    import makeApp from 'discoteque/lib';
    import { injectGlobal } from 'emotion';
    import config from './config';
    
    import 'react-toastify/dist/ReactToastify.css';
    
    // Set up default font
    import font from "discoteque/src/assets/fonts/AnticSlab-Regular.woff2";
    injectGlobal`
        @font-face {
            font-family: 'Antic Slab Regular';
            src:  url(${font}) format('woff2');
        }
        body {
            font-family: 'Antic Slab Regular';
        }
    `;
    
    // Import your config file
    
    // Make an app. Mounting onto DOM is already handled by App.
    makeApp(config);

    Development

    Run build

    yarn build

    Link

    yarn link

    Use link in live project

    yarn link discoteque

    Keywords

    Install

    npm i discoteque@1.0.5

    Version

    1.0.5

    License

    MIT

    Unpacked Size

    452 kB

    Total Files

    97

    Last publish

    Collaborators

    • deiru2k