Added save mechanic

This commit is contained in:
Renan LE CARO 2025-03-17 11:50:13 +01:00
parent 7aff5be9cd
commit 659d79bcd0
23 changed files with 333 additions and 20292 deletions

View file

@ -11,8 +11,8 @@ android {
applicationId = "me.lecaro.breakout" applicationId = "me.lecaro.breakout"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 29035871 versionCode = 29036807
versionName = "29035871" versionName = "29036807"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding ="utf-8"?> <?xml version="1.0" encoding ="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

File diff suppressed because one or more lines are too long

View file

@ -1,13 +1,40 @@
package me.lecaro.breakout package me.lecaro.breakout
import android.app.Activity
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.Log import android.util.Log
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import android.webkit.ConsoleMessage import android.webkit.ConsoleMessage
import android.webkit.DownloadListener
import android.webkit.ValueCallback
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebView import android.webkit.WebView
import android.widget.Toast
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
const val CHOOSE_FILE_REQUEST_CODE = 548459
class MainActivity : android.app.Activity() { class MainActivity : android.app.Activity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
CHOOSE_FILE_REQUEST_CODE -> {
if (resultCode == RESULT_OK) {
filePathCallback?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data))
filePathCallback = null
}
}
}
}
var filePathCallback: ValueCallback<Array<Uri>>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
@ -15,11 +42,13 @@ class MainActivity : android.app.Activity() {
WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN WindowManager.LayoutParams.FLAG_FULLSCREEN
); );
// WebView.setWebContentsDebuggingEnabled(true)
val webView = WebView(this) val webView = WebView(this)
webView.settings.javaScriptEnabled = true webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true webView.settings.domStorageEnabled = true
webView.settings.setSupportZoom(false)
webView.loadUrl("file:///android_asset/index.html?isInWebView=true") webView.loadUrl("file:///android_asset/index.html?isInWebView=true")
webView.webChromeClient = object : WebChromeClient() { webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d( Log.d(
@ -28,7 +57,46 @@ class MainActivity : android.app.Activity() {
) )
return true return true
} }
override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
startActivityForResult(fileChooserParams?.createIntent(), CHOOSE_FILE_REQUEST_CODE)
this@MainActivity.filePathCallback = filePathCallback
return true
}
} }
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
if (!url.startsWith("data:")) {
Log.w("DL","url ignored because it does not start with data:")
return@DownloadListener
}
val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm")
val currentDate = sdf.format(Date())
// Extract filename from contentDisposition if available
if (url.startsWith("data:application/json;base64,")) {
Log.d("DL","saving application/json ")
val base64Data = url.substringAfterLast(',')
val decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
val jsonData = String(decodedBytes)
;
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val fileName = "breakout-71-save-$currentDate.b71"
val file = File(dir, fileName)
file.writeText(jsonData)
Toast.makeText(this, "Saved in $dir", Toast. LENGTH_LONG).show()
Log.d("DL","finished saving application/json ")
}else if (url.startsWith("data:video/webm;base64,")){
Log.d("DL","saving video/webm ")
// TODO
Log.d("DL","finished savign video/webm ")
}else{
Log.w("DL","unexpected type "+url)
}
})
setContentView(webView) setContentView(webView)
} }

2
dist/PWA/sw-b71.js vendored
View file

