danoniplus 46.3.0 → 46.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Dancing☆Onigiri (CW Edition)
2
2
 
3
3
  [![CodeQL](https://github.com/cwtickle/danoniplus/workflows/CodeQL/badge.svg)](https://github.com/cwtickle/danoniplus/actions?query=workflow%3ACodeQL)
4
- ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/cwtickle/danoniplus?utm_source=oss&utm_medium=github&utm_campaign=cwtickle%2Fdanoniplus&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews&logo=coderabbit)
4
+ [![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/cwtickle/danoniplus?utm_source=oss&utm_medium=github&utm_campaign=cwtickle%2Fdanoniplus&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews&logo=coderabbit)](https://www.coderabbit.ai/)
5
5
  [![Discord](https://img.shields.io/discord/698460971231870977?style=flat&logo=discord&logoColor=%23ffffff&label=Discord&labelColor=%236666ff&color=%23000066)](https://discord.gg/YVWUdUGyMy)
6
6
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-cwtickle%2Fdanoniplus-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/cwtickle/danoniplus)
7
7
  [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/cwtickle/danoniplus?sort=semver)](https://github.com/cwtickle/danoniplus/security/policy)
@@ -120,7 +120,7 @@ If you would like to cooperate with the development, please see below. Even if y
120
120
 
121
121
  ### Dancing☆Onigiri
122
122
 
123
- - [Dancing☆Onigiri Preview](https://danonicw.skr.jp/)
123
+ - [Dancing☆Onigiri Preview](https://danonicw.skr.jp/) ([Repository](https://github.com/cwtickle/danoniplus-preview)) @cwtickle
124
124
  - [Dancing☆Onigiri エディター(CW Edition 対応)](https://github.com/superkuppabros/danoni-editor) @superkuppabros
125
125
  - [ダンおに曲データjs化ツール](https://github.com/suzme/danoni-base64) @suzme
126
126
  - [ダンおに矢印色ツール](https://github.com/suzme/danoni-colorpicker) @suzme
@@ -134,6 +134,7 @@ If you would like to cooperate with the development, please see below. Even if y
134
134
 
135
135
  ### Kirizma / キリズマ
136
136
 
137
+ - [Kirizma (CW Edition)](https://github.com/cwtickle/kirizma-cw) @cwtickle
137
138
  - [キリズマ譜面データ変換機](https://github.com/suzme/kirizma-converter) @suzme
138
139
  - [キリズマ歌詞表示作成ツール](https://github.com/prlg25/kirizma_lyric) @prlg25
139
140
  - [キリズマ難易度表](https://github.com/suzme/kirizma) @suzme
package/js/danoni_main.js CHANGED
@@ -4,12 +4,12 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2026/03/25
7
+ * Revised : 2026/03/29
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 46.3.0`;
12
- const g_revisedDate = `2026/03/25`;
11
+ const g_version = `Ver 46.5.0`;
12
+ const g_revisedDate = `2026/03/29`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -1176,6 +1176,43 @@ const loadMultipleFiles2 = async (_fileData, _loadType) => {
1176
1176
  }));
1177
1177
  };
1178
1178
 
1179
+ /**
1180
+ * ユーザー定義のカスタム関数配列を安全に実行する共通関数
1181
+ * @param {string} _hookName - 識別名
1182
+ * @param {Function[]} _funcArray - カスタム関数の配列
1183
+ * @param {...any} args - 関数に渡したい引数(可変長)
1184
+ */
1185
+ const safeExecuteCustomHooks = (_hookName, _funcArray, ...args) => {
1186
+ if (!Array.isArray(_funcArray)) return true;
1187
+ const errorCache = g_errorCache[_hookName];
1188
+
1189
+ for (const [index, func] of _funcArray.entries()) {
1190
+ if (typeof func !== C_TYP_FUNCTION) continue; // 関数以外が入っていた場合の自衛
1191
+ if (errorCache && errorCache[index]?.has(func)) continue; // エラー検知済みの場合は以後スキップ
1192
+
1193
+ try {
1194
+ // ...args で受け取った引数をそのまま横流しして実行
1195
+ func(...args);
1196
+ } catch (e) {
1197
+
1198
+ if (!errorCache) {
1199
+ // ループしない場合のエラー処理
1200
+ console.group(`${unEscapeEmoji(g_emojiObj.crossMark)} Custom Function Error: [${_hookName}] (Index: ${index}${func.name ? `, Func: ${func.name}` : ``})`);
1201
+ console.error(e);
1202
+ console.groupEnd();
1203
+
1204
+ } else if (errorCache && !errorCache[index]?.has(func)) {
1205
+ // ループがある場合のエラー処理(初回のみ)
1206
+ console.group(`${unEscapeEmoji(g_emojiObj.crossMark)} Custom Function Error: [${_hookName}] (Index: ${index}${func.name ? `, Func: ${func.name}` : ``})`);
1207
+ console.error(`${unEscapeEmoji(g_emojiObj.policeLight)} [${g_scoreObj.baseFrame} Frame] ${g_msgObj.customFunctionError}`, e);
1208
+ console.groupEnd();
1209
+ errorCache[index] = new Set();
1210
+ errorCache[index].add(func);
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+
1179
1216
  /**
1180
1217
  * 与えられたパスより、キーワードとディレクトリに分割
1181
1218
  * @param {string} _fileName
@@ -1461,9 +1498,10 @@ const getLongestStr = _array => {
1461
1498
  * @returns {HTMLDivElement}
1462
1499
  */
1463
1500
  const createDescDiv = (_id, _str, { altId = _id, siz = g_limitObj.mainSiz } = {}) =>
1464
- createDivCss2Label(_id, _str, Object.assign(g_lblPosObj[altId], {
1501
+ createDivCss2Label(_id, _str, {
1502
+ ...g_lblPosObj[altId],
1465
1503
  siz: getFontSize2(_str, g_lblPosObj[altId]?.w || g_sWidth, { maxSiz: siz }),
1466
- }));
1504
+ });
1467
1505
 
1468
1506
  /*-----------------------------------------------------------*/
1469
1507
  /* ラベル・ボタン・オブジェクトの作成 */
@@ -2780,7 +2818,7 @@ const initialControl = async () => {
2780
2818
  }
2781
2819
  }
2782
2820
  }
2783
- g_customJsObj.preTitle.forEach(func => func());
2821
+ safeExecuteCustomHooks(`g_customJsObj.preTitle`, g_customJsObj.preTitle);
2784
2822
  const queryMusicId = getQueryParamVal(`musicId`);
2785
2823
  g_settings.musicIdxNum = queryMusicId !== null ? Number(queryMusicId) :
2786
2824
  g_headerObj.musicGroups?.[g_headerObj.musicNos[g_stateObj.scoreId]] ??
@@ -5282,9 +5320,7 @@ const titleInit = (_initFlg = false) => {
5282
5320
  g_keyObj.prevKey = `Dummy${g_settings.musicIdxNum}`;
5283
5321
  g_langStorage.bgmVolume = g_stateObj.bgmVolume;
5284
5322
  localStorage.setItem(`danoni-locale`, JSON.stringify(g_langStorage));
5285
- }, Object.assign({
5286
- resetFunc: () => optionInit(),
5287
- }, g_lblPosObj.btnStart_music), g_cssObj.button_Tweet),
5323
+ }, { ...g_lblPosObj.btnStart_music, resetFunc: () => optionInit() }, g_cssObj.button_Tweet),
5288
5324
  createCss2Button(`btnMusicSelectPrev`, `↑`, () => changeMSelect(-1),
5289
5325
  g_lblPosObj.btnMusicSelectPrev, g_cssObj.button_Setting),
5290
5326
  createCss2Button(`btnMusicSelectNext`, `↓`, () => changeMSelect(1),
@@ -5301,10 +5337,9 @@ const titleInit = (_initFlg = false) => {
5301
5337
  g_stateObj.bgmMuteFlg ? pauseBGM() : playBGM(0);
5302
5338
  evt.target.innerHTML = g_stateObj.bgmMuteFlg ? g_emojiObj.muted : g_emojiObj.speaker;
5303
5339
  }, g_lblPosObj.btnBgmMute, g_cssObj.button_Default),
5304
- createCss2Button(`btnBgmVolume`, `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`, () => setBGMVolume(),
5305
- Object.assign({
5306
- cxtFunc: () => setBGMVolume(-1),
5307
- }, g_lblPosObj.btnBgmVolume), g_cssObj.button_Default),
5340
+ createCss2Button(`btnBgmVolume`, `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`, () => setBGMVolume(), {
5341
+ ...g_lblPosObj.btnBgmVolume, cxtFunc: () => setBGMVolume(-1),
5342
+ }, g_cssObj.button_Default),
5308
5343
  createCss2Button(`btnBgmVolumeL`, `<`, () => setBGMVolume(-1),
5309
5344
  g_lblPosObj.btnBgmVolumeL, g_cssObj.button_Setting),
5310
5345
  createCss2Button(`btnBgmVolumeR`, `>`, () => setBGMVolume(),
@@ -5397,7 +5432,7 @@ const titleInit = (_initFlg = false) => {
5397
5432
  }
5398
5433
 
5399
5434
  // ユーザカスタムイベント(初期)
5400
- g_customJsObj.title.forEach(func => func());
5435
+ safeExecuteCustomHooks(`g_customJsObj.title`, g_customJsObj.title);
5401
5436
 
5402
5437
  // バージョン情報取得
5403
5438
  let customVersion = ``;
@@ -5421,7 +5456,7 @@ const titleInit = (_initFlg = false) => {
5421
5456
  */
5422
5457
  const createCreditBtn = (_id, _text, _url) =>
5423
5458
  createCss2Button(_id, _text, () => true,
5424
- Object.assign(g_lblPosObj[_id], { siz: getLinkSiz(_text), whiteSpace: `normal`, resetFunc: () => openLink(_url) }), g_cssObj.button_Default);
5459
+ { ...g_lblPosObj[_id], siz: getLinkSiz(_text), whiteSpace: `normal`, resetFunc: () => openLink(_url) }, g_cssObj.button_Default);
5425
5460
 
5426
5461
  if (g_headerObj.musicSelectUse && getQueryParamVal(`scoreId`) === null) {
5427
5462
  // 選曲モードではクレジット表示は別で行われているため表示しない
@@ -5460,34 +5495,34 @@ const titleInit = (_initFlg = false) => {
5460
5495
  }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
5461
5496
 
5462
5497
  // ロケール切替
5463
- createCss2Button(`btnReload`, g_localeObj.val, () => true,
5464
- Object.assign(g_lblPosObj.btnReload, {
5465
- resetFunc: () => {
5466
- g_localeObj.num = (++g_localeObj.num) % g_localeObj.list.length;
5467
- g_langStorage.locale = g_localeObj.list[g_localeObj.num];
5468
- localStorage.setItem(`danoni-locale`, JSON.stringify(g_langStorage));
5469
- location.reload();
5470
- },
5471
- }), g_cssObj.button_Start),
5498
+ createCss2Button(`btnReload`, g_localeObj.val, () => true, {
5499
+ ...g_lblPosObj.btnReload,
5500
+ resetFunc: () => {
5501
+ g_localeObj.num = (++g_localeObj.num) % g_localeObj.list.length;
5502
+ g_langStorage.locale = g_localeObj.list[g_localeObj.num];
5503
+ localStorage.setItem(`danoni-locale`, JSON.stringify(g_langStorage));
5504
+ location.reload();
5505
+ },
5506
+ }, g_cssObj.button_Start),
5472
5507
 
5473
5508
  // ヘルプ
5474
- createCss2Button(`btnHelp`, `?`, () => true,
5475
- Object.assign(g_lblPosObj.btnHelp, {
5476
- resetFunc: () => openLink(g_lblNameObj.helpUrl),
5477
- }), g_cssObj.button_Setting),
5509
+ createCss2Button(`btnHelp`, `?`, () => true, {
5510
+ ...g_lblPosObj.btnHelp,
5511
+ resetFunc: () => openLink(g_lblNameObj.helpUrl),
5512
+ }, g_cssObj.button_Setting),
5478
5513
 
5479
5514
  // バージョン描画
5480
- createCss2Button(`lnkVersion`, versionName, () => true,
5481
- Object.assign(g_lblPosObj.lnkVersion, {
5482
- siz: getFontSize2(versionName, g_sWidth * 3 / 4 - 20, { maxSiz: 12 }),
5483
- resetFunc: () => openLink(`https://github.com/cwtickle/danoniplus`),
5484
- }), g_cssObj.button_Tweet),
5515
+ createCss2Button(`lnkVersion`, versionName, () => true, {
5516
+ ...g_lblPosObj.lnkVersion,
5517
+ siz: getFontSize2(versionName, g_sWidth * 3 / 4 - 20, { maxSiz: 12 }),
5518
+ resetFunc: () => openLink(`https://github.com/cwtickle/danoniplus`),
5519
+ }, g_cssObj.button_Tweet),
5485
5520
 
5486
5521
  // セキュリティリンク
5487
- createCss2Button(`lnkComparison`, g_emojiObj.shield, () => true,
5488
- Object.assign(g_lblPosObj.lnkComparison, {
5489
- resetFunc: () => openLink(g_lblNameObj.securityUrl),
5490
- }), g_cssObj.button_Tweet),
5522
+ createCss2Button(`lnkComparison`, g_emojiObj.shield, () => true, {
5523
+ ...g_lblPosObj.lnkComparison,
5524
+ resetFunc: () => openLink(g_lblNameObj.securityUrl),
5525
+ }, g_cssObj.button_Tweet),
5491
5526
  );
5492
5527
 
5493
5528
  // コメントエリア作成
@@ -5524,7 +5559,7 @@ const titleInit = (_initFlg = false) => {
5524
5559
  const flowTitleTimeline = () => {
5525
5560
 
5526
5561
  // ユーザカスタムイベント(フレーム毎)
5527
- g_customJsObj.titleEnterFrame.forEach(func => func());
5562
+ safeExecuteCustomHooks(`g_customJsObj.titleEnterFrame`, g_customJsObj.titleEnterFrame);
5528
5563
 
5529
5564
  // 背景・マスクモーション、スキン変更
5530
5565
  drawTitleResultMotion(g_currentPage);
@@ -5545,7 +5580,7 @@ const titleInit = (_initFlg = false) => {
5545
5580
  document.oncontextmenu = () => true;
5546
5581
  divRoot.oncontextmenu = () => false;
5547
5582
 
5548
- g_skinJsObj.title.forEach(func => func());
5583
+ safeExecuteCustomHooks(`g_skinJsObj.title`, g_skinJsObj.title);
5549
5584
  };
5550
5585
 
5551
5586
  /**
@@ -6008,10 +6043,7 @@ const changeMSelect = (_num, _initFlg = false) => {
6008
6043
  deleteChildspriteAll(`keyTitleSprite`);
6009
6044
  makeDedupliArray(tmpKeyList).sort((a, b) => parseInt(a) - parseInt(b))
6010
6045
  .forEach((val, j) => keyTitleSprite.appendChild(
6011
- createDivCss2Label(`btnKeyTitle${val}`, val,
6012
- Object.assign({ x: 10 + j * 40 }, g_lblPosObj.btnKeyTitle)
6013
- )));
6014
-
6046
+ createDivCss2Label(`btnKeyTitle${val}`, val, { ...g_lblPosObj.btnKeyTitle, x: 10 + j * 40 })));
6015
6047
 
6016
6048
  // 選択した楽曲の選択位置を表示
6017
6049
  lblMusicCnt.innerHTML = `${g_settings.musicIdxNum + 1} / ${g_headerObj.musicIdxList.length}`;
@@ -6043,7 +6075,7 @@ const changeMSelect = (_num, _initFlg = false) => {
6043
6075
  }
6044
6076
 
6045
6077
  // 選曲変更時のカスタム関数実行
6046
- g_customJsObj.musicSelect.forEach(func => func(g_settings.musicIdxNum));
6078
+ safeExecuteCustomHooks(`g_customJsObj.musicSelect`, g_customJsObj.musicSelect, g_settings.musicIdxNum);
6047
6079
  };
6048
6080
 
6049
6081
  /**
@@ -6273,18 +6305,18 @@ const dataMgtInit = () => {
6273
6305
  document.getElementById(`btnView${selectedKey}`).click();
6274
6306
 
6275
6307
  // ユーザカスタムイベント(初期)
6276
- g_customJsObj.dataMgt.forEach(func => func());
6308
+ safeExecuteCustomHooks(`g_customJsObj.dataMgt`, g_customJsObj.dataMgt);
6277
6309
 
6278
6310
  multiAppend(divRoot,
6279
- createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true,
6280
- Object.assign(g_lblPosObj.btnResetBack, {
6281
- resetFunc: () => [`title`, `precondition`].includes(prevPage) ? titleInit() : g_moveSettingWindow(false),
6282
- }), g_cssObj.button_Back),
6283
-
6284
- createCss2Button(`btnPrecond`, g_lblNameObj.b_precond, () => true,
6285
- Object.assign(g_lblPosObj.btnPrecond, {
6286
- resetFunc: () => preconditionInit(),
6287
- }), g_cssObj.button_Setting),
6311
+ createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true, {
6312
+ ...g_lblPosObj.btnResetBack,
6313
+ resetFunc: () => [`title`, `precondition`].includes(prevPage) ? titleInit() : g_moveSettingWindow(false),
6314
+ }, g_cssObj.button_Back),
6315
+
6316
+ createCss2Button(`btnPrecond`, g_lblNameObj.b_precond, () => true, {
6317
+ ...g_lblPosObj.btnPrecond,
6318
+ resetFunc: () => preconditionInit(),
6319
+ }, g_cssObj.button_Setting),
6288
6320
 
6289
6321
  createCss2Button(`btnSafeMode`, g_lblNameObj.b_safeMode +
6290
6322
  (g_langStorage.safeMode === C_FLG_ON ? C_FLG_OFF : C_FLG_ON), () => {
@@ -6328,14 +6360,15 @@ const dataMgtInit = () => {
6328
6360
  reloadFlg = true;
6329
6361
  sessionStorage.setItem(`resetBackup${g_settings.musicIdxNum}`, JSON.stringify(Array.from(backupData.entries())));
6330
6362
  }
6331
- }, Object.assign(g_lblPosObj.btnResetN, {
6363
+ }, {
6364
+ ...g_lblPosObj.btnResetN,
6332
6365
  visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
6333
6366
  resetFunc: () => {
6334
6367
  if (reloadFlg) {
6335
6368
  location.reload();
6336
6369
  }
6337
6370
  },
6338
- }), g_cssObj.button_Reset),
6371
+ }, g_cssObj.button_Reset),
6339
6372
 
6340
6373
  // リカバリー用のボタン
6341
6374
  createCss2Button(`btnUndo`, g_lblNameObj.b_undo, () => {
@@ -6364,7 +6397,7 @@ const dataMgtInit = () => {
6364
6397
  document.oncontextmenu = () => true;
6365
6398
  divRoot.oncontextmenu = () => false;
6366
6399
 
6367
- g_skinJsObj.dataMgt.forEach(func => func());
6400
+ safeExecuteCustomHooks(`g_skinJsObj.dataMgt`, g_skinJsObj.dataMgt);
6368
6401
  };
6369
6402
 
6370
6403
 
@@ -6442,7 +6475,7 @@ const preconditionInit = () => {
6442
6475
  }, g_cssObj.button_Setting));
6443
6476
 
6444
6477
  // ユーザカスタムイベント(初期)
6445
- g_customJsObj.precondition.forEach(func => func());
6478
+ safeExecuteCustomHooks(`g_customJsObj.precondition`, g_customJsObj.precondition);
6446
6479
 
6447
6480
  multiAppend(divRoot,
6448
6481
 
@@ -6451,13 +6484,13 @@ const preconditionInit = () => {
6451
6484
  dataMgtInit();
6452
6485
  }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
6453
6486
 
6454
- createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true,
6455
- Object.assign(g_lblPosObj.btnPrecond, {
6456
- resetFunc: () => {
6457
- viewKeyStorage.cache = new Map();
6458
- prevPage === `dataMgt` ? dataMgtInit() : g_moveSettingWindow(false);
6459
- },
6460
- }), g_cssObj.button_Back),
6487
+ createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true, {
6488
+ ...g_lblPosObj.btnPrecond,
6489
+ resetFunc: () => {
6490
+ viewKeyStorage.cache = new Map();
6491
+ prevPage === `dataMgt` ? dataMgtInit() : g_moveSettingWindow(false);
6492
+ },
6493
+ }, g_cssObj.button_Back),
6461
6494
  );
6462
6495
  // キー操作イベント(デフォルト)
6463
6496
  setShortcutEvent(g_currentPage, () => true, { dfEvtFlg: true });
@@ -6465,7 +6498,7 @@ const preconditionInit = () => {
6465
6498
  document.oncontextmenu = () => true;
6466
6499
  divRoot.oncontextmenu = () => true;
6467
6500
 
6468
- g_skinJsObj.precondition.forEach(func => func());
6501
+ safeExecuteCustomHooks(`g_skinJsObj.precondition`, g_skinJsObj.precondition);
6469
6502
  };
6470
6503
 
6471
6504
  /*-----------------------------------------------------------*/
@@ -6486,33 +6519,33 @@ const commonSettingBtn = _labelName => {
6486
6519
  multiAppend(divRoot,
6487
6520
 
6488
6521
  // タイトル画面へ戻る
6489
- createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true,
6490
- Object.assign(g_lblPosObj.btnBack, {
6491
- animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: () => titleInit(),
6492
- }), g_cssObj.button_Back),
6522
+ createCss2Button(`btnBack`, g_lblNameObj.b_back, () => true, {
6523
+ ...g_lblPosObj.btnBack,
6524
+ animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: () => titleInit(),
6525
+ }, g_cssObj.button_Back),
6493
6526
 
6494
6527
  // キーコンフィグ画面へ移動
6495
- createCss2Button(`btnKeyConfig`, g_lblNameObj.b_keyConfig, () => true,
6496
- Object.assign(g_lblPosObj.btnKeyConfig, {
6497
- animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: () => keyConfigInit(`Main`),
6498
- }), g_cssObj.button_Setting),
6528
+ createCss2Button(`btnKeyConfig`, g_lblNameObj.b_keyConfig, () => true, {
6529
+ ...g_lblPosObj.btnKeyConfig,
6530
+ animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: () => keyConfigInit(`Main`),
6531
+ }, g_cssObj.button_Setting),
6499
6532
 
6500
6533
  // プレイ開始
6501
6534
  makePlayButton(() => loadMusic()),
6502
6535
 
6503
6536
  // Display設定へ移動
6504
- createCss2Button(`btn${_labelName}`, `>`, () => true,
6505
- Object.assign(g_lblPosObj.btnSwitchSetting, {
6506
- title: g_msgObj[`to${_labelName}`], resetFunc: () => g_moveSettingWindow(),
6507
- cxtFunc: () => g_moveSettingWindow(true, -1),
6508
- }), g_cssObj.button_Mini),
6537
+ createCss2Button(`btn${_labelName}`, `>`, () => true, {
6538
+ ...g_lblPosObj.btnSwitchSetting,
6539
+ title: g_msgObj[`to${_labelName}`], resetFunc: () => g_moveSettingWindow(),
6540
+ cxtFunc: () => g_moveSettingWindow(true, -1),
6541
+ }, g_cssObj.button_Mini),
6509
6542
 
6510
6543
  // データセーブフラグの切替
6511
- createCss2Button(`btnSave`, g_lblNameObj.dataSave, evt => switchSave(evt),
6512
- Object.assign(g_lblPosObj.btnSave, {
6513
- cxtFunc: evt => switchSave(evt),
6514
- visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
6515
- }), g_cssObj.button_Default, (g_stateObj.dataSaveFlg ? g_cssObj.button_ON : g_cssObj.button_OFF)),
6544
+ createCss2Button(`btnSave`, g_lblNameObj.dataSave, evt => switchSave(evt), {
6545
+ ...g_lblPosObj.btnSave,
6546
+ cxtFunc: evt => switchSave(evt),
6547
+ visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
6548
+ }, g_cssObj.button_Default, (g_stateObj.dataSaveFlg ? g_cssObj.button_ON : g_cssObj.button_OFF)),
6516
6549
 
6517
6550
  // データ管理画面へ移動
6518
6551
  createCss2Button(`btnReset`, g_lblNameObj.dataReset, () => {
@@ -6520,19 +6553,19 @@ const commonSettingBtn = _labelName => {
6520
6553
  }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
6521
6554
 
6522
6555
  // 前提条件表示用画面へ移動(debug=trueの場合のみ)
6523
- createCss2Button(`btnPrecond`, g_lblNameObj.b_precond, () => true,
6524
- Object.assign(g_lblPosObj.btnPrecond, {
6525
- resetFunc: () => preconditionInit(),
6526
- }), g_cssObj.button_Setting),
6556
+ createCss2Button(`btnPrecond`, g_lblNameObj.b_precond, () => true, {
6557
+ ...g_lblPosObj.btnPrecond,
6558
+ resetFunc: () => preconditionInit(),
6559
+ }, g_cssObj.button_Setting),
6527
6560
 
6528
6561
  // 設定内容サマリを表示
6529
- createCss2Button(`btnSettingSummary`, `>`, () => true,
6530
- Object.assign(g_lblPosObj.btnSettingSummary, {
6531
- resetFunc: () => {
6532
- g_stateObj.settingSummaryVisible = !g_stateObj.settingSummaryVisible;
6533
- visibleSettingSummary(g_stateObj.settingSummaryVisible);
6534
- },
6535
- }), g_cssObj.button_Mini),
6562
+ createCss2Button(`btnSettingSummary`, `>`, () => true, {
6563
+ ...g_lblPosObj.btnSettingSummary,
6564
+ resetFunc: () => {
6565
+ g_stateObj.settingSummaryVisible = !g_stateObj.settingSummaryVisible;
6566
+ visibleSettingSummary(g_stateObj.settingSummaryVisible);
6567
+ },
6568
+ }, g_cssObj.button_Mini),
6536
6569
  );
6537
6570
  makeSettingSummary();
6538
6571
  };
@@ -6584,7 +6617,7 @@ const updateSettingSummary = () => {
6584
6617
  `(Adj: ${g_stateObj.adjustment} f, Volume: ${g_stateObj.volume}%, ` +
6585
6618
  `ColorType: ${g_colorType}, KeyPattern: ${g_keyObj.currentPtn === -1 ? 'Self' : g_keyObj.currentPtn + 1})`;
6586
6619
 
6587
- g_customJsObj.settingSummary.forEach(func => func());
6620
+ safeExecuteCustomHooks(`g_customJsObj.settingSummary`, g_customJsObj.settingSummary);
6588
6621
  };
6589
6622
 
6590
6623
  /**
@@ -6592,10 +6625,9 @@ const updateSettingSummary = () => {
6592
6625
  * @param {function} _func
6593
6626
  * @returns {HTMLDivElement}
6594
6627
  */
6595
- const makePlayButton = _func => createCss2Button(`btnPlay`, g_lblNameObj.b_play, () => true,
6596
- Object.assign(g_lblPosObj.btnPlay, {
6597
- animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: _func,
6598
- }), g_cssObj.button_Next);
6628
+ const makePlayButton = _func => createCss2Button(`btnPlay`, g_lblNameObj.b_play, () => true, {
6629
+ ...g_lblPosObj.btnPlay, animationName: (g_initialFlg ? `` : `smallToNormalY`), resetFunc: _func,
6630
+ }, g_cssObj.button_Next);
6599
6631
 
6600
6632
  /**
6601
6633
  * 設定・オプション画面初期化
@@ -6611,7 +6643,7 @@ const optionInit = () => {
6611
6643
  // 楽曲データの表示
6612
6644
  const text = getMusicInfoView();
6613
6645
  divRoot.appendChild(createDivCss2Label(`lblMusicInfo`, text,
6614
- Object.assign({ siz: getFontSize2(text, g_btnWidth(3 / 4), { maxSiz: 12 }) }, g_lblPosObj.lblMusicInfo)));
6646
+ { ...g_lblPosObj.lblMusicInfo, siz: getFontSize2(text, g_btnWidth(3 / 4), { maxSiz: 12 }) }));
6615
6647
 
6616
6648
  // タイトル文字描画
6617
6649
  divRoot.appendChild(getTitleDivLabel(`lblTitle`, g_lblNameObj.settings, 0, 15, `settings_Title`));
@@ -6620,7 +6652,7 @@ const optionInit = () => {
6620
6652
  createOptionWindow(divRoot);
6621
6653
 
6622
6654
  // ユーザカスタムイベント(初期)
6623
- g_customJsObj.option.forEach(func => func());
6655
+ safeExecuteCustomHooks(`g_customJsObj.option`, g_customJsObj.option);
6624
6656
 
6625
6657
  // ボタン描画
6626
6658
  commonSettingBtn(`Display`);
@@ -6630,7 +6662,7 @@ const optionInit = () => {
6630
6662
  document.oncontextmenu = () => true;
6631
6663
  g_initialFlg = true;
6632
6664
 
6633
- g_skinJsObj.option.forEach(func => func());
6665
+ safeExecuteCustomHooks(`g_skinJsObj.option`, g_skinJsObj.option);
6634
6666
  };
6635
6667
 
6636
6668
  /**
@@ -7343,10 +7375,11 @@ const makeHighScore = _scoreId => {
7343
7375
  });
7344
7376
  // ランク、クリアランプ、特殊設定条件
7345
7377
  multiAppend(detailHighScore,
7346
- createDivCss2Label(`lblHRank`, g_localStorage.highscores?.[scoreName]?.rankMark ?? `--`, Object.assign(g_lblPosObj.lblHRank, {
7378
+ createDivCss2Label(`lblHRank`, g_localStorage.highscores?.[scoreName]?.rankMark ?? `--`, {
7379
+ ...g_lblPosObj.lblHRank,
7347
7380
  color: g_localStorage.highscores?.[scoreName]?.rankColor ?? `#666666`,
7348
7381
  fontFamily: getBasicFont(`"Bookman Old Style"`),
7349
- })),
7382
+ }),
7350
7383
  createScoreLabel(`lblHDateTime`, g_localStorage.highscores?.[scoreName]?.dateTime ?? `----/--/-- --:--`, { yPos: 12 }),
7351
7384
  createScoreLabel(`lblHMarks`,
7352
7385
  `${g_localStorage.highscores?.[scoreName]?.fullCombo ?? '' ? '<span class="result_FullCombo">◆</span>' : ''}` +
@@ -7401,7 +7434,7 @@ const makeHighScore = _scoreId => {
7401
7434
  delete g_localStorage.highscores[scoreName];
7402
7435
  makeHighScore(_scoreId);
7403
7436
  }
7404
- }, Object.assign({ btnStyle: `Reset` }, g_lblPosObj.lnkHighScore)),
7437
+ }, { ...g_lblPosObj.lnkHighScore, btnStyle: `Reset` }),
7405
7438
  makeDifLblCssButton(`lnkHighScore`, g_lblNameObj.s_result, 8, () => {
7406
7439
  copyTextToClipboard(keyIsShift() ? resultCommon : resultText, g_msgInfoObj.I_0001);
7407
7440
  }, g_lblPosObj.lnkHighScore),
@@ -7604,7 +7637,7 @@ const setDifficulty = (_initFlg) => {
7604
7637
  lblMusicInfo.style.fontSize = wUnit(getFontSize2(lblMusicInfo.textContent, g_btnWidth(3 / 4), { maxSiz: 12 }));
7605
7638
 
7606
7639
  // ユーザカスタムイベント(初期)
7607
- g_customJsObj.difficulty.forEach(func => func(_initFlg, g_canLoadDifInfoFlg));
7640
+ safeExecuteCustomHooks(`g_customJsObj.difficulty`, g_customJsObj.difficulty, _initFlg, g_canLoadDifInfoFlg);
7608
7641
 
7609
7642
  // 設定サマリー表示の更新
7610
7643
  updateSettingSummary();
@@ -7796,10 +7829,10 @@ const createOptionWindow = _sprite => {
7796
7829
  ];
7797
7830
 
7798
7831
  spriteList.scroll.appendChild(
7799
- createCss2Button(`btnReverse`, `${g_lblNameObj.Reverse}:${getStgDetailName(g_stateObj.reverse)}`, evt => setReverse(evt.target),
7800
- Object.assign({
7801
- cxtFunc: evt => setReverse(evt.target),
7802
- }, g_lblPosObj.btnReverse), g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.reverse}`])
7832
+ createCss2Button(`btnReverse`, `${g_lblNameObj.Reverse}:${getStgDetailName(g_stateObj.reverse)}`, evt => setReverse(evt.target), {
7833
+ ...g_lblPosObj.btnReverse,
7834
+ cxtFunc: evt => setReverse(evt.target),
7835
+ }, g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.reverse}`])
7803
7836
  );
7804
7837
  spriteList[g_settings.scrolls.length > 1 ? `reverse` : `scroll`].style.display = C_DIS_NONE;
7805
7838
  } else {
@@ -7847,10 +7880,10 @@ const createOptionWindow = _sprite => {
7847
7880
  )
7848
7881
  );
7849
7882
  spriteList.gauge.appendChild(
7850
- createCss2Button(`lnkExcessive`, g_lblNameObj.Excessive, evt => setExcessive(evt.target),
7851
- Object.assign(g_lblPosObj.btnExcessive, {
7852
- title: g_msgObj.excessive, cxtFunc: evt => setExcessive(evt.target),
7853
- }), g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.excessive}`])
7883
+ createCss2Button(`lnkExcessive`, g_lblNameObj.Excessive, evt => setExcessive(evt.target), {
7884
+ ...g_lblPosObj.btnExcessive,
7885
+ title: g_msgObj.excessive, cxtFunc: evt => setExcessive(evt.target),
7886
+ }, g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.excessive}`])
7854
7887
  );
7855
7888
 
7856
7889
  // ---------------------------------------------------
@@ -8502,7 +8535,7 @@ const settingsDisplayInit = () => {
8502
8535
  divRoot.appendChild(createDescDiv(`scMsg`, g_lblNameObj.sdShortcutDesc));
8503
8536
 
8504
8537
  // ユーザカスタムイベント(初期)
8505
- g_customJsObj.settingsDisplay.forEach(func => func());
8538
+ safeExecuteCustomHooks(`g_customJsObj.settingsDisplay`, g_customJsObj.settingsDisplay);
8506
8539
 
8507
8540
  // ボタン描画
8508
8541
  commonSettingBtn(`Settings`);
@@ -8511,7 +8544,7 @@ const settingsDisplayInit = () => {
8511
8544
  setShortcutEvent(g_currentPage);
8512
8545
  document.oncontextmenu = () => true;
8513
8546
 
8514
- g_skinJsObj.settingsDisplay.forEach(func => func());
8547
+ safeExecuteCustomHooks(`g_skinJsObj.settingsDisplay`, g_skinJsObj.settingsDisplay);
8515
8548
  };
8516
8549
 
8517
8550
  /**
@@ -8619,10 +8652,9 @@ const createSettingsDisplayWindow = _sprite => {
8619
8652
  createDivCss2Label(`lblAppearancePos`, `${g_hidSudObj.distH[g_stateObj.appearance](g_hidSudObj.filterPos)}`, g_lblPosObj.lblAppearancePos),
8620
8653
  createDivCss2Label(`lblAppearanceBar`, `<input id="appearanceSlider" type="range" value="${g_hidSudObj.filterPos}" min="0" max="100" step="1">`,
8621
8654
  g_lblPosObj.lblAppearanceBar),
8622
- createCss2Button(`lnkLockBtn`, g_lblNameObj.filterLock, evt => setLockView(evt.target),
8623
- Object.assign(g_lblPosObj.lnkLockBtn, {
8624
- cxtFunc: evt => setLockView(evt.target),
8625
- }), g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.filterLock}`]),
8655
+ createCss2Button(`lnkLockBtn`, g_lblNameObj.filterLock, evt => setLockView(evt.target), {
8656
+ ...g_lblPosObj.lnkLockBtn, cxtFunc: evt => setLockView(evt.target),
8657
+ }, g_cssObj.button_Default, g_cssObj[`button_Rev${g_stateObj.filterLock}`]),
8626
8658
  );
8627
8659
 
8628
8660
  const setLockView = (_btn) => {
@@ -8767,7 +8799,7 @@ const exSettingInit = () => {
8767
8799
  );
8768
8800
 
8769
8801
  // ユーザカスタムイベント(初期)
8770
- g_customJsObj.exSetting.forEach(func => func());
8802
+ safeExecuteCustomHooks(`g_customJsObj.exSetting`, g_customJsObj.exSetting);
8771
8803
 
8772
8804
  // 設定系のボタン群をまとめて作成(Data Save, Display切替, Back, KeyConfig, Playボタン)
8773
8805
  commonSettingBtn(g_currentPage);
@@ -8776,7 +8808,7 @@ const exSettingInit = () => {
8776
8808
  setShortcutEvent(g_currentPage, () => true, { dfEvtFlg: true });
8777
8809
  document.oncontextmenu = () => true;
8778
8810
 
8779
- g_skinJsObj.exSetting.forEach(func => func());
8811
+ safeExecuteCustomHooks(`g_skinJsObj.exSetting`, g_skinJsObj.exSetting);
8780
8812
  };
8781
8813
 
8782
8814
  /**
@@ -8821,14 +8853,15 @@ const createGeneralSettingEx = (_spriteList, _name, { defaultList = [C_FLG_OFF],
8821
8853
  funcEx();
8822
8854
  createExpandedScView(_name);
8823
8855
  },
8824
- Object.assign({
8856
+ {
8857
+ ...g_lblPosObj.btnReverse,
8825
8858
  cxtFunc: () => {
8826
8859
  setSetting(-1, `${_name}Type`, { maxSiz: g_limitObj.difSelectorSiz });
8827
8860
  funcEx();
8828
8861
  createExpandedScView(_name);
8829
8862
  },
8830
8863
  title: g_msgObj[`${_name}Type`] ?? ``,
8831
- }, g_lblPosObj.btnReverse), g_cssObj.button_Default, g_cssObj.button_RevON);
8864
+ }, g_cssObj.button_Default, g_cssObj.button_RevON);
8832
8865
 
8833
8866
  /**
8834
8867
  * 拡張ボタンのショートカット表示、拡張ボタンのCSS切り替え
@@ -8890,7 +8923,9 @@ const keyConfigInit = (_kcType = g_kcType) => {
8890
8923
  createDescDiv(`kcDesc`, g_lblNameObj.kcDesc.split(`{0}`).join(g_kCd[C_KEY_RETRY])
8891
8924
  .split(`{1}:`).join(g_isMac ? `` : `Delete:`)),
8892
8925
 
8893
- createDescDiv(`kcShuffleDesc`, g_headerObj.shuffleUse ? g_lblNameObj.kcShuffleDesc : g_lblNameObj.kcNoShuffleDesc),
8926
+ createDescDiv(`kcShuffleDesc`,
8927
+ g_headerObj.shuffleUse && g_settings.shuffles.filter(val => val.endsWith(`+`)).length > 0
8928
+ ? g_lblNameObj.kcShuffleDesc : g_lblNameObj.kcNoShuffleDesc),
8894
8929
  );
8895
8930
 
8896
8931
  // キーの一覧を表示
@@ -9438,7 +9473,7 @@ const keyConfigInit = (_kcType = g_kcType) => {
9438
9473
  keyConfigInit(g_kcType);
9439
9474
  };
9440
9475
 
9441
- const colorPickSprite = createEmptySprite(divRoot, `colorPickSprite`, Object.assign({ title: g_msgObj.pickArrow }, g_windowObj.colorPickSprite));
9476
+ const colorPickSprite = createEmptySprite(divRoot, `colorPickSprite`, { ...g_windowObj.colorPickSprite, title: g_msgObj.pickArrow });
9442
9477
  if ([`Default`, `Type0`].includes(g_colorType)) {
9443
9478
  colorPickSprite.style.display = C_DIS_NONE;
9444
9479
  }
@@ -9465,8 +9500,8 @@ const keyConfigInit = (_kcType = g_kcType) => {
9465
9500
  }
9466
9501
  }, g_lblPosObj.lnkColorCopy, g_cssObj.button_Start),
9467
9502
 
9468
- createDivCss2Label(`lblPickArrow`, g_lblNameObj.s_arrow, Object.assign({ y: 0 }, g_lblPosObj.pickPos)),
9469
- createDivCss2Label(`lblPickFrz`, g_lblNameObj.s_frz, Object.assign({ y: 140 }, g_lblPosObj.pickPos)),
9503
+ createDivCss2Label(`lblPickArrow`, g_lblNameObj.s_arrow, { ...g_lblPosObj.pickPos, y: 0 }),
9504
+ createDivCss2Label(`lblPickFrz`, g_lblNameObj.s_frz, { ...g_lblPosObj.pickPos, y: 140 }),
9470
9505
 
9471
9506
  // ColorPickerの色を元に戻す
9472
9507
  createCss2Button(`lnkColorReset`, g_lblNameObj.b_cReset, () => {
@@ -9596,7 +9631,7 @@ const keyConfigInit = (_kcType = g_kcType) => {
9596
9631
  };
9597
9632
 
9598
9633
  // ユーザカスタムイベント(初期)
9599
- g_customJsObj.keyconfig.forEach(func => func());
9634
+ safeExecuteCustomHooks(`g_customJsObj.keyconfig`, g_customJsObj.keyconfig);
9600
9635
 
9601
9636
  // 部分キー表示用ボタン描画
9602
9637
  if (configKeyGroupList.length > 1) {
@@ -9605,7 +9640,7 @@ const keyConfigInit = (_kcType = g_kcType) => {
9605
9640
  configKeyGroupList.forEach((val, j) =>
9606
9641
  divRoot.appendChild(
9607
9642
  createCss2Button(`key${j}`, `${j + 1}`, () => appearConfigSteps(j),
9608
- Object.assign({ y: 110 + j * 20 }, g_lblPosObj.lnkKeySwitch), g_cssObj.button_Mini),
9643
+ { ...g_lblPosObj.lnkKeySwitch, y: 110 + j * 20 }, g_cssObj.button_Mini),
9609
9644
  ));
9610
9645
  }
9611
9646
 
@@ -9620,32 +9655,32 @@ const keyConfigInit = (_kcType = g_kcType) => {
9620
9655
  g_currentj = 0;
9621
9656
  g_currentk = 0;
9622
9657
  g_prevKey = 0;
9623
- }, Object.assign(g_lblPosObj.btnKcBack, {
9624
- resetFunc: () => g_moveSettingWindow(false),
9625
- }), g_cssObj.button_Back),
9658
+ }, {
9659
+ ...g_lblPosObj.btnKcBack, resetFunc: () => g_moveSettingWindow(false),
9660
+ }, g_cssObj.button_Back),
9626
9661
 
9627
9662
  createDivCss2Label(`lblPattern`, `${g_lblNameObj.KeyPattern}: ${g_keyObj.currentPtn === -1 ?
9628
9663
  'Self' : g_keyObj.currentPtn + 1}${lblTransKey}`, g_lblPosObj.lblPattern),
9629
9664
 
9630
9665
  // パターン変更ボタン描画(右回り)
9631
- createCss2Button(`btnPtnChangeR`, `>`, () => true, Object.assign(g_lblPosObj.btnPtnChangeR, {
9632
- resetFunc: () => changePattern(),
9633
- }), g_cssObj.button_Mini),
9666
+ createCss2Button(`btnPtnChangeR`, `>`, () => true, {
9667
+ ...g_lblPosObj.btnPtnChangeR, resetFunc: () => changePattern(),
9668
+ }, g_cssObj.button_Mini),
9634
9669
 
9635
9670
  // パターン変更ボタン描画(左回り)
9636
- createCss2Button(`btnPtnChangeL`, `<`, () => true, Object.assign(g_lblPosObj.btnPtnChangeL, {
9637
- resetFunc: () => changePattern(-1),
9638
- }), g_cssObj.button_Mini),
9671
+ createCss2Button(`btnPtnChangeL`, `<`, () => true, {
9672
+ ...g_lblPosObj.btnPtnChangeL, resetFunc: () => changePattern(-1),
9673
+ }, g_cssObj.button_Mini),
9639
9674
 
9640
9675
  // パターン変更ボタン描画(右回り/別キーモード間スキップ)
9641
- createCss2Button(`btnPtnChangeRR`, `|>`, () => true, Object.assign(g_lblPosObj.btnPtnChangeRR, {
9642
- resetFunc: () => changePattern(1, true),
9643
- }), g_cssObj.button_Setting),
9676
+ createCss2Button(`btnPtnChangeRR`, `|>`, () => true, {
9677
+ ...g_lblPosObj.btnPtnChangeRR, resetFunc: () => changePattern(1, true),
9678
+ }, g_cssObj.button_Setting),
9644
9679
 
9645
9680
  // パターン変更ボタン描画(左回り/別キーモード間スキップ)
9646
- createCss2Button(`btnPtnChangeLL`, `<|`, () => true, Object.assign(g_lblPosObj.btnPtnChangeLL, {
9647
- resetFunc: () => changePattern(-1, true),
9648
- }), g_cssObj.button_Setting),
9681
+ createCss2Button(`btnPtnChangeLL`, `<|`, () => true, {
9682
+ ...g_lblPosObj.btnPtnChangeLL, resetFunc: () => changePattern(-1, true),
9683
+ }, g_cssObj.button_Setting),
9649
9684
 
9650
9685
  // キーコンフィグリセットボタン描画
9651
9686
  createCss2Button(`btnReset`, g_lblNameObj.b_reset, () => {
@@ -9677,12 +9712,17 @@ const keyConfigInit = (_kcType = g_kcType) => {
9677
9712
  // また、直前と同じキーを押した場合(BackSpaceを除く)はキー操作を無効にする
9678
9713
  const disabledKeys = [240, 242, 243, 244, 91, 29, 28, 27, 259, g_prevKey];
9679
9714
 
9715
+ if (g_localeObj.val === `Ja`) {
9716
+ disabledKeys.unshift(229);
9717
+ }
9718
+ if (disabledKeys.includes(setKey) || g_kCdN[setKey] === undefined) {
9719
+ makeInfoWindow(g_msgInfoObj.I_0002, `fadeOut0`);
9720
+ return;
9721
+ } else if ((keyIsDown(g_kCdNameObj.metaLKey) || keyIsDown(g_kCdNameObj.metaRKey)) && keyIsShift()) {
9722
+ return;
9723
+ }
9680
9724
  if (selectedKc === `TitleBack` || selectedKc === `Retry`) {
9681
9725
  // プレイ中ショートカットキー変更
9682
- if (disabledKeys.includes(setKey) || g_kCdN[setKey] === undefined) {
9683
- makeInfoWindow(g_msgInfoObj.I_0002, `fadeOut0`);
9684
- return;
9685
- }
9686
9726
  g_headerObj[`key${selectedKc}`] = setKey;
9687
9727
  g_headerObj[`key${selectedKc}Def`] = setKey;
9688
9728
  document.getElementById(`sc${selectedKc}`).textContent = getScMsg[selectedKc]();
@@ -9697,14 +9737,7 @@ const keyConfigInit = (_kcType = g_kcType) => {
9697
9737
  return;
9698
9738
  }
9699
9739
 
9700
- if (g_localeObj.val === `Ja`) {
9701
- disabledKeys.unshift(229);
9702
- }
9703
- if (disabledKeys.includes(setKey) || g_kCdN[setKey] === undefined) {
9704
- makeInfoWindow(g_msgInfoObj.I_0002, `fadeOut0`);
9705
- return;
9706
- } else if ((setKey === C_KEY_TITLEBACK && g_currentk === 0) ||
9707
- ((keyIsDown(g_kCdNameObj.metaLKey) || keyIsDown(g_kCdNameObj.metaRKey)) && keyIsShift())) {
9740
+ if (setKey === C_KEY_TITLEBACK && g_currentk === 0) {
9708
9741
  return;
9709
9742
  }
9710
9743
 
@@ -9736,7 +9769,7 @@ const keyConfigInit = (_kcType = g_kcType) => {
9736
9769
  }
9737
9770
  });
9738
9771
 
9739
- g_skinJsObj.keyconfig.forEach(func => func());
9772
+ safeExecuteCustomHooks(`g_skinJsObj.keyconfig`, g_skinJsObj.keyconfig);
9740
9773
  document.onkeyup = evt => commonKeyUp(evt);
9741
9774
  document.oncontextmenu = () => false;
9742
9775
  };
@@ -9926,7 +9959,7 @@ const loadMusic = () => {
9926
9959
  lblLoading.textContent = `${g_lblNameObj.nowLoading} ${_event.loaded}Bytes`;
9927
9960
  }
9928
9961
  // ユーザカスタムイベント
9929
- g_customJsObj.progress.forEach(func => func(_event));
9962
+ safeExecuteCustomHooks(`g_customJsObj.progress`, g_customJsObj.progress, _event);
9930
9963
  });
9931
9964
 
9932
9965
  // エラー処理
@@ -10034,8 +10067,8 @@ const loadingScoreInit = async () => {
10034
10067
  g_headerObj.blankFrame = g_headerObj.blankFrameDef;
10035
10068
 
10036
10069
  // ユーザカスタムイベント
10037
- g_customJsObj.preloading.forEach(func => func());
10038
- g_skinJsObj.preloading.forEach(func => func());
10070
+ safeExecuteCustomHooks(`g_customJsObj.preloading`, g_customJsObj.preloading);
10071
+ safeExecuteCustomHooks(`g_skinJsObj.preloading`, g_skinJsObj.preloading);
10039
10072
 
10040
10073
  let dummyIdHeader = ``;
10041
10074
  if (g_stateObj.dummyId !== ``) {
@@ -10174,7 +10207,7 @@ const loadingScoreInit = async () => {
10174
10207
  getArrowSettings();
10175
10208
 
10176
10209
  // ユーザカスタムイベント
10177
- g_customJsObj.loading.forEach(func => func());
10210
+ safeExecuteCustomHooks(`g_customJsObj.loading`, g_customJsObj.loading);
10178
10211
 
10179
10212
  mainInit();
10180
10213
  };
@@ -11823,6 +11856,7 @@ const getArrowSettings = () => {
11823
11856
  g_workObj.keyCtrl = structuredClone(g_keyObj[`keyCtrl${keyCtrlPtn}`]);
11824
11857
  g_workObj.diffList = [];
11825
11858
  g_workObj.mainEndTime = 0;
11859
+ g_workObj.currentLifeState = ``;
11826
11860
 
11827
11861
  const rotateBy = (val, delta) => {
11828
11862
  // numeric
@@ -12359,7 +12393,7 @@ const mainInit = () => {
12359
12393
  g_keyObj[`layerTrans${keyCtrlPtn}`]?.[0]?.[Math.floor(transj / 2) * 2 + (transj + Number(g_stateObj.reverse === C_FLG_ON)) % 2], g_transPriority.layer);
12360
12394
 
12361
12395
  stepSprite.push(createEmptySprite(mainSpriteJ, `stepSprite${j}`, mainCommonPos));
12362
- arrowSprite.push(createEmptySprite(mainSpriteJ, `arrowSprite${j}`, Object.assign({ y: g_workObj.hitPosition * (j % 2 === 0 ? 1 : -1) }, mainCommonPos)));
12396
+ arrowSprite.push(createEmptySprite(mainSpriteJ, `arrowSprite${j}`, { ...mainCommonPos, y: g_workObj.hitPosition * (j % 2 === 0 ? 1 : -1) }));
12363
12397
  frzHitSprite.push(createEmptySprite(mainSpriteJ, `frzHitSprite${j}`, mainCommonPos));
12364
12398
  }
12365
12399
 
@@ -12473,7 +12507,7 @@ const mainInit = () => {
12473
12507
  const fullTime = transFrameToTimer(fullFrame - g_stateObj.intAdjustment);
12474
12508
 
12475
12509
  // フレーム数
12476
- divRoot.appendChild(createDivCss2Label(`lblframe`, g_scoreObj.baseFrame, Object.assign(g_lblPosObj.lblframe, { display: g_workObj.lifegaugeDisp })));
12510
+ divRoot.appendChild(createDivCss2Label(`lblframe`, g_scoreObj.baseFrame, { ...g_lblPosObj.lblframe, display: g_workObj.lifegaugeDisp }));
12477
12511
 
12478
12512
  // ライフ(数字)部作成
12479
12513
  const intLifeVal = Math.floor(g_workObj.lifeVal);
@@ -12511,9 +12545,7 @@ const mainInit = () => {
12511
12545
  }, lblInitColor),
12512
12546
 
12513
12547
  // ライフ背景
12514
- createColorObject2(`lifeBackObj`, {
12515
- x: 5, y: 50, w: 15, h: g_headerObj.playingHeight - 100, styleName: `lifeBar`, display: g_workObj.lifegaugeDisp,
12516
- }, g_cssObj.life_Background),
12548
+ createColorObject2(`lifeBackObj`, { ...g_lblPosObj.lifeBackObj, display: g_workObj.lifegaugeDisp }, g_cssObj.life_Background),
12517
12549
 
12518
12550
  // ライフ本体
12519
12551
  createColorObject2(`lifeBar`, {
@@ -12531,38 +12563,34 @@ const mainInit = () => {
12531
12563
  }, g_cssObj.life_Border, g_cssObj.life_BorderColor),
12532
12564
 
12533
12565
  // 曲名・アーティスト名表示
12534
- createDivCss2Label(`lblCredit`, creditName, Object.assign(g_lblPosObj.lblCredit, { siz: checkMusicSiz(creditName, g_limitObj.musicTitleSiz) })),
12566
+ createDivCss2Label(`lblCredit`, creditName, { ...g_lblPosObj.lblCredit, siz: checkMusicSiz(creditName, g_limitObj.musicTitleSiz) }),
12535
12567
 
12536
12568
  // 譜面名表示
12537
- createDivCss2Label(`lblDifName`, difName, Object.assign(g_lblPosObj.lblDifName, { siz: checkMusicSiz(difName, 12) })),
12569
+ createDivCss2Label(`lblDifName`, difName, { ...g_lblPosObj.lblDifName, siz: checkMusicSiz(difName, 12) }),
12538
12570
 
12539
12571
  // 曲時間表示:現在時間
12540
- createDivCss2Label(`lblTime1`, `-:--`, Object.assign(g_lblPosObj.lblTime1, { display: g_workObj.musicinfoDisp })),
12572
+ createDivCss2Label(`lblTime1`, `-:--`, { ...g_lblPosObj.lblTime1, display: g_workObj.musicinfoDisp }),
12541
12573
 
12542
12574
  // 曲時間表示:総時間
12543
- createDivCss2Label(`lblTime2`, `/ ${fullTime}`, Object.assign(g_lblPosObj.lblTime2, { display: g_workObj.musicinfoDisp })),
12575
+ createDivCss2Label(`lblTime2`, `/ ${fullTime}`, { ...g_lblPosObj.lblTime2, display: g_workObj.musicinfoDisp }),
12544
12576
  );
12545
12577
 
12546
12578
  if (g_stateObj.frzReturn !== C_FLG_OFF) {
12547
12579
  multiAppend(infoSprite,
12548
12580
  // FrzReturnゲージ
12549
- createColorObject2(`lifeBackFrzObj`, {
12550
- x: 0, y: 50, w: 5, h: g_headerObj.playingHeight - 100, styleName: `lifeBarFrz`, display: g_workObj.scoreDisp,
12551
- }, g_cssObj.life_Background),
12552
- createColorObject2(`lifeBarFrz`, {
12553
- x: 0, y: 50, w: 5, h: 0, styleName: `lifeBarFrz`, display: g_workObj.scoreDisp,
12554
- }, g_cssObj.main_stepShobon),
12581
+ createColorObject2(`lifeBackFrzObj`, { ...g_lblPosObj.lifeBackFrzObj, display: g_workObj.scoreDisp }, g_cssObj.life_Background),
12582
+ createColorObject2(`lifeBarFrz`, { ...g_lblPosObj.lifeBarFrz, display: g_workObj.scoreDisp }, g_cssObj.main_stepShobon),
12555
12583
  )
12556
12584
  }
12557
12585
 
12558
12586
  if (g_workObj.nonDefaultSc) {
12559
12587
  multiAppend(infoSprite,
12560
- createDivCss2Label(`lblRetry`, `[${g_lblNameObj.l_retry}]`, Object.assign(g_lblPosObj.lblMainScHeader, { y: g_headerObj.playingHeight - 65 })),
12588
+ createDivCss2Label(`lblRetry`, `[${g_lblNameObj.l_retry}]`, { ...g_lblPosObj.lblMainScHeader, y: g_headerObj.playingHeight - 65 }),
12561
12589
  createDivCss2Label(`lblRetrySc`, g_kCd[g_headerObj.keyRetry],
12562
- Object.assign(g_lblPosObj.lblMainScKey, { y: g_headerObj.playingHeight - 50, fontWeight: g_headerObj.keyRetry === C_KEY_RETRY ? `normal` : `bold` })),
12563
- createDivCss2Label(`lblTitleBack`, `[${g_lblNameObj.l_titleBack}]`, Object.assign(g_lblPosObj.lblMainScHeader, { y: g_headerObj.playingHeight - 35 })),
12590
+ { ...g_lblPosObj.lblMainScKey, y: g_headerObj.playingHeight - 50, fontWeight: g_headerObj.keyRetry === C_KEY_RETRY ? `normal` : `bold` }),
12591
+ createDivCss2Label(`lblTitleBack`, `[${g_lblNameObj.l_titleBack}]`, { ...g_lblPosObj.lblMainScHeader, y: g_headerObj.playingHeight - 35 }),
12564
12592
  createDivCss2Label(`lblTitleBackSc`, g_isMac ? `Shift+${g_kCd[g_headerObj.keyRetry]}` : g_kCd[g_headerObj.keyTitleBack],
12565
- Object.assign(g_lblPosObj.lblMainScKey, { y: g_headerObj.playingHeight - 20, fontWeight: g_headerObj.keyTitleBack === C_KEY_TITLEBACK ? `normal` : `bold` })),
12593
+ { ...g_lblPosObj.lblMainScKey, y: g_headerObj.playingHeight - 20, fontWeight: g_headerObj.keyTitleBack === C_KEY_TITLEBACK ? `normal` : `bold` }),
12566
12594
  );
12567
12595
  }
12568
12596
 
@@ -12576,7 +12604,7 @@ const mainInit = () => {
12576
12604
  const wordSprite = createEmptySprite(judgeSprite, `wordSprite`, { w: g_headerObj.playingWidth });
12577
12605
  for (let j = 0; j <= g_scoreObj.wordMaxDepth; j++) {
12578
12606
  const wordY = (j % 2 === 0 ? 10 : (g_headerObj.bottomWordSetFlg ? g_posObj.distY + 10 : g_headerObj.playingHeight - 60));
12579
- wordSprite.appendChild(createDivCss2Label(`lblword${j}`, ``, Object.assign(g_lblPosObj.lblWord, { y: wordY, fontFamily: getBasicFont() })));
12607
+ wordSprite.appendChild(createDivCss2Label(`lblword${j}`, ``, { ...g_lblPosObj.lblWord, y: wordY, fontFamily: getBasicFont() }));
12580
12608
  }
12581
12609
 
12582
12610
  const jdgGroups = [`J`, `FJ`];
@@ -12678,7 +12706,7 @@ const mainInit = () => {
12678
12706
  }
12679
12707
 
12680
12708
  // ユーザカスタムイベント(初期)
12681
- g_customJsObj.main.forEach(func => func());
12709
+ safeExecuteCustomHooks(`g_customJsObj.main`, g_customJsObj.main);
12682
12710
 
12683
12711
  /**
12684
12712
  * キーを押したときの処理
@@ -12926,7 +12954,7 @@ const mainInit = () => {
12926
12954
  if (_cnt === 0) {
12927
12955
  const stepDivHit = document.getElementById(`stepHit${_j}`);
12928
12956
 
12929
- g_customJsObj.dummyArrow.forEach(func => func());
12957
+ safeExecuteCustomHooks(`g_customJsObj.dummyArrow`, g_customJsObj.dummyArrow);
12930
12958
  stepDivHit.style.top = wUnit(-15);
12931
12959
  stepDivHit.style.opacity = 1;
12932
12960
  stepDivHit.classList.value = ``;
@@ -12946,7 +12974,7 @@ const mainInit = () => {
12946
12974
 
12947
12975
  // ダミーフリーズアロー(成功時)
12948
12976
  dummyFrzOK: (_j, _k, _frzName, _cnt) => {
12949
- g_customJsObj.dummyFrz.forEach(func => func());
12977
+ safeExecuteCustomHooks(`g_customJsObj.dummyFrz`, g_customJsObj.dummyFrz);
12950
12978
  $id(`frzHit${_j}`).opacity = 0;
12951
12979
  g_attrObj[_frzName].judgEndFlg = true;
12952
12980
  judgeObjDelete.dummyFrz(_j, _frzName);
@@ -13166,7 +13194,7 @@ const mainInit = () => {
13166
13194
  arrowSubRoot.appendChild(createColorObject2(`${_name}Top${_j}_${_arrowCnt}`, {
13167
13195
  background: _color, rotate: g_workObj.arrowRtn[_j],
13168
13196
  }));
13169
- g_customJsObj.makeArrow.forEach(func => func(_attrs, arrowName, _name, _arrowCnt));
13197
+ safeExecuteCustomHooks(`g_customJsObj.makeArrow`, g_customJsObj.makeArrow, _attrs, arrowName, _name, _arrowCnt);
13170
13198
  };
13171
13199
 
13172
13200
  /**
@@ -13335,7 +13363,7 @@ const mainInit = () => {
13335
13363
  addTransform(frzName, `rootX`, `translateX(${g_workObj.stepX[_j]}px)`);
13336
13364
  }
13337
13365
 
13338
- g_customJsObj.makeFrzArrow.forEach(func => func(_attrs, frzName, _name, _arrowCnt));
13366
+ safeExecuteCustomHooks(`g_customJsObj.makeFrzArrow`, g_customJsObj.makeFrzArrow, _attrs, frzName, _name, _arrowCnt);
13339
13367
  };
13340
13368
 
13341
13369
  /**
@@ -13454,7 +13482,7 @@ const mainInit = () => {
13454
13482
  }
13455
13483
 
13456
13484
  // ユーザカスタムイベント(フレーム毎)
13457
- g_customJsObj.mainEnterFrame.forEach(func => func());
13485
+ safeExecuteCustomHooks(`g_customJsObj.mainEnterFrame`, g_customJsObj.mainEnterFrame);
13458
13486
 
13459
13487
  // 速度変化 (途中変速, 個別加速)
13460
13488
  while (currentFrame >= g_workObj.speedData?.[speedCnts]) {
@@ -13652,7 +13680,7 @@ const mainInit = () => {
13652
13680
  g_timeoutEvtId = setTimeout(flowTimeline, 1000 / g_fps - buffTime);
13653
13681
  }
13654
13682
  };
13655
- g_skinJsObj.main.forEach(func => func());
13683
+ safeExecuteCustomHooks(`g_skinJsObj.main`, g_skinJsObj.main);
13656
13684
 
13657
13685
  g_audio.currentTime = firstFrame / g_fps * g_headerObj.playbackRate;
13658
13686
  g_audio.playbackRate = g_headerObj.playbackRate;
@@ -13733,9 +13761,9 @@ const makeStepZone = (_j, _keyCtrlPtn) => {
13733
13761
  }, g_cssObj.main_stepKeyDown),
13734
13762
 
13735
13763
  // ヒット時モーション
13736
- createColorObject2(`stepHit${_j}`, Object.assign(g_lblPosObj.stepHit, {
13737
- rotate: g_workObj.stepHitRtn[_j], styleName: `StepHit`, opacity: 0,
13738
- }), g_cssObj.main_stepDefault),
13764
+ createColorObject2(`stepHit${_j}`, {
13765
+ ...g_lblPosObj.stepHit, rotate: g_workObj.stepHitRtn[_j], styleName: `StepHit`, opacity: 0,
13766
+ }, g_cssObj.main_stepDefault),
13739
13767
 
13740
13768
  );
13741
13769
 
@@ -13755,9 +13783,9 @@ const makeStepZone = (_j, _keyCtrlPtn) => {
13755
13783
  );
13756
13784
  } else {
13757
13785
  frzHit.appendChild(
13758
- createColorObject2(`frzHitTop${_j}`, Object.assign(g_lblPosObj.frzHitTop, {
13759
- rotate: g_workObj.arrowRtn[_j], styleName: `Shadow`,
13760
- }), g_cssObj.main_frzHitTop)
13786
+ createColorObject2(`frzHitTop${_j}`, {
13787
+ ...g_lblPosObj.frzHitTop, rotate: g_workObj.arrowRtn[_j], styleName: `Shadow`,
13788
+ }, g_cssObj.main_frzHitTop)
13761
13789
  );
13762
13790
  }
13763
13791
  };
@@ -13872,7 +13900,7 @@ const changeAppearanceFilter = (_num = 10) => {
13872
13900
  }
13873
13901
 
13874
13902
  // ユーザカスタムイベント(アルファマスクの再描画)
13875
- g_customJsObj.appearanceFilter.forEach(func => func(topNum, bottomNum));
13903
+ safeExecuteCustomHooks(`g_customJsObj.appearanceFilter`, g_customJsObj.appearanceFilter, topNum, bottomNum);
13876
13904
 
13877
13905
  return doubleFilterFlg;
13878
13906
  };
@@ -14196,7 +14224,7 @@ const changeHitFrz = (_j, _k, _name, _difFrame = 0) => {
14196
14224
  if (g_stateObj.frzReturn !== C_FLG_OFF) {
14197
14225
  startFrzReturn();
14198
14226
  }
14199
- g_customJsObj[`judg_${_name}Hit`].forEach(func => func(_difFrame));
14227
+ safeExecuteCustomHooks(`g_customJsObj.judg_${_name}Hit`, g_customJsObj[`judg_${_name}Hit`], _difFrame);
14200
14228
  };
14201
14229
 
14202
14230
  /**
@@ -14354,18 +14382,23 @@ const displayDiff = (_difFrame, _fjdg = ``, _justFrames = g_headerObj.justFrames
14354
14382
  const changeLifeColor = (_state = ``) => {
14355
14383
  const lblLife = document.getElementById(`lblLife`);
14356
14384
  const lifeBar = document.getElementById(`lifeBar`);
14357
- if (_state !== ``) {
14385
+
14386
+ if (_state !== `` && _state !== g_workObj.currentLifeState) {
14358
14387
  const lifeCss = g_cssObj[`life_${_state}`];
14359
14388
  lblLife.classList.remove(g_cssObj.life_Max, g_cssObj.life_Cleared, g_cssObj.life_Failed);
14360
14389
  lifeBar.classList.remove(g_cssObj.life_Max, g_cssObj.life_Cleared, g_cssObj.life_Failed);
14361
14390
  lblLife.classList.add(lifeCss);
14362
14391
  lifeBar.classList.add(lifeCss);
14392
+
14393
+ g_workObj.currentLifeState = _state;
14363
14394
  }
14364
14395
 
14365
14396
  const intLifeVal = Math.floor(g_workObj.lifeVal);
14366
14397
  lblLife.textContent = intLifeVal;
14367
- lifeBar.style.top = wUnit(50 + (g_headerObj.playingHeight - 100) * (g_headerObj.maxLifeVal - intLifeVal) / g_headerObj.maxLifeVal);
14368
- lifeBar.style.height = wUnit((g_headerObj.playingHeight - 100) * intLifeVal / g_headerObj.maxLifeVal);
14398
+
14399
+ const playableHeight = g_headerObj.playingHeight - 100;
14400
+ lifeBar.style.top = wUnit(50 + playableHeight * (g_headerObj.maxLifeVal - intLifeVal) / g_headerObj.maxLifeVal);
14401
+ lifeBar.style.height = wUnit(playableHeight * intLifeVal / g_headerObj.maxLifeVal);
14369
14402
  };
14370
14403
 
14371
14404
  /**
@@ -14438,8 +14471,9 @@ const judgeRecovery = (_name, _difFrame) => {
14438
14471
 
14439
14472
  if (g_stateObj.frzReturn !== C_FLG_OFF) {
14440
14473
  g_workObj.arrowReturnVal = (g_resultObj.ii + g_resultObj.shakin) % 100;
14441
- $id(`lifeBarFrz`).top = wUnit(50 + (g_headerObj.playingHeight - 100) * (100 - g_workObj.arrowReturnVal) / 100);
14442
- $id(`lifeBarFrz`).height = wUnit((g_headerObj.playingHeight - 100) * g_workObj.arrowReturnVal / 100);
14474
+ const playableHeight = g_headerObj.playingHeight - 100;
14475
+ $id(`lifeBarFrz`).top = wUnit(50 + playableHeight * (100 - g_workObj.arrowReturnVal) / 100);
14476
+ $id(`lifeBarFrz`).height = wUnit(playableHeight * g_workObj.arrowReturnVal / 100);
14443
14477
  if (g_workObj.arrowReturnVal === 0) {
14444
14478
  startFrzReturn();
14445
14479
  }
@@ -14447,7 +14481,7 @@ const judgeRecovery = (_name, _difFrame) => {
14447
14481
  if (_name === `shakin`) {
14448
14482
  quickRetry(`Shakin(Great)`);
14449
14483
  }
14450
- g_customJsObj[`judg_${_name}`].forEach(func => func(_difFrame));
14484
+ safeExecuteCustomHooks(`g_customJsObj.judg_${_name}`, g_customJsObj[`judg_${_name}`], _difFrame);
14451
14485
  };
14452
14486
 
14453
14487
  /**
@@ -14461,7 +14495,7 @@ const judgeDamage = (_name, _difFrame) => {
14461
14495
  comboJ.textContent = ``;
14462
14496
  diffJ.textContent = ``;
14463
14497
  lifeDamage();
14464
- g_customJsObj[`judg_${_name}`].forEach(func => func(_difFrame));
14498
+ safeExecuteCustomHooks(`g_customJsObj.judg_${_name}`, g_customJsObj[`judg_${_name}`], _difFrame);
14465
14499
  };
14466
14500
 
14467
14501
  /**
@@ -14486,7 +14520,7 @@ const judgeMatari = _difFrame => {
14486
14520
  finishViewing();
14487
14521
  quickRetry(`Matari(Good)`);
14488
14522
 
14489
- g_customJsObj.judg_matari.forEach(func => func(_difFrame));
14523
+ safeExecuteCustomHooks(`g_customJsObj.judg_matari`, g_customJsObj.judg_matari, _difFrame);
14490
14524
  };
14491
14525
 
14492
14526
  /**
@@ -14517,7 +14551,7 @@ const judgeKita = _difFrame => {
14517
14551
  lifeRecovery();
14518
14552
  finishViewing();
14519
14553
 
14520
- g_customJsObj.judg_kita.forEach(func => func(_difFrame));
14554
+ safeExecuteCustomHooks(`g_customJsObj.judg_kita`, g_customJsObj.judg_kita, _difFrame);
14521
14555
  };
14522
14556
 
14523
14557
  /**
@@ -14531,7 +14565,7 @@ const judgeIknai = _difFrame => {
14531
14565
 
14532
14566
  lifeDamage();
14533
14567
 
14534
- g_customJsObj.judg_iknai.forEach(func => func(_difFrame));
14568
+ safeExecuteCustomHooks(`g_customJsObj.judg_iknai`, g_customJsObj.judg_iknai, _difFrame);
14535
14569
  };
14536
14570
 
14537
14571
  const jdgList = [`ii`, `shakin`, `matari`, `shobon`].map(jdg => toCapitalize(jdg));
@@ -14777,19 +14811,20 @@ const resultInit = () => {
14777
14811
 
14778
14812
  // ランク描画
14779
14813
  resultWindow.appendChild(
14780
- createDivCss2Label(`lblRank`, rankMark, Object.assign(g_lblPosObj.lblRank, {
14781
- color: rankColor, fontFamily: getBasicFont(`"Bookman Old Style"`),
14782
- }))
14814
+ createDivCss2Label(`lblRank`, rankMark, {
14815
+ ...g_lblPosObj.lblRank, color: rankColor, fontFamily: getBasicFont(`"Bookman Old Style"`),
14816
+ })
14783
14817
  );
14784
14818
 
14785
14819
  // Cleared & Failed表示
14786
14820
  const lblResultPre = createDivCss2Label(
14787
14821
  `lblResultPre`,
14788
14822
  resultViewText(g_gameOverFlg ? `failed` : `cleared`),
14789
- Object.assign(g_lblPosObj.lblResultPre, {
14823
+ {
14824
+ ...g_lblPosObj.lblResultPre,
14790
14825
  animationDuration: (g_gameOverFlg ? `3s` : `2.5s`),
14791
14826
  animationName: (g_gameOverFlg ? `upToDownFade` : `leftToRightFade`)
14792
- }), g_cssObj.result_Cleared, g_cssObj.result_Window
14827
+ }, g_cssObj.result_Cleared, g_cssObj.result_Window
14793
14828
  );
14794
14829
  divRoot.appendChild(lblResultPre);
14795
14830
 
@@ -14992,7 +15027,7 @@ const resultInit = () => {
14992
15027
 
14993
15028
  // ユーザカスタムイベント(初期)
14994
15029
  const currentDateTime = new Date().toLocaleString();
14995
- g_customJsObj.result.forEach(func => func());
15030
+ safeExecuteCustomHooks(`g_customJsObj.result`, g_customJsObj.result);
14996
15031
 
14997
15032
  if (highscorePreCondition) {
14998
15033
 
@@ -15222,14 +15257,14 @@ const resultInit = () => {
15222
15257
  if (document.getElementById(`tmpClose`) === null) {
15223
15258
  divRoot.oncontextmenu = () => true;
15224
15259
  makeLinkButton(tmpDiv, `Tmp`);
15225
- tmpDiv.appendChild(createCss2Button(`tmpClose`, g_lblNameObj.b_close, () => true,
15226
- Object.assign(g_lblPosObj.btnRsCopyClose, {
15227
- resetFunc: () => {
15228
- tmpDiv.removeChild(canvas);
15229
- divRoot.removeChild(tmpDiv);
15230
- divRoot.oncontextmenu = () => false;
15231
- },
15232
- }), g_cssObj.button_Back));
15260
+ tmpDiv.appendChild(createCss2Button(`tmpClose`, g_lblNameObj.b_close, () => true, {
15261
+ ...g_lblPosObj.btnRsCopyClose,
15262
+ resetFunc: () => {
15263
+ tmpDiv.removeChild(canvas);
15264
+ divRoot.removeChild(tmpDiv);
15265
+ divRoot.oncontextmenu = () => false;
15266
+ },
15267
+ }, g_cssObj.button_Back));
15233
15268
  tmpDiv.appendChild(createDescDiv(`resultImageDesc`, g_lblNameObj.resultImageDesc));
15234
15269
  }
15235
15270
  };
@@ -15276,7 +15311,7 @@ const resultInit = () => {
15276
15311
  }
15277
15312
  clearTimeout(g_timeoutEvtId);
15278
15313
  clearTimeout(g_timeoutEvtResultId);
15279
- }, Object.assign(_posObj, { resetFunc: () => _func() }), _cssClass);
15314
+ }, { ..._posObj, resetFunc: () => _func() }, _cssClass);
15280
15315
 
15281
15316
  /**
15282
15317
  * 外部リンクボタンを作成
@@ -15286,14 +15321,14 @@ const resultInit = () => {
15286
15321
  const makeLinkButton = (_div = divRoot, _param = ``) => {
15287
15322
  multiAppend(_div,
15288
15323
  // リザルトデータをX (Twitter)へ転送
15289
- createCss2Button(`btnTweet${_param}`, g_lblNameObj.b_tweet, () => true, Object.assign(g_lblPosObj.btnRsTweet, {
15290
- resetFunc: () => openLink(tweetResult),
15291
- }), g_cssObj.button_Tweet),
15324
+ createCss2Button(`btnTweet${_param}`, g_lblNameObj.b_tweet, () => true, {
15325
+ ...g_lblPosObj.btnRsTweet, resetFunc: () => openLink(tweetResult),
15326
+ }, g_cssObj.button_Tweet),
15292
15327
 
15293
15328
  // Discordへのリンク
15294
- createCss2Button(`btnGitter${_param}`, g_lblNameObj.b_gitter, () => true, Object.assign(g_lblPosObj.btnRsGitter, {
15295
- resetFunc: () => openLink(g_linkObj.discord),
15296
- }), g_cssObj.button_Discord),
15329
+ createCss2Button(`btnGitter${_param}`, g_lblNameObj.b_gitter, () => true, {
15330
+ ...g_lblPosObj.btnRsGitter, resetFunc: () => openLink(g_linkObj.discord),
15331
+ }, g_cssObj.button_Discord),
15297
15332
  );
15298
15333
  }
15299
15334
 
@@ -15314,10 +15349,9 @@ const resultInit = () => {
15314
15349
  // リトライ
15315
15350
  resetCommonBtn(`btnRetry`, g_lblNameObj.b_retry, g_lblPosObj.btnRsRetry, loadMusic, g_cssObj.button_Reset),
15316
15351
 
15317
- createCss2Button(`btnCopyImage`, g_emojiObj.camera, () => true,
15318
- Object.assign(g_lblPosObj.btnRsCopyImage, {
15319
- resetFunc: () => copyResultImageData(g_msgInfoObj.I_0001),
15320
- }), g_cssObj.button_Default_NoColor),
15352
+ createCss2Button(`btnCopyImage`, g_emojiObj.camera, () => true, {
15353
+ ...g_lblPosObj.btnRsCopyImage, resetFunc: () => copyResultImageData(g_msgInfoObj.I_0001),
15354
+ }, g_cssObj.button_Default_NoColor),
15321
15355
  );
15322
15356
 
15323
15357
  // マスクスプライトを作成
@@ -15337,7 +15371,7 @@ const resultInit = () => {
15337
15371
  const flowResultTimeline = () => {
15338
15372
 
15339
15373
  // ユーザカスタムイベント(フレーム毎)
15340
- g_customJsObj.resultEnterFrame.forEach(func => func());
15374
+ safeExecuteCustomHooks(`g_customJsObj.resultEnterFrame`, g_customJsObj.resultEnterFrame);
15341
15375
 
15342
15376
  // 背景・マスクモーション、スキン変更
15343
15377
  drawTitleResultMotion(g_currentPage);
@@ -15371,7 +15405,7 @@ const resultInit = () => {
15371
15405
  setShortcutEvent(g_currentPage, () => true, { dfEvtFlg: true });
15372
15406
  document.oncontextmenu = () => true;
15373
15407
 
15374
- g_skinJsObj.result.forEach(func => func());
15408
+ safeExecuteCustomHooks(`g_skinJsObj.result`, g_skinJsObj.result);
15375
15409
  };
15376
15410
 
15377
15411
  /**
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2026/03/25 (v46.3.0)
8
+ * Revised : 2026/03/29 (v46.5.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -617,6 +617,15 @@ const updateWindowSiz = () => {
617
617
  lblTime2: {
618
618
  x: 60, y: g_headerObj.playingHeight - 30, w: 60, h: 20, siz: g_limitObj.mainSiz,
619
619
  },
620
+ lifeBackObj: {
621
+ x: 5, y: 50, w: 15, h: g_headerObj.playingHeight - 100, styleName: `lifeBar`,
622
+ },
623
+ lifeBackFrzObj: {
624
+ x: 0, y: 50, w: 3.5, h: g_headerObj.playingHeight - 100, styleName: `lifeBarFrz`,
625
+ },
626
+ lifeBarFrz: {
627
+ x: 0, y: 50, w: 4, h: 0, styleName: `lifeBarFrz`,
628
+ },
620
629
  lblWord: {
621
630
  x: 100, w: g_headerObj.playingWidth - 200, h: 50,
622
631
  siz: g_limitObj.mainSiz, align: C_ALIGN_LEFT, display: `block`, margin: C_DIS_AUTO,
@@ -954,6 +963,7 @@ const g_escapeStr = {
954
963
  const g_emojiObj = {
955
964
  checkMark: `&#x2714;`, // チェックマーク (check mark)
956
965
  crossMark: `&#x274c;`, // バツ (cross mark)
966
+ policeLight: `&#x1f6a8;`, // パトランプ (police car light)
957
967
  muted: `&#x1f507;`, // 無音のスピーカー (muted speaker)
958
968
  speaker: `&#x1f50a;`, // 音量大のスピーカー (speaker high volume)
959
969
  shield: `&#x1f6e1;`, // 盾 (shield)
@@ -4818,6 +4828,8 @@ const g_lang_msgObj = {
4818
4828
  pickColorR: `設定する矢印色の種類を切り替えます。`,
4819
4829
  pickColorCopy: `このボタンを押すと、フリーズアローの配色を矢印(枠)の色で上書きします。\nヒット時のフリーズアローの色も上書きします。`,
4820
4830
  pickColorReset: `矢印・フリーズアローの配色を元に戻します。`,
4831
+
4832
+ customFunctionError: `初回の実行エラーが発生しました。修正されるまでこの処理は無視されます。`,
4821
4833
  },
4822
4834
 
4823
4835
  En: {
@@ -4916,6 +4928,8 @@ const g_lang_msgObj = {
4916
4928
  pickColorR: `Switches the arrow color type to be set.`,
4917
4929
  pickColorCopy: `Pressing this button will override the color scheme of the freeze arrow with the frame color of the arrow. \nIt also overrides the color of the freeze arrow on hit.`,
4918
4930
  pickColorReset: `Restore the color scheme for arrows and freeze arrows.`,
4931
+
4932
+ customFunctionError: `An error occurred during the first execution. This process will be ignored until the error is resolved.`,
4919
4933
  },
4920
4934
 
4921
4935
  };
@@ -5000,6 +5014,12 @@ const g_skinJsObj = {
5000
5014
  result: [],
5001
5015
  };
5002
5016
 
5017
+ const g_errorCache = {
5018
+ 'g_customJsObj.titleEnterFrame': [],
5019
+ 'g_customJsObj.mainEnterFrame': [],
5020
+ 'g_customJsObj.resultEnterFrame': [],
5021
+ };
5022
+
5003
5023
  /**
5004
5024
  * 従来のカスタム関数をg_customJsObj, g_skinJsObjへ追加
5005
5025
  * - customjsファイルを読み込んだ直後にこの関数を呼び出している
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "46.3.0",
3
+ "version": "46.5.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
+ "jsdelivr": "./js/danoni_main.js",
6
7
  "scripts": {
7
8
  "test": "echo \"Error: no test specified\" && exit 1"
8
9
  },