Effect Radar Implementation v1 #46

Merged
hersi merged 12 commits from feature/radar into master 2023-11-23 07:41:15 +01:00
2 changed files with 231 additions and 37 deletions
Showing only changes of commit 2363b381ed - Show all commits

View File

@ -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
}

View File

@ -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)