danoniplus 52.1.0 → 52.2.1
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 +331 -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/settings.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2026/09/13
|
|
8
|
-
* Revised : 2026/11/
|
|
8
|
+
* Revised : 2026/11/08 (v52.2.1)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -625,6 +625,7 @@ const drawDensityGraph = _scoreId => {
|
|
|
625
625
|
updateScoreDetailLabel(`Density`, `Arrow`, obj.arrowCnts, 3, g_lblNameObj.s_arrow);
|
|
626
626
|
updateScoreDetailLabel(`Density`, `Frz`, obj.frzCnts, 4,
|
|
627
627
|
`${g_lblNameObj.s_frz}${g_headerObj.frzStartjdgUse ? ' <span class="common_bold">(2x)</span>' : ''}`);
|
|
628
|
+
updateScoreDetailLabel(`Density`, `Ring`, g_detailObj.ringCnt[_scoreId], 5, g_lblNameObj.s_ring);
|
|
628
629
|
};
|
|
629
630
|
|
|
630
631
|
/**
|
|
@@ -734,11 +735,13 @@ const makeDifInfoLabels = _scoreId => {
|
|
|
734
735
|
// 縦連
|
|
735
736
|
`${g_detailObj.toolDif[j].tate}\t` +
|
|
736
737
|
// 総矢印数
|
|
737
|
-
`${(obj.arrowCnts + obj.frzCnts)}\t` +
|
|
738
|
+
`${(obj.arrowCnts + obj.frzCnts + g_detailObj.ringCnt[j])}\t` +
|
|
738
739
|
// 矢印
|
|
739
740
|
`${obj.arrowCnts}\t` +
|
|
740
741
|
// フリーズアロー
|
|
741
742
|
`${obj.frzCnts}\t` +
|
|
743
|
+
// リングノート
|
|
744
|
+
`${g_detailObj.ringCnt[j]}\t` +
|
|
742
745
|
// APM
|
|
743
746
|
`${obj.apm}\t` +
|
|
744
747
|
// 時間(分秒)
|
|
@@ -787,9 +790,10 @@ const makeDifInfo = _scoreId => {
|
|
|
787
790
|
lblArrowInfo2.innerHTML = g_lblNameObj.s_linecnts.split(`{0}`)
|
|
788
791
|
.join(`${makeDedupliArray(g_detailObj.toolDif[_scoreId].push3).length} /cnt:${g_detailObj.toolDif[_scoreId].push3cnt}`);
|
|
789
792
|
dataArrowInfo.innerHTML =
|
|
790
|
-
`${arrowCnts + frzCnts * (g_headerObj.frzStartjdgUse ? 2 : 1)} ` +
|
|
793
|
+
`${arrowCnts + frzCnts * (g_headerObj.frzStartjdgUse ? 2 : 1) + g_detailObj.ringCnt[_scoreId]} ` +
|
|
791
794
|
`<span style="font-size:${wUnit(g_limitObj.difSelectorSiz)};">` +
|
|
792
|
-
`(${arrowCnts} + ${frzCnts}${g_headerObj.frzStartjdgUse ? ' <span class="common_bold">x 2</span>' : ''}
|
|
795
|
+
`(${arrowCnts} + ${frzCnts}${g_headerObj.frzStartjdgUse ? ' <span class="common_bold">x 2</span>' : ''}` +
|
|
796
|
+
`${hasRingNotes() ? ` + ${g_detailObj.ringCnt[_scoreId]}` : ''})` +
|
|
793
797
|
`</span>`;
|
|
794
798
|
|
|
795
799
|
const makeArrowCntsView = (_cntlist) => {
|
|
@@ -917,25 +921,25 @@ const makeHighScore = _scoreId => {
|
|
|
917
921
|
color: g_localStorage.highscores?.[scoreName]?.rankColor ?? `#666666`,
|
|
918
922
|
fontFamily: getBasicFont(`"Bookman Old Style"`),
|
|
919
923
|
}),
|
|
920
|
-
createScoreLabel(`
|
|
921
|
-
createScoreLabel(`
|
|
924
|
+
createScoreLabel(`dateTime`, g_localStorage.highscores?.[scoreName]?.dateTime ?? `----/--/-- --:--`, { yPos: 12 }),
|
|
925
|
+
createScoreLabel(`marks`,
|
|
922
926
|
`${g_localStorage.highscores?.[scoreName]?.fullCombo ?? '' ? '<span class="result_FullCombo">◆</span>' : ''}` +
|
|
923
927
|
`${g_localStorage.highscores?.[scoreName]?.perfect ?? '' ? '<span class="result_Perfect">◆</span>' : ''}` +
|
|
924
928
|
`${g_localStorage.highscores?.[scoreName]?.allPerfect ?? '' ? '<span class="result_AllPerfect">◆</span>' : ''}`,
|
|
925
929
|
{ xPos: 1, dx: 20, yPos: 12, w: 100, align: C_ALIGN_CENTER }),
|
|
926
|
-
createScoreLabel(`
|
|
930
|
+
createScoreLabel(`clearLamps`,
|
|
927
931
|
`Cleared: ` + (g_localStorage.highscores?.[scoreName]?.clearLamps?.join(', ') ?? C_FLG_HYPHEN),
|
|
928
932
|
{ yPos: 13, overflow: C_DIS_AUTO, w: g_sWidth / 2 + 40, h: 37 }),
|
|
929
933
|
|
|
930
|
-
createScoreLabel(`
|
|
934
|
+
createScoreLabel(`shuffleS`,
|
|
931
935
|
g_stateObj.shuffle.indexOf(`Mirror`) < 0
|
|
932
936
|
? `` : `Shuffle: <span class="common_shuffle">${g_stateObj.shuffle}</span>`,
|
|
933
937
|
{ yPos: 11.5, dx: -130 }),
|
|
934
|
-
createScoreLabel(`
|
|
938
|
+
createScoreLabel(`assistS`,
|
|
935
939
|
g_autoPlaysBase.includes(g_stateObj.autoPlay)
|
|
936
940
|
? `` : `Assist: <span class="common_assist">${g_stateObj.autoPlay}</span>`,
|
|
937
941
|
{ yPos: 12.5, dx: -130 }),
|
|
938
|
-
createScoreLabel(`
|
|
942
|
+
createScoreLabel(`anotherS`,
|
|
939
943
|
!hasVal(g_keyObj[`transKey${keyCtrlPtn}`])
|
|
940
944
|
? `` : `A.Keymode: <span class="common_another">${g_keyObj[`transKey${keyCtrlPtn}`]}</span>`,
|
|
941
945
|
{ yPos: 13.5, dx: -130 }),
|
|
@@ -1031,9 +1035,11 @@ const drawMinimap = (_scoreId, { _initFlg = false, _fadeinFlg = false } = {}) =>
|
|
|
1031
1035
|
|
|
1032
1036
|
const params = g_detailObj.miniMapParams[_scoreId];
|
|
1033
1037
|
const kPtn = params._keyCtrlPtn;
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1038
|
+
// リング帯の有無で幅が変わるため、キー種 + リング帯有無でキャッシュする
|
|
1039
|
+
const headerKey = params.config.ringBandWidth > 0 ? `${kPtn}_ring` : kPtn;
|
|
1040
|
+
if (!g_detailObj.scoreMinimapHeader[headerKey]) {
|
|
1041
|
+
// ヘッダーはキー種(+リング帯有無)ごとに共通なので、未作成の場合のみ生成してキャッシュ
|
|
1042
|
+
g_detailObj.scoreMinimapHeader[headerKey] = createMinimapHeader(params.config, kPtn, params._keyNum);
|
|
1037
1043
|
}
|
|
1038
1044
|
if (!savedCanvases) {
|
|
1039
1045
|
// 未作成の場合のみミニマップを生成(Lazy Generation)
|
|
@@ -1050,7 +1056,7 @@ const drawMinimap = (_scoreId, { _initFlg = false, _fadeinFlg = false } = {}) =>
|
|
|
1050
1056
|
// --- ヘッダー部分 ---
|
|
1051
1057
|
const detailMiniMapHeader = createEmptySprite(detailMiniMap, `detailMiniMapHeader`, g_windowObj.detailMiniMapHeader);
|
|
1052
1058
|
$id(`detailMiniMapHeader`).top = wUnit(g_stateObj.miniMapRevFlg ? 230 + g_sHeight - 500 : 0);
|
|
1053
|
-
detailMiniMapHeader.appendChild(g_detailObj.scoreMinimapHeader[
|
|
1059
|
+
detailMiniMapHeader.appendChild(g_detailObj.scoreMinimapHeader[headerKey]);
|
|
1054
1060
|
|
|
1055
1061
|
// --- メイン(譜面)部分 ---
|
|
1056
1062
|
const detailMiniMapSub = createEmptySprite(detailMiniMap, `detailMiniMapSub`, g_windowObj.detailMiniMapSub);
|
|
@@ -1128,7 +1134,7 @@ const drawMinimap = (_scoreId, { _initFlg = false, _fadeinFlg = false } = {}) =>
|
|
|
1128
1134
|
* @return {HTMLCanvasElement} ヘッダー用のキャンバス要素
|
|
1129
1135
|
*/
|
|
1130
1136
|
const createMinimapHeader = (_config, _keyCtrlPtn, _keyNum) => {
|
|
1131
|
-
const { timeMargin, laneWidth, logicalWidth } = _config;
|
|
1137
|
+
const { timeMargin, laneWidth, logicalWidth, ringBandWidth = 0 } = _config;
|
|
1132
1138
|
const headerHeight = 15; // ヘッダーの固定高
|
|
1133
1139
|
|
|
1134
1140
|
const canvas = createCanvas(null, { w: logicalWidth, h: headerHeight, position: null });
|
|
@@ -1143,6 +1149,12 @@ const createMinimapHeader = (_config, _keyCtrlPtn, _keyNum) => {
|
|
|
1143
1149
|
// 視覚的な中央調整で +2px
|
|
1144
1150
|
fillCanvasText(ctx, keyText, x, headerHeight / 2 + 2, { siz: 10, color: '#999', align: C_ALIGN_CENTER, baseline: `middle` });
|
|
1145
1151
|
}
|
|
1152
|
+
// リングノート帯の見出し
|
|
1153
|
+
if (ringBandWidth > 0) {
|
|
1154
|
+
const bandX = timeMargin + laneWidth * _keyNum + 4;
|
|
1155
|
+
fillCanvasText(ctx, `Ring`, bandX + (ringBandWidth - 4) / 2, headerHeight / 2 + 2,
|
|
1156
|
+
{ siz: 10, color: '#999', align: C_ALIGN_CENTER, baseline: `middle` });
|
|
1157
|
+
}
|
|
1146
1158
|
|
|
1147
1159
|
return canvas;
|
|
1148
1160
|
};
|
|
@@ -1166,7 +1178,7 @@ const createMinimapHeader = (_config, _keyCtrlPtn, _keyNum) => {
|
|
|
1166
1178
|
*/
|
|
1167
1179
|
const generateMinimapData = (_params, _isReverse) => {
|
|
1168
1180
|
const { _scoreObj, _keyNum, _playingFrame, _firstArrowFrame, _keyCtrlPtn, config } = _params;
|
|
1169
|
-
const { scale, timeMargin, laneWidth, logicalWidth, mmMarginY } = config;
|
|
1181
|
+
const { scale, timeMargin, laneWidth, logicalWidth, mmMarginY, ringBandWidth = 0 } = config;
|
|
1170
1182
|
|
|
1171
1183
|
const mmHeightTotal = _playingFrame * scale + mmMarginY * 2;
|
|
1172
1184
|
const canvases = createSplitCanvases(logicalWidth, mmHeightTotal);
|
|
@@ -1234,6 +1246,46 @@ const generateMinimapData = (_params, _isReverse) => {
|
|
|
1234
1246
|
});
|
|
1235
1247
|
}
|
|
1236
1248
|
|
|
1249
|
+
// 4. リングノート (レーンを持たないため、右側の専用帯に出現フレームのみ表示)
|
|
1250
|
+
// 座標は使わない。同一フレームに複数ある場合は帯内で横に散らして重なりを避ける
|
|
1251
|
+
if (ringBandWidth > 0 && _scoreObj.ringData !== undefined) {
|
|
1252
|
+
const bandX = timeMargin + laneWidth * _keyNum + 4;
|
|
1253
|
+
const bandW = ringBandWidth - 6;
|
|
1254
|
+
const cols = g_ringObj.cols;
|
|
1255
|
+
|
|
1256
|
+
// 帯の背景
|
|
1257
|
+
distributeDrawing(canvases, 0, mmHeightTotal, g_dpr, (ctx) => {
|
|
1258
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.05)';
|
|
1259
|
+
ctx.fillRect(bandX, 0, bandW, mmHeightTotal);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
// フレーム毎の出現数
|
|
1263
|
+
const frameCnts = new Map();
|
|
1264
|
+
for (let k = 0; k < _scoreObj.ringData.length; k += cols) {
|
|
1265
|
+
const frame = parseFloat(_scoreObj.ringData[k]);
|
|
1266
|
+
if (!isNaN(frame)) {
|
|
1267
|
+
frameCnts.set(frame, (frameCnts.get(frame) ?? 0) + 1);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const seen = new Map();
|
|
1272
|
+
for (let k = 0; k < _scoreObj.ringData.length; k += cols) {
|
|
1273
|
+
const frame = parseFloat(_scoreObj.ringData[k]);
|
|
1274
|
+
if (isNaN(frame)) continue;
|
|
1275
|
+
|
|
1276
|
+
const idx = seen.get(frame) ?? 0;
|
|
1277
|
+
seen.set(frame, idx + 1);
|
|
1278
|
+
const x = bandX + bandW * (idx + 1) / (frameCnts.get(frame) + 1);
|
|
1279
|
+
const y = getY(frame);
|
|
1280
|
+
distributeDrawing(canvases, y - 4, 8, g_dpr, (ctx) => {
|
|
1281
|
+
ctx.fillStyle = 'rgba(255, 170, 60, 0.8)';
|
|
1282
|
+
ctx.beginPath();
|
|
1283
|
+
ctx.arc(x, y, 3, 0, Math.PI * 2);
|
|
1284
|
+
ctx.fill();
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1237
1289
|
return canvases.map(item => item.canvas);
|
|
1238
1290
|
};
|
|
1239
1291
|
|
|
@@ -1828,7 +1880,7 @@ const createOptionWindow = _sprite => {
|
|
|
1828
1880
|
|
|
1829
1881
|
// ---------------------------------------------------
|
|
1830
1882
|
// タイミング調整 (Adjustment)
|
|
1831
|
-
// 縦位置: 10.
|
|
1883
|
+
// 縦位置: 10.6 短縮ショートカットあり
|
|
1832
1884
|
createGeneralSetting(spriteList.adjustment, `adjustment`, {
|
|
1833
1885
|
skipTerms: g_settings.adjustmentTerms, hiddenBtn: true, scLabel: g_lblNameObj.sc_adjustment, roundNum: 5,
|
|
1834
1886
|
unitName: g_lblNameObj.frame, addRFunc: () => viewAdjustment(),
|
|
@@ -1857,7 +1909,7 @@ const createOptionWindow = _sprite => {
|
|
|
1857
1909
|
|
|
1858
1910
|
// ---------------------------------------------------
|
|
1859
1911
|
// フェードイン (Fadein)
|
|
1860
|
-
// 縦位置: 11.
|
|
1912
|
+
// 縦位置: 11.6 スライダーあり
|
|
1861
1913
|
spriteList.fadein.appendChild(createLblSetting(`Fadein`));
|
|
1862
1914
|
|
|
1863
1915
|
const lnkFadein = createDivCss2Label(`lnkFadein`, `${g_stateObj.fadein}${g_lblNameObj.percent}`,
|
|
@@ -1893,7 +1945,7 @@ const createOptionWindow = _sprite => {
|
|
|
1893
1945
|
|
|
1894
1946
|
// ---------------------------------------------------
|
|
1895
1947
|
// ボリューム (Volume)
|
|
1896
|
-
// 縦位置: 12.
|
|
1948
|
+
// 縦位置: 12.6
|
|
1897
1949
|
createGeneralSetting(spriteList.volume, `volume`, { unitName: g_lblNameObj.percent });
|
|
1898
1950
|
|
|
1899
1951
|
// 譜面番号の再取得
|
|
@@ -2168,7 +2220,14 @@ const openHelpOverlay = (_title, _msg, _stateKey) => {
|
|
|
2168
2220
|
);
|
|
2169
2221
|
createScText(btnHelpClose, `HelpClose`, { displayName: `helpOverlay`, targetLabel: `btnHelpClose`, x: -5 });
|
|
2170
2222
|
if (previewH > 0) {
|
|
2171
|
-
|
|
2223
|
+
const def = g_helpPreviewDefs[_stateKey];
|
|
2224
|
+
let func;
|
|
2225
|
+
if (def?.ring) {
|
|
2226
|
+
func = makeRingHelpPreview;
|
|
2227
|
+
} else {
|
|
2228
|
+
func = makeHelpPreview;
|
|
2229
|
+
}
|
|
2230
|
+
func(overlay, _stateKey, g_sHeight - 30 - previewH, previewH);
|
|
2172
2231
|
}
|
|
2173
2232
|
// ユーザカスタムイベント(ヘルプ表示)
|
|
2174
2233
|
safeExecuteCustomHooks(`g_customJsObj.helpOverlay`, g_customJsObj.helpOverlay);
|
|
@@ -2218,6 +2277,7 @@ const isLongHelp = _msg =>
|
|
|
2218
2277
|
* (StepArea, Swappingは実際のキーには非対応)
|
|
2219
2278
|
* - frzUse : サンプルでフリーズアローを使用するかどうか
|
|
2220
2279
|
* - sub : 派生する設定 (Typeなど)。選択肢の列を2列目に追加する
|
|
2280
|
+
* - ring : リングのプレビューを出す
|
|
2221
2281
|
*/
|
|
2222
2282
|
const g_helpPreviewDefs = {
|
|
2223
2283
|
motion: {},
|
|
@@ -2229,6 +2289,8 @@ const g_helpPreviewDefs = {
|
|
|
2229
2289
|
frzReturn: { sub: `frzReturnType` },
|
|
2230
2290
|
effect: { frzUse: true, splitLayer: true },
|
|
2231
2291
|
swapping: { frzUse: true },
|
|
2292
|
+
ringSpeed: { ring: true },
|
|
2293
|
+
ringGuide: { ring: true },
|
|
2232
2294
|
};
|
|
2233
2295
|
|
|
2234
2296
|
/**
|
|
@@ -2937,6 +2999,227 @@ const makeHelpPreview = (_parent, _key, _top, _height) => {
|
|
|
2937
2999
|
timerId = g_timerHandler.setTimeout(tick, 1000 / g_fps);
|
|
2938
3000
|
};
|
|
2939
3001
|
|
|
3002
|
+
/**
|
|
3003
|
+
* リングノート設定 (RingSpeed / RingGuide) 用の詳細ヘルプ内プレビュー
|
|
3004
|
+
* - ゲーム側 makeRing/updateRings と同じ構成 (ガイド円 + 収束する本体円) を縮小表示する
|
|
3005
|
+
* - 判定はオートプレイ相当 (到達フレームで判定済み扱い)。ポインタ判定は再現しない
|
|
3006
|
+
* - RingSpeed: 現在の倍率での出現~到達フレーム数で収束させる
|
|
3007
|
+
* - RingGuide: ガイドの違いが分かるよう、複数のリングが同時に出る固定の速さで表示する
|
|
3008
|
+
* @param {HTMLDivElement} _parent オーバーレイ
|
|
3009
|
+
* @param {string} _key ringSpeed / ringGuide
|
|
3010
|
+
* @param {number} _top 配置Y座標
|
|
3011
|
+
* @param {number} _height 領域の高さ
|
|
3012
|
+
*/
|
|
3013
|
+
const makeRingHelpPreview = (_parent, _key, _top, _height) => {
|
|
3014
|
+
if (document.getElementById(`pvRingFrame`) !== null) {
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
const isSpeed = _key === `ringSpeed`;
|
|
3018
|
+
const optList = g_settings[`${_key}s`];
|
|
3019
|
+
const round2 = _val => Math.round(_val * 100) / 100;
|
|
3020
|
+
const sameVal = (_a, _b) => (isSpeed ? round2(_a) === round2(_b) : _a === _b);
|
|
3021
|
+
|
|
3022
|
+
// チップに出す選択肢 (RingSpeedは数が多いため 0.5x 刻みの代表値のみ)
|
|
3023
|
+
const chipList = isSpeed ? optList.filter(val => Math.round(val * 100) % 50 === 0) : optList;
|
|
3024
|
+
|
|
3025
|
+
const playW = g_headerObj.playingWidth || g_sWidth;
|
|
3026
|
+
const playH = g_headerObj.playingHeight || g_sHeight;
|
|
3027
|
+
const chipW = 100;
|
|
3028
|
+
const areaW = g_sWidth - 60;
|
|
3029
|
+
const rate = Math.min(g_limitObj.helpPreviewMaxRate, _height / playH, (areaW - chipW - 15) / playW);
|
|
3030
|
+
const viewW = Math.round(playW * rate);
|
|
3031
|
+
const baseX = Math.max(Math.round((areaW - viewW - chipW - 15) / 2), 0);
|
|
3032
|
+
|
|
3033
|
+
// 領域内のクリックでオーバーレイが閉じないようにする
|
|
3034
|
+
const area = createEmptySprite(_parent, `helpPreview`, {
|
|
3035
|
+
x: 30, y: _top, w: areaW, h: _height, pointerEvents: C_DIS_AUTO,
|
|
3036
|
+
});
|
|
3037
|
+
g_handler.addListener(area, `click`, evt => evt.stopPropagation());
|
|
3038
|
+
|
|
3039
|
+
// ---------------------------------------------------
|
|
3040
|
+
// ミニプレイ画面 (縮小表示)
|
|
3041
|
+
const scaler = createEmptySprite(area, `pvRingScale`, {
|
|
3042
|
+
x: baseX, y: 0, w: playW, h: playH, transform: `scale(${rate})`, transformOrigin: `top left`,
|
|
3043
|
+
});
|
|
3044
|
+
const pvFrame = createEmptySprite(scaler, `pvRingFrame`, {
|
|
3045
|
+
w: playW, h: playH, background: g_headerObj.baseBrightFlg ? `#eeeeee` : `#111111`,
|
|
3046
|
+
border: `1px solid #444444`, boxSizing: `border-box`, overflow: `hidden`,
|
|
3047
|
+
});
|
|
3048
|
+
const ringSprite = createEmptySprite(pvFrame, `pvRingSprite`, { w: playW, h: playH });
|
|
3049
|
+
|
|
3050
|
+
// ---------------------------------------------------
|
|
3051
|
+
// プレビュー状態
|
|
3052
|
+
const radius = g_ringObj.radius;
|
|
3053
|
+
const ringColor = g_headerObj.baseBrightFlg ? g_ringObj.colorLight : g_ringObj.color;
|
|
3054
|
+
const spots = [[0.3, 0.3], [0.65, 0.4], [0.45, 0.62], [0.75, 0.72], [0.25, 0.68], [0.55, 0.25]];
|
|
3055
|
+
const holdFrames = 6; // 到達後に残すフレーム数 (判定の瞬間を見せる)
|
|
3056
|
+
const guideDuration = 90, guideInterval = 28; // RingGuide用 (同時に3個前後出る)
|
|
3057
|
+
const speedInterval = 45; // RingSpeed用
|
|
3058
|
+
|
|
3059
|
+
let selected = isSpeed
|
|
3060
|
+
? (optList.find(val => sameVal(val, g_stateObj.ringSpeed)) ?? optList[0])
|
|
3061
|
+
: (optList.includes(g_stateObj.ringGuide) ? g_stateObj.ringGuide : optList[0]);
|
|
3062
|
+
let rings = [];
|
|
3063
|
+
let frameCnt = 0;
|
|
3064
|
+
let noteCnt = 0;
|
|
3065
|
+
let autoCnt = 0;
|
|
3066
|
+
let timerId = null;
|
|
3067
|
+
|
|
3068
|
+
// ゲーム側 getRingDuration (ringSpeedData なし) と同じ式
|
|
3069
|
+
const getTotal = () => isSpeed ? Math.max(Math.round(g_ringObj.baseFrame / selected), 1) : guideDuration;
|
|
3070
|
+
const getInterval = () => isSpeed ? speedInterval : guideInterval;
|
|
3071
|
+
const guideMode = () => isSpeed ? `ALL` : selected;
|
|
3072
|
+
|
|
3073
|
+
const clearRings = () => {
|
|
3074
|
+
rings.forEach(ring => {
|
|
3075
|
+
ring.guide.remove();
|
|
3076
|
+
ring.body.remove();
|
|
3077
|
+
});
|
|
3078
|
+
rings = [];
|
|
3079
|
+
};
|
|
3080
|
+
|
|
3081
|
+
const spawn = () => {
|
|
3082
|
+
const [rx, ry] = spots[noteCnt % spots.length];
|
|
3083
|
+
const no = ++noteCnt;
|
|
3084
|
+
const base = {
|
|
3085
|
+
x: Math.round(playW * rx) - radius, y: Math.round(playH * ry) - radius,
|
|
3086
|
+
w: radius * 2, h: radius * 2, borderRadius: `50%`, boxSizing: `border-box`,
|
|
3087
|
+
};
|
|
3088
|
+
const guide = createEmptySprite(ringSprite, `pvRingGuide${no}`, {
|
|
3089
|
+
...base, border: `solid ${g_ringObj.lineWidth}px ${g_ringObj.guideColor}`,
|
|
3090
|
+
visibility: guideMode() === `Hidden` ? `hidden` : `visible`,
|
|
3091
|
+
});
|
|
3092
|
+
const body = createEmptySprite(ringSprite, `pvRingBody${no}`, {
|
|
3093
|
+
...base, border: `solid ${g_ringObj.lineWidth}px ${ringColor}`,
|
|
3094
|
+
transform: `scale(${g_ringObj.startRate})`,
|
|
3095
|
+
});
|
|
3096
|
+
const total = getTotal();
|
|
3097
|
+
rings.push({ guide, body, total, cnt: total + 1, guideVisible: guideMode() !== `Hidden` });
|
|
3098
|
+
};
|
|
3099
|
+
|
|
3100
|
+
const updateRings = () => {
|
|
3101
|
+
rings = rings.filter(ring => {
|
|
3102
|
+
ring.cnt--;
|
|
3103
|
+
if (ring.cnt < -holdFrames) {
|
|
3104
|
+
ring.guide.remove();
|
|
3105
|
+
ring.body.remove();
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
// 本体の大きさ (cnt === 0 でガイドと一致)
|
|
3109
|
+
const t = Math.min(Math.max(1 - ring.cnt / ring.total, 0), 1);
|
|
3110
|
+
ring.body.style.transform = `scale(${g_ringObj.startRate + (1 - g_ringObj.startRate) * t})`;
|
|
3111
|
+
ring.body.style.opacity = ring.cnt > 0 ? 1 : Math.max((holdFrames + ring.cnt) / holdFrames, 0);
|
|
3112
|
+
return true;
|
|
3113
|
+
});
|
|
3114
|
+
|
|
3115
|
+
// ガイド表示 (ゲーム側 updateRings と同じ条件。Next: 到達済み or 次に判定するリングのみ)
|
|
3116
|
+
const mode = guideMode();
|
|
3117
|
+
const nextRing = rings.find(ring => ring.cnt > 0);
|
|
3118
|
+
rings.forEach(ring => {
|
|
3119
|
+
const visible = mode === `Hidden` ? false
|
|
3120
|
+
: mode === `Next` ? (ring.cnt <= 0 || ring === nextRing) : true;
|
|
3121
|
+
if (ring.guideVisible !== visible) {
|
|
3122
|
+
ring.guide.style.visibility = visible ? `visible` : `hidden`;
|
|
3123
|
+
ring.guideVisible = visible;
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
};
|
|
3127
|
+
|
|
3128
|
+
// ---------------------------------------------------
|
|
3129
|
+
// 選択肢ボタン
|
|
3130
|
+
const colMain = createEmptySprite(area, `pvColMain`, { x: baseX + viewW + 15, y: 0, w: chipW, h: _height });
|
|
3131
|
+
const chipH = Math.min(20, Math.floor(_height / (chipList.length + 2)));
|
|
3132
|
+
const getChipName = opt => isSpeed ? `${opt} ${g_lblNameObj.multi}` : getStgDetailName(opt);
|
|
3133
|
+
const chips = chipList.map((opt, i) =>
|
|
3134
|
+
createCss2Button(`pvRingChip${i}`, getChipName(opt), () => selectOption(opt), {
|
|
3135
|
+
x: 0, y: i * chipH, w: chipW, h: chipH - 2, siz: 12,
|
|
3136
|
+
}, g_cssObj.button_Default));
|
|
3137
|
+
multiAppend(colMain, ...chips);
|
|
3138
|
+
|
|
3139
|
+
// RingSpeed: 現在の倍率と出現~到達フレーム数
|
|
3140
|
+
const lblInfo = createDivCss2Label(`pvRingInfo`, ``, {
|
|
3141
|
+
x: 0, y: chipList.length * chipH + 4, w: chipW, h: 32, siz: 12, align: C_ALIGN_LEFT,
|
|
3142
|
+
});
|
|
3143
|
+
colMain.appendChild(lblInfo);
|
|
3144
|
+
|
|
3145
|
+
const markChips = () => {
|
|
3146
|
+
chips.forEach((chip, i) => {
|
|
3147
|
+
chip.classList.remove(g_cssObj.button_Default, g_cssObj.button_Setting);
|
|
3148
|
+
chip.classList.add(sameVal(chipList[i], selected) ? g_cssObj.button_Setting : g_cssObj.button_Default);
|
|
3149
|
+
});
|
|
3150
|
+
lblInfo.textContent = isSpeed ? `${round2(selected)} ${g_lblNameObj.multi} (${getTotal()}f)` : ``;
|
|
3151
|
+
g_helpSelectHook([selected], 0);
|
|
3152
|
+
safeExecuteCustomHooks(`g_customJsObj.helpPreview`, g_customJsObj.helpPreview, _key, selected,
|
|
3153
|
+
{ pvFrame, ringSprite, playW, playH });
|
|
3154
|
+
};
|
|
3155
|
+
|
|
3156
|
+
const applyOption = opt => {
|
|
3157
|
+
selected = opt;
|
|
3158
|
+
clearRings();
|
|
3159
|
+
frameCnt = 0;
|
|
3160
|
+
spawn();
|
|
3161
|
+
};
|
|
3162
|
+
const selectOption = opt => {
|
|
3163
|
+
autoCnt = -g_limitObj.helpPreviewAutoFrames;
|
|
3164
|
+
applyOption(opt);
|
|
3165
|
+
markChips();
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
// 上下キー: RingSpeedは内側ボタンと同じ5つ刻み (0.25x)、RingGuideは1つずつ
|
|
3169
|
+
const moveOption = _step => {
|
|
3170
|
+
const idx = Math.max(optList.findIndex(val => sameVal(val, selected)), 0);
|
|
3171
|
+
applyOption(optList[nextPos(idx, _step, optList.length)]);
|
|
3172
|
+
markChips();
|
|
3173
|
+
};
|
|
3174
|
+
// 自動切替: RingSpeedは代表値 (チップ) を順に巡回
|
|
3175
|
+
const autoOption = () => {
|
|
3176
|
+
if (!isSpeed) {
|
|
3177
|
+
moveOption(1);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
const next = chipList.find(val => round2(val) > round2(selected));
|
|
3181
|
+
applyOption(next ?? chipList[0]);
|
|
3182
|
+
markChips();
|
|
3183
|
+
};
|
|
3184
|
+
|
|
3185
|
+
const stop = () => {
|
|
3186
|
+
if (timerId !== null) {
|
|
3187
|
+
g_timerHandler.clearTimeout(timerId);
|
|
3188
|
+
}
|
|
3189
|
+
timerId = null;
|
|
3190
|
+
g_helpPreviewStop = () => true;
|
|
3191
|
+
g_helpPreviewMove = _dir => true;
|
|
3192
|
+
g_helpPreviewColumn = _dir => true;
|
|
3193
|
+
};
|
|
3194
|
+
g_helpPreviewStop = stop;
|
|
3195
|
+
g_helpPreviewMove = _dir => {
|
|
3196
|
+
autoCnt = -g_limitObj.helpPreviewAutoFrames;
|
|
3197
|
+
moveOption(_dir * (isSpeed ? g_settings.ringSpeedTerms[1] : 1));
|
|
3198
|
+
};
|
|
3199
|
+
|
|
3200
|
+
const tick = () => {
|
|
3201
|
+
if (document.getElementById(`pvRingFrame`) === null) {
|
|
3202
|
+
stop();
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
frameCnt++;
|
|
3206
|
+
if (g_limitObj.helpPreviewAutoFrames > 0 && ++autoCnt >= g_limitObj.helpPreviewAutoFrames) {
|
|
3207
|
+
autoCnt = 0;
|
|
3208
|
+
autoOption();
|
|
3209
|
+
}
|
|
3210
|
+
if (frameCnt % getInterval() === 0) {
|
|
3211
|
+
spawn();
|
|
3212
|
+
}
|
|
3213
|
+
updateRings();
|
|
3214
|
+
timerId = g_timerHandler.setTimeout(tick, 1000 / g_fps);
|
|
3215
|
+
};
|
|
3216
|
+
|
|
3217
|
+
// 初期表示: 現在の設定値
|
|
3218
|
+
applyOption(selected);
|
|
3219
|
+
markChips();
|
|
3220
|
+
timerId = g_timerHandler.setTimeout(tick, 1000 / g_fps);
|
|
3221
|
+
};
|
|
3222
|
+
|
|
2940
3223
|
/**
|
|
2941
3224
|
* 設定名の置き換え処理(未指定時は原則g_lblNameObjの値を使用、明示的な指定がある場合のみ元の名前を返却)
|
|
2942
3225
|
* @param {string} _name
|
|
@@ -3088,7 +3371,8 @@ const gaugeFormat = (_mode, _border, _rcv, _dmg, _init, _lifeValFlg) => {
|
|
|
3088
3371
|
let rcvText = round2(_rcv), dmgText = round2(_dmg);
|
|
3089
3372
|
let realRcv = _rcv, realDmg = _dmg;
|
|
3090
3373
|
const allCnt = sumData(g_detailObj.arrowCnt[g_stateObj.scoreId]) +
|
|
3091
|
-
(g_headerObj.frzStartjdgUse ? 2 : 1) * sumData(g_detailObj.frzCnt[g_stateObj.scoreId])
|
|
3374
|
+
(g_headerObj.frzStartjdgUse ? 2 : 1) * sumData(g_detailObj.frzCnt[g_stateObj.scoreId]) +
|
|
3375
|
+
g_detailObj.ringCnt[g_stateObj.scoreId];
|
|
3092
3376
|
|
|
3093
3377
|
// ゲージ設定が矢印数依存の場合、実際の値に変換して表示する
|
|
3094
3378
|
// 表示上、計算した値は小数第二位までの表示とする(それ以外はそのまま)
|
|
@@ -4068,8 +4352,30 @@ const createSettingsDisplayWindow = _sprite => {
|
|
|
4068
4352
|
unitName: g_lblNameObj.pixel,
|
|
4069
4353
|
addRFunc: () => updatePreviewHitPositionLine(g_stateObj.hitPosition),
|
|
4070
4354
|
});
|
|
4355
|
+
|
|
4356
|
+
if (hasRingNotes()) {
|
|
4357
|
+
// ---------------------------------------------------
|
|
4358
|
+
// リングノートの速度 (RingSpeed)
|
|
4359
|
+
// 縦位置: 9.8
|
|
4360
|
+
g_settings.ringSpeedNum = Math.max(g_settings.ringSpeeds.indexOf(g_stateObj.ringSpeed), 0);
|
|
4361
|
+
g_stateObj.ringSpeed = g_settings.ringSpeeds[g_settings.ringSpeedNum];
|
|
4362
|
+
createGeneralSetting(spriteList.ringSpeed, `ringSpeed`, { skipTerms: g_settings.ringSpeedTerms, unitName: ` ${g_lblNameObj.multi}` });
|
|
4363
|
+
|
|
4364
|
+
// ---------------------------------------------------
|
|
4365
|
+
// リングノートのガイド表示 (RingGuide)
|
|
4366
|
+
// 縦位置: 10.8
|
|
4367
|
+
g_settings.ringGuideNum = Math.max(g_settings.ringGuides.indexOf(g_stateObj.ringGuide), 0);
|
|
4368
|
+
g_stateObj.ringGuide = g_settings.ringGuides[g_settings.ringGuideNum];
|
|
4369
|
+
createGeneralSetting(spriteList.ringGuide, `ringGuide`);
|
|
4370
|
+
}
|
|
4071
4371
|
};
|
|
4072
4372
|
|
|
4373
|
+
/**
|
|
4374
|
+
* 選択中の譜面にリングノートが含まれるかをチェック
|
|
4375
|
+
* @returns {boolean}
|
|
4376
|
+
*/
|
|
4377
|
+
const hasRingNotes = () => (g_detailObj.ringCnt[g_stateObj.scoreId] ?? 0) > 0;
|
|
4378
|
+
|
|
4073
4379
|
/**
|
|
4074
4380
|
* Displayボタンを切り替えたときに連動して切り替えるボタンの設定
|
|
4075
4381
|
* @param {object} _headerObj
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
スキンcssファイル (default)
|
|
6
6
|
|
|
7
7
|
Created : 2019/11/04
|
|
8
|
-
Revised : 2026/
|
|
8
|
+
Revised : 2026/11/08 (v52.2.1)
|
|
9
9
|
|
|
10
10
|
https://github.com/cwtickle/danoniplus
|
|
11
11
|
------------------------------------------ */
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
--settings-appearance-x: #cc99ff;
|
|
44
44
|
--settings-opacity-x: #ee99ff;
|
|
45
45
|
--settings-hitPosition-x: #ff99ff;
|
|
46
|
+
--settings-ringSpeed-x: #ff99cc;
|
|
47
|
+
--settings-ringGuide-x: #ff9999;
|
|
46
48
|
--settings-playWindow-x: #9999ff;
|
|
47
49
|
--settings-stepArea-x: #6699ff;
|
|
48
50
|
--settings-frzReturn-x: #99ccff;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
スキンcssファイル (light)
|
|
6
6
|
|
|
7
7
|
Created : 2019/11/04
|
|
8
|
-
Revised : 2026/
|
|
8
|
+
Revised : 2026/11/08 (v52.2.1)
|
|
9
9
|
|
|
10
10
|
https://github.com/cwtickle/danoniplus
|
|
11
11
|
------------------------------------------ */
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
--settings-appearance-x: #9900ff;
|
|
44
44
|
--settings-opacity-x: #cc00ff;
|
|
45
45
|
--settings-hitPosition-x: #ff00ff;
|
|
46
|
+
--settings-ringSpeed-x: #cc0066;
|
|
47
|
+
--settings-ringGuide-x: #cc3333;
|
|
46
48
|
--settings-playWindow-x: #000099;
|
|
47
49
|
--settings-stepArea-x: #222299;
|
|
48
50
|
--settings-frzReturn-x: #003377;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
スキンcssファイル (skyblue)
|
|
6
6
|
|
|
7
7
|
Created : 2019/11/04
|
|
8
|
-
Revised : 2026/
|
|
8
|
+
Revised : 2026/11/08 (v52.2.1)
|
|
9
9
|
|
|
10
10
|
https://github.com/cwtickle/danoniplus
|
|
11
11
|
------------------------------------------ */
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
--settings-appearance-x: #9900ff;
|
|
44
44
|
--settings-opacity-x: #cc00ff;
|
|
45
45
|
--settings-hitPosition-x: #ff00ff;
|
|
46
|
+
--settings-ringSpeed-x: #9900cc;
|
|
47
|
+
--settings-ringGuide-x: #3333ff;
|
|
46
48
|
--settings-playWindow-x: #000099;
|
|
47
49
|
--settings-stepArea-x: #000099;
|
|
48
50
|
--settings-frzReturn-x: #003399;
|
|
Binary file
|