@ -1,2 +1,2 @@
function e(e,t,n,r,a,i,c){try{var o=e[i](c),u=o.value}catch(e){n(e);return}o.done?t(u):Promise.resolve(u).then(r,a)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(a,i){var c=t.apply(n,r);function o(t){e(c,a,i,o,u,"next",t)}function u(t){e(c,a,i,o,u,"throw",t)}o(void 0)})}}function n(e,t){var n,r,a,i,c={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return i={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function o(i){return function(o){return function(i){if(n)throw TypeError("Generator is already executing.");for(;c;)try{if(n=1,r&&(a=2&i[0]?r.return:i[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,i[1])).done)return a;switch(r=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,r=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(a=(a=c.trys).length>0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]<a[3])){c.label=i[1];break}if(6===i[0]&&c.label<a[1]){c.label=a[1],a=i;break}if(a&&c.label<a[2]){c.label=a[2],c.ops.push(i);break}a[2]&&c.ops.pop(),c.trys.pop();continue}i=t.call(e,c)}catch(e){i=[6,e],r=0}finally{n=a=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,o])}}}var r="breakout-71-".concat("29035871"),a=["/"];self.addEventListener("install",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.open(r)];case 1:return e.sent().addAll(a),[2]}})})())}),self.addEventListener("activate",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.keys()];case 1:return[4,Promise.all(e.sent().map(function(e){if(e!==r)return caches.delete(e)}))];case 2:return e.sent(),[4,clients.claim()];case 3:return e.sent(),[2]}})})())}),self.addEventListener("fetch",function(e){if("navigate"===e.request.mode&&e.request.url.endsWith("/index.html?isPWA=true")){e.respondWith(caches.match("/"));return}}); function e(e,t,n,r,a,i,c){try{var o=e[i](c),u=o.value}catch(e){n(e);return}o.done?t(u):Promise.resolve(u).then(r,a)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(a,i){var c=t.apply(n,r);function o(t){e(c,a,i,o,u,"next",t)}function u(t){e(c,a,i,o,u,"throw",t)}o(void 0)})}}function n(e,t){var n,r,a,i,c={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return i={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function o(i){return function(o){return function(i){if(n)throw TypeError("Generator is already executing.");for(;c;)try{if(n=1,r&&(a=2&i[0]?r.return:i[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,i[1])).done)return a;switch(r=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,r=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(a=(a=c.trys).length>0&&a[a.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]<a[3])){c.label=i[1];break}if(6===i[0]&&c.label<a[1]){c.label=a[1],a=i;break}if(a&&c.label<a[2]){c.label=a[2],c.ops.push(i);break}a[2]&&c.ops.pop(),c.trys.pop();continue}i=t.call(e,c)}catch(e){i=[6,e],r=0}finally{n=a=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,o])}}}var r="breakout-71-".concat("29036807"),a=["/"];self.addEventListener("install",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.open(r)];case 1:return e.sent().addAll(a),[2]}})})())}),self.addEventListener("activate",function(e){e.waitUntil(t(function(){return n(this,function(e){switch(e.label){case 0:return[4,caches.keys()];case 1:return[4,Promise.all(e.sent().map(function(e){if(e!==r)return caches.delete(e)}))];case 2:return e.sent(),[4,clients.claim()];case 3:return e.sent(),[2]}})})())}),self.addEventListener("fetch",function(e){if("navigate"===e.request.mode&&e.request.url.endsWith("/index.html?isPWA=true")){e.respondWith(caches.match("/"));return}});
//# sourceMappingURL=sw-b71.js.map //# sourceMappingURL=sw-b71.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16297
dist/editor.1ec04b8f.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,56 +0,0 @@
body {
color: #fff;
background: #000;
}
#palette {
width: 80px;
position: fixed;
top: 0;
bottom: 0;
right: 0;
overflow: auto;
}
#palette button.active {
transform: scale(1.2);
}
#levels {
flex-wrap: wrap;
align-items: flex-start;
gap: 40px;
margin-right: 80px;
display: flex;
}
#levels .level-bricks-preview {
position: relative;
}
#levels input[type="number"] {
width: 50px;
}
#levels > div {
grid-template-columns: auto auto;
grid-template-areas: ". name"
"buttons bricks";
display: grid;
}
#levels > div > :first-child {
grid-area: name;
}
#levels > div > div:nth-child(2) {
flex-direction: column;
grid-area: buttons;
align-items: flex-end;
display: flex;
}
#levels > div > div:nth-child(3) {
grid-area: bricks;
}
/*# sourceMappingURL=editor.9680328c.css.map */

View file

