npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

discoteque

v2.1.1

Published

Interactive Ficiton engine with RPG elements

Downloads

8

Readme

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 (Source Code for 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 emotion @emotion/core
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,
    // Set menu text values
    menu: {
        title: 'Example Story',
        description: 'An Example Discoteque Story',
    },
    // 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