1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Inital Update

This commit is contained in:
Edward Badel 2021-06-25 17:41:39 -04:00
parent 5074727efa
commit 5dd2f5b0fe
11 changed files with 334 additions and 6 deletions

1
.gitignore vendored
View File

@ -48,6 +48,7 @@ gulp/runnable-texturepacker.jar
tmp_standalone_files
tmp_standalone_files_china
tmp_standalone_files_wegame
contributors.json
# Local config
config.local.js

156
gulp/contributors.js Normal file
View File

@ -0,0 +1,156 @@
const fs = require("fs");
const fsp = require("fs/promises");
const path = require("path");
const fetch = 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 JSONFileLocation = path.join(__dirname, "..", "contributors.json");
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) {
// return true;
// 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();
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]);
}
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 - 1; 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,
user_avatar: res[i].user.avatar_url,
title: res[i].title,
time: res[i].createdAt,
};
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 = 0; i < numOfPageReqs; 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 (!(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(`Received ${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,
};

View File

@ -74,6 +74,8 @@ releaseUploader.gulptasksReleaseUploader($, gulp, buildFolder);
const translations = require("./translations");
translations.gulptasksTranslations($, gulp, buildFolder);
const contributors = require("./contributors");
contributors.gulpTaskContributors($, gulp, buildFolder);
///////////////////// BUILD TASKS /////////////////////
// Cleans up everything
@ -252,6 +254,7 @@ gulp.task(
"imgres.copyImageResources",
"imgres.copyNonImageResources",
"translations.fullBuild",
"contributors.build",
"css.dev",
"html.dev"
)
@ -270,13 +273,17 @@ gulp.task(
"imgres.copyImageResources",
"imgres.copyNonImageResources",
"translations.fullBuild",
"contributors.build",
"css.dev",
"html.standalone-dev"
)
);
// Builds everything (staging)
gulp.task("step.staging.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.staging"));
gulp.task(
"step.staging.code",
gulp.series("sounds.fullbuild", "translations.fullBuild", "contributors.build", "js.staging")
);
gulp.task(
"step.staging.mainbuild",
gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.staging.code")
@ -285,7 +292,10 @@ gulp.task("step.staging.all", gulp.series("step.staging.mainbuild", "css.prod",
gulp.task("build.staging", gulp.series("utils.cleanup", "step.staging.all", "step.postbuild"));
// Builds everything (prod)
gulp.task("step.prod.code", gulp.series("sounds.fullbuild", "translations.fullBuild", "js.prod"));
gulp.task(
"step.prod.code",
gulp.series("sounds.fullbuild", "translations.fullBuild", "contributors.build", "js.prod")
);
gulp.task(
"step.prod.mainbuild",
gulp.parallel("utils.copyAdditionalBuildFiles", "step.baseResources", "step.prod.code")
@ -296,7 +306,7 @@ gulp.task("build.prod", gulp.series("utils.cleanup", "step.prod.all", "step.post
// Builds everything (standalone-beta)
gulp.task(
"step.standalone-beta.code",
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-beta")
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "contributors.build", "js.standalone-beta")
);
gulp.task("step.standalone-beta.mainbuild", gulp.parallel("step.baseResources", "step.standalone-beta.code"));
gulp.task(
@ -313,7 +323,12 @@ gulp.task(
for (const prefix of ["", "china.", "wegame."]) {
gulp.task(
prefix + "step.standalone-prod.code",
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", prefix + "js.standalone-prod")
gulp.series(
"sounds.fullbuildHQ",
"translations.fullBuild",
"contributors.build",
prefix + "js.standalone-prod"
)
);
gulp.task(

View File

@ -15,7 +15,7 @@
"@babel/preset-env": "^7.5.4",
"@types/cordova": "^0.0.34",
"@types/filesystem": "^0.0.29",
"@types/node": "^12.7.5",
"@types/node": "^15.12.4",
"ajv": "^6.10.2",
"audiosprite": "^0.7.2",
"babel-core": "^6.26.3",
@ -41,6 +41,7 @@
"ignore-loader": "^0.1.2",
"lz-string": "^1.4.4",
"markdown-loader": "^5.1.0",
"node-fetch": "^2.6.1",
"node-sri": "^1.1.1",
"phonegap-plugin-mobile-accessibility": "^1.0.5",
"postcss": ">=5.0.0",

View File

@ -994,11 +994,16 @@
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*", "@types/node@^12.7.5":
"@types/node@*":
version "12.7.5"
resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz"
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":
version "1.5.2"
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz"
@ -8318,6 +8323,11 @@ no-case@^2.2.0:
dependencies:
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:
version "2.2.1"
resolved "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz"

View File

@ -30,6 +30,7 @@
@import "states/mobile_warning";
@import "states/changelog";
@import "states/puzzle_menu";
@import "states/credits.scss";
@import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer";

View File

@ -0,0 +1,40 @@
#state_CreditsState {
.container .content {
text-align: center;
.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;
}
.people {
.entry {
}
.flex-entry {
display: flex;
.entry {
display: inline-block;
width: min-content;
}
}
}
.people-flex {
display: flex;
.entry {
display: flex;
}
}
}
}
}

View File

@ -34,6 +34,7 @@ import { RestrictionManager } from "./core/restriction_manager";
import { PuzzleMenuState } from "./states/puzzle_menu";
import { ClientAPI } from "./platform/api";
import { LoginState } from "./states/login";
import { CreditsState } from "./states/credits";
/**
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
@ -165,6 +166,7 @@ export class Application {
ChangelogState,
PuzzleMenuState,
LoginState,
CreditsState,
];
for (let i = 0; i < states.length; ++i) {

View File

@ -35,6 +35,14 @@ export class AboutState extends TextualGameState {
{ 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() {

92
src/js/states/credits.js Normal file
View File

@ -0,0 +1,92 @@
import { TextualGameState } from "../core/textual_game_state";
import { contributors, translators } from "../../../contributors.json";
const APILink = "https://api.github.com/repos/tobspr/shapez.io"; // Should use THRIDPARY_URLS, but it is really hard to read.
const numOfReqPerPage = 100; // Max is 100, change to something lower if loads are too long
export class CreditsState extends TextualGameState {
constructor() {
super("CreditsState");
this.state = "Credits";
}
getStateHeaderTitle() {
return this.state;
}
getMainContentHTML() {
return `
<div class="section"><div class="title">Tobias Springer - Main Programer and Artist</div></div>
<div class="special-shout-out section">
<div class="title">A Special Thanks To:</div>
<div class="people">
<div class="entry">Pepsin - Created the soundtrack</div>
<div class="entry">Sense 101 - Designed the Puzzle DLC's official puzzles</div>
<div class="entry">SargeanTravis - Created an achievement by whining a lot</div>
<div class="entry">Bagel03 - Was an absolute CHAD</div>
<div class="entry">Dengr - Wouldn't tell me what to put here</div>
<div class="entry">Block of Emerald - Also wouldn't tell me what to put here</div>
</div>
</div>
<div class="translators section">
<div class="title">Translators: </div>
<div class="flex-people">
${this.getTranslatorsHTML()}
</div>
</div>
<div class="contributors section">
<div class="title">Contributors: </div>
<div id="loading">Loading... <div>
</div>
`;
}
getTranslatorsHTML() {
let html = "";
for (let i = 0; i < translators.length; i++) {
html += `
<br>
<div class="entry">
<a href="${translators[i].username}" target="_blank">${
translators[i].username
}</a>: <br> ${translators[i].value
.map(pr => {
return `<a href=${pr.html_url} target="_blank">${this.getGoodTitle(pr.title)}</a>, `;
})
.reduce((p, c) => p + c)
.slice(0, -2)}
</div>
`;
}
return html;
}
getContributorsHTML() {
let html = "";
for (let i = 0; i < contributors.length; i++) {
html += `
<br>
<div class="entry">
<a href="${contributors[i].username}" target="_blank">${
contributors[i].username
}</a>: <br> ${contributors[i].value
.map(pr => {
return `<a href=${pr.html_url} target="_blank">${this.getGoodTitle(pr.title)}</a>, `;
})
.reduce((p, c) => p + c)
.slice(0, -2)}
</div>
`;
}
return html;
}
getGoodTitle(title) {
if (title.endsWith(".")) return title.slice(0, -1);
return title;
}
onEnter() {
// this.setPRInnerHTML();
}
}

View File

@ -1368,6 +1368,8 @@ about:
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>
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>
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>