From 7e745fd0ce22df8ab9f7ac75817dad30726254b4 Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 13 Jun 2020 10:57:29 +0200 Subject: [PATCH] Allow exporting whole bases, closes #137 --- src/js/core/vector.js | 11 +- src/js/game/core.js | 2 +- src/js/game/entity.js | 2 +- src/js/game/hud/hud.js | 10 +- src/js/game/hud/parts/keybinding_overlay.js | 6 -- src/js/game/hud/parts/screenshot_exporter.js | 105 +++++++++++++++++++ src/js/game/key_action_mapper.js | 3 +- src/js/game/map_view.js | 4 +- translations/base-en.yaml | 6 ++ 9 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 src/js/game/hud/parts/screenshot_exporter.js diff --git a/src/js/core/vector.js b/src/js/core/vector.js index 2a02f75d..635556d6 100644 --- a/src/js/core/vector.js +++ b/src/js/core/vector.js @@ -10,6 +10,7 @@ import { Math_atan2, Math_sin, Math_cos, + Math_ceil, } from "./builtins"; const tileSize = globalConfig.tileSize; @@ -303,13 +304,21 @@ export class Vector { } /** - * Computes componentwise floor and return a new vector + * Computes componentwise floor and returns a new vector * @returns {Vector} */ floor() { return new Vector(Math_floor(this.x), Math_floor(this.y)); } + /** + * Computes componentwise ceil and returns a new vector + * @returns {Vector} + */ + ceil() { + return new Vector(Math_ceil(this.x), Math_ceil(this.y)); + } + /** * Computes componentwise round and return a new vector * @returns {Vector} diff --git a/src/js/game/core.js b/src/js/game/core.js index 3d2c1f3d..8b1c464d 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -409,7 +409,7 @@ export class GameCore { } if (G_IS_DEV) { - root.map.drawStaticEntities(params); + root.map.drawStaticEntityDebugOverlays(params); } // END OF GAME CONTENT diff --git a/src/js/game/entity.js b/src/js/game/entity.js index dc849851..9dea1c2b 100644 --- a/src/js/game/entity.js +++ b/src/js/game/entity.js @@ -136,7 +136,7 @@ export class Entity extends BasicSerializableObject { * Draws the entity, to override use @see Entity.drawImpl * @param {DrawParameters} parameters */ - draw(parameters) { + drawDebugOverlays(parameters) { const context = parameters.context; const staticComp = this.components.StaticMapEntity; diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index a541dc2e..979190f8 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -2,6 +2,10 @@ import { GameRoot } from "../root"; /* typehints:end */ +/* dev:start */ +import { TrailerMaker } from "./trailer_maker"; +/* dev:end */ + import { Signal } from "../../core/signal"; import { DrawParameters } from "../../core/draw_parameters"; import { HUDProcessingOverlay } from "./parts/processing_overlay"; @@ -29,10 +33,7 @@ import { HUDModalDialogs } from "./parts/modal_dialogs"; import { HUDPartTutorialHints } from "./parts/tutorial_hints"; import { HUDWaypoints } from "./parts/waypoints"; import { HUDInteractiveTutorial } from "./parts/interactive_tutorial"; - -/* dev:start */ -import { TrailerMaker } from "./trailer_maker"; -/* dev:end */ +import { HUDScreenshotExporter } from "./parts/screenshot_exporter"; export class GameHUD { /** @@ -66,6 +67,7 @@ export class GameHUD { // betaOverlay: new HUDBetaOverlay(this.root), debugInfo: new HUDDebugInfo(this.root), dialogs: new HUDModalDialogs(this.root), + screenshotExporter: new HUDScreenshotExporter(this.root), }; this.signals = { diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js index 0c3f3342..24d7040e 100644 --- a/src/js/game/hud/parts/keybinding_overlay.js +++ b/src/js/game/hud/parts/keybinding_overlay.js @@ -57,12 +57,6 @@ export class HUDKeybindingOverlay extends BaseHUDPart { -
- ${getKeycode(KEYMAPPINGS.massSelect.pasteLastBlueprint)} - -
- -
diff --git a/src/js/game/hud/parts/screenshot_exporter.js b/src/js/game/hud/parts/screenshot_exporter.js new file mode 100644 index 00000000..dfdd8224 --- /dev/null +++ b/src/js/game/hud/parts/screenshot_exporter.js @@ -0,0 +1,105 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { KEYMAPPINGS } from "../../key_action_mapper"; +import { IS_DEMO, globalConfig } from "../../../core/config"; +import { T } from "../../../translations"; +import { createLogger } from "../../../core/logging"; +import { StaticMapEntityComponent } from "../../components/static_map_entity"; +import { Vector } from "../../../core/vector"; +import { Math_max, Math_min } from "../../../core/builtins"; +import { makeOffscreenBuffer } from "../../../core/buffer_utils"; +import { DrawParameters } from "../../../core/draw_parameters"; +import { Rectangle } from "../../../core/rectangle"; + +const logger = createLogger("screenshot_exporter"); + +export class HUDScreenshotExporter extends BaseHUDPart { + createElements() {} + + initialize() { + this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.exportScreenshot).add(this.startExport, this); + } + + startExport() { + if (IS_DEMO) { + this.root.hud.parts.dialogs.showFeatureRestrictionInfo(T.demo.features.exportingBase); + return; + } + + const { ok } = this.root.hud.parts.dialogs.showInfo( + T.dialogs.exportScreenshotWarning.title, + T.dialogs.exportScreenshotWarning.desc, + ["cancel:good", "ok:bad"] + ); + ok.add(this.doExport, this); + } + + doExport() { + logger.log("Starting export ..."); + + // Find extends + const staticEntities = this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent); + + const minTile = new Vector(0, 0); + const maxTile = new Vector(0, 0); + for (let i = 0; i < staticEntities.length; ++i) { + const bounds = staticEntities[i].components.StaticMapEntity.getTileSpaceBounds(); + minTile.x = Math_min(minTile.x, bounds.x); + minTile.y = Math_min(minTile.y, bounds.y); + + maxTile.x = Math_max(maxTile.x, bounds.x + bounds.w); + maxTile.y = Math_max(maxTile.y, bounds.y + bounds.h); + } + + const minChunk = minTile.divideScalar(globalConfig.mapChunkSize).floor(); + const maxChunk = maxTile.divideScalar(globalConfig.mapChunkSize).ceil(); + + const dimensions = maxChunk.sub(minChunk); + logger.log("Dimensions:", dimensions); + + const chunkSizePixels = 128; + const chunkScale = chunkSizePixels / (globalConfig.mapChunkSize * globalConfig.tileSize); + logger.log("Scale:", chunkScale); + + logger.log("Allocating buffer, if the factory grew too big it will crash here"); + const [canvas, context] = makeOffscreenBuffer( + dimensions.x * chunkSizePixels, + dimensions.y * chunkSizePixels, + { + smooth: true, + reusable: false, + label: "export-buffer", + } + ); + logger.log("Got buffer, rendering now ..."); + + const visibleRect = new Rectangle( + minChunk.x * globalConfig.mapChunkSize * globalConfig.tileSize, + minChunk.y * globalConfig.mapChunkSize * globalConfig.tileSize, + dimensions.x * globalConfig.mapChunkSize * globalConfig.tileSize, + dimensions.y * globalConfig.mapChunkSize * globalConfig.tileSize + ); + const parameters = new DrawParameters({ + context, + visibleRect, + desiredAtlasScale: "1", + root: this.root, + zoomLevel: chunkScale, + }); + + context.scale(chunkScale, chunkScale); + context.translate(-visibleRect.x, -visibleRect.y); + + // Render all relevant chunks + this.root.map.drawBackground(parameters); + this.root.map.drawForeground(parameters); + + // Offer export + logger.log("Rendered buffer, exporting ..."); + const image = canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = "base.png"; + link.href = image; + link.click(); + logger.log("Done!"); + } +} diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 58077f01..816c5cd3 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -24,7 +24,8 @@ export const KEYMAPPINGS = { menuOpenStats: { keyCode: key("G") }, toggleHud: { keyCode: 113 }, // F2 - toggleFPSInfo: { keyCode: 115 }, // F1 + exportScreenshot: { keyCode: 114 }, // F3 + toggleFPSInfo: { keyCode: 115 }, // F4 }, navigation: { diff --git a/src/js/game/map_view.js b/src/js/game/map_view.js index 90919a2a..5c4bf88a 100644 --- a/src/js/game/map_view.js +++ b/src/js/game/map_view.js @@ -64,7 +64,7 @@ export class MapView extends BaseMap { * Draws all static entities like buildings etc. * @param {DrawParameters} drawParameters */ - drawStaticEntities(drawParameters) { + drawStaticEntityDebugOverlays(drawParameters) { const cullRange = drawParameters.visibleRect.toTileCullRectangle(); const top = cullRange.top(); const right = cullRange.right(); @@ -90,7 +90,7 @@ export class MapView extends BaseMap { if (content) { let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1; if (!isBorder) { - content.draw(drawParameters); + content.drawDebugOverlays(drawParameters); } } } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 7430f7dd..2764a804 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -260,6 +260,10 @@ dialogs: markerDemoLimit: desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers! + exportScreenshotWarning: + title: Export screenshot + desc: You requested to export your base as a screenshot. Please note that this can be quite slow for a big base and even crash your game! + ingame: # This is shown in the top left corner and displays useful keybindings in # every situation @@ -698,6 +702,7 @@ keybindings: toggleHud: Toggle HUD toggleFPSInfo: Toggle FPS and Debug Info + exportScreenshot: Export whole Base as Image belt: *belt splitter: *splitter underground_belt: *underground_belt @@ -739,5 +744,6 @@ demo: importingGames: Importing savegames oneGameLimit: Limited to one savegame customizeKeybindings: Customizing Keybindings + exportingBase: Exporting whole Base as Image settingNotAvailable: Not available in the demo.