Effect Radar Implementation v1 #46
|
@ -92,6 +92,28 @@ local function dump(o)
|
|||
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 {
|
||||
split = split,
|
||||
filter = filter,
|
||||
|
@ -104,5 +126,7 @@ return {
|
|||
mix = mix,
|
||||
modIndex = modIndex,
|
||||
firstAlphaNum = firstAlphaNum,
|
||||
dump = dump
|
||||
dump = dump,
|
||||
all = all,
|
||||
any = any
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ require("api.point2d")
|
|||
require("api.color")
|
||||
|
||||
local Dim = require("common.dimensions")
|
||||
local Util = require("common.util")
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
|
@ -15,6 +16,8 @@ local RADAR_PURPLE = ColorRGBA.new(238, 130, 238)
|
|||
local RADAR_MAGENTA = ColorRGBA.new(191, 70, 235)
|
||||
local RADAR_GREEN = ColorRGBA.new(0, 255, 100)
|
||||
|
||||
local maxScaleFactor = 1.8
|
||||
|
||||
---@param p1 Point2D
|
||||
---@param p2 Point2D
|
||||
---@param width number
|
||||
|
@ -282,8 +285,8 @@ function Radar:drawRadarMesh()
|
|||
local colorCenter = ColorRGBA.new(112, 119, 255, 230) -- light blue-ish purple
|
||||
|
||||
-- Calculate the maximum size based on the constraint
|
||||
local maxSize = self.RADIUS * self.scale + 10
|
||||
local maxLineLength = maxSize + (maxSize / 2)
|
||||
local maxSize = self.RADIUS * self.scale
|
||||
local maxLineLength = maxSize * maxScaleFactor
|
||||
self._hexagonMesh:SetParam("maxSize", maxLineLength + .0)
|
||||
|
||||
-- Set the color of the hexagon
|
||||
|
@ -304,8 +307,7 @@ function Radar:drawRadarMesh()
|
|||
|
||||
--local angle = math.rad(60 * (i-1)) + rotationAngle
|
||||
local scale = scaleFact[j]
|
||||
local lineLength = maxLineLength * scale
|
||||
lineLength = math.min(lineLength, maxLineLength) -- Cap the length
|
||||
local lineLength = maxSize * scale
|
||||
local px = lineLength * math.cos(angle)
|
||||
local py = lineLength * math.sin(angle)
|
||||
table.insert(vertices, {{px, py}, {0, 0}})
|
||||
|
@ -366,52 +368,220 @@ function Radar:updateGraph(info, dif)
|
|||
--local txtFilePath = extractedSubstring .. "radar\\" .. dif .. ".txt"
|
||||
|
||||
--local song = io.open(txtFilePath, "r")
|
||||
local song = io.open(info.."/"..dif..".ksh")
|
||||
game.Log(info.."/"..dif..".ksh", game.LOGGER_DEBUG)
|
||||
local fullPath = info.."/"..dif..".ksh"
|
||||
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)
|
||||
if song then
|
||||
local chartData = song:read("*all")
|
||||
song:close()
|
||||
|
||||
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 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
|
||||
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
|
||||
local noteCount = noteType:match("1") and 1 or 0
|
||||
--game.Log(line, game.LOGGER_DEBUG)
|
||||
|
||||
notesCount = notesCount + noteCount
|
||||
knobCount = knobCount + (fxType == "02" and 1 or 0)
|
||||
local patternBt = "([012][012][012][012])"
|
||||
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)
|
||||
handTripCount = handTripCount + (laserType:match("[HKPUV:]") and 1 or 0)
|
||||
if laserType ~= "--" then
|
||||
tsumamiValue = tsumamiValue + 0.5
|
||||
-- match line format
|
||||
|
||||
local noteType, fxType, laserType = line:match(pattern)
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
elseif line:match("t=(%d+)") then
|
||||
local bpm = tonumber(line:match("t=(%d+)"))
|
||||
totalSongLength = math.max(totalSongLength, bpm)
|
||||
end
|
||||
|
||||
if line:match("beat=") or line:match("stop=") or
|
||||
line:match("zoom_top=") or line:match("zoom_bottom=") or line:match("center_split=") then
|
||||
trickyValue = trickyValue + 0.5
|
||||
local beat = line:match("beat=(%d+/%d+)")
|
||||
if beat then
|
||||
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
|
||||
|
||||
local graphValues = {
|
||||
notes = (notesCount / totalSongLength),
|
||||
peak = (notesCount / totalMeasures) * 10,
|
||||
tsumami = tsumamiValue,
|
||||
notes = notesValue / totalMeasures,
|
||||
peak = peakValue,
|
||||
tsumami = tsumamiValue / totalMeasures,
|
||||
tricky = trickyValue,
|
||||
handtrip = handTripCount,
|
||||
onehand = oneHandCount,
|
||||
|
@ -422,21 +592,21 @@ function Radar:updateGraph(info, dif)
|
|||
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||
end
|
||||
|
||||
local scaleFactors = {
|
||||
notes = 2,
|
||||
peak = 50,
|
||||
tsumami = 500,
|
||||
tricky = 50,
|
||||
handtrip = 100,
|
||||
onehand = 50,
|
||||
local calibration = {
|
||||
notes = 10,
|
||||
peak = 48,
|
||||
tsumami = 20000,
|
||||
tricky = 128,
|
||||
handtrip = 300,
|
||||
onehand = 300,
|
||||
}
|
||||
|
||||
for key, factor in pairs(scaleFactors) do
|
||||
for key, factor in pairs(calibration) do
|
||||
-- Apply the scaling factor to each value
|
||||
self._graphdata[key] = graphValues[key] / factor
|
||||
|
||||
-- Limit to a maximum of 125%
|
||||
self._graphdata[key] = math.min(1.25, self._graphdata[key])
|
||||
-- Limit to maximum scale factor
|
||||
self._graphdata[key] = math.min(self._graphdata[key], maxScaleFactor)
|
||||
end
|
||||
|
||||
game.Log("_graphdata", game.LOGGER_DEBUG)
|
||||
|
|
Loading…
Reference in New Issue