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

Add hidden HUD elements, zone, and zoom, boundary constraints

This commit is contained in:
Greg Considine 2021-03-17 20:57:20 -04:00
parent f478228557
commit af980c7114
18 changed files with 433 additions and 94 deletions

View File

@ -62,6 +62,9 @@ export default {
// Allows unlocked achievements to be logged to console in the local build
// testAchievements: true,
// -----------------------------------------------------------------------------------
// Enables use of (some) existing flags within the puzzle mode context
// testPuzzleMode: true,
// -----------------------------------------------------------------------------------
// Disables the automatic switch to an overview when zooming out
// disableMapOverview: true,
// -----------------------------------------------------------------------------------

View File

@ -392,13 +392,20 @@ export class Camera extends BasicSerializableObject {
return rect.containsPoint(point.x, point.y);
}
getMaximumZoom() {
return this.root.gameMode.getMaximumZoom() * this.root.app.platformWrapper.getScreenScale();
}
getMinimumZoom() {
return this.root.gameMode.getMinimumZoom() * this.root.app.platformWrapper.getScreenScale();
}
/**
* Returns if we can further zoom in
* @returns {boolean}
*/
canZoomIn() {
const maxLevel = this.root.app.platformWrapper.getMaximumZoom();
return this.zoomLevel <= maxLevel - 0.01;
return this.zoomLevel <= this.getMaximumZoom() - 0.01;
}
/**
@ -406,8 +413,7 @@ export class Camera extends BasicSerializableObject {
* @returns {boolean}
*/
canZoomOut() {
const minLevel = this.root.app.platformWrapper.getMinimumZoom();
return this.zoomLevel >= minLevel + 0.01;
return this.zoomLevel >= this.getMinimumZoom() + 0.01;
}
// EVENTS
@ -705,6 +711,7 @@ export class Camera extends BasicSerializableObject {
this.didMoveSinceTouchStart = this.didMoveSinceTouchStart || delta.length() > 0;
this.center = this.center.add(delta);
this.clampPosition(this.center);
this.touchPostMoveVelocity = this.touchPostMoveVelocity
.multiplyScalar(velocitySmoothing)
@ -743,17 +750,31 @@ export class Camera extends BasicSerializableObject {
if (G_IS_DEV && globalConfig.debug.disableZoomLimits) {
return;
}
const wrapper = this.root.app.platformWrapper;
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *before* clamp: " + this.zoomLevel);
this.zoomLevel = clamp(this.zoomLevel, wrapper.getMinimumZoom(), wrapper.getMaximumZoom());
this.zoomLevel = clamp(this.zoomLevel, this.getMinimumZoom(), this.getMaximumZoom());
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *after* clamp: " + this.zoomLevel);
if (this.desiredZoom) {
this.desiredZoom = clamp(this.desiredZoom, wrapper.getMinimumZoom(), wrapper.getMaximumZoom());
this.desiredZoom = clamp(this.desiredZoom, this.getMinimumZoom(), this.getMaximumZoom());
}
}
/**
* Clamps x, y position within set boundaries
* @param {Vector} vector
*/
clampPosition(vector) {
if (!this.root.gameMode.hasBoundaries()) {
return;
}
const width = this.root.gameMode.getBoundaryWidth();
const height = this.root.gameMode.getBoundaryHeight();
vector.x = clamp(vector.x, -width, width);
vector.y = clamp(vector.y, -height, height);
}
/**
* Updates the camera
* @param {number} dt Delta time in milliseconds
@ -921,6 +942,8 @@ export class Camera extends BasicSerializableObject {
((0.5 * dt) / this.zoomLevel) * this.root.app.settings.getMovementSpeed()
)
);
this.clampPosition(this.center)
}
/**

View File

@ -9,7 +9,6 @@ import {
registerCanvas,
} from "../core/buffer_utils";
import { globalConfig } from "../core/config";
import { gGameModeRegistry } from "./game_mode_registry";
import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
import { DrawParameters } from "../core/draw_parameters";
import { gMetaBuildingRegistry } from "../core/global_registries";
@ -169,6 +168,10 @@ export class GameCore {
this.root.gameIsFresh = true;
this.root.map.seed = randomInt(0, 100000);
if (!this.root.gameMode.hasHub()) {
return;
}
// Place the hub
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
root: this.root,

View File

@ -5,50 +5,160 @@ import { GameRoot } from "./root";
import { gGameModeRegistry } from "../core/global_registries";
import { types, BasicSerializableObject } from "../savegame/serialization";
/** @enum {string} */
export const enumGameModeIds = {
puzzleEdit: "puzzleEditMode",
puzzlePlay: "puzzlePlayMode",
regular: "regularMode",
};
/** @enum {string} */
export const enumGameModeTypes = {
default: "defaultModeType",
puzzle: "puzzleModeType",
};
export class GameMode extends BasicSerializableObject {
/** @returns {string} */
static getId() {
abstract;
return "Unknown";
return "unknownMode";
}
static getSchema() {
return {};
/** @returns {string} */
static getType() {
abstract;
return "unknownType";
}
/**
* @param {GameRoot} root
* @param {string} [id=Regular]
*/
static create (root, id = "Regular") {
// id = "Regular"
static create(root, id = enumGameModeIds.regular) {
return new (gGameModeRegistry.findById(id))(root);
}
/**
* @param {GameRoot} root
* @param {string} [id=Regular]
*/
constructor(root) {
super();
this.root = root;
}
/** @returns {object} */
serialize() {
return {
$: this.getId(),
data: super.serialize()
}
data: super.serialize(),
};
}
deserialize({ $, data }) {
const Mode = gGameModeRegistry.findById($);
return super.deserialize(data, Mode, gGameModeRegistry.getId(), this.root);
/** @param {object} savedata */
deserialize({ data }) {
super.deserialize(data, this.root);
}
/** @returns {string} */
getId() {
// @ts-ignore
return this.constructor.getId();
}
/** @returns {string} */
getType() {
// @ts-ignore
return this.constructor.getType();
}
/**
* @param {string} name - Class name of HUD Part
* @returns {boolean}
*/
isHudPartHidden(name) {
return false;
}
/**
* @param {string} name - Class name of HUD Part
* @returns {boolean}
*/
isHudPartExcluded(name) {
return false;
}
/** @returns {boolean} */
hasZone() {
return false;
}
/** @returns {boolean} */
hasHints() {
return true;
}
/** @returns {boolean} */
hasHub() {
return true;
}
/** @returns {boolean} */
hasResources() {
return true;
}
/** @returns {boolean} */
hasBoundaries() {
return false;
}
/** @returns {number} */
getMinimumZoom() {
return 0.1;
}
/** @returns {number} */
getMaximumZoom() {
return 3.5;
}
/** @returns {object} */
getUpgrades() {
return {};
}
/** @returns {number} */
getZoneWidth() {
return 0;
}
/** @returns {number} */
getZoneHeight() {
return 0;
}
/** @returns {number} */
getBoundaryWidth() {
return Infinity;
}
/** @returns {number} */
getBoundaryHeight() {
return Infinity;
}
/** @returns {array} */
getLevelDefinitions() {
return [];
}
/** @returns {boolean} */
getIsFreeplayAvailable() {
return false;
}
/** @returns {string} */
getBlueprintShapeKey() {
return "CbCbCbRb:CwCwCwCw";
}
}

View File

@ -6,6 +6,7 @@ import { createLogger } from "../core/logging";
import { BeltSystem } from "./systems/belt";
import { ItemEjectorSystem } from "./systems/item_ejector";
import { MapResourcesSystem } from "./systems/map_resources";
import { MapZoneSystem } from "./systems/map_zone";
import { MinerSystem } from "./systems/miner";
import { ItemProcessorSystem } from "./systems/item_processor";
import { UndergroundBeltSystem } from "./systems/underground_belt";
@ -46,6 +47,9 @@ export class GameSystemManager {
/** @type {MapResourcesSystem} */
mapResources: null,
/** @type {MapZoneSystem} */
mapZone: null,
/** @type {MinerSystem} */
miner: null,
@ -140,6 +144,8 @@ export class GameSystemManager {
add("mapResources", MapResourcesSystem);
add("mapZone", MapZoneSystem);
add("hub", HubSystem);
add("staticMapEntities", StaticMapEntitySystem);

View File

@ -74,42 +74,42 @@ export class GameHUD {
unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
};
this.parts = {
buildingsToolbar: new HUDBuildingsToolbar(this.root),
wiresToolbar: new HUDWiresToolbar(this.root),
blueprintPlacer: new HUDBlueprintPlacer(this.root),
buildingPlacer: new HUDBuildingPlacer(this.root),
unlockNotification: new HUDUnlockNotification(this.root),
gameMenu: new HUDGameMenu(this.root),
massSelector: new HUDMassSelector(this.root),
shop: new HUDShop(this.root),
statistics: new HUDStatistics(this.root),
waypoints: new HUDWaypoints(this.root),
wireInfo: new HUDWireInfo(this.root),
leverToggle: new HUDLeverToggle(this.root),
constantSignalEdit: new HUDConstantSignalEdit(this.root),
this.initParts({
buildingsToolbar: HUDBuildingsToolbar,
wiresToolbar: HUDWiresToolbar,
blueprintPlacer: HUDBlueprintPlacer,
buildingPlacer: HUDBuildingPlacer,
unlockNotification: HUDUnlockNotification,
gameMenu: HUDGameMenu,
massSelector: HUDMassSelector,
shop: HUDShop,
statistics: HUDStatistics,
waypoints: HUDWaypoints,
wireInfo: HUDWireInfo,
leverToggle: HUDLeverToggle,
constantSignalEdit: HUDConstantSignalEdit,
// Must always exist
pinnedShapes: new HUDPinnedShapes(this.root),
notifications: new HUDNotifications(this.root),
settingsMenu: new HUDSettingsMenu(this.root),
debugInfo: new HUDDebugInfo(this.root),
dialogs: new HUDModalDialogs(this.root),
screenshotExporter: new HUDScreenshotExporter(this.root),
shapeViewer: new HUDShapeViewer(this.root),
pinnedShapes: HUDPinnedShapes,
notifications: HUDNotifications,
settingsMenu: HUDSettingsMenu,
debugInfo: HUDDebugInfo,
dialogs: HUDModalDialogs,
screenshotExporter: HUDScreenshotExporter,
shapeViewer: HUDShapeViewer,
wiresOverlay: new HUDWiresOverlay(this.root),
layerPreview: new HUDLayerPreview(this.root),
wiresOverlay: HUDWiresOverlay,
layerPreview: HUDLayerPreview,
minerHighlight: new HUDMinerHighlight(this.root),
tutorialVideoOffer: new HUDTutorialVideoOffer(this.root),
minerHighlight: HUDMinerHighlight,
tutorialVideoOffer: HUDTutorialVideoOffer,
// Typing hints
/* typehints:start */
/** @type {HUDChangesDebugger} */
changesDebugger: null,
/* typehints:end */
};
});
if (!IS_MOBILE) {
this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root);
@ -129,7 +129,7 @@ export class GameHUD {
this.parts.changesDebugger = new HUDChangesDebugger(this.root);
}
if (this.root.app.settings.getAllSettings().offerHints) {
if (this.root.gameMode.hasHints() && this.root.app.settings.getAllSettings().offerHints) {
this.parts.tutorialHints = new HUDPartTutorialHints(this.root);
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
}
@ -170,6 +170,21 @@ export class GameHUD {
/* dev:end*/
}
/** @param {object} parts */
initParts(parts) {
this.parts = {};
for (let key in parts) {
const Part = parts[key];
if (!Part || this.root.gameMode.isHudPartExcluded(Part)) {
continue;
}
this.parts[key] = new Part(this.root);
}
}
/**
* Attempts to close all overlays
*/

View File

@ -153,6 +153,10 @@ export class HUDPinnedShapes extends BaseHUDPart {
* Rerenders the whole component
*/
rerenderFull() {
if (this.root.gameMode.isHudPartHidden(this.constructor.name)) {
return;
}
const currentGoal = this.root.hubGoals.currentGoal;
const currentKey = currentGoal.definition.getHash();

View File

@ -100,16 +100,16 @@ export class HUDWaypoints extends BaseHUDPart {
this.directionIndicatorSprite = Loader.getSprite("sprites/misc/hub_direction_indicator.png");
/** @type {Array<Waypoint>}
*/
this.waypoints = [
{
/** @type {Array<Waypoint>} */
this.waypoints = [];
if (this.root.gameMode.hasHub()) {
this.waypoints.push({
label: null,
center: { x: 0, y: 0 },
zoomLevel: 3,
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
},
];
});
}
// Create a buffer we can use to measure text
this.dummyBuffer = makeOffscreenBuffer(1, 1, {

View File

@ -41,7 +41,14 @@ export class MapChunkView extends MapChunk {
*/
drawBackgroundLayer(parameters) {
const systems = this.root.systemMgr.systems;
systems.mapResources.drawChunk(parameters, this);
if (this.root.gameMode.hasZone()) {
systems.mapZone.drawChunk(parameters, this);
}
if (this.root.gameMode.hasResources()) {
systems.mapResources.drawChunk(parameters, this);
}
systems.beltUnderlays.drawChunk(parameters, this);
systems.belt.drawChunk(parameters, this);
}

View File

@ -0,0 +1,99 @@
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
import { globalConfig } from "../../core/config";
import { types } from "../../savegame/serialization";
import { HUDPinnedShapes } from "../hud/parts/pinned_shapes";
import { enumGameModeTypes, GameMode } from "../game_mode";
export class PuzzleGameMode extends GameMode {
static getType() {
return enumGameModeTypes.puzzle;
}
static getSchema() {
return {
hiddenHudParts: types.keyValueMap(types.bool),
zoneHeight: types.uint,
zoneWidth: types.uint,
};
}
/** @param {GameRoot} root */
constructor(root) {
super(root);
}
initialize() {
const data = this.getSaveData();
this.type = this.getType();
this.hiddenHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts();
// this.excludedHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts();
this.zoneHeight = data.zoneHeight || (3 * globalConfig.tileSize);
this.zoneWidth = data.zoneWidth || (4 * globalConfig.tileSize);
this.boundaryHeight = this.zoneHeight * 2;
this.boundaryWidth = this.zoneWidth * 2;
}
getSaveData() {
const save = this.root.savegame.getCurrentDump();
if (!save) {
return {};
}
return save.gameMode.data;
}
getDefaultHiddenHudParts() {
return {
[HUDPinnedShapes.name]: true
};
}
isHudPartHidden(name) {
return this.hiddenHudParts[name];
}
hasZone() {
return true;
}
hasHints() {
return false;
}
hasHub() {
return false;
}
hasResources() {
return false;
}
hasBoundaries() {
return true;
}
getMinimumZoom() {
return 1;
}
getBoundaryWidth() {
return this.boundaryWidth;
}
getBoundaryHeight() {
return this.boundaryHeight;
}
getZoneWidth() {
return this.zoneWidth;
}
getZoneHeight() {
return this.zoneHeight;
}
}

View File

@ -1,10 +1,16 @@
import { GameMode } from "../game_mode";
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
export class PuzzleEditGameMode extends GameMode {
import { PuzzleGameMode } from "./puzzle";
import { enumGameModeIds } from "../game_mode";
export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() {
return "PuzzleEdit";
return enumGameModeIds.puzzleEdit;
}
/** @param {GameRoot} root */
constructor(root) {
super(root);
}

View File

@ -1,12 +1,18 @@
import { GameMode } from "../game_mode";
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
export class PuzzlePlayGameMode extends GameMode {
import { PuzzleGameMode } from "./puzzle";
import { enumGameModeIds } from "../game_mode";
export class PuzzlePlayGameMode extends PuzzleGameMode {
static getId() {
return "PuzzlePlay";
return enumGameModeIds.puzzlePlay;
}
/** param {GameRoot} root */
/** @param {GameRoot} root */
constructor(root) {
super(root);
this.initialize();
}
}

View File

@ -1,10 +1,9 @@
/* typehints:start */
import { enumHubGoalRewards } from "../tutorial_goals";
import { GameRoot } from "../root";
/* typehints:end */
import { findNiceIntegerValue } from "../../core/utils";
import { GameMode } from "../game_mode";
import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode";
import { ShapeDefinition } from "../shape_definition";
import { enumHubGoalRewards } from "../tutorial_goals";
import { types } from "../../savegame/serialization";
@ -32,14 +31,13 @@ import { types } from "../../savegame/serialization";
const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
const finalGameShape = "RuCw--Cw:----Ru--";
const preparementShape = "CpRpCp--:SwSwSwSw";
const blueprintShape = "CbCbCbRb:CwCwCwCw";
// Tiers need % of the previous tier as requirement too
const tierGrowth = 2.5;
/**
* Generates all upgrades
* @returns {Object<string, import("../game_mode").UpgradeTiers>} */
* @returns {Object<string, UpgradeTiers>} */
function generateUpgrades(limitedVersion = false) {
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
@ -481,18 +479,16 @@ const demoVersionLevels = generateLevelDefinitions(true);
export class RegularGameMode extends GameMode {
static getId() {
return "Regular";
return enumGameModeIds.regular;
}
static getSchema() {
return {
test: types.string
}
static getType() {
return enumGameModeTypes.default;
}
/** @param {GameRoot} root */
constructor(root) {
super(root);
this.test = "test";
}
/**
@ -505,23 +501,6 @@ export class RegularGameMode extends GameMode {
: demoVersionUpgrades;
}
/**
* Should return whether free play is available or if the game stops
* after the predefined levels
* @returns {boolean}
*/
getIsFreeplayAvailable() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
}
/**
* Returns the blueprint shape key
* @returns {string}
*/
getBlueprintShapeKey() {
return blueprintShape;
}
/**
* Returns the goals for all levels including their reward
* @returns {Array<LevelDefinition>}
@ -531,4 +510,13 @@ export class RegularGameMode extends GameMode {
? fullVersionLevels
: demoVersionLevels;
}
/**
* Should return whether free play is available or if the game stops
* after the predefined levels
* @returns {boolean}
*/
getIsFreeplayAvailable() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
}
}

View File

@ -0,0 +1,60 @@
/* typehints:start */
import { DrawParameters } from "../../core/draw_parameters";
import { MapChunkView } from "../map_chunk_view";
/* typehints:end */
import { globalConfig } from "../../core/config";
import { drawSpriteClipped } from "../../core/draw_utils";
import { GameSystem } from "../game_system";
import { THEME } from "../theme";
export class MapZoneSystem extends GameSystem {
/**
* Draws the map resources
* @param {DrawParameters} parameters
* @param {MapChunkView} chunk
*/
drawChunk(parameters, chunk) {
const width = this.root.gameMode.getZoneWidth();
const height = this.root.gameMode.getZoneHeight();
const zoneChunkBackground = this.root.buffers.getForKey({
key: "mapzonebg",
subKey: chunk.renderKey,
w: width,
h: height,
dpi: 1,
redrawMethod: this.generateChunkBackground.bind(this, chunk),
});
parameters.context.imageSmoothingEnabled = false;
drawSpriteClipped({
parameters,
sprite: zoneChunkBackground,
x: -width,
y: -height,
w: this.root.gameMode.getBoundaryWidth(),
h: this.root.gameMode.getBoundaryHeight(),
originalW: width,
originalH: height,
});
parameters.context.imageSmoothingEnabled = true;
}
/**
* @param {MapChunkView} chunk
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context
* @param {number} w
* @param {number} h
* @param {number} dpi
*/
generateChunkBackground(chunk, canvas, context, w, h, dpi) {
context.clearRect(0, 0, w, h);
context.fillStyle = THEME.map.zone.background;
context.strokeStyle = THEME.map.zone.border;
context.fillRect(0, 0, w, h);
context.strokeRect(0, 0, w, h);
}
}

View File

@ -47,6 +47,11 @@
"textColor": "#fff",
"textColorCapped": "#ef5072",
"background": "rgba(40, 50, 60, 0.8)"
},
"zone": {
"background": "#3e3f47",
"border": "#667964"
}
},

View File

@ -48,6 +48,11 @@
"textColor": "#fff",
"textColorCapped": "#ef5072",
"background": "rgba(40, 50, 60, 0.8)"
},
"zone": {
"background": "#fff",
"border": "#cbffc4"
}
},

View File

@ -12,7 +12,7 @@
* time: any,
* entityMgr: any,
* map: any,
* gameMode: any,
* gameMode: object,
* hubGoals: any,
* pinnedShapes: any,
* waypoints: any,

View File

@ -15,7 +15,7 @@ import {
startFileChoose,
waitNextFrame,
} from "../core/utils";
import { gGameModeRegistry } from "../core/global_registries";
import { enumGameModeIds } from "../game/game_mode";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { getApplicationSettingById } from "../profile/application_settings";
import { T } from "../translations";
@ -360,10 +360,9 @@ export class MainMenuState extends GameState {
onPuzzlePlayButtonClicked() {
const savegame = this.app.savegameMgr.createNewSavegame();
const gameModeId = gGameModeRegistry.idToEntry.PuzzlePlay.getId();
this.moveToState("InGameState", {
gameModeId,
gameModeId: enumGameModeIds.puzzlePlay,
savegame,
});
}