Compare commits
2 commits
67cf76dfc8
...
99bd85c5f8
Author | SHA1 | Date | |
---|---|---|---|
Alexander Yakovlev | 99bd85c5f8 | ||
Alexander Yakovlev | c0faddcf53 |
10
.babelrc
Normal file
10
.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-flow",
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-transform-flow-enums"
|
||||
],
|
||||
"targets": "> 0.25%, not dead"
|
||||
}
|
2
.flowconfig
Normal file
2
.flowconfig
Normal file
|
@ -0,0 +1,2 @@
|
|||
[options]
|
||||
enums=true
|
7
.gitattributes
vendored
Normal file
7
.gitattributes
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*.exe filter=lfs diff=lfs merge=lfs -text
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.xcf filter=lfs diff=lfs merge=lfs -text
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
*.webm filter=lfs diff=lfs merge=lfs -text
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/build/
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
/package-lock.json
|
||||
*.ink.json
|
36
chargen.ink
36
chargen.ink
|
@ -1,36 +0,0 @@
|
|||
LIST dice = (d2, d4, d6, d8, d10, d12, d20)
|
||||
|
||||
VAR strength = d2
|
||||
VAR will = d2
|
||||
VAR humanity = d2 # How toon-ish you look, from Roger Rabbit to Jessica Rabbit
|
||||
|
||||
VAR athletics = d2 # Jumping, Running
|
||||
VAR conceal = d2 # Sneaking, Disguise
|
||||
VAR first_aid = d2
|
||||
VAR driving = d2
|
||||
VAR performance = d2
|
||||
|
||||
VAR fighting = d2
|
||||
VAR shooting = d2
|
||||
VAR writing_magic = d2
|
||||
VAR comic_magic = d2
|
||||
|
||||
Step 1: main stats
|
||||
|
||||
The stats are dice rolls. You upgrade your dice rolls from d2 (a 1-2 coin toss) to d20 (1-20). Roll targets can range from easy (2) to almost impossible (19). You win rolls on a draw.
|
||||
|
||||
Strength: [-](./str_minus) {strength} [+](./str_plus)
|
||||
|
||||
Will: [-](./will_minus) {will} [+](./will_plus)
|
||||
|
||||
Humanity: [-](hum_minus) {humanity} [+](./hum_plus)
|
||||
|
||||
=== function minus(attr)
|
||||
if (attr > d2)
|
||||
~ attr = list_pop(dice)
|
||||
|
||||
|
||||
=== hum_minus
|
||||
~ minus(humanity)
|
||||
|
||||
Step 2: skills
|
18
eslint.config.js
Normal file
18
eslint.config.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
export default [
|
||||
{
|
||||
files: ["src/**/*.js"],
|
||||
"plugins": [
|
||||
"fb-flow"
|
||||
],
|
||||
rules: {
|
||||
semi: "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-case-declarations": "error",
|
||||
"prefer-const": "error",
|
||||
"use-flow-enums": "error",
|
||||
"flow-enums-default-if-possible": "error",
|
||||
"no-flow-enums-object-mapping": "error"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
64
game/functions.ink
Normal file
64
game/functions.ink
Normal file
|
@ -0,0 +1,64 @@
|
|||
=== function take(x)
|
||||
~ Inventory += x
|
||||
|
||||
=== function have(x)
|
||||
~ return Inventory ? x
|
||||
|
||||
=== function came_from(-> x)
|
||||
~ return TURNS_SINCE(x) == 0
|
||||
|
||||
// System: positioning things
|
||||
// Items can be put in and on places
|
||||
LIST Supporters = on_desk, on_floor, on_bed, under_bed, held, with_joe
|
||||
|
||||
=== function move_to_supporter(ref item_state, new_supporter) ===
|
||||
~ item_state -= LIST_ALL(Supporters)
|
||||
~ item_state += new_supporter
|
||||
|
||||
// System: Incremental knowledge.
|
||||
// Each list is a chain of facts. Each fact supersedes the fact before
|
||||
VAR knowledgeState = ()
|
||||
|
||||
=== function reached (x)
|
||||
~ return knowledgeState ? x
|
||||
|
||||
=== function between(x, y)
|
||||
~ return knowledgeState? x && not (knowledgeState ^ y)
|
||||
|
||||
== function current_quest_description()
|
||||
{current_quest:
|
||||
- "introquest": Your current quest will be displayed here.
|
||||
- "tea": You were asked to get some tea.
|
||||
}
|
||||
{current_quest2:
|
||||
- "find_xxx": XXX asked to find YYY. They should be to the north.
|
||||
}
|
||||
~ return
|
||||
|
||||
// Helper function: popping elements from lists
|
||||
=== function pop(ref list)
|
||||
~ temp x = LIST_MIN(list)
|
||||
~ list -= x
|
||||
~ return x
|
||||
|
||||
=== function reach(statesToSet)
|
||||
~ temp x = pop(statesToSet)
|
||||
{
|
||||
- not x:
|
||||
~ return false
|
||||
|
||||
- not reached(x):
|
||||
~ temp chain = LIST_ALL(x)
|
||||
~ temp statesGained = LIST_RANGE(chain, LIST_MIN(chain), x)
|
||||
~ knowledgeState += statesGained
|
||||
~ reach (statesToSet) // set any other states left to set
|
||||
~ return true // and we set this state, so true
|
||||
|
||||
- else:
|
||||
~ return false || reach(statesToSet)
|
||||
}
|
||||
|
||||
// Random scene switch, generates a new scene in the next room.
|
||||
=== scene_switch(-> next) ===
|
||||
~ random_scene = RANDOM(1,2)
|
||||
-> next
|
|
@ -1,12 +1,52 @@
|
|||
// Ink clicker code created by IFcoltransG
|
||||
// Released into public domain
|
||||
// May be used under the MIT No Attribution License
|
||||
# author: Александр Яковлев
|
||||
# title: chargen
|
||||
VAR ifid = "fill this pls"
|
||||
VAR background = "back.png"
|
||||
|
||||
LIST dice = d2, d4, d6, d8, d10, d12, d20
|
||||
|
||||
LIST journey_soundtrack_songs = Nascence, Apotheosis, Reclamation
|
||||
VAR a = Nascence
|
||||
VAR b = Apotheosis
|
||||
|
||||
VAR strength = d2
|
||||
VAR will = d2
|
||||
VAR humanity = d2 // How toon-ish you look, from Roger Rabbit to Jessica Rabbit
|
||||
|
||||
VAR athletics = d2 // Jumping, Running
|
||||
VAR conceal = d2 // Sneaking, Disguise
|
||||
VAR first_aid = d2
|
||||
VAR driving = d2
|
||||
VAR performance = d2
|
||||
|
||||
VAR fighting = d2
|
||||
VAR shooting = d2
|
||||
VAR writing_magic = d2
|
||||
VAR comic_magic = d2
|
||||
|
||||
-> go
|
||||
|
||||
Step 1: main stats
|
||||
|
||||
The stats are dice rolls. You upgrade your dice rolls from d2 (a 1-2 coin toss) to d20 (1-20). Roll targets can range from easy (2) to almost impossible (19). You win rolls on a draw.
|
||||
|
||||
Strength: [-](./str_minus) {strength} [+](./str_plus)
|
||||
|
||||
Will: [-](./will_minus) {will} [+](./will_plus)
|
||||
|
||||
Humanity: [-](hum_minus) {humanity} [+](./hum_plus)
|
||||
|
||||
=== function minus(attr)
|
||||
hi
|
||||
//if (attr > d2)
|
||||
//~ attr = list_pop(dice)
|
||||
|
||||
|
||||
=== hum_minus
|
||||
~ minus(humanity)
|
||||
|
||||
Step 2: skills
|
||||
|
||||
== go
|
||||
Here's Box A: <-clicker("a", a, -> go)
|
||||
. And here's Box B: <-clicker("b", b, -> go)
|
||||
|
@ -41,4 +81,4 @@ Here's Box A: <-clicker("a", a, -> go)
|
|||
~ val = LIST_MIN(LIST_ALL(val))
|
||||
- else:
|
||||
~ val += 1
|
||||
}
|
||||
}
|
53
game/pronouns.ink
Normal file
53
game/pronouns.ink
Normal file
|
@ -0,0 +1,53 @@
|
|||
LIST pronouns = he, she, it, they_singular, they_plural
|
||||
// Case constants: Nominative, Accusative, Dative, Genitive, Instrumental, Prepositional
|
||||
// (unused in English)
|
||||
CONST CNOM = "nom"
|
||||
CONST CACC = "acc"
|
||||
CONST CDAT = "dat"
|
||||
CONST CGEN = "gen"
|
||||
CONST CINS = "ins"
|
||||
CONST CPRE = "pre"
|
||||
CONST GEN_HE = "he"
|
||||
CONST GEN_SHE = "she"
|
||||
CONST GEN_IT = "it"
|
||||
CONST GEN_THEY = "they"
|
||||
VAR pronoun = pronouns.he
|
||||
// Choose a random pronoun - useful when we're skipping the intro.
|
||||
~ pronoun = LIST_RANDOM(pronouns)
|
||||
|
||||
=== function me ===
|
||||
{ pronoun:
|
||||
- they_plural:
|
||||
~ return "we"
|
||||
- else:
|
||||
~ return "me"
|
||||
}
|
||||
|
||||
=== function Me ===
|
||||
{ pronoun:
|
||||
- they_plural:
|
||||
~ return "We"
|
||||
- else:
|
||||
~ return "Me"
|
||||
}
|
||||
|
||||
// You(), you(), Yours(), yours(), spnoun() and splural() are unused in English.
|
||||
=== choose_pronouns ===
|
||||
Choose your pronouns:
|
||||
|
||||
* He/him
|
||||
~ pronoun = pronouns.he
|
||||
->intro
|
||||
* She/her
|
||||
~ pronoun = pronouns.she
|
||||
->intro
|
||||
* It/they
|
||||
~ pronoun = pronouns.it
|
||||
->intro
|
||||
* They/they (singular)
|
||||
~ pronoun = pronouns.they_singular
|
||||
->intro
|
||||
* They/they (plural)
|
||||
~ pronoun = pronouns.they_plural
|
||||
->intro
|
||||
|
74
html/index.html
Normal file
74
html/index.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rusalka</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="main.css" />
|
||||
</head>
|
||||
<body id="body">
|
||||
<div id="page" class="container">
|
||||
<div class="row sticky" id="current_quest">
|
||||
<div class="col-sm-3 col-md-2">
|
||||
<button id="undo" style="display:none" type="button" class="btn btn-warning">Undo</button>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-8 text-center">
|
||||
<h3 id="title"></h3>
|
||||
<p id="quest"></p>
|
||||
</div>
|
||||
<div class="col-sm-3 col-md-2 text-right">
|
||||
<button id="settings" type="button" class="btn btn-primary">Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<svg viewBox="0 0 480 480" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" id="picture">
|
||||
</svg>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div id="content"></div>
|
||||
<ul id="verbs" class="col-6">
|
||||
</ul>
|
||||
<ul id="options" class="col-6">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings_page" class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<h3>Settings</h3>
|
||||
<p>Text size, rem: <input id="font_size_control" type=number step=0.1 value=1 /></p>
|
||||
<p>Line spacing: <input id="line_height_control" type=number step="0.1" value=1.5 /></p>
|
||||
<p><h4>Saves</h4></p>
|
||||
<ul>
|
||||
<li>Automatic <a href="" download="autosave.json" id="download_save" class="btn btn-outline">Download</a></li>
|
||||
</ul>
|
||||
<p><button id="restart" type="button" class="btn btn-danger">Erase the save and restart the game</button></p>
|
||||
<p><button id="back" type="button" class="btn btn-primary">Save settings and return to the game</button></p>
|
||||
|
||||
<p>Image credit: Shurajo & AVALANCHE Game Studio (characters); CraftPix.net 2D Game Assets (background)</p>
|
||||
<p><a href="https://opengameart.org/content/boy">[1]</a>, <a href="https://opengameart.org/content/mermaid">[2]</a>, <a href="https://opengameart.org/content/pixel-ocean-and-sky-background">[3]</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="transcript_page" class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2"></div>
|
||||
<div class="col-sm-6 col-md-8 text-center">
|
||||
<h3>Transcript</h3>
|
||||
</div>
|
||||
<div class="col-sm-3 col-md-2 text-right">
|
||||
<button class="play" type="button" class="btn btn-primary">Back to game</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12" id="transcript"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CDN JS Libraries -->
|
||||
<script src="main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
43
package.json
Normal file
43
package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"scripts": {
|
||||
"publish": "zip -r build build && butler push build.zip oreolek/pretty-soldier-of-the-night:default",
|
||||
"build": "webpack --config ./webpack.config.js",
|
||||
"start": "webpack-dev-server --content-base public/ --inline --port 3001",
|
||||
"prod": "./node_modules/cross-env/src/bin/cross-env.js NODE_ENV=production ./node_modules/webpack/bin/webpack.js --mode=production",
|
||||
"dev": "./node_modules/cross-env/src/bin/cross-env.js NODE_ENV=development ./node_modules/webpack/bin/webpack.js",
|
||||
"watch": "./node_modules/webpack/bin/webpack.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.22.20",
|
||||
"@svgdotjs/svg.js": "^3.2.0",
|
||||
"@types/loader-utils": "^2.0.4",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"babel-loader": "^9.1.3",
|
||||
"bootstrap": "^5.3.2",
|
||||
"browser-sync": "^2.29.3",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.8.1",
|
||||
"flow-enums-runtime": "^0.0.6",
|
||||
"inkjs": "^2.2.2",
|
||||
"inklecate-loader": "^1.8.0",
|
||||
"jquery": "^3.7.1",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"postcss": "^8.4.30",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"sass": "^1.68.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"schema-utils": "^4.2.0",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.23.0",
|
||||
"@babel/core": "^7.23.0",
|
||||
"@babel/preset-flow": "^7.22.15",
|
||||
"@types/jquery": "^3.5.19",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"eslint-plugin-fb-flow": "^0.0.4"
|
||||
}
|
||||
}
|
4274
pnpm-lock.yaml
Normal file
4274
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
5
postcss.config.js
Normal file
5
postcss.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
'autoprefixer': {}
|
||||
}
|
||||
}
|
232
scss/style.scss
Normal file
232
scss/style.scss
Normal file
|
@ -0,0 +1,232 @@
|
|||
@import 'bootstrap/scss/functions';
|
||||
@import 'bootstrap/scss/variables';
|
||||
@import 'bootstrap/scss/variables-dark';
|
||||
@import 'bootstrap/scss/maps';
|
||||
@import 'bootstrap/scss/mixins';
|
||||
|
||||
$font-family-sans-serif: 'PT Sans','Open Sans',"Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$headings-font-family: "PT Sans Caption",$font-family-sans-serif;
|
||||
$font-family-base: $font-family-sans-serif;
|
||||
|
||||
$grey-color: #cc925d;
|
||||
$body-bg: #E4CDD4;
|
||||
$content-bg: rgba(202,158,185, 0.4);
|
||||
$body-color: #3A2D42;
|
||||
$link-color: #7D6393;
|
||||
$link-button-background: rgb(202, 158, 185);
|
||||
$link-button-background-hover: $body-color;
|
||||
$link-button-foreground: $body-color;
|
||||
$link-button-foreground-hover: $body-bg;
|
||||
$status-color: #7E5980;
|
||||
$btn-bg: grey;
|
||||
$btn-color: lighten($btn-bg, 50%);
|
||||
$secondary-bg: #F1EED9;
|
||||
|
||||
$primary: $link-color;
|
||||
$brand-primary: lighten($body-color, 20%);
|
||||
$brand-danger: darken($body-bg, 30%);
|
||||
|
||||
$waycolor: $link-color;
|
||||
$animation_duration: 2s;
|
||||
|
||||
$enable-rounded: true;
|
||||
$enable-shadows: false;
|
||||
$enable-gradients: false;
|
||||
$enable-transitions: false;
|
||||
$enable-hover-media-query: false;
|
||||
$enable-grid-classes: true;
|
||||
$enable-print-styles: true;
|
||||
|
||||
$ok-color: $link-color;
|
||||
$neutral-color: brown;
|
||||
$warning-color: darkred;
|
||||
|
||||
@import 'bootstrap/scss/maps';
|
||||
@import 'bootstrap/scss/mixins';
|
||||
@import 'bootstrap/scss/utilities';
|
||||
@import 'bootstrap/scss/root';
|
||||
@import 'bootstrap/scss/reboot';
|
||||
@import 'bootstrap/scss/type';
|
||||
@import 'bootstrap/scss/containers';
|
||||
@import 'bootstrap/scss/grid';
|
||||
@import 'bootstrap/scss/buttons';
|
||||
//@import 'bootstrap/scss/transitions';
|
||||
|
||||
h1,h2,h3 {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
h2 {
|
||||
color: #666;
|
||||
}
|
||||
#page {
|
||||
background: $content-bg;
|
||||
border-radius: 5px;
|
||||
margin-top: $font-size-base;
|
||||
padding-top: $font-size-base * 0.5;
|
||||
}
|
||||
#content {
|
||||
.subtitle {
|
||||
display: none;
|
||||
}
|
||||
p.old {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
body {
|
||||
/*
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("images/background.png");
|
||||
*/
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
#options,
|
||||
#options ul,
|
||||
#verbs {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
border-radius: 4px;
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
li {
|
||||
background-color: $link-button-background;
|
||||
padding: 0.5em;
|
||||
border-bottom: 1px solid #876;
|
||||
&,a {
|
||||
color: $link-button-foreground;
|
||||
}
|
||||
}
|
||||
li:hover {
|
||||
background-color: $link-button-background-hover;
|
||||
cursor: pointer;
|
||||
&,a {
|
||||
color: $link-button-foreground-hover;
|
||||
}
|
||||
}
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.subtitle {
|
||||
display: block;
|
||||
font-size: ($font-size-base * 0.9);
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.room-start {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.portrait {
|
||||
font-size: ($font-size-base * 1.5);
|
||||
}
|
||||
|
||||
.portraits {
|
||||
float: right;
|
||||
margin: ($font-size-base * 0.5);
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.bigger {
|
||||
font-size: ($font-size-base * 1.15);
|
||||
small {
|
||||
font-size: ($font-size-base * 0.8);
|
||||
}
|
||||
}
|
||||
.fright {
|
||||
float: right;
|
||||
}
|
||||
.disclaimer {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 20%;
|
||||
margin-right: 15%;
|
||||
width: 65%;
|
||||
font-size: ($font-size-base * 0.8);
|
||||
}
|
||||
.clearboth {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.spell {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: ($font-size-base * 1.1);
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
#settings_page,
|
||||
#transcript_page {
|
||||
display: none;
|
||||
}
|
||||
.btn-outline {
|
||||
border: 1px solid $btn-bg;
|
||||
}
|
||||
#background, #picture, .char {
|
||||
position: relative;
|
||||
height: 483px;
|
||||
max-width: 100%;
|
||||
}
|
||||
#background {
|
||||
background-size: cover;
|
||||
}
|
||||
.char {
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
.chartop {
|
||||
z-index: 2;
|
||||
}
|
||||
.charbottom {
|
||||
z-index: 1;
|
||||
}
|
||||
.chartop,
|
||||
.charbottom {
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
&.posleft {
|
||||
.chartop,
|
||||
.charbottom {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
&.posright {
|
||||
.chartop,
|
||||
.charbottom {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
&.poscenter {
|
||||
.chartop,
|
||||
.charbottom {
|
||||
left: 30%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#current_quest {
|
||||
background-color: rgba(255,255,255, 0.5);
|
||||
}
|
299
src/index.js
Normal file
299
src/index.js
Normal file
|
@ -0,0 +1,299 @@
|
|||
/* @flow */
|
||||
import * as jQuery from "jquery";
|
||||
const inkjs = require('inkjs').Story;
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
|
||||
/**
|
||||
* Path to your game's compiled JSON.
|
||||
* It is relative to index.html file.
|
||||
*/
|
||||
const entryPoint = require('../game/game.ink');
|
||||
|
||||
/**
|
||||
* You can change this function.
|
||||
* It's an easy way to define your own tags and text transformations.
|
||||
*/
|
||||
function transform (text: string) {
|
||||
text = text.replace('<st>', '<span class="subtitle">');
|
||||
text = text.replace('</st>', '</span>');
|
||||
text = text.replace('<ell>', '<span class="ellipsis">⏸️ </span>');
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* You don't need to change anything past this point.
|
||||
*/
|
||||
function saveChoice(index: number) {
|
||||
window['progress_steps']++;
|
||||
window['progress'].push(index);
|
||||
if (window['progress_steps'] % 5 === 0) {
|
||||
// 5 step autosave
|
||||
localStorage.setItem("progress_5", JSON.stringify(window['progress']));
|
||||
}
|
||||
if (window['progress_steps'] % 30 === 0) {
|
||||
// 30 step autosave
|
||||
localStorage.setItem("progress_30", JSON.stringify(window['progress']));
|
||||
}
|
||||
return localStorage.setItem("progress", JSON.stringify(window['progress']));
|
||||
};
|
||||
|
||||
function displayText(interactive = true) {
|
||||
let block, delay, html, i;
|
||||
const results = [];
|
||||
updateBgr();
|
||||
jQuery("#content p").addClass("old");
|
||||
//document.getElementById('content').innerHTML = ''
|
||||
const paragraphs = window['s'].ContinueMaximally().split("\n");
|
||||
if (interactive) {
|
||||
delay = 1000;
|
||||
}
|
||||
results.push((function() {
|
||||
let j, len;
|
||||
const results1 = [];
|
||||
for (j = 0, len = paragraphs.length; j < len; j++) {
|
||||
i = paragraphs[j];
|
||||
if (i !== "") {
|
||||
i = transform(i);
|
||||
html = jQuery.parseHTML(i);
|
||||
block = jQuery('<p>').html(html);
|
||||
if (interactive) {
|
||||
block.hide();
|
||||
}
|
||||
jQuery("#content").append(block);
|
||||
if (interactive) {
|
||||
block.fadeIn(delay);
|
||||
results1.push(delay += 500);
|
||||
} else {
|
||||
results1.push(void 0);
|
||||
}
|
||||
} else {
|
||||
results1.push(void 0);
|
||||
}
|
||||
}
|
||||
return results1;
|
||||
})());
|
||||
updateBgr();
|
||||
return results;
|
||||
};
|
||||
|
||||
function continueToNextChoice (s: any) {
|
||||
let choice, j, len, ref;
|
||||
displayText(true);
|
||||
jQuery("#options").html("").hide();
|
||||
jQuery(".options").html("");
|
||||
jQuery("#verbs").html("").hide();
|
||||
const verbs = {};
|
||||
let mode = 'menu';
|
||||
//let quest = s.EvaluateFunction('current_quest_description', [], true);
|
||||
//jQuery("#quest").html(quest.output);
|
||||
if (window["picture"] !== undefined && s.currentTags.length > 0) {
|
||||
// Scanning "characters" tag for sprites to show
|
||||
// EXAMPLE: # characters: boy.happy, mermaid.rest
|
||||
s.currentTags.forEach(function(tag) {
|
||||
const characters = tag.match(/^characters:\s*(.*)/);
|
||||
if (characters !== null && characters.length > 0) {
|
||||
const charlist = characters[1].split(",");
|
||||
charlist.forEach(function(char) {
|
||||
const a = char.split(".");
|
||||
let character, pose;
|
||||
if (a[0] !== undefined) {
|
||||
character = a[0];
|
||||
}
|
||||
if (a[1] !== undefined) {
|
||||
pose = a[1];
|
||||
}
|
||||
if (pose === "") {
|
||||
pose = "rest";
|
||||
}
|
||||
if (window["characters"] === undefined) {
|
||||
window["characters"] = {};
|
||||
}
|
||||
if (window["characters"][character] !== undefined) {
|
||||
window["characters"][character].setPose(pose);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (
|
||||
s.currentTags.includes('choices: parser')
|
||||
|| s.currentChoices[0].tags.includes('parser')
|
||||
|| s.currentChoices[0].text.match("Осмотреть себя")
|
||||
) {
|
||||
mode = 'parser';
|
||||
}
|
||||
if (s.currentChoices.length > 0) {
|
||||
ref = s.currentChoices;
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
choice = ref[j];
|
||||
let text = transform(choice.text)
|
||||
if (mode === 'parser') {
|
||||
text = text.replace("> ", "")
|
||||
let verb = text;
|
||||
if (text.split(" ").length > 1) {
|
||||
verb = text.split(" ")[0];
|
||||
}
|
||||
let id = Math.round(Math.random() * 100000);
|
||||
if (verbs[verb] === undefined) {
|
||||
verbs[verb] = id;
|
||||
jQuery("#verbs").append(`<li><a href='#' id='verb-${id}' data-verbid='${id}'>${verb}</a></li>`);
|
||||
} else {
|
||||
id = verbs[verb];
|
||||
}
|
||||
if (jQuery(`#options-${id}`).length === 0) {
|
||||
jQuery("#options").append(`<ul id="options-${id}" class="options"></ul>`);
|
||||
}
|
||||
jQuery(`#options-${id}`).append(`<li><a href='#' id='choice-${choice.index}' data-index=${choice.index}>${text}</a></li>`);
|
||||
} else {
|
||||
jQuery(`#options`).append(`<li><a href='#' id='choice-${choice.index}' data-index=${choice.index}>${text}</a></li>`);
|
||||
}
|
||||
}
|
||||
if (!jQuery("html").is(":animated")) {
|
||||
if (mode === 'parser') {
|
||||
jQuery("#verbs").fadeIn(500);
|
||||
} else {
|
||||
jQuery("#options").fadeIn(500);
|
||||
}
|
||||
}
|
||||
if (mode === 'parser') {
|
||||
jQuery("#options").show();
|
||||
jQuery(".options:visible").hide();
|
||||
}
|
||||
}
|
||||
const scrollTo = jQuery('#options').offset().top;
|
||||
if (scrollTo > 0 && window['progress'].length > 0 && !jQuery('html').is(':animated') && !jQuery('body').is(':animated')) {
|
||||
return jQuery('html, body').animate({
|
||||
scrollTop: scrollTo
|
||||
}, 800);
|
||||
}
|
||||
};
|
||||
|
||||
function loadGame (s: any) {
|
||||
let index, j, len;
|
||||
document.getElementById("content").innerHTML = ""
|
||||
document.getElementById("options").innerHTML = ""
|
||||
const ref = window['progress'];
|
||||
const results = [];
|
||||
if (ref.length > 0) {
|
||||
window['progress_steps'] = ref.length;
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
index = ref[j];
|
||||
displayText(false);
|
||||
results.push(s.ChooseChoiceIndex(index));
|
||||
}
|
||||
} else {
|
||||
continueToNextChoice(s);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const data = entryPoint.storyContent;
|
||||
const progress = localStorage.getItem("progress");
|
||||
if (progress != null) {
|
||||
window['progress'] = JSON.parse(progress);
|
||||
} else {
|
||||
window['progress_steps'] = 0;
|
||||
window['progress'] = [];
|
||||
}
|
||||
window["picture"] = SVG().addTo("#picture");
|
||||
window["picture"].image('./images/back.png').id("background").move(0,0).css("width", "100%").css("height", "100%");
|
||||
window['s'] = new inkjs(data)
|
||||
window['s'].onError = function(error){
|
||||
alert(error);
|
||||
};
|
||||
if (window['s'].globalTags) {
|
||||
const title = window['s'].globalTags['title'];
|
||||
if (title !== undefined) {
|
||||
document.getElementById('title').innerHTML = title;
|
||||
document.getElementsByTagName('title')[0].innerHTML = title;
|
||||
}
|
||||
}
|
||||
if (window['progress'].length > 0) {
|
||||
loadGame(window['s']);
|
||||
}
|
||||
continueToNextChoice(window['s']);
|
||||
|
||||
jQuery(document).on('click', "#restart", function() {
|
||||
localStorage.setItem("progress", '[]');
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
jQuery(document).on('click', "#verbs a", function() {
|
||||
jQuery(".options:visible").hide();
|
||||
const verb = jQuery(this).data('verbid');
|
||||
jQuery(`#options-${verb}`).show();
|
||||
});
|
||||
jQuery(document).on('click', "#verbs li", function() {
|
||||
jQuery(this).find('a').click();
|
||||
});
|
||||
|
||||
jQuery(document).on('click', "#undo", function() {
|
||||
jQuery("#undo").hide();
|
||||
window['progress'].pop()
|
||||
localStorage.setItem("progress", JSON.stringify(window['progress']));
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
function choose(index) {
|
||||
window['s'].ChooseChoiceIndex(index);
|
||||
jQuery("#undo").show()
|
||||
saveChoice(index);
|
||||
continueToNextChoice(window['s']);
|
||||
}
|
||||
|
||||
jQuery(document).on('click', "#settings", function() {
|
||||
jQuery("#page").hide();
|
||||
jQuery("#settings_page").show();
|
||||
return false;
|
||||
});
|
||||
jQuery(document).on('click', "#options li a", function() {
|
||||
choose(jQuery(this).data("index"));
|
||||
return false;
|
||||
});
|
||||
jQuery(document).on('click', "#options li", function() {
|
||||
choose(jQuery(this).find('a').data("index"));
|
||||
return false;
|
||||
});
|
||||
jQuery(document).on('change', "#font_size_control", function() {
|
||||
jQuery("body").css("font-size", jQuery("#font_size_control").val()+"rem")
|
||||
return false;
|
||||
});
|
||||
jQuery(document).on('change', "#line_height_control", function() {
|
||||
const height = jQuery("#line_height_control").val();
|
||||
jQuery("body").css("line-height", height.toString())
|
||||
return false;
|
||||
});
|
||||
jQuery(document).on('click', "#download_save", function() {
|
||||
const progress = localStorage.getItem("progress")
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(progress));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute("download", "save.json");
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
// TODO keyboard navigation
|
||||
jQuery(document).on('keydown', function(key: any) {
|
||||
key = key.key
|
||||
if (key === '0') {
|
||||
key = 10;
|
||||
} else {
|
||||
key = parseInt(key);
|
||||
}
|
||||
if (key > 0) {
|
||||
if (jQuery("#verbs").is(":visible")) {
|
||||
jQuery(`#verbs li:nth-child(${key}) a`).first().trigger("click")
|
||||
} else {
|
||||
jQuery(`.options:visible li:nth-child(${key}) a`).first().trigger("click")
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
function updateBgr() {
|
||||
jQuery('#background').attr("href", "images/" + window['s'].variablesState['background'])
|
||||
}
|
60
webpack.config.js
Normal file
60
webpack.config.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const path = require('path')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
||||
entry: [
|
||||
'./src/index.js',
|
||||
'./scss/style.scss'
|
||||
],
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin(),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: './html/', to: '' },
|
||||
//{ from: './images/', to: 'images' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
devtool: process.env.NODE_ENV === 'production' ? false : 'inline-source-map',
|
||||
performance: {
|
||||
maxEntrypointSize: 900000,
|
||||
maxAssetSize: 900000
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ink$/,
|
||||
use: require.resolve('inklecate-loader'),
|
||||
},
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
exclude: /(node_modules)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
// Translates CSS into CommonJS
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
// Compiles Sass to CSS
|
||||
'sass-loader'
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'build')
|
||||
},
|
||||
watchOptions: {
|
||||
// for some systems, watching many files can result in a lot of CPU or memory usage
|
||||
// https://webpack.js.org/configuration/watch/#watchoptionsignored
|
||||
// don't use this pattern, if you have a monorepo with linked packages
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue