-- Find a key in tab with the given value. function FindValue(tab, value) for key, name in pairs(tab) do if value == name then return key end end return nil end -- Return the index of a true value in list. function FindSelection(list) for index, on in ipairs(list) do if on then return index end end return nil end -- Look up each value in a table, returning a table with the resulting strings. function TableStringLookup(t, group) local ret = { } for key, val in t do Trace(val) ret[key] = THEME:GetString(group,val) end return ret end function wrap(val,n) local x = val Trace( "wrap "..x.." "..n ) if x<0 then x = x + (math.ceil(-x/n)+1)*n end Trace( "adjusted "..x ) local ret = math.mod(x,n) Trace( "ret "..ret ) return ret end function fapproach(val, other_val, to_move) -- This does not use the (faster) C++ side version of approach because I -- don't want to find out how many themes pass a negative speed. -Kyz if val == other_val then return val -- already done! end local delta = other_val - val local sign = delta / math.abs(delta) local toMove = sign*to_move if math.abs(toMove) > math.abs(delta) then toMove = delta -- snap end val = val + toMove return val end function tableshuffle(t) local ret = {} for i=1,table.getn(t) do table.insert(ret, math.random(i), t[i]) end return ret end table.shuffle = tableshuffle function tableslice(t, num) local ret = {} for i=1,table.getn(t) do table.insert(ret, i, t[i]) end return ret end table.slice = tableslice -- add together the contents of a table function table.sum(t) local sum = 0 for i=1,#t do sum = sum + t[i] end return sum end -- average of all table values function table.average(t) return table.sum(t) / #t end -- furthest value from the average of a given table function table.deviation(t) local offset = math.abs(table.average(t)) for i=1,#t do offset = math.max(math.abs(t[i]), offset) end return offset end -- See if this exists function table.search(t, sFind) for _, v in pairs(t) do if v == sFind then return true end end return false end -- Retreive the entry that has this function table.find(t, sFind) for _, v in pairs(t) do if v == sFind then return _ end end return nil end --Get the count of all items in a table function table.itemcount(t) local i = 0 for _, v in pairs(t) do i = i+1 end return i end function math.round(num, pre) if pre and pre < 0 then pre = 0 end local mult = 10^(pre or 0) if num >= 0 then return math.floor(num*mult+.5)/mult else return math.ceil(num*mult-.5)/mult end end function math.gcd(a, b) while b ~= 0 do a, b = b, math.mod(a, b) end return a end -- deprecated? function round(val, decimal) if decimal then return math.floor((val * 10^decimal) + 0.5) / (10^decimal) else return math.floor(val+0.5) end end function GetRandomSongBackground() for i=0,50 do local song = SONGMAN:GetRandomSong() if song then local path = song:GetBackgroundPath() if path then return path end end end return THEME:GetPathG("", "_blank") end function GetSongBackground() local song = GAMESTATE:GetCurrentSong() if song then local path = song:GetBackgroundPath() if path then return path end end return THEME:GetPathG("Common","fallback background") end function StepsOrTrailToCustomDifficulty(stepsOrTrail) if lua.CheckType("Steps", stepsOrTrail) then return StepsToCustomDifficulty(stepsOrTrail) end if lua.CheckType("Trail", stepsOrTrail) then return TrailToCustomDifficulty(stepsOrTrail) end end function IsArcade() return GAMESTATE:GetCoinMode() ~= 'CoinMode_Home' end function IsHome() return GAMESTATE:GetCoinMode() == 'CoinMode_Home' end function IsFreePlay() return IsArcade() and (GAMESTATE:GetCoinMode() == 'CoinMode_Free') or false end function IsCourse() local pm = GAMESTATE:GetPlayMode(); return pm == "PlayMode_Nonstop" or "PlayMode_Oni" or "PlayMode_Endless" end function ArgsIfPlayerJoinedOrNil(arg1,arg2) if arg1==nil then arg1=arg2 elseif arg2==nil then arg2=arg1 end return (GAMESTATE:IsSideJoined(PLAYER_1) and arg1 or nil),(GAMESTATE:IsSideJoined(PLAYER_2) and arg2 or nil) end function Center1Player() local styleType = GAMESTATE:GetCurrentStyle():GetStyleType() -- always center in OnePlayerTwoSides ( Doubles ) or TwoPlayersSharedSides ( Couples ) if styleType == "StyleType_OnePlayerTwoSides" or styleType == "StyleType_TwoPlayersSharedSides" then return true -- only Center1P if Pref enabled and OnePlayerOneSide. -- (implicitly excludes Rave, Battle, Versus, Routine) elseif PREFSMAN:GetPreference("Center1Player") then return styleType == "StyleType_OnePlayerOneSide" else return false end end function IsRoutine() local style= GAMESTATE:GetCurrentStyle() if style and style:GetStyleType() == "StyleType_TwoPlayersSharedSides" then return true end return false end Date = { Today = function() return string.format("%i%02i%02i", Year(), (MonthOfYear()+1), DayOfMonth()) end } Time = { Now = function() return string.format( "%02i:%02i:%02i", Hour(), Minute(), Second() ) end } -- file utilities File = { Write = function(path,buf) local f = RageFileUtil.CreateRageFile() if f:Open(path, 2) then f:Write( tostring(buf) ) f:destroy() return true else Trace( "[FileUtils] Error writing to ".. path ..": ".. f:GetError() ) f:ClearError() f:destroy() return false end end, Read = function(path) local f = RageFileUtil.CreateRageFile() local ret = "" if f:Open(path, 1) then ret = tostring( f:Read() ) f:destroy() return ret else Trace( "[FileUtils] Error reading from ".. path ..": ".. f:GetError() ) f:ClearError() f:destroy() return nil end end } -- setenv(name,value) -- Sets aside an entry for and puts into it. -- Use a table as to store multiple values function setenv(name,value) GAMESTATE:Env()[name] = value end -- getenv(name) -- This will return whatever value is at GAMESTATE:Env()[name] function getenv(name) return GAMESTATE:Env()[name] end -- tobool(v) -- Converts v to a boolean. function tobool(v) local meta= getmetatable(v) if meta and type(meta.__tobool) == "function" then return meta.__tobool(v) elseif type(v) == "string" then local cmp = string.lower(v) if cmp == "true" or cmp == "t" then return true elseif cmp == "false" or cmp == "f" then return false end elseif type(v) == "number" then if v == 0 then return false else return true end elseif type(v) == "boolean" then return v end return nil end -- GetPlayerOrMachineProfile(pn) -- This returns a profile, preferably a player one. -- If there isn't one, we fall back on the machine profile. function GetPlayerOrMachineProfile(pn) if PROFILEMAN:IsPersistentProfile(pn) then -- player profile return PROFILEMAN:GetProfile(pn); else -- machine profile return PROFILEMAN:GetMachineProfile(); end; end; function pname(pn) return ToEnumShortString(pn) end function ThemeManager:GetAbsolutePath(sPath) sFinPath = "/Themes/"..self:GetCurThemeName().."/"..sPath assert(RageFileManager.DoesFileExist(sFinPath), "the theme element "..sPath.." is missing") return sFinPath end -- supported aspect ratios AspectRatios = { ThreeFour = 0.75, -- (576x760 at 1024x768; meant for rotated monitors?) OneOne = 1.0, -- 480x480 (uses Y value of specified resolution) FiveFour = 1.25, -- 600x480 (1280x1024 is a real use case) FourThree = 1.33333, -- 640x480 (common) SixteenTen = 1.6, -- 720x480 (common) SixteenNine = 1.77778, -- 853x480 (common) EightThree = 2.66666, -- 1280x480 (two monitors) TwentyOneNine= 2.37, -- 1138x480 (Ultrawide) NineSixteen = 0.5625, -- 480x720 (16:9 widescreen as portrait display) } function WideScale(AR4_3, AR16_9) return scale( SCREEN_WIDTH, 640, 854, AR4_3, AR16_9 ) end local function round(num, idp) if idp and idp > 0 then local mult = 10 ^ idp; return math.floor(num * mult + 0.5) / mult; end; return math.floor(num + 0.5); end function IsUsingWideScreen() local curAspect = GetScreenAspectRatio() return curAspect > 16/10 - .044 end -- Usage: Pass in an ActorFrame and a string to put in front of every line. -- indent will be appended to at each level of the recursion, to indent each -- generation further. function rec_print_children(parent, indent) if not indent then indent= "" end if #parent > 0 and type(parent) == "table" then for i, c in ipairs(parent) do rec_print_children(c, indent .. i .. "->") end elseif parent.GetChildren then local pname= (parent.GetName and parent:GetName()) or "" local children= parent:GetChildren() Trace(indent .. pname .. " children:") for k, v in pairs(children) do if #v > 0 then Trace(indent .. pname .. "->" .. k .. " shared name:") rec_print_children(v, indent .. pname .. "->") Trace(indent .. pname .. "->" .. k .. " shared name over.") else rec_print_children(v, indent .. pname .. "->") end end Trace(indent .. pname .. " children over.") else local pname= (parent.GetName and parent:GetName()) or "" Trace(indent .. pname .. "(" .. tostring(parent) .. ")") end end -- Usage: Pass in a table and a string to indent each line with. -- indent will be appended to at each level of the recursion, to indent each -- generation further. -- DO NOT pass in a table that contains a reference loop. -- A reference loop is a case where a table contains a member that is a -- reference to itself, or contains a table that contains a reference to -- itself. -- Short reference loop example: a= {} a[1]= a -- Longer reference loop example: a= {b= {c= {}}} a.b.c[1]= a function rec_print_table(t, indent, depth_remaining) if not indent then indent= "" end if type(t) ~= "table" then Trace(indent .. "rec_print_table passed a " .. type(t)) return end depth_remaining= depth_remaining or -1 if depth_remaining == 0 then return end for k, v in pairs(t) do if type(v) == "table" then Trace(indent .. k .. ": table") rec_print_table(v, indent .. " ", depth_remaining - 1) else Trace(indent .. "(" .. type(k) .. ")" .. k .. ": " .. "(" .. type(v) .. ")" .. tostring(v)) end end Trace(indent .. "end") end function rec_count_children(parent) local total= 1 if #parent > 0 and type(parent) == "table" then for i, c in ipairs(parent) do total= total + rec_count_children(c) end elseif parent.GetChildren then local pname= (parent.GetName and parent:GetName()) or "" local children= parent:GetChildren() for k, v in pairs(children) do if #v > 0 then total= total + rec_count_children(v) else total= total + rec_count_children(v) end end end return total end -- Minor text formatting functions from Kyzentun. -- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. -- Intentionally undocumented because they should be moved to BitmapText ASAP function width_limit_text(text, limit, natural_zoom) natural_zoom= natural_zoom or 1 if text:GetWidth() * natural_zoom > limit then text:zoomx(limit / text:GetWidth()) else text:zoomx(natural_zoom) end end function width_clip_text(text, limit) local full_text= text:GetText() local fits= text:GetZoomedWidth() <= limit local prev_max= #full_text - 1 local prev_min= 0 if not fits then while prev_max - prev_min > 1 do local new_max= math.round((prev_max + prev_min) / 2) text:settext(full_text:sub(1, 1+new_max)) if text:GetZoomedWidth() <= limit then prev_min= new_max else prev_max= new_max end end text:settext(full_text:sub(1, 1+prev_min)) end end function width_clip_limit_text(text, limit, natural_zoom) natural_zoom= natural_zoom or text:GetZoomY() local text_width= text:GetWidth() * natural_zoom if text_width > limit * 2 then text:zoomx(natural_zoom * .5) width_clip_text(text, limit) else width_limit_text(text, limit, natural_zoom) end end function convert_text_to_indented_lines(text, indent, width, text_zoom) local text_as_lines= split("\n", text:GetText()) local indented_lines= {} for i, line in ipairs(text_as_lines) do local remain= line local sub_lines= 0 repeat text:settext(remain) local clipped= false local indent_mult= 0 if i > 1 then indent_mult= indent_mult + 1 end if sub_lines > 0 then indent_mult= 2 end -- On larger resolutions, the font can be squished a bit to fit more on a line. local resolution_width_mult= math.max(1, DISPLAY:GetDisplayHeight() / 720) local usable_width= (width - (indent * indent_mult)) * resolution_width_mult if text:GetWidth() * text_zoom > usable_width then clipped= true width_clip_text(text, usable_width) end indented_lines[#indented_lines+1]= { indent_mult, text:GetText()} remain= remain:sub(#text:GetText()+1) sub_lines= sub_lines + 1 until not clipped end return indented_lines end -- (c) 2005-2011 Glenn Maynard, Chris Danford, SSC -- All rights reserved. -- -- Permission is hereby granted, free of charge, to any person obtaining a -- copy of this software and associated documentation files (the -- "Software"), to deal in the Software without restriction, including -- without limitation the rights to use, copy, modify, merge, publish, -- distribute, and/or sell copies of the Software, and to permit persons to -- whom the Software is furnished to do so, provided that the above -- copyright notice(s) and this permission notice appear in all copies of -- the Software and that both the above copyright notice(s) and this -- permission notice appear in supporting documentation. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF -- THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS -- INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT -- OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -- PERFORMANCE OF THIS SOFTWARE.