mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 04:05:06 -04:00
Trying to get file dowload to work
This commit is contained in:
parent
5ca2d58c9d
commit
ffdbd71a88
28 changed files with 1525 additions and 5236 deletions
|
@ -3,6 +3,8 @@
|
|||
Break colourful bricks, catch bouncing coins and select powerful upgrades !
|
||||
|
||||
- [Play now](https://breakout.lecaro.me/)
|
||||
- [Donate](https://paypal.me/renanlecaro)
|
||||
- [Discord](https://discord.gg/DZSPqyJkwP)
|
||||
- [Post your comments on itch.io](https://renanlecaro.itch.io/breakout71)
|
||||
- [Help and tips about the game](./Help.md)
|
||||
- [Credits](./Credits.md)
|
||||
|
@ -10,7 +12,6 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
|
|||
- [Google Play](https://play.google.com/store/apps/details?id=me.lecaro.breakout)
|
||||
- [GitLab](https://gitlab.com/lecarore/breakout71)
|
||||
- [HackerNews thread](https://news.ycombinator.com/item?id=43183131)
|
||||
- [Donate](https://github.com/sponsors/renanlecaro)
|
||||
|
||||
|
||||
# System requirements
|
||||
|
@ -22,8 +23,7 @@ There's also an easy mode for kids (slower ball).
|
|||
|
||||
|
||||
# Next
|
||||
|
||||
- separate particles by type, reuse coins and particles
|
||||
- check which color you get if picking a color related perk
|
||||
- sturdy bricks map of remaining hits
|
||||
|
||||
# bugs
|
||||
|
@ -159,6 +159,9 @@ There's also an easy mode for kids (slower ball).
|
|||
- [colin] perk: analyzer - permet de voir les caractéristiques cachées des blocs (sturdy…)
|
||||
- [colin] perk: roulette - gagne instantanément 2 perks aléatoires
|
||||
- let coins go out of bounds left and right, where they'll get lost, but +1 combo per brick
|
||||
- more combo if no coin catch
|
||||
- combo climbs every time a ball bounces on puck (but bounce is random?)
|
||||
-
|
||||
|
||||
# extra levels
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ android {
|
|||
applicationId = "me.lecaro.breakout"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 29036807
|
||||
versionName = "29036807"
|
||||
versionCode = 29038230
|
||||
versionName = "29038230"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<?xml version="1.0" encoding ="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,10 @@
|
|||
package me.lecaro.breakout
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
|
@ -18,8 +20,11 @@ import android.widget.Toast
|
|||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.jar.Manifest
|
||||
|
||||
const val CHOOSE_FILE_REQUEST_CODE = 548459
|
||||
const val PERM_REQUEST_CODE = 66622635
|
||||
|
||||
class MainActivity : android.app.Activity() {
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
|
@ -28,13 +33,73 @@ class MainActivity : android.app.Activity() {
|
|||
when (requestCode) {
|
||||
CHOOSE_FILE_REQUEST_CODE -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
filePathCallback?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data))
|
||||
filePathCallback?.onReceiveValue(
|
||||
WebChromeClient.FileChooserParams.parseResult(
|
||||
resultCode,
|
||||
data
|
||||
)
|
||||
)
|
||||
filePathCallback = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
PERM_REQUEST_CODE -> {
|
||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
downloadFile()
|
||||
} else {
|
||||
Toast.makeText(this, "We cant make a save file without that permission", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
var filePathCallback: ValueCallback<Array<Uri>>? = null
|
||||
var fileToDownload:String? = null
|
||||
|
||||
fun downloadFile(){
|
||||
val url = fileToDownload ?: return
|
||||
try{
|
||||
if (!url.startsWith("data:")) {
|
||||
Log.w("DL", "url ignored because it does not start with data:")
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
}catch (e:Exception){
|
||||
Log.e("DL", "Error ${e.message}")
|
||||
Toast.makeText(this, "Error ${e.message}", Toast.LENGTH_LONG).show()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
@ -46,8 +111,9 @@ class MainActivity : android.app.Activity() {
|
|||
webView.settings.javaScriptEnabled = 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")
|
||||
val activity=this;
|
||||
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
|
||||
|
@ -57,43 +123,35 @@ class MainActivity : android.app.Activity() {
|
|||
)
|
||||
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
|
||||
|
||||
override fun onShowFileChooser(
|
||||
webView: WebView?,
|
||||
filePathCallback: ValueCallback<Array<Uri>>?,
|
||||
fileChooserParams: FileChooserParams?
|
||||
): Boolean {
|
||||
try{
|
||||
|
||||
startActivityForResult(fileChooserParams?.createIntent(), CHOOSE_FILE_REQUEST_CODE)
|
||||
this@MainActivity.filePathCallback = filePathCallback
|
||||
return true
|
||||
}catch (e:Exception){
|
||||
Log.e("DL", "Error ${e.message}")
|
||||
Toast.makeText(activity, "Error ${e.message}", Toast.LENGTH_LONG).show()
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ")
|
||||
fileToDownload = url
|
||||
if (activity.checkSelfPermission( android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
activity.requestPermissions(arrayOf( android.Manifest.permission.WRITE_EXTERNAL_STORAGE), PERM_REQUEST_CODE)
|
||||
}else{
|
||||
Log.w("DL","unexpected type "+url)
|
||||
downloadFile()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
|
@ -1,19 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
<include domain="sharedpref"/>
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
<include domain="sharedpref"/>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
8
build.sh
8
build.sh
|
@ -5,7 +5,6 @@
|
|||
defaultVersionCode=$(($(date +%s) / 60))
|
||||
versionCode=${1:-$defaultVersionCode}
|
||||
|
||||
# TODO crash without app version
|
||||
|
||||
source ~/.nvm/nvm.sh;
|
||||
|
||||
|
@ -17,13 +16,6 @@ if [[ $(node --version) != v21* ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if grep -rE "T[O]DO|F[I]XME|console\.log" src
|
||||
then
|
||||
echo "You have left some TO""DO or logs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
if grep -rE "T[O]DO|F[I]XME|console\.log" src
|
||||
then
|
||||
echo "You have left some TO""DO or logs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
|
|
33
dist/PWA/sw-b71.js
vendored
33
dist/PWA/sw-b71.js
vendored
|
@ -1,33 +1,2 @@
|
|||
// The version of the cache.
|
||||
const VERSION = "29036807";
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||
// The static resources that the app needs to function.
|
||||
const APP_STATIC_RESOURCES = [
|
||||
"/"
|
||||
];
|
||||
// On install, cache the static resources
|
||||
self.addEventListener("install", (event)=>{
|
||||
event.waitUntil((async ()=>{
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.addAll(APP_STATIC_RESOURCES);
|
||||
})());
|
||||
});
|
||||
// delete old caches on activate
|
||||
self.addEventListener("activate", (event)=>{
|
||||
event.waitUntil((async ()=>{
|
||||
const names = await caches.keys();
|
||||
await Promise.all(names.map((name)=>{
|
||||
if (name !== CACHE_NAME) return caches.delete(name);
|
||||
}));
|
||||
await clients.claim();
|
||||
})());
|
||||
});
|
||||
self.addEventListener("fetch", (event)=>{
|
||||
if (event.request.mode === "navigate" && event.request.url.endsWith("/index.html?isPWA=true")) {
|
||||
event.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("29038230"),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
|
||||
|
|
2
dist/PWA/sw-b71.js.map
vendored
2
dist/PWA/sw-b71.js.map
vendored
File diff suppressed because one or more lines are too long
3849
dist/index.html
vendored
3849
dist/index.html
vendored
File diff suppressed because one or more lines are too long
19
package-lock.json
generated
19
package-lock.json
generated
|
@ -25,6 +25,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@parcel/optimizer-data-url": "^2.13.3",
|
||||
"@parcel/packager-raw-url": "^2.13.3",
|
||||
"@parcel/transformer-inline-string": "^2.13.3",
|
||||
"@parcel/transformer-less": "^2.13.3",
|
||||
"@parcel/transformer-webmanifest": "^2.13.3",
|
||||
|
@ -1669,6 +1670,24 @@
|
|||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/packager-raw-url": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/packager-raw-url/-/packager-raw-url-2.13.3.tgz",
|
||||
"integrity": "sha512-Dc8WeVagLGEUzVP4FqJBljXN59XSkvLoZaHeysvN9P33eznocrhIvc9T/OAQjOmsCj18X8jwxm0dIE7LNJbVCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@parcel/plugin": "2.13.3",
|
||||
"@parcel/utils": "2.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.0.0",
|
||||
"parcel": "^2.13.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/packager-svg": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.13.3.tgz",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@parcel/optimizer-data-url": "^2.13.3",
|
||||
"@parcel/packager-raw-url": "^2.13.3",
|
||||
"@parcel/transformer-inline-string": "^2.13.3",
|
||||
"@parcel/transformer-less": "^2.13.3",
|
||||
"@parcel/transformer-webmanifest": "^2.13.3",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// The version of the cache.
|
||||
const VERSION = "29036807";
|
||||
const VERSION = "29038230";
|
||||
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||
|
|
|
@ -1 +1 @@
|
|||
"29036807"
|
||||
"29038230"
|
||||
|
|
79
src/game.ts
79
src/game.ts
|
@ -3,12 +3,15 @@ import {
|
|||
Ball,
|
||||
Coin,
|
||||
GameState,
|
||||
LightFlash,
|
||||
OptionId,
|
||||
ParticleFlash,
|
||||
PerkId,
|
||||
RunParams,
|
||||
TextFlash,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import {getAudioContext, playPendingSounds} from "./sounds";
|
||||
import { getAudioContext, playPendingSounds } from "./sounds";
|
||||
import {
|
||||
currentLevelInfo,
|
||||
getRowColIndex,
|
||||
|
@ -20,6 +23,8 @@ import "./PWA/sw_loader";
|
|||
import { getCurrentLang, t } from "./i18n/i18n";
|
||||
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
|
||||
import {
|
||||
empty,
|
||||
forEachLiveOne,
|
||||
gameStateTick,
|
||||
normalizeGameState,
|
||||
pickRandomUpgrades,
|
||||
|
@ -51,11 +56,12 @@ import {
|
|||
closeModal,
|
||||
} from "./asyncAlert";
|
||||
import { isOptionOn, options, toggleOption } from "./options";
|
||||
import {hashCode} from "./getLevelBackground";
|
||||
import { hashCode } from "./getLevelBackground";
|
||||
|
||||
export function play() {
|
||||
if (gameState.running) return;
|
||||
gameState.running = true;
|
||||
gameState.ballStickToPuck = false;
|
||||
|
||||
startRecordingGame(gameState);
|
||||
getAudioContext()?.resume();
|
||||
|
@ -95,6 +101,10 @@ export function pause(playerAskedForPause: boolean) {
|
|||
}
|
||||
|
||||
export const fitSize = () => {
|
||||
const past_off = gameState.offsetXRoundedDown,
|
||||
past_width = gameState.gameZoneWidthRoundedUp,
|
||||
past_heigh = gameState.gameZoneHeight;
|
||||
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
gameState.canvasWidth = width;
|
||||
gameState.canvasHeight = height;
|
||||
|
@ -123,10 +133,27 @@ export const fitSize = () => {
|
|||
backgroundCanvas.title = "resized";
|
||||
// Ensure puck stays within bounds
|
||||
setMousePos(gameState, gameState.puckPosition);
|
||||
gameState.coins = [];
|
||||
gameState.flashes = [];
|
||||
|
||||
function mapXY(item: ParticleFlash | TextFlash | LightFlash) {
|
||||
item.x =
|
||||
gameState.offsetXRoundedDown +
|
||||
((item.x - past_off) / past_width) * gameState.gameZoneWidthRoundedUp;
|
||||
item.y = (item.y / past_heigh) * gameState.gameZoneHeight;
|
||||
}
|
||||
function mapXYPastCoord(coin: Coin | Ball) {
|
||||
coin.x =
|
||||
gameState.offsetXRoundedDown +
|
||||
((coin.x - past_off) / past_width) * gameState.gameZoneWidthRoundedUp;
|
||||
coin.y = (coin.y / past_heigh) * gameState.gameZoneHeight;
|
||||
coin.previousX = coin.x;
|
||||
coin.previousY = coin.y;
|
||||
}
|
||||
gameState.balls.forEach(mapXYPastCoord);
|
||||
forEachLiveOne(gameState.coins, mapXYPastCoord);
|
||||
forEachLiveOne(gameState.particles, mapXY);
|
||||
forEachLiveOne(gameState.texts, mapXY);
|
||||
forEachLiveOne(gameState.lights, mapXY);
|
||||
pause(true);
|
||||
putBallsAtPuck(gameState);
|
||||
// For safari mobile https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||
document.documentElement.style.setProperty(
|
||||
"--vh",
|
||||
|
@ -273,7 +300,9 @@ gameCanvas.addEventListener("mousemove", (e) => {
|
|||
gameCanvas.addEventListener("touchstart", (e) => {
|
||||
e.preventDefault();
|
||||
if (!e.touches?.length) return;
|
||||
|
||||
setMousePos(gameState, e.touches[0].pageX);
|
||||
normalizeGameState(gameState);
|
||||
play();
|
||||
});
|
||||
gameCanvas.addEventListener("touchend", (e) => {
|
||||
|
@ -442,7 +471,6 @@ export function tick() {
|
|||
gameState.puckPosition + gameState.keyboardPuckSpeed,
|
||||
);
|
||||
}
|
||||
|
||||
normalizeGameState(gameState);
|
||||
|
||||
if (gameState.running) {
|
||||
|
@ -457,8 +485,8 @@ export function tick() {
|
|||
if (gameState.running) {
|
||||
recordOneFrame(gameState);
|
||||
}
|
||||
if(isOptionOn('sound') ){
|
||||
playPendingSounds(gameState)
|
||||
if (isOptionOn("sound")) {
|
||||
playPendingSounds(gameState);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
@ -514,10 +542,13 @@ async function openScorePanel() {
|
|||
}
|
||||
}
|
||||
|
||||
document.getElementById("menu")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
openSettingsPanel();
|
||||
});
|
||||
(document.getElementById("menu") as HTMLButtonElement).addEventListener(
|
||||
"click",
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
openSettingsPanel();
|
||||
},
|
||||
);
|
||||
|
||||
async function openSettingsPanel() {
|
||||
pause(true);
|
||||
|
@ -665,7 +696,7 @@ async function openSettingsPanel() {
|
|||
localStorageContent[key] = value;
|
||||
}
|
||||
|
||||
const signedPayload=JSON.stringify(localStorageContent)
|
||||
const signedPayload = JSON.stringify(localStorageContent);
|
||||
const dlLink = document.createElement("a");
|
||||
|
||||
dlLink.setAttribute(
|
||||
|
@ -676,7 +707,10 @@ async function openSettingsPanel() {
|
|||
fileType: "B71-save-file",
|
||||
appVersion,
|
||||
signedPayload,
|
||||
key: hashCode('Security by obscurity, but really the game is oss so eh'+signedPayload)
|
||||
key: hashCode(
|
||||
"Security by obscurity, but really the game is oss so eh" +
|
||||
signedPayload,
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -727,7 +761,8 @@ async function openSettingsPanel() {
|
|||
const {
|
||||
fileType,
|
||||
appVersion: fileVersion,
|
||||
signedPayload,key
|
||||
signedPayload,
|
||||
key,
|
||||
} = JSON.parse(content);
|
||||
if (fileType !== "B71-save-file")
|
||||
throw new Error("Not a B71 save file");
|
||||
|
@ -738,11 +773,17 @@ async function openSettingsPanel() {
|
|||
" or newer.",
|
||||
);
|
||||
|
||||
if(key!== hashCode('Security by obscurity, but really the game is oss so eh'+signedPayload)){
|
||||
throw new Error("Key does not match content.")
|
||||
if (
|
||||
key !==
|
||||
hashCode(
|
||||
"Security by obscurity, but really the game is oss so eh" +
|
||||
signedPayload,
|
||||
)
|
||||
) {
|
||||
throw new Error("Key does not match content.");
|
||||
}
|
||||
|
||||
const localStorageContent=JSON.parse(signedPayload)
|
||||
const localStorageContent = JSON.parse(signedPayload);
|
||||
localStorage.clear();
|
||||
for (let key in localStorageContent) {
|
||||
localStorage.setItem(key, localStorageContent[key]);
|
||||
|
@ -982,4 +1023,4 @@ tick();
|
|||
// @ts-ignore
|
||||
// window.stressTest= ()=>restart({level:'Shark',perks:{base_combo:100, pierce:10, multiball:8}})
|
||||
window.stressTest = () =>
|
||||
restart({ level: "Shark", perks: { sapper: 2, pierce: 10, multiball: 3 } });
|
||||
restart({ level: "Bird", perks: { sapper: 2, pierce: 10, multiball: 3 } });
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
import {Ball, GameState, PerkId, PerksMap} from "./types";
|
||||
import {icons, upgrades} from "./loadGameData";
|
||||
import { Ball, GameState, PerkId, PerksMap } from "./types";
|
||||
import { icons, upgrades } from "./loadGameData";
|
||||
|
||||
export function getMajorityValue(arr: string[]): string {
|
||||
const count: { [k: string]: number } = {};
|
||||
|
@ -101,16 +101,16 @@ export function distanceBetween(
|
|||
}
|
||||
|
||||
export function defaultSounds() {
|
||||
return {
|
||||
aboutToPlaySound: {
|
||||
wallBeep: {vol: 0, x: 0},
|
||||
comboIncreaseMaybe: {vol: 0, x: 0},
|
||||
comboDecrease: {vol: 0, x: 0},
|
||||
coinBounce: {vol: 0, x: 0},
|
||||
explode: {vol: 0, x: 0},
|
||||
lifeLost: {vol: 0, x: 0},
|
||||
coinCatch: {vol: 0, x: 0},
|
||||
colorChange: {vol: 0, x: 0},
|
||||
}
|
||||
}
|
||||
return {
|
||||
aboutToPlaySound: {
|
||||
wallBeep: { vol: 0, x: 0 },
|
||||
comboIncreaseMaybe: { vol: 0, x: 0 },
|
||||
comboDecrease: { vol: 0, x: 0 },
|
||||
coinBounce: { vol: 0, x: 0 },
|
||||
explode: { vol: 0, x: 0 },
|
||||
lifeLost: { vol: 0, x: 0 },
|
||||
coinCatch: { vol: 0, x: 0 },
|
||||
colorChange: { vol: 0, x: 0 },
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<babeledit_project be_version="5.3.0" version="1.3">
|
||||
<babeledit_project be_version="5.2.0" version="1.3">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
|
@ -3353,7 +3353,6 @@
|
|||
</package_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<embedded_source_texts>false</embedded_source_texts>
|
||||
<isTemplateProject>false</isTemplateProject>
|
||||
<languages>
|
||||
<language>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"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> \n<span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donate</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</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<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n<span>v.{{appVersion}}</span>\n</p>\n",
|
||||
"main_menu.fullscreen": "Fullscreen",
|
||||
"main_menu.fullscreen_exit": "Exit Fullscreen",
|
||||
"main_menu.fullscreen_exit_help": "Might not work on some machines",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"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> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</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\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>",
|
||||
"main_menu.fullscreen": "Plein écran",
|
||||
"main_menu.fullscreen_exit": "Quitter le plein écran",
|
||||
"main_menu.fullscreen_exit_help": "Peut ne pas fonctionner sur certaines machines",
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import {GameState, RunParams} from "./types";
|
||||
import {getTotalScore} from "./settings";
|
||||
import {allLevels, upgrades} from "./loadGameData";
|
||||
import {defaultSounds, getPossibleUpgrades, makeEmptyPerksMap, sumOfKeys,} from "./game_utils";
|
||||
import {dontOfferTooSoon, resetBalls} from "./gameStateMutators";
|
||||
import {isOptionOn} from "./options";
|
||||
|
||||
import { GameState, RunParams } from "./types";
|
||||
import { getTotalScore } from "./settings";
|
||||
import { allLevels, upgrades } from "./loadGameData";
|
||||
import {
|
||||
defaultSounds,
|
||||
getPossibleUpgrades,
|
||||
makeEmptyPerksMap,
|
||||
sumOfKeys,
|
||||
} from "./game_utils";
|
||||
import { dontOfferTooSoon, resetBalls } from "./gameStateMutators";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export function newGameState(params: RunParams): GameState {
|
||||
const totalScoreAtRunStart = getTotalScore();
|
||||
|
@ -33,6 +37,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
combo: 1,
|
||||
gridSize: 12,
|
||||
running: false,
|
||||
ballStickToPuck: true,
|
||||
puckPosition: 400,
|
||||
pauseTimeout: null,
|
||||
canvasWidth: 0,
|
||||
|
@ -50,10 +55,10 @@ export function newGameState(params: RunParams): GameState {
|
|||
balls: [],
|
||||
ballsColor: "white",
|
||||
bricks: [],
|
||||
lights: {indexMin:0,list:[]},
|
||||
particles: {indexMin:0,list:[]},
|
||||
texts: {indexMin:0,list:[]},
|
||||
coins: {indexMin:0,list:[]},
|
||||
lights: { indexMin: 0, total: 0, list: [] },
|
||||
particles: { indexMin: 0, total: 0, list: [] },
|
||||
texts: { indexMin: 0, total: 0, list: [] },
|
||||
coins: { indexMin: 0, total: 0, list: [] },
|
||||
levelStartScore: 0,
|
||||
levelMisses: 0,
|
||||
levelSpawnedCoins: 0,
|
||||
|
@ -90,7 +95,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
levelWallBounces: 0,
|
||||
needsRender: true,
|
||||
autoCleanUses: 0,
|
||||
...defaultSounds()
|
||||
...defaultSounds(),
|
||||
};
|
||||
resetBalls(gameState);
|
||||
|
||||
|
@ -109,4 +114,3 @@ export function newGameState(params: RunParams): GameState {
|
|||
}
|
||||
return gameState;
|
||||
}
|
||||
|
||||
|
|
|
@ -323,12 +323,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "sturdy_bricks",
|
||||
max: 4,
|
||||
name: t("upgrades.telekinesis.name"),
|
||||
name: t("upgrades.sturdy_bricks.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.telekinesis.help")
|
||||
: t("upgrades.telekinesis.help_plural"),
|
||||
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||
? t("upgrades.sturdy_bricks.help")
|
||||
: t("upgrades.sturdy_bricks.help_plural"),
|
||||
fullHelp: t("upgrades.sturdy_bricks.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { baseCombo } from "./gameStateMutators";
|
||||
import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators";
|
||||
import {
|
||||
brickCenterX,
|
||||
brickCenterY,
|
||||
|
@ -54,9 +54,8 @@ export function render(gameState: GameState) {
|
|||
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
ctx.globalAlpha = 0.6;
|
||||
gameState.coins.forEach((coin) => {
|
||||
if (!coin.destroyed)
|
||||
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
||||
});
|
||||
gameState.balls.forEach((ball) => {
|
||||
drawFuzzyBall(
|
||||
|
@ -81,17 +80,19 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
ctx.globalAlpha = 1;
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.lights, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "ball") {
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
}
|
||||
if (type === "particle") {
|
||||
drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||
}
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
});
|
||||
forEachLiveOne(gameState.particles, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||
});
|
||||
|
||||
// Decides how brights the bg black parts can get
|
||||
ctx.globalAlpha = 0.2;
|
||||
ctx.globalCompositeOperation = "multiply";
|
||||
|
@ -128,14 +129,11 @@ export function render(gameState: GameState) {
|
|||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillStyle = level.color || "#000";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.particles, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "particle") {
|
||||
drawBall(ctx, color, size, x, y);
|
||||
}
|
||||
drawBall(ctx, color, size, x, y);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -161,27 +159,24 @@ export function render(gameState: GameState) {
|
|||
}
|
||||
// Coins
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
gameState.coins.forEach((coin) => {
|
||||
if (!coin.destroyed) {
|
||||
ctx.globalCompositeOperation =
|
||||
coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||
drawCoin(
|
||||
ctx,
|
||||
coin.color,
|
||||
coin.size,
|
||||
coin.x,
|
||||
coin.y,
|
||||
level.color || "black",
|
||||
coin.a,
|
||||
);
|
||||
}
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
ctx.globalCompositeOperation =
|
||||
coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||
drawCoin(
|
||||
ctx,
|
||||
coin.color,
|
||||
coin.size,
|
||||
coin.x,
|
||||
coin.y,
|
||||
level.color || "black",
|
||||
coin.a,
|
||||
);
|
||||
});
|
||||
|
||||
// Black shadow around balls
|
||||
if (!isOptionOn("basic")) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.globalAlpha = Math.min(0.8, gameState.coins.length / 20);
|
||||
ctx.globalAlpha = Math.min(0.8, liveCount(gameState.coins) / 20);
|
||||
gameState.balls.forEach((ball) => {
|
||||
drawBall(
|
||||
ctx,
|
||||
|
@ -197,22 +192,21 @@ export function render(gameState: GameState) {
|
|||
renderAllBricks();
|
||||
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
gameState.flashes = gameState.flashes.filter(
|
||||
(f) => gameState.levelTime - f.time < f.duration && !f.destroyed,
|
||||
);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.texts, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
if (type === "text") {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||
} else if (type === "particle") {
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
drawBall(ctx, color, size, x, y);
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
}
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||
});
|
||||
|
||||
forEachLiveOne(gameState.particles, (particle) => {
|
||||
const { x, y, time, color, size, duration } = particle;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
drawBall(ctx, color, size, x, y);
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
});
|
||||
|
||||
if (gameState.perks.extra_life) {
|
||||
|
|
|
@ -1,31 +1,36 @@
|
|||
|
||||
import { isOptionOn } from "./options";
|
||||
import {GameState} from "./types";
|
||||
import { GameState } from "./types";
|
||||
|
||||
let lastPlay = Date.now()
|
||||
let lastPlay = Date.now();
|
||||
|
||||
export function playPendingSounds(gameState:GameState){
|
||||
if(lastPlay>Date.now()-60){
|
||||
return
|
||||
export function playPendingSounds(gameState: GameState) {
|
||||
if (lastPlay > Date.now() - 60) {
|
||||
return;
|
||||
}
|
||||
lastPlay=Date.now()
|
||||
for(let key in gameState.aboutToPlaySound){
|
||||
const soundName = key as keyof GameState["aboutToPlaySound"]
|
||||
const ex = gameState.aboutToPlaySound[soundName] as {vol:number, x:number}
|
||||
if(ex.vol){
|
||||
sounds[soundName](Math.min(2,ex.vol),pixelsToPan(gameState, ex.x), gameState.combo)
|
||||
ex.vol=0
|
||||
lastPlay = Date.now();
|
||||
for (let key in gameState.aboutToPlaySound) {
|
||||
const soundName = key as keyof GameState["aboutToPlaySound"];
|
||||
const ex = gameState.aboutToPlaySound[soundName] as {
|
||||
vol: number;
|
||||
x: number;
|
||||
};
|
||||
if (ex.vol) {
|
||||
sounds[soundName](
|
||||
Math.min(2, ex.vol),
|
||||
pixelsToPan(gameState, ex.x),
|
||||
gameState.combo,
|
||||
);
|
||||
ex.vol = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export const sounds = {
|
||||
wallBeep: (vol:number, pan: number, combo:number) => {
|
||||
wallBeep: (vol: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(800, pan, vol);
|
||||
},
|
||||
|
||||
comboIncreaseMaybe: ( volume: number,pan: number,combo: number, ) => {
|
||||
comboIncreaseMaybe: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
let delta = 0;
|
||||
if (!isNaN(lastComboPlayed)) {
|
||||
|
@ -36,28 +41,28 @@ export const sounds = {
|
|||
lastComboPlayed = combo;
|
||||
},
|
||||
|
||||
comboDecrease(volume: number,pan: number,combo: number) {
|
||||
comboDecrease(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
playShepard(-1, pan, volume);
|
||||
},
|
||||
coinBounce: (volume: number,pan: number,combo: number) => {
|
||||
coinBounce: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(1200, pan, volume, 0.1, "triangle");
|
||||
},
|
||||
explode: (volume: number,pan: number,combo: number) => {
|
||||
explode: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createExplosionSound(pan);
|
||||
},
|
||||
lifeLost(volume: number,pan: number,combo: number) {
|
||||
lifeLost(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createShatteredGlassSound(pan);
|
||||
},
|
||||
|
||||
coinCatch(volume: number,pan: number,combo: number) {
|
||||
coinCatch(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(900, (pan), volume, 0.1, "triangle");
|
||||
createSingleBounceSound(900, pan, volume, 0.1, "triangle");
|
||||
},
|
||||
colorChange(volume: number,pan: number,combo: number) {
|
||||
colorChange(volume: number, pan: number, combo: number) {
|
||||
createSingleBounceSound(400, pan, volume, 0.5, "sine");
|
||||
createSingleBounceSound(800, pan, volume * 0.5, 0.2, "square");
|
||||
},
|
||||
|
@ -175,7 +180,7 @@ function createExplosionSound(pan = 0.5) {
|
|||
noiseSource.stop(context.currentTime + 1);
|
||||
}
|
||||
|
||||
function pixelsToPan(gameState:GameState, pan: number) {
|
||||
function pixelsToPan(gameState: GameState, pan: number) {
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
|
|
46
src/types.d.ts
vendored
46
src/types.d.ts
vendored
|
@ -113,22 +113,22 @@ interface BaseFlash {
|
|||
}
|
||||
|
||||
interface ParticleFlash extends BaseFlash {
|
||||
type: "particle";
|
||||
// type: "particle";
|
||||
vx: number;
|
||||
vy: number;
|
||||
ethereal: boolean;
|
||||
}
|
||||
|
||||
interface TextFlash extends BaseFlash {
|
||||
type: "text";
|
||||
// type: "text";
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface BallFlash extends BaseFlash {
|
||||
type: "ball";
|
||||
interface LightFlash extends BaseFlash {
|
||||
// type: "ball";
|
||||
}
|
||||
|
||||
export type Flash = ParticleFlash | TextFlash | BallFlash;
|
||||
export type Flash = ParticleFlash | TextFlash | LightFlash;
|
||||
|
||||
export type RunStats = {
|
||||
started: number;
|
||||
|
@ -153,9 +153,10 @@ export type PerksMap = {
|
|||
// TODO ensure T has a destroyed;boolean field
|
||||
export type ReusableArray<T> = {
|
||||
// All items below that index should not be destroyed
|
||||
indexMin:number;
|
||||
list:T[]
|
||||
}
|
||||
indexMin: number;
|
||||
total: number;
|
||||
list: T[];
|
||||
};
|
||||
|
||||
export type RunHistoryItem = RunStats & {
|
||||
perks?: PerksMap;
|
||||
|
@ -197,6 +198,7 @@ export type GameState = {
|
|||
combo: number;
|
||||
// Whether the game is running or paused
|
||||
running: boolean;
|
||||
ballStickToPuck: boolean;
|
||||
// Whether the game should be re-rendered once even if not running
|
||||
needsRender: boolean;
|
||||
// Position of the center of the puck on the canvas in pixels, from the left of the canvas.
|
||||
|
@ -220,11 +222,9 @@ export type GameState = {
|
|||
// Array of bricks to display. 'black' means bomb. '' means no brick.
|
||||
bricks: colorString[];
|
||||
|
||||
|
||||
|
||||
particles: ReusableArray<ParticleFlash>
|
||||
texts: ReusableArray<TextFlash>
|
||||
lights: ReusableArray<BallFlash>
|
||||
particles: ReusableArray<ParticleFlash>;
|
||||
texts: ReusableArray<TextFlash>;
|
||||
lights: ReusableArray<LightFlash>;
|
||||
coins: ReusableArray<Coin>;
|
||||
levelStartScore: number;
|
||||
levelMisses: number;
|
||||
|
@ -248,16 +248,16 @@ export type GameState = {
|
|||
levelTime: number;
|
||||
levelWallBounces: number;
|
||||
autoCleanUses: number;
|
||||
aboutToPlaySound:{
|
||||
wallBeep:{vol:number, x:number},
|
||||
comboIncreaseMaybe:{vol:number, x:number},
|
||||
comboDecrease:{vol:number, x:number},
|
||||
coinBounce:{vol:number, x:number},
|
||||
explode:{vol:number, x:number},
|
||||
lifeLost:{vol:number, x:number},
|
||||
coinCatch:{vol:number, x:number},
|
||||
colorChange:{vol:number, x:number},
|
||||
}
|
||||
aboutToPlaySound: {
|
||||
wallBeep: { vol: number; x: number };
|
||||
comboIncreaseMaybe: { vol: number; x: number };
|
||||
comboDecrease: { vol: number; x: number };
|
||||
coinBounce: { vol: number; x: number };
|
||||
explode: { vol: number; x: number };
|
||||
lifeLost: { vol: number; x: number };
|
||||
coinCatch: { vol: number; x: number };
|
||||
colorChange: { vol: number; x: number };
|
||||
};
|
||||
};
|
||||
|
||||
export type RunParams = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue