radar calc rewrite

This commit is contained in:
Hersi 2023-11-18 09:30:13 +01:00
parent a0ceb0bd01
commit 2363b381ed
2 changed files with 231 additions and 37 deletions

View File

@ -92,6 +92,28 @@ local function dump(o)
end end
end end
local function all(t, predicate)
predicate = predicate or function(e) return e end
for _, e in ipairs(t) do
if not predicate(e) then
return false
end
end
return true
end
local function any(t, predicate)
predicate = predicate or function(e) return e end
for _, e in ipairs(t) do
if predicate(e) then
return true
end
end
return false
end
return { return {
split = split, split = split,
filter = filter, filter = filter,
@ -104,5 +126,7 @@ return {
mix = mix, mix = mix,
modIndex = modIndex, modIndex = modIndex,
firstAlphaNum = firstAlphaNum, firstAlphaNum = firstAlphaNum,
dump = dump dump = dump,
all = all,
any = any
} }

View File

@ -8,6 +8,7 @@ require("api.point2d")
require("api.color") require("api.color")
local Dim = require("common.dimensions") local Dim = require("common.dimensions")
local Util = require("common.util")
Dim.updateResolution() Dim.updateResolution()
@ -15,6 +16,8 @@ local RADAR_PURPLE = ColorRGBA.new(238, 130, 238)
local RADAR_MAGENTA = ColorRGBA.new(191, 70, 235) local RADAR_MAGENTA = ColorRGBA.new(191, 70, 235)
local RADAR_GREEN = ColorRGBA.new(0, 255, 100) local RADAR_GREEN = ColorRGBA.new(0, 255, 100)
local maxScaleFactor = 1.8
---@param p1 Point2D ---@param p1 Point2D
---@param p2 Point2D ---@param p2 Point2D
---@param width number ---@param width number
@ -282,8 +285,8 @@ function Radar:drawRadarMesh()
local colorCenter = ColorRGBA.new(112, 119, 255, 230) -- light blue-ish purple local colorCenter = ColorRGBA.new(112, 119, 255, 230) -- light blue-ish purple
-- Calculate the maximum size based on the constraint -- Calculate the maximum size based on the constraint
local maxSize = self.RADIUS * self.scale + 10 local maxSize = self.RADIUS * self.scale
local maxLineLength = maxSize + (maxSize / 2) local maxLineLength = maxSize * maxScaleFactor
self._hexagonMesh:SetParam("maxSize", maxLineLength + .0) self._hexagonMesh:SetParam("maxSize", maxLineLength + .0)
-- Set the color of the hexagon -- Set the color of the hexagon
@ -304,8 +307,7 @@ function Radar:drawRadarMesh()
--local angle = math.rad(60 * (i-1)) + rotationAngle --local angle = math.rad(60 * (i-1)) + rotationAngle
local scale = scaleFact[j] local scale = scaleFact[j]
local lineLength = maxLineLength * scale local lineLength = maxSize * scale
lineLength = math.min(lineLength, maxLineLength) -- Cap the length
local px = lineLength * math.cos(angle) local px = lineLength * math.cos(angle)
local py = lineLength * math.sin(angle) local py = lineLength * math.sin(angle)
table.insert(vertices, {{px, py}, {0, 0}}) table.insert(vertices, {{px, py}, {0, 0}})
@ -366,52 +368,220 @@ function Radar:updateGraph(info, dif)
--local txtFilePath = extractedSubstring .. "radar\\" .. dif .. ".txt" --local txtFilePath = extractedSubstring .. "radar\\" .. dif .. ".txt"
--local song = io.open(txtFilePath, "r") --local song = io.open(txtFilePath, "r")
local song = io.open(info.."/"..dif..".ksh") local fullPath = info.."/"..dif..".ksh"
game.Log(info.."/"..dif..".ksh", game.LOGGER_DEBUG) local song = io.open(fullPath)
game.Log('Reading chart data from "'..fullPath..'"', game.LOGGER_DEBUG)
game.Log(song and "file open" or "file not found", game.LOGGER_DEBUG) game.Log(song and "file open" or "file not found", game.LOGGER_DEBUG)
if song then if song then
local chartData = song:read("*all") local chartData = song:read("*all")
song:close() song:close()
local notesCount, knobCount, oneHandCount, handTripCount = 0, 0, 0, 0 local notesCount, knobCount, oneHandCount, handTripCount = 0, 0, 0, 0
local chartLineCount = 0
local notesValue = 0
local peakValue = 0
local tsumamiValue = 0
local trickyValue = 0 local trickyValue = 0
local totalMeasures = 0 local totalMeasures = 0
local totalSongLength = 0
local tsumamiValue = 0
local lastNotes = {}
local lastFx = {}
local measureLength = 0
---@cast chartData string
for line in chartData:gmatch("[^\r\n]+") do for line in chartData:gmatch("[^\r\n]+") do
local noteType, fxType, laserType = line:match("(%d%d%d%d)|(%d%d)|([%-%a ]+)") -- <bt-lanes x 4>|<fx-lanes x 2>|<laser-lanes x 2><lane-spin (optional)>
if noteType and fxType then --game.Log(line, game.LOGGER_DEBUG)
local noteCount = noteType:match("1") and 1 or 0
notesCount = notesCount + noteCount local patternBt = "([012][012][012][012])"
knobCount = knobCount + (fxType == "02" and 1 or 0) local patternFx = "([012ABDFGHIJKLPQSTUVWX][012ABDFGHIJKLPQSTUVWX])"
local patternLaser = "([%-:%dA-Za-o][%-:%dA-Za-o])"
local patternLaneSpin = "([@S][%(%)<>]%d+)" -- optional
local pattern = patternBt.."|"..patternFx.."|"..patternLaser
oneHandCount = oneHandCount + (laserType:match("[79A-D:]") and 1 or 0) -- match line format
handTripCount = handTripCount + (laserType:match("[HKPUV:]") and 1 or 0)
if laserType ~= "--" then local noteType, fxType, laserType = line:match(pattern)
tsumamiValue = tsumamiValue + 0.5 local laneSpin = line:match(patternLaneSpin)
if noteType and fxType and laserType then
chartLineCount = chartLineCount + 1
-- convert strings to array, to be easily indexable
noteType = {noteType:match("([012])([012])([012])([012])")}
fxType = {fxType:match("([012ABDFGHIJKLPQSTUVWX])([012ABDFGHIJKLPQSTUVWX])")}
laserType = {laserType:match("([%-:%dA-Za-o])([%-:%dA-Za-o])")}
---@cast noteType string[]
---@cast fxType string[]
---@cast laserType string[]
-- parse notes
local function isNewNote(idx, note)
if note == "2" and lastNotes[idx] ~= note then
-- a new hold note
return true
end
if note == "1" then
-- a chip
return true
end
end end
for noteIdx, note in ipairs(noteType) do
if isNewNote(noteIdx, note) then
notesCount = notesCount + 1
end
end
-- parse fx
local function isNewFx(idx, fx)
if fx:match("[1ABDFGHIJKLPQSTUVWX]") and lastFx[idx] ~= fx then
-- a new hold note
return true
end
if fx == "2" then
-- a chip
return true
end
end
for fxIdx, fx in ipairs(fxType) do
if isNewFx(fxIdx, fx) then
notesCount = notesCount + 1
end
end
-- parse laser
for _, laser in ipairs(laserType) do
if laser ~= "-" then
knobCount = knobCount + 1
end
end
-- figure out one-handed notes (there's a BT or FX while a hand is manipulating a knob)
-- also try to figure out cross-handed notes (one-handed notes, but on the same side as knob)
local function countBtFx()
local count = 0
for noteIdx, note in ipairs(noteType) do
if isNewNote(noteIdx, note) then
count = count + 1
end
end
for fxIdx, fx in ipairs(fxType) do
if isNewFx(fxIdx, fx) then
count = count + 1
end
end
return count
end
---@param side "left"|"right"
local function countSide(side)
local count = 0
local notes = {}
local fx = ""
if side == "left" then
notes = {noteType[1], noteType[2]}
fx = fxType[1]
if isNewFx(1, fx) then
count = count + 1
end
elseif side == "right" then
notes = {noteType[3], noteType[4]}
fx = fxType[2]
if isNewFx(2, fx) then
count = count + 1
end
else
game.Log("countSide: Invalid side parameter", game.LOGGER_ERROR)
return 0
end
for noteIdx, note in ipairs(notes) do
if isNewNote(noteIdx, note) then
count = count + 1
end
end
return count
end
if laserType[1] ~= "-" and laserType[2] == "-" then
oneHandCount = oneHandCount + countBtFx()
handTripCount = handTripCount + countSide("left")
end
if laserType[1] == "-" and laserType[2] ~= "-" then
oneHandCount = oneHandCount + countBtFx()
handTripCount = handTripCount + countSide("right")
end
lastNotes = noteType
lastFx = fxType
measureLength = measureLength + 1
end end
if line == "--" then if line == "--" then
-- end of measure
measureLength = math.max(1, measureLength)
local relativeMeasureLength = measureLength / 192
-- calculate peak density
local peak = (notesCount / 6) / relativeMeasureLength
peakValue = math.max(peakValue, peak)
--[[
local debuglog = {
measureLength = measureLength,
notesCount = notesCount,
relativeMeasureLength = relativeMeasureLength,
peak = peak,
}
for k, v in pairs(debuglog) do
game.Log(k..": "..v, game.LOGGER_DEBUG)
end
]]
-- cumulate "time" spent operating the knobs
local tsumami = (knobCount / 2) / relativeMeasureLength
tsumamiValue = tsumamiValue + tsumami
measureLength = 0
notesCount = 0
-- cumulate peak values (used to average notes over the length of the song)
notesValue = notesValue + peak
totalMeasures = totalMeasures + 1 totalMeasures = totalMeasures + 1
elseif line:match("t=(%d+)") then
local bpm = tonumber(line:match("t=(%d+)"))
totalSongLength = math.max(totalSongLength, bpm)
end end
if line:match("beat=") or line:match("stop=") or local beat = line:match("beat=(%d+/%d+)")
line:match("zoom_top=") or line:match("zoom_bottom=") or line:match("center_split=") then if beat then
trickyValue = trickyValue + 0.5 beat = {beat:match("(%d+)/(%d+)")}
end
--BUG: This is not correct, it needs to account for effect length
local function isTricky()
local tricks = {
"beat",
"stop",
"zoom_top",
"zoom_bottom",
"zoom_side",
"center_split",
}
return Util.any(tricks, function(e) return line:match("e") end)
end
if laneSpin or isTricky() then
trickyValue = trickyValue + 1
end end
end end
local graphValues = { local graphValues = {
notes = (notesCount / totalSongLength), notes = notesValue / totalMeasures,
peak = (notesCount / totalMeasures) * 10, peak = peakValue,
tsumami = tsumamiValue, tsumami = tsumamiValue / totalMeasures,
tricky = trickyValue, tricky = trickyValue,
handtrip = handTripCount, handtrip = handTripCount,
onehand = oneHandCount, onehand = oneHandCount,
@ -422,21 +592,21 @@ function Radar:updateGraph(info, dif)
game.Log(k..": "..v, game.LOGGER_DEBUG) game.Log(k..": "..v, game.LOGGER_DEBUG)
end end
local scaleFactors = { local calibration = {
notes = 2, notes = 10,
peak = 50, peak = 48,
tsumami = 500, tsumami = 20000,
tricky = 50, tricky = 128,
handtrip = 100, handtrip = 300,
onehand = 50, onehand = 300,
} }
for key, factor in pairs(scaleFactors) do for key, factor in pairs(calibration) do
-- Apply the scaling factor to each value -- Apply the scaling factor to each value
self._graphdata[key] = graphValues[key] / factor self._graphdata[key] = graphValues[key] / factor
-- Limit to a maximum of 125% -- Limit to maximum scale factor
self._graphdata[key] = math.min(1.25, self._graphdata[key]) self._graphdata[key] = math.min(self._graphdata[key], maxScaleFactor)
end end
game.Log("_graphdata", game.LOGGER_DEBUG) game.Log("_graphdata", game.LOGGER_DEBUG)