danoniplus 47.1.1 → 47.3.2

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/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/04/22
7
+ * Revised : 2026/04/25
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 47.1.1`;
12
- const g_revisedDate = `2026/04/22`;
11
+ const g_version = `Ver 47.3.2`;
12
+ const g_revisedDate = `2026/04/25`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -3451,7 +3451,11 @@ const createSplitCanvases = (_width, _totalHeight, _dpr) => {
3451
3451
 
3452
3452
  /**
3453
3453
  * 描画対象のCanvasを判定して描画を実行する
3454
- * @param {HTMLCanvasElement[]} _canvases
3454
+ * @param {object[]} _canvases
3455
+ * @param {HTMLCanvasElement} _canvases[].canvas 分割されたCanvas要素
3456
+ * @param {CanvasRenderingContext2D} _canvases[].ctx Canvasの描画コンテキスト
3457
+ * @param {number} _canvases[].offsetTop Canvasの論理上のオフセット位置
3458
+ * @param {number} _canvases[].logicalHeight Canvasの論理上の高さ
3455
3459
  * @param {number} _y
3456
3460
  * @param {number} _h
3457
3461
  * @param {number} _dpr
@@ -7286,6 +7290,7 @@ const drawSpeedGraph = _scoreId => {
7286
7290
  speed: { frame: [0], speed: [1], cnt: 0, strokeColor: g_graphColorObj.speed },
7287
7291
  boost: { frame: [0], speed: [1], cnt: 0, strokeColor: g_graphColorObj.boost }
7288
7292
  };
7293
+ const dpr = window.devicePixelRatio || 1;
7289
7294
 
7290
7295
  const tmpSpeedPoint = [0];
7291
7296
  Object.keys(speedObj).forEach(speedType => {
@@ -7837,18 +7842,40 @@ const makeHighScore = _scoreId => {
7837
7842
  }
7838
7843
  };
7839
7844
 
7840
- const drawMinimap = (_scoreId, _initFlg = false) => {
7845
+ /**
7846
+ * 譜面ミニマップを描画し、適切なスクロール位置へ調整する。
7847
+ * デフォルトはスクロール反転で、現在位置を保つように上下反転する。
7848
+ * @param {string} _scoreId - 描画対象の譜面ID
7849
+ * @param {object} [_options={}] - オプションパラメータ
7850
+ * @param {boolean} [_options._initFlg=false] - 譜面切替モード
7851
+ * - 前の譜面での演奏進行度(時間軸の比率)を算出し、新しい譜面でも同じ進行位置を表示する。
7852
+ * 位置記憶がない場合はフェードイン設定に基づく初期位置を表示。
7853
+ * @param {boolean} [_options._fadeinFlg=false] - フェードインモード。フェードイン設定値を優先して移動する。
7854
+ */
7855
+ const drawMinimap = (_scoreId, { _initFlg = false, _fadeinFlg = false } = {}) => {
7841
7856
  const detailMiniMap = document.getElementById(`detailMiniMap`);
7842
7857
  if (detailMiniMap === null) return; // scoreDetailUse=false 等で未生成の場合は何もしない
7843
7858
 
7844
- const currentScrollTop = document.getElementById(`detailMiniMapSub`)
7845
- ? document.getElementById(`detailMiniMapSub`).scrollTop : 0;
7859
+ const isRev = g_stateObj.miniMapRevFlg;
7860
+ const subEl = document.getElementById(`detailMiniMapSub`);
7861
+ const currentScrollTop = subEl ? subEl.scrollTop : 0;
7862
+
7863
+ // 前の譜面でスクロール可能な状態だったかを判定
7864
+ const hasPreviousScrollRange = subEl && subEl.scrollHeight > subEl.clientHeight;
7865
+
7866
+ // 前の譜面での「スクロール位置の比率」を計算
7867
+ // 完全に一番上のときは 0、一番下のときは 1 となる比率
7868
+ let progressRatio = 0;
7869
+ if (hasPreviousScrollRange) {
7870
+ const rawRatio = currentScrollTop / (subEl.scrollHeight - subEl.clientHeight);
7871
+ // リバース時は「上が終点」なので、進行度としては反転させる
7872
+ progressRatio = isRev ? (1.0 - rawRatio) : rawRatio;
7873
+ }
7846
7874
 
7847
7875
  // 再描画のため一度クリア
7848
7876
  deleteChildspriteAll(`detailMiniMap`);
7849
7877
 
7850
- // drawMinimap 内の Canvas 追加部分
7851
- const isRev = g_stateObj.miniMapRevFlg;
7878
+ // --- ミニマップ生成/取得 (Lazy Generation) ---
7852
7879
  let savedCanvases = isRev
7853
7880
  ? g_detailObj.scoreMinimapReverse[_scoreId]
7854
7881
  : g_detailObj.scoreMinimap[_scoreId];
@@ -7868,27 +7895,25 @@ const drawMinimap = (_scoreId, _initFlg = false) => {
7868
7895
 
7869
7896
  // --- ヘッダー部分 ---
7870
7897
  const detailMiniMapHeader = createEmptySprite(detailMiniMap, `detailMiniMapHeader`, g_windowObj.detailMiniMapHeader);
7871
- $id(`detailMiniMapHeader`).top = (g_stateObj.miniMapRevFlg ? 230 : 0) + `px`;
7898
+ $id(`detailMiniMapHeader`).top = (g_stateObj.miniMapRevFlg ? 230 + g_sHeight - 500 : 0) + `px`;
7872
7899
  detailMiniMapHeader.appendChild(g_detailObj.scoreMinimapHeader[_scoreId]);
7873
7900
 
7874
7901
  // --- メイン(譜面)部分 ---
7875
7902
  const detailMiniMapSub = createEmptySprite(detailMiniMap, `detailMiniMapSub`, g_windowObj.detailMiniMapSub);
7876
7903
  $id(`detailMiniMapSub`).top = (g_stateObj.miniMapRevFlg ? 0 : 15) + `px`;
7877
7904
 
7878
- detailMiniMapSub.style.overflowX = 'hidden';
7879
- detailMiniMapSub.style.overflowY = 'auto';
7880
- detailMiniMapSub.style.pointerEvents = 'auto';
7881
- detailMiniMapSub.style.display = 'block';
7882
- detailMiniMapSub.style.textAlign = 'left';
7905
+ Object.assign(detailMiniMapSub.style, {
7906
+ overflowX: 'hidden',
7907
+ overflowY: 'auto',
7908
+ pointerEvents: 'auto',
7909
+ display: 'block',
7910
+ textAlign: 'left',
7911
+ });
7883
7912
 
7884
7913
  if (savedCanvases && Array.isArray(savedCanvases)) {
7885
7914
  // 退避したCanvasそのものをDOMに追加(再描画不要で高速)
7886
- detailMiniMapSub.style.overflow = C_DIS_AUTO;
7887
- detailMiniMapSub.style.pointerEvents = C_DIS_AUTO;
7888
7915
  savedCanvases.forEach(canvas => {
7889
- canvas.style.position = 'static';
7890
- canvas.style.display = 'block';
7891
- canvas.style.height = 'auto';
7916
+ Object.assign(canvas.style, { position: 'static', display: 'block', height: 'auto' });
7892
7917
  detailMiniMapSub.appendChild(canvas);
7893
7918
  });
7894
7919
  }
@@ -7901,16 +7926,36 @@ const drawMinimap = (_scoreId, _initFlg = false) => {
7901
7926
  getStartFrame(lastFrame, g_stateObj.fadein, _scoreId) - firstArrowFrame
7902
7927
  ));
7903
7928
  const fadeinScrollTop = scrollHeight * fadeinFrameOffset / playingFrame;
7904
- detailMiniMapSub.scrollTop = g_stateObj.miniMapRevFlg
7905
- ? (_initFlg ? scrollHeight - currentScrollTop : scrollHeight - fadeinScrollTop)
7906
- : (_initFlg ? scrollHeight - currentScrollTop : fadeinScrollTop);
7929
+ const visualFadeinPos = isRev ? scrollHeight - fadeinScrollTop : fadeinScrollTop;
7930
+
7931
+ // --- スクロール位置の決定ロジック
7932
+ let targetScrollTop = 0;
7933
+
7934
+ if (_fadeinFlg) {
7935
+ // 【最優先】フェードイン操作時:設定値を強制適用
7936
+ targetScrollTop = visualFadeinPos;
7937
+ } else if (_initFlg) {
7938
+ // 【譜面切替時】
7939
+ if (hasPreviousScrollRange) {
7940
+ // 以前の進行度を継承
7941
+ const visualRatio = isRev ? (1.0 - progressRatio) : progressRatio;
7942
+ targetScrollTop = scrollHeight * visualRatio;
7943
+ } else {
7944
+ // 初回はフェードイン位置
7945
+ targetScrollTop = visualFadeinPos;
7946
+ }
7947
+ } else {
7948
+ // 【リバース切替時】物理反転
7949
+ targetScrollTop = scrollHeight - currentScrollTop;
7950
+ }
7951
+ detailMiniMapSub.scrollTop = targetScrollTop;
7907
7952
 
7908
7953
  if (document.getElementById(`lnkMiniMapRev`) === null) {
7909
7954
  scoreDetail.appendChild(
7910
7955
  makeDifLblCssButton(`lnkMiniMapRev`, g_lblNameObj.s_rev + `${g_stateObj.miniMapRevFlg ? `↑` : `↓`}`, 8, () => {
7911
7956
  g_stateObj.miniMapRevFlg = !g_stateObj.miniMapRevFlg;
7912
7957
  lnkMiniMapRev.textContent = g_lblNameObj.s_rev + `${g_stateObj.miniMapRevFlg ? `↑` : `↓`}`;
7913
- drawMinimap(g_stateObj.scoreId, true);
7958
+ drawMinimap(g_stateObj.scoreId);
7914
7959
  createScText(lnkMiniMapRev, `MiniMapRev`, { targetLabel: `lnkMiniMapRev`, x: -12 });
7915
7960
  }, g_lblPosObj.lnkMiniMapRev)
7916
7961
  );
@@ -8106,7 +8151,7 @@ const setDifficulty = (_initFlg) => {
8106
8151
  drawDensityGraph(g_stateObj.scoreId);
8107
8152
  makeDifInfo(g_stateObj.scoreId);
8108
8153
  makeHighScore(g_stateObj.scoreId);
8109
- drawMinimap(g_stateObj.scoreId);
8154
+ drawMinimap(g_stateObj.scoreId, { _initFlg: true });
8110
8155
  }
8111
8156
 
8112
8157
  // 楽曲データの表示
@@ -8189,13 +8234,18 @@ const createOptionWindow = _sprite => {
8189
8234
  const bkColor = window.getComputedStyle(textBaseObj, ``).backgroundColor;
8190
8235
 
8191
8236
  graphObj.id = `graph${_name}${j > 0 ? j + 1 : ``}`;
8192
- graphObj.width = g_limitObj.graphWidth;
8193
- graphObj.height = g_limitObj.graphHeight;
8237
+ const dpr = window.devicePixelRatio || 1;
8238
+ graphObj.width = g_limitObj.graphWidth * dpr;
8239
+ graphObj.height = g_limitObj.graphHeight * dpr;
8240
+ graphObj.style.width = wUnit(g_limitObj.graphWidth);
8241
+ graphObj.style.height = wUnit(g_limitObj.graphHeight);
8194
8242
  graphObj.style.left = wUnit(125);
8195
8243
  graphObj.style.top = wUnit(0);
8196
8244
  graphObj.style.position = `absolute`;
8197
8245
  graphObj.style.background = j === 0 ? bkColor : `#ffffff00`;
