Compare commits

...

2 commits

Author SHA1 Message Date
Alexander Yakovlev 99bd85c5f8 WIP wrapper 2024-01-09 13:30:37 +07:00
Alexander Yakovlev c0faddcf53 fix syntax 2024-01-09 12:57:51 +07:00
17 changed files with 5190 additions and 40 deletions

10
.babelrc Normal file
View 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
View file

@ -0,0 +1,2 @@
[options]
enums=true

7
.gitattributes vendored Normal file
View 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
View file

@ -0,0 +1,5 @@
/build/
/node_modules
/yarn.lock
/package-lock.json
*.ink.json

View file

@ -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
View 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
View 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

View file

@ -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
View 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
View 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 &amp; 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
View 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

File diff suppressed because it is too large Load diff

5
postcss.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
plugins: {
'autoprefixer': {}
}
}

232
scss/style.scss Normal file
View 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
View 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">⏸️&nbsp;</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
View 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/,
},
}