ExperimentalGear/scripts/songselect/songwheel old.lua

676 lines
20 KiB
Lua

easing = require("easing")
gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf")
game.LoadSkinSample("cursor_song")
game.LoadSkinSample("cursor_difficulty")
local resx, resy = game.GetResolution()
local levelFont = ImageFont.new("font-level", "0123456789")
local diffFont = ImageFont.new("diff_num", "0123456789")
local bpmFont = ImageFont.new("number", "0123456789.") -- FIXME: font-default
local desw, desh;
local resx, resy;
local portrait;
local scale;
function ResetLayoutInformation()
resx, resy = game.GetResolution()
portrait = resy > resx
desw = portrait and 1080 or 1920
desh = desw * (resy / resx)
scale = resx / desw
end
function render(deltaTime)
ResetLayoutInformation()
end
-- Grades
---------
local noGrade = Image.skin("song_select/grade/nograde.png")
local grades = {
{["min"] = 9900000, ["image"] = Image.skin("song_select/grade/s.png")},
{["min"] = 9800000, ["image"] = Image.skin("song_select/grade/aaap.png")},
{["min"] = 9700000, ["image"] = Image.skin("song_select/grade/aaa.png")},
{["min"] = 9500000, ["image"] = Image.skin("song_select/grade/aap.png")},
{["min"] = 9300000, ["image"] = Image.skin("song_select/grade/aa.png")},
{["min"] = 9000000, ["image"] = Image.skin("song_select/grade/ap.png")},
{["min"] = 8700000, ["image"] = Image.skin("song_select/grade/a.png")},
{["min"] = 7500000, ["image"] = Image.skin("song_select/grade/b.png")},
{["min"] = 6500000, ["image"] = Image.skin("song_select/grade/c.png")},
{["min"] = 0, ["image"] = Image.skin("song_select/grade/d.png")},
}
function lookup_grade_image(difficulty)
local gradeImage = noGrade
if difficulty.scores[1] ~= nil then
local highScore = difficulty.scores[1]
for i, v in ipairs(grades) do
if highScore.score >= v.min then
gradeImage = v.image
break
end
end
end
return { image = gradeImage, flicker = (gradeImage == grades[1].image) }
end
-- Medals
---------
local noMedal = Image.skin("song_select/medal/nomedal.png")
local medals = {
Image.skin("song_select/medal/played.png"),
Image.skin("song_select/medal/clear.png"),
Image.skin("song_select/medal/hard.png"),
Image.skin("song_select/medal/uc.png"),
Image.skin("song_select/medal/puc.png")
}
function lookup_medal_image(difficulty)
local medalImage = noMedal
local flicker = false
if difficulty.scores[1] ~= nil then
if difficulty.topBadge ~= 0 then
medalImage = medals[difficulty.topBadge]
if difficulty.topBadge >= 3 then -- hard
flicker = true
end
end
end
return { image = medalImage, flicker = flicker }
end
-- Lookup difficulty
function lookup_difficulty(diffs, diff)
local diffIndex = nil
for i, v in ipairs(diffs) do
if v.difficulty + 1 == diff then
diffIndex = i
end
end
local difficulty = nil
if diffIndex ~= nil then
difficulty = diffs[diffIndex]
end
return difficulty
end
-- JacketCache class
--------------------
JacketCache = {}
JacketCache.new = function()
local this = {
cache = {},
images = {
loading = Image.skin("song_select/loading.png"),
}
}
setmetatable(this, {__index = JacketCache})
return this
end
JacketCache.get = function(this, path)
local jacket = this.cache[path]
if not jacket or jacket == this.images.loading.image then
jacket = gfx.LoadImageJob(path, this.images.loading.image)
this.cache[path] = jacket
end
return Image.wrap(jacket)
end
-- SongData class
-----------------
SongData = {}
SongData.new = function(jacketCache)
local this = {
selectedIndex = 1,
selectedDifficulty = 0,
memo = Memo.new(),
jacketCache = jacketCache,
images = {
dataBg = Image.skin("song_select/data_bg.png"),
fg = Image.skin("song_select/fg.png"),
cursor = Image.skin("song_select/level_cursor.png"),
none = Image.skin("song_select/level/none.png"),
difficulties = {
Image.skin("song_select/level/novice.png"),
Image.skin("song_select/level/advanced.png"),
Image.skin("song_select/level/exhaust.png"),
Image.skin("song_select/level/maximum.png"),
Image.skin("song_select/level/infinite.png"),
Image.skin("song_select/level/gravity.png"),
Image.skin("song_select/level/heavenly.png"),
Image.skin("song_select/level/vivid.png")
},
}
}
setmetatable(this, {__index = SongData})
return this
end
SongData.render = function(this, deltaTime)
local song = songwheel.songs[this.selectedIndex]
if not song then return end
-- Lookup difficulty
local diff = song.difficulties[this.selectedDifficulty]
if diff == nil then diff = song.difficulties[1] end
-- Draw the background
this.images.dataBg:draw({ x = desw / 2, y = desh / 2, w = 1080 ,h = 1920})
-- Draw the jacket
local jacket = this.jacketCache:get(diff.jacketPath)
jacket:draw({ x = 97, y = 326, w = 346, h = 346, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP })
-- Draw the title
local title = this.memo:memoize(string.format("title_%s", song.id), function ()
gfx.LoadSkinFont("NotoSans-Regular.ttf")
return gfx.CreateLabel(song.title, 24, 0)
end)
gfx.FillColor(255, 255, 255, 255)
gfx.DrawLabel(title, 32, desh / 2 - 4, 390)
-- Draw the artist
local artist = this.memo:memoize(string.format("artist_%s", song.id), function ()
gfx.LoadSkinFont("NotoSans-Regular.ttf")
return gfx.CreateLabel(song.artist, 24, 0)
end)
gfx.FillColor(255, 255, 255, 255)
gfx.DrawLabel(artist, 32, desh / 2 + 42, 390)
-- Draw the effector
local effector = this.memo:memoize(string.format("eff_%s_%s", song.id, diff.id), function ()
gfx.LoadSkinFont("NotoSans-Regular.ttf")
return gfx.CreateLabel(diff.effector, 16, 0)
end)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
gfx.FillColor(255, 255, 255, 255)
gfx.DrawLabel(effector, 260, desh / 2 + 208, 320)
-- Draw the illustrator
if diff.illustrator then
local illustrator = this.memo:memoize(string.format("ill_%s_%s", song.id, diff.id), function ()
gfx.LoadSkinFont("NotoSans-Regular.ttf")
return gfx.CreateLabel(diff.illustrator, 16, 0)
end)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
gfx.FillColor(255, 255, 255, 255)
gfx.DrawLabel(illustrator, 260, desh / 2 + 238, 320)
end
-- Draw the bpm
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
gfx.FontSize(24)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
gfx.FillColor(255, 255, 255, 255)
gfx.Text(song.bpm, 75, desh / 2 - 34)
this:draw_cursor(diff.difficulty)
-- Draw the hi-score
local hiScore = diff.scores[1]
if hiScore then
-- FIXME: large / small font
local scoreText = string.format("%08d", hiScore.score)
levelFont:draw(scoreText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
-- local scoreHiText = string.format("%04d", math.floor(hiScore.score / 1000))
-- levelFont:draw(scoreHiText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
-- local scoreLoText = string.format("%04d", hiScore.score % 1000)
-- bpmFont:draw(scoreLoText, 470, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
end
-- Draw the grade and medal
local grade = lookup_grade_image(diff)
grade.image:draw({ x = desw / 2 - 157, y = desh / 2 - 162, scale = 0.85, alpha = grade.flicker and glowState and 0.9 or 1 })
local medal = lookup_medal_image(diff)
medal.image:draw({ x = desw / 2 - 72, y = desh / 2 - 199, scale = 0.86, alpha = medal.flicker and glowState and 0.9 or 1})
for i = 1, 4 do
local d = lookup_difficulty(song.difficulties, i)
this:draw_difficulty(i - 1, d, jacket)
end
end
SongData.draw_title_artist = function(this, label, x, y, maxWidth)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
gfx.FillColor(55, 55, 55, 64)
gfx.DrawLabel(label, x + 2, y + 2, maxWidth)
gfx.FillColor(55, 55, 55, 255)
gfx.DrawLabel(label, x, y, maxWidth)
end
SongData.set_index = function(this, newIndex)
this.selectedIndex = newIndex
end
SongData.draw_cursor = function(this, index)
local x = 98
local y = desh / 2 + 133
-- Draw the cursor
this.images.cursor:draw({ x = x + index * 115, y = y, scale = 0.85 })
end
SongData.set_difficulty = function(this, newDiff)
this.selectedDifficulty = newDiff
end
SongData.draw_difficulty = function(this, index, diff, jacket)
local x = 98
local y = desh / 2 + 135
-- Draw the jacket icon
local jacket = this.jacketCache.images.loading
if diff ~= nil then jacket = this.jacketCache:get(diff.jacketPath) end
if diff == nil then
this.images.none:draw({ x = x + index * 115, y = y - 600, scale = 0.78})
else
-- Draw the background
this.images.difficulties[diff.difficulty + 1]:draw({ x = x + index * 115, y = y, scale = 0.78})
-- Draw the level
local levelText = string.format("%02d", diff.level)
diffFont:draw(levelText, x + index * 115, y - 20, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
end
end
-- SongTable class
------------------
SongTable = {}
SongTable.new = function(jacketCache)
local this = {
cols = 1,
rows = 11,
selectedIndex = 1,
selectedDifficulty = 0,
rowOffset = 0, -- song index offset of top-left song in page
cursorPos = 0, -- cursor position in page [0..cols * rows)
displayCursorPos = 0,
cursorAnim = 0,
cursorAnimTotal = 0.1,
memo = Memo.new(),
jacketCache = jacketCache,
images = {
matchingBg = Image.skin("song_select/matching_bg.png"),
scoreBg = Image.skin("song_select/score_bg.png"),
force = Image.skin("song_select/force.png"),
cursor = Image.skin("song_select/cursor.png"),
cursorText = Image.skin("song_select/cursor_text.png"),
cursorDiamond = Image.skin("song_select/cursor_diamond.png"),
cursorDiamondWire = Image.skin("song_select/cursor_diamond_wire.png"),
plates = {
Image.skin("song_select/plate/novice.png"),
Image.skin("song_select/plate/advanced.png"),
Image.skin("song_select/plate/exhaust.png"),
Image.skin("song_select/plate/maximum.png"),
Image.skin("song_select/plate/infinite.png"),
Image.skin("song_select/plate/gravity.png"),
Image.skin("song_select/plate/heavenly.png"),
Image.skin("song_select/plate/vivid.png")
}
}
}
setmetatable(this, {__index = SongTable})
return this
end
SongTable.calc_cursor_point = function(this, pos)
local col = pos % this.cols
local row = math.floor((pos) / this.cols)
local x = desw * 0.75 + col * this.images.cursor.w
local y = 0 + row * this.images.cursor.h
return x, y
end
SongTable.set_index = function(this, newIndex)
if newIndex ~= this.selectedIndex then
game.PlaySample("cursor_song")
end
local delta = newIndex - this.selectedIndex
if delta < -1 or delta > 1 then
local newOffset = newIndex - 1
this.rowOffset = math.floor((newIndex - 1) / this.cols) * this.cols
this.cursorPos = (newIndex - 1) - this.rowOffset
this.displayCursorPos = this.cursorPos
else
local newCursorPos = this.cursorPos + delta
if newCursorPos < 0 then
-- scroll up
this.rowOffset = this.rowOffset - this.cols
if this.rowOffset < 0 then
-- this.rowOffset = math.floor(#songwheel.songs / this.cols)
end
newCursorPos = newCursorPos + this.cols
elseif newCursorPos >= this.cols * this.rows then
-- scroll down
this.rowOffset = this.rowOffset + this.cols
newCursorPos = newCursorPos - this.cols
else
-- no scroll, move cursor in page
end
if this.cursorAnim > 0 then
this.displayCursorPos = easing.outQuad(0.5 - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, 0.5)
end
this.cursorPos = newCursorPos
this.cursorAnim = this.cursorAnimTotal
end
this.selectedIndex = newIndex
end
SongTable.set_difficulty = function(this, newDiff)
if newDiff ~= this.selectedDifficulty then
game.PlaySample("cursor_difficulty")
end
this.selectedDifficulty = newDiff
end
SongTable.render = function(this, deltaTime)
this:draw_songs()
this:draw_cursor(deltaTime)
end
SongTable.draw_songs = function(this)
for i = 1, this.cols * this.rows do
if this.rowOffset + i <= #songwheel.songs then
this:draw_song(i - 1, this.rowOffset + i)
end
end
end
-- Draw the song plate
SongTable.draw_song = function(this, pos, songIndex)
local song = songwheel.songs[songIndex]
if not song then return end
-- Lookup difficulty
local diff = song.difficulties[this.selectedDifficulty]
if diff == nil then diff = song.difficulties[1] end
local x, y = this:calc_cursor_point(pos)
x = x + 4
y = y + 16
-- Draw the jacket
local jacket = this.jacketCache:get(diff.jacketPath)
jacket:draw({ x = x - 24, y = y - 21, w = 122, h = 122 })
-- Draw the background
gfx.FillColor(255, 255, 255)
this.images.scoreBg:draw({ x = x + 72, y = y + 16 })
if diff.force and diff.force > 0 then
this.images.matchingBg:draw({ x = x + 72, y = y - 62 })
end
this.images.plates[diff.difficulty + 1]:draw({ x = x, y = y })
-- Draw the title
local title = this.memo:memoize(string.format("title_%s", song.id), function ()
gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf")
return gfx.CreateLabel(song.title, 14, 0)
end)
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE)
gfx.DrawLabel(title, x - 22, y + 53, 125)
-- Draw the grade and medal
local grade = lookup_grade_image(diff)
grade.image:draw({ x = x + 78, y = y - 23, alpha = grade.flicker and glowState and 0.9 or 1 })
local medal = lookup_medal_image(diff)
medal.image:draw({ x = x + 78, y = y + 10, alpha = medal.flicker and glowState and 0.9 or 1 })
-- Draw the level
local levelText = string.format("%02d", diff.level)
levelFont:draw(levelText, x + 72, y + 56, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
-- Draw the volforce
--if diff.force and diff.force > 0 then
--local forceText = string.format("%d", math.floor(diff.force * 100))
--bpmFont:draw(forceText, x + , y - 60, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
--end
--if diff.forceInTotal then
--this.images.force:draw({x = x - 75, y = y - 60, w = 59, h = 59 })
--end
end
-- Draw the song cursor
SongTable.draw_cursor = function(this, deltaTime)
gfx.Save()
local pos = this.displayCursorPos
if this.cursorAnim > 0 then
this.cursorAnim = this.cursorAnim - deltaTime
if this.cursorAnim <= 0 then
this.displayCursorPos = this.cursorPos
pos = this.cursorPos
else
pos = easing.outQuad(this.cursorAnimTotal - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, this.cursorAnimTotal)
end
end
local x, y = this:calc_cursor_point(pos)
gfx.FillColor(255, 255, 255)
local t = currentTime % 1
-- scroll text
gfx.Scissor(
x - this.images.cursor.w / 2, y - (this.images.cursor.h - 30) / 2,
this.images.cursor.w, this.images.cursor.h - 30)
local offset = (currentTime * 50) % 290
local alpha = glowState and 0.8 or 1
this.images.cursorText:draw({ x = x + 96, y = y + offset, alpha = alpha })
this.images.cursorText:draw({ x = x + 96, y = y - 290 + offset, alpha = alpha })
this.images.cursorText:draw({ x = x - 96, y = y + offset, alpha = alpha })
this.images.cursorText:draw({ x = x - 96, y = y - 290 + offset, alpha = alpha })
gfx.ResetScissor()
-- diamong wireframe
local h = (this.images.cursorDiamondWire.h * 1.5) * easing.outQuad(t * 2, 0, 1, 1)
this.images.cursorDiamondWire:draw({ x = x, y = y, w = this.images.cursorDiamondWire.w * 1.5, h = h, alpha = 0.5 })
-- ghost cursor
alpha = easing.outSine(t, 1, -1, 1)
h = this.images.cursor.h * easing.outSine(t, 0, 1, 1)
this.images.cursor:draw({ x = x, y = y, h = h, alpha = alpha })
-- concrete cursor
-- local w = this.images.cursor.w * easing.outSine(t, 1, 0.05, 0.5)
this.images.cursor:draw({ x = x, y = y, alpha = glowState and 0.8 or 1 })
-- diamond knot
gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER)
this.images.cursorDiamond:draw({ x = x + 100, y = y, alpha = 1 })
this.images.cursorDiamond:draw({ x = x - 100, y = y, alpha = 1 })
local s = this.images.cursorDiamond.w / 1.5
this.images.cursorDiamond:draw({ x = x + 90 + easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 })
this.images.cursorDiamond:draw({ x = x - 90 - easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 })
gfx.Restore()
end
-- main
-------
local jacketCache = JacketCache.new()
local songData = SongData.new(jacketCache)
local songTable = SongTable.new(jacketCache)
glowState = false
currentTime = 0
-- Callback
get_page_size = function()
return 12
end
searchIndex = 1
soffset = 0
searchText = gfx.CreateLabel("", 5, 0)
draw_search = function(x,y,w,h)
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
game.PlaySample("woosh")
end
searchIndex = songwheel.searchInputActive and 0 or 1
gfx.BeginPath()
local bgfade = 1 - (searchIndex + soffset)
--if not songwheel.searchInputActive then bgfade = soffset end
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
gfx.Rect(0,0,resx,resy)
gfx.Fill()
gfx.ForceRender()
local xpos = x + (searchIndex + soffset)*w
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
gfx.BeginPath()
gfx.RoundedRect(xpos,y,w,h,h/2)
gfx.FillColor(30,30,30)
gfx.StrokeColor(0,128,255)
gfx.StrokeWidth(1)
gfx.Fill()
gfx.Stroke()
gfx.BeginPath();
gfx.LoadSkinFont("segoeui.ttf");
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
end
-- Callback
function render(deltaTime)
ResetLayoutInformation()
if ((math.floor(currentTime * 1000) % 100) < 50) then
glowState = false
else
glowState = true
end
local xshift = (resx - desw * scale) / 2
local yshift = (resy - desh * scale) / 2
gfx.Translate(xshift, yshift)
--gfx.Scale(scale, scale)
songData:render(deltaTime)
songTable:render(deltaTime)
--if totalForce then
--local forceText = string.format("%.2f", totalForce)
-- gfx.SetImageTint(255, 254, 2)
--bpmFont:draw(forceText, 140, 353, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
--end
-- Draw the search status
if songwheel.searchStatus then
gfx.BeginPath()
gfx.LoadSkinFont("segoeui.ttf")
gfx.FillColor(255, 255, 255)
gfx.FontSize(20)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
gfx.Text(songwheel.searchStatus, 3, desh)
end
soffset = soffset * 0.8
draw_search(120, 5, 600, 40)
end
-- Callback
set_index = function(newIndex)
songData:set_index(newIndex)
songTable:set_index(newIndex)
end
-- Callback
set_diff = function(newDiff)
songData:set_difficulty(newDiff)
songTable:set_difficulty(newDiff)
end
-- force calculation
--------------------
totalForce = nil
local badgeRates = {
0.5, -- Played
1.0, -- Cleared
1.02, -- Hard clear
1.04, -- UC
1.1 -- PUC
}
local gradeRates = {
{["min"] = 9900000, ["rate"] = 1.05}, -- S
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
{["min"] = 9700000, ["rate"] = 1}, -- AAA
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
{["min"] = 8700000, ["rate"] = 0.88}, -- A
{["min"] = 7500000, ["rate"] = 0.85}, -- B
{["min"] = 6500000, ["rate"] = 0.82}, -- C
{["min"] = 0, ["rate"] = 0.8} -- D
}
calculate_force = function(diff)
if #diff.scores < 1 then
return 0
end
local score = diff.scores[1]
local badgeRate = badgeRates[diff.topBadge]
local gradeRate
for i, v in ipairs(gradeRates) do
if score.score >= v.min then
gradeRate = v.rate
break
end
end
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
end
-- callback
songs_changed = function(withAll)
if (not withAll) then return end
local diffsById = {}
local diffs = {}
for i = 1, #songwheel.allSongs do
local song = songwheel.allSongs[i]
for j = 1, #song.difficulties do
local diff = song.difficulties[j]
diff.force = calculate_force(diff)
table.insert(diffs, diff)
diffsById[diff.id] = diff
end
end
table.sort(diffs, function (l, r)
return l.force > r.force
end)
totalForce = 0
for i = 1, 50 do
if diffs[i] then
totalForce = totalForce + diffs[i].force
diffs[i].forceInTotal = true
end
end
for i = 1, #songwheel.songs do
local song = songwheel.songs[i]
for j = 1, #song.difficulties do
local diff = song.difficulties[j]
local newDiff = diffsById[diff.id]
song.difficulties[j] = newDiff
end
end
end