8198
8246
  graphObj.style.border = `dotted ${wUnit(2)}`;
8247
+ const ctx = graphObj.getContext(`2d`);
8248
+ ctx.scale(dpr, dpr);
8199
8249
 
8200
8250
  detailObj.appendChild(graphObj);
8201
8251
  }
@@ -8404,7 +8454,7 @@ const createOptionWindow = _sprite => {
8404
8454
  fadeinSlider.value = g_stateObj.fadein;
8405
8455
  lnkFadein.textContent = `${g_stateObj.fadein}${g_lblNameObj.percent}`;
8406
8456
  updateSettingSummary();
8407
- drawMinimap(g_stateObj.scoreId);
8457
+ drawMinimap(g_stateObj.scoreId, { _fadeinFlg: true });
8408
8458
  };
8409
8459
 
8410
8460
  multiAppend(spriteList.fadein,
@@ -8423,7 +8473,7 @@ const createOptionWindow = _sprite => {
8423
8473
  fadeinSlider.addEventListener(`input`, () => {
8424
8474
  g_stateObj.fadein = inputSlider(fadeinSlider, lnkFadein, `fadein`);
8425
8475
  updateSettingSummary();
8426
- drawMinimap(g_stateObj.scoreId);
8476
+ drawMinimap(g_stateObj.scoreId, { _fadeinFlg: true });
8427
8477
  }, false);
8428
8478
 
8429
8479
  // ---------------------------------------------------
@@ -11808,6 +11858,7 @@ const pushArrows = (_dataObj, _speedOnFrame, _firstArrivalFrame) => {
11808
11858
  g_workObj.arrivalFrame[frmPrev] = tmpObj.arrivalFrm;
11809
11859
  g_workObj.motionFrame[frmPrev] = tmpObj.motionFrm;
11810
11860
  g_workObj.initBoostY[frmPrev] = calcInitBoostY(frmPrev);
11861
+ let minNotesFrame = startPoint[lastk];
11811
11862
 
11812
11863
  if (_frzFlg) {
11813
11864
  g_workObj[`mk${camelHeader}Length`][_j] = [];
@@ -11856,6 +11907,41 @@ const pushArrows = (_dataObj, _speedOnFrame, _firstArrivalFrame) => {
11856
11907
  g_workObj.initBoostY[frmPrev] = calcInitBoostY(frmPrev);
11857
11908
  }
11858
11909
 
11910
+ // --- 逆転検知ロジック ---
11911
+ // 後ろからループしているため、minNotesFrameには「自分より譜面上後ろにあるノーツ」の
11912
+ // 最小生成フレーム(最も早く出現するもの)が入っている。
11913
+
11914
+ // 「自分より後ろのノーツ」の方が、「自分」よりも早く出現する場合、
11915
+ // 配列の順序と出現時間の順序が入れ替わっている(逆転)とみなす。
11916
+ // 逆転している場合は、最小生成フレームまでさらに遡って、出現フレームを再計算する。
11917
+ if (minNotesFrame < startPoint[k]) {
11918
+
11919
+ const getAdjArrowStartFrame = (_obj, _speedOnFrame, _targetFrame) => {
11920
+ while (_obj.frm > _targetFrame) {
11921
+ _obj.startY += _speedOnFrame[_obj.frm - 1];
11922
+
11923
+ if (_speedOnFrame[_obj.frm - 1] !== 0) {
11924
+ _obj.motionFrm++;
11925
+ }
11926
+ _obj.frm--;
11927
+ _obj.arrivalFrm++;
11928
+ }
11929
+ return _obj;
11930
+ };
11931
+ tmpObj = getAdjArrowStartFrame(tmpObj, _speedOnFrame, minNotesFrame);
11932
+ startPoint[k] = tmpObj.frm;
11933
+ frmPrev = tmpObj.frm;
11934
+ g_workObj.initY[frmPrev] = tmpObj.startY;
11935
+ g_workObj.arrivalFrame[frmPrev] = tmpObj.arrivalFrm;
11936
+ g_workObj.motionFrame[frmPrev] = tmpObj.motionFrm;
11937
+ g_workObj.initBoostY[frmPrev] = calcInitBoostY(frmPrev);
11938
+ }
11939
+
11940
+ // 最小値を更新
11941
+ if (startPoint[k] < minNotesFrame) {
11942
+ minNotesFrame = startPoint[k];
11943
+ }
11944
+
11859
11945
  // 出現タイミングを保存
11860
11946
  setNotes(_j, k, _data, startPoint[k], camelHeader, _frzFlg,
11861
11947
  { initY: tmpObj.startY, initBoostY: g_workObj.initBoostY[frmPrev], arrivalFrame: tmpObj.arrivalFrm, motionFrame: tmpObj.motionFrm }
@@ -15382,8 +15468,12 @@ const resultInit = () => {
15382
15468
  for (let j = 0; j < 2; j++) {
15383
15469
  const canvas = document.createElement(`canvas`);
15384
15470
  canvas.id = `graphGaugeTransition${j > 0 ? j + 1 : ``}`;
15385
- canvas.width = g_limitObj.gaugeTransitionWidth;
15386
- canvas.height = g_limitObj.gaugeTransitionHeight;
15471
+ const dpr = window.devicePixelRatio || 1;
15472
+ canvas.width = g_limitObj.gaugeTransitionWidth * dpr;
15473
+ canvas.height = g_limitObj.gaugeTransitionHeight * dpr;
15474
+ canvas.style.width = wUnit(g_limitObj.gaugeTransitionWidth);
15475
+ canvas.style.height = wUnit(g_limitObj.gaugeTransitionHeight);
15476
+ canvas.getContext(`2d`).scale(dpr, dpr);
15387
15477
  canvas.style.left = wUnit(0);
15388
15478
  canvas.style.top = wUnit(0);
15389
15479
  canvas.style.position = `absolute`;
@@ -15510,13 +15600,14 @@ const resultInit = () => {
15510
15600
 
15511
15601
  const canvas = document.getElementById(`graphGaugeTransition2`);
15512
15602
  const ctx = canvas.getContext(`2d`);
15513
- const x = cursorFrame / playingFrame * canvas.width;
15514
- ctx.clearRect(0, 0, canvas.width, canvas.height);
15603
+ const [w, h] = [parseInt(canvas.style.width), parseInt(canvas.style.height)];
15604
+ const x = cursorFrame / playingFrame * w;
15605
+ ctx.clearRect(0, 0, w, h);
15515
15606
 
15516
15607
  // 縦線
15517
15608
  ctx.beginPath();
15518
15609
  ctx.moveTo(x, 0);
15519
- ctx.lineTo(x, canvas.height);
15610
+ ctx.lineTo(x, h);
15520
15611
  ctx.strokeStyle = "#009999";
15521
15612
  ctx.lineWidth = 1.5;
15522
15613
  ctx.stroke();
@@ -15525,10 +15616,10 @@ const resultInit = () => {
15525
15616
  const timer = transFrameToTimer(cursorFrame + startFrame);
15526
15617
  ctx.font = `14px ${getBasicFont()}`;
15527
15618
  ctx.fillStyle = "#009999";
15528
- ctx.textAlign = x > canvas.width * 0.8 ? C_ALIGN_RIGHT : C_ALIGN_LEFT;
15619
+ ctx.textAlign = x > w * 0.8 ? C_ALIGN_RIGHT : C_ALIGN_LEFT;
15529
15620
  ctx.fillText(
15530
15621
  `${timer}`,
15531
- x > canvas.width * 0.8 ? x - 5 : x + 5,
15622
+ x > w * 0.8 ? x - 5 : x + 5,
15532
15623
  g_limitObj.gaugeTransitionHeight - 35
15533
15624
  );
15534
15625
  };
@@ -15693,22 +15784,28 @@ const resultInit = () => {
15693
15784
  tmpDiv.style.background = `#000000cc`;
15694
15785
  const canvas = document.createElement(`canvas`);
15695
15786
  const artistName = g_headerObj.artistNames[g_headerObj.musicNos[g_stateObj.scoreId]] || g_headerObj.artistName;
15787
+ const dpr = window.devicePixelRatio || 1;
15788
+ const logicalWidth = 400;
15789
+ const logicalHeight = g_sHeight - 90;
15696
15790
 
15697
15791
  canvas.id = `resultImage`;
15698
- canvas.width = 400;
15699
- canvas.height = g_sHeight - 90;
15700
- canvas.style.left = wUnit((g_sWidth - canvas.width) / 2);
15792
+ canvas.width = logicalWidth * dpr;
15793
+ canvas.height = logicalHeight * dpr;
15794
+ canvas.style.width = wUnit(logicalWidth);
15795
+ canvas.style.height = wUnit(logicalHeight);
15796
+ canvas.style.left = wUnit((g_sWidth - parseFloat(canvas.style.width)) / 2);
15701
15797
  canvas.style.top = wUnit(20);
15702
15798
  canvas.style.position = `absolute`;
15703
15799
 
15704
15800
  const context = canvas.getContext(`2d`);
15801
+ context.scale(dpr, dpr);
15705
15802
  const drawText = (_text, { x = 30, dy = 0, hy, siz = 15, color = `#cccccc`, align = C_ALIGN_LEFT, font } = {}) => {
15706
15803
  context.font = `${wUnit(siz)} ${getBasicFont(font)}`;
15707
15804
  context.fillStyle = color;
15708
15805
  context.textAlign = align;
15709
15806
  context.fillText(_text, x, 35 + hy * 18 + dy);
15710
15807
  };
15711
- makeBgCanvas(context, { h: canvas.height });
15808
+ makeBgCanvas(context, { w: logicalWidth, h: logicalHeight });
15712
15809
 
15713
15810
  drawText(`R`, { dy: -5, hy: 0, siz: 40, color: `#9999ff` });
15714
15811
  drawText(`ESULT`, { x: 57, dy: -5, hy: 0, siz: 25 });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2026/04/21 (v47.1.0)
8
+ * Revised : 2026/04/25 (v47.3.2)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -212,9 +212,9 @@ const updateWindowSiz = () => {
212
212
  difCover: { x: 20, y: 60, w: 145, h: 270 + g_sHeight - 500, opacity: 0.95, pointerEvents: C_DIS_AUTO },
213
213
  difFilter: { x: 0, y: 66, w: 140, h: 204 + g_sHeight - 500, overflow: C_DIS_AUTO, pointerEvents: C_DIS_AUTO },
214
214
  displaySprite: { x: 25, y: 30, w: (g_sWidth - 450) / 2, h: g_limitObj.setLblHeight * 5 },
215
- scoreDetail: { x: 20, y: 85, w: (g_sWidth - 500) / 2 + 420, h: 245, visibility: `hidden`, pointerEvents: C_DIS_AUTO },
215
+ scoreDetail: { x: 20, y: 85, w: (g_sWidth - 500) / 2 + 420, h: 245 + g_sHeight - 500, visibility: `hidden`, pointerEvents: C_DIS_AUTO },
216
216
  detailMiniMapHeader: { x: 110, y: 0, w: (g_sWidth - 500) / 2 + 310, h: 15 },
217
- detailMiniMapSub: { x: 110, y: 15, w: (g_sWidth - 500) / 2 + 310, h: 230, overflow: C_DIS_AUTO, pointerEvents: C_DIS_AUTO },
217
+ detailMiniMapSub: { x: 110, y: 15, w: (g_sWidth - 500) / 2 + 310, h: 230 + g_sHeight - 500, overflow: C_DIS_AUTO, pointerEvents: C_DIS_AUTO },
218
218
  detailObj: { w: (g_sWidth - 500) / 2 + 420, h: 230, visibility: `hidden` },
219
219
  keyconSprite: { y: 105, h: g_sHeight - 105, overflow: C_DIS_AUTO },
220
220
  loader: { y: g_sHeight - 10, h: 10, backgroundColor: `#333333` },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "47.1.1",
3
+ "version": "47.3.2",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "jsdelivr": "./js/danoni_main.js",