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

Update to latest version

This commit is contained in:
Sense101 2022-03-05 21:42:29 +00:00
parent 55f968ea4a
commit bf17ccf202
39 changed files with 154 additions and 110 deletions

View File

@ -23,7 +23,7 @@ Your goal is to produce shapes by cutting, rotating, merging and painting parts
## Building
- Make sure `ffmpeg` is on your path
- Install Node.js and Yarn
- Install Node.js (v16.0 or earlier) and Yarn
- Install Java (required for textures)
- Run `yarn` in the root folder
- Cd into `gulp` folder

View File

@ -97,7 +97,6 @@ module.exports = ({ watch = false, standalone = false, chineseVersion = false, w
loader: path.resolve(__dirname, "mod.js"),
},
],
],
},
{
test: /\.worker\.js$/,

View File

@ -50,7 +50,7 @@ To get into shapez.io modding, I highly recommend checking out all of the exampl
| [usage_statistics.js](usage_statistics.js) | Displays a percentage on every building showing its utilization | Adding a new component, Adding a new GameSystem, Drawing within a GameSystem, Modifying builtin buildings, Adding custom game logic |
| [new_item_type.js](new_item_type.js) | Adds a new type of items to the map (fluids) | Adding a new item type, modifying map generation |
| [buildings_have_cost.js](buildings_have_cost.js) | Adds a new currency, and belts cost 1 of that currency | Extending and replacing builtin methods, Adding CSS and custom sprites |
| [mirrored_cutter.js](mirrored_cutter.js) | Adds a mirorred variant of the cutter | Adding a new variant to existing buildings |
| [mirrored_cutter.js](mirrored_cutter.js) | Adds a mirrored variant of the cutter | Adding a new variant to existing buildings |
### Creating new sprites

View File

@ -15,9 +15,9 @@ const BeltExtension = ({ $super, $old }) => ({
return !$old.getShowWiresLayerPreview();
},
getIsReplaceable() {
getIsReplaceable(variant, rotationVariant) {
// Instead of super, use $super
return $super.getIsReplaceable.call(this);
return $super.getIsReplaceable.call(this, variant, rotationVariant);
},
getIsRemoveable() {

View File

@ -87,7 +87,7 @@ class FluidItem extends shapez.BaseItem {
* @param {number} diameter
* @param {DrawParameters} parameters
*/
drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
drawItemCenteredClipped(x, y, parameters, diameter = shapez.globalConfig.defaultItemDiameter) {
const realDiameter = diameter * 0.6;
if (!this.cachedSprite) {
this.cachedSprite = shapez.Loader.getSprite(`sprites/fluids/${this.fluidType}.png`);
@ -120,10 +120,11 @@ class Mod extends shapez.Mod {
this.modInterface.registerSprite("sprites/fluids/water.png", RESOURCES["water.png"]);
// Make the item spawn on the map
this.modInterface.runAfterMethod(
shapez.MapChunk,
"generatePatches",
function ({ rng, chunkCenter, distanceToOriginInChunks }) {
this.modInterface.runAfterMethod(shapez.MapChunk, "generatePatches", function ({
rng,
chunkCenter,
distanceToOriginInChunks,
}) {
// Generate a simple patch
// ALWAYS use rng and NEVER use Math.random() otherwise the map will look different
// every time you resume the game
@ -131,8 +132,7 @@ class Mod extends shapez.Mod {
const fluidType = rng.choice(Array.from(Object.keys(enumFluidType)));
this.internalGeneratePatch(rng, 4, FLUID_ITEM_SINGLETONS[fluidType]);
}
}
);
});
this.modInterface.registerItem(FluidItem, itemData => FLUID_ITEM_SINGLETONS[itemData]);
}

View File

@ -1,9 +1,10 @@
export const CHANGELOG = [
{
version: "1.5.0",
date: "unreleased",
version: "1.5.1",
date: "25.02.2022",
entries: [
"This version adds an official modloader! You can now load mods by placing it in the mods/ folder of the game.",
"This version adds an official modloader! You can now load mods by extracting them and placing the .js file in the mods/ folder of the game.",
"Mods can be found <a href='https://shapez.mod.io'>here</a>",
"When holding shift while placing a belt, the indicator now becomes red when crossing buildings",
"Lots of performance improvements, leading to up to 50% more FPS",
],

View File

@ -211,6 +211,7 @@ export class GameState {
/**
* Should return the html code of the state.
* @returns {string}
* @abstract
*/
getInnerHTML() {
abstract;

View File

@ -3,20 +3,8 @@ const options = queryString.parse(location.search);
export let queryParamOptions = {
embedProvider: null,
fullVersion: false,
sandboxMode: false,
};
if (options.embed) {
queryParamOptions.embedProvider = options.embed;
}
// Allow testing full version outside of standalone
if (options.fullVersion && !G_IS_RELEASE) {
queryParamOptions.fullVersion = true;
}
// Allow testing full version outside of standalone
if (options.sandboxMode && !G_IS_RELEASE) {
queryParamOptions.sandboxMode = true;
}

View File

@ -268,7 +268,7 @@ export class Rectangle {
}
/**
* Returns if hte rectangle contains the given point
* Returns if the rectangle contains the given point
* @param {number} x
* @param {number} y
* @returns {boolean}

View File

@ -84,11 +84,6 @@ export class RestrictionManager extends ReadWriteProxy {
return false;
}
if (queryParamOptions.fullVersion) {
// Full version is activated via flag
return false;
}
if (queryParamOptions.embedProvider === "gamedistribution") {
// also full version on gamedistribution
return false;

View File

@ -11,6 +11,7 @@ export class BaseSprite {
/**
* Returns the raw handle
* @returns {HTMLImageElement|HTMLCanvasElement}
* @abstract
*/
getRawTexture() {
abstract;

View File

@ -29,6 +29,7 @@ export class BaseItem extends BasicSerializableObject {
/**
* Returns a string id of the item
* @returns {string}
* @abstract
*/
getAsCopyableKey() {
abstract;
@ -49,9 +50,9 @@ export class BaseItem extends BasicSerializableObject {
/**
* Override for custom comparison
* @abstract
* @param {BaseItem} other
* @returns {boolean}
* @abstract
*/
equalsImpl(other) {
abstract;
@ -62,6 +63,7 @@ export class BaseItem extends BasicSerializableObject {
* Draws the item to a canvas
* @param {CanvasRenderingContext2D} context
* @param {number} size
* @abstract
*/
drawFullSizeOnCanvas(context, size) {
abstract;
@ -86,6 +88,7 @@ export class BaseItem extends BasicSerializableObject {
* @param {number} y
* @param {DrawParameters} parameters
* @param {number=} diameter
* @abstract
*/
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
abstract;

View File

@ -1526,7 +1526,17 @@ export class BeltPath extends BasicSerializableObject {
const sprite = this.root.buffers.getForKey({
key: "beltpaths",
subKey: "stack-" + directionProp + "-" + dpi + "-" + stack.length + firstItem[1].serialize(),
subKey:
"stack-" +
directionProp +
"-" +
dpi +
"#" +
stack.length +
"#" +
firstItem[1].getItemType() +
"#" +
firstItem[1].serialize(),
dpi,
w: dimensions.x,
h: dimensions.y,

View File

@ -4,6 +4,7 @@ export class Component extends BasicSerializableObject {
/**
* Returns the components unique id
* @returns {string}
* @abstract
*/
static getId() {
abstract;

View File

@ -224,6 +224,7 @@ export class Entity extends BasicSerializableObject {
/**
* override, should draw the entity
* @param {DrawParameters} parameters
* @abstract
*/
drawImpl(parameters) {
abstract;

View File

@ -144,6 +144,7 @@ export class GameMode extends BasicSerializableObject {
/**
* @param {number} w
* @param {number} h
* @abstract
*/
adjustZone(w = 0, h = 0) {
abstract;

View File

@ -25,6 +25,7 @@ export class BaseHUDPart {
/**
* Should initialize the element, called *after* the elements have been created
* @abstract
*/
initialize() {
abstract;

View File

@ -192,7 +192,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
const metaBuilding = this.currentMetaBuilding.get();
return (
metaBuilding &&
metaBuilding.getHasDirectionLockAvailable() &&
metaBuilding.getHasDirectionLockAvailable(this.currentVariant.get()) &&
this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.lockBeltDirection).pressed
);
}

View File

@ -49,7 +49,7 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
!this.mapOverviewActive &&
placer &&
placer.currentMetaBuilding.get() &&
placer.currentMetaBuilding.get().getHasDirectionLockAvailable()
placer.currentMetaBuilding.get().getHasDirectionLockAvailable(placer.currentVariant.get())
);
}

View File

@ -16,7 +16,7 @@ export function itemResolverSingleton(root, data) {
const itemData = data.data;
if (MODS_ADDITIONAL_ITEMS[itemType]) {
return MODS_ADDITIONAL_ITEMS[itemType](itemData);
return MODS_ADDITIONAL_ITEMS[itemType](itemData, root);
}
switch (itemType) {

View File

@ -72,8 +72,13 @@ export class GameLogic {
// Check if there is any direct collision
const otherEntity = this.root.map.getLayerContentXY(x, y, entity.layer);
if (otherEntity) {
const metaClass = otherEntity.components.StaticMapEntity.getMetaBuilding();
if (!allowReplaceBuildings || !metaClass.getIsReplaceable()) {
const staticComp = otherEntity.components.StaticMapEntity;
if (
!allowReplaceBuildings ||
!staticComp
.getMetaBuilding()
.getIsReplaceable(staticComp.getVariant(), staticComp.getRotationVariant())
) {
// This one is a direct blocker
return false;
}
@ -140,8 +145,11 @@ export class GameLogic {
for (let y = rect.y; y < rect.y + rect.h; ++y) {
const contents = this.root.map.getLayerContentXY(x, y, entity.layer);
if (contents) {
const staticComp = contents.components.StaticMapEntity;
assertAlways(
contents.components.StaticMapEntity.getMetaBuilding().getIsReplaceable(),
staticComp
.getMetaBuilding()
.getIsReplaceable(staticComp.getVariant(), staticComp.getRotationVariant()),
"Tried to replace non-repleaceable entity"
);
if (!this.tryDeleteBuilding(contents)) {

View File

@ -52,8 +52,9 @@ export class MetaBuilding {
/**
* Returns whether the building has the direction lock switch available
* @param {string} variant
*/
getHasDirectionLockAvailable() {
getHasDirectionLockAvailable(variant) {
return false;
}
@ -88,8 +89,10 @@ export class MetaBuilding {
/**
* Returns whether this building can get replaced
* @param {string} variant
* @param {number} rotationVariant
*/
getIsReplaceable() {
getIsReplaceable(variant, rotationVariant) {
return false;
}
@ -278,6 +281,7 @@ export class MetaBuilding {
* Should setup the entity components
* @param {Entity} entity
* @param {GameRoot} root
* @abstract
*/
setupEntityComponents(entity, root) {
abstract;

View File

@ -22,6 +22,7 @@ import { MetaTransistorBuilding } from "../buildings/transistor";
import { HUDPuzzleEditorControls } from "../hud/parts/puzzle_editor_controls";
import { HUDPuzzleEditorReview } from "../hud/parts/puzzle_editor_review";
import { HUDPuzzleEditorSettings } from "../hud/parts/puzzle_editor_settings";
import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit";
export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() {
@ -58,6 +59,7 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls;
this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview;
this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings;
this.additionalHudParts.constantSignalEdit = HUDConstantSignalEdit;
}
getIsEditor() {

View File

@ -594,18 +594,13 @@ export class RegularGameMode extends GameMode {
this.additionalHudParts.interactiveTutorial = HUDInteractiveTutorial;
}
// @ts-ignore
if (queryParamOptions.sandboxMode || window.sandboxMode || G_IS_DEV) {
this.additionalHudParts.sandboxController = HUDSandboxController;
}
/** @type {(typeof MetaBuilding)[]} */
this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding, MetaBlockBuilding];
// @ts-ignore
if (!(G_IS_DEV || window.sandboxMode || queryParamOptions.sandboxMode)) {
this.hiddenBuildings.push(MetaItemProducerBuilding);
}
this.hiddenBuildings = [
MetaConstantProducerBuilding,
MetaGoalAcceptorBuilding,
MetaBlockBuilding,
MetaItemProducerBuilding,
];
}
/**

View File

@ -39,6 +39,11 @@ import { ShapeItem } from "../items/shape_item";
*/
export const MOD_ITEM_PROCESSOR_HANDLERS = {};
/**
* @type {Object<string, ({entity: Entity}) => boolean>}
*/
export const MODS_CAN_PROCESS = {};
export class ItemProcessorSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ItemProcessorComponent]);
@ -168,6 +173,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const acceptorComp = entity.components.ItemAcceptor;
const processorComp = entity.components.ItemProcessor;
if (MODS_CAN_PROCESS[processorComp.processingRequirement]) {
return MODS_CAN_PROCESS[processorComp.processingRequirement].bind(this)({
entity,
});
}
switch (processorComp.processingRequirement) {
// DEFAULT
// By default, we can start processing once all inputs are there

View File

@ -59,7 +59,11 @@ export class WiredPinsSystem extends GameSystemWithFilter {
continue;
}
if (staticComp.getMetaBuilding().getIsReplaceable()) {
if (
staticComp
.getMetaBuilding()
.getIsReplaceable(staticComp.getVariant(), staticComp.getRotationVariant())
) {
// Don't mind here, even if there would be a collision we
// could replace it
continue;
@ -113,7 +117,12 @@ export class WiredPinsSystem extends GameSystemWithFilter {
// If there's an entity, and it can't get removed -> That's a collision
if (collidingEntity) {
if (!collidingEntity.components.StaticMapEntity.getMetaBuilding().getIsReplaceable()) {
const staticComp = collidingEntity.components.StaticMapEntity;
if (
!staticComp
.getMetaBuilding()
.getIsReplaceable(staticComp.getVariant(), staticComp.getRotationVariant())
) {
return true;
}
}
@ -138,8 +147,11 @@ export class WiredPinsSystem extends GameSystemWithFilter {
const worldPos = entity.components.StaticMapEntity.localTileToWorld(slot.pos);
const collidingEntity = this.root.map.getLayerContentXY(worldPos.x, worldPos.y, "wires");
if (collidingEntity) {
const staticComp = collidingEntity.components.StaticMapEntity;
assertAlways(
collidingEntity.components.StaticMapEntity.getMetaBuilding().getIsReplaceable(),
staticComp
.getMetaBuilding()
.getIsReplaceable(staticComp.getVariant(), staticComp.getRotationVariant()),
"Tried to replace non-repleaceable entity for pins"
);
if (!this.root.logic.tryDeleteBuilding(collidingEntity)) {

View File

@ -92,6 +92,7 @@ export class AchievementProviderInterface {
/**
* Initializes the achievement provider.
* @returns {Promise<void>}
* @abstract
*/
initialize() {
abstract;
@ -102,6 +103,7 @@ export class AchievementProviderInterface {
* Opportunity to do additional initialization work with the GameRoot.
* @param {GameRoot} root
* @returns {Promise<void>}
* @abstract
*/
onLoad(root) {
abstract;
@ -118,6 +120,7 @@ export class AchievementProviderInterface {
* Call to activate an achievement with the provider
* @param {string} key - Maps to an Achievement
* @returns {Promise<void>}
* @abstract
*/
activate(key) {
abstract;
@ -127,6 +130,7 @@ export class AchievementProviderInterface {
/**
* Checks if achievements are supported in the current build
* @returns {boolean}
* @abstract
*/
hasAchievements() {
abstract;

View File

@ -19,6 +19,7 @@ export class AdProviderInterface {
/**
* Returns if this provider serves ads at all
* @returns {boolean}
* @abstract
*/
getHasAds() {
abstract;
@ -29,6 +30,7 @@ export class AdProviderInterface {
* Returns if it would be possible to show a video ad *now*. This can be false if for
* example the last video ad is
* @returns {boolean}
* @abstract
*/
getCanShowVideoAd() {
abstract;

View File

@ -11,6 +11,7 @@ export class AnalyticsInterface {
/**
* Initializes the analytics
* @returns {Promise<void>}
* @abstract
*/
initialize() {
abstract;

View File

@ -1,6 +1,5 @@
import { globalConfig } from "../../core/config";
import { createLogger } from "../../core/logging";
import { queryParamOptions } from "../../core/query_parameters";
import { BeltComponent } from "../../game/components/belt";
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
import { RegularGameMode } from "../../game/modes/regular";
@ -24,9 +23,6 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
}
if (G_IS_STANDALONE) {
if (queryParamOptions.sandboxMode) {
return "steam-sandbox";
}
return "steam";
}
@ -35,14 +31,8 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
}
if (window.location.host.indexOf("alpha") >= 0) {
if (queryParamOptions.sandboxMode) {
return "alpha-sandbox";
}
return "alpha";
} else {
if (queryParamOptions.sandboxMode) {
return "beta-sandbox";
}
return "beta";
}
}

View File

@ -11,6 +11,7 @@ export class GameAnalyticsInterface {
/**
* Initializes the analytics
* @returns {Promise<void>}
* @abstract
*/
initialize() {
abstract;
@ -43,6 +44,7 @@ export class GameAnalyticsInterface {
/**
* Activates a DLC
* @param {string} dlc
* @abstract
*/
activateDlc(dlc) {
abstract;

View File

@ -13,6 +13,7 @@ export class StorageInterface {
/**
* Initializes the storage
* @returns {Promise<void>}
* @abstract
*/
initialize() {
abstract;
@ -24,6 +25,7 @@ export class StorageInterface {
* @param {string} filename
* @param {string} contents
* @returns {Promise<void>}
* @abstract
*/
writeFileAsync(filename, contents) {
abstract;
@ -34,6 +36,7 @@ export class StorageInterface {
* Reads a string asynchronously. Returns Promise<FILE_NOT_FOUND> if file was not found.
* @param {string} filename
* @returns {Promise<string>}
* @abstract
*/
readFileAsync(filename) {
abstract;

View File

@ -81,6 +81,7 @@ export class PlatformWrapperInterface {
* Attempt to open an external url
* @param {string} url
* @param {boolean=} force Whether to always open the url even if not allowed
* @abstract
*/
openExternalLink(url, force = false) {
abstract;
@ -88,6 +89,7 @@ export class PlatformWrapperInterface {
/**
* Attempt to restart the app
* @abstract
*/
performRestart() {
abstract;
@ -103,6 +105,7 @@ export class PlatformWrapperInterface {
/**
* Should set the apps fullscreen state to the desired state
* @param {boolean} flag
* @abstract
*/
setFullscreen(flag) {
abstract;
@ -117,6 +120,7 @@ export class PlatformWrapperInterface {
/**
* Attempts to quit the app
* @abstract
*/
exitApp() {
abstract;

View File

@ -64,6 +64,7 @@ export class BaseSetting {
/**
* Returns the HTML for this setting
* @param {Application} app
* @abstract
*/
getHtml(app) {
abstract;
@ -84,6 +85,7 @@ export class BaseSetting {
/**
* Attempts to modify the setting
* @abstract
*/
modify() {
abstract;
@ -107,6 +109,7 @@ export class BaseSetting {
* Validates the set value
* @param {any} value
* @returns {boolean}
* @abstract
*/
validate(value) {
abstract;

View File

@ -48,6 +48,7 @@ export class BaseDataType {
/**
* Serializes a given raw value
* @param {any} value
* @abstract
*/
serialize(value) {
abstract;
@ -68,6 +69,7 @@ export class BaseDataType {
* @param {object} targetObject
* @param {string|number} targetKey
* @returns {string|void} String error code or null on success
* @abstract
*/
deserialize(value, targetObject, targetKey, root) {
abstract;
@ -92,6 +94,7 @@ export class BaseDataType {
/**
* INTERNAL Should return the json schema representation
* @abstract
*/
getAsJsonSchemaUncached() {
abstract;
@ -131,6 +134,7 @@ export class BaseDataType {
/**
* Should return a cacheable key
* @abstract
*/
getCacheKey() {
abstract;

View File

@ -1013,10 +1013,8 @@ tips:
- <b>SHIFT</b>を押したままにするとベルトプランナーが有効になり、長距離のベルトを簡単に配置できます。
- 切断機は配置された向きを考慮せず、常に垂直に切断します。
- ストレージは左側の出力を優先します。
- 増築可能なデザインを作るために時間を使ってください。それだけの価値があります!
- Invest time to build repeatable designs - it's worth it!
- 増築可能なデザインを目指してみましょう。それだけの価値があります!
- <b>ALT</b>を押しながらベルトを設置すると、向きを逆転できます。
- You can hold <b>ALT</b> to invert the direction of placed belts.
- ハブから遠くに離れるほど、形状資源はより複雑な形になります。
- 機械の速度には上限があるので、最大効率を得るためには入力を分割してください。
- 効率を最大化するために分配機/合流機を使用できます。
@ -1032,8 +1030,7 @@ tips:
- モジュールがあれば、空間はただの認識に過ぎなくなる――生ある人間に対する気遣いだ。
- 設計図としての工場を別に作っておくと、工場のモジュール化において重要な役割を果たします。
- 混色機をよく見ると、色の混ぜ方が解ります。
- Have a closer look at the color mixer, and your questions will be answered.
- Use <b>CTRL</b> + Click to select an area.
- <b>CTRL</b>を押したままクリックすると、領域を選択できます。
- アップグレードリストの各形状の横にあるピンのアイコンは、その形状を画面左に固定表示します。
- 三原色全てを混ぜ合わせると白になります!
- マップは無限の広さがあります。臆せずに拡張してください。
@ -1046,8 +1043,7 @@ tips:
- ベルトの中身をクリアするには、範囲選択して同じ場所に貼り付けをします。
- F4を押すことで、FPSとTickレートを表示できます。
- F4を2回押すと、マウスとカメラの座標を表示できます。
- 左のピン留めされた図形をクリックすると、固定を解除できます。
- You can click a pinned shape on the left side to unpin it.
- 左のピン留めされた図形をクリックすると、ピン留めを解除できます。
puzzleMenu:
play: Play
edit: Edit

View File

@ -50,7 +50,7 @@ global:
escape: ESC
shift: SHIFT
space: ПРОБЕЛ
loggingIn: Logging in
loggingIn: Вход
demoBanners:
title: Демоверсия
intro: Приобретите полную версию, чтобы разблокировать все возможности!
@ -74,14 +74,14 @@ mainMenu:
puzzleMode: Головоломка
back: Назад
puzzleDlcText: Нравится оптимизировать фабрики и делать их меньше? Купите
обновление "Головоломка" в Steam сейчас и получите еще больше
обновление «Головоломка» в Steam сейчас и получите еще больше
удовольствия!
puzzleDlcWishlist: Добавь в список желаемого!
puzzleDlcViewNow: Посмотреть
mods:
title: Active Mods
warningPuzzleDLC: Playing the Puzzle DLC is not possible with mods. Please
disable all mods to play the DLC.
title: Активные моды
warningPuzzleDLC: Обновление «Головоломка» невозможна с модами. Пожалуйста,
отключите все моды, чтобы играть в DLC.
dialogs:
buttons:
ok: OK
@ -230,8 +230,8 @@ dialogs:
desc: "Не удалось отправить вашу головоломку:"
puzzleSubmitOk:
title: Головоломка опубликована
desc: Поздравляю! Ваша головоломка была опубликована, и теперь в нее могут
играть остальные. Теперь вы можете найти ее в разделе "Мои
desc: Поздравляю! Ваша головоломка была опубликована, и теперь в неё могут
играть остальные. Теперь вы можете найти её в разделе "Мои
головоломки".
puzzleCreateOffline:
title: Оффлайн режим
@ -265,12 +265,13 @@ dialogs:
title: Удалить головоломку?
desc: Вы уверены, что хотите удалить '<title>'? Это действие нельзя отменить!
modsDifference:
title: Mod Warning
desc: The currently installed mods differ from the mods the savegame was created
with. This might cause the savegame to break or not load at all. Are
you sure you want to continue?
missingMods: Missing Mods
newMods: Newly installed Mods
title: Предупреждение Мода
desc: Установленные в данный момент моды отличаются от модов, с которыми была
создана игра сохранения с ним. Это может привести к тому, что сохранение
не загрузится или вообще ничего не загрузится. Вы вы уверены, что хотите
продолжить?
missingMods: Отсутствуют Моды
newMods: Недавно установленные Моды
ingame:
keybindingsOverlay:
moveMap: Передвижение
@ -1312,19 +1313,19 @@ backendErrors:
все еще хотите удалить ее, обратитесь в support@shapez.io!
no-permission: У вас нет прав на выполнение этого действия.
mods:
title: Mods
author: Author
version: Version
modWebsite: Website
openFolder: Open Mods Folder
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
browseMods: Browse Mods
modsInfo: To install and manage mods, copy them to the mods folder within the
game directory. You can also use the 'Open Mods Folder' button on the
top right.
noModSupport: You need the standalone version on Steam to install mods.
title: Моды
author: Автор
version: Версия
modWebsite: Веб-сайт
openFolder: Открыть папку с Модами
folderOnlyStandalone: Открытие папки Модов возможно только при запуске автономного режима.
browseMods: Просмотреть Моды
modsInfo: Чтобы установить и управлять модами, скопируйте их в папку модов в
директории игры. Вы также можете воспользоваться кнопкой "Открыть папку модов" в
справа вверху.
noModSupport: Для установки модов вам нужна автономная версия в Steam.
togglingComingSoon:
title: Coming Soon
description: Enabling or disabling mods is currently only possible by copying
the mod file from or to the mods/ folder. However, being able to
toggle them here is planned for a future update!
title: Совсем скоро
description: Включение или отключение модов в настоящее время возможно только
путем копирования файла мода из/в папку mods/. Однако, возможность
управлять ими здесь планируется в будущем обновлении!

View File

@ -52,7 +52,7 @@ global:
escape: ESC
shift: SKIFT
space: MELLANSLAG
loggingIn: Logging in
loggingIn: Loggar in
demoBanners:
title: Demo-version
intro: Skaffa den fristående versionen för att låsa upp alla funktioner!

View File

@ -1 +1 @@
1.5.0
1.5.1