danoniplus 52.1.0 → 52.2.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/css/danoni_main.css +16 -1
- package/danoni/ringtest.html +98 -0
- package/js/danoni_main.js +14 -3
- package/js/lib/danoni_constants.js +109 -11
- package/js/lib/dataLoader.js +333 -9
- package/js/lib/dosConverter.js +32 -5
- package/js/lib/mainWindow.js +382 -26
- package/js/lib/result.js +3 -1
- package/js/lib/settings.js +327 -21
- package/package.json +1 -1
- package/skin/danoni_skin_default.css +3 -1
- package/skin/danoni_skin_light.css +3 -1
- package/skin/danoni_skin_skyblue.css +3 -1
- package/sound/nosound.mp3 +0 -0
package/js/lib/mainWindow.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2026/09/13
|
|
8
|
-
* Revised : 2026/
|
|
8
|
+
* Revised : 2026/11/08 (v52.2.0)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -51,6 +51,18 @@ const mainInit = () => {
|
|
|
51
51
|
addTransform(`infoSprite`, `main`,
|
|
52
52
|
`translateX(${wUnit(g_workObj.playingX)}) translateY(${wUnit(g_headerObj.playingY)})`, g_transPriority.base);
|
|
53
53
|
|
|
54
|
+
// リングノート用スプライト (mainSpriteの外: PlayWindow/Shaking等の影響を受けない)
|
|
55
|
+
let ringSprite = null;
|
|
56
|
+
if ((g_scoreObj.ringData?.length ?? 0) > 0) {
|
|
57
|
+
bindRingPointer(divRoot);
|
|
58
|
+
startRingKeepAlive();
|
|
59
|
+
ringSprite = createEmptySprite(divRoot, `ringSprite`, mainCommonPos);
|
|
60
|
+
addTransform(`ringSprite`, `main`,
|
|
61
|
+
`translateX(${wUnit(g_workObj.playingX)}) translateY(${wUnit(g_headerObj.playingY)})`, g_transPriority.base);
|
|
62
|
+
} else {
|
|
63
|
+
stopRingKeepAlive();
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
// 判定系スプライトを作成(メインスプライトより上位)
|
|
55
67
|
const judgeSprite = createEmptySprite(divRoot, `judgeSprite`, mainCommonPos);
|
|
56
68
|
addTransform(`judgeSprite`, `main`,
|
|
@@ -1450,10 +1462,120 @@ const mainInit = () => {
|
|
|
1450
1462
|
}
|
|
1451
1463
|
};
|
|
1452
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* リングノート
|
|
1467
|
+
* - 状態は mainInit ローカルのため、リトライ時は自然に初期化される
|
|
1468
|
+
* - cnt === 0 がガイドと一致するフレーム。判定は cnt <= 0 の間のみ行い、
|
|
1469
|
+
* 最初にポインタがガイド内にあったフレームの遅れ (-cnt) で判定する
|
|
1470
|
+
*/
|
|
1471
|
+
const rings = [];
|
|
1472
|
+
let ringCnt = 0;
|
|
1473
|
+
|
|
1474
|
+
const makeRing = _data => {
|
|
1475
|
+
const no = ++ringCnt;
|
|
1476
|
+
const diameter = _data.radius * 2;
|
|
1477
|
+
const base = {
|
|
1478
|
+
x: _data.x - _data.radius, y: _data.y - _data.radius, w: diameter, h: diameter,
|
|
1479
|
+
borderRadius: `50%`, boxSizing: `border-box`,
|
|
1480
|
+
};
|
|
1481
|
+
const guide = createEmptySprite(ringSprite, `ringGuide${no}`, {
|
|
1482
|
+
...base, border: `solid ${g_ringObj.lineWidth}px ${g_ringObj.guideColor}`,
|
|
1483
|
+
visibility: g_stateObj.ringGuide === `Hidden` ? `hidden` : `visible`,
|
|
1484
|
+
});
|
|
1485
|
+
const body = createEmptySprite(ringSprite, `ringBody${no}`, {
|
|
1486
|
+
...base, border: `solid ${g_ringObj.lineWidth}px ${_data.color}`,
|
|
1487
|
+
});
|
|
1488
|
+
rings.push({ ..._data, cnt: _data.arrivalFrame + 1, guide, body, guideVisible: true });
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1491
|
+
const ringDone = new Set(); // 判定済み(削除済み)リングの通し番号
|
|
1492
|
+
let ringNextSeq = 0; // 未判定のうち最小の通し番号
|
|
1493
|
+
|
|
1494
|
+
const removeRing = _i => {
|
|
1495
|
+
ringDone.add(rings[_i].seq);
|
|
1496
|
+
rings[_i].guide.remove();
|
|
1497
|
+
rings[_i].body.remove();
|
|
1498
|
+
rings.splice(_i, 1);
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
const updateRings = () => {
|
|
1502
|
+
for (let i = rings.length - 1; i >= 0; i--) {
|
|
1503
|
+
const ring = rings[i];
|
|
1504
|
+
ring.cnt--;
|
|
1505
|
+
|
|
1506
|
+
// リング本体の大きさ (cnt === 0 でガイドと一致)
|
|
1507
|
+
const t = Math.min(Math.max(1 - ring.cnt / ring.total, 0), 1);
|
|
1508
|
+
ring.body.style.transform = `scale(${g_ringObj.startRate + (1 - g_ringObj.startRate) * t})`;
|
|
1509
|
+
|
|
1510
|
+
// ガイド到達前は判定しない
|
|
1511
|
+
if (ring.cnt > 0) {
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// 判定範囲外は、ポインタ位置に関係なくミスにする
|
|
1516
|
+
if (ring.cnt < -g_judgObj.arrowJ[g_judgPosObj.shobon]) {
|
|
1517
|
+
judgeUwan(ring.cnt, -1);
|
|
1518
|
+
removeRing(i);
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const autoPlay = g_stateObj.autoAll === C_FLG_ON;
|
|
1523
|
+
const hit = autoPlay ? ring.cnt === 0 : isPointerInside(ring.guide);
|
|
1524
|
+
|
|
1525
|
+
if (hit) {
|
|
1526
|
+
// 判定の選択には正の遅れ、フックには符号付きのカウントを渡す
|
|
1527
|
+
checkJudgment(-ring.cnt)[0](ring.cnt, -1);
|
|
1528
|
+
playRingSound(ring.sound);
|
|
1529
|
+
removeRing(i);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// ガイドを「次に判定するリング」だけに絞る
|
|
1534
|
+
// - 次 = 未判定(未出現を含む)のうち到達が最も早いリング。出現順ではなく譜面上の到達順で決める
|
|
1535
|
+
if (g_stateObj.ringGuide === `Next`) {
|
|
1536
|
+
while (ringDone.has(ringNextSeq)) {
|
|
1537
|
+
ringNextSeq++;
|
|
1538
|
+
}
|
|
1539
|
+
const nextArrival = g_workObj.ringArrivals[ringNextSeq];
|
|
1540
|
+
rings.forEach(ring => {
|
|
1541
|
+
const visible = ring.cnt <= 0 || ring.arrival === nextArrival;
|
|
1542
|
+
if (ring.guideVisible !== visible) {
|
|
1543
|
+
ring.guide.style.visibility = visible ? `visible` : `hidden`;
|
|
1544
|
+
ring.guideVisible = visible;
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* ライフ制&ライフ0の場合の途中終了
|
|
1552
|
+
*/
|
|
1553
|
+
const isLifeZero = () => g_workObj.lifeVal === 0 && g_workObj.lifeBorder === 0;
|
|
1554
|
+
const finishByLifeZero = () => {
|
|
1555
|
+
g_audio.pause();
|
|
1556
|
+
g_timerHandler.clearTimeout(g_timeoutEvtId);
|
|
1557
|
+
g_gameOverFlg = true;
|
|
1558
|
+
g_finishFlg = false;
|
|
1559
|
+
resultInit();
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1453
1562
|
/**
|
|
1454
1563
|
* フレーム処理(譜面台)
|
|
1455
1564
|
*/
|
|
1456
1565
|
const flowTimeline = () => {
|
|
1566
|
+
const terminalFrame = g_scoreObj.frameNum >= fullFrame;
|
|
1567
|
+
if (!terminalFrame && g_audio instanceof AudioPlayer && g_audioClockSync &&
|
|
1568
|
+
musicStartCtxTime !== undefined && g_audio.contextState !== `running`) {
|
|
1569
|
+
// 停止中でも、ライフ0の途中終了は待たずに行う
|
|
1570
|
+
// (最終フレームは上の terminalFrame で本体へ通し、そちらの判定を優先する)
|
|
1571
|
+
if (isLifeZero()) {
|
|
1572
|
+
finishByLifeZero();
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
getSharedAudioContext(); // suspended時はresumeを試行
|
|
1576
|
+
g_timeoutEvtId = g_timerHandler.setTimeout(flowTimeline, g_maxFrameWait);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1457
1579
|
|
|
1458
1580
|
const currentFrame = g_scoreObj.frameNum;
|
|
1459
1581
|
lblframe.textContent = g_scoreObj.baseFrame;
|
|
@@ -1598,6 +1720,10 @@ const mainInit = () => {
|
|
|
1598
1720
|
}
|
|
1599
1721
|
}
|
|
1600
1722
|
|
|
1723
|
+
// リングノート生成・更新
|
|
1724
|
+
g_workObj.mkRing?.[currentFrame]?.forEach(data => makeRing(data));
|
|
1725
|
+
updateRings();
|
|
1726
|
+
|
|
1601
1727
|
// 歌詞表示
|
|
1602
1728
|
g_scoreObj.wordData[currentFrame]?.forEach(wordData => updateWord(wordData, currentFrame));
|
|
1603
1729
|
|
|
@@ -1629,14 +1755,10 @@ const mainInit = () => {
|
|
|
1629
1755
|
g_workObj.mainEndTime = thisTime;
|
|
1630
1756
|
resultInit();
|
|
1631
1757
|
|
|
1632
|
-
} else if (
|
|
1758
|
+
} else if (isLifeZero()) {
|
|
1633
1759
|
|
|
1634
1760
|
// ライフ制&ライフ0の場合は途中終了
|
|
1635
|
-
|
|
1636
|
-
g_timerHandler.clearTimeout(g_timeoutEvtId);
|
|
1637
|
-
g_gameOverFlg = true;
|
|
1638
|
-
g_finishFlg = false;
|
|
1639
|
-
resultInit();
|
|
1761
|
+
finishByLifeZero();
|
|
1640
1762
|
|
|
1641
1763
|
} else {
|
|
1642
1764
|
|
|
@@ -1653,27 +1775,20 @@ const mainInit = () => {
|
|
|
1653
1775
|
// 再生開始時のオフセットずれやsuspend/resumeによるずれを吸収できない
|
|
1654
1776
|
thisTime = performance.now();
|
|
1655
1777
|
buffTime = 0;
|
|
1656
|
-
let holdFrame = false;
|
|
1657
1778
|
|
|
1658
1779
|
if (g_audio instanceof AudioPlayer && g_audioClockSync && musicStartCtxTime !== undefined) {
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
- (currentFrame - musicStartFrame) * 1000 / g_fps;
|
|
1662
|
-
} else {
|
|
1663
|
-
// AudioContext停止中は音源も止まっているため、フレーム進行も止めて復帰を待つ
|
|
1664
|
-
getSharedAudioContext(); // suspended時はresumeを試行
|
|
1665
|
-
holdFrame = true;
|
|
1666
|
-
}
|
|
1780
|
+
buffTime = (g_audio.contextTime - musicStartCtxTime) * 1000
|
|
1781
|
+
- (currentFrame - musicStartFrame) * 1000 / g_fps;
|
|
1667
1782
|
} else if (g_audio instanceof AudioPlayer || currentFrame >= musicStartFrame) {
|
|
1668
|
-
buffTime =
|
|
1783
|
+
buffTime = thisTime - musicStartTime - (currentFrame - musicStartFrame) * 1000 / g_fps;
|
|
1669
1784
|
}
|
|
1670
1785
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1786
|
+
g_scoreObj.frameNum++;
|
|
1787
|
+
g_scoreObj.baseFrame++;
|
|
1788
|
+
g_timeoutEvtId = g_timerHandler.setTimeout(
|
|
1789
|
+
flowTimeline,
|
|
1790
|
+
Math.min(Math.max(1000 / g_fps - buffTime, 0), g_maxFrameWait)
|
|
1791
|
+
);
|
|
1677
1792
|
}
|
|
1678
1793
|
};
|
|
1679
1794
|
|
|
@@ -1855,6 +1970,242 @@ const mainInit = () => {
|
|
|
1855
1970
|
}
|
|
1856
1971
|
};
|
|
1857
1972
|
|
|
1973
|
+
/** マウス/タッチ位置 (リングノートの判定用) */
|
|
1974
|
+
const g_pointer = { x: 0, y: 0, valid: false, type: null, id: null };
|
|
1975
|
+
|
|
1976
|
+
// 接触中のタッチ位置
|
|
1977
|
+
const g_touchPointers = new Map();
|
|
1978
|
+
let g_ringTouchCleanup = null;
|
|
1979
|
+
|
|
1980
|
+
/** デバイス待機防止用の維持音 */
|
|
1981
|
+
let g_ringKeepAlive = null;
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* リングノートの効果音が欠けないよう、聞こえないレベルのノイズを鳴らし続ける
|
|
1985
|
+
* - 完全な無音だとデバイスが省電力に入り、その直後の短い効果音が欠けることがある
|
|
1986
|
+
* - 振幅1e-4は16bitで約3LSB(約-80dBFS)で、聞こえない
|
|
1987
|
+
*/
|
|
1988
|
+
const startRingKeepAlive = () => {
|
|
1989
|
+
stopRingKeepAlive();
|
|
1990
|
+
const ctx = getSharedAudioContext();
|
|
1991
|
+
const buffer = ctx.createBuffer(1, ctx.sampleRate, ctx.sampleRate);
|
|
1992
|
+
const data = buffer.getChannelData(0);
|
|
1993
|
+
for (let i = 0; i < data.length; i++) {
|
|
1994
|
+
data[i] = (Math.random() * 2 - 1) * 1e-4;
|
|
1995
|
+
}
|
|
1996
|
+
const source = ctx.createBufferSource();
|
|
1997
|
+
source.buffer = buffer;
|
|
1998
|
+
source.loop = true;
|
|
1999
|
+
source.connect(ctx.destination);
|
|
2000
|
+
source.start();
|
|
2001
|
+
g_ringKeepAlive = source;
|
|
2002
|
+
};
|
|
2003
|
+
|
|
2004
|
+
const stopRingKeepAlive = () => {
|
|
2005
|
+
if (g_ringKeepAlive) {
|
|
2006
|
+
try {
|
|
2007
|
+
g_ringKeepAlive.stop();
|
|
2008
|
+
} catch (e) { /* 既に停止済み */ }
|
|
2009
|
+
g_ringKeepAlive.disconnect();
|
|
2010
|
+
g_ringKeepAlive = null;
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2013
|
+
|
|
2014
|
+
// ポインタ状態をクリア
|
|
2015
|
+
const resetRingPointers = () => {
|
|
2016
|
+
g_pointer.valid = false;
|
|
2017
|
+
g_touchPointers.clear();
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
// プレイ開始前に一度だけ登録する。
|
|
2021
|
+
// document のリスナーは clearWindow では解除されない。
|
|
2022
|
+
const bindRingMouse = () => {
|
|
2023
|
+
const update = evt => {
|
|
2024
|
+
if (evt.pointerType !== `mouse`) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
g_pointer.x = evt.clientX;
|
|
2028
|
+
g_pointer.y = evt.clientY;
|
|
2029
|
+
g_pointer.type = `mouse`;
|
|
2030
|
+
g_pointer.id = evt.pointerId;
|
|
2031
|
+
g_pointer.valid = true;
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
g_handler.addListener(document, `pointermove`, update, true);
|
|
2035
|
+
g_handler.addListener(document, `pointerdown`, update, true);
|
|
2036
|
+
g_handler.addListener(document, `pointerover`, evt => {
|
|
2037
|
+
// 文書外から入った場合だけ更新する。
|
|
2038
|
+
if (evt.relatedTarget === null) {
|
|
2039
|
+
update(evt);
|
|
2040
|
+
}
|
|
2041
|
+
}, true);
|
|
2042
|
+
g_handler.addListener(document, `pointerout`, evt => {
|
|
2043
|
+
if (evt.pointerType === `mouse` && evt.relatedTarget === null &&
|
|
2044
|
+
g_pointer.type === `mouse`) {
|
|
2045
|
+
g_pointer.valid = false;
|
|
2046
|
+
}
|
|
2047
|
+
}, true);
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
// タッチを document で追跡し、子要素のクリック先を変更しない。
|
|
2051
|
+
const bindRingTouchTracking = _root => {
|
|
2052
|
+
const ignoreSelector = [
|
|
2053
|
+
`.button_common`,
|
|
2054
|
+
`button`, `input`, `select`, `textarea`, `a[href]`,
|
|
2055
|
+
`[role="button"]`, `[data-ring-ignore]`,
|
|
2056
|
+
].join(`,`);
|
|
2057
|
+
|
|
2058
|
+
const onDown = evt => {
|
|
2059
|
+
if (evt.pointerType !== `touch` ||
|
|
2060
|
+
!(evt.target instanceof Element) || !_root.contains(evt.target)) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// 除外したコントロールでも、古いマウス・ペン位置は無効にする。
|
|
2065
|
+
g_pointer.valid = false;
|
|
2066
|
+
if (evt.target.closest(ignoreSelector)) {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
g_touchPointers.set(evt.pointerId, {
|
|
2071
|
+
x: evt.clientX,
|
|
2072
|
+
y: evt.clientY,
|
|
2073
|
+
startTarget: evt.target,
|
|
2074
|
+
});
|
|
2075
|
+
};
|
|
2076
|
+
|
|
2077
|
+
const onMove = evt => {
|
|
2078
|
+
const pointer = g_touchPointers.get(evt.pointerId);
|
|
2079
|
+
if (!pointer) return;
|
|
2080
|
+
if (!_root.contains(pointer.startTarget)) {
|
|
2081
|
+
g_touchPointers.delete(evt.pointerId);
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
pointer.x = evt.clientX;
|
|
2085
|
+
pointer.y = evt.clientY;
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
const onEnd = evt => {
|
|
2089
|
+
g_touchPointers.delete(evt.pointerId);
|
|
2090
|
+
};
|
|
2091
|
+
|
|
2092
|
+
const listenerIds = [
|
|
2093
|
+
g_handler.addListener(document, `pointerdown`, onDown, true),
|
|
2094
|
+
g_handler.addListener(document, `pointermove`, onMove, true),
|
|
2095
|
+
...[`pointerup`, `pointercancel`, `lostpointercapture`].map(type =>
|
|
2096
|
+
g_handler.addListener(document, type, onEnd, true)),
|
|
2097
|
+
];
|
|
2098
|
+
|
|
2099
|
+
return () => {
|
|
2100
|
+
listenerIds.forEach(id => g_handler.removeListener(id));
|
|
2101
|
+
g_touchPointers.clear();
|
|
2102
|
+
};
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
// mainInit のたびに登録する。
|
|
2106
|
+
// clearWindow は _root のリスナーを解除する。
|
|
2107
|
+
const bindRingPointer = _root => {
|
|
2108
|
+
g_ringTouchCleanup?.();
|
|
2109
|
+
g_touchPointers.clear();
|
|
2110
|
+
|
|
2111
|
+
// マウスの位置だけをリトライ後も保持する。
|
|
2112
|
+
if (g_pointer.type !== `mouse`) {
|
|
2113
|
+
g_pointer.valid = false;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
g_ringTouchCleanup = bindRingTouchTracking(_root);
|
|
2117
|
+
|
|
2118
|
+
const update = evt => {
|
|
2119
|
+
// マウスとタッチは document 側で追跡する。
|
|
2120
|
+
if (evt.pointerType === `mouse` || evt.pointerType === `touch`) {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
g_pointer.x = evt.clientX;
|
|
2124
|
+
g_pointer.y = evt.clientY;
|
|
2125
|
+
g_pointer.type = evt.pointerType;
|
|
2126
|
+
g_pointer.id = evt.pointerId;
|
|
2127
|
+
g_pointer.valid = true;
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
const release = evt => {
|
|
2131
|
+
if (evt.pointerType === `touch`) {
|
|
2132
|
+
g_touchPointers.delete(evt.pointerId);
|
|
2133
|
+
} else if (evt.type === `pointercancel` &&
|
|
2134
|
+
g_pointer.type === evt.pointerType && g_pointer.id === evt.pointerId) {
|
|
2135
|
+
g_pointer.valid = false;
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
const leave = evt => {
|
|
2140
|
+
if (evt.pointerType !== `touch` && evt.type === `pointercancel` &&
|
|
2141
|
+
g_pointer.type === evt.pointerType && g_pointer.id === evt.pointerId) {
|
|
2142
|
+
g_pointer.valid = false;
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
|
|
2146
|
+
g_handler.addListener(_root, `pointermove`, update);
|
|
2147
|
+
g_handler.addListener(_root, `pointerdown`, update);
|
|
2148
|
+
g_handler.addListener(_root, `pointerup`, release);
|
|
2149
|
+
g_handler.addListener(_root, `pointercancel`, release);
|
|
2150
|
+
g_handler.addListener(_root, `pointerleave`, leave);
|
|
2151
|
+
};
|
|
2152
|
+
|
|
2153
|
+
/**
|
|
2154
|
+
* リングノート用効果音の再生
|
|
2155
|
+
* @param {number} _no 音番号 (0: 無音)
|
|
2156
|
+
*/
|
|
2157
|
+
const playRingSound = _no => {
|
|
2158
|
+
const buffer = g_ringSoundBuffers.get(_no);
|
|
2159
|
+
if (_no <= 0 || buffer === undefined) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const ctx = getSharedAudioContext();
|
|
2163
|
+
const source = ctx.createBufferSource();
|
|
2164
|
+
const gain = ctx.createGain();
|
|
2165
|
+
source.buffer = buffer;
|
|
2166
|
+
gain.gain.value = g_stateObj.volume / 100;
|
|
2167
|
+
source.connect(gain);
|
|
2168
|
+
gain.connect(ctx.destination);
|
|
2169
|
+
source.onended = () => {
|
|
2170
|
+
source.disconnect();
|
|
2171
|
+
gain.disconnect();
|
|
2172
|
+
};
|
|
2173
|
+
source.start();
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
/**
|
|
2177
|
+
* 要素(ガイド円)の内側にポインタがあるか
|
|
2178
|
+
* - 画面の拡縮・transform後の実座標で判定するため getBoundingClientRect を使用
|
|
2179
|
+
*/
|
|
2180
|
+
const isPointerInside = _el => {
|
|
2181
|
+
const rect = _el.getBoundingClientRect();
|
|
2182
|
+
const radius = rect.width / 2;
|
|
2183
|
+
const centerX = rect.left + radius;
|
|
2184
|
+
const centerY = rect.top + rect.height / 2;
|
|
2185
|
+
|
|
2186
|
+
const inside = pointer => {
|
|
2187
|
+
const dx = pointer.x - centerX;
|
|
2188
|
+
const dy = pointer.y - centerY;
|
|
2189
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
if (g_pointer.valid && inside(g_pointer)) {
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
const root = document.getElementById(`divRoot`);
|
|
2196
|
+
for (const [id, pointer] of g_touchPointers) {
|
|
2197
|
+
// 開始要素が削除されたタッチは、古い座標で判定しない。
|
|
2198
|
+
if (!root?.contains(pointer.startTarget)) {
|
|
2199
|
+
g_touchPointers.delete(id);
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
if (inside(pointer)) {
|
|
2203
|
+
return true;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return false;
|
|
2207
|
+
};
|
|
2208
|
+
|
|
1858
2209
|
/**
|
|
1859
2210
|
* 矢印・フリーズアローの初期位置
|
|
1860
2211
|
* - true_true: スクロールなし/初期位置マニュアル
|
|
@@ -2609,6 +2960,11 @@ const changeLifeColor = (_state = ``) => {
|
|
|
2609
2960
|
lifeBar.style.height = wUnit(playableHeight * intLifeVal / g_headerObj.maxLifeVal);
|
|
2610
2961
|
};
|
|
2611
2962
|
|
|
2963
|
+
/**
|
|
2964
|
+
* 判定カスタムフック名 (リングノートは _j = -1 で専用フックへ)
|
|
2965
|
+
*/
|
|
2966
|
+
const judgHookName = (_name, _j) => _j < 0 ? `judg_ring_${_name}` : `judg_${_name}`;
|
|
2967
|
+
|
|
2612
2968
|
/**
|
|
2613
2969
|
* ゲージ回復処理
|
|
2614
2970
|
*/
|
|
@@ -2693,7 +3049,7 @@ const judgeRecovery = (_name, _difFrame, _j) => {
|
|
|
2693
3049
|
if (_name === `shakin`) {
|
|
2694
3050
|
quickRetry(`Shakin(Great)`);
|
|
2695
3051
|
}
|
|
2696
|
-
safeExecuteCustomHooks(`g_customJsObj
|
|
3052
|
+
safeExecuteCustomHooks(`g_customJsObj.${judgHookName(_name, _j)}`, g_customJsObj[judgHookName(_name, _j)], _difFrame, _j);
|
|
2697
3053
|
};
|
|
2698
3054
|
|
|
2699
3055
|
/**
|
|
@@ -2708,7 +3064,7 @@ const judgeDamage = (_name, _difFrame, _j) => {
|
|
|
2708
3064
|
comboJ.textContent = ``;
|
|
2709
3065
|
diffJ.textContent = ``;
|
|
2710
3066
|
lifeDamage();
|
|
2711
|
-
safeExecuteCustomHooks(`g_customJsObj
|
|
3067
|
+
safeExecuteCustomHooks(`g_customJsObj.${judgHookName(_name, _j)}`, g_customJsObj[judgHookName(_name, _j)], _difFrame, _j);
|
|
2712
3068
|
};
|
|
2713
3069
|
|
|
2714
3070
|
/**
|
|
@@ -2736,7 +3092,7 @@ const judgeMatari = (_difFrame, _j) => {
|
|
|
2736
3092
|
finishViewing();
|
|
2737
3093
|
quickRetry(`Matari(Good)`);
|
|
2738
3094
|
|
|
2739
|
-
safeExecuteCustomHooks(`g_customJsObj
|
|
3095
|
+
safeExecuteCustomHooks(`g_customJsObj.${judgHookName(`matari`, _j)}`, g_customJsObj[judgHookName(`matari`, _j)], _difFrame, _j);
|
|
2740
3096
|
};
|
|
2741
3097
|
|
|
2742
3098
|
/**
|
package/js/lib/result.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2026/09/13
|
|
8
|
-
* Revised : 2026/
|
|
8
|
+
* Revised : 2026/11/08 (v52.2.0)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -867,6 +867,8 @@ const getSelectedSettingList = (_orgShuffleFlg) => {
|
|
|
867
867
|
withOptions(g_stateObj.swapping, C_FLG_OFF,
|
|
868
868
|
`Swap:${getStgDetailName(g_stateObj.swapping)}${!_orgShuffleFlg && !g_stateObj.swapping.endsWith(`+`) ? getStgDetailName(`(S)`) : ``}`),
|
|
869
869
|
withOptions(g_stateObj.judgRange, `Normal`, `Judg:${getStgDetailName(g_stateObj.judgRange)}`),
|
|
870
|
+
hasRingNotes() ? withOptions(g_stateObj.ringSpeed, 1, `RingSpd:${getStgDetailName(g_stateObj.ringSpeed)}`) : ``,
|
|
871
|
+
hasRingNotes() ? withOptions(g_stateObj.ringGuide, `Next`, `Guide:${getStgDetailName(g_stateObj.ringGuide)}`) : ``,
|
|
870
872
|
].filter(value => value !== ``).join(`, `);
|
|
871
873
|
|
|
872
874
|
// Display設定の組み立て処理 (Ex: Step : FlatBar, Judge, Life : OFF)
|