|
|
@@ -0,0 +1,344 @@ |
|
|
|
import { IGameState, setState, reducer } from '@/lib/store'; |
|
|
|
import { INode, IActor, EngineConfig, ILineOption, ILocation } from 'discoteque/lib/engine/types'; |
|
|
|
import { toast } from 'react-toastify'; |
|
|
|
|
|
|
|
import skills, { GameSkills } from './lib/skills'; |
|
|
|
import { setSkillpoints, lockSkills, setSkillMenu } from 'discoteque/lib/engine/lib/store'; |
|
|
|
import { Dispatch } from 'redux'; |
|
|
|
|
|
|
|
const awardSkill = (dispatch: Dispatch, skillPoints: number) => { |
|
|
|
dispatch(setSkillpoints(skillPoints + 1)); |
|
|
|
toast("[Skill +1] You have been awarded a skill point!", { |
|
|
|
onClick: () => dispatch(setSkillMenu(true)), |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
const nodes: INode<IGameState>[] = [ |
|
|
|
{ |
|
|
|
id: 'trying_out', |
|
|
|
kind: 'node', |
|
|
|
next: 'pre_choice', |
|
|
|
lines: [ |
|
|
|
{ actorId: 'char_helper', text: "Hi!" }, |
|
|
|
(_, _g, dispatch) => dispatch(lockSkills()) && null, |
|
|
|
{ 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); |
|
|
|
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); |
|
|
|
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: [ |
|
|
|
{ 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: [ |
|
|
|
(_, _gameState, dispatch) => { |
|
|
|
dispatch(setState({ beenOutside: true })); |
|
|
|
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: [ |
|
|
|
{ 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> { |
|
|
|
} |
|
|
|
|
|
|
|
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, |
|
|
|
skillDescriptoins: { |
|
|
|
'manic': 'Out of this world at the highest pace', |
|
|
|
'tired': 'Slow burning and contemplation', |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export default config; |