@ -1 +0,0 @@
{"mappings":"AAAA;;;;;AAKA;;;;;;;;;AAAA;;;;AAaA;;;;;;;;AAAA;;;;AAAA;;;;AAcE;;;;;;;AAKE;;;;AAIA;;;;;;;AAOA","sources":["src/level_editor/levels_editor.less"],"sourcesContent":["body {\n background: black;\n color: white;\n}\n\n#palette {\n position: fixed;\n top: 0;\n right: 0;\n width: 80px;\n bottom: 0;\n overflow: auto;\n\n button.active {\n transform: scale(1.2);\n }\n}\n\n#levels {\n display: flex;\n gap: 40px;\n align-items: flex-start;\n flex-wrap: wrap;\n margin-right: 80px;\n\n .level-bricks-preview {\n position: relative;\n }\n input[type=\"number\"] {\n width: 50px;\n }\n\n & > div {\n display: grid;\n grid-template-columns: auto auto;\n grid-template-areas: \". name\" \"buttons bricks\";\n\n & > *:nth-child(1) {\n grid-area: name;\n }\n\n & > div:nth-child(2) {\n grid-area: buttons;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n }\n\n & > div:nth-child(3) {\n grid-area: bricks;\n }\n }\n}\n"],"names":[],"version":3,"file":"editor.9680328c.css.map","sourceRoot":"/__parcel_source_root/"}

14
dist/editor.html vendored
View file

@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<head><script src="/editor.1350aee5.js"></script>
<meta charset="UTF-8">
<title>Level editor</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔨</text></svg>">
<link rel="stylesheet" href="/editor.9680328c.css">
</head>
<body>
<div id="app"></div>
<script src="/editor.1ec04b8f.js" defer=""></script>
</body>
</html>

3687
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
// The version of the cache. // The version of the cache.
const VERSION = "29035871"; const VERSION = "29036807";
// The name of the cache // The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`; const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -1 +1 @@
"29035871" "29036807"

View file

@ -52,12 +52,6 @@ import {
} from "./asyncAlert"; } from "./asyncAlert";
import { isOptionOn, options, toggleOption } from "./options"; import { isOptionOn, options, toggleOption } from "./options";
bombSVG.src =
"data:image/svg+xml;base64," +
btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1528 26.516c-2.6992-3.4942-2.9332-8.281-.58305-11.981a10.454 10.454 0 017.3701-4.7582c1.962-.27726 4.1646.05953 5.8835.90027l.45013.22017.89782-.87417c.83748-.81464.91169-.87499 1.0992-.90271.40528-.058713.58876.03425 1.1971.6116l.55451.52679 1.0821-1.0821c1.1963-1.1963 1.383-1.3357 2.1039-1.5877.57898-.20223 1.5681-.19816 2.1691.00897 1.4613.50314 2.3673 1.7622 2.3567 3.2773-.0058.95654-.24464 1.5795-.90924 2.3746-.40936.48928-.55533.81057-.57898 1.2737-.02039.41018.1109.77714.42322 1.1792.30172.38816.3694.61323.2797.93044-.12803.45666-.56674.71598-1.0242.60507-.601-.14597-1.3031-1.3088-1.3969-2.3126-.09459-1.0161.19245-1.8682.92392-2.7432.42567-.50885.5643-.82851.5643-1.3031 0-.50151-.14026-.83177-.51211-1.2028-.50966-.50966-1.0968-.64829-1.781-.41996l-.37348.12477-2.1006 2.1006.52597.55696c.45421.48194.5325.58876.57898.78855.09622.41588.07502.45014-.88396 1.4548l-.87173.9125.26339.57979a10.193 10.193 0 01.9231 4.1001c.03996 2.046-.41996 3.8082-1.4442 5.537-.55044.928-1.0185 1.5013-1.8968 2.3241-.83503.78284-1.5526 1.2827-2.4904 1.7361-3.4266 1.657-7.4721 1.3422-10.549-.82035-.73473-.51782-1.7312-1.4621-2.2515-2.1357zm21.869-4.5584c-.0579-.19734-.05871-2.2662 0-2.4545.11906-.39142.57898-.63361 1.0038-.53005.23812.05708.54147.32455.6116.5382.06279.19163.06769 2.1805.0065 2.3811-.12558.40773-.61649.67602-1.0462.57164-.234-.05708-.51615-.30498-.57568-.50722m3.0417-2.6013c-.12313-.6222.37837-1.1049 1.0479-1.0079.18348.0261.25279.08399 1.0071.83911.75838.75838.81301.82362.84074 1.0112.10193.68499-.40365 1.1938-1.034 1.0405-.1949-.0473-.28786-.12558-1.0144-.85216-.7649-.76409-.80241-.81057-.84645-1.0316m.61323-3.0629a.85623.85623 0 01.59284-.99975c.28949-.09214 2.1814-.08318 2.3917.01141.38734.17369.6279.61078.53984.98181-.06035.25606-.35391.57327-.60181.64992-.25279.07747-2.2278.053-2.4097-.03017-.26013-.11906-.46318-.36125-.51374-.61323" fill="#fff" opacity="0.3"/>
</svg>`);
export function play() { export function play() {
if (gameState.running) return; if (gameState.running) return;
gameState.running = true; gameState.running = true;
@ -261,8 +255,8 @@ gameCanvas.addEventListener("mouseup", (e) => {
pause(true); pause(true);
} else { } else {
play(); play();
if (isOptionOn("pointerLock")) { if (isOptionOn("pointerLock") && gameCanvas.requestPointerLock) {
gameCanvas.requestPointerLock(); gameCanvas.requestPointerLock().then();
} }
} }
}); });
@ -543,7 +537,6 @@ async function openSettingsPanel() {
for (const key of Object.keys(options) as OptionId[]) { for (const key of Object.keys(options) as OptionId[]) {
if (options[key]) if (options[key])
actions.push({ actions.push({
disabled: options[key].disabled(),
icon: isOptionOn(key) icon: isOptionOn(key)
? icons["icon:checkmark_checked"] ? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"], : icons["icon:checkmark_unchecked"],
@ -655,6 +648,116 @@ async function openSettingsPanel() {
}, },
}); });
actions.push({
text: t("main_menu.download_save_file"),
help: t("main_menu.download_save_file_help"),
async value() {
const localStorageContent: Record<string, string> = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i) as string;
const value = localStorage.getItem(key) as string;
// Store the key-value pair in the object
localStorageContent[key] = value;
}
const dlLink = document.createElement("a");
dlLink.setAttribute(
"href",
"data:application/json;base64," +
btoa(
JSON.stringify({
fileType: "B71-save-file",
appVersion,
localStorageContent,
}),
),
);
dlLink.setAttribute(
"download",
"b71-save-" +
new Date()
.toISOString()
.slice(0, 19)
.replace(/[^0-9]+/gi, "-") +
".b71",
);
document.body.appendChild(dlLink);
dlLink.click();
setTimeout(() => document.body.removeChild(dlLink), 1000);
},
});
actions.push({
text: t("main_menu.load_save_file"),
help: t("main_menu.load_save_file_help"),
async value() {
if (!document.getElementById("save_file_picker")) {
let input: HTMLInputElement = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("id", "save_file_picker");
input.setAttribute("accept", ".b71");
input.style.position = "absolute";
input.style.left = "-1000px";
input.addEventListener("change", async (e) => {
try {
const file = input && input.files?.item(0);
if (file) {
const content = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = function () {
resolve(reader.result?.toString() || "");
};
reader.onerror = function () {
reject(reader.error);
};
// Read the file as a text string
reader.readAsText(file);
});
const {
fileType,
appVersion: fileVersion,
localStorageContent,
} = JSON.parse(content);
if (fileType !== "B71-save-file")
throw new Error("Not a B71 save file");
if (fileVersion > appVersion)
throw new Error(
"Please update your app first, this file is for version " +
fileVersion +
" or newer.",
);
localStorage.clear();
for (let key in localStorageContent) {
localStorage.setItem(key, localStorageContent[key]);
}
await asyncAlert({
title: t("main_menu.save_file_loaded"),
text: t("main_menu.save_file_loaded_help"),
actions: [{ text: t("main_menu.save_file_loaded_ok") }],
});
window.location.reload();
}
} catch (e: any) {
await asyncAlert({
title: t("main_menu.save_file_error"),
text: e.message,
actions: [{ text: t("main_menu.save_file_loaded_ok") }],
});
}
input.value = "";
});
document.body.appendChild(input);
}
document.getElementById("save_file_picker")?.click();
},
});
actions.push({ actions.push({
text: t("main_menu.language"), text: t("main_menu.language"),
help: t("main_menu.language_help"), help: t("main_menu.language_help"),

View file

@ -622,6 +622,36 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>download_save_file</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>download_save_file_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>footer_html</name> <name>footer_html</name>
<description/> <description/>
@ -757,6 +787,36 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>load_save_file</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>load_save_file_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>mobile</name> <name>mobile</name>
<description/> <description/>
@ -967,6 +1027,66 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>save_file_error</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>save_file_loaded</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>save_file_loaded_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>save_file_loaded_ok</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>sounds</name> <name>sounds</name>
<description/> <description/>

View file

@ -37,6 +37,8 @@
"level_up.upgrade_perk_to_level": " lvl {{level}}", "level_up.upgrade_perk_to_level": " lvl {{level}}",
"main_menu.basic": "Basic graphics", "main_menu.basic": "Basic graphics",
"main_menu.basic_help": "Fewer particles and flashes, better performance.", "main_menu.basic_help": "Fewer particles and flashes, better performance.",
"main_menu.download_save_file": "Download save file",
"main_menu.download_save_file_help": "Get a transferable .b71 file with your score and stats",
"main_menu.footer_html": " <p> <span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n <span>v.{{appVersion}}</span></p>", "main_menu.footer_html": " <p> <span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n <span>v.{{appVersion}}</span></p>",
"main_menu.fullscreen": "Fullscreen", "main_menu.fullscreen": "Fullscreen",
"main_menu.fullscreen_exit": "Exit Fullscreen", "main_menu.fullscreen_exit": "Exit Fullscreen",
@ -46,6 +48,8 @@
"main_menu.kid_help": "Start future runs with \"slower ball\".", "main_menu.kid_help": "Start future runs with \"slower ball\".",
"main_menu.language": "Language", "main_menu.language": "Language",
"main_menu.language_help": "Choose the game's language", "main_menu.language_help": "Choose the game's language",
"main_menu.load_save_file": "Load save file",
"main_menu.load_save_file_help": "Select a .b71 file on your device",
"main_menu.mobile": "Mobile mode", "main_menu.mobile": "Mobile mode",
"main_menu.mobile_help": "Leaves space for your thumb under the puck.", "main_menu.mobile_help": "Leaves space for your thumb under the puck.",
"main_menu.pointer_lock": "Mouse pointer lock", "main_menu.pointer_lock": "Mouse pointer lock",
@ -60,6 +64,10 @@
"main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?", "main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?",
"main_menu.resume": "Resume", "main_menu.resume": "Resume",
"main_menu.resume_help": "Return to your run", "main_menu.resume_help": "Return to your run",
"main_menu.save_file_error": "Error loading save file",
"main_menu.save_file_loaded": "Save file loaded",
"main_menu.save_file_loaded_help": "The app will now reload to apply your save",
"main_menu.save_file_loaded_ok": "Ok",
"main_menu.sounds": "Game sounds", "main_menu.sounds": "Game sounds",
"main_menu.sounds_help": "Can slow down some phones.", "main_menu.sounds_help": "Can slow down some phones.",
"main_menu.title": "Breakout 71", "main_menu.title": "Breakout 71",

View file

@ -37,6 +37,8 @@
"level_up.upgrade_perk_to_level": " niveau {{level}}", "level_up.upgrade_perk_to_level": " niveau {{level}}",
"main_menu.basic": "Graphismes simplifiés", "main_menu.basic": "Graphismes simplifiés",
"main_menu.basic_help": "Moins de particules et effets, meilleures performances.", "main_menu.basic_help": "Moins de particules et effets, meilleures performances.",
"main_menu.download_save_file": "Sauvegarder mes progrès",
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde .b71 transférable",
"main_menu.footer_html": " <p> <span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a> <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a> <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a> <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a> <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a> <span>v.{{appVersion}}</span></p>", "main_menu.footer_html": " <p> <span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a> <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a> <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a> <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a> <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a> <span>v.{{appVersion}}</span></p>",
"main_menu.fullscreen": "Plein écran", "main_menu.fullscreen": "Plein écran",
"main_menu.fullscreen_exit": "Quitter le plein écran", "main_menu.fullscreen_exit": "Quitter le plein écran",
@ -46,6 +48,8 @@
"main_menu.kid_help": "Balle plus lente", "main_menu.kid_help": "Balle plus lente",
"main_menu.language": "Langue", "main_menu.language": "Langue",
"main_menu.language_help": "Changer la langue d'affichage", "main_menu.language_help": "Changer la langue d'affichage",
"main_menu.load_save_file": "Charger une sauvegarde",
"main_menu.load_save_file_help": "Sélectionnez un fichier .b71 sur votre appareil ",
"main_menu.mobile": "Mode mobile", "main_menu.mobile": "Mode mobile",
"main_menu.mobile_help": "Laisse un espace pour le pouce sous le palet.", "main_menu.mobile_help": "Laisse un espace pour le pouce sous le palet.",
"main_menu.pointer_lock": "Verrouillage du pointeur de la souris", "main_menu.pointer_lock": "Verrouillage du pointeur de la souris",
@ -60,6 +64,10 @@
"main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?", "main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?",
"main_menu.resume": "Retourner à la partie", "main_menu.resume": "Retourner à la partie",
"main_menu.resume_help": "Continuer la partie en cours", "main_menu.resume_help": "Continuer la partie en cours",
"main_menu.save_file_error": "Erreur lors du chargement du fichier de sauvegarde",
"main_menu.save_file_loaded": "Sauvegarde chargée",
"main_menu.save_file_loaded_help": "L'appli va redémarrer",
"main_menu.save_file_loaded_ok": "Ok",
"main_menu.sounds": "Sons du jeu", "main_menu.sounds": "Sons du jeu",
"main_menu.sounds_help": "Ralentis certains téléphones.", "main_menu.sounds_help": "Ralentis certains téléphones.",
"main_menu.title": "Breakout 71", "main_menu.title": "Breakout 71",

View file

@ -8,40 +8,32 @@ export const options = {
default: true, default: true,
name: t("main_menu.sounds"), name: t("main_menu.sounds"),
help: t("main_menu.sounds_help"), help: t("main_menu.sounds_help"),
disabled: () => false,
}, },
"mobile-mode": { "mobile-mode": {
default: window.innerHeight > window.innerWidth, default: window.innerHeight > window.innerWidth,
name: t("main_menu.mobile"), name: t("main_menu.mobile"),
help: t("main_menu.mobile_help"), help: t("main_menu.mobile_help"),
disabled: () => false,
}, },
basic: { basic: {
default: false, default: false,
name: t("main_menu.basic"), name: t("main_menu.basic"),
help: t("main_menu.basic_help"), help: t("main_menu.basic_help"),
disabled: () => false,
}, },
pointerLock: { pointerLock: {
default: false, default: false,
name: t("main_menu.pointer_lock"), name: t("main_menu.pointer_lock"),
help: t("main_menu.pointer_lock_help"), help: t("main_menu.pointer_lock_help"),
disabled: () => !document.body.requestPointerLock,
}, },
easy: { easy: {
default: false, default: false,
name: t("main_menu.kid"), name: t("main_menu.kid"),
help: t("main_menu.kid_help"), help: t("main_menu.kid_help"),
disabled: () => false,
}, },
// Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app // Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app
record: { record: {
default: false, default: false,
name: t("main_menu.record"), name: t("main_menu.record"),
help: t("main_menu.record_help"), help: t("main_menu.record_help"),
disabled() {
return window.location.search.includes("isInWebView=true");
},
}, },
} as const satisfies { [k: string]: OptionDef }; } as const satisfies { [k: string]: OptionDef };

View file

@ -16,6 +16,13 @@ export const ctx = gameCanvas.getContext("2d", {
alpha: false, alpha: false,
}) as CanvasRenderingContext2D; }) as CanvasRenderingContext2D;
export const bombSVG = document.createElement("img"); export const bombSVG = document.createElement("img");
bombSVG.src =
"data:image/svg+xml;base64," +
btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1528 26.516c-2.6992-3.4942-2.9332-8.281-.58305-11.981a10.454 10.454 0 017.3701-4.7582c1.962-.27726 4.1646.05953 5.8835.90027l.45013.22017.89782-.87417c.83748-.81464.91169-.87499 1.0992-.90271.40528-.058713.58876.03425 1.1971.6116l.55451.52679 1.0821-1.0821c1.1963-1.1963 1.383-1.3357 2.1039-1.5877.57898-.20223 1.5681-.19816 2.1691.00897 1.4613.50314 2.3673 1.7622 2.3567 3.2773-.0058.95654-.24464 1.5795-.90924 2.3746-.40936.48928-.55533.81057-.57898 1.2737-.02039.41018.1109.77714.42322 1.1792.30172.38816.3694.61323.2797.93044-.12803.45666-.56674.71598-1.0242.60507-.601-.14597-1.3031-1.3088-1.3969-2.3126-.09459-1.0161.19245-1.8682.92392-2.7432.42567-.50885.5643-.82851.5643-1.3031 0-.50151-.14026-.83177-.51211-1.2028-.50966-.50966-1.0968-.64829-1.781-.41996l-.37348.12477-2.1006 2.1006.52597.55696c.45421.48194.5325.58876.57898.78855.09622.41588.07502.45014-.88396 1.4548l-.87173.9125.26339.57979a10.193 10.193 0 01.9231 4.1001c.03996 2.046-.41996 3.8082-1.4442 5.537-.55044.928-1.0185 1.5013-1.8968 2.3241-.83503.78284-1.5526 1.2827-2.4904 1.7361-3.4266 1.657-7.4721 1.3422-10.549-.82035-.73473-.51782-1.7312-1.4621-2.2515-2.1357zm21.869-4.5584c-.0579-.19734-.05871-2.2662 0-2.4545.11906-.39142.57898-.63361 1.0038-.53005.23812.05708.54147.32455.6116.5382.06279.19163.06769 2.1805.0065 2.3811-.12558.40773-.61649.67602-1.0462.57164-.234-.05708-.51615-.30498-.57568-.50722m3.0417-2.6013c-.12313-.6222.37837-1.1049 1.0479-1.0079.18348.0261.25279.08399 1.0071.83911.75838.75838.81301.82362.84074 1.0112.10193.68499-.40365 1.1938-1.034 1.0405-.1949-.0473-.28786-.12558-1.0144-.85216-.7649-.76409-.80241-.81057-.84645-1.0316m.61323-3.0629a.85623.85623 0 01.59284-.99975c.28949-.09214 2.1814-.08318 2.3917.01141.38734.17369.6279.61078.53984.98181-.06035.25606-.35391.57327-.60181.64992-.25279.07747-2.2278.053-2.4097-.03017-.26013-.11906-.46318-.36125-.51374-.61323" fill="#fff" opacity="0.3"/>
</svg>`);
export const background = document.createElement("img"); export const background = document.createElement("img");
export const backgroundCanvas = document.createElement("canvas"); export const backgroundCanvas = document.createElement("canvas");

1
src/types.d.ts vendored
View file

@ -248,6 +248,5 @@ export type OptionDef = {
default: boolean; default: boolean;
name: string; name: string;
help: string; help: string;
disabled: () => boolean;
}; };
export type OptionId = keyof typeof options; export type OptionId = keyof typeof options;