diff --git a/locales/index.d.ts b/locales/index.d.ts
index e4ddab3e0..fcece45ed 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1190,6 +1190,7 @@ export interface Locale {
"decorate": string;
"addMfmFunction": string;
"enableQuickAddMfmFunction": string;
+ "bubbleGame": string;
"abuseReportCategory": string;
"selectCategory": string;
"reportComplete": string;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index a07ed3242..7c3967059 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1187,6 +1187,7 @@ seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
+bubbleGame: "バブルゲーム"
abuseReportCategory: "通報の種類"
selectCategory: "カテゴリを選択"
reportComplete: "通報完了"
diff --git a/packages/frontend/assets/drop-and-fusion/cold_face.png b/packages/frontend/assets/drop-and-fusion/cold_face.png
new file mode 100644
index 000000000..f5f53e9ef
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/cold_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/drop-arrow.svg b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg
new file mode 100644
index 000000000..f98bb8a1a
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/packages/frontend/assets/drop-and-fusion/dropper.png b/packages/frontend/assets/drop-and-fusion/dropper.png
new file mode 100644
index 000000000..f4300aa5c
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/dropper.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/exploding_head.png b/packages/frontend/assets/drop-and-fusion/exploding_head.png
new file mode 100644
index 000000000..e8ec5182c
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/exploding_head.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png b/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png
new file mode 100644
index 000000000..c523020f6
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png b/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png
new file mode 100644
index 000000000..db9e839c8
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/frame-dark.svg b/packages/frontend/assets/drop-and-fusion/frame-dark.svg
new file mode 100644
index 000000000..3fa7c0da8
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/frame-dark.svg
@@ -0,0 +1,28 @@
+
+
+
diff --git a/packages/frontend/assets/drop-and-fusion/frame-light.svg b/packages/frontend/assets/drop-and-fusion/frame-light.svg
new file mode 100644
index 000000000..6052ccbaa
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/frame-light.svg
@@ -0,0 +1,28 @@
+
+
+
diff --git a/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png b/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png
new file mode 100644
index 000000000..fd72d749a
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/heart_suit.png b/packages/frontend/assets/drop-and-fusion/heart_suit.png
new file mode 100644
index 000000000..b0105f858
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/heart_suit.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_1.png b/packages/frontend/assets/drop-and-fusion/keycap_1.png
new file mode 100644
index 000000000..d672f2854
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_1.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_10.png b/packages/frontend/assets/drop-and-fusion/keycap_10.png
new file mode 100644
index 000000000..32cf19354
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_10.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_2.png b/packages/frontend/assets/drop-and-fusion/keycap_2.png
new file mode 100644
index 000000000..81c3f58e6
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_2.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_3.png b/packages/frontend/assets/drop-and-fusion/keycap_3.png
new file mode 100644
index 000000000..424d8c123
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_3.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_4.png b/packages/frontend/assets/drop-and-fusion/keycap_4.png
new file mode 100644
index 000000000..ea6ae5053
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_4.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_5.png b/packages/frontend/assets/drop-and-fusion/keycap_5.png
new file mode 100644
index 000000000..ad435da69
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_5.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_6.png b/packages/frontend/assets/drop-and-fusion/keycap_6.png
new file mode 100644
index 000000000..70c9522b4
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_6.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_7.png b/packages/frontend/assets/drop-and-fusion/keycap_7.png
new file mode 100644
index 000000000..5a2430748
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_7.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_8.png b/packages/frontend/assets/drop-and-fusion/keycap_8.png
new file mode 100644
index 000000000..9689d8ecf
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_8.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/keycap_9.png b/packages/frontend/assets/drop-and-fusion/keycap_9.png
new file mode 100644
index 000000000..ac3f63884
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_9.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/pleading_face.png b/packages/frontend/assets/drop-and-fusion/pleading_face.png
new file mode 100644
index 000000000..42f58d411
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/pleading_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png b/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png
new file mode 100644
index 000000000..416ef0410
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png b/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png
new file mode 100644
index 000000000..c0f72254c
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/zany_face.png b/packages/frontend/assets/drop-and-fusion/zany_face.png
new file mode 100644
index 000000000..f14f9db20
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/zany_face.png differ
diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue
index a741a3f7a..6feb85d8d 100644
--- a/packages/frontend/src/components/MkPlusOneEffect.vue
+++ b/packages/frontend/src/components/MkPlusOneEffect.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +1
+ +{{ value }}
@@ -16,7 +16,9 @@ import * as os from '@/os.js';
const props = withDefaults(defineProps<{
x: number;
y: number;
+ value?: number;
}>(), {
+ value: 1,
});
const emit = defineEmits<{
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
new file mode 100644
index 000000000..739473519
--- /dev/null
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -0,0 +1,1039 @@
+
+
+
+
+
+
+
+
+
{{ i18n.ts.bubbleGame }}
+
+
+
+
+
{{ i18n.ts.start }}
+
+
+
+
+
+
+
BUBBLE GAME
+
- {{ gameMode }} -
+
+
+
+
+ NEXT >>>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ comboPrev }} Chain!
+
+
+
+
+
+
+
+
+
+
+
GAME OVER!
+
SCORE:
+
Share
+
+
+
+
+
+
+
SCORE:
+
HIGH SCORE: -
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 14fce2db2..d94ae9054 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -531,6 +531,10 @@ export const routes = [{
path: '/clicker',
component: page(() => import('./pages/clicker.vue')),
loginRequired: true,
+}, {
+ path: '/bubble-game',
+ component: page(() => import('./pages/drop-and-fusion.vue')),
+ loginRequired: true,
}, {
path: '/timeline',
component: page(() => import('./pages/timeline.vue')),
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index a5ebc6499..20ef9eb18 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -154,7 +154,13 @@ export type OperationType = typeof operationTypes[number];
* @param soundStore サウンド設定
* @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする
*/
-export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) {
+export async function loadAudio(soundStore: {
+ type: Exclude;
+} | {
+ type: '_driveFile_';
+ fileId: string;
+ fileUrl: string;
+}, options?: { useCache?: boolean; }) {
if (_DEV_) console.log('loading audio. opts:', options);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (soundStore.type === null || (soundStore.type === '_driveFile_' && (!$i?.policies.canUseDriveFileInSoundSettings || !soundStore.fileUrl))) {
@@ -241,18 +247,31 @@ export async function playFile(soundStore: SoundStore) {
createSourceNode(buffer, soundStore.volume)?.start();
}
-export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
+export async function playRaw(type: Exclude, volume = 1, pan = 0, playbackRate = 1) {
+ const buffer = await loadAudio({ type });
+ if (!buffer) return;
+ createSourceNode(buffer, volume, pan, playbackRate)?.start();
+}
+
+export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1) : AudioBufferSourceNode | null {
const masterVolume = defaultStore.state.sound_masterVolume;
if (isMute() || masterVolume === 0 || volume === 0) {
return null;
}
+ const panNode = ctx.createStereoPanner();
+ panNode.pan.value = pan;
+
const gainNode = ctx.createGain();
gainNode.gain.value = masterVolume * volume;
const soundSource = ctx.createBufferSource();
soundSource.buffer = buffer;
- soundSource.connect(gainNode).connect(ctx.destination);
+ soundSource.playbackRate.value = playbackRate;
+ soundSource
+ .connect(panNode)
+ .connect(gainNode)
+ .connect(ctx.destination);
return soundSource;
}
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index b970ff1df..9930b321f 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -27,6 +27,11 @@ function toolsMenuItems(): MenuItem[] {
to: '/clicker',
text: '🍪👈',
icon: 'ti ti-cookie',
+ }, {
+ type: 'link',
+ to: '/bubble-game',
+ text: i18n.ts.bubbleGame,
+ icon: 'ti ti-apple',
}, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? {
type: 'link',
to: '/custom-emojis-manager',