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

Merge branch 'master' into waypoints

This commit is contained in:
dengr1065 2020-05-23 13:28:36 +03:00 committed by GitHub
commit 86e29fb34b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 608 additions and 160 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
res/videos/level_1.webm Normal file

Binary file not shown.

BIN
res/videos/level_2.webm Normal file

Binary file not shown.

View File

@ -0,0 +1,68 @@
#ingame_HUD_TutorialHints {
position: absolute;
@include S(left, 10px);
@include S(bottom, 50px);
display: flex;
flex-direction: column;
background: rgba(50, 60, 70, 0);
transition: all 0.2s ease-in-out;
pointer-events: all;
transition-property: background-color, transform, bottom, left;
@include S(padding, 5px);
video {
transition: all 0.2s ease-in-out;
transition-property: opacity, width;
@include S(width, 0px);
opacity: 0;
}
.header {
@include PlainText;
color: #333438;
display: grid;
align-items: center;
@include S(grid-gap, 2px);
grid-template-columns: 1fr;
@include S(margin-bottom, 3px);
}
button.toggleHint {
.hide {
display: none;
}
}
&.enlarged {
background: rgba(50, 60, 70, 0.9);
left: 50%;
bottom: 50%;
transform: translate(-50%, 50%);
.header {
grid-template-columns: 1fr auto;
color: #fff;
}
video {
@include InlineAnimation(0.2s ease-in-out) {
0% {
opacity: 0;
@include S(width, 0px);
}
}
opacity: 1;
@include S(width, 500px);
}
button.toggleHint {
.hide {
display: block;
}
.show {
display: none;
}
}
}
}

View File

@ -9,6 +9,7 @@
justify-content: center;
align-items: center;
pointer-events: all;
@include InlineAnimation(0.1s ease-in-out) {
0% {
opacity: 0;
@ -16,7 +17,7 @@
}
.dialog {
background: rgba(#222428, 0.5);
// background: rgba(#222428, 0.5);
@include S(border-radius, $globalBorderRadius);
@include S(padding, 30px);
@ -32,7 +33,7 @@
.subTitle {
@include SuperHeading;
text-transform: uppercase;
@include S(font-size, 50px);
@include S(font-size, 40px);
@include InlineAnimation(0.5s ease-in-out) {
0% {
@ -48,11 +49,10 @@
}
.subTitle {
@include Heading;
background: $colorGreenBright;
@include PlainText;
display: inline-block;
@include S(padding, 1px, 6px);
@include S(margin, 20px, 0, 20px);
@include S(margin, 0px, 0, 20px);
color: $colorGreenBright;
@include S(border-radius, $globalBorderRadius);
@include InlineAnimation(0.5s ease-in-out) {
@ -82,14 +82,15 @@
transform: translateX(-2vw);
}
}
display: grid;
grid-template-columns: auto auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
@include S(grid-gap, 10px);
.reward {
.rewardName {
grid-column: 1 / 3;
display: none;
@include InlineAnimation(0.5s ease-in-out) {
0% {
transform: translateX(200vw);
@ -104,6 +105,20 @@
}
}
.rewardDesc {
grid-column: 1 / 3;
@include PlainText;
@include S(margin-bottom, 15px);
color: #aaacaf;
@include S(width, 400px);
text-align: left;
strong {
color: #fff;
}
}
.images {
display: flex;
.buildingExplanation {
@include S(width, 200px);
@include S(height, 200px);
@ -115,18 +130,38 @@
box-shadow: #{D(2px)} #{D(3px)} 0 0 rgba(0, 0, 0, 0.15);
}
}
}
button.close {
border: 0;
@include InlineAnimation(2s ease-in-out) {
0% {
opacity: 0;
}
95% {
opacity: 0;
}
}
position: relative;
@include S(margin-top, 30px);
&:not(.unlocked) {
pointer-events: none;
opacity: 0.8;
cursor: default;
}
&::after {
content: " ";
display: inline-block;
position: absolute;
top: 0;
left: 100%;
right: 0;
bottom: 0;
background: rgba(0, 10, 20, 0.8);
@include InlineAnimation(10s linear) {
0% {
left: 0;
}
100% {
left: 100%;
}
}
}
}
}
}

View File

@ -26,6 +26,7 @@
@import "states/keybindings";
@import "states/settings";
@import "states/about";
@import "states/mobile_warning";
@import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer";
@ -44,6 +45,7 @@
@import "ingame_hud/settings_menu";
@import "ingame_hud/debug_info";
@import "ingame_hud/entity_debugger";
@import "ingame_hud/tutorial_hints";
// prettier-ignore
$elements:
@ -57,13 +59,14 @@ ingame_HUD_PlacerVariants,
// Regular hud
ingame_HUD_PinnedShapes,
ingame_HUD_buildings_toolbar,
ingame_HUD_GameMenu,
ingame_HUD_KeybindingOverlay,
ingame_HUD_Notifications,
ingame_HUD_MassSelector,
ingame_HUD_DebugInfo,
ingame_HUD_EntityDebugger,
ingame_HUD_TutorialHints,
ingame_HUD_buildings_toolbar,
// Overlays
ingame_HUD_BetaOverlay,
@ -92,7 +95,8 @@ body.uiHidden {
#ingame_HUD_GameMenu,
#ingame_HUD_MassSelector,
#ingame_HUD_PinnedShapes,
#ingame_HUD_Notifications {
#ingame_HUD_Notifications,
#ingame_HUD_TutorialHints {
display: none !important;
}
}
@ -100,7 +104,7 @@ body.uiHidden {
body.modalDialogActive,
body.externalAdOpen,
body.ingameDialogOpen {
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs) {
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs):not(.noBlur) {
filter: blur(5px) !important;
}
}

View File

@ -0,0 +1,48 @@
#state_MobileWarningState {
display: flex;
align-items: center;
background: #333438 !important;
@include S(padding, 20px);
box-sizing: border-box;
justify-content: center;
flex-direction: column;
.logo {
width: 80%;
max-width: 200px;
margin-bottom: 10px;
}
p {
color: #aaacaf;
display: block;
margin-bottom: 13px;
font-size: 16px;
line-height: 20px;
max-width: 300px;
text-align: left;
a {
color: $colorBlueBright;
}
}
.standaloneLink {
width: 200px;
height: 80px;
min-height: 40px;
background: uiResource("get_on_itch_io.svg") center center / contain no-repeat;
overflow: hidden;
display: block;
text-indent: -999em;
cursor: pointer;
margin-top: 10px;
pointer-events: all;
transition: all 0.12s ease-in;
transition-property: opacity, transform;
transform: skewX(-0.5deg);
&:hover {
transform: skewX(-1deg) scale(1.02);
opacity: 0.9;
}
}
}

View File

@ -1,5 +1,5 @@
import { AnimationFrame } from "./core/animation_frame";
import { performanceNow } from "./core/builtins";
import { performanceNow, Math_min } from "./core/builtins";
import { GameState } from "./core/game_state";
import { GLOBAL_APP, setGlobalApp } from "./core/globals";
import { InputDistributor } from "./core/input_distributor";
@ -36,6 +36,7 @@ import { KeybindingsState } from "./states/keybindings";
import { AboutState } from "./states/about";
import { PlatformWrapperImplElectron } from "./platform/electron/wrapper";
import { StorageImplElectron } from "./platform/electron/storage";
import { MobileWarningState } from "./states/mobile_warning";
const logger = createLogger("application");
@ -158,6 +159,7 @@ export class Application {
/** @type {Array<typeof GameState>} */
const states = [
PreloadState,
MobileWarningState,
MainMenuState,
InGameState,
SettingsState,
@ -315,7 +317,12 @@ export class Application {
Loader.linkAppAfterBoot(this);
// Check for mobile
if (IS_MOBILE) {
this.stateMgr.moveToState("MobileWarningState");
} else {
this.stateMgr.moveToState("PreloadState");
}
// Starting rendering
this.ticker.frameEmitted.add(this.onFrameEmitted, this);

View File

@ -25,13 +25,8 @@ const essentialBareGameSprites = G_ALL_UI_IMAGES;
const essentialBareGameSounds = [MUSIC.theme];
const additionalGameSprites = [];
const additionalGameSounds = [];
for (const key in SOUNDS) {
additionalGameSounds.push(SOUNDS[key]);
}
for (const key in MUSIC) {
additionalGameSounds.push(MUSIC[key]);
}
// @ts-ignore
const additionalGameSounds = [...Object.values(SOUNDS), ...Object.values(MUSIC)];
export class BackgroundResourcesLoader {
/**

View File

@ -83,8 +83,8 @@ export const globalConfig = {
debug: {
/* dev:start */
// fastGameEnter: true,
// noArtificialDelays: true,
fastGameEnter: true,
noArtificialDelays: true,
// disableSavegameWrite: true,
// showEntityBounds: true,
// showAcceptorEjectors: true,

View File

@ -143,6 +143,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
const tier = enumUndergroundBeltVariantToTier[variant];
const targetRotation = (rotation + 180) % 360;
const targetSenderRotation = rotation;
for (
let searchOffset = 1;
@ -161,12 +162,20 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
// If we encounter an underground receiver on our way which is also faced in our direction, we don't accept that
break;
}
return {
rotation: targetRotation,
rotationVariant: 1,
connectedEntities: [contents],
};
} else if (staticComp.rotation === targetSenderRotation) {
// Draw connections to receivers
if (undergroundComp.mode === enumUndergroundBeltMode.receiver) {
return {
rotation: rotation,
rotationVariant: 0,
connectedEntities: [contents],
};
}
}
}
}

View File

@ -352,6 +352,9 @@ export class Camera extends BasicSerializableObject {
mapper.getBinding(KEYMAPPINGS.ingame.mapMoveRight).add(() => (this.keyboardForce.x = 1));
mapper.getBinding(KEYMAPPINGS.ingame.mapMoveLeft).add(() => (this.keyboardForce.x = -1));
mapper.getBinding(KEYMAPPINGS.ingame.mapZoomIn).add(() => (this.desiredZoom = this.zoomLevel * 1.2));
mapper.getBinding(KEYMAPPINGS.ingame.mapZoomOut).add(() => (this.desiredZoom = this.zoomLevel * 0.8));
mapper.getBinding(KEYMAPPINGS.ingame.centerMap).add(() => this.centerOnMap());
}
@ -832,7 +835,7 @@ export class Camera extends BasicSerializableObject {
internalUpdateZooming(now, dt) {
if (!this.currentlyPinching && this.desiredZoom !== null) {
const diff = this.zoomLevel - this.desiredZoom;
if (Math_abs(diff) > 0.05) {
if (Math_abs(diff) > 0.0001) {
let fade = 0.94;
if (diff > 0) {
// Zoom out faster than in

View File

@ -188,13 +188,11 @@ export class HubGoals extends BasicSerializableObject {
return;
}
const reward = enumHubGoalRewards.no_reward;
this.currentGoal = {
/** @type {ShapeDefinition} */
definition: this.createRandomShape(),
required: 1000 + findNiceIntegerValue(this.level * 47.5),
reward,
reward: enumHubGoalRewards.no_reward_freeplay,
};
}
@ -212,6 +210,13 @@ export class HubGoals extends BasicSerializableObject {
this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward);
}
/**
* Returns whether we are playing in free-play
*/
isFreePlay() {
return this.level >= tutorialGoals.length;
}
/**
* Returns whether a given upgrade can be unlocked
* @param {string} upgradeId

View File

@ -27,7 +27,7 @@ import { HUDEntityDebugger } from "./parts/entity_debugger";
import { KEYMAPPINGS } from "../key_action_mapper";
import { HUDWatermark } from "./parts/watermark";
import { HUDModalDialogs } from "./parts/modal_dialogs";
import { Vector } from "../../core/vector";
import { HUDPartTutorialHints } from "./parts/tutorial_hints";
export class GameHUD {
/**
@ -64,6 +64,8 @@ export class GameHUD {
notifications: new HUDNotifications(this.root),
settingsMenu: new HUDSettingsMenu(this.root),
tutorialHints: new HUDPartTutorialHints(this.root),
// betaOverlay: new HUDBetaOverlay(this.root),
debugInfo: new HUDDebugInfo(this.root),

View File

@ -468,7 +468,11 @@ export class HUDBuildingPlacer extends BaseHUDPart {
this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
}
if (!metaBuilding.getStayInPlacementMode() && !this.root.app.inputMgr.shiftIsDown) {
if (
!metaBuilding.getStayInPlacementMode() &&
!this.root.app.inputMgr.shiftIsDown &&
!this.root.app.settings.getAllSettings().alwaysMultiplace
) {
// Stop placement
this.currentMetaBuilding.set(null);
}

View File

@ -0,0 +1,101 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils";
import { cachebust } from "../../../core/cachebust";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { InputReceiver } from "../../../core/input_receiver";
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { tutorialGoals } from "../../tutorial_goals";
import { TrackedState } from "../../../core/tracked_state";
const maxTutorialVideo = 2;
export class HUDPartTutorialHints extends BaseHUDPart {
createElements(parent) {
this.element = makeDiv(
parent,
"ingame_HUD_TutorialHints",
[],
`
<div class="header">
No idea what to do?
<button class="styledButton toggleHint">
<span class="show">Show hint</span>
<span class="hide">Hide hint</span>
</button>
</div>
<video autoplay muted loop class="fullscreenBackgroundVideo">
<source type="video/webm">
</video>
`
);
this.videoElement = this.element.querySelector("video");
}
shouldPauseGame() {
return this.enlarged;
}
initialize() {
this.trackClicks(this.element.querySelector(".toggleHint"), this.toggleHintEnlarged);
this.videoAttach = new DynamicDomAttach(this.root, this.videoElement, {
timeToKeepSeconds: 0.3,
});
this.videoAttach.update(false);
this.enlarged = false;
this.inputReciever = new InputReceiver("tutorial_hints");
this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
this.domAttach = new DynamicDomAttach(this.root, this.element);
this.currentShownLevel = new TrackedState(this.updateVideoUrl, this);
}
updateVideoUrl(level) {
console.log("update video url.", level);
this.videoElement
.querySelector("source")
.setAttribute("src", cachebust("res/videos/level_" + level + ".webm"));
}
close() {
this.enlarged = false;
document.body.classList.remove("ingameDialogOpen");
this.element.classList.remove("enlarged", "noBlur");
this.root.app.inputMgr.makeSureDetached(this.inputReciever);
this.update();
}
show() {
document.body.classList.add("ingameDialogOpen");
this.element.classList.add("enlarged", "noBlur");
this.enlarged = true;
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
this.update();
this.videoElement.currentTime = 0;
this.videoElement.play();
}
update() {
this.videoAttach.update(this.enlarged);
this.currentShownLevel.set(this.root.hubGoals.level);
const tutorialVisible = this.root.hubGoals.level <= maxTutorialVideo;
this.domAttach.update(tutorialVisible);
}
toggleHintEnlarged() {
if (this.enlarged) {
this.close();
} else {
this.show();
}
}
}

View File

@ -2,18 +2,12 @@ import { globalConfig } from "../../../core/config";
import { gMetaBuildingRegistry } from "../../../core/global_registries";
import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound";
import { MetaCutterBuilding } from "../../buildings/cutter";
import { MetaMixerBuilding } from "../../buildings/mixer";
import { MetaPainterBuilding } from "../../buildings/painter";
import { MetaRotaterBuilding } from "../../buildings/rotater";
import { MetaSplitterBuilding } from "../../buildings/splitter";
import { MetaStackerBuilding } from "../../buildings/stacker";
import { MetaTrashBuilding } from "../../buildings/trash";
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
import { T } from "../../../translations";
import { defaultBuildingVariant } from "../../meta_building";
import { enumHubGoalRewards } from "../../tutorial_goals";
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { T } from "../../../translations";
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
export class HUDUnlockNotification extends BaseHUDPart {
initialize() {
@ -26,10 +20,8 @@ export class HUDUnlockNotification extends BaseHUDPart {
if (!(G_IS_DEV && globalConfig.debug.disableUnlockDialog)) {
this.root.signals.storyGoalCompleted.add(this.showForLevel, this);
}
}
shouldPauseGame() {
return this.visible;
this.buttonShowTimeout = null;
}
createElements(parent) {
@ -60,63 +52,50 @@ export class HUDUnlockNotification extends BaseHUDPart {
("" + level).padStart(2, "0")
);
const rewardText = T.storyRewards[reward];
const rewardName = T.storyRewards[reward].title;
let html =
"<span class='reward'>" +
T.ingame.levelCompleteNotification.unlockText.replace("<reward>", rewardText) +
"</span>";
let html = `
<div class="rewardName">
${T.ingame.levelCompleteNotification.unlockText.replace("<reward>", rewardName)}
</div>
const addBuildingExplanation = metaBuildingClass => {
<div class="rewardDesc">
${T.storyRewards[reward].desc}
</div>
`;
html += "<div class='images'>";
const gained = enumHubGoalRewardsToContentUnlocked[reward];
if (gained) {
gained.forEach(([metaBuildingClass, variant]) => {
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);
html += `<div class="buildingExplanation" data-icon="building_tutorials/${metaBuilding.getId()}.png"></div>`;
};
switch (reward) {
case enumHubGoalRewards.reward_cutter_and_trash: {
addBuildingExplanation(MetaCutterBuilding);
addBuildingExplanation(MetaTrashBuilding);
break;
html += `<div class="buildingExplanation" data-icon="building_tutorials/${
metaBuilding.getId() + (variant === defaultBuildingVariant ? "" : "-" + variant)
}.png"></div>`;
});
}
case enumHubGoalRewards.reward_mixer: {
addBuildingExplanation(MetaMixerBuilding);
break;
}
case enumHubGoalRewards.reward_painter: {
addBuildingExplanation(MetaPainterBuilding);
break;
}
case enumHubGoalRewards.reward_rotater: {
addBuildingExplanation(MetaRotaterBuilding);
break;
}
case enumHubGoalRewards.reward_splitter: {
addBuildingExplanation(MetaSplitterBuilding);
break;
}
case enumHubGoalRewards.reward_stacker: {
addBuildingExplanation(MetaStackerBuilding);
break;
}
case enumHubGoalRewards.reward_tunnel: {
addBuildingExplanation(MetaUndergroundBeltBuilding);
break;
}
}
// addBuildingExplanation(MetaSplitterBuilding);
// addBuildingExplanation(MetaCutterBuilding);
html += "</div>";
this.elemContents.innerHTML = html;
this.visible = true;
this.root.soundProxy.playUi(SOUNDS.levelComplete);
if (this.buttonShowTimeout) {
clearTimeout(this.buttonShowTimeout);
}
this.buttonShowTimeout = setTimeout(
() => this.element.querySelector("button.close").classList.add("unlocked"),
G_IS_DEV ? 1000 : 10000
);
}
cleanup() {
if (this.buttonShowTimeout) {
clearTimeout(this.buttonShowTimeout);
this.buttonShowTimeout = null;
}
}
requestClose() {
@ -126,10 +105,18 @@ export class HUDUnlockNotification extends BaseHUDPart {
}
close() {
if (this.buttonShowTimeout) {
clearTimeout(this.buttonShowTimeout);
this.buttonShowTimeout = null;
}
this.visible = false;
}
update() {
this.domAttach.update(this.visible);
if (!this.visible && this.buttonShowTimeout) {
clearTimeout(this.buttonShowTimeout);
this.buttonShowTimeout = null;
}
}
}

View File

@ -33,6 +33,9 @@ export const KEYMAPPINGS = {
toggleHud: { keyCode: 113 }, // F2
toggleFPSInfo: { keyCode: 115 }, // F1
mapZoomIn: { keyCode: 187, repeated: true }, // "+"
mapZoomOut: { keyCode: 189, repeated: true }, // "-"
},
buildings: {
@ -194,13 +197,11 @@ export function getStringForKeyCode(code) {
case 186:
return ";";
case 187:
return "=";
return "+";
case 188:
return ",";
case 189:
return "-";
case 189:
return ".";
case 191:
return "/";
case 219:
@ -224,12 +225,14 @@ export class Keybinding {
* @param {object} param0
* @param {number} param0.keyCode
* @param {boolean=} param0.builtin
* @param {boolean=} param0.repeated
*/
constructor(app, { keyCode, builtin = false }) {
constructor(app, { keyCode, builtin = false, repeated = false }) {
assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
this.app = app;
this.keyCode = keyCode;
this.builtin = builtin;
this.repeated = repeated;
this.currentlyDown = false;
@ -364,7 +367,7 @@ export class KeyActionMapper {
for (const key in this.keybindings) {
/** @type {Keybinding} */
const binding = this.keybindings[key];
if (binding.keyCode === keyCode && !binding.currentlyDown) {
if (binding.keyCode === keyCode && (!binding.currentlyDown || binding.repeated)) {
binding.currentlyDown = true;
/** @type {Signal} */

View File

@ -88,7 +88,7 @@ export class HubSystem extends GameSystemWithFilter {
context.font = "bold 11px GameFont";
context.fillStyle = "#fd0752";
context.textAlign = "center";
context.fillText(T.storyRewards[goals.reward].toUpperCase(), pos.x, pos.y + 46);
context.fillText(T.storyRewards[goals.reward].title.toUpperCase(), pos.x, pos.y + 46);
// Level
context.font = "bold 11px GameFont";

View File

@ -2,6 +2,7 @@ import { ShapeDefinition } from "./shape_definition";
import { finalGameShape } from "./upgrades";
/**
* Don't forget to also update tutorial_goals_mappings.js as well as the translations!
* @enum {string}
*/
export const enumHubGoalRewards = {
@ -25,6 +26,7 @@ export const enumHubGoalRewards = {
reward_freeplay: "reward_freeplay",
no_reward: "no_reward",
no_reward_freeplay: "no_reward_freeplay",
};
export const tutorialGoals = [

View File

@ -0,0 +1,51 @@
import { MetaBuilding, defaultBuildingVariant } from "./meta_building";
import { MetaCutterBuilding, enumCutterVariants } from "./buildings/cutter";
import { MetaRotaterBuilding, enumRotaterVariants } from "./buildings/rotater";
import { MetaPainterBuilding, enumPainterVariants } from "./buildings/painter";
import { MetaMixerBuilding } from "./buildings/mixer";
import { MetaStackerBuilding } from "./buildings/stacker";
import { MetaSplitterBuilding, enumSplitterVariants } from "./buildings/splitter";
import { MetaUndergroundBeltBuilding, enumUndergroundBeltVariants } from "./buildings/underground_belt";
import { MetaMinerBuilding, enumMinerVariants } from "./buildings/miner";
import { MetaTrashBuilding, enumTrashVariants } from "./buildings/trash";
/** @typedef {Array<[typeof MetaBuilding, string]>} TutorialGoalReward */
import { enumHubGoalRewards } from "./tutorial_goals";
/**
* Helper method for proper types
* @returns {TutorialGoalReward}
*/
const typed = x => x;
/**
* Stores which reward unlocks what
* @enum {TutorialGoalReward?}
*/
export const enumHubGoalRewardsToContentUnlocked = {
[enumHubGoalRewards.reward_cutter_and_trash]: typed([[MetaCutterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_rotater]: typed([[MetaRotaterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_painter]: typed([[MetaPainterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_mixer]: typed([[MetaMixerBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_stacker]: typed([[MetaStackerBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_splitter]: typed([[MetaSplitterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_tunnel]: typed([[MetaUndergroundBeltBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_rotater_ccw]: typed([[MetaRotaterBuilding, enumRotaterVariants.ccw]]),
[enumHubGoalRewards.reward_miner_chainable]: typed([[MetaMinerBuilding, enumMinerVariants.chainable]]),
[enumHubGoalRewards.reward_underground_belt_tier_2]: typed([
[MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2],
]),
[enumHubGoalRewards.reward_splitter_compact]: typed([
[MetaSplitterBuilding, enumSplitterVariants.compact],
]),
[enumHubGoalRewards.reward_cutter_quad]: typed([[MetaCutterBuilding, enumCutterVariants.quad]]),
[enumHubGoalRewards.reward_painter_double]: typed([[MetaPainterBuilding, enumPainterVariants.double]]),
[enumHubGoalRewards.reward_painter_quad]: typed([[MetaPainterBuilding, enumPainterVariants.quad]]),
[enumHubGoalRewards.reward_storage]: typed([[MetaTrashBuilding, enumTrashVariants.storage]]),
[enumHubGoalRewards.reward_freeplay]: null,
[enumHubGoalRewards.no_reward]: null,
[enumHubGoalRewards.no_reward_freeplay]: null,
};

View File

@ -23,7 +23,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
initialize() {
this.syncKey = null;
setInterval(() => this.sendTimePoints(), 30 * 1000);
setInterval(() => this.sendTimePoints(), 120 * 1000);
// Retrieve sync key from player
return this.app.storage.readFileAsync(analyticsLocalFile).then(
@ -158,6 +158,8 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
const entities = root.entityMgr.getAllWithComponent(StaticMapEntityComponent);
// Limit the entities
if (entities.length < 5000) {
for (let i = 0; i < entities.length; ++i) {
const entity = entities[i];
const staticComp = entity.components.StaticMapEntity;
@ -179,6 +181,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
}
staticEntities.push(payload);
}
}
return {
storedShapes: root.hubGoals.storedShapes,

View File

@ -104,7 +104,7 @@ class MusicInstance extends MusicInstanceInterface {
}),
new Promise((resolve, reject) => {
this.howl = new Howl({
src: cachebust("res/sounds/music/" + this.url),
src: cachebust("res/sounds/music/" + this.url + ".mp3"),
autoplay: false,
loop: true,
html5: true,

View File

@ -27,8 +27,8 @@ export const SOUNDS = {
};
export const MUSIC = {
theme: "theme.mp3",
menu: "menu.mp3",
theme: "theme",
menu: "menu",
};
export class SoundInstanceInterface {

View File

@ -111,13 +111,11 @@ export const allApplicationSettings = [
textGetter: rate => rate + " Hz",
category: categoryGame,
restartRequired: false,
changeCb:
/**
* @param {Application} app
*/
(app, id) => {},
changeCb: (app, id) => {},
enabled: !IS_DEMO,
}),
new BoolSetting("alwaysMultiplace", categoryGame, (app, value) => {}),
];
export function getApplicationSettingById(id) {
@ -134,6 +132,8 @@ class SettingsStorage {
this.theme = "light";
this.refreshRate = "60";
this.alwaysMultiplace = false;
/**
* @type {Object.<string, number>}
*/
@ -291,14 +291,20 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
return 5;
return 6;
}
migrate(data) {
// Simply reset
if (data.version < this.getCurrentVersion()) {
// Simply reset before
if (data.version < 5) {
data.settings = new SettingsStorage();
data.version = this.getCurrentVersion();
return ExplainedResult.good();
}
if (data.version < 6) {
data.alwaysMultiplace = false;
data.version = 6;
}
return ExplainedResult.good();

View File

@ -0,0 +1,52 @@
import { GameState } from "../core/game_state";
import { cachebust } from "../core/cachebust";
import { THIRDPARTY_URLS } from "../core/config";
export class MobileWarningState extends GameState {
constructor() {
super("MobileWarningState");
}
getInnerHTML() {
return `
<img class="logo" src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<p>
I'm sorry, but shapez.io is not yet available on mobile devices!
(There is also no estimate when this will change, but feel to make a contribution! It's
&nbsp;<a href="https://github.com/tobspr/shapez.io" target="_blank">open source</a>!)</p>
<p>If you want to play on your computer, you can also get the standalone on itch.io:</p>
<a href="${
THIRDPARTY_URLS.standaloneStorePage
}" class="standaloneLink" target="_blank">Get the shapez.io standalone!</a>
`;
}
getThemeMusic() {
return null;
}
getHasFadeIn() {
return false;
}
onEnter() {
try {
if (window.gtag) {
window.gtag("event", "click", {
event_category: "ui",
event_label: "mobile_warning",
});
}
} catch (ex) {
console.warn("Failed to track mobile click:", ex);
}
}
onLeave() {
// this.dialogs.cleanup();
}
}

View File

@ -367,27 +367,82 @@ buildings:
storyRewards:
# Those are the rewards gained from completing the store
reward_cutter_and_trash: Cutting Shapes
reward_rotater: Rotating
reward_painter: Painting
reward_mixer: Color Mixing
reward_stacker: Combiner
reward_splitter: Splitter/Merger
reward_tunnel: Tunnel
reward_cutter_and_trash:
title: Cutting Shapes
desc: You just unlocked the <strong>cutter</strong> - it cuts shapes half from <strong>top to bottom</strong> regardless of its orientation!<br><br>Be sure to get rid of the waste, or otherwise <strong>it will stall</strong> - For this purpose I gave you a trash, which destroys everything you put into it!
reward_rotater_ccw: CCW Rotating
reward_miner_chainable: Chaining Extractor
reward_underground_belt_tier_2: Tunnel Tier II
reward_splitter_compact: Compact Balancer
reward_cutter_quad: Quad Cutting
reward_painter_double: Double Painting
reward_painter_quad: Quad Painting
reward_storage: Storage Buffer
reward_rotater:
title: Rotating
desc: The <strong>rotater</strong> has been unlocked! It rotates shapes clockwise by 90 degrees.
reward_freeplay: Freeplay
reward_painter:
title: Painting
desc: >-
The <strong>painter</strong> has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them!<br><br>PS: If you are colorblind, I'm working on a solution already!
reward_mixer:
title: Color Mixing
desc: The <strong>mixer</strong> has been unlocked - Combine two colors using <strong>additive blending</strong> with this building!
reward_stacker:
title: Combiner
desc: You can now combine shapes with the <strong>combiner</strong>! Both inputs are combined, and if they can be put next to each other, they will be <strong>fused</strong>. If not, the right input is <strong>stacked on top</strong> of the left input!
reward_splitter:
title: Splitter/Merger
desc: The multifunctional <strong>balancer</strong> has been unlocked - It can be used to build bigger factories by <strong>splitting and merging items</strong> onto multiple belts!<br><br>
reward_tunnel:
title: Tunnel
desc: The <strong>tunnel</strong> has been unlocked - You can now pipe items through belts and buildings with it!
reward_rotater_ccw:
title: CCW Rotating
desc: You have unlocked a variant of the <strong>rotater</strong> - It allows to rotate counter clockwise! To build it, select the rotater and <strong>press 'T' to cycle its variants</strong>!
reward_miner_chainable:
title: Chaining Extractor
desc: You have unlocked the <strong>chaining extractor</strong>! It can <strong>forward its resources</strong> to other extractors so you can more efficiently extract resources!
reward_underground_belt_tier_2:
title: Tunnel Tier II
desc: You have unlocked a new variant of the <strong>tunnel</strong> - It has a <strong>bigger range</strong>, and you can also mix-n-match those tunnels now!
reward_splitter_compact:
title: Compact Balancer
desc: >-
You have unlocked a compact variant of the <strong>balancer</strong> - It accepts two inputs and merges them into one!
reward_cutter_quad:
title: Quad Cutting
desc: You have unlocked a variant of the <strong>cutter</strong> - It allows you to cut shapes in <strong>four parts</strong> instead of just two!
reward_painter_double:
title: Double Painting
desc: You have unlocked a variant of the <strong>painter</strong> - It works as the regular painter but processes <strong>two shapes at once</strong> consuming just one color instead of two!
reward_painter_quad:
title: Quad Painting
desc: You have unlocked a variant of the <strong>painter</strong> - It allows to paint each part of the shape individually!
reward_storage:
title: Storage Buffer
desc: You have unlocked a variant of the <strong>trash</strong> - It allows to store items up to a given capacity!
reward_freeplay:
title: Freeplay
desc: You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now randomly generated! (No worries, more content is planned for the standalone!)
# Special reward, which is shown when there is no reward actually
no_reward: Next level
no_reward:
title: Next level
desc: >-
This level gave you no reward, but the next one will! <br><br> PS: Better don't destroy your existing factory - You need <strong>all</strong> those shapes later again to <strong>unlock upgrades</strong>!
no_reward_freeplay:
title: Next level
desc: >-
Congratulations! By the way, more content is planned for the standalone!
settings:
title: Settings
@ -432,6 +487,11 @@ settings:
description: >-
If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates. This might actually decrease the FPS if your computer is too slow.
alwaysMultiplace:
title: Multiplace
description: >-
If enabled, all buildings will stay selected after placement until you cancel it. This is equivalent to holding SHIFT permanently.
keybindings:
title: Keybindings
hint: >-
@ -457,6 +517,9 @@ keybindings:
centerMap: Center Map
mapZoomIn: Zoom in
mapZoomOut: Zoom out
menuOpenShop: Upgrades
menuOpenStats: Statistics