Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ node_modules/
release/
dist/
.cache/
.home/

# Reconstructed reproducibly from the pinned MiniDayZ Plus upstream commit.
# Keep game changes as explicit patches instead of silently vendoring generated files.
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

All notable desktop-port changes are documented here. The embedded game remains MiniDayZ Plus 1.2.

## [0.2.0] - 2026-07-14

### Added

- Complete desktop action bindings: WASD and arrow movement, inventory, interaction, combat, reload, weapon selection, aiming, perks, flare, talk, pause, and vehicle actions.
- Mouse aliases for aiming, cycling weapons, and interaction while preserving the game's original left-click behavior.
- An in-game `F1` controls card.
- Deterministic patching of the pinned Construct 2 event sheet by cloning the original guarded touch actions as keyboard actions.
- Automated checks for every injected binding, forced WASD mode, patch metadata, and renderer-side control installation.

### Changed

- Desktop builds now force the native MiniDayZ WASD movement group and remember `WASD` as the selected control scheme.
- Legacy Construct 2 service-worker caches are cleared without touching save storage, preventing older embedded game data from shadowing a desktop update.
- Windows packaging removes a redundant base `electron.exe` when cross-building, reducing the portable executable without changing runtime files.

## [0.1.0] - 2026-07-14

### Added
Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A reproducible Windows desktop port of **MiniDayZ Plus 1.2**. The game runs loca

## Current release

- Desktop port: **v0.1.0**
- Desktop port: **v0.2.0**
- Game build: **MiniDayZ Plus 1.2**
- Platform: Windows 10/11 x64
- Package: portable `.exe` (no installer required)
Expand All @@ -15,10 +15,26 @@ Download the newest executable from [GitHub Releases](https://github.com/lestx05

## Controls and desktop behavior

- `F11`: toggle full screen
- `Alt+F4`: close the game
- Launch with `--windowed`: start in a resizable 4:3 window
- Only one instance runs at a time
| Input | Action |
| --- | --- |
| `WASD` / arrow keys | Move or drive |
| `Space` | Attack, fire, or fire from a vehicle |
| `F` | Alternate attack |
| `E` | Interact, pick up, or leave a vehicle |
| `R` | Reload |
| `Q` / middle mouse button | Cycle weapon |
| `1` / `2` / `3` | Select melee / primary firearm / pistol |
| `Tab` | Open or close inventory |
| `X` / right mouse button | Toggle aim |
| `P` | Open perks and status |
| `G` | Use the equipped flare |
| `T` | Talk |
| `Esc` | Pause and options |
| `F1` | Show or hide the in-game controls card |
| `F11` | Toggle full screen |
| `Alt+F4` | Close the game |

The left mouse button keeps the original touch behavior for menus, inventory, and on-screen controls. Launch with `--windowed` to start in a resizable 4:3 window; only one instance runs at a time.

Game saves use a stable private origin under Electron's `%APPDATA%\MiniDayZ PC` profile, so they persist between launches and desktop-port updates.

Expand All @@ -34,7 +50,7 @@ pnpm install --frozen-lockfile
pnpm start
```

`pnpm start` downloads the pinned upstream MiniDayZ Plus files on first use and verifies the archive's SHA-256 checksum before extracting it. Later runs reuse the verified local copy.
`pnpm start` downloads the pinned upstream MiniDayZ Plus files on first use, verifies the archive's SHA-256 checksum, and applies the versioned desktop-control patch. Later runs reuse the verified local copy and reapply the patch idempotently.

Useful commands:

Expand Down
235 changes: 235 additions & 0 deletions desktop/game-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
'use strict';

(function createDesktopControls(root, factory) {
const api = factory();

if (typeof module === 'object' && module.exports) {
module.exports = api;
}

if (root?.document) {
root.MiniDayZPCControls = api;
api.install(root);
}
}(typeof globalThis === 'undefined' ? this : globalThis, () => {
const VERSION = '0.2.0';
const PATCH_BINDING_COUNT = 17;
const ARROW_ALIASES = Object.freeze({
ArrowDown: { code: 'KeyS', key: 's', keyCode: 83 },
ArrowLeft: { code: 'KeyA', key: 'a', keyCode: 65 },
ArrowRight: { code: 'KeyD', key: 'd', keyCode: 68 },
ArrowUp: { code: 'KeyW', key: 'w', keyCode: 87 },
});
const MOUSE_ALIASES = Object.freeze({
1: { code: 'KeyQ', key: 'q', keyCode: 81 },
2: { code: 'KeyX', key: 'x', keyCode: 88 },
3: { code: 'KeyQ', key: 'q', keyCode: 81 },
4: { code: 'KeyE', key: 'e', keyCode: 69 },
});
const PREVENTED_KEYS = new Set([
' ', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Tab',
]);
const HELP_ROWS = Object.freeze([
['WASD / flechas', 'Moverse o conducir'],
['Espacio', 'Atacar / disparar'],
['F', 'Ataque alternativo'],
['E', 'Interactuar, recoger o salir del vehículo'],
['R', 'Recargar'],
['Q / botón central', 'Cambiar de arma'],
['1 / 2 / 3', 'Cuerpo a cuerpo / arma principal / pistola'],
['Tab', 'Abrir o cerrar inventario'],
['X / clic derecho', 'Activar o desactivar apuntado'],
['P', 'Ventana de ventajas y estado'],
['G', 'Usar bengala equipada'],
['T', 'Hablar'],
['Esc', 'Pausa y opciones'],
['F11', 'Pantalla completa'],
['F1', 'Mostrar u ocultar esta ayuda'],
]);

function isTypingTarget(target) {
const tagName = target?.tagName?.toLowerCase();
return target?.isContentEditable || tagName === 'input' || tagName === 'select' || tagName === 'textarea';
}

function createKeyboardEvent(windowObject, type, binding, repeat = false) {
const event = new windowObject.KeyboardEvent(type, {
bubbles: true,
cancelable: true,
code: binding.code,
key: binding.key,
repeat,
});
Object.defineProperties(event, {
keyCode: { configurable: true, get: () => binding.keyCode },
which: { configurable: true, get: () => binding.keyCode },
});
return event;
}

function dispatchKeyboard(windowObject, type, binding, repeat = false) {
windowObject.document.dispatchEvent(createKeyboardEvent(windowObject, type, binding, repeat));
}

function createHelpOverlay(documentObject) {
const overlay = documentObject.createElement('section');
overlay.id = 'minidayz-pc-controls-help';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-label', 'Controles de MiniDayZ PC');
Object.assign(overlay.style, {
background: 'rgba(8, 12, 10, 0.94)',
border: '1px solid #80906a',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.65)',
color: '#f1f1df',
display: 'none',
font: '14px/1.35 Arial, sans-serif',
left: '50%',
maxHeight: 'calc(100vh - 48px)',
maxWidth: 'min(620px, calc(100vw - 48px))',
overflow: 'auto',
padding: '18px 22px',
pointerEvents: 'none',
position: 'fixed',
top: '50%',
transform: 'translate(-50%, -50%)',
width: '520px',
zIndex: '2147483647',
});

const title = documentObject.createElement('h2');
title.textContent = `MiniDayZ PC ${VERSION} — Controles`;
Object.assign(title.style, { fontSize: '18px', margin: '0 0 12px' });
overlay.append(title);

const table = documentObject.createElement('table');
Object.assign(table.style, { borderCollapse: 'collapse', width: '100%' });
for (const [key, action] of HELP_ROWS) {
const row = documentObject.createElement('tr');
const keyCell = documentObject.createElement('th');
const actionCell = documentObject.createElement('td');
keyCell.textContent = key;
actionCell.textContent = action;
Object.assign(keyCell.style, {
color: '#d7dc8a',
padding: '3px 14px 3px 0',
textAlign: 'left',
whiteSpace: 'nowrap',
});
Object.assign(actionCell.style, { padding: '3px 0' });
row.append(keyCell, actionCell);
table.append(row);
}
overlay.append(table);

const footer = documentObject.createElement('p');
footer.textContent = 'El clic izquierdo conserva la interacción táctil original para menús e inventario.';
Object.assign(footer.style, { color: '#b9b9aa', margin: '12px 0 0' });
overlay.append(footer);
documentObject.body.append(overlay);
return overlay;
}

function install(windowObject) {
if (windowObject.__MINIDAYZ_PC_CONTROLS__?.installed) {
return windowObject.__MINIDAYZ_PC_CONTROLS__;
}

const documentObject = windowObject.document;
let overlay = null;
let overlayVisible = false;
const mouseButtonsDown = new Set();

function setOverlayVisible(visible) {
overlay ??= createHelpOverlay(documentObject);
overlayVisible = visible;
overlay.style.display = visible ? 'block' : 'none';
}

documentObject.addEventListener('keydown', (event) => {
if (event.key === 'F1') {
event.preventDefault();
event.stopImmediatePropagation();
setOverlayVisible(!overlayVisible);
return;
}

if (event.key === 'Escape' && overlayVisible) {
event.preventDefault();
event.stopImmediatePropagation();
setOverlayVisible(false);
return;
}

if (isTypingTarget(event.target)) {
return;
}

const alias = ARROW_ALIASES[event.key];
if (alias) {
event.preventDefault();
dispatchKeyboard(windowObject, 'keydown', alias, event.repeat);
} else if (PREVENTED_KEYS.has(event.key)) {
event.preventDefault();
}
}, true);

documentObject.addEventListener('keyup', (event) => {
if (isTypingTarget(event.target)) {
return;
}
const alias = ARROW_ALIASES[event.key];
if (alias) {
event.preventDefault();
dispatchKeyboard(windowObject, 'keyup', alias);
}
}, true);

documentObject.addEventListener('contextmenu', (event) => {
if (event.target?.closest?.('#c2canvas, #c2canvasdiv')) {
event.preventDefault();
}
}, true);

documentObject.addEventListener('mousedown', (event) => {
const binding = MOUSE_ALIASES[event.button];
if (!binding || !event.target?.closest?.('#c2canvas, #c2canvasdiv')) {
return;
}
event.preventDefault();
mouseButtonsDown.add(event.button);
dispatchKeyboard(windowObject, 'keydown', binding);
}, true);

documentObject.addEventListener('mouseup', (event) => {
const binding = MOUSE_ALIASES[event.button];
if (!binding || !mouseButtonsDown.delete(event.button)) {
return;
}
event.preventDefault();
dispatchKeyboard(windowObject, 'keyup', binding);
}, true);

const status = Object.freeze({
bindings: PATCH_BINDING_COUNT,
installed: true,
version: VERSION,
});
Object.defineProperty(windowObject, '__MINIDAYZ_PC_CONTROLS__', {
configurable: false,
enumerable: false,
value: status,
writable: false,
});
return status;
}

return Object.freeze({
ARROW_ALIASES,
HELP_ROWS,
MOUSE_ALIASES,
PATCH_BINDING_COUNT,
VERSION,
createKeyboardEvent,
install,
});
}));
33 changes: 30 additions & 3 deletions desktop/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const {
GAME_SCHEME,
createAssetResponse,
} = require('./game-protocol');
const { version: DESKTOP_VERSION } = require('../package.json');

const DESKTOP_BINDING_COUNT = 17;

const GAME_URL = `${GAME_SCHEME}://${GAME_HOST}/index.html`;
const GAME_ROOT = path.join(__dirname, '..', 'docs');
Expand All @@ -25,7 +28,6 @@ protocol.registerSchemesAsPrivileged([
{
scheme: GAME_SCHEME,
privileges: {
allowServiceWorkers: true,
bypassCSP: false,
corsEnabled: true,
secure: true,
Expand Down Expand Up @@ -53,7 +55,7 @@ function finishSmokeTest(error) {
console.error(`[smoke-test] ${error.stack || error}`);
app.exit(1);
} else {
console.log('[smoke-test] Construct 2 canvas and runtime loaded successfully.');
console.log('[smoke-test] Construct 2 runtime and MiniDayZ PC controls loaded successfully.');
app.exit(0);
}
}
Expand Down Expand Up @@ -165,8 +167,14 @@ function createMainWindow() {

const assetResponse = await fetch('config.xml', { cache: 'no-store' });
const assetText = await assetResponse.text();
const controlsResponse = await fetch('.minidayz-desktop-patch.json', { cache: 'no-store' });
const controlsPatch = controlsResponse.ok ? await controlsResponse.json() : null;
return {
assetLoaded: assetResponse.ok && assetText.includes('com.bistudio.minidayz.plus'),
controlsBindingCount: window.__MINIDAYZ_PC_CONTROLS__?.bindings,
controlsInstalled: window.__MINIDAYZ_PC_CONTROLS__?.installed === true,
controlsVersion: window.__MINIDAYZ_PC_CONTROLS__?.version,
controlsPatch,
hasCanvas: Boolean(canvas),
hasRuntime: typeof window.cr_createRuntime === 'function',
hasRuntimeInstance: Boolean(canvas?.c2runtime),
Expand All @@ -176,6 +184,12 @@ function createMainWindow() {

if (
!result.assetLoaded
|| result.controlsBindingCount !== DESKTOP_BINDING_COUNT
|| !result.controlsInstalled
|| result.controlsVersion !== DESKTOP_VERSION
|| result.controlsPatch?.version !== DESKTOP_VERSION
|| result.controlsPatch?.bindingCount !== DESKTOP_BINDING_COUNT
|| result.controlsPatch?.movement !== 'WASD'
|| !result.hasCanvas
|| !result.hasRuntime
|| !result.hasRuntimeInstance
Expand Down Expand Up @@ -206,7 +220,20 @@ function createMainWindow() {
}
});

void window.loadURL(GAME_URL);
// The packaged game is already fully local. Clear legacy Construct 2 offline
// workers/caches so an update can never shadow a newer embedded data.js.
void window.webContents.session.clearStorageData({
storages: ['serviceworkers', 'cachestorage'],
}).then(() => window.loadURL(GAME_URL)).catch((error) => {
if (isSmokeTest) {
finishSmokeTest(error);
} else {
dialog.showErrorBox('MiniDayZ PC could not prepare local game files', error.stack || String(error));
}
if (!window.isDestroyed()) {
window.close();
}
});
return window;
}

Expand Down
Loading
Loading