Sample game made on Discoteque engine https://deiru.moe/disco/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

379 lines
16 KiB

import React from 'react';
import { IGameState, setState, reducer, addKnownCharacter, setTask } from '@/lib/store';
import { INode, IActor, EngineConfig, ILineOption, ILocation, RendererFN, ToolbarOptionsFN } from 'discoteque/lib/engine/types';
import { setShowCustom } from 'discoteque/lib/engine/lib/store';
import skills, { GameSkills } from './lib/skills';
import Menu from './components/Menu';
import { awardSkill } from 'discoteque/lib/engine/lib/utils';
import { lockSkills } from 'discoteque/lib/engine/lib/store';
const nodes: INode<IGameState>[] = [
{
id: 'trying_out',
kind: 'node',
next: 'pre_choice',
lines: [
{ actorId: 'char_helper', text: "Hi!" } ,
(_, _g, dispatch) => {
dispatch(lockSkills());
dispatch(addKnownCharacter('char_helper')(dispatch));
return { actorId: 'char_helper', text: 'I\'m Helper! I help people play this demo. (Not really...)' };
},
{ actorId: 'char_helper', text: "Lets's start by advancing some lines!" },
{ actorId: 'char_helper', text: "This should be easy enough for you!" },
{ actorId: 'char_helper', text: "See?" },
{ text: "You think you're getting the hang of it!" },
{ actorId: 'char_helper', text: "Here's an exceptionally long line specifically designed to annoy me (and you!) and test us (and our patience!) on how long a single line could be. Turns out - pretty long!" },
{ actorId: 'char_helper', text: "Now let's try something harder. Left, or right?" },
{ actorId: 'char_helper', text: "This will use your two skills, tired and manic, representing two most important parts of your personality!" },
{ actorId: 'char_helper', text: "Failing the check on the left will allow you to go again." },
{ actorId: 'char_helper', text: "Failing the check on the right will instantly end the game." },
{ actorId: 'char_helper', text: "You could also try going outside." },
({ skillPoints }, _, dispatch) => {
awardSkill(dispatch, skillPoints);
dispatch(setTask("choice", { stages: [0] })(dispatch));
dispatch(setTask("outside", { stages: [0] })(dispatch));
return { actorId: 'char_helper', text: "By the way, here's a skill point just for you!" }
},
{ actorId: 'char_helper', text: "You can spend it using \"Skills\" menu in \"Options\", in the upper left corner! " },
{ actorId: 'char_helper', text: "Going outside will change your location, as displayed at the top." },
{ actorId: 'char_helper', text: "Current time is displayed in the upper right corner, along with current date (if you want it to!)" },
{ actorId: 'char_helper', text: "Turns out, talking takes time!" },
{ actorId: 'char_helper', text: "It has no logical consequence, but helps you as the player navigate the game better." },
]
},
{
kind: 'node',
id: 'failed_choice_tired',
next: 'pre_choice',
lines: [
{ text: '[FAIL] You tried very hard, but just weren\'t really in the mood' },
],
},
{
kind: 'node',
id: 'failed_choice_manic',
next: 'exit',
lines: [
{ text: '[FAIL] You were so overhyped you couldn\'t even say what you wanted to say' },
{ actorId: 'char_helper', text: 'Well, this is the end.' },
(_, _gameState, dispatch) => {
dispatch(setState({ isOver: true }))
},
],
},
{
id: 'pre_choice',
kind: 'node',
lines: [
(_, gameState) => {
const counterRight = gameState.visitedRight;
const counterLeft = gameState.visitedLeft;
const combinedCounter = counterRight + counterLeft;
const showExit = combinedCounter >= 3;
return { actorId: 'char_helper', text: 'So, left, or right?', options: [
{ text: '"I want to go outside!"', value: 'pre_outside' },
{ text: '"Left!"', value: 'left_choice', skill: { name: 'tired', difficulty: 17, failTo: 'failed_choice_tired' } },
{ text: '"Right!"', value: 'right_choice', skill: { name: 'manic', difficulty: 9, failTo: 'failed_choice_manic' } },
showExit && { 'text': 'Let me out, please', value: 'exit' }
].filter(x => x) as ILineOption[] };
}
]
},
{
id: 'pre_outside',
kind: 'node',
next: 'outside',
lines: [
(_, { beenOutside }) => beenOutside
? ({ actorId: 'char_helper', text: 'Ok, ok, I\'m not going anywhere' })
: ({ actorId: 'char_helper', text: 'Oki! Come find me inside the house if you wanna chat!', time: 10 }),
(_, { beenOutside }) => beenOutside
? { actorId: 'char_helper', text: 'I\'ll be here if you need me.' }
: { actorId: 'char_helper', text: 'Just be careful with the trap door. Many tried to find it, and many have failed.', time: 10 },
],
},
{
id: 'left_choice',
kind: 'node',
next: 'pre_choice',
lines: [
{ text: 'Pausing to think, you have decided to pick left option' },
(_, gameState) => {
const { visitedLeft } = gameState;
return visitedLeft ? (
{ actorId: 'char_helper', text: 'Huh. I think we\'ve been here before, no?' }
) : { actorId: 'char_helper', text: 'Good choice! As i\'ve said, picking left will lead you back.' };
},
({ skillPoints }, gameState, dispatch) => {
if ((gameState.visitedLeft + 1) === 3) {
awardSkill(dispatch, skillPoints);
}
dispatch(setState({ visitedLeft: gameState.visitedLeft + 1 }));
},
{ actorId: 'char_helper', text: 'Pick the other choice next time, maybe?' },
]
},
{
id: 'right_choice',
kind: 'node',
next: 'pre_choice',
lines: [
{ text: 'Without hesistation, you decided to go straight for the right option!' },
{ actorId: 'char_helper', text: 'Well, you passed this check. Try the other one now, and don\'t forget to fail this one.' },
(_, gameState, dispatch) => {
dispatch(setState({ visitedRight: gameState.visitedRight + 1 }));
}
]
},
{
id: 'exit',
kind: 'node',
lines: [
{ actorId: 'char_helper', text: 'Ok, it\'s time for us to part ways. We\'re done here' },
{ actorId: 'char_helper', text: 'Thanks, but this is the end.' },
(_engineState, _gameState, dispatch) => {
dispatch(setState({ isOver: true }))
},
]
},
{
id: 'mailbox',
kind: 'node',
next: 'outside',
lines: [
{ text: 'There\'s a letter inside.' },
{ text: 'You open it to take a look...' },
{ text: '"Welcome to Discoteque! An engine for games of adventure, danger, and low cunning!"' },
({ skillPoints }, gameState, dispatch) => {
if (!gameState.readMailbox) {
dispatch(setState({ readMailbox: true }));
awardSkill(dispatch, skillPoints);
}
return { text: `"No browser should be without one!" ${!gameState.readMailbox ? "[+1 Skill Point]" : ""}`, time: 5 };
}
]
},
{
id: 'trapdoor_end',
kind: 'node',
lines: [
{ text: 'You went to search for a trapdoor to the great underground empire!'},
(_engineState, _gameState, dispatch) => {
dispatch(setState({ isOver: true }));
return { text: 'It got dark and you got eaten by a grue.', time: 240 }
},
]
},
{
id: 'trapdoor_win',
kind: 'node',
lines: [
{ text: 'You went to search for a trapdoor to the great underground empire!', time: 240 },
{ text: 'You have found the coveted trap door! Many adventures await you...' },
(_engineState, _gameState, dispatch) => {
dispatch(setState({ isOver: true }));
return { text: 'But not in this demo!' }
},
]
},
{
id: 'about_trapdoor_fail',
kind: 'node', next: 'dialogue_helper',
lines: [
{ text: 'To ask this question you need to formulate it first...' },
{ text: 'But you can\'t be bothered to do it right now' },
],
},
{
'id': 'dialogue_helper', kind: 'node',
lines: [
(_, { talkedAboutTrapdoor }) => {
const trapdoorTalkSkill = talkedAboutTrapdoor ? 0 : 10;
return { text: 'What do you want to ask?', options: [
{ text: '"Tell me about that choice."', value: 'about_choice' },
{ text: '"Tell me about the trapdoor"', value: 'about_trapdoor', skill: {
name: 'tired', difficulty: trapdoorTalkSkill, failTo: 'about_trapdoor_fail'
} },
{ text: '"I\'m ready to pick"', value: 'pre_choice' },
{ text: '"Just came by to say hi!"', value: 'inside' },
] };
},
],
},
{
'id': 'about_choice', kind: 'node', next: 'dialogue_helper',
lines: [
(_, gameState, dispatch) => {
dispatch(setState({ askedAboutChoice: true }));
return gameState.askedAboutChoice
? { actorId: 'char_helper', text: 'Huh? I thought I already explained it to you...' }
: null;
},
{ actorId: 'char_helper', text: 'Well, there\'s not much to tell. It\'s just a demo Dale devised to showcase his engine.' },
{ actorId: 'char_helper', text: 'Don\' ask questions, just pick. Tell me when you\'re ready to pick.' },
]
},
{
id: 'about_trapdoor', kind: 'node', next: 'dialogue_helper',
lines: [
{ actorId: 'char_helper', text: 'Well, it\'s a cat in the box kind of thing.' },
{ actorId: 'char_helper', text: 'Some claim they have seen and used it. Others say it\'s a joke...' },
{ actorId: 'char_helper', text: 'What I know for sure is that those who aren\'t [MANIC] enough to take on the door are destined to fail.' },
{ actorId: 'char_helper', text: 'They will wander the house, going in circles, again and again...' },
{ actorId: 'char_helper', text: 'Searching for the door, in vain...' },
{ actorId: 'char_helper', text: 'Until it gets so, so dark that they get eaten by a fearsome grue, without even noticing!' },
{ actorId: 'char_helper', text: 'What a *grue*some end, right?' },
{ text: 'Helper starts laughing and can\'t help themselves. You don\'t appreciate the joke though.' },
{ actorId: 'char_helper', text: 'Well, enought about gruesome things, let\'s get back to the topic of chosing' },
({ skillPoints }, gameState, dispatch) => {
const { talkedAboutTrapdoor } = gameState;
if (!talkedAboutTrapdoor) {
dispatch(setState({ talkedAboutTrapdoor: true }));
awardSkill(dispatch, skillPoints);
dispatch(setTask('trapdoor', { stagesComplete: [0, 1] })(dispatch));
return { text: 'You feel like this will help you search for the trap door. [+1 Skill Point]' };
}
}
]
},
{
id: 'look_trapdoor', kind: 'node', next: 'inside',
lines: [
{ text: 'You scan the room for the presence of a trapdoor...' },
{ text: 'But nothing comes up!' },
{ text: 'It\'s almost as if it\'s there but every time you look at it moves to another place.' },
{ text: 'Or perhaps it is always just right out of the corner of your eye...' },
({ skillPoints }, gameState, dispatch) => {
const { lookedAtTrapdoor } = gameState;
if (lookedAtTrapdoor) {
return { text: 'Looks like you will have to search more thoroughly.' }
}
dispatch(setState({ lookedAtTrapdoor: true }));
awardSkill(dispatch, skillPoints)
return { text: 'You feel like you\'ll have an easier time looking for the trapdoor properly. [+1 Skill Point]' }
},
],
}
]
// { actorId: 'char_helper', text: '' },
const actors: IActor<IGameState>[] = [
{ 'id': 'char_helper', kind: 'actor', name: 'Helper', image: require('@/assets/images/user.png').default, lines: [
(_, { haveTalkedToHelper }, dispatch) => {
if (!haveTalkedToHelper) {
dispatch(setState({ haveTalkedToHelper: true }));
dispatch(setTask('outside', { stagesComplete: [0, 1, 2] })(dispatch));
dispatch(setTask('trapdoor', { stages: [0, 1, 2], stagesComplete: [0] })(dispatch));
}
},
{ actorId: 'char_helper', text: 'Back again, huh?' },
{ actorId: 'char_helper', text: 'Then I guess you\'re ready to pick right from left now.', },
], next: 'dialogue_helper' },
{ 'id': 'char_you', kind: 'actor', name: 'You', lines: [] },
]
const locations: ILocation<IGameState>[] = [
{ 'id': 'outside', kind: 'location', name: 'Great Outdoors', lines: [
(_, { beenOutside }, dispatch) => {
if (!beenOutside) {
dispatch(setState({ beenOutside: true }));
dispatch(setTask("outside", { stages: [0, 1], stagesComplete: [0] })(dispatch));
}
return { text: 'You are standing in an open field, west of house.' };
},
{ text: 'The sea of green extends into all directions, as far as your eye can see.' },
{ text: 'What now?', options: [
{ text: 'Look inside mailbox', value: 'mailbox' },
{ text: 'Go inside the house', value: 'inside' },
] },
] },
{ 'id': 'inside', kind: 'location', name: 'Inside House', lines: [
(_, { beenInside }, dispatch) => {
if (!beenInside) {
dispatch(setState({ beenInside: true }));
dispatch(setTask('outside', { stages: [0, 1, 2], stagesComplete: [0, 1] })(dispatch));
dispatch(setTask('trapdoor', { stages: [0, 2], stagesComplete: [0] })(dispatch));
}
},
{ text: 'You are standing inside a small wooden house. The setup feels familiar... ' },
{ text: 'You almost expect there to be a trapdoor to a great underground empire.' },
{ text: 'You can see Helper here as well. They are standing in the corner, gesturing you to come and talk to them.' },
(_, { lookedAtTrapdoor, talkedAboutTrapdoor }) => {
const searchDifficulties = [
lookedAtTrapdoor && 10,
talkedAboutTrapdoor && 5,
].filter(id => id) as Array<number>;
const searchDifficulty = searchDifficulties.reduce(
(acc, val) => acc - val,
25,
);
const searchEffects = [
lookedAtTrapdoor && '[+10 from scanning the room]',
talkedAboutTrapdoor && '[+5 from talking about trap door with Helper]',
].filter(id => id);
return { text: 'What will you do now?', options: [
{ text: 'Go back outside', value: 'outside' },
{ text: 'Talk to Helper', value: 'char_helper' },
{ text: 'Scan the room for the trapdoor', value: 'look_trapdoor' },
{
text: `Search thoroughly for the trapdoor ${searchEffects.join(' ')}`,
value: 'trapdoor_win',
skill: { name: 'manic', difficulty: searchDifficulty, failTo: 'trapdoor_end' }
},
] }
},
] },
];
interface IGameConfig extends EngineConfig<IGameState, undefined, GameSkills> {
}
const renderFn: RendererFN<IGameState, undefined, GameSkills> = (engine) => {
const nodeMap = engine.config.nodes.reduce((acc, node) => ({ ...acc, [node.id]: node }), {});
return (
<Menu nodeMap={nodeMap} />
);
};
const optionsFn: ToolbarOptionsFN<IGameState, undefined, GameSkills> = (_engine, _engineConfig, _gameState, dispatch) => {
return [
{ label: 'Journal', onClick: () => dispatch(setShowCustom(true)) },
]
}
export const config: IGameConfig = {
reducer: reducer,
startNode: 'trying_out',
nodes: [
...nodes,
...actors,
...locations,
],
chrono: {
time: 1435,
date: "10 September",
},
onDayCycle: (time) => {
const day = time.date?.substr(0, 2) || "10";
const restOfDate = time.date?.substr(2) || " September";
const dayNum = Number(day) !== NaN ? Number(day) : 10;
return {
time: 0,
date: `${dayNum + 1}${restOfDate}`,
}
},
skills,
skillPointsOnStart: 5,
customRenderer: renderFn,
customOptions: optionsFn,
skillDescriptoins: {
'manic': 'Out of this world at the highest pace',
'tired': 'Slow burning and contemplation',
}
}
export default config;