mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Merge 2fa40fb3a6
into a1c6a99df0
This commit is contained in:
commit
a1c62cc069
1
.gitignore
vendored
1
.gitignore
vendored
@ -48,6 +48,7 @@ gulp/runnable-texturepacker.jar
|
|||||||
tmp_standalone_files
|
tmp_standalone_files
|
||||||
tmp_standalone_files_china
|
tmp_standalone_files_china
|
||||||
tmp_standalone_files_wegame
|
tmp_standalone_files_wegame
|
||||||
|
contributors.json
|
||||||
|
|
||||||
# Local config
|
# Local config
|
||||||
config.local.js
|
config.local.js
|
||||||
|
180
gulp/contributors.js
Normal file
180
gulp/contributors.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const fsp = require("fs/promises");
|
||||||
|
const path = require("path");
|
||||||
|
const nodeFetch = require("node-fetch");
|
||||||
|
|
||||||
|
const APILink = "https://api.github.com/repos/tobspr/shapez.io";
|
||||||
|
const numOfReqPerPage = 100; // Max is 100, change to something lower if loads are too long
|
||||||
|
const personalAccessToken = "PUT TOKEN HERE";
|
||||||
|
|
||||||
|
const JSONFileLocation = path.join(__dirname, "..", "contributors.json");
|
||||||
|
|
||||||
|
function fetch(url) {
|
||||||
|
return nodeFetch(url, {
|
||||||
|
headers: [["Authorization", "token " + personalAccessToken]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function JSONFileExists() {
|
||||||
|
return fs.existsSync(JSONFileLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJSONFile() {
|
||||||
|
return fsp.readFile(JSONFileLocation, { encoding: "utf-8" }).then(res => JSON.parse(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJSONFile(translators, contributors) {
|
||||||
|
return fsp.writeFile(
|
||||||
|
JSONFileLocation,
|
||||||
|
JSON.stringify({
|
||||||
|
lastUpdatedAt: Date.now(),
|
||||||
|
//@ts-ignore
|
||||||
|
translators: Array.from(translators, ([username, value]) => ({ username, value })),
|
||||||
|
//@ts-ignore
|
||||||
|
contributors: Array.from(contributors, ([username, value]) => ({ username, value })),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalNumOfPRs() {
|
||||||
|
return fetch(APILink + "/pulls?state=closed&per_page=1&page=0").then(res => {
|
||||||
|
// This part is very messy
|
||||||
|
let link = res.headers.get("Link");
|
||||||
|
link = link.slice(link.indexOf(",") + 1); // Gets rid of the first "next" link
|
||||||
|
return parseInt(link.slice(link.indexOf("&page=") + 6, link.indexOf(">")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldDownloadPRs() {
|
||||||
|
if (!JSONFileExists()) return Promise.resolve(true);
|
||||||
|
else {
|
||||||
|
return readJSONFile().then(res => Date.now() - res.lastUpdatedAt > 1000 * 60 * 30); // once every 30 min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function PRIsTranslation(link) {
|
||||||
|
// We just say that if a PR only edits translation files, its a translation, all others are something else
|
||||||
|
return fetch(link + "/files")
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
if (res.message) {
|
||||||
|
console.log("GITHUB HAS RATE-LIMITED THIS MACHINE, PLEASE WAIT ABOUT AN HOUR");
|
||||||
|
throw new Error("rate-limit reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let file of res) {
|
||||||
|
if (!file.filename.startsWith("translations/")) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sortPRs(prs) {
|
||||||
|
const contributors = new Map();
|
||||||
|
const translators = new Map();
|
||||||
|
|
||||||
|
const clearLine = () => {
|
||||||
|
process.stdout.moveCursor(0, -1); // up one line
|
||||||
|
process.stdout.clearLine(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < prs.length; i++) {
|
||||||
|
let map;
|
||||||
|
|
||||||
|
if (await PRIsTranslation(prs[i].url)) map = translators;
|
||||||
|
else map = contributors;
|
||||||
|
|
||||||
|
if (!map.has(prs[i].username)) map.set(prs[i].username, []);
|
||||||
|
|
||||||
|
map.get(prs[i].username).push(prs[i]);
|
||||||
|
|
||||||
|
if (i !== 0) clearLine();
|
||||||
|
console.log(`PR's Downloaded: ${i} out of ${prs.length} (${prs.length - i} left)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLine();
|
||||||
|
console.log("Downloaded All PR's");
|
||||||
|
|
||||||
|
return {
|
||||||
|
contributors,
|
||||||
|
translators,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reqPage(page) {
|
||||||
|
return fetch(APILink + `/pulls?state=closed&per_page=${numOfReqPerPage}&page=${page}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(async res => {
|
||||||
|
const prs = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (!res[i].merged_at) {
|
||||||
|
continue;
|
||||||
|
} // Skip files that were never merged
|
||||||
|
|
||||||
|
const prInfo = {
|
||||||
|
username: res[i].user.login,
|
||||||
|
html_url: res[i].html_url,
|
||||||
|
url: res[i].url,
|
||||||
|
title: res[i].title,
|
||||||
|
};
|
||||||
|
|
||||||
|
prs.push(prInfo);
|
||||||
|
// if (await PRIsTranslation(res[i].url)) {
|
||||||
|
// translations.push(prInfo);
|
||||||
|
// } else {
|
||||||
|
// others.push(prInfo);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return prs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAllPrs() {
|
||||||
|
const totalNumOfPrs = await getTotalNumOfPRs();
|
||||||
|
const numOfPageReqs = Math.ceil(totalNumOfPrs / numOfReqPerPage);
|
||||||
|
|
||||||
|
const prs = [];
|
||||||
|
|
||||||
|
for (let i = 1; i < numOfPageReqs + 1; i++) {
|
||||||
|
prs.push(...(await reqPage(i))); // Yes promise.all would be good, but I wanna keep them in order (at least for now)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tryToUpdateContributors() {
|
||||||
|
if (personalAccessToken === "PUT TOKEN HERE") {
|
||||||
|
console.log("A github token was not provided, writing default contributors.json");
|
||||||
|
await writeJSONFile([], []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await shouldDownloadPRs())) {
|
||||||
|
console.log("Not updating contributors to prevent github API from rate-limiting this computer");
|
||||||
|
console.log("If you wish to force a contributors update, use contributors.build.force");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateContributors();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateContributors() {
|
||||||
|
const allPrs = await downloadAllPrs();
|
||||||
|
console.log(`Discovered ${allPrs.length} PRs`);
|
||||||
|
|
||||||
|
const sorted = await sortPRs(allPrs);
|
||||||
|
|
||||||
|
await writeJSONFile(sorted.translators, sorted.contributors);
|
||||||
|
console.log("Wrote JSON File");
|
||||||
|
}
|
||||||
|
|
||||||
|
function gulpTaskContributors($, gulp) {
|
||||||
|
gulp.task("contributors.build", cb => tryToUpdateContributors().then(() => cb));
|
||||||
|
gulp.task("contributors.build.force", cb => updateContributors().then(() => cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
gulpTaskContributors,
|
||||||
|
};
|
@ -74,6 +74,8 @@ releaseUploader.gulptasksReleaseUploader($, gulp, buildFolder);
|
|||||||
const translations = require("./translations");
|
const translations = require("./translations");
|
||||||
translations.gulptasksTranslations($, gulp, buildFolder);
|
translations.gulptasksTranslations($, gulp, buildFolder);
|
||||||
|
|
||||||
|
const contributors = require("./contributors");
|
||||||
|
contributors.gulpTaskContributors($, gulp, buildFolder);
|
||||||
///////////////////// BUILD TASKS /////////////////////
|
///////////////////// BUILD TASKS /////////////////////
|
||||||
|
|
||||||
// Cleans up everything
|
// Cleans up everything
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"@babel/preset-env": "^7.5.4",
|
"@babel/preset-env": "^7.5.4",
|
||||||
"@types/cordova": "^0.0.34",
|
"@types/cordova": "^0.0.34",
|
||||||
"@types/filesystem": "^0.0.29",
|
"@types/filesystem": "^0.0.29",
|
||||||
"@types/node": "^12.7.5",
|
"@types/node": "^15.12.4",
|
||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
"audiosprite": "^0.7.2",
|
"audiosprite": "^0.7.2",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
@ -41,6 +41,7 @@
|
|||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"markdown-loader": "^5.1.0",
|
"markdown-loader": "^5.1.0",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
"node-sri": "^1.1.1",
|
"node-sri": "^1.1.1",
|
||||||
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
"phonegap-plugin-mobile-accessibility": "^1.0.5",
|
||||||
"postcss": ">=5.0.0",
|
"postcss": ">=5.0.0",
|
||||||
|
@ -994,11 +994,16 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz"
|
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz"
|
||||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^12.7.5":
|
"@types/node@*":
|
||||||
version "12.7.5"
|
version "12.7.5"
|
||||||
resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz"
|
resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz"
|
||||||
integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==
|
integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==
|
||||||
|
|
||||||
|
"@types/node@^15.12.4":
|
||||||
|
version "15.12.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.4.tgz#e1cf817d70a1e118e81922c4ff6683ce9d422e26"
|
||||||
|
integrity sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==
|
||||||
|
|
||||||
"@types/q@^1.5.1":
|
"@types/q@^1.5.1":
|
||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz"
|
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz"
|
||||||
@ -8318,6 +8323,11 @@ no-case@^2.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lower-case "^1.1.1"
|
lower-case "^1.1.1"
|
||||||
|
|
||||||
|
node-fetch@^2.6.1:
|
||||||
|
version "2.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||||
|
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
|
|
||||||
node-libs-browser@^2.2.1:
|
node-libs-browser@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz"
|
resolved "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
@import "states/mobile_warning";
|
@import "states/mobile_warning";
|
||||||
@import "states/changelog";
|
@import "states/changelog";
|
||||||
@import "states/puzzle_menu";
|
@import "states/puzzle_menu";
|
||||||
|
@import "states/credits.scss";
|
||||||
|
|
||||||
@import "ingame_hud/buildings_toolbar";
|
@import "ingame_hud/buildings_toolbar";
|
||||||
@import "ingame_hud/building_placer";
|
@import "ingame_hud/building_placer";
|
||||||
|
35
src/css/states/credits.scss
Normal file
35
src/css/states/credits.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#state_CreditsState {
|
||||||
|
.container .content {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.tobspr .title {
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
@include S(margin-top, 10px);
|
||||||
|
@include S(padding, 5px);
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
background: rgba(0, 10, 20, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include Heading;
|
||||||
|
|
||||||
|
color: #555;
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.people {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.5s ease-out;
|
||||||
|
}
|
||||||
|
.people > :first-child {
|
||||||
|
@include S(margin-top, 8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import { RestrictionManager } from "./core/restriction_manager";
|
|||||||
import { PuzzleMenuState } from "./states/puzzle_menu";
|
import { PuzzleMenuState } from "./states/puzzle_menu";
|
||||||
import { ClientAPI } from "./platform/api";
|
import { ClientAPI } from "./platform/api";
|
||||||
import { LoginState } from "./states/login";
|
import { LoginState } from "./states/login";
|
||||||
|
import { CreditsState } from "./states/credits";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
|
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
|
||||||
@ -165,6 +166,7 @@ export class Application {
|
|||||||
ChangelogState,
|
ChangelogState,
|
||||||
PuzzleMenuState,
|
PuzzleMenuState,
|
||||||
LoginState,
|
LoginState,
|
||||||
|
CreditsState,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < states.length; ++i) {
|
for (let i = 0; i < states.length; ++i) {
|
||||||
|
@ -35,6 +35,14 @@ export class AboutState extends TextualGameState {
|
|||||||
{ preventClick: true }
|
{ preventClick: true }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stateChangers = this.htmlElement.querySelectorAll("a[state]");
|
||||||
|
console.log(stateChangers);
|
||||||
|
stateChangers.forEach(element => {
|
||||||
|
this.trackClicks(element, () => this.moveToState(element.getAttribute("state")), {
|
||||||
|
preventClick: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultPreviousState() {
|
getDefaultPreviousState() {
|
||||||
|
112
src/js/states/credits.js
Normal file
112
src/js/states/credits.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
|
import { contributors, translators } from "../../../contributors.json";
|
||||||
|
import { T } from "../translations";
|
||||||
|
|
||||||
|
export class CreditsState extends TextualGameState {
|
||||||
|
constructor() {
|
||||||
|
super("CreditsState");
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateHeaderTitle() {
|
||||||
|
return T.credits.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMainContentHTML() {
|
||||||
|
return `
|
||||||
|
<div class="section tobspr">
|
||||||
|
<button class="title">${T.credits.tobspr}</button>
|
||||||
|
<div class="people">
|
||||||
|
<div class="entry">${this.linkify("https://github.com/tobspr", "Tobias Springer")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="special-shout-out section">
|
||||||
|
<button class="title">${T.credits.specialThanks.title}:</button>
|
||||||
|
<div class="people">
|
||||||
|
<div class="entry">${this.linkify(
|
||||||
|
"https://soundcloud.com/pettersumelius",
|
||||||
|
"Peppsen"
|
||||||
|
)} - ${T.credits.specialThanks.descriptions.peppsen}</div>
|
||||||
|
<div class="entry">Add some other people here (Whoever you think deserves it)</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="translators section">
|
||||||
|
<button class="title">${T.credits.translators.title}:</button>
|
||||||
|
<div class="people">
|
||||||
|
${this.getGithubHTML(translators)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contributors section">
|
||||||
|
<button class="title">${T.credits.contributors.title}:</button>
|
||||||
|
<div class="people">
|
||||||
|
${this.getGithubHTML(contributors)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkify(href, text) {
|
||||||
|
return `<a href="${href}" target="_blank">${text}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGithubHTML(list) {
|
||||||
|
let html = "";
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
html += `
|
||||||
|
${i === 0 ? "" : "<br>"}
|
||||||
|
<div class="entry">
|
||||||
|
${this.linkify(`https://github.com/${list[i].username}`, list[i].username)}: <br> ${list[
|
||||||
|
i
|
||||||
|
].value
|
||||||
|
.map(pr => {
|
||||||
|
return `${this.linkify(pr.html_url, this.getGoodTitle(pr.title))}, `;
|
||||||
|
})
|
||||||
|
.reduce((p, c) => p + c)
|
||||||
|
.slice(0, -2)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGoodTitle(title) {
|
||||||
|
if (title.endsWith(".")) return title.slice(0, -1);
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnter() {
|
||||||
|
// Allow the user to close any section by clicking on the title
|
||||||
|
const buttons = this.htmlElement.querySelectorAll("button.title");
|
||||||
|
buttons.forEach(button => {
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
//@ts-ignore
|
||||||
|
const people = button.nextElementSibling;
|
||||||
|
|
||||||
|
button.addEventListener("click", e => {
|
||||||
|
if (people.style.maxHeight) {
|
||||||
|
people.style.maxHeight = null;
|
||||||
|
} else {
|
||||||
|
people.style.maxHeight = people.scrollHeight + "px";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set them to open at the start
|
||||||
|
people.style.maxHeight = people.scrollHeight + "px";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Link stuff
|
||||||
|
const links = this.htmlElement.querySelectorAll("a[href]");
|
||||||
|
links.forEach(link => {
|
||||||
|
this.trackClicks(
|
||||||
|
link,
|
||||||
|
() => this.app.platformWrapper.openExternalLink(link.getAttribute("href")),
|
||||||
|
{ preventClick: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPreviousState() {
|
||||||
|
return "AboutState";
|
||||||
|
}
|
||||||
|
}
|
@ -1368,6 +1368,8 @@ about:
|
|||||||
body: >-
|
body: >-
|
||||||
This game is open source and developed by <a href="https://github.com/tobspr" target="_blank">Tobias Springer</a> (this is me).<br><br>
|
This game is open source and developed by <a href="https://github.com/tobspr" target="_blank">Tobias Springer</a> (this is me).<br><br>
|
||||||
|
|
||||||
|
Have a look at the <a state="CreditsState">Credits</a> to see every who has helped out by translating or contributing.<br><br>
|
||||||
|
|
||||||
If you want to contribute, check out <a href="<githublink>" target="_blank">shapez.io on GitHub</a>.<br><br>
|
If you want to contribute, check out <a href="<githublink>" target="_blank">shapez.io on GitHub</a>.<br><br>
|
||||||
|
|
||||||
This game wouldn't have been possible without the great Discord community around my games - You should really join the <a href="<discordlink>" target="_blank">Discord server</a>!<br><br>
|
This game wouldn't have been possible without the great Discord community around my games - You should really join the <a href="<discordlink>" target="_blank">Discord server</a>!<br><br>
|
||||||
@ -1379,6 +1381,18 @@ about:
|
|||||||
changelog:
|
changelog:
|
||||||
title: Changelog
|
title: Changelog
|
||||||
|
|
||||||
|
credits:
|
||||||
|
title: Credits
|
||||||
|
tobspr: Main Programer and Artist
|
||||||
|
specialThanks:
|
||||||
|
title: Special Thanks To
|
||||||
|
descriptions:
|
||||||
|
peppsen: Created the awesome soundtrack
|
||||||
|
translators:
|
||||||
|
title: Translators
|
||||||
|
contributors:
|
||||||
|
title: Contributors
|
||||||
|
|
||||||
demo:
|
demo:
|
||||||
features:
|
features:
|
||||||
restoringGames: Restoring savegames
|
restoringGames: Restoring savegames
|
||||||
|
Loading…
Reference in New Issue
Block a user