diff --git a/Custom Characters Readme.txt b/Custom Characters Readme.txt deleted file mode 100644 index 2d0f85ba..00000000 --- a/Custom Characters Readme.txt +++ /dev/null @@ -1,47 +0,0 @@ -Adding/modding characters: - -Step by step instructions for adding/modding characters (Windows example): - -1. Press the Windows key then type "%appdata%" without quotes hit enter. - -2. See the folders located in: %appdata%\Panel Attack\characters\__Stock PdP_TA\ for a reference of where your assets should go and what files should be named. - -Note: folders starting with "__" will be ignored upon loading. You may choose to remove those "__" to mod existing characters - -3. Create a folder with your character id. That id should be specific enough so that people downloading your character don't struggle with merge. We recommend using a short suffix. e.g. pichu_gbc - -Note: while playing online, characters will be looked up by id then by display name as a fallback - -4. Place assets, sounds and txt file in that folder with the proper names to add your data. Exhaustive list below. - -5. Set the "Use default characters" option to Off. - -6. Add/update characters.txt file in the assets folder. All characters will be loaded. This file specifies which ones get to be displayed in the lobby. - -~~~~ Exhaustive list of a character folder data! ~~~~ - -- name.txt: display name of this character, this value will be displayed in the lobby and will also serve as a fallback when trying to match your opponent's character -- stage.txt: stage for this character, this feature is currently being improved, please wait a bit more! -- topleft.png, botleft.png, topright.png, botright.png, top.png, bot.png, left.png, right.png, face.png, pop.png, doubleface.png, filler1.png, filler2.png, flash.png: assets for garbages -- portrait.png, icon.png: display of your character ingame and in the lobby - -Note: The following extensions are supported for all sound files: .mp3, .ogg, .it. Optional SFX are in parenthesis - -- Combo: "combo" (,"combo2", "combo3"...): [selected at random if more than one] -- Six metal blocks combo: ("combo_echo", ("combo_echo2", "combo_echo3"...)) [selected at random if more than one] -- Chain: depending on the current chain length, the appopriate sound file will be played: - x2/3 plays "chain", x4 plays ("chain2"), x5 plays ("chain_echo"), x6+ plays ("chain2_echo") -- Clear garbage: ("garbage_match" (,"garbage_match2", "garbage_match3"...)) [selected at random if more than one] -- Selection: ("selection", ("selection2", "selection3"...)) [selected at random if more than one] -- Win: ("win"(, "win2", "win3"...)) [selected at random if more than one] - -Note: providing just a "chain" or just a "combo" SFX is OK. It would get used for all combos and chains. - -Music files should be named "normal_music" and "danger_music" -If your music has an intro, cut it from the main music file, and name it "normal_music_start" or "danger_music_start" -The game looks for music in the following folders, in this order: - 1. "%appdata%/Panel Attack/characters/[character id]" - 2. "%appdata%/Panel Attack/sounds/[chosen sound pack]/music/[character's stage name]" - 3. "[built-in sounds directory]/music/[character's stage name]" - - Note: when searching for a sound file, it will stop at the first folder containing the searched file. \ No newline at end of file diff --git a/Custom Graphics Readme.txt b/Custom Graphics Readme.txt deleted file mode 100644 index 2d22aa9e..00000000 --- a/Custom Graphics Readme.txt +++ /dev/null @@ -1,30 +0,0 @@ -Using custom graphics (assets): - -If your custom assets folder is missing files, that's fine. -Panel Attack will just use the stock assets for the missing files. - -Step by step instructions for using custom graphics (Windows example): - -1. Go to any Windows explorer window (my computer, my documents, my pictures, etc). - -2. Type "%appdata%" without quotes into the address bar and hit enter. - -3. See the folders: %appdata%\Panel Attack\assets\__Stock PdP_TA - and %appdata%\Panel Attack\panels\__Stock PdP_TA - for a reference of where your assets should go and what files should be named. - -Note: folders starting with "__" will be ignored upon loading. - -4. Make a folder in your game saves directory like this: - %appdata%\Panel Attack\assets\[your_set_name_here] - or %appdata%\Panel Attack\panels\[your_set_name_here] - -5. Place assets in that folder with the proper file names - and file paths of the assets you want to replace. - -6. Restart Panel Attack. Go into the options menu. Select the graphics/panels set with - the name you provided in step 4 (having multiple custom graphics sets is supported). - The panels sets will also show in the lobby as per usual. - -Note: if you would like to display a different character name for a custom character, -include a name.txt file in that character's folder. (but don't change the folder name!) diff --git a/Custom Sounds Readme.txt b/Custom Sounds Readme.txt deleted file mode 100644 index f4b3652e..00000000 --- a/Custom Sounds Readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -About Custom Sounds in Panel Attack - -The following extensions are supported: .mp3, .ogg, .it. - -File and folder names are Case-SeNsItIvE [it's mostly all lower-case, except "SFX"] - -Sound Effects (aka SFX) go in: "%appdata%/Panel Attack/sounds/[sound_pack_name_here]/SFX" -and file names should be: - Game SFX: "countdown" "go" "move", "swap", "land", "game_over" - Fanfares: "fanfare1", "fanfare2", "fanfare3" - Garbage Thuds: "thud_1", "thud_2", "thud_3" - Menu: "menu_move", "menu_validate", "menu_cancel" - Panel pops: "pop1-1", "pop1-2", ..., "pop1-10", "pop2-1", ..., "pop2-10", ..., "pop4-10" diff --git a/analytics.lua b/analytics.lua index 9d9a97eb..3f5eaac2 100644 --- a/analytics.lua +++ b/analytics.lua @@ -1,6 +1,10 @@ -local analytics_version = 2 +local analytics = {} -analytics = { +local analytics_version = 3 + +local analytic_data_cap = 999999 -- prevents overflow + +local analytics_data = { -- The lastly used version version = analytics_version, @@ -10,6 +14,10 @@ analytics = { destroyed_panels = 0, -- the amount of sent garbage sent_garbage_lines = 0, + -- the amount of times the cursor was moved + move_count = 0, + -- the amount of times the panels were swapped + swap_count = 0, -- 1 to 12, then 13+, 1 is obviously meaningless reached_chains = { }, -- 1 to 40, 1 to 3 being meaningless @@ -22,6 +30,10 @@ analytics = { destroyed_panels = 0, -- the amount of sent garbage sent_garbage_lines = 0, + -- the amount of times the cursor was moved + move_count = 0, + -- the amount of times the panels were swapped + swap_count = 0, -- 1 to 12, then 13+, 1 is obviously meaningless reached_chains = { }, -- 1 to 40, 1 to 3 being meaningless @@ -32,6 +44,8 @@ analytics = { local function analytic_clear(analytic) analytic.destroyed_panels = 0 analytic.sent_garbage_lines = 0 + analytic.move_count = 0 + analytic.swap_count = 0 analytic.reached_chains = {} analytic.used_combos = {} end @@ -69,27 +83,49 @@ local function refresh_sent_garbage_lines(analytic) analytic.sent_garbage_lines = sent_garbage_lines_count end -function analytics_init() pcall(function() +function analytics.init() pcall(function() if not config.enable_analytics then return end - local file = love.filesystem.newFile("analytics.json") - file:open("r") + local read_data = {} + local analytics_file, err = love.filesystem.newFile("analytics.json", "r") + if analytics_file then + local teh_json = analytics_file:read(analytics_file:getSize()) + for k,v in pairs(json.decode(teh_json)) do + read_data[k] = v + end + end - local teh_json = file:read(file:getSize()) - for k,v in pairs(json.decode(teh_json)) do - analytics[k] = v + if read_data.version and type(read_data.version) == "number" then + analytics_data.version = read_data.version + end + local analytics_filters = { "last_game", "overall" } + local number_params = { "destroyed_panels", "sent_garbage_lines", "move_count", "swap_count" } + local table_params = { "reached_chains", "used_combos" } + for _,analytic in pairs(analytics_filters) do + if read_data[analytic] and type(read_data[analytic]) == "table" then + for n,param in pairs(number_params) do + if read_data[analytic][param] and type(read_data[analytic][param]) == "number" then + analytics_data[analytic][param] = math.min(read_data[analytic][param],analytic_data_cap) + end + end + for m,param in pairs(table_params) do + if read_data[analytic][param] and type(read_data[analytic][param]) == "table" then + analytics_data[analytic][param] = read_data[analytic][param] + end + end + end end - analytic_clear(analytics.last_game) + analytic_clear(analytics_data.last_game) -- do stuff regarding version compatibility here, before we patch it - if analytics.version < 2 then - refresh_sent_garbage_lines(analytics.overall) + if analytics_data.version < 2 then + refresh_sent_garbage_lines(analytics_data.overall) end - analytics.version = analytics_version + analytics_data.version = analytics_version file:close() end) end @@ -98,13 +134,15 @@ local function output_pretty_analytics() pcall(function() return end - local analytics_filters = { analytics.last_game, analytics.overall } + local analytics_filters = { analytics_data.last_game, analytics_data.overall } local titles = { "Last game\n-------------------------------------\n", "Overall\n-------------------------------------\n" } local text = "" for i,analytic in pairs(analytics_filters) do text = text..titles[i] text = text.."Destroyed "..analytic.destroyed_panels.." panels.\n" text = text.."Sent "..analytic.sent_garbage_lines.." lines of garbage.\n" + text = text.."Moved "..analytic.move_count.." times.\n" + text = text.."Swapped "..analytic.swap_count.." times.\n" text = text.."Performed combos:\n" for k,v in pairs(analytic.used_combos) do if k then @@ -127,91 +165,121 @@ local function output_pretty_analytics() pcall(function() end) end -function analytics_draw(x,y) +function analytics.draw(x,y) if not config.enable_analytics then return end - gprint("Panels destroyed: "..analytics.last_game.destroyed_panels, x, y) + gprint("Panels destroyed: "..analytics_data.last_game.destroyed_panels, x, y) + y = y+15 + + gprint("Sent garbage lines: "..analytics_data.last_game.sent_garbage_lines, x, y) + y = y+15 + + gprint("Moved "..analytics_data.last_game.move_count.." times", x, y) y = y+15 - gprint("Sent garbage lines: "..analytics.last_game.sent_garbage_lines, x, y) + gprint("Swapped "..analytics_data.last_game.swap_count.." times", x, y) y = y+15 local ycombo = y for i=2,13 do - local chain_amount = analytics.last_game.reached_chains[i] or 0 + local chain_amount = analytics_data.last_game.reached_chains[i] or 0 gprint("x"..i..": "..chain_amount, x, y) y = y+15 end - local chain_above_13 = compute_above_13(analytics.last_game) + local chain_above_13 = compute_above_13(analytics_data.last_game) gprint("x?: "..chain_above_13, x, y) local xcombo = x + 50 for i=4,15 do - local combo_amount = analytics.last_game.used_combos[i] or 0 + local combo_amount = analytics_data.last_game.used_combos[i] or 0 gprint("c"..i..": "..combo_amount, xcombo, ycombo) ycombo = ycombo+15 end end -function write_analytics_files() pcall(function() +local function write_analytics_files() pcall(function() if not config.enable_analytics then return end local file = love.filesystem.newFile("analytics.json") file:open("w") - file:write(json.encode(analytics)) + file:write(json.encode(analytics_data)) file:close() output_pretty_analytics() end) end -function analytics_register_destroyed_panels(amount) +function analytics.register_destroyed_panels(amount) if not config.enable_analytics then return end - local analytics_filters = { analytics.last_game, analytics.overall } + local analytics_filters = { analytics_data.last_game, analytics_data.overall } for _,analytic in pairs(analytics_filters) do analytic.destroyed_panels = analytic.destroyed_panels + amount if amount > 3 then if not analytic.used_combos[amount] then analytic.used_combos[amount] = 1 else - analytic.used_combos[amount] = analytic.used_combos[amount] + 1 + analytic.used_combos[amount] = math.min(analytic.used_combos[amount]+1,analytic_data_cap) end analytic.sent_garbage_lines = analytic.sent_garbage_lines + amount_of_garbages_lines_per_combo[amount] end end end -function analytics_register_chain(size) +function analytics.register_chain(size) if not config.enable_analytics then return end local max_size = math.min(size, 13) - local analytics_filters = { analytics.last_game, analytics.overall } + local analytics_filters = { analytics_data.last_game, analytics_data.overall } for _,analytic in pairs(analytics_filters) do if not analytic.reached_chains[size] then analytic.reached_chains[size] = 1 else - analytic.reached_chains[size] = analytic.reached_chains[size]+1 + analytic.reached_chains[size] = math.min(analytic.reached_chains[size]+1,analytic_data_cap) end size = math.min(size, 13) analytic.sent_garbage_lines = analytic.sent_garbage_lines + (max_size-1) end end -function analytics_game_ends() +function analytics.register_swap() + if not config.enable_analytics then + return + end + + local analytics_filters = { analytics_data.last_game, analytics_data.overall } + for _,analytic in pairs(analytics_filters) do + analytic.swap_count = math.min(analytic.swap_count + 1,analytic_data_cap) + end +end + +function analytics.register_move() + if not config.enable_analytics then + return + end + + local analytics_filters = { analytics_data.last_game, analytics_data.overall } + for _,analytic in pairs(analytics_filters) do + analytic.move_count = math.min(analytic.move_count + 1,analytic_data_cap) + end +end + +function analytics.game_ends() if not config.enable_analytics then return end write_analytics_files() - analytic_clear(analytics.last_game) -end \ No newline at end of file + analytic_clear(analytics_data.last_game) +end + +return analytics \ No newline at end of file diff --git a/assets/Stock PdP_TA/blueend10.png b/assets/Stock PdP_TA/blueend10.png deleted file mode 100644 index bd31c119..00000000 Binary files a/assets/Stock PdP_TA/blueend10.png and /dev/null differ diff --git a/assets/Stock PdP_TA/blueend11.png b/assets/Stock PdP_TA/blueend11.png deleted file mode 100644 index 6a20405e..00000000 Binary files a/assets/Stock PdP_TA/blueend11.png and /dev/null differ diff --git a/assets/Stock PdP_TA/blueface1.png b/assets/Stock PdP_TA/blueface1.png deleted file mode 100644 index ca13e1c7..00000000 Binary files a/assets/Stock PdP_TA/blueface1.png and /dev/null differ diff --git a/assets/Stock PdP_TA/bluemid1.png b/assets/Stock PdP_TA/bluemid1.png deleted file mode 100644 index 2142499e..00000000 Binary files a/assets/Stock PdP_TA/bluemid1.png and /dev/null differ diff --git a/assets/Stock PdP_TA/bluepop.png b/assets/Stock PdP_TA/bluepop.png deleted file mode 100644 index 93ce2121..00000000 Binary files a/assets/Stock PdP_TA/bluepop.png and /dev/null differ diff --git a/assets/Stock PdP_TA/char_sel_cursors.png b/assets/Stock PdP_TA/char_sel_cursors.png deleted file mode 100644 index 67080873..00000000 Binary files a/assets/Stock PdP_TA/char_sel_cursors.png and /dev/null differ diff --git a/assets/Stock PdP_TA/garbage.png b/assets/Stock PdP_TA/garbage.png deleted file mode 100644 index 9894af52..00000000 Binary files a/assets/Stock PdP_TA/garbage.png and /dev/null differ diff --git a/assets/Stock PdP_TA/menu/title.png b/assets/Stock PdP_TA/menu/title.png deleted file mode 100644 index 8284cdd7..00000000 Binary files a/assets/Stock PdP_TA/menu/title.png and /dev/null differ diff --git a/assets/Stock PdP_TA/redend10.png b/assets/Stock PdP_TA/redend10.png deleted file mode 100644 index 049fe756..00000000 Binary files a/assets/Stock PdP_TA/redend10.png and /dev/null differ diff --git a/assets/Stock PdP_TA/redend11.png b/assets/Stock PdP_TA/redend11.png deleted file mode 100644 index 7cf38fe0..00000000 Binary files a/assets/Stock PdP_TA/redend11.png and /dev/null differ diff --git a/assets/Stock PdP_TA/redface1.png b/assets/Stock PdP_TA/redface1.png deleted file mode 100644 index 010b43d1..00000000 Binary files a/assets/Stock PdP_TA/redface1.png and /dev/null differ diff --git a/assets/Stock PdP_TA/redmid1.png b/assets/Stock PdP_TA/redmid1.png deleted file mode 100644 index c6d1d729..00000000 Binary files a/assets/Stock PdP_TA/redmid1.png and /dev/null differ diff --git a/assets/Stock PdP_TA/redpop.png b/assets/Stock PdP_TA/redpop.png deleted file mode 100644 index 73e70053..00000000 Binary files a/assets/Stock PdP_TA/redpop.png and /dev/null differ diff --git a/assets/Stock PdP_TA/stages/1.png b/assets/Stock PdP_TA/stages/1.png deleted file mode 100644 index 6356ca6a..00000000 Binary files a/assets/Stock PdP_TA/stages/1.png and /dev/null differ diff --git a/auto_updater/README.txt b/auto_updater/README.txt new file mode 100644 index 00000000..e011a380 --- /dev/null +++ b/auto_updater/README.txt @@ -0,0 +1,9 @@ +Here is the process to publish the auto_updater: +- configure UPDATER_NAME in main.lua +- configure updater_config.lua with the server_url +- copy the last version of the game here in this folder "any-name.love" +- make the exe auto_updater, publish with a name like "panel.zip" or "panel-beta.zip", ... + +Here is the process to release a version of the game: +- make a love file +- upload it to the server with a name like "panel-beta-DATE_HOUR.love" \ No newline at end of file diff --git a/auto_updater/_config.lua b/auto_updater/_config.lua new file mode 100644 index 00000000..8dc5ef3b --- /dev/null +++ b/auto_updater/_config.lua @@ -0,0 +1,7 @@ +updater_config = { + auto_update= true, + launch_check_interval= 6 * 3600, -- in seconds (default 6h) will check for updates at launch if interval has passed (it will always check for updates ingame in background) + launch_check_timeout= 0.4, -- in seconds (default 400ms) time before aborting the check for updates request at launch, it will always take all the time needed ingame in background + server_url= "http://panelattack.com/updates", + force_version= "", -- ex: "panel-2019-11-17.love" +} \ No newline at end of file diff --git a/auto_updater/class.lua b/auto_updater/class.lua new file mode 100644 index 00000000..8f93dc77 --- /dev/null +++ b/auto_updater/class.lua @@ -0,0 +1,12 @@ +function class(init) + local c,mt = {},{} + c.__index = c + mt.__call = function(class_tbl, ...) + local obj = {} + setmetatable(obj,c) + init(obj,...) + return obj + end + setmetatable(c, mt) + return c +end diff --git a/auto_updater/conf.lua b/auto_updater/conf.lua new file mode 100644 index 00000000..cca3703c --- /dev/null +++ b/auto_updater/conf.lua @@ -0,0 +1,52 @@ +function love.conf(t) + t.identity = "Panel Attack" -- The name of the save directory (string) + t.appendidentity = false -- Search files in source directory before save directory (boolean) + t.version = "11.3" -- The LÖVE version this game was made for (string) + t.console = false -- Attach a console (boolean, Windows only) + t.accelerometerjoystick = false -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) + t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) + t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean) + + t.audio.mic = false -- Request and use microphone capabilities in Android (boolean) + t.audio.mixwithsystem = false -- Keep background music playing when opening LOVE (boolean, iOS and Android only) + + t.window.title = "Panel Attack - Auto Updater" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 800 -- The window width (number) + t.window.height = 600 -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = false -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) + t.window.usedpiscale = true -- Enable automatic DPI scaling (boolean) + t.window.vsync = 1 -- Vertical sync mode (number) + t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.depth = nil -- The number of bits per sample in the depth buffer + t.window.stencil = nil -- The number of bits per sample in the stencil buffer + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + t.window = nil + + t.modules.audio = false -- Enable the audio module (boolean) + t.modules.data = false -- Enable the data module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.font = true -- Enable the font module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = false -- Enable the image module (boolean) + t.modules.joystick = false -- Enable the joystick module (boolean) + t.modules.keyboard = false -- Enable the keyboard module (boolean) + t.modules.math = false -- Enable the math module (boolean) + t.modules.mouse = false -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = false -- Enable the sound module (boolean) + t.modules.system = false -- Enable the system module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) + t.modules.timer = false -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update + t.modules.touch = false -- Enable the touch module (boolean) + t.modules.video = false -- Enable the video module (boolean) + t.modules.window = true -- Enable the window module (boolean) +end \ No newline at end of file diff --git a/auto_updater/game_updater.lua b/auto_updater/game_updater.lua new file mode 100644 index 00000000..fd799ccd --- /dev/null +++ b/auto_updater/game_updater.lua @@ -0,0 +1,74 @@ +local socket = require("socket") +local http = require("socket.http") +require("class") + +GameUpdater = class(function(self, name) + if not name then error("GameUpdater: you need to specify a name") end + + self.path = "updater/"..name.."/" + if not love.filesystem.getInfo(self.path) then love.filesystem.createDirectory(self.path) end + if not love.filesystem.getInfo(self.path.."config.lua") then love.filesystem.write(self.path.."config.lua", love.filesystem.read("_config.lua")) end + + self.name = name + self.thread = nil + require("_config") + self.config = updater_config + package.loaded["_config"] = nil + require("updater."..name..".config") + if type(updater_config) == "table" then + for k,v in pairs(updater_config) do + if type(v) == type(self.config[k]) then + self.config[k] = v + end + end + end + + self.version_file = self.path..".version" + self.local_version = love.filesystem.read(self.version_file) + self.timestamp_file = self.path..".timestamp" + self.check_timestamp = love.filesystem.read(self.timestamp_file) + + self.has_local_version = false + if self.local_version and love.filesystem.getInfo(self.path..self.local_version) then + self.has_local_version = true + end + + end) + +function GameUpdater.async(self, function_name, ...) + if self.thread and self.thread:isRunning() then error ("GameUpdater: a thread is already running") end + self.thread = love.thread.newThread("game_updater_thread.lua") + self.thread:start(function_name, ...) + return love.thread.getChannel(function_name) +end + +-- returns thread channel [async returns sorted list of all versions available on the server] +function GameUpdater.async_download_available_versions(self, max_size) + return self:async("download_available_versions", self.config.server_url, self.config.launch_check_timeout, max_size, self.timestamp_file) +end + +-- returns thread channel [async returns bool download success] +function GameUpdater.async_download_file(self, filename) + return self:async("download_file", self.config.server_url.."/"..filename, self.path..filename) +end + +function GameUpdater.async_download_latest_version(self) + if not self.config.auto_update then return end + return self:async("download_lastest_version", self.config.server_url, self.path, self.version_file, self.local_version) +end + +function GameUpdater.get_version(self) + if self.has_local_version then + return self.local_version + else + return nil + end +end + +function GameUpdater.change_version(self, filename) + if filename and love.filesystem.getInfo(self.path..filename) then + love.filesystem.write(self.version_file, filename) + return true + end + return false +end \ No newline at end of file diff --git a/auto_updater/game_updater_thread.lua b/auto_updater/game_updater_thread.lua new file mode 100644 index 00000000..d6ffbdcb --- /dev/null +++ b/auto_updater/game_updater_thread.lua @@ -0,0 +1,86 @@ +local socket = require("socket") +local http = require("socket.http") +require("love.timer") + + +function download_available_versions(server_url, timeout, max_size, timestamp_file) + local body = "" + local all_versions = {} + + http.request{ + url=server_url, + create=function() + local req_sock = socket.tcp() + -- note the second parameter here + if timeout then req_sock:settimeout(timeout, 't') end + + return req_sock + end, + sink=function(chunk, err) + if chunk == nil then return false end + + body = body..chunk + return max_size == nil or #body < max_size + end + } + + if body ~= "" then + for w in body:gmatch('') do + all_versions[#all_versions+1] = w:gsub("^/[/%w_-]+/", "") + end + end + + if #all_versions > 0 then + table.sort(all_versions, function(a,b) return a>b end) + for i=1,#all_versions do + all_versions[i] = all_versions[i]..'.love' + end + + if timestamp_file then + love.filesystem.write(timestamp_file, os.time()) + end + end + + love.thread.getChannel("download_available_versions"):push(all_versions) +end + +function download_file(server_filepath, local_filepath) + local body = http.request(server_filepath) + love.filesystem.write(local_filepath, body) + + love.thread.getChannel("download_file"):push(true) +end + +function download_lastest_version(server_url, local_path, version_file, local_version) + download_available_versions(server_url, nil, nil) + local versions = nil + while versions == nil do + versions = love.thread.getChannel("download_available_versions"):pop() + love.timer.sleep(0.2) + end + if #versions > 0 and versions[1] ~= local_version then + download_file(server_url.."/"..versions[1], local_path..versions[1]) + local downloaded = nil + while downloaded == nil do + downloaded = love.thread.getChannel("download_file"):pop() + love.timer.sleep(0.2) + end + + if downloaded then + love.filesystem.write(version_file, versions[1]) + love.thread.getChannel("download_lastest_version"):push(true) + return + end + end + love.thread.getChannel("download_lastest_version"):push(false) +end + + +local ftable = { + download_available_versions= download_available_versions, + download_file= download_file, + download_lastest_version= download_lastest_version, + +} + +ftable[select(1, ...)](select(2, ...)) \ No newline at end of file diff --git a/auto_updater/main.lua b/auto_updater/main.lua new file mode 100644 index 00000000..f0602b59 --- /dev/null +++ b/auto_updater/main.lua @@ -0,0 +1,147 @@ +require("game_updater") + +-- CONSTANTS +local UPDATER_NAME = "panel" -- you should name the distributed auto updater zip the same as this +-- use a different name for the different versions of the updater +-- ex: "panel" for the release, "panel-beta" for the main beta, "panel-exmode" for testing the EX Mode +local MAX_REQ_SIZE = 100000 -- 100kB + +-- GLOBALS +GAME_UPDATER = GameUpdater(UPDATER_NAME) +GAME_UPDATER_GAME_VERSION = nil +GAME_UPDATER_CHECK_UPDATE_INGAME = (GAME_UPDATER.config.force_version == "") + +-- VARS +local path = GAME_UPDATER.path +local local_version = GAME_UPDATER:get_version() +local top_version = nil +local messages = "" +local wait_messages = 0 +local next_step = "" +local wait_all_versions = nil +local wait_download = nil + +function love.load() + -- Cleanup old love files + for i, v in ipairs(love.filesystem.getDirectoryItems(path)) do + if v ~= local_version and v:match('%.love$') then + love.filesystem.remove(path..v) + end + end + -- should we check for an update? + if GAME_UPDATER.config.auto_update and not ( + GAME_UPDATER.check_timestamp ~= nil + and os.time() < GAME_UPDATER.check_timestamp + GAME_UPDATER.config.launch_check_interval + and local_version + and (GAME_UPDATER.config.force_version == "" or GAME_UPDATER.config.force_version == local_version)) then + + -- Check for a version online + wait_all_versions = GAME_UPDATER:async_download_available_versions(MAX_REQ_SIZE) + end + + next_step = "check_versions" +end + +function love.update(dt) + if wait_messages > 0 then + wait_messages = wait_messages - 1 + return + end + + local all_versions = nil + if wait_all_versions ~= nil then + all_versions = wait_all_versions:pop() + end + + if next_step == "check_versions" and wait_all_versions == nil or all_versions ~= nil then + if all_versions and #all_versions > 0 then + top_version = nil + if GAME_UPDATER.config.force_version ~= "" then + for i, v in ipairs(all_versions) do + if GAME_UPDATER.config.force_version == v then + top_version = v + break + end + end + if top_version == nil then + local err = 'Could not find online version: "'..GAME_UPDATER.config.force_version..'" (force_version)\nAvailable versions are:\n' + for i, v in ipairs(all_versions) do err = err..v.."\n" end + error(err) + end + + else + top_version = all_versions[1] + end + + GAME_UPDATER_CHECK_UPDATE_INGAME = false + + if top_version == local_version then + start_game(local_version) + elseif local_version == nil and top_version == get_embedded_version() then + display_message("Copying embedded version...\n") + next_step = "copy_embedded" + else + display_message("A new version of the game has been found:\n"..top_version.."\nDownloading...\n") + wait_download = GAME_UPDATER:async_download_file(top_version) + next_step = "download_game" + end + + -- Check for a version locally + elseif local_version then + if GAME_UPDATER.config.force_version ~= "" and GAME_UPDATER.config.force_version ~= local_version then + error('Could not find local version: "'..GAME_UPDATER.config.force_version..'" (force_version)\nPlease connect to the internet and restart the game.') + end + start_game(local_version) + + -- Fallback use embedded version + else + display_message("Could not connect to the internet.\nCopying embedded version...\n") + next_step = "copy_embedded" + top_version = get_embedded_version() + + if top_version == nil then + error('Could not find an embedded version of the game\nPlease connect to the internet and restart the game.') + end + end + + elseif next_step == "copy_embedded" then + love.filesystem.write(path..top_version, love.filesystem.read(top_version)) + GAME_UPDATER:change_version(top_version) + start_game(top_version) + + elseif next_step == "download_game" then + local ret = wait_download:pop() + if ret ~= nil then + if not ret then error("Could not download and save: "..top_version) end + GAME_UPDATER:change_version(top_version) + start_game(top_version) + end + end +end + +function love.draw() + love.graphics.print(messages, 10, 10) +end + +function start_game(file) + if not love.filesystem.mount(path..file, '') then error("Could not mount game file: "..file) end + GAME_UPDATER_GAME_VERSION = file:gsub("^panel%-", ""):gsub("%.love", "") + package.loaded.main = nil + package.loaded.conf = nil + love.conf = nil + love.init() + love.load(args) +end + +function display_message(txt) + if not love.window.isOpen() then love.window.setMode(800, 600) end + messages = messages..txt + wait_messages = 10 +end + +function get_embedded_version() + for i, v in ipairs(love.filesystem.getDirectoryItems("")) do + if v:match('%.love$') then return v end + end + return nil +end \ No newline at end of file diff --git a/character.lua b/character.lua index 67c4fe83..50fb13a0 100644 --- a/character.lua +++ b/character.lua @@ -1,190 +1,121 @@ require("character_loader") - -- Stuff defined in this file: - -- . the data structure that store a character's data -local min, pairs, deepcpy = math.min, pairs, deepcpy -local max = math.max + -- Stuff defined in this file: the data structure that store a character's data -local basic_character_images = { "icon" } -local other_character_images = {"topleft", "botleft", "topright", "botright", +local basic_images = { "icon" } +local other_images = {"topleft", "botleft", "topright", "botright", "top", "bot", "left", "right", "face", "pop", "doubleface", "filler1", "filler2", "flash", - "portrait"} - -local required_char_SFX = {"chain", "combo"} -local required_char_music = {"normal_music"} - - -- @CardsOfTheHeart says there are 4 chain sfx: --x2/x3, --x4, --x5 is x2/x3 with an echo effect, --x6+ is x4 with an echo effect - -- combo sounds, on the other hand, can have multiple variations, hence combo, combo2, combo3 (...) and combo_echo, combo_echo2... -local basic_characters_sfx = {"selection"} -local other_characters_sfx = {"chain", "combo", "combo_echo", "chain_echo", "chain2" ,"chain2_echo", "garbage_match", "win"} -local basic_characters_musics = {} -local other_characters_musics = {"normal_music", "danger_music", "normal_music_start", "danger_music_start"} - -local default_stages = {lip="flower", windy="wind", sherbet="ice", thiana="forest", ruby="jewel", - elias="water", flare="fire", neris="sea", seren="moon", phoenix="cave", - dragon="cave", thanatos="king", cordelia="cordelia", lakitu="wind", - bumpty="ice", poochy="forest", wiggler="jewel", froggy="water", blargg="fire", - lungefish="sea", raphael="moon", yoshi="yoshi", hookbill="cave", - navalpiranha="cave", kamek="cave", bowser="king"} - -Character = class(function(s, id) - s.id = id -- string | id of the character, is also the name of its folder - s.display_name = id -- string | display name of the character - s.favorite_stage = default_stages[id] -- string | id of the character, is also the name of its folder - s.images = {} - s.sounds = { combos = {}, combo_echos = {}, selections = {}, wins = {}, garbage_matches = {}, others = {} } - s.musics = {} - s.fully_loaded = false + "portrait", "burst", "fade"} +local defaulted_images = { icon=true, topleft=true, botleft=true, topright=true, botright=true, + top=true, bot=true, left=true, right=true, face=true, pop=true, + doubleface=true, filler1=true, filler2=true, flash=true, + portrait=true, burst=true, fade=true} -- those images will be defaulted if missing +local basic_sfx = {"selection"} +local other_sfx = {"chain", "combo", "combo_echo", "chain_echo", "chain2" ,"chain2_echo", "garbage_match", "garbage_land", "win", "taunt_up", "taunt_down"} +local defaulted_sfxs = {} -- those sfxs will be defaulted if missing +local basic_musics = {} +local other_musics = {"normal_music", "danger_music", "normal_music_start", "danger_music_start"} +local defaulted_musics = {} -- those musics will be defaulted if missing + +local default_character = nil -- holds default assets fallbacks + +local e_chain_style = { classic=0, per_chain=1 } + +Character = class(function(self, full_path, folder_name) + self.path = full_path -- string | path to the character folder content + self.id = folder_name -- string | id of the character, specified in config.json + self.display_name = self.id -- string | display name of the stage + self.stage = nil -- string | stage that get selected upon doing the super selection of that character + self.panels = nil -- string | panels that get selected upon doing the super selection of that character + self.sub_characters = {} -- stringS | either empty or with two elements at least; holds the sub characters IDs for bundle characters + self.images = {} + self.sounds = { combos = {}, combo_echos = {}, chains = {}, selections = {}, wins = {}, garbage_matches = {}, garbage_lands = {}, taunt_ups = {}, taunt_downs = {}, others = {} } + self.musics = {} + self.flag = nil -- string | flag to be displayed in the select screen + self.fully_loaded = false + self.is_visible = true + self.chain_style = e_chain_style.classic + self.popfx_style = "burst" + self.popfx_burstRotate = false + self.popfx_burstScale = 1 + self.popfx_fadeScale = 1 end) -function Character.other_data_init(self) - local dirs_to_check = { "characters/" } - if config.use_default_characters and self.id ~= default_character_id then - dirs_to_check = { "assets/"..config.assets_dir.."/", - "assets/"..default_assets_dir.."/", - "characters/"} - end - self.display_name = self.id - self.favorite_stage = default_stages[self.id] - - -- display name - for _,current_dir in ipairs(dirs_to_check) do - local txt_file, err = love.filesystem.newFile(current_dir..self.id.."/name.txt", "r") - if txt_file then - local display_name = txt_file:read(txt_file:getSize()) - if display_name then - self.display_name = display_name - break - end - end - end - -- favorite stage - for _,current_dir in ipairs(dirs_to_check) do - local txt_file, err = love.filesystem.newFile(current_dir..self.id.."/stage.txt", "r") - if txt_file then - local stage = txt_file:read(txt_file:getSize()) - if stage then - self.favorite_stage = stage - break - end +function Character.json_init(self) + local read_data = {} + local config_file, err = love.filesystem.newFile(self.path.."/config.json", "r") + if config_file then + local teh_json = config_file:read(config_file:getSize()) + for k,v in pairs(json.decode(teh_json)) do + read_data[k] = v end end - print( self.id..(self.id ~= self.display_name and (", aka "..self.display_name..", ") or " ")..(self.favorite_stage and ("likes to play in stage "..self.favorite_stage) or "would play anywhere")) -end -local function find_character_SFX(character_id, SFX_name,fallback) - fallback = fallback or nil - local dirs_to_check = { "characters/" } - if config.use_default_characters and character_id ~= default_character_id then - dirs_to_check = { "sounds/"..config.sounds_dir.."/characters/", - "sounds/"..default_sounds_dir.."/characters/", - "characters/"} - end - for _,current_dir in ipairs(dirs_to_check) do - --Note: if there is a chain or a combo, but not the other, return the same SFX for either inquiry. - --This way, we can always depend on a character having a combo and a chain SFX. - --If they are missing others, that's fine. - --(ie. some characters won't have "match_garbage" or a fancier "chain-x6") - local cur_dir_chain = get_from_supported_extensions(current_dir..character_id.."/chain") - if SFX_name == "chain" and cur_dir_chain then - if config.debug_mode then print("loaded "..SFX_name.." for "..character_id) end - return cur_dir_chain + if read_data.id and type(read_data.id) == "string" then + self.id = read_data.id + + -- sub ids for bundles + if read_data.sub_ids and type(read_data.sub_ids) == "table" then + self.sub_characters = read_data.sub_ids + end + -- display name + if read_data.name and type(read_data.name) == "string" then + self.display_name = read_data.name end - local cur_dir_combo = get_from_supported_extensions(current_dir..character_id.."/combo") - if SFX_name == "combo" and cur_dir_combo then - if config.debug_mode then print("loaded "..SFX_name.." for "..character_id) end - return cur_dir_combo - elseif SFX_name == "combo" and cur_dir_chain then - if config.debug_mode then print("substituted found chain SFX for "..SFX_name.." for "..character_id) end - return cur_dir_chain --in place of the combo SFX + -- is visible + if read_data.visible ~= nil and type(read_data.visible) == "boolean" then + self.is_visible = read_data.visible + elseif read_data.visible and type(read_data.visible) == "string" then + self.is_visible = read_data.visible=="true" end - if SFX_name == "chain" and cur_dir_combo then - if config.debug_mode then print("substituted found combo SFX for "..SFX_name.." for "..character_id) end - return cur_dir_combo + + -- chain_style + if read_data.chain_style and type(read_data.chain_style) == "string" then + self.chain_style = read_data.chain_style=="per_chain" and e_chain_style.per_chain or e_chain_style.classic end - - local other_requested_SFX = get_from_supported_extensions(current_dir..character_id.."/"..SFX_name) - if other_requested_SFX then - if config.debug_mode then print("loaded "..SFX_name.." for "..character_id) end - return other_requested_SFX - else - if config.debug_mode then print("did not find "..SFX_name.." for "..character_id.." in current directory: "..current_dir) end + + --popfx_burstRotate + if read_data.popfx_burstRotate and type(read_data.popfx_burstRotate) == "boolean" then + self.popfx_burstRrotate = read_data.popfx_burstRotate end - if cur_dir_chain or cur_dir_combo --[[and we didn't find the requested SFX in this dir]] then - if config.debug_mode then print("chain or combo was provided, but "..SFX_name.." was not.") end - return nil --don't continue looking in other fallback directories, - --else - --keep looking + + --popfx_type + if read_data.popfx_style and type(read_data.popfx_style) == "string" then + self.popfx_style = read_data.popfx_style end - end - --if not found in above directories: fallback - return fallback -end -local function find_music(character_id, music_type) - local dirs_to_check = { "", - "sounds/"..default_sounds_dir.."/" } - if config.use_default_characters and character_id ~= default_character_id then - dirs_to_check = { "sounds/"..config.sounds_dir.."/", - "sounds/"..default_sounds_dir.."/", - ""} - end - for _,current_dir in ipairs(dirs_to_check) do - local path = current_dir.."characters/"..character_id - if any_supported_extension(path.."/normal_music") then -- character has control over their musics, no fallback allowed! - local found_source = get_from_supported_extensions(path.."/"..music_type, true) - if config.debug_mode then print("In "..path.." directory, "..(found_source and "" or "did not").." found "..music_type.." for "..character_id) end - return found_source - elseif characters[character_id].favorite_stage then - local path = (current_dir~="" and current_dir or "sounds/"..config.sounds_dir.."/").."music/"..characters[character_id].favorite_stage - if any_supported_extension(path.."/normal_music") then - local found_source = get_from_supported_extensions(path.."/"..music_type, true) - if config.debug_mode then print("In "..path.." directory, "..(found_source and "" or "did not").." found "..music_type.." for "..character_id) end - return found_source - end + --popfx_burstScale + if read_data.popfx_burstScale and type(read_data.popfx_burstScale) == "number" then + self.popfx_burstScale = read_data.popfx_burstScale end - end - - return characters[default_character_id].musics[music_type] -end -local function init_variations_sfx(character_id, sfx_array, sfx_name, first_sound) - local sound_name = sfx_name..1 - if first_sound then - -- "combo" in others will be stored in "combo1" in combos - sfx_array[1] = first_sound - first_sound = nil - else - local sound = find_character_SFX(character_id, sound_name) - if sound then - sfx_array[1] = sound + --popfx_fadeScale + if read_data.popfx_fadeScale and type(read_data.popfx_fadeScale) == "number" then + self.popfx_fadeScale = read_data.popfx_fadeScale end - end - -- search for all variations - local sfx_count = 1 - while sfx_array[sfx_count] do - sfx_count = sfx_count+1 - sound_name = sfx_name..sfx_count - local sound = find_character_SFX(character_id, sound_name) - if sound then - sfx_array[sfx_count] = sound + -- associated stage + if read_data.stage and type(read_data.stage) == "string" and stages[read_data.stage] and not stages[read_data.stage]:is_bundle() then + self.stage = read_data.stage + end + -- associated panel + if read_data.panels and type(read_data.panels) == "string" and panels[read_data.panels] then + self.panels = read_data.panels end - end -end -function Character.assert_requirements_met(self) - assert(self.sounds.others["chain"], "Character SFX chain for "..self.id.." was not loaded.") - assert(#self.sounds.combos ~= 0, "Character SFX combo for "..self.id.." was not loaded.") - for k, music_type in ipairs(required_char_music) do - assert(self.musics[music_type], music_type.." for "..self.id.." was not loaded.") + -- flag + if read_data.flag and type(read_data.flag) == "string" then + self.flag = read_data.flag + end + + return true end + + return false end function Character.stop_sounds(self) - music_t = {} - -- SFX for _, sound_table in ipairs(self.sounds) do if type(sound_table) == "table" then @@ -205,31 +136,145 @@ end function Character.play_selection_sfx(self) if not SFX_mute and #self.sounds.selections ~= 0 then self.sounds.selections[math.random(#self.sounds.selections)]:play() + return true + end + return false +end + +function Character.play_combo_chain_sfx(self,chain_combo) + if not SFX_mute then + -- stop previous sounds if any + for _,v in pairs(self.sounds.combos) do + v:stop() + end + for _,v in pairs(self.sounds.combo_echos) do + v:stop() + end + if self.chain_style == e_chain_style.classic then + self.sounds.others["chain"]:stop() + self.sounds.others["chain2"]:stop() + self.sounds.others["chain_echo"]:stop() + self.sounds.others["chain2_echo"]:stop() + else --elseif self.chain_style == e_chain_style.per_chain then + for _,v in pairs(self.sounds.chains) do + for _,w in pairs(v) do + w:stop() + end + end + end + + -- play combos or chains + if chain_combo[1] == e_chain_or_combo.combo then + -- either combos or combo_echos + self.sounds[chain_combo[2]][math.random(#self.sounds[chain_combo[2]])]:play() + else --elseif chain_combo[1] == e_chain_or_combo.chain then + local length = chain_combo[2] + if self.chain_style == e_chain_style.classic then + if length < 4 then + self.sounds.others["chain"]:play() + elseif length == 4 then + self.sounds.others["chain2"]:play() + elseif length == 5 then + self.sounds.others["chain_echo"]:play() + elseif length >= 6 then + self.sounds.others["chain2_echo"]:play() + end + else --elseif self.chain_style == e_chain_style.per_chain then + length = math.max(length, 2) + if length > 13 then + length = 0 + end + self.sounds.chains[length][math.random(#self.sounds.chains[length])]:play() + end + end end end function Character.preload(self) - print("preloading "..self.id) - self:other_data_init() + print("preloading character "..self.id) self:graphics_init(false,false) self:sound_init(false,false) end function Character.load(self,instant) - print("loading "..self.id) + print("loading character "..self.id) self:graphics_init(true,(not instant)) self:sound_init(true,(not instant)) - self:assert_requirements_met() self.fully_loaded = true - print("loaded "..self.id) + print("loaded character "..self.id) end function Character.unload(self) - print("unloading "..self.id) + print("unloading character "..self.id) self:graphics_uninit() self:sound_uninit() self.fully_loaded = false - print("unloaded "..self.id) + print("unloaded character "..self.id) +end + +local function add_characters_from_dir_rec(path) + local lfs = love.filesystem + local raw_dir_list = lfs.getDirectoryItems(path) + for i,v in ipairs(raw_dir_list) do + local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) + if start_of_v ~= prefix_of_ignored_dirs then + local current_path = path.."/"..v + if lfs.getInfo(current_path) and lfs.getInfo(current_path).type == "directory" then + -- call recursively: facade folder + add_characters_from_dir_rec(current_path) + + -- init stage: 'real' folder + local character = Character(current_path,v) + local success = character:json_init() + + if success then + if characters[character.id] ~= nil then + print(current_path.." has been ignored since a character with this id has already been found") + else + characters[character.id] = character + characters_ids[#characters_ids+1] = character.id + -- print(current_path.." has been added to the character list!") + end + end + end + end + end +end + +local function fill_characters_ids() + -- check validity of bundle characters + local invalid = {} + local copy_of_characters_ids = shallowcpy(characters_ids) + characters_ids = {} -- clean up + for _,character_id in ipairs(copy_of_characters_ids) do + local character = characters[character_id] + if #character.sub_characters > 0 then -- bundle character (needs to be filtered if invalid) + local copy_of_sub_characters = shallowcpy(character.sub_characters) + character.sub_characters = {} + for _,sub_character in ipairs(copy_of_sub_characters) do + if characters[sub_character] and #characters[sub_character].sub_characters == 0 then -- inner bundles are prohibited + character.sub_characters[#character.sub_characters+1] = sub_character + print(character.id.." has "..sub_character.." as part of its subcharacters.") + end + end + + if #character.sub_characters < 2 then + invalid[#invalid+1] = character_id -- character is invalid + print(character.id.." (bundle) is being ignored since it's invalid!") + else + characters_ids[#characters_ids+1] = character_id + print(character.id.." (bundle) has been added to the character list!") + end + else -- normal character + characters_ids[#characters_ids+1] = character_id + print(character.id.." has been added to the character list!") + end + end + + -- characters are removed outside of the loop since erasing while iterating isn't working + for _,invalid_character in pairs(invalid) do + characters[invalid_character] = nil + end end function characters_init() @@ -238,69 +283,46 @@ function characters_init() characters_ids_for_current_theme = {} -- holds characters ids for the current theme, those characters will appear in the lobby characters_ids_by_display_names = {} -- holds keys to array of character ids holding that name - if config.use_default_characters then - -- retrocompatibility with older versions and mods - characters_ids = deepcpy(default_characters_ids) - characters_ids[#characters_ids+1] = default_character_id; - characters_ids_for_current_theme = deepcpy(default_characters_ids) - for _,character_id in ipairs(characters_ids) do - characters[character_id] = Character(character_id) - end - else - -- new system with characters belonging to their own folder and characters.txt detailing current characters - local raw_dir_list = love.filesystem.getDirectoryItems("characters") - for _,v in ipairs(raw_dir_list) do - local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) - if love.filesystem.getInfo("characters/"..v) and start_of_v ~= prefix_of_ignored_dirs then - characters[v] = Character(v) - characters_ids[#characters_ids+1] = v - end - end + add_characters_from_dir_rec("characters") + fill_characters_ids() - if love.filesystem.getInfo("assets/"..config.assets_dir.."/characters.txt") then - for line in love.filesystem.lines("assets/"..config.assets_dir.."/characters.txt") do - line = trim(line) -- remove whitespace - if love.filesystem.getInfo("characters/"..line) then - -- found at least a valid character in a characters.txt file - if characters[line] then - characters_ids_for_current_theme[#characters_ids_for_current_theme+1] = line - print("found a valid character:"..characters_ids_for_current_theme[#characters_ids_for_current_theme]) - end - end - end - end + if #characters_ids == 0 then + recursive_copy("default_data/characters", "characters") + add_characters_from_dir_rec("characters") + fill_characters_ids() + end - local function has_exactly_all_default_ids() - if #characters_ids ~= #default_characters_ids then - return false - end - for _,id in ipairs(default_characters_ids) do - if not characters[id] then - return false - end + if love.filesystem.getInfo("themes/"..config.theme.."/characters.txt") then + for line in love.filesystem.lines("themes/"..config.theme.."/characters.txt") do + line = trim(line) -- remove whitespace + if characters[line] then + -- found at least a valid character in a characters.txt file + characters_ids_for_current_theme[#characters_ids_for_current_theme+1] = line end - return true end - - -- all characters case - if #characters_ids_for_current_theme == 0 then - -- kinda retrocompatibility stuff: order the characters as the legacy order if they are exactly the default ones - if has_exactly_all_default_ids() then - characters_ids_for_current_theme = shallowcpy(default_characters_ids) - else - characters_ids_for_current_theme = shallowcpy(characters_ids) + else + for _,character_id in ipairs(characters_ids) do + if characters[character_id].is_visible then + characters_ids_for_current_theme[#characters_ids_for_current_theme+1] = character_id end end + end - characters_ids[#characters_ids+1] = default_character_id; - characters[default_character_id] = Character(default_character_id) + -- all characters case + if #characters_ids_for_current_theme == 0 then + characters_ids_for_current_theme = shallowcpy(characters_ids) end - -- init default first, as it is used as a fallback we initialize it fully - characters[default_character_id]:preload() - characters[default_character_id]:load(true) - - -- actual init for all characters (default is initialized twice but that's 'okay', it's cheap enough) + -- fix config character if it's missing + if not config.character or ( config.character ~= random_character_special_value and not characters[config.character] ) then + config.character = uniformly(characters_ids_for_current_theme) + end + + -- actual init for all characters, starting with the default one + default_character = Character("characters/__default", "__default") + default_character:preload() + default_character:load(true) + for _,character in pairs(characters) do character:preload() @@ -311,39 +333,72 @@ function characters_init() end end - -- fix config character if it's missing - if not config.character or not characters[config.character] then - config.character = uniformly(characters_ids_for_current_theme) + if config.character ~= random_character_special_value and not characters[config.character]:is_bundle() then + character_loader_load(config.character) + character_loader_wait() end +end - character_loader_load(config.character) - character_loader_wait() +function Character.is_bundle(self) + return #self.sub_characters > 1 end function Character.graphics_init(self,full,yields) - local character_images = full and other_character_images or basic_character_images - if config.use_default_characters and self.id ~= default_character_id then - for _,image_name in ipairs(character_images) do - self.images[image_name] = load_img(self.id.."/"..image_name..".png") - if not self.images[image_name] then - self.images[image_name] = load_img(image_name..".png","characters/"..self.id, "characters/__default") + local character_images = full and other_images or basic_images + for _,image_name in ipairs(character_images) do + print(image_name) + self.images[image_name] = load_img_from_supported_extensions(self.path.."/"..image_name) + if not self.images[image_name] and defaulted_images[image_name] and not self:is_bundle() then + if image_name == "burst" or image_name == "fade" then + self.images[image_name] = themes[config.theme].images[image_name] + else + self.images[image_name] = default_character.images[image_name] end - if yields then coroutine.yield() end - end - else - for _,image_name in ipairs(character_images) do - self.images[image_name] = load_img(image_name..".png","characters/"..self.id, "characters/__default") - if yields then coroutine.yield() end + print("MISSING!") end + if yields then coroutine.yield() end end end function Character.graphics_uninit(self) - for _,image_name in ipairs(other_character_images) do + for _,image_name in ipairs(other_images) do self.images[image_name] = nil end end +function Character.init_sfx_variants(self, sfx_array, sfx_name, sfx_suffix_at_higher_count) + sfx_suffix_at_higher_count = sfx_suffix_at_higher_count or "" + + -- be careful is we are to support chain2X sfx since chain21 will be found and used for chain2 (unwanted behavior), might be a future bug! + local sound_name = sfx_name..sfx_suffix_at_higher_count..1 + if self.sounds.others[sfx_name] then + -- "combo" in others will be stored in 'combos' and 'others' will be freed from it + sfx_array[1] = self.sounds.others[sfx_name] + self.sounds.others[sfx_name] = nil + elseif sfx_suffix_at_higher_count == "" then + local sound = load_sound_from_supported_extensions(self.path.."/"..sound_name, false) + if sound then + sfx_array[1] = sound + end + else + local sound = load_sound_from_supported_extensions(self.path.."/"..sfx_name, false) + if sound then + sfx_array[1] = sound + end + end + + -- search for all variants + local sfx_count = 1 + while sfx_array[sfx_count] do + sfx_count = sfx_count+1 + sound_name = sfx_name..sfx_suffix_at_higher_count..sfx_count + local sound = load_sound_from_supported_extensions(self.path.."/"..sound_name, false) + if sound then + sfx_array[sfx_count] = sound + end + end +end + function Character.apply_config_volume(self) set_volume(self.sounds, config.SFX_volume/100) set_volume(self.musics, config.music_volume/100) @@ -351,38 +406,85 @@ end function Character.sound_init(self,full,yields) -- SFX - local character_sfx = full and other_characters_sfx or basic_characters_sfx - for _, sound in ipairs(character_sfx) do - self.sounds.others[sound] = find_character_SFX(self.id, sound, characters[default_character_id].sounds.others[sound]) - if not self.sounds.others[sound] then - print("could not find "..sound.." for "..self.id) - if string.find(sound, "chain") then - self.sounds.others[sound] = find_character_SFX(self.id, "chain") - elseif string.find(sound, "combo") then - self.sounds.others[sound] = find_character_SFX(self.id, "combo") + local character_sfx = full and other_sfx or basic_sfx + for _, sfx in ipairs(character_sfx) do + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/"..sfx, false) + + -- fallback case: chain/combo can be used for the other one if missing and for the longer names versions ("combo" used for "combo_echo" for instance) + if not self.sounds.others[sfx] then + if sfx == "combo" then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/chain", false) + elseif sfx == "chain" then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/combo", false) + elseif sfx == "combo_echo" then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/combo", false) + if not self.sounds.others[sfx] then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/chain", false) + end + elseif string.find(sfx, "chain") then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/chain", false) + elseif string.find(sfx, "combo") then + self.sounds.others[sfx] = load_sound_from_supported_extensions(self.path.."/combo", false) end end + if not self.sounds.others[sfx] and defaulted_sfxs[sfx] and not self:is_bundle() then + self.sounds.others[sfx] = default_character.sounds.others[sfx] or zero_sound + end if yields then coroutine.yield() end end if not full then - init_variations_sfx(self.id, self.sounds.selections, "selection", self.sounds.others["selection"]) + self:init_sfx_variants(self.sounds.selections, "selection") if yields then coroutine.yield() end - else - init_variations_sfx(self.id, self.sounds.combos, "combo", self.sounds.others["combo"]) + elseif not self:is_bundle() then + if self.chain_style == e_chain_style.per_chain then + -- actual init of sounds + for i=2,13 do + self.sounds.chains[i] = {} + self:init_sfx_variants(self.sounds.chains[i], "chain"..i, "_") + if #self.sounds.chains[i] ~= 0 then + print("chain"..i.." has "..#self.sounds.chains[i].." variant(s)") + end + end + self.sounds.chains[0] = {} + self:init_sfx_variants(self.sounds.chains[0], "chain0", "_") + -- make it so every values in the chain array point to a properly filled arrays of sfx (last index used as fallback) + if #self.sounds.chains[2] == 0 then + self.sounds.chains[2][1] = self.sounds.others["chain"] + end + local last_filled_chains = 2 + for i=3,13 do + if #self.sounds.chains[i] == 0 then + self.sounds.chains[i] = self.sounds.chains[last_filled_chains] -- points to the same array, not an actual copy + else + last_filled_chains = i + end + end + if #self.sounds.chains[0] == 0 then + self.sounds.chains[0] = self.sounds.chains[last_filled_chains] + end + if yields then coroutine.yield() end + end + self:init_sfx_variants(self.sounds.combos, "combo") + if yields then coroutine.yield() end + self:init_sfx_variants(self.sounds.combo_echos, "combo_echo") + if yields then coroutine.yield() end + self:init_sfx_variants(self.sounds.wins, "win") if yields then coroutine.yield() end - init_variations_sfx(self.id, self.sounds.combo_echos, "combo_echo", self.sounds.others["combo_echo"]) + self:init_sfx_variants(self.sounds.garbage_matches, "garbage_match") if yields then coroutine.yield() end - init_variations_sfx(self.id, self.sounds.wins, "win", self.sounds.others["win"]) + self:init_sfx_variants(self.sounds.garbage_lands, "garbage_land") if yields then coroutine.yield() end - init_variations_sfx(self.id, self.sounds.garbage_matches, "garbage_match", self.sounds.others["garbage_match"]) + self:init_sfx_variants(self.sounds.taunt_downs, "taunt_down") + if yields then coroutine.yield() end + self:init_sfx_variants(self.sounds.taunt_ups, "taunt_up") if yields then coroutine.yield() end end -- music - local character_musics = full and other_characters_musics or basic_characters_musics + local character_musics = full and other_musics or basic_musics for _, music in ipairs(character_musics) do - self.musics[music] = find_music(self.id, music) + self.musics[music] = load_sound_from_supported_extensions(self.path.."/"..music, true) -- Set looping status for music. -- Intros won't loop, but other parts should. if self.musics[music] then @@ -391,25 +493,29 @@ function Character.sound_init(self,full,yields) else self.musics[music]:setLooping(false) end + elseif not self.musics[music] and defaulted_musics[music] and not self:is_bundle() then + self.musics[music] = default_character.musics[music] or zero_sound end + if yields then coroutine.yield() end end - + self:apply_config_volume() end function Character.sound_uninit(self) -- SFX - for _,sound in ipairs(other_characters_sfx) do + for _,sound in ipairs(other_sfx) do self.sounds.others[sound] = nil end self.sounds.combos = {} self.sounds.combo_echos = {} self.sounds.wins = {} self.sounds.garbage_matches = {} + self.sounds.garbage_lands = {} -- music - for _,music in ipairs(other_characters_musics) do + for _,music in ipairs(other_musics) do self.musics[music] = nil end end \ No newline at end of file diff --git a/character_loader.lua b/character_loader.lua index 601eff1d..a62dd2f4 100644 --- a/character_loader.lua +++ b/character_loader.lua @@ -49,7 +49,7 @@ end function character_loader_clear() local p2_local_character = global_op_state and global_op_state.character or nil for character_id,character in pairs(characters) do - if character.fully_loaded and character_id ~= default_character_id and character_id ~= config.character and character_id ~= p2_local_character then + if character.fully_loaded and character_id ~= config.character and character_id ~= p2_local_character then character:unload() end end diff --git a/characters/__default/burst.png b/characters/__default/burst.png new file mode 100644 index 00000000..c6280433 Binary files /dev/null and b/characters/__default/burst.png differ diff --git a/characters/__default/danger_music.ogg b/characters/__default/danger_music.ogg deleted file mode 100644 index 78cdd35c..00000000 Binary files a/characters/__default/danger_music.ogg and /dev/null differ diff --git a/characters/__default/fade.png b/characters/__default/fade.png new file mode 100644 index 00000000..b67e97c8 Binary files /dev/null and b/characters/__default/fade.png differ diff --git a/characters/__default/normal_music.ogg b/characters/__default/normal_music.ogg deleted file mode 100644 index 78cdd35c..00000000 Binary files a/characters/__default/normal_music.ogg and /dev/null differ diff --git a/characters/flare/doubleface.png b/characters/flare/doubleface.png deleted file mode 100644 index 904bc481..00000000 Binary files a/characters/flare/doubleface.png and /dev/null differ diff --git a/characters/flare/face.png b/characters/flare/face.png deleted file mode 100644 index ad58182d..00000000 Binary files a/characters/flare/face.png and /dev/null differ diff --git a/characters/flare/pop.png b/characters/flare/pop.png deleted file mode 100644 index 150b5093..00000000 Binary files a/characters/flare/pop.png and /dev/null differ diff --git a/characters/froggy/doubleface.png b/characters/froggy/doubleface.png deleted file mode 100644 index 3ea6ad7f..00000000 Binary files a/characters/froggy/doubleface.png and /dev/null differ diff --git a/characters/froggy/face.png b/characters/froggy/face.png deleted file mode 100644 index 5eccc008..00000000 Binary files a/characters/froggy/face.png and /dev/null differ diff --git a/characters/froggy/pop.png b/characters/froggy/pop.png deleted file mode 100644 index ff8dab2e..00000000 Binary files a/characters/froggy/pop.png and /dev/null differ diff --git a/characters/neris/doubleface.png b/characters/neris/doubleface.png deleted file mode 100644 index bbe89b7c..00000000 Binary files a/characters/neris/doubleface.png and /dev/null differ diff --git a/characters/neris/face.png b/characters/neris/face.png deleted file mode 100644 index 9322de4d..00000000 Binary files a/characters/neris/face.png and /dev/null differ diff --git a/characters/neris/pop.png b/characters/neris/pop.png deleted file mode 100644 index 8620692b..00000000 Binary files a/characters/neris/pop.png and /dev/null differ diff --git a/characters/seren/doubleface.png b/characters/seren/doubleface.png deleted file mode 100644 index 1af50ff7..00000000 Binary files a/characters/seren/doubleface.png and /dev/null differ diff --git a/characters/seren/face.png b/characters/seren/face.png deleted file mode 100644 index 65fe4c76..00000000 Binary files a/characters/seren/face.png and /dev/null differ diff --git a/characters/seren/pop.png b/characters/seren/pop.png deleted file mode 100644 index e29ea637..00000000 Binary files a/characters/seren/pop.png and /dev/null differ diff --git a/characters/sherbet/doubleface.png b/characters/sherbet/doubleface.png deleted file mode 100644 index 44f00107..00000000 Binary files a/characters/sherbet/doubleface.png and /dev/null differ diff --git a/characters/sherbet/face.png b/characters/sherbet/face.png deleted file mode 100644 index d391c13d..00000000 Binary files a/characters/sherbet/face.png and /dev/null differ diff --git a/characters/sherbet/pop.png b/characters/sherbet/pop.png deleted file mode 100644 index 93e65806..00000000 Binary files a/characters/sherbet/pop.png and /dev/null differ diff --git a/characters/thiana/doubleface.png b/characters/thiana/doubleface.png deleted file mode 100644 index c3100ea2..00000000 Binary files a/characters/thiana/doubleface.png and /dev/null differ diff --git a/characters/thiana/face.png b/characters/thiana/face.png deleted file mode 100644 index 9b4be1a1..00000000 Binary files a/characters/thiana/face.png and /dev/null differ diff --git a/characters/thiana/pop.png b/characters/thiana/pop.png deleted file mode 100644 index ba4d182f..00000000 Binary files a/characters/thiana/pop.png and /dev/null differ diff --git a/characters/wiggler/bot.png b/characters/wiggler/bot.png deleted file mode 100644 index 3b5fbaa3..00000000 Binary files a/characters/wiggler/bot.png and /dev/null differ diff --git a/characters/wiggler/botleft.png b/characters/wiggler/botleft.png deleted file mode 100644 index 5f23c521..00000000 Binary files a/characters/wiggler/botleft.png and /dev/null differ diff --git a/characters/wiggler/botright.png b/characters/wiggler/botright.png deleted file mode 100644 index b3f20cfc..00000000 Binary files a/characters/wiggler/botright.png and /dev/null differ diff --git a/characters/wiggler/doubleface.png b/characters/wiggler/doubleface.png deleted file mode 100644 index 7b81c6b2..00000000 Binary files a/characters/wiggler/doubleface.png and /dev/null differ diff --git a/characters/wiggler/face.png b/characters/wiggler/face.png deleted file mode 100644 index 35bed76c..00000000 Binary files a/characters/wiggler/face.png and /dev/null differ diff --git a/characters/wiggler/filler1.png b/characters/wiggler/filler1.png deleted file mode 100644 index 98fec45c..00000000 Binary files a/characters/wiggler/filler1.png and /dev/null differ diff --git a/characters/wiggler/filler2.png b/characters/wiggler/filler2.png deleted file mode 100644 index 2addb064..00000000 Binary files a/characters/wiggler/filler2.png and /dev/null differ diff --git a/characters/wiggler/flash.png b/characters/wiggler/flash.png deleted file mode 100644 index eacc7f7e..00000000 Binary files a/characters/wiggler/flash.png and /dev/null differ diff --git a/characters/wiggler/left.png b/characters/wiggler/left.png deleted file mode 100644 index 1453e18b..00000000 Binary files a/characters/wiggler/left.png and /dev/null differ diff --git a/characters/wiggler/pop.png b/characters/wiggler/pop.png deleted file mode 100644 index c126a2a5..00000000 Binary files a/characters/wiggler/pop.png and /dev/null differ diff --git a/characters/wiggler/right.png b/characters/wiggler/right.png deleted file mode 100644 index 3d017482..00000000 Binary files a/characters/wiggler/right.png and /dev/null differ diff --git a/characters/wiggler/top.png b/characters/wiggler/top.png deleted file mode 100644 index 36ec233f..00000000 Binary files a/characters/wiggler/top.png and /dev/null differ diff --git a/characters/wiggler/topleft.png b/characters/wiggler/topleft.png deleted file mode 100644 index a340a172..00000000 Binary files a/characters/wiggler/topleft.png and /dev/null differ diff --git a/characters/wiggler/topright.png b/characters/wiggler/topright.png deleted file mode 100644 index 78feb425..00000000 Binary files a/characters/wiggler/topright.png and /dev/null differ diff --git a/characters/windy/doubleface.png b/characters/windy/doubleface.png deleted file mode 100644 index 602f011e..00000000 Binary files a/characters/windy/doubleface.png and /dev/null differ diff --git a/characters/windy/face.png b/characters/windy/face.png deleted file mode 100644 index 19fbf207..00000000 Binary files a/characters/windy/face.png and /dev/null differ diff --git a/characters/windy/pop.png b/characters/windy/pop.png deleted file mode 100644 index 7146ad30..00000000 Binary files a/characters/windy/pop.png and /dev/null differ diff --git a/characters/yoshi/bot.png b/characters/yoshi/bot.png deleted file mode 100644 index b73eb5ef..00000000 Binary files a/characters/yoshi/bot.png and /dev/null differ diff --git a/characters/yoshi/botleft.png b/characters/yoshi/botleft.png deleted file mode 100644 index 1cc0be1e..00000000 Binary files a/characters/yoshi/botleft.png and /dev/null differ diff --git a/characters/yoshi/botright.png b/characters/yoshi/botright.png deleted file mode 100644 index 829cbb88..00000000 Binary files a/characters/yoshi/botright.png and /dev/null differ diff --git a/characters/yoshi/doubleface.png b/characters/yoshi/doubleface.png deleted file mode 100644 index 5a2f3a40..00000000 Binary files a/characters/yoshi/doubleface.png and /dev/null differ diff --git a/characters/yoshi/face.png b/characters/yoshi/face.png deleted file mode 100644 index cf54b928..00000000 Binary files a/characters/yoshi/face.png and /dev/null differ diff --git a/characters/yoshi/filler1.png b/characters/yoshi/filler1.png deleted file mode 100644 index 88cbdea2..00000000 Binary files a/characters/yoshi/filler1.png and /dev/null differ diff --git a/characters/yoshi/filler2.png b/characters/yoshi/filler2.png deleted file mode 100644 index d44e9b5d..00000000 Binary files a/characters/yoshi/filler2.png and /dev/null differ diff --git a/characters/yoshi/left.png b/characters/yoshi/left.png deleted file mode 100644 index caf52a16..00000000 Binary files a/characters/yoshi/left.png and /dev/null differ diff --git a/characters/yoshi/pop.png b/characters/yoshi/pop.png deleted file mode 100644 index 73e70053..00000000 Binary files a/characters/yoshi/pop.png and /dev/null differ diff --git a/characters/yoshi/right.png b/characters/yoshi/right.png deleted file mode 100644 index efb6f6b7..00000000 Binary files a/characters/yoshi/right.png and /dev/null differ diff --git a/characters/yoshi/top.png b/characters/yoshi/top.png deleted file mode 100644 index ff0e02c7..00000000 Binary files a/characters/yoshi/top.png and /dev/null differ diff --git a/characters/yoshi/topleft.png b/characters/yoshi/topleft.png deleted file mode 100644 index c5a8e420..00000000 Binary files a/characters/yoshi/topleft.png and /dev/null differ diff --git a/characters/yoshi/topright.png b/characters/yoshi/topright.png deleted file mode 100644 index ad45f699..00000000 Binary files a/characters/yoshi/topright.png and /dev/null differ diff --git a/click_menu.lua b/click_menu.lua new file mode 100644 index 00000000..d0db793c --- /dev/null +++ b/click_menu.lua @@ -0,0 +1,317 @@ +require("graphics_util") + +menu_font = love.graphics.getFont() +click_menus = {} +last_active_idx = 1 + +Click_menu = class(function(self, list, x, y, width, height, padding, active_idx, buttons_outlined, button_padding,background) + self.x = x or 0 + self.y = y or 0 + self.width = width or love.graphics.getWidth()-self.x - 30 --width not used yet for scrolling + self.height = height or love.graphics.getHeight()-self.y - 30 --scrolling does care about height + self.new_item_y = 0 + self.menu_controls = + { + up = + { + text = love.graphics.newText(menu_font, "^"), + x=self.width - 30, + y=10, + w=30, + h=30, + outlined=true, + visible=false + }, + down = + { + text = love.graphics.newText(menu_font, "v"), + x=self.width - 30, + y=80, + w=30, + h=30, + outlined=true, + visible=false + } + } + self.buttons = {} + self.padding = padding or 0 + self.buttons_outlined = buttons_outlined + self.button_padding = button_padding or 0 + self.background = background + self.new_item_y = 0 + if list then + for i=1,#list or 0 do + self:add_button(list[i], nil, nil, nil, nil, self.buttons_outlined) + end + end + self.arrow = ">" + self.arrow_padding = 12 + self.active = true + self.visible = true + click_menus[#click_menus+1] = self + self.active_idx = active_idx or 1 + self.id = #click_menus + last_active_idx = self.active_idx + self.top_visible_button = 1 + + + + self:layout_buttons() + end) + +function Click_menu.add_button(self, string_text, x, y, w, h, outlined, button_padding, current_setting) +-- x and y are optional. by default will add underneath existing menu buttons +-- w and h are optional. by default, button width will be the width of the text + self.w = w or 0 + self.h = h or 0 + if x or y then + self.fixed_buttons[#self.fixed_buttons] = + { + text=love.graphics.newText(menu_font, string_text), + x=x or 0, + y=y or 0, + w=w, + h=h, + outlined=self.buttons_outlined or outlined, + current_setting = current_setting + } + else + self.buttons[#self.buttons+1] = + { + text=love.graphics.newText(menu_font, string_text), + x=x or 0, + y=y or 0, + w=w, + h=h, + outlined=self.buttons_outlined or outlined, + current_setting = current_setting + } + if self.buttons[#self.buttons].current_setting then + self.buttons[#self.buttons].current_setting = love.graphics.newText(menu_font, self.buttons[#self.buttons].current_setting) + end + self.buttons[#self.buttons].y = self.new_item_y or 0 + end + self:resize_to_fit() + self:layout_buttons() + +end + +function Click_menu.set_button_setting(self, button_idx, new_setting) + self.buttons[button_idx].current_setting = love.graphics.newText(menu_font, new_setting) +end + +function Click_menu.set_button_visibility(self, idx, visible) + self.buttons[idx].visible = visible +end + +function Click_menu.get_button_width(self, idx) + return self.buttons[idx].w or self.buttons[idx].text:getWidth()+2*self.button_padding +end + +function Click_menu.get_button_height(self, idx) + if self.buttons and self.buttons[idx] then + return self.buttons[idx].h or self.buttons[idx].text:getHeight()+2*self.button_padding + else + return 0 + end +end + +function Click_menu.remove_self(self) + last_active_idx = self.active_idx + click_menus[self.id] = nil +end + +function Click_menu.set_active_idx(self, idx) + idx = wrap(1,idx,#self.buttons) + self.active_idx = idx + local top_visible_button_before = self.top_visible_button + if self.active_idx < self.top_visible_button then + self.top_visible_button = math.max(self.active_idx, 1) + end + if self.active_idx > self.top_visible_button + self.button_limit then + self.top_visible_button = math.max(self.active_idx - self.button_limit, 1) + end + if self.top_visible_button ~= top_visible_button_before or not self.buttons[idx].visible then + self:layout_buttons() + end +end + +function Click_menu.set_current_setting(self, idx, new_setting) + if self.buttons[idx] then + self.buttons[idx].current_setting = love.graphics.newText(menu_font, new_setting) + end +end + +function Click_menu.resize_to_fit(self) + for k,v in pairs(self.buttons) do + self.current_setting_x = math.max(self.current_setting_x or 0, self:get_button_width(k) + 2*(self.button_padding or 0)) + local potential_width = self:get_button_width(#self.buttons) + 2*(self.padding or 0) + if self.buttons[k].current_setting then + potential_width = potential_width + 2*(self.padding or 0) + end + self.w = math.max(self.w , potential_width) + self.current_setting_x = math.max(self.current_setting_x or 0, self.buttons[#self.buttons].text:getWidth()+(button_padding or self.button_padding or 0)) + end +end + +function Click_menu.layout_buttons(self) + self.new_item_y = self.padding or 0 + self.top_visible_button = self.top_visible_button or 1 + self.active_idx = self.active_idx or 1 + self.button_limit = math.min(self.button_limit or 1, #self.buttons) + --scroll up or down if not showing the active button + if self.active_idx < self.top_visible_button then + self.top_visible_button = math.max(self.active_idx, 1) + end + if self.active_idx >= self.top_visible_button + self.button_limit then + self.top_visible_button = math.max(self.active_idx - self.button_limit + 1, 1) + end + --reset button limit so it can be recalculated. + self.button_limit = 0 --this will increase as there is room for more buttons. + local menu_is_full = false + for i=1,#self.buttons do + if i < self.top_visible_button then + self.buttons[i].visible = false + elseif not menu_is_full and self.new_item_y + self:get_button_height(i) < self.height then + self.buttons[i].visible = true + self.buttons[i].x = self.button_padding + self.buttons[i].y = self.new_item_y or 0 + self.new_item_y = self.new_item_y + self:get_button_height(i)+self.padding + self.button_limit = self.button_limit + 1 + else --button doesn't fit + menu_is_full = true + self.buttons[i].visible = false + end + end + if #self.buttons > self.button_limit then + self:show_controls(true) + else + self:show_controls(false) + end + +end + +function Click_menu.show_controls(self, bool) + if bool or #self.buttons > self.button_limit then + for k,v in pairs(self.menu_controls) do + self.menu_controls[k].visible = true + end + else + for k,v in pairs(self.menu_controls) do + self.menu_controls[k].visible = false + end + end +end + +function Click_menu.draw(self) + if self.visible then + if self.background then + menu_drawf(self.background, self.x , self.y) + end + if self.outline then + --TO DO whole menu outline, maybe + --grectangle("line", self.x + self.buttons[i].x, self.y + self.buttons[i].y, self.get_button_width(self,i), button_height) + end + --draw buttons (not including fixed buttons, or menu controls) + for i=1,#self.buttons do + if self.buttons[i].visible then + if self.buttons[i].background then + menu_drawf(self.buttons[i].background, self.x + self.buttons[i].x, self.y + self.buttons[i].y) + end + if self.buttons[i].outlined then + grectangle("line", self.x + self.buttons[i].x, self.y + self.buttons[i].y, self:get_button_width(i), self:get_button_height(i)) + end + menu_draw(self.buttons[i].text, self.x + self.buttons[i].x + self.button_padding, self.y + self.buttons[i].y + self.button_padding) + if self.buttons[i].current_setting then + menu_draw(self.buttons[i].current_setting, self.x + self.current_setting_x or 0, self.y + self.buttons[i].y + self.button_padding) + end + end + end + --draw menu controls (up and down scrolling buttons, so far) + for k, control in pairs(self.menu_controls) do + if control.visible then + if control.background then + menu_drawf(control.background, self.x + control.x, self.y + control.y) + end + if control.outlined then + grectangle("line", self.x + control.x, self.y + control.y, control.w, control.h) + end + menu_draw(control.text, self.x + control.x + self.button_padding, self.y + control.y + self.button_padding) + if control.current_setting then + menu_draw(control.current_setting, self.x + self.current_setting_x or 0, self.y + control.y + self.button_padding) + end + end + end + --TO DO: Draw fixed buttons + if self.active_idx and self.buttons[1] then + gprint(self.arrow or ">", + self.x + + self.buttons[self.active_idx].x + - self.arrow_padding, + self.y +self.button_padding + + self.buttons[self.active_idx].y) + end + end +end + +function Click_menu.move(self, x, y) + self.x = x or 0 + self.y = y or 0 +end + +function click_or_tap(x, y, touchpress) + + print(x..","..y) + for menu_name,menu in pairs(click_menus) do + if menu.active then + menu.idx_selected = nil + for i=1, #menu.buttons do + if y >= menu.y + menu.buttons[i].y and y <= menu.y + menu.buttons[i].y + menu:get_button_height(i) and + x >= menu.x + menu.buttons[i].x and x <= menu.x + menu.buttons[i].x + menu:get_button_width(i) then + menu.idx_selected = i + last_active_idx = menu_idx_selected + end + end + for control_name, control in pairs(menu.menu_controls) do + if control.visible then + if y >= menu.y + control.y and y <= menu.y + control.y+ control.h and + x >= menu.x + control.x and x <= menu.x + control.x + control.w then + --print(menu_name.."'s "..control_name.." was clicked or tapped") + this_frame_keys[control_name] = true + + --this method moves the menu's index, but doesn't let you move the leaderboard up/down + -- if control_name == "up" then + -- -- menu:set_active_idx(menu.active_idx - 1) + -- elseif control_name == "down" then + -- menu:set_active_idx(menu.active_idx + 1) + -- end + + + --attempt at getting repeat of the key to work (didn't work) + --love:keypressed(control_name, control_name, repeating_key(control_name)) + + + end + end + end + end + end +end + +function transform_coordinates(x,y) + local lbx, lby, lbw, lbh = scale_letterbox(love.graphics.getWidth(), love.graphics.getHeight(), 16, 9) + local scale = canvas_width/math.max(background:getWidth(),background:getHeight()) + return (x-lbx)/scale*canvas_width/lbw, + (y-lby)/scale*canvas_height/lbh +end + +function love.mousepressed(x,y) + click_or_tap(transform_coordinates(x,y)) +end + +function love.touchpressed(id, x, y, dx, dy, pressure) + local _x, _y = transform_coordinates(x,y) + click_or_tap(_x, _y, {id=id, x=_x, y=_y, dx=dx, dy=dy, pressure=pressure}) +end + + diff --git a/conf.lua b/conf.lua index d10498d1..fbee502b 100644 --- a/conf.lua +++ b/conf.lua @@ -1,31 +1,56 @@ require("consts") function love.conf(t) - t.title = "Panel Attack" - t.author = "sharpobject@gmail.com" - t.url = "https://github.com/panel-attack/panel-attack" - t.window.width = canvas_width - t.window.height = canvas_height - t.window.resizable = true - t.modules.audio = true - t.modules.mouse = true - t.modules.sound = true - t.modules.physics = false - t.identity = "Panel Attack" - t.version = "11.3" - t.release = false - t.externalstorage = true - - -- DEFAULTS FROM HERE DOWN - local window = t.window or t.screen - window.vsync = true -- Enable vertical sync (boolean) - window.fullscreen = false -- Enable fullscreen (boolean) - window.fsaa = 0 -- The number of FSAA-buffers (number) - t.console = false -- Attach a console (boolean, Windows only) - t.modules.joystick = true - t.modules.timer = true -- Enable the timer module (boolean) - t.modules.image = true -- Enable the image module (boolean) - t.modules.graphics = true -- Enable the graphics module (boolean) - t.modules.keyboard = true -- Enable the keyboard module (boolean) - t.modules.event = true -end + t.identity = "Panel Attack" -- The name of the save directory (string) + t.author = "sharpobject@gmail.com" + t.url = "https://github.com/panel-attack/panel-attack" + t.release = false + t.appendidentity = false -- Search files in source directory before save directory (boolean) + t.version = "11.3" -- The LÖVE version this game was made for (string) + t.console = false -- Attach a console (boolean, Windows only) + t.accelerometerjoystick = false -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) + t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) + t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean) + + t.audio.mic = false -- Request and use microphone capabilities in Android (boolean) + t.audio.mixwithsystem = false -- Keep background music playing when opening LOVE (boolean, iOS and Android only) + + t.window.title = "Panel Attack" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = canvas_width -- The window width (number) + t.window.height = canvas_height -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = true -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) + t.window.usedpiscale = true -- Enable automatic DPI scaling (boolean) + t.window.vsync = 1 -- Vertical sync mode (number) + t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.depth = nil -- The number of bits per sample in the depth buffer + t.window.stencil = nil -- The number of bits per sample in the stencil buffer + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = true -- Enable the audio module (boolean) + t.modules.data = false -- Enable the data module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.font = true -- Enable the font module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = true -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = false -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = true -- Enable the sound module (boolean) + t.modules.system = false -- Enable the system module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) + t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update + t.modules.touch = true -- Enable the touch module (boolean) + t.modules.video = false -- Enable the video module (boolean) + t.modules.window = true -- Enable the window module (boolean) +end \ No newline at end of file diff --git a/consts.lua b/consts.lua index 839349a1..956d87d0 100644 --- a/consts.lua +++ b/consts.lua @@ -1,7 +1,7 @@ require("util") -- The values in this file are constants (except in this file perhaps) and are expected never to change during the game, not to be confused with globals! -VERSION = "037" +VERSION = "045" canvas_width = 1280 canvas_height = 720 @@ -13,25 +13,30 @@ global_background_color = { 0.1, 0.1, 0.1 } mouse_pointer_timeout = 1.5 --seconds RATING_SPREAD_MODIFIER = 400 -prefix_of_ignored_dirs = "__" +super_selection_duration = 30 -- frames (reminder: 60 frames per sec) +super_selection_enable_ratio = 0.3 -- ratio at which super enable is considered started (cancelling it won't validate a character) +assert(super_selection_enable_ratio<1.0,"") -default_assets_dir = "Stock PdP_TA" -default_panels_dir = "Stock PdP_TA" -default_sounds_dir = "Stock PdP_TA" +prefix_of_ignored_dirs = "__" -join_community_msg = " Join the community at\ndiscord.panelattack.com" +default_theme_dir = "Panel Attack" +default_panels_dir = "Panels HD - Basic" -default_characters_ids = {"lip", "windy", "sherbet", "thiana", "ruby", +default_characters_folders = {"lip", "windy", "sherbet", "thiana", "ruby", "elias", "flare", "neris", "seren", "phoenix", "dragon", "thanatos", "cordelia", "lakitu", "bumpty", "poochy", "wiggler", "froggy", "blargg", "lungefish", "raphael", "yoshi", "hookbill", "navalpiranha", "kamek", "bowser"} -default_character_id = "__default" +default_stages_folders = {"cave", "fire", "flower", "forest", "ice", + "jewel", "king", "moon", "sea", "water", "wind" } + +random_stage_special_value = "__RandomStage" +random_character_special_value = "__RandomCharacter" key_names = {"up", "down", "left", "right", "swap1", - "swap2", "raise1", "raise2"} + "swap2", "taunt_up", "taunt_down", "raise1", "raise2", "pause"} bounce_table = {1, 1, 1, 1, 2, 2, 2, @@ -75,12 +80,18 @@ card_animation = {false, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11} +-- The popping particle animation. First number is how far the particles go, second is wich frame to show from the spritesheet + popfx_burst_animation = {{1, 1}, {4, 1}, {7, 1}, {8, 1}, + {9, 1}, {9, 1}, {10, 1}, {10, 2}, {10, 2}, {10, 3}, + {10, 3}, {10, 4}, {10, 4}, {10, 5}, {10, 5}, {10, 6}, {10, 6}, {10, 7}, {10, 7}, {10, 8}, {10, 8}, {10, 8}} -FC_HOVER = {12, 9, 6, 2} + popfx_fade_animation = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8} + +FC_HOVER = {12, 9, 6, 3} -- TODO: delete FC_MATCH? --FC_MATCH = {61, 49, 37} -FC_FLASH = {44, 36, 22, 14} -FC_FACE = {17, 13, 15, 8} -- idk this is just MATCH-FLASH +FC_FLASH = {44, 36, 22, 16} +FC_FACE = {17, 13, 15, 10} -- idk this is just MATCH-FLASH FC_POP = { 9, 8, 7, 6} stop_time_combo = {120, 120, 120, 90} stop_time_chain = {300, 180, 120, 90} @@ -130,19 +141,19 @@ panels_to_next_speed = --level_to_difficulty = { 1, 1, 2, 2, 2, 2, 2, 3, 3, 3} -- What speed level you start on. -level_to_starting_speed = { 1, 5, 9, 13, 17, 21, 25, 29, 27, 32, 99} +level_to_starting_speed = { 1, 5, 9, 13, 17, 21, 25, 29, 27, 32, 45} -- How long you can spend at the top of the screen without dying, in frames ("Health"). -level_to_hang_time = {121,101, 81, 66, 51, 41, 31, 21, 11, 1, 0.25} +level_to_hang_time = {121,101, 81, 66, 51, 41, 31, 21, 11, 1, 1} -- How many colors of panels can spawn in VS mode, not including metal panels. level_to_ncolors_vs = { 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6} -- How many colors of panels can spawn in time trial mode. level_to_ncolors_time = { 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6} -- How long panels will hover if not supported by anything, in frames. -level_to_hover = { 12, 12, 11, 10, 9, 6, 5, 4, 3, 6, 2} +level_to_hover = { 12, 12, 11, 10, 9, 6, 5, 4, 3, 6, 3} -- How long newly-transformed panels from garbage will hover before falling, in frames. -level_to_garbage_panel_hover = { 41, 36, 31, 26, 21, 16, 13, 10, 7, 4, 2} +level_to_garbage_panel_hover = { 41, 36, 31, 26, 21, 16, 13, 10, 7, 4, 3} -- How long panels flash for before popping, in frames. -level_to_flash = { 44, 44, 42, 42, 38, 36, 34, 32, 30, 28, 14} +level_to_flash = { 44, 44, 42, 42, 38, 36, 34, 32, 30, 28, 22} -- How long panels remain in their "face" frame before popping, in frames. -- (They actually stay in their face frame for five frames longer than the numbers in this table for some reason... -- This makes timings accurate with Tetris Attack / Panel de Pon SFC.) @@ -150,10 +161,10 @@ level_to_face = { 20, 18, 17, 16, 15, 14, 13, 12, 11, 10, 8} -- How long panels take to pop after finishing their "face" frame, in frames. level_to_pop = { 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 6} -- How long the stack stops when you clear combos, in frames. -level_to_combo_constant = {-20,-16,-12, -8, -3, 2, 7, 12, 17, 22, 30} +level_to_combo_constant = {-20,-16,-12, -8, -3, 2, 7, 12, 17, 22, 27} level_to_combo_coefficient = { 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 1} -- How long the stack stops when you clear chains, in frames. -level_to_chain_constant = { 80, 77, 74, 71, 68, 65, 62, 60, 58, 56, 50} +level_to_chain_constant = { 80, 77, 74, 71, 68, 65, 62, 60, 58, 56, 53} level_to_chain_coefficient = { 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 1} -- How many panels you have to pop to earn a metal panel in your next row. level_to_metal_panel_frequency = { 12, 14, 16, 19, 23, 26, 29, 33, 37, 41, 18} @@ -189,6 +200,8 @@ colors = { red = {220/255, 50/255, 47/255 }, white = {234/255, 234/255, 234/255}, black = {20/255, 20/255, 20/255 }, dgray = {28/255, 28/255, 28/255 }} + +e_chain_or_combo = { combo=0, chain=1 } panel_color_number_to_upper = {"A", "B", "C", "D", "E", "F", "G", "H",[0]="0"} panel_color_number_to_lower = {"a", "b", "c", "d", "e", "f", "g", "h",[0]="0"} diff --git a/characters/blargg/bot.png b/default_data/characters/blargg/bot.png similarity index 100% rename from characters/blargg/bot.png rename to default_data/characters/blargg/bot.png diff --git a/characters/blargg/botleft.png b/default_data/characters/blargg/botleft.png similarity index 100% rename from characters/blargg/botleft.png rename to default_data/characters/blargg/botleft.png diff --git a/characters/blargg/botright.png b/default_data/characters/blargg/botright.png similarity index 100% rename from characters/blargg/botright.png rename to default_data/characters/blargg/botright.png diff --git a/default_data/characters/blargg/burst.png b/default_data/characters/blargg/burst.png new file mode 100644 index 00000000..4a0847ab Binary files /dev/null and b/default_data/characters/blargg/burst.png differ diff --git a/characters/blargg/chain.ogg b/default_data/characters/blargg/chain.ogg similarity index 100% rename from characters/blargg/chain.ogg rename to default_data/characters/blargg/chain.ogg diff --git a/default_data/characters/blargg/config.json b/default_data/characters/blargg/config.json new file mode 100644 index 00000000..4bc8b4a5 --- /dev/null +++ b/default_data/characters/blargg/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_blargg", + "name":"Blargg" +} \ No newline at end of file diff --git a/default_data/characters/blargg/doubleface.png b/default_data/characters/blargg/doubleface.png new file mode 100644 index 00000000..742e7423 Binary files /dev/null and b/default_data/characters/blargg/doubleface.png differ diff --git a/default_data/characters/blargg/face.png b/default_data/characters/blargg/face.png new file mode 100644 index 00000000..ffabb15f Binary files /dev/null and b/default_data/characters/blargg/face.png differ diff --git a/characters/blargg/filler1.png b/default_data/characters/blargg/filler1.png similarity index 100% rename from characters/blargg/filler1.png rename to default_data/characters/blargg/filler1.png diff --git a/characters/blargg/filler2.png b/default_data/characters/blargg/filler2.png similarity index 100% rename from characters/blargg/filler2.png rename to default_data/characters/blargg/filler2.png diff --git a/characters/blargg/flash.png b/default_data/characters/blargg/flash.png similarity index 100% rename from characters/blargg/flash.png rename to default_data/characters/blargg/flash.png diff --git a/characters/blargg/icon.png b/default_data/characters/blargg/icon.png similarity index 100% rename from characters/blargg/icon.png rename to default_data/characters/blargg/icon.png diff --git a/characters/blargg/left.png b/default_data/characters/blargg/left.png similarity index 100% rename from characters/blargg/left.png rename to default_data/characters/blargg/left.png diff --git a/default_data/characters/blargg/pop.png b/default_data/characters/blargg/pop.png new file mode 100644 index 00000000..b78e92e4 Binary files /dev/null and b/default_data/characters/blargg/pop.png differ diff --git a/characters/blargg/portrait.png b/default_data/characters/blargg/portrait.png similarity index 100% rename from characters/blargg/portrait.png rename to default_data/characters/blargg/portrait.png diff --git a/characters/blargg/right.png b/default_data/characters/blargg/right.png similarity index 100% rename from characters/blargg/right.png rename to default_data/characters/blargg/right.png diff --git a/characters/blargg/top.png b/default_data/characters/blargg/top.png similarity index 100% rename from characters/blargg/top.png rename to default_data/characters/blargg/top.png diff --git a/characters/blargg/topleft.png b/default_data/characters/blargg/topleft.png similarity index 100% rename from characters/blargg/topleft.png rename to default_data/characters/blargg/topleft.png diff --git a/characters/blargg/topright.png b/default_data/characters/blargg/topright.png similarity index 100% rename from characters/blargg/topright.png rename to default_data/characters/blargg/topright.png diff --git a/characters/bowser/bot.png b/default_data/characters/bowser/bot.png similarity index 100% rename from characters/bowser/bot.png rename to default_data/characters/bowser/bot.png diff --git a/characters/bowser/botleft.png b/default_data/characters/bowser/botleft.png similarity index 100% rename from characters/bowser/botleft.png rename to default_data/characters/bowser/botleft.png diff --git a/characters/bowser/botright.png b/default_data/characters/bowser/botright.png similarity index 100% rename from characters/bowser/botright.png rename to default_data/characters/bowser/botright.png diff --git a/default_data/characters/bowser/burst.png b/default_data/characters/bowser/burst.png new file mode 100644 index 00000000..7ca78b78 Binary files /dev/null and b/default_data/characters/bowser/burst.png differ diff --git a/characters/bowser/chain.ogg b/default_data/characters/bowser/chain.ogg similarity index 100% rename from characters/bowser/chain.ogg rename to default_data/characters/bowser/chain.ogg diff --git a/default_data/characters/bowser/config.json b/default_data/characters/bowser/config.json new file mode 100644 index 00000000..90e3c8b6 --- /dev/null +++ b/default_data/characters/bowser/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_bowser", + "name":"Bowser" +} \ No newline at end of file diff --git a/characters/bowser/doubleface.png b/default_data/characters/bowser/doubleface.png similarity index 100% rename from characters/bowser/doubleface.png rename to default_data/characters/bowser/doubleface.png diff --git a/characters/bowser/face.png b/default_data/characters/bowser/face.png similarity index 100% rename from characters/bowser/face.png rename to default_data/characters/bowser/face.png diff --git a/characters/bowser/filler1.png b/default_data/characters/bowser/filler1.png similarity index 100% rename from characters/bowser/filler1.png rename to default_data/characters/bowser/filler1.png diff --git a/characters/bowser/filler2.png b/default_data/characters/bowser/filler2.png similarity index 100% rename from characters/bowser/filler2.png rename to default_data/characters/bowser/filler2.png diff --git a/characters/bowser/flash.png b/default_data/characters/bowser/flash.png similarity index 100% rename from characters/bowser/flash.png rename to default_data/characters/bowser/flash.png diff --git a/characters/bowser/icon.png b/default_data/characters/bowser/icon.png similarity index 100% rename from characters/bowser/icon.png rename to default_data/characters/bowser/icon.png diff --git a/characters/bowser/left.png b/default_data/characters/bowser/left.png similarity index 100% rename from characters/bowser/left.png rename to default_data/characters/bowser/left.png diff --git a/characters/bowser/pop.png b/default_data/characters/bowser/pop.png similarity index 100% rename from characters/bowser/pop.png rename to default_data/characters/bowser/pop.png diff --git a/characters/bowser/portrait.png b/default_data/characters/bowser/portrait.png similarity index 100% rename from characters/bowser/portrait.png rename to default_data/characters/bowser/portrait.png diff --git a/characters/bowser/right.png b/default_data/characters/bowser/right.png similarity index 100% rename from characters/bowser/right.png rename to default_data/characters/bowser/right.png diff --git a/characters/bowser/top.png b/default_data/characters/bowser/top.png similarity index 100% rename from characters/bowser/top.png rename to default_data/characters/bowser/top.png diff --git a/characters/bowser/topleft.png b/default_data/characters/bowser/topleft.png similarity index 100% rename from characters/bowser/topleft.png rename to default_data/characters/bowser/topleft.png diff --git a/characters/bowser/topright.png b/default_data/characters/bowser/topright.png similarity index 100% rename from characters/bowser/topright.png rename to default_data/characters/bowser/topright.png diff --git a/characters/bumpty/bot.png b/default_data/characters/bumpty/bot.png similarity index 100% rename from characters/bumpty/bot.png rename to default_data/characters/bumpty/bot.png diff --git a/characters/bumpty/botleft.png b/default_data/characters/bumpty/botleft.png similarity index 100% rename from characters/bumpty/botleft.png rename to default_data/characters/bumpty/botleft.png diff --git a/characters/bumpty/botright.png b/default_data/characters/bumpty/botright.png similarity index 100% rename from characters/bumpty/botright.png rename to default_data/characters/bumpty/botright.png diff --git a/default_data/characters/bumpty/burst.png b/default_data/characters/bumpty/burst.png new file mode 100644 index 00000000..6d1bbb90 Binary files /dev/null and b/default_data/characters/bumpty/burst.png differ diff --git a/characters/bumpty/chain.ogg b/default_data/characters/bumpty/chain.ogg similarity index 100% rename from characters/bumpty/chain.ogg rename to default_data/characters/bumpty/chain.ogg diff --git a/default_data/characters/bumpty/config.json b/default_data/characters/bumpty/config.json new file mode 100644 index 00000000..64d3ae6e --- /dev/null +++ b/default_data/characters/bumpty/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_bumpty", + "name":"Bumpty" +} \ No newline at end of file diff --git a/default_data/characters/bumpty/doubleface.png b/default_data/characters/bumpty/doubleface.png new file mode 100644 index 00000000..d6cf6a2d Binary files /dev/null and b/default_data/characters/bumpty/doubleface.png differ diff --git a/default_data/characters/bumpty/face.png b/default_data/characters/bumpty/face.png new file mode 100644 index 00000000..3ae85806 Binary files /dev/null and b/default_data/characters/bumpty/face.png differ diff --git a/characters/bumpty/filler1.png b/default_data/characters/bumpty/filler1.png similarity index 100% rename from characters/bumpty/filler1.png rename to default_data/characters/bumpty/filler1.png diff --git a/characters/bumpty/filler2.png b/default_data/characters/bumpty/filler2.png similarity index 100% rename from characters/bumpty/filler2.png rename to default_data/characters/bumpty/filler2.png diff --git a/characters/bumpty/flash.png b/default_data/characters/bumpty/flash.png similarity index 100% rename from characters/bumpty/flash.png rename to default_data/characters/bumpty/flash.png diff --git a/characters/bumpty/icon.png b/default_data/characters/bumpty/icon.png similarity index 100% rename from characters/bumpty/icon.png rename to default_data/characters/bumpty/icon.png diff --git a/characters/bumpty/left.png b/default_data/characters/bumpty/left.png similarity index 100% rename from characters/bumpty/left.png rename to default_data/characters/bumpty/left.png diff --git a/default_data/characters/bumpty/pop.png b/default_data/characters/bumpty/pop.png new file mode 100644 index 00000000..6cbafba1 Binary files /dev/null and b/default_data/characters/bumpty/pop.png differ diff --git a/characters/bumpty/portrait.png b/default_data/characters/bumpty/portrait.png similarity index 100% rename from characters/bumpty/portrait.png rename to default_data/characters/bumpty/portrait.png diff --git a/characters/bumpty/right.png b/default_data/characters/bumpty/right.png similarity index 100% rename from characters/bumpty/right.png rename to default_data/characters/bumpty/right.png diff --git a/characters/bumpty/top.png b/default_data/characters/bumpty/top.png similarity index 100% rename from characters/bumpty/top.png rename to default_data/characters/bumpty/top.png diff --git a/characters/bumpty/topleft.png b/default_data/characters/bumpty/topleft.png similarity index 100% rename from characters/bumpty/topleft.png rename to default_data/characters/bumpty/topleft.png diff --git a/characters/bumpty/topright.png b/default_data/characters/bumpty/topright.png similarity index 100% rename from characters/bumpty/topright.png rename to default_data/characters/bumpty/topright.png diff --git a/characters/cordelia/Cordelia.png b/default_data/characters/cordelia/Cordelia.png similarity index 100% rename from characters/cordelia/Cordelia.png rename to default_data/characters/cordelia/Cordelia.png diff --git a/characters/cordelia/bot.png b/default_data/characters/cordelia/bot.png similarity index 100% rename from characters/cordelia/bot.png rename to default_data/characters/cordelia/bot.png diff --git a/characters/cordelia/botleft.png b/default_data/characters/cordelia/botleft.png similarity index 100% rename from characters/cordelia/botleft.png rename to default_data/characters/cordelia/botleft.png diff --git a/characters/cordelia/botright.png b/default_data/characters/cordelia/botright.png similarity index 100% rename from characters/cordelia/botright.png rename to default_data/characters/cordelia/botright.png diff --git a/default_data/characters/cordelia/burst.png b/default_data/characters/cordelia/burst.png new file mode 100644 index 00000000..7ca78b78 Binary files /dev/null and b/default_data/characters/cordelia/burst.png differ diff --git a/characters/cordelia/chain.ogg b/default_data/characters/cordelia/chain.ogg similarity index 100% rename from characters/cordelia/chain.ogg rename to default_data/characters/cordelia/chain.ogg diff --git a/default_data/characters/cordelia/config.json b/default_data/characters/cordelia/config.json new file mode 100644 index 00000000..42a0648f --- /dev/null +++ b/default_data/characters/cordelia/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_cordelia", + "name":"Cordelia" +} \ No newline at end of file diff --git a/characters/cordelia/danger_music.it b/default_data/characters/cordelia/danger_music.it similarity index 100% rename from characters/cordelia/danger_music.it rename to default_data/characters/cordelia/danger_music.it diff --git a/characters/cordelia/doubleface.png b/default_data/characters/cordelia/doubleface.png similarity index 100% rename from characters/cordelia/doubleface.png rename to default_data/characters/cordelia/doubleface.png diff --git a/characters/cordelia/face.png b/default_data/characters/cordelia/face.png similarity index 100% rename from characters/cordelia/face.png rename to default_data/characters/cordelia/face.png diff --git a/characters/cordelia/filler1.png b/default_data/characters/cordelia/filler1.png similarity index 100% rename from characters/cordelia/filler1.png rename to default_data/characters/cordelia/filler1.png diff --git a/characters/cordelia/filler2.png b/default_data/characters/cordelia/filler2.png similarity index 100% rename from characters/cordelia/filler2.png rename to default_data/characters/cordelia/filler2.png diff --git a/characters/cordelia/flash.png b/default_data/characters/cordelia/flash.png similarity index 100% rename from characters/cordelia/flash.png rename to default_data/characters/cordelia/flash.png diff --git a/characters/cordelia/icon.png b/default_data/characters/cordelia/icon.png similarity index 100% rename from characters/cordelia/icon.png rename to default_data/characters/cordelia/icon.png diff --git a/characters/cordelia/left.png b/default_data/characters/cordelia/left.png similarity index 100% rename from characters/cordelia/left.png rename to default_data/characters/cordelia/left.png diff --git a/characters/cordelia/normal_music.it b/default_data/characters/cordelia/normal_music.it similarity index 100% rename from characters/cordelia/normal_music.it rename to default_data/characters/cordelia/normal_music.it diff --git a/characters/cordelia/normal_music_start.it b/default_data/characters/cordelia/normal_music_start.it similarity index 100% rename from characters/cordelia/normal_music_start.it rename to default_data/characters/cordelia/normal_music_start.it diff --git a/characters/cordelia/pop.png b/default_data/characters/cordelia/pop.png similarity index 100% rename from characters/cordelia/pop.png rename to default_data/characters/cordelia/pop.png diff --git a/characters/cordelia/portrait.png b/default_data/characters/cordelia/portrait.png similarity index 100% rename from characters/cordelia/portrait.png rename to default_data/characters/cordelia/portrait.png diff --git a/characters/cordelia/right.png b/default_data/characters/cordelia/right.png similarity index 100% rename from characters/cordelia/right.png rename to default_data/characters/cordelia/right.png diff --git a/characters/cordelia/top.png b/default_data/characters/cordelia/top.png similarity index 100% rename from characters/cordelia/top.png rename to default_data/characters/cordelia/top.png diff --git a/characters/cordelia/topleft.png b/default_data/characters/cordelia/topleft.png similarity index 100% rename from characters/cordelia/topleft.png rename to default_data/characters/cordelia/topleft.png diff --git a/characters/cordelia/topright.png b/default_data/characters/cordelia/topright.png similarity index 100% rename from characters/cordelia/topright.png rename to default_data/characters/cordelia/topright.png diff --git a/characters/dragon/bot.png b/default_data/characters/dragon/bot.png similarity index 100% rename from characters/dragon/bot.png rename to default_data/characters/dragon/bot.png diff --git a/characters/dragon/botleft.png b/default_data/characters/dragon/botleft.png similarity index 100% rename from characters/dragon/botleft.png rename to default_data/characters/dragon/botleft.png diff --git a/characters/dragon/botright.png b/default_data/characters/dragon/botright.png similarity index 100% rename from characters/dragon/botright.png rename to default_data/characters/dragon/botright.png diff --git a/default_data/characters/dragon/burst.png b/default_data/characters/dragon/burst.png new file mode 100644 index 00000000..fbd9d6b9 Binary files /dev/null and b/default_data/characters/dragon/burst.png differ diff --git a/characters/dragon/chain.ogg b/default_data/characters/dragon/chain.ogg similarity index 100% rename from characters/dragon/chain.ogg rename to default_data/characters/dragon/chain.ogg diff --git a/default_data/characters/dragon/config.json b/default_data/characters/dragon/config.json new file mode 100644 index 00000000..3efb1561 --- /dev/null +++ b/default_data/characters/dragon/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_dragon", + "name":"Dragon" +} \ No newline at end of file diff --git a/characters/dragon/doubleface.png b/default_data/characters/dragon/doubleface.png similarity index 100% rename from characters/dragon/doubleface.png rename to default_data/characters/dragon/doubleface.png diff --git a/characters/dragon/face.png b/default_data/characters/dragon/face.png similarity index 100% rename from characters/dragon/face.png rename to default_data/characters/dragon/face.png diff --git a/characters/dragon/filler1.png b/default_data/characters/dragon/filler1.png similarity index 100% rename from characters/dragon/filler1.png rename to default_data/characters/dragon/filler1.png diff --git a/characters/dragon/filler2.png b/default_data/characters/dragon/filler2.png similarity index 100% rename from characters/dragon/filler2.png rename to default_data/characters/dragon/filler2.png diff --git a/characters/dragon/flash.png b/default_data/characters/dragon/flash.png similarity index 100% rename from characters/dragon/flash.png rename to default_data/characters/dragon/flash.png diff --git a/characters/dragon/icon.png b/default_data/characters/dragon/icon.png similarity index 100% rename from characters/dragon/icon.png rename to default_data/characters/dragon/icon.png diff --git a/characters/dragon/left.png b/default_data/characters/dragon/left.png similarity index 100% rename from characters/dragon/left.png rename to default_data/characters/dragon/left.png diff --git a/characters/dragon/pop.png b/default_data/characters/dragon/pop.png similarity index 100% rename from characters/dragon/pop.png rename to default_data/characters/dragon/pop.png diff --git a/characters/dragon/portrait.png b/default_data/characters/dragon/portrait.png similarity index 100% rename from characters/dragon/portrait.png rename to default_data/characters/dragon/portrait.png diff --git a/characters/dragon/right.png b/default_data/characters/dragon/right.png similarity index 100% rename from characters/dragon/right.png rename to default_data/characters/dragon/right.png diff --git a/characters/dragon/top.png b/default_data/characters/dragon/top.png similarity index 100% rename from characters/dragon/top.png rename to default_data/characters/dragon/top.png diff --git a/characters/dragon/topleft.png b/default_data/characters/dragon/topleft.png similarity index 100% rename from characters/dragon/topleft.png rename to default_data/characters/dragon/topleft.png diff --git a/characters/dragon/topright.png b/default_data/characters/dragon/topright.png similarity index 100% rename from characters/dragon/topright.png rename to default_data/characters/dragon/topright.png diff --git a/characters/elias/bot.png b/default_data/characters/elias/bot.png similarity index 100% rename from characters/elias/bot.png rename to default_data/characters/elias/bot.png diff --git a/characters/elias/botleft.png b/default_data/characters/elias/botleft.png similarity index 100% rename from characters/elias/botleft.png rename to default_data/characters/elias/botleft.png diff --git a/characters/elias/botright.png b/default_data/characters/elias/botright.png similarity index 100% rename from characters/elias/botright.png rename to default_data/characters/elias/botright.png diff --git a/default_data/characters/elias/burst.png b/default_data/characters/elias/burst.png new file mode 100644 index 00000000..b4ab12aa Binary files /dev/null and b/default_data/characters/elias/burst.png differ diff --git a/characters/elias/chain.ogg b/default_data/characters/elias/chain.ogg similarity index 100% rename from characters/elias/chain.ogg rename to default_data/characters/elias/chain.ogg diff --git a/default_data/characters/elias/config.json b/default_data/characters/elias/config.json new file mode 100644 index 00000000..ae8c1377 --- /dev/null +++ b/default_data/characters/elias/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_elias", + "name":"Elias" +} \ No newline at end of file diff --git a/characters/elias/doubleface.png b/default_data/characters/elias/doubleface.png similarity index 100% rename from characters/elias/doubleface.png rename to default_data/characters/elias/doubleface.png diff --git a/characters/elias/face.png b/default_data/characters/elias/face.png similarity index 100% rename from characters/elias/face.png rename to default_data/characters/elias/face.png diff --git a/characters/elias/filler1.png b/default_data/characters/elias/filler1.png similarity index 100% rename from characters/elias/filler1.png rename to default_data/characters/elias/filler1.png diff --git a/characters/elias/filler2.png b/default_data/characters/elias/filler2.png similarity index 100% rename from characters/elias/filler2.png rename to default_data/characters/elias/filler2.png diff --git a/characters/elias/flash.png b/default_data/characters/elias/flash.png similarity index 100% rename from characters/elias/flash.png rename to default_data/characters/elias/flash.png diff --git a/characters/elias/icon.png b/default_data/characters/elias/icon.png similarity index 100% rename from characters/elias/icon.png rename to default_data/characters/elias/icon.png diff --git a/characters/elias/left.png b/default_data/characters/elias/left.png similarity index 100% rename from characters/elias/left.png rename to default_data/characters/elias/left.png diff --git a/characters/elias/pop.png b/default_data/characters/elias/pop.png similarity index 100% rename from characters/elias/pop.png rename to default_data/characters/elias/pop.png diff --git a/characters/elias/portrait.png b/default_data/characters/elias/portrait.png similarity index 100% rename from characters/elias/portrait.png rename to default_data/characters/elias/portrait.png diff --git a/characters/elias/right.png b/default_data/characters/elias/right.png similarity index 100% rename from characters/elias/right.png rename to default_data/characters/elias/right.png diff --git a/characters/elias/top.png b/default_data/characters/elias/top.png similarity index 100% rename from characters/elias/top.png rename to default_data/characters/elias/top.png diff --git a/characters/elias/topleft.png b/default_data/characters/elias/topleft.png similarity index 100% rename from characters/elias/topleft.png rename to default_data/characters/elias/topleft.png diff --git a/characters/elias/topright.png b/default_data/characters/elias/topright.png similarity index 100% rename from characters/elias/topright.png rename to default_data/characters/elias/topright.png diff --git a/characters/flare/bot.png b/default_data/characters/flare/bot.png similarity index 100% rename from characters/flare/bot.png rename to default_data/characters/flare/bot.png diff --git a/characters/flare/botleft.png b/default_data/characters/flare/botleft.png similarity index 100% rename from characters/flare/botleft.png rename to default_data/characters/flare/botleft.png diff --git a/characters/flare/botright.png b/default_data/characters/flare/botright.png similarity index 100% rename from characters/flare/botright.png rename to default_data/characters/flare/botright.png diff --git a/default_data/characters/flare/burst.png b/default_data/characters/flare/burst.png new file mode 100644 index 00000000..4a0847ab Binary files /dev/null and b/default_data/characters/flare/burst.png differ diff --git a/characters/flare/chain.ogg b/default_data/characters/flare/chain.ogg similarity index 100% rename from characters/flare/chain.ogg rename to default_data/characters/flare/chain.ogg diff --git a/default_data/characters/flare/config.json b/default_data/characters/flare/config.json new file mode 100644 index 00000000..926d3b01 --- /dev/null +++ b/default_data/characters/flare/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_flare", + "name":"Flare" +} \ No newline at end of file diff --git a/characters/blargg/doubleface.png b/default_data/characters/flare/doubleface.png similarity index 100% rename from characters/blargg/doubleface.png rename to default_data/characters/flare/doubleface.png diff --git a/characters/blargg/face.png b/default_data/characters/flare/face.png similarity index 100% rename from characters/blargg/face.png rename to default_data/characters/flare/face.png diff --git a/characters/flare/filler1.png b/default_data/characters/flare/filler1.png similarity index 100% rename from characters/flare/filler1.png rename to default_data/characters/flare/filler1.png diff --git a/characters/flare/filler2.png b/default_data/characters/flare/filler2.png similarity index 100% rename from characters/flare/filler2.png rename to default_data/characters/flare/filler2.png diff --git a/characters/flare/flash.png b/default_data/characters/flare/flash.png similarity index 100% rename from characters/flare/flash.png rename to default_data/characters/flare/flash.png diff --git a/characters/flare/icon.png b/default_data/characters/flare/icon.png similarity index 100% rename from characters/flare/icon.png rename to default_data/characters/flare/icon.png diff --git a/characters/flare/left.png b/default_data/characters/flare/left.png similarity index 100% rename from characters/flare/left.png rename to default_data/characters/flare/left.png diff --git a/characters/blargg/pop.png b/default_data/characters/flare/pop.png similarity index 100% rename from characters/blargg/pop.png rename to default_data/characters/flare/pop.png diff --git a/characters/flare/portrait.png b/default_data/characters/flare/portrait.png similarity index 100% rename from characters/flare/portrait.png rename to default_data/characters/flare/portrait.png diff --git a/characters/flare/right.png b/default_data/characters/flare/right.png similarity index 100% rename from characters/flare/right.png rename to default_data/characters/flare/right.png diff --git a/characters/flare/top.png b/default_data/characters/flare/top.png similarity index 100% rename from characters/flare/top.png rename to default_data/characters/flare/top.png diff --git a/characters/flare/topleft.png b/default_data/characters/flare/topleft.png similarity index 100% rename from characters/flare/topleft.png rename to default_data/characters/flare/topleft.png diff --git a/characters/flare/topright.png b/default_data/characters/flare/topright.png similarity index 100% rename from characters/flare/topright.png rename to default_data/characters/flare/topright.png diff --git a/characters/froggy/bot.png b/default_data/characters/froggy/bot.png similarity index 100% rename from characters/froggy/bot.png rename to default_data/characters/froggy/bot.png diff --git a/characters/froggy/botleft.png b/default_data/characters/froggy/botleft.png similarity index 100% rename from characters/froggy/botleft.png rename to default_data/characters/froggy/botleft.png diff --git a/characters/froggy/botright.png b/default_data/characters/froggy/botright.png similarity index 100% rename from characters/froggy/botright.png rename to default_data/characters/froggy/botright.png diff --git a/default_data/characters/froggy/burst.png b/default_data/characters/froggy/burst.png new file mode 100644 index 00000000..b4ab12aa Binary files /dev/null and b/default_data/characters/froggy/burst.png differ diff --git a/characters/froggy/chain.ogg b/default_data/characters/froggy/chain.ogg similarity index 100% rename from characters/froggy/chain.ogg rename to default_data/characters/froggy/chain.ogg diff --git a/default_data/characters/froggy/config.json b/default_data/characters/froggy/config.json new file mode 100644 index 00000000..d32b77d7 --- /dev/null +++ b/default_data/characters/froggy/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_froggy", + "name":"Froggy" +} \ No newline at end of file diff --git a/default_data/characters/froggy/doubleface.png b/default_data/characters/froggy/doubleface.png new file mode 100644 index 00000000..7b56e744 Binary files /dev/null and b/default_data/characters/froggy/doubleface.png differ diff --git a/default_data/characters/froggy/face.png b/default_data/characters/froggy/face.png new file mode 100644 index 00000000..81ab57c2 Binary files /dev/null and b/default_data/characters/froggy/face.png differ diff --git a/characters/froggy/filler1.png b/default_data/characters/froggy/filler1.png similarity index 100% rename from characters/froggy/filler1.png rename to default_data/characters/froggy/filler1.png diff --git a/characters/froggy/filler2.png b/default_data/characters/froggy/filler2.png similarity index 100% rename from characters/froggy/filler2.png rename to default_data/characters/froggy/filler2.png diff --git a/characters/froggy/flash.png b/default_data/characters/froggy/flash.png similarity index 100% rename from characters/froggy/flash.png rename to default_data/characters/froggy/flash.png diff --git a/characters/froggy/icon.png b/default_data/characters/froggy/icon.png similarity index 100% rename from characters/froggy/icon.png rename to default_data/characters/froggy/icon.png diff --git a/characters/froggy/left.png b/default_data/characters/froggy/left.png similarity index 100% rename from characters/froggy/left.png rename to default_data/characters/froggy/left.png diff --git a/default_data/characters/froggy/pop.png b/default_data/characters/froggy/pop.png new file mode 100644 index 00000000..888f528f Binary files /dev/null and b/default_data/characters/froggy/pop.png differ diff --git a/characters/froggy/portrait.png b/default_data/characters/froggy/portrait.png similarity index 100% rename from characters/froggy/portrait.png rename to default_data/characters/froggy/portrait.png diff --git a/characters/froggy/right.png b/default_data/characters/froggy/right.png similarity index 100% rename from characters/froggy/right.png rename to default_data/characters/froggy/right.png diff --git a/characters/froggy/top.png b/default_data/characters/froggy/top.png similarity index 100% rename from characters/froggy/top.png rename to default_data/characters/froggy/top.png diff --git a/characters/froggy/topleft.png b/default_data/characters/froggy/topleft.png similarity index 100% rename from characters/froggy/topleft.png rename to default_data/characters/froggy/topleft.png diff --git a/characters/froggy/topright.png b/default_data/characters/froggy/topright.png similarity index 100% rename from characters/froggy/topright.png rename to default_data/characters/froggy/topright.png diff --git a/characters/hookbill/bot.png b/default_data/characters/hookbill/bot.png similarity index 100% rename from characters/hookbill/bot.png rename to default_data/characters/hookbill/bot.png diff --git a/characters/hookbill/botleft.png b/default_data/characters/hookbill/botleft.png similarity index 100% rename from characters/hookbill/botleft.png rename to default_data/characters/hookbill/botleft.png diff --git a/characters/hookbill/botright.png b/default_data/characters/hookbill/botright.png similarity index 100% rename from characters/hookbill/botright.png rename to default_data/characters/hookbill/botright.png diff --git a/default_data/characters/hookbill/burst.png b/default_data/characters/hookbill/burst.png new file mode 100644 index 00000000..4ae3ddd6 Binary files /dev/null and b/default_data/characters/hookbill/burst.png differ diff --git a/characters/hookbill/chain.ogg b/default_data/characters/hookbill/chain.ogg similarity index 100% rename from characters/hookbill/chain.ogg rename to default_data/characters/hookbill/chain.ogg diff --git a/default_data/characters/hookbill/config.json b/default_data/characters/hookbill/config.json new file mode 100644 index 00000000..d1db0ab6 --- /dev/null +++ b/default_data/characters/hookbill/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_hookbill", + "name":"Hookbill" +} \ No newline at end of file diff --git a/characters/hookbill/doubleface.png b/default_data/characters/hookbill/doubleface.png similarity index 100% rename from characters/hookbill/doubleface.png rename to default_data/characters/hookbill/doubleface.png diff --git a/characters/hookbill/face.png b/default_data/characters/hookbill/face.png similarity index 100% rename from characters/hookbill/face.png rename to default_data/characters/hookbill/face.png diff --git a/characters/hookbill/filler1.png b/default_data/characters/hookbill/filler1.png similarity index 100% rename from characters/hookbill/filler1.png rename to default_data/characters/hookbill/filler1.png diff --git a/characters/hookbill/filler2.png b/default_data/characters/hookbill/filler2.png similarity index 100% rename from characters/hookbill/filler2.png rename to default_data/characters/hookbill/filler2.png diff --git a/characters/hookbill/flash.png b/default_data/characters/hookbill/flash.png similarity index 100% rename from characters/hookbill/flash.png rename to default_data/characters/hookbill/flash.png diff --git a/characters/hookbill/icon.png b/default_data/characters/hookbill/icon.png similarity index 100% rename from characters/hookbill/icon.png rename to default_data/characters/hookbill/icon.png diff --git a/characters/hookbill/left.png b/default_data/characters/hookbill/left.png similarity index 100% rename from characters/hookbill/left.png rename to default_data/characters/hookbill/left.png diff --git a/characters/hookbill/pop.png b/default_data/characters/hookbill/pop.png similarity index 100% rename from characters/hookbill/pop.png rename to default_data/characters/hookbill/pop.png diff --git a/characters/hookbill/portrait.png b/default_data/characters/hookbill/portrait.png similarity index 100% rename from characters/hookbill/portrait.png rename to default_data/characters/hookbill/portrait.png diff --git a/characters/hookbill/right.png b/default_data/characters/hookbill/right.png similarity index 100% rename from characters/hookbill/right.png rename to default_data/characters/hookbill/right.png diff --git a/characters/hookbill/top.png b/default_data/characters/hookbill/top.png similarity index 100% rename from characters/hookbill/top.png rename to default_data/characters/hookbill/top.png diff --git a/characters/hookbill/topleft.png b/default_data/characters/hookbill/topleft.png similarity index 100% rename from characters/hookbill/topleft.png rename to default_data/characters/hookbill/topleft.png diff --git a/characters/hookbill/topright.png b/default_data/characters/hookbill/topright.png similarity index 100% rename from characters/hookbill/topright.png rename to default_data/characters/hookbill/topright.png diff --git a/characters/kamek/bot.png b/default_data/characters/kamek/bot.png similarity index 100% rename from characters/kamek/bot.png rename to default_data/characters/kamek/bot.png diff --git a/characters/kamek/botleft.png b/default_data/characters/kamek/botleft.png similarity index 100% rename from characters/kamek/botleft.png rename to default_data/characters/kamek/botleft.png diff --git a/characters/kamek/botright.png b/default_data/characters/kamek/botright.png similarity index 100% rename from characters/kamek/botright.png rename to default_data/characters/kamek/botright.png diff --git a/default_data/characters/kamek/burst.png b/default_data/characters/kamek/burst.png new file mode 100644 index 00000000..c6a8066d Binary files /dev/null and b/default_data/characters/kamek/burst.png differ diff --git a/characters/kamek/chain.ogg b/default_data/characters/kamek/chain.ogg similarity index 100% rename from characters/kamek/chain.ogg rename to default_data/characters/kamek/chain.ogg diff --git a/default_data/characters/kamek/config.json b/default_data/characters/kamek/config.json new file mode 100644 index 00000000..bf606d03 --- /dev/null +++ b/default_data/characters/kamek/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_kamek", + "name":"Kamek" +} \ No newline at end of file diff --git a/characters/kamek/doubleface.png b/default_data/characters/kamek/doubleface.png similarity index 100% rename from characters/kamek/doubleface.png rename to default_data/characters/kamek/doubleface.png diff --git a/characters/kamek/face.png b/default_data/characters/kamek/face.png similarity index 100% rename from characters/kamek/face.png rename to default_data/characters/kamek/face.png diff --git a/characters/kamek/filler1.png b/default_data/characters/kamek/filler1.png similarity index 100% rename from characters/kamek/filler1.png rename to default_data/characters/kamek/filler1.png diff --git a/characters/kamek/filler2.png b/default_data/characters/kamek/filler2.png similarity index 100% rename from characters/kamek/filler2.png rename to default_data/characters/kamek/filler2.png diff --git a/characters/kamek/flash.png b/default_data/characters/kamek/flash.png similarity index 100% rename from characters/kamek/flash.png rename to default_data/characters/kamek/flash.png diff --git a/characters/kamek/icon.png b/default_data/characters/kamek/icon.png similarity index 100% rename from characters/kamek/icon.png rename to default_data/characters/kamek/icon.png diff --git a/characters/kamek/left.png b/default_data/characters/kamek/left.png similarity index 100% rename from characters/kamek/left.png rename to default_data/characters/kamek/left.png diff --git a/characters/kamek/pop.png b/default_data/characters/kamek/pop.png similarity index 100% rename from characters/kamek/pop.png rename to default_data/characters/kamek/pop.png diff --git a/characters/kamek/portrait.png b/default_data/characters/kamek/portrait.png similarity index 100% rename from characters/kamek/portrait.png rename to default_data/characters/kamek/portrait.png diff --git a/characters/kamek/right.png b/default_data/characters/kamek/right.png similarity index 100% rename from characters/kamek/right.png rename to default_data/characters/kamek/right.png diff --git a/characters/kamek/top.png b/default_data/characters/kamek/top.png similarity index 100% rename from characters/kamek/top.png rename to default_data/characters/kamek/top.png diff --git a/characters/kamek/topleft.png b/default_data/characters/kamek/topleft.png similarity index 100% rename from characters/kamek/topleft.png rename to default_data/characters/kamek/topleft.png diff --git a/characters/kamek/topright.png b/default_data/characters/kamek/topright.png similarity index 100% rename from characters/kamek/topright.png rename to default_data/characters/kamek/topright.png diff --git a/characters/lakitu/bot.png b/default_data/characters/lakitu/bot.png similarity index 100% rename from characters/lakitu/bot.png rename to default_data/characters/lakitu/bot.png diff --git a/characters/lakitu/botleft.png b/default_data/characters/lakitu/botleft.png similarity index 100% rename from characters/lakitu/botleft.png rename to default_data/characters/lakitu/botleft.png diff --git a/characters/lakitu/botright.png b/default_data/characters/lakitu/botright.png similarity index 100% rename from characters/lakitu/botright.png rename to default_data/characters/lakitu/botright.png diff --git a/default_data/characters/lakitu/burst.png b/default_data/characters/lakitu/burst.png new file mode 100644 index 00000000..b95de274 Binary files /dev/null and b/default_data/characters/lakitu/burst.png differ diff --git a/characters/lakitu/chain.ogg b/default_data/characters/lakitu/chain.ogg similarity index 100% rename from characters/lakitu/chain.ogg rename to default_data/characters/lakitu/chain.ogg diff --git a/default_data/characters/lakitu/config.json b/default_data/characters/lakitu/config.json new file mode 100644 index 00000000..94303877 --- /dev/null +++ b/default_data/characters/lakitu/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_lakitu", + "name":"Lakitu" +} \ No newline at end of file diff --git a/default_data/characters/lakitu/doubleface.png b/default_data/characters/lakitu/doubleface.png new file mode 100644 index 00000000..936d068f Binary files /dev/null and b/default_data/characters/lakitu/doubleface.png differ diff --git a/default_data/characters/lakitu/face.png b/default_data/characters/lakitu/face.png new file mode 100644 index 00000000..e3ef968a Binary files /dev/null and b/default_data/characters/lakitu/face.png differ diff --git a/characters/lakitu/filler1.png b/default_data/characters/lakitu/filler1.png similarity index 100% rename from characters/lakitu/filler1.png rename to default_data/characters/lakitu/filler1.png diff --git a/characters/lakitu/filler2.png b/default_data/characters/lakitu/filler2.png similarity index 100% rename from characters/lakitu/filler2.png rename to default_data/characters/lakitu/filler2.png diff --git a/characters/lakitu/flash.png b/default_data/characters/lakitu/flash.png similarity index 100% rename from characters/lakitu/flash.png rename to default_data/characters/lakitu/flash.png diff --git a/characters/lakitu/icon.png b/default_data/characters/lakitu/icon.png similarity index 100% rename from characters/lakitu/icon.png rename to default_data/characters/lakitu/icon.png diff --git a/characters/lakitu/left.png b/default_data/characters/lakitu/left.png similarity index 100% rename from characters/lakitu/left.png rename to default_data/characters/lakitu/left.png diff --git a/default_data/characters/lakitu/pop.png b/default_data/characters/lakitu/pop.png new file mode 100644 index 00000000..fccf2064 Binary files /dev/null and b/default_data/characters/lakitu/pop.png differ diff --git a/characters/lakitu/portrait.png b/default_data/characters/lakitu/portrait.png similarity index 100% rename from characters/lakitu/portrait.png rename to default_data/characters/lakitu/portrait.png diff --git a/characters/lakitu/right.png b/default_data/characters/lakitu/right.png similarity index 100% rename from characters/lakitu/right.png rename to default_data/characters/lakitu/right.png diff --git a/characters/lakitu/top.png b/default_data/characters/lakitu/top.png similarity index 100% rename from characters/lakitu/top.png rename to default_data/characters/lakitu/top.png diff --git a/characters/lakitu/topleft.png b/default_data/characters/lakitu/topleft.png similarity index 100% rename from characters/lakitu/topleft.png rename to default_data/characters/lakitu/topleft.png diff --git a/characters/lakitu/topright.png b/default_data/characters/lakitu/topright.png similarity index 100% rename from characters/lakitu/topright.png rename to default_data/characters/lakitu/topright.png diff --git a/characters/lip/bot.png b/default_data/characters/lip/bot.png similarity index 100% rename from characters/lip/bot.png rename to default_data/characters/lip/bot.png diff --git a/characters/lip/botleft.png b/default_data/characters/lip/botleft.png similarity index 100% rename from characters/lip/botleft.png rename to default_data/characters/lip/botleft.png diff --git a/characters/lip/botright.png b/default_data/characters/lip/botright.png similarity index 100% rename from characters/lip/botright.png rename to default_data/characters/lip/botright.png diff --git a/default_data/characters/lip/burst.png b/default_data/characters/lip/burst.png new file mode 100644 index 00000000..b7c03454 Binary files /dev/null and b/default_data/characters/lip/burst.png differ diff --git a/characters/lip/chain.ogg b/default_data/characters/lip/chain.ogg similarity index 100% rename from characters/lip/chain.ogg rename to default_data/characters/lip/chain.ogg diff --git a/default_data/characters/lip/config.json b/default_data/characters/lip/config.json new file mode 100644 index 00000000..856f9d30 --- /dev/null +++ b/default_data/characters/lip/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_lip", + "name":"Lip" +} \ No newline at end of file diff --git a/characters/lip/doubleface.png b/default_data/characters/lip/doubleface.png similarity index 100% rename from characters/lip/doubleface.png rename to default_data/characters/lip/doubleface.png diff --git a/characters/lip/face.png b/default_data/characters/lip/face.png similarity index 100% rename from characters/lip/face.png rename to default_data/characters/lip/face.png diff --git a/characters/lip/filler1.png b/default_data/characters/lip/filler1.png similarity index 100% rename from characters/lip/filler1.png rename to default_data/characters/lip/filler1.png diff --git a/characters/lip/filler2.png b/default_data/characters/lip/filler2.png similarity index 100% rename from characters/lip/filler2.png rename to default_data/characters/lip/filler2.png diff --git a/characters/lip/flash.png b/default_data/characters/lip/flash.png similarity index 100% rename from characters/lip/flash.png rename to default_data/characters/lip/flash.png diff --git a/characters/lip/icon.png b/default_data/characters/lip/icon.png similarity index 100% rename from characters/lip/icon.png rename to default_data/characters/lip/icon.png diff --git a/characters/lip/left.png b/default_data/characters/lip/left.png similarity index 100% rename from characters/lip/left.png rename to default_data/characters/lip/left.png diff --git a/characters/lip/pop.png b/default_data/characters/lip/pop.png similarity index 100% rename from characters/lip/pop.png rename to default_data/characters/lip/pop.png diff --git a/characters/lip/portrait.png b/default_data/characters/lip/portrait.png similarity index 100% rename from characters/lip/portrait.png rename to default_data/characters/lip/portrait.png diff --git a/characters/lip/right.png b/default_data/characters/lip/right.png similarity index 100% rename from characters/lip/right.png rename to default_data/characters/lip/right.png diff --git a/characters/lip/top.png b/default_data/characters/lip/top.png similarity index 100% rename from characters/lip/top.png rename to default_data/characters/lip/top.png diff --git a/characters/lip/topleft.png b/default_data/characters/lip/topleft.png similarity index 100% rename from characters/lip/topleft.png rename to default_data/characters/lip/topleft.png diff --git a/characters/lip/topright.png b/default_data/characters/lip/topright.png similarity index 100% rename from characters/lip/topright.png rename to default_data/characters/lip/topright.png diff --git a/characters/lungefish/bot.png b/default_data/characters/lungefish/bot.png similarity index 100% rename from characters/lungefish/bot.png rename to default_data/characters/lungefish/bot.png diff --git a/characters/lungefish/botleft.png b/default_data/characters/lungefish/botleft.png similarity index 100% rename from characters/lungefish/botleft.png rename to default_data/characters/lungefish/botleft.png diff --git a/characters/lungefish/botright.png b/default_data/characters/lungefish/botright.png similarity index 100% rename from characters/lungefish/botright.png rename to default_data/characters/lungefish/botright.png diff --git a/default_data/characters/lungefish/burst.png b/default_data/characters/lungefish/burst.png new file mode 100644 index 00000000..b4ab12aa Binary files /dev/null and b/default_data/characters/lungefish/burst.png differ diff --git a/characters/lungefish/chain.ogg b/default_data/characters/lungefish/chain.ogg similarity index 100% rename from characters/lungefish/chain.ogg rename to default_data/characters/lungefish/chain.ogg diff --git a/default_data/characters/lungefish/config.json b/default_data/characters/lungefish/config.json new file mode 100644 index 00000000..f8688dc6 --- /dev/null +++ b/default_data/characters/lungefish/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_lungefish", + "name":"Lungefish" +} \ No newline at end of file diff --git a/default_data/characters/lungefish/doubleface.png b/default_data/characters/lungefish/doubleface.png new file mode 100644 index 00000000..9ac40176 Binary files /dev/null and b/default_data/characters/lungefish/doubleface.png differ diff --git a/default_data/characters/lungefish/face.png b/default_data/characters/lungefish/face.png new file mode 100644 index 00000000..3cc2c9a8 Binary files /dev/null and b/default_data/characters/lungefish/face.png differ diff --git a/characters/lungefish/filler1.png b/default_data/characters/lungefish/filler1.png similarity index 100% rename from characters/lungefish/filler1.png rename to default_data/characters/lungefish/filler1.png diff --git a/characters/lungefish/filler2.png b/default_data/characters/lungefish/filler2.png similarity index 100% rename from characters/lungefish/filler2.png rename to default_data/characters/lungefish/filler2.png diff --git a/characters/lungefish/flash.png b/default_data/characters/lungefish/flash.png similarity index 100% rename from characters/lungefish/flash.png rename to default_data/characters/lungefish/flash.png diff --git a/characters/lungefish/icon.png b/default_data/characters/lungefish/icon.png similarity index 100% rename from characters/lungefish/icon.png rename to default_data/characters/lungefish/icon.png diff --git a/characters/lungefish/left.png b/default_data/characters/lungefish/left.png similarity index 100% rename from characters/lungefish/left.png rename to default_data/characters/lungefish/left.png diff --git a/default_data/characters/lungefish/pop.png b/default_data/characters/lungefish/pop.png new file mode 100644 index 00000000..f93a591b Binary files /dev/null and b/default_data/characters/lungefish/pop.png differ diff --git a/characters/lungefish/portrait.png b/default_data/characters/lungefish/portrait.png similarity index 100% rename from characters/lungefish/portrait.png rename to default_data/characters/lungefish/portrait.png diff --git a/characters/lungefish/right.png b/default_data/characters/lungefish/right.png similarity index 100% rename from characters/lungefish/right.png rename to default_data/characters/lungefish/right.png diff --git a/characters/lungefish/top.png b/default_data/characters/lungefish/top.png similarity index 100% rename from characters/lungefish/top.png rename to default_data/characters/lungefish/top.png diff --git a/characters/lungefish/topleft.png b/default_data/characters/lungefish/topleft.png similarity index 100% rename from characters/lungefish/topleft.png rename to default_data/characters/lungefish/topleft.png diff --git a/characters/lungefish/topright.png b/default_data/characters/lungefish/topright.png similarity index 100% rename from characters/lungefish/topright.png rename to default_data/characters/lungefish/topright.png diff --git a/characters/navalpiranha/bot.png b/default_data/characters/navalpiranha/bot.png similarity index 100% rename from characters/navalpiranha/bot.png rename to default_data/characters/navalpiranha/bot.png diff --git a/characters/navalpiranha/botleft.png b/default_data/characters/navalpiranha/botleft.png similarity index 100% rename from characters/navalpiranha/botleft.png rename to default_data/characters/navalpiranha/botleft.png diff --git a/characters/navalpiranha/botright.png b/default_data/characters/navalpiranha/botright.png similarity index 100% rename from characters/navalpiranha/botright.png rename to default_data/characters/navalpiranha/botright.png diff --git a/default_data/characters/navalpiranha/burst.png b/default_data/characters/navalpiranha/burst.png new file mode 100644 index 00000000..64be9612 Binary files /dev/null and b/default_data/characters/navalpiranha/burst.png differ diff --git a/characters/navalpiranha/chain.ogg b/default_data/characters/navalpiranha/chain.ogg similarity index 100% rename from characters/navalpiranha/chain.ogg rename to default_data/characters/navalpiranha/chain.ogg diff --git a/default_data/characters/navalpiranha/config.json b/default_data/characters/navalpiranha/config.json new file mode 100644 index 00000000..ec06113a --- /dev/null +++ b/default_data/characters/navalpiranha/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_navalpiranha", + "name":"NavalPiranha" +} \ No newline at end of file diff --git a/characters/navalpiranha/doubleface.png b/default_data/characters/navalpiranha/doubleface.png similarity index 100% rename from characters/navalpiranha/doubleface.png rename to default_data/characters/navalpiranha/doubleface.png diff --git a/characters/navalpiranha/face.png b/default_data/characters/navalpiranha/face.png similarity index 100% rename from characters/navalpiranha/face.png rename to default_data/characters/navalpiranha/face.png diff --git a/characters/navalpiranha/filler1.png b/default_data/characters/navalpiranha/filler1.png similarity index 100% rename from characters/navalpiranha/filler1.png rename to default_data/characters/navalpiranha/filler1.png diff --git a/characters/navalpiranha/filler2.png b/default_data/characters/navalpiranha/filler2.png similarity index 100% rename from characters/navalpiranha/filler2.png rename to default_data/characters/navalpiranha/filler2.png diff --git a/characters/navalpiranha/flash.png b/default_data/characters/navalpiranha/flash.png similarity index 100% rename from characters/navalpiranha/flash.png rename to default_data/characters/navalpiranha/flash.png diff --git a/characters/navalpiranha/icon.png b/default_data/characters/navalpiranha/icon.png similarity index 100% rename from characters/navalpiranha/icon.png rename to default_data/characters/navalpiranha/icon.png diff --git a/characters/navalpiranha/left.png b/default_data/characters/navalpiranha/left.png similarity index 100% rename from characters/navalpiranha/left.png rename to default_data/characters/navalpiranha/left.png diff --git a/characters/navalpiranha/pop.png b/default_data/characters/navalpiranha/pop.png similarity index 100% rename from characters/navalpiranha/pop.png rename to default_data/characters/navalpiranha/pop.png diff --git a/characters/navalpiranha/portrait.png b/default_data/characters/navalpiranha/portrait.png similarity index 100% rename from characters/navalpiranha/portrait.png rename to default_data/characters/navalpiranha/portrait.png diff --git a/characters/navalpiranha/right.png b/default_data/characters/navalpiranha/right.png similarity index 100% rename from characters/navalpiranha/right.png rename to default_data/characters/navalpiranha/right.png diff --git a/characters/navalpiranha/top.png b/default_data/characters/navalpiranha/top.png similarity index 100% rename from characters/navalpiranha/top.png rename to default_data/characters/navalpiranha/top.png diff --git a/characters/navalpiranha/topleft.png b/default_data/characters/navalpiranha/topleft.png similarity index 100% rename from characters/navalpiranha/topleft.png rename to default_data/characters/navalpiranha/topleft.png diff --git a/characters/navalpiranha/topright.png b/default_data/characters/navalpiranha/topright.png similarity index 100% rename from characters/navalpiranha/topright.png rename to default_data/characters/navalpiranha/topright.png diff --git a/characters/neris/bot.png b/default_data/characters/neris/bot.png similarity index 100% rename from characters/neris/bot.png rename to default_data/characters/neris/bot.png diff --git a/characters/neris/botleft.png b/default_data/characters/neris/botleft.png similarity index 100% rename from characters/neris/botleft.png rename to default_data/characters/neris/botleft.png diff --git a/characters/neris/botright.png b/default_data/characters/neris/botright.png similarity index 100% rename from characters/neris/botright.png rename to default_data/characters/neris/botright.png diff --git a/default_data/characters/neris/burst.png b/default_data/characters/neris/burst.png new file mode 100644 index 00000000..b4ab12aa Binary files /dev/null and b/default_data/characters/neris/burst.png differ diff --git a/characters/neris/chain.ogg b/default_data/characters/neris/chain.ogg similarity index 100% rename from characters/neris/chain.ogg rename to default_data/characters/neris/chain.ogg diff --git a/default_data/characters/neris/config.json b/default_data/characters/neris/config.json new file mode 100644 index 00000000..1f231a06 --- /dev/null +++ b/default_data/characters/neris/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_neris", + "name":"Neris" +} \ No newline at end of file diff --git a/characters/lungefish/doubleface.png b/default_data/characters/neris/doubleface.png similarity index 100% rename from characters/lungefish/doubleface.png rename to default_data/characters/neris/doubleface.png diff --git a/characters/lungefish/face.png b/default_data/characters/neris/face.png similarity index 100% rename from characters/lungefish/face.png rename to default_data/characters/neris/face.png diff --git a/characters/neris/filler1.png b/default_data/characters/neris/filler1.png similarity index 100% rename from characters/neris/filler1.png rename to default_data/characters/neris/filler1.png diff --git a/characters/neris/filler2.png b/default_data/characters/neris/filler2.png similarity index 100% rename from characters/neris/filler2.png rename to default_data/characters/neris/filler2.png diff --git a/characters/neris/flash.png b/default_data/characters/neris/flash.png similarity index 100% rename from characters/neris/flash.png rename to default_data/characters/neris/flash.png diff --git a/characters/neris/icon.png b/default_data/characters/neris/icon.png similarity index 100% rename from characters/neris/icon.png rename to default_data/characters/neris/icon.png diff --git a/characters/neris/left.png b/default_data/characters/neris/left.png similarity index 100% rename from characters/neris/left.png rename to default_data/characters/neris/left.png diff --git a/characters/lungefish/pop.png b/default_data/characters/neris/pop.png similarity index 100% rename from characters/lungefish/pop.png rename to default_data/characters/neris/pop.png diff --git a/characters/neris/portrait.png b/default_data/characters/neris/portrait.png similarity index 100% rename from characters/neris/portrait.png rename to default_data/characters/neris/portrait.png diff --git a/characters/neris/right.png b/default_data/characters/neris/right.png similarity index 100% rename from characters/neris/right.png rename to default_data/characters/neris/right.png diff --git a/characters/neris/top.png b/default_data/characters/neris/top.png similarity index 100% rename from characters/neris/top.png rename to default_data/characters/neris/top.png diff --git a/characters/neris/topleft.png b/default_data/characters/neris/topleft.png similarity index 100% rename from characters/neris/topleft.png rename to default_data/characters/neris/topleft.png diff --git a/characters/neris/topright.png b/default_data/characters/neris/topright.png similarity index 100% rename from characters/neris/topright.png rename to default_data/characters/neris/topright.png diff --git a/characters/phoenix/bot.png b/default_data/characters/phoenix/bot.png similarity index 100% rename from characters/phoenix/bot.png rename to default_data/characters/phoenix/bot.png diff --git a/characters/phoenix/botleft.png b/default_data/characters/phoenix/botleft.png similarity index 100% rename from characters/phoenix/botleft.png rename to default_data/characters/phoenix/botleft.png diff --git a/characters/phoenix/botright.png b/default_data/characters/phoenix/botright.png similarity index 100% rename from characters/phoenix/botright.png rename to default_data/characters/phoenix/botright.png diff --git a/default_data/characters/phoenix/burst.png b/default_data/characters/phoenix/burst.png new file mode 100644 index 00000000..df41ac55 Binary files /dev/null and b/default_data/characters/phoenix/burst.png differ diff --git a/characters/phoenix/chain.ogg b/default_data/characters/phoenix/chain.ogg similarity index 100% rename from characters/phoenix/chain.ogg rename to default_data/characters/phoenix/chain.ogg diff --git a/default_data/characters/phoenix/config.json b/default_data/characters/phoenix/config.json new file mode 100644 index 00000000..91b39a7f --- /dev/null +++ b/default_data/characters/phoenix/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_phoenix", + "name":"Phoenix" +} \ No newline at end of file diff --git a/characters/phoenix/doubleface.png b/default_data/characters/phoenix/doubleface.png similarity index 100% rename from characters/phoenix/doubleface.png rename to default_data/characters/phoenix/doubleface.png diff --git a/characters/phoenix/face.png b/default_data/characters/phoenix/face.png similarity index 100% rename from characters/phoenix/face.png rename to default_data/characters/phoenix/face.png diff --git a/characters/phoenix/filler1.png b/default_data/characters/phoenix/filler1.png similarity index 100% rename from characters/phoenix/filler1.png rename to default_data/characters/phoenix/filler1.png diff --git a/characters/phoenix/filler2.png b/default_data/characters/phoenix/filler2.png similarity index 100% rename from characters/phoenix/filler2.png rename to default_data/characters/phoenix/filler2.png diff --git a/characters/phoenix/flash.png b/default_data/characters/phoenix/flash.png similarity index 100% rename from characters/phoenix/flash.png rename to default_data/characters/phoenix/flash.png diff --git a/characters/phoenix/icon.png b/default_data/characters/phoenix/icon.png similarity index 100% rename from characters/phoenix/icon.png rename to default_data/characters/phoenix/icon.png diff --git a/characters/phoenix/left.png b/default_data/characters/phoenix/left.png similarity index 100% rename from characters/phoenix/left.png rename to default_data/characters/phoenix/left.png diff --git a/characters/phoenix/pop.png b/default_data/characters/phoenix/pop.png similarity index 100% rename from characters/phoenix/pop.png rename to default_data/characters/phoenix/pop.png diff --git a/characters/phoenix/portrait.png b/default_data/characters/phoenix/portrait.png similarity index 100% rename from characters/phoenix/portrait.png rename to default_data/characters/phoenix/portrait.png diff --git a/characters/phoenix/right.png b/default_data/characters/phoenix/right.png similarity index 100% rename from characters/phoenix/right.png rename to default_data/characters/phoenix/right.png diff --git a/characters/phoenix/top.png b/default_data/characters/phoenix/top.png similarity index 100% rename from characters/phoenix/top.png rename to default_data/characters/phoenix/top.png diff --git a/characters/phoenix/topleft.png b/default_data/characters/phoenix/topleft.png similarity index 100% rename from characters/phoenix/topleft.png rename to default_data/characters/phoenix/topleft.png diff --git a/characters/phoenix/topright.png b/default_data/characters/phoenix/topright.png similarity index 100% rename from characters/phoenix/topright.png rename to default_data/characters/phoenix/topright.png diff --git a/characters/poochy/bot.png b/default_data/characters/poochy/bot.png similarity index 100% rename from characters/poochy/bot.png rename to default_data/characters/poochy/bot.png diff --git a/characters/poochy/botleft.png b/default_data/characters/poochy/botleft.png similarity index 100% rename from characters/poochy/botleft.png rename to default_data/characters/poochy/botleft.png diff --git a/characters/poochy/botright.png b/default_data/characters/poochy/botright.png similarity index 100% rename from characters/poochy/botright.png rename to default_data/characters/poochy/botright.png diff --git a/default_data/characters/poochy/burst.png b/default_data/characters/poochy/burst.png new file mode 100644 index 00000000..7649abc4 Binary files /dev/null and b/default_data/characters/poochy/burst.png differ diff --git a/characters/poochy/chain.ogg b/default_data/characters/poochy/chain.ogg similarity index 100% rename from characters/poochy/chain.ogg rename to default_data/characters/poochy/chain.ogg diff --git a/default_data/characters/poochy/config.json b/default_data/characters/poochy/config.json new file mode 100644 index 00000000..2e4140f9 --- /dev/null +++ b/default_data/characters/poochy/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_poochy", + "name":"Poochy" +} \ No newline at end of file diff --git a/default_data/characters/poochy/doubleface.png b/default_data/characters/poochy/doubleface.png new file mode 100644 index 00000000..5c3f16fe Binary files /dev/null and b/default_data/characters/poochy/doubleface.png differ diff --git a/default_data/characters/poochy/face.png b/default_data/characters/poochy/face.png new file mode 100644 index 00000000..09b84e79 Binary files /dev/null and b/default_data/characters/poochy/face.png differ diff --git a/characters/poochy/filler1.png b/default_data/characters/poochy/filler1.png similarity index 100% rename from characters/poochy/filler1.png rename to default_data/characters/poochy/filler1.png diff --git a/characters/poochy/filler2.png b/default_data/characters/poochy/filler2.png similarity index 100% rename from characters/poochy/filler2.png rename to default_data/characters/poochy/filler2.png diff --git a/characters/poochy/flash.png b/default_data/characters/poochy/flash.png similarity index 100% rename from characters/poochy/flash.png rename to default_data/characters/poochy/flash.png diff --git a/characters/poochy/icon.png b/default_data/characters/poochy/icon.png similarity index 100% rename from characters/poochy/icon.png rename to default_data/characters/poochy/icon.png diff --git a/characters/poochy/left.png b/default_data/characters/poochy/left.png similarity index 100% rename from characters/poochy/left.png rename to default_data/characters/poochy/left.png diff --git a/default_data/characters/poochy/pop.png b/default_data/characters/poochy/pop.png new file mode 100644 index 00000000..f60d389c Binary files /dev/null and b/default_data/characters/poochy/pop.png differ diff --git a/characters/poochy/portrait.png b/default_data/characters/poochy/portrait.png similarity index 100% rename from characters/poochy/portrait.png rename to default_data/characters/poochy/portrait.png diff --git a/characters/poochy/right.png b/default_data/characters/poochy/right.png similarity index 100% rename from characters/poochy/right.png rename to default_data/characters/poochy/right.png diff --git a/characters/poochy/top.png b/default_data/characters/poochy/top.png similarity index 100% rename from characters/poochy/top.png rename to default_data/characters/poochy/top.png diff --git a/characters/poochy/topleft.png b/default_data/characters/poochy/topleft.png similarity index 100% rename from characters/poochy/topleft.png rename to default_data/characters/poochy/topleft.png diff --git a/characters/poochy/topright.png b/default_data/characters/poochy/topright.png similarity index 100% rename from characters/poochy/topright.png rename to default_data/characters/poochy/topright.png diff --git a/characters/raphael/bot.png b/default_data/characters/raphael/bot.png similarity index 100% rename from characters/raphael/bot.png rename to default_data/characters/raphael/bot.png diff --git a/characters/raphael/botleft.png b/default_data/characters/raphael/botleft.png similarity index 100% rename from characters/raphael/botleft.png rename to default_data/characters/raphael/botleft.png diff --git a/characters/raphael/botright.png b/default_data/characters/raphael/botright.png similarity index 100% rename from characters/raphael/botright.png rename to default_data/characters/raphael/botright.png diff --git a/default_data/characters/raphael/burst.png b/default_data/characters/raphael/burst.png new file mode 100644 index 00000000..c3f59f8f Binary files /dev/null and b/default_data/characters/raphael/burst.png differ diff --git a/characters/raphael/chain.ogg b/default_data/characters/raphael/chain.ogg similarity index 100% rename from characters/raphael/chain.ogg rename to default_data/characters/raphael/chain.ogg diff --git a/default_data/characters/raphael/config.json b/default_data/characters/raphael/config.json new file mode 100644 index 00000000..12f08cf4 --- /dev/null +++ b/default_data/characters/raphael/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_raphael", + "name":"Raphael" +} \ No newline at end of file diff --git a/default_data/characters/raphael/doubleface.png b/default_data/characters/raphael/doubleface.png new file mode 100644 index 00000000..8346937f Binary files /dev/null and b/default_data/characters/raphael/doubleface.png differ diff --git a/default_data/characters/raphael/face.png b/default_data/characters/raphael/face.png new file mode 100644 index 00000000..d1356df9 Binary files /dev/null and b/default_data/characters/raphael/face.png differ diff --git a/characters/raphael/filler1.png b/default_data/characters/raphael/filler1.png similarity index 100% rename from characters/raphael/filler1.png rename to default_data/characters/raphael/filler1.png diff --git a/characters/raphael/filler2.png b/default_data/characters/raphael/filler2.png similarity index 100% rename from characters/raphael/filler2.png rename to default_data/characters/raphael/filler2.png diff --git a/characters/raphael/flash.png b/default_data/characters/raphael/flash.png similarity index 100% rename from characters/raphael/flash.png rename to default_data/characters/raphael/flash.png diff --git a/characters/raphael/icon.png b/default_data/characters/raphael/icon.png similarity index 100% rename from characters/raphael/icon.png rename to default_data/characters/raphael/icon.png diff --git a/characters/raphael/left.png b/default_data/characters/raphael/left.png similarity index 100% rename from characters/raphael/left.png rename to default_data/characters/raphael/left.png diff --git a/default_data/characters/raphael/pop.png b/default_data/characters/raphael/pop.png new file mode 100644 index 00000000..493f48c3 Binary files /dev/null and b/default_data/characters/raphael/pop.png differ diff --git a/characters/raphael/portrait.png b/default_data/characters/raphael/portrait.png similarity index 100% rename from characters/raphael/portrait.png rename to default_data/characters/raphael/portrait.png diff --git a/characters/raphael/right.png b/default_data/characters/raphael/right.png similarity index 100% rename from characters/raphael/right.png rename to default_data/characters/raphael/right.png diff --git a/characters/raphael/top.png b/default_data/characters/raphael/top.png similarity index 100% rename from characters/raphael/top.png rename to default_data/characters/raphael/top.png diff --git a/characters/raphael/topleft.png b/default_data/characters/raphael/topleft.png similarity index 100% rename from characters/raphael/topleft.png rename to default_data/characters/raphael/topleft.png diff --git a/characters/raphael/topright.png b/default_data/characters/raphael/topright.png similarity index 100% rename from characters/raphael/topright.png rename to default_data/characters/raphael/topright.png diff --git a/characters/ruby/bot.png b/default_data/characters/ruby/bot.png similarity index 100% rename from characters/ruby/bot.png rename to default_data/characters/ruby/bot.png diff --git a/characters/ruby/botleft.png b/default_data/characters/ruby/botleft.png similarity index 100% rename from characters/ruby/botleft.png rename to default_data/characters/ruby/botleft.png diff --git a/characters/ruby/botright.png b/default_data/characters/ruby/botright.png similarity index 100% rename from characters/ruby/botright.png rename to default_data/characters/ruby/botright.png diff --git a/default_data/characters/ruby/burst.png b/default_data/characters/ruby/burst.png new file mode 100644 index 00000000..b43c7856 Binary files /dev/null and b/default_data/characters/ruby/burst.png differ diff --git a/characters/ruby/chain.ogg b/default_data/characters/ruby/chain.ogg similarity index 100% rename from characters/ruby/chain.ogg rename to default_data/characters/ruby/chain.ogg diff --git a/default_data/characters/ruby/config.json b/default_data/characters/ruby/config.json new file mode 100644 index 00000000..6a5d908f --- /dev/null +++ b/default_data/characters/ruby/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_ruby", + "name":"Ruby" +} \ No newline at end of file diff --git a/characters/ruby/doubleface.png b/default_data/characters/ruby/doubleface.png similarity index 100% rename from characters/ruby/doubleface.png rename to default_data/characters/ruby/doubleface.png diff --git a/characters/ruby/face.png b/default_data/characters/ruby/face.png similarity index 100% rename from characters/ruby/face.png rename to default_data/characters/ruby/face.png diff --git a/characters/ruby/filler1.png b/default_data/characters/ruby/filler1.png similarity index 100% rename from characters/ruby/filler1.png rename to default_data/characters/ruby/filler1.png diff --git a/characters/ruby/filler2.png b/default_data/characters/ruby/filler2.png similarity index 100% rename from characters/ruby/filler2.png rename to default_data/characters/ruby/filler2.png diff --git a/characters/ruby/flash.png b/default_data/characters/ruby/flash.png similarity index 100% rename from characters/ruby/flash.png rename to default_data/characters/ruby/flash.png diff --git a/characters/ruby/icon.png b/default_data/characters/ruby/icon.png similarity index 100% rename from characters/ruby/icon.png rename to default_data/characters/ruby/icon.png diff --git a/characters/ruby/left.png b/default_data/characters/ruby/left.png similarity index 100% rename from characters/ruby/left.png rename to default_data/characters/ruby/left.png diff --git a/characters/ruby/pop.png b/default_data/characters/ruby/pop.png similarity index 100% rename from characters/ruby/pop.png rename to default_data/characters/ruby/pop.png diff --git a/characters/ruby/portrait.png b/default_data/characters/ruby/portrait.png similarity index 100% rename from characters/ruby/portrait.png rename to default_data/characters/ruby/portrait.png diff --git a/characters/ruby/right.png b/default_data/characters/ruby/right.png similarity index 100% rename from characters/ruby/right.png rename to default_data/characters/ruby/right.png diff --git a/characters/ruby/top.png b/default_data/characters/ruby/top.png similarity index 100% rename from characters/ruby/top.png rename to default_data/characters/ruby/top.png diff --git a/characters/ruby/topleft.png b/default_data/characters/ruby/topleft.png similarity index 100% rename from characters/ruby/topleft.png rename to default_data/characters/ruby/topleft.png diff --git a/characters/ruby/topright.png b/default_data/characters/ruby/topright.png similarity index 100% rename from characters/ruby/topright.png rename to default_data/characters/ruby/topright.png diff --git a/characters/seren/bot.png b/default_data/characters/seren/bot.png similarity index 100% rename from characters/seren/bot.png rename to default_data/characters/seren/bot.png diff --git a/characters/seren/botleft.png b/default_data/characters/seren/botleft.png similarity index 100% rename from characters/seren/botleft.png rename to default_data/characters/seren/botleft.png diff --git a/characters/seren/botright.png b/default_data/characters/seren/botright.png similarity index 100% rename from characters/seren/botright.png rename to default_data/characters/seren/botright.png diff --git a/default_data/characters/seren/burst.png b/default_data/characters/seren/burst.png new file mode 100644 index 00000000..c3f59f8f Binary files /dev/null and b/default_data/characters/seren/burst.png differ diff --git a/characters/seren/chain.ogg b/default_data/characters/seren/chain.ogg similarity index 100% rename from characters/seren/chain.ogg rename to default_data/characters/seren/chain.ogg diff --git a/default_data/characters/seren/config.json b/default_data/characters/seren/config.json new file mode 100644 index 00000000..93a96094 --- /dev/null +++ b/default_data/characters/seren/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_seren", + "name":"Seren" +} \ No newline at end of file diff --git a/characters/raphael/doubleface.png b/default_data/characters/seren/doubleface.png similarity index 100% rename from characters/raphael/doubleface.png rename to default_data/characters/seren/doubleface.png diff --git a/characters/raphael/face.png b/default_data/characters/seren/face.png similarity index 100% rename from characters/raphael/face.png rename to default_data/characters/seren/face.png diff --git a/characters/seren/filler1.png b/default_data/characters/seren/filler1.png similarity index 100% rename from characters/seren/filler1.png rename to default_data/characters/seren/filler1.png diff --git a/characters/seren/filler2.png b/default_data/characters/seren/filler2.png similarity index 100% rename from characters/seren/filler2.png rename to default_data/characters/seren/filler2.png diff --git a/characters/seren/flash.png b/default_data/characters/seren/flash.png similarity index 100% rename from characters/seren/flash.png rename to default_data/characters/seren/flash.png diff --git a/characters/seren/icon.png b/default_data/characters/seren/icon.png similarity index 100% rename from characters/seren/icon.png rename to default_data/characters/seren/icon.png diff --git a/characters/seren/left.png b/default_data/characters/seren/left.png similarity index 100% rename from characters/seren/left.png rename to default_data/characters/seren/left.png diff --git a/characters/raphael/pop.png b/default_data/characters/seren/pop.png similarity index 100% rename from characters/raphael/pop.png rename to default_data/characters/seren/pop.png diff --git a/characters/seren/portrait.png b/default_data/characters/seren/portrait.png similarity index 100% rename from characters/seren/portrait.png rename to default_data/characters/seren/portrait.png diff --git a/characters/seren/right.png b/default_data/characters/seren/right.png similarity index 100% rename from characters/seren/right.png rename to default_data/characters/seren/right.png diff --git a/characters/seren/top.png b/default_data/characters/seren/top.png similarity index 100% rename from characters/seren/top.png rename to default_data/characters/seren/top.png diff --git a/characters/seren/topleft.png b/default_data/characters/seren/topleft.png similarity index 100% rename from characters/seren/topleft.png rename to default_data/characters/seren/topleft.png diff --git a/characters/seren/topright.png b/default_data/characters/seren/topright.png similarity index 100% rename from characters/seren/topright.png rename to default_data/characters/seren/topright.png diff --git a/characters/sherbet/bot.png b/default_data/characters/sherbet/bot.png similarity index 100% rename from characters/sherbet/bot.png rename to default_data/characters/sherbet/bot.png diff --git a/characters/sherbet/botleft.png b/default_data/characters/sherbet/botleft.png similarity index 100% rename from characters/sherbet/botleft.png rename to default_data/characters/sherbet/botleft.png diff --git a/characters/sherbet/botright.png b/default_data/characters/sherbet/botright.png similarity index 100% rename from characters/sherbet/botright.png rename to default_data/characters/sherbet/botright.png diff --git a/default_data/characters/sherbet/burst.png b/default_data/characters/sherbet/burst.png new file mode 100644 index 00000000..6d1bbb90 Binary files /dev/null and b/default_data/characters/sherbet/burst.png differ diff --git a/characters/sherbet/chain.ogg b/default_data/characters/sherbet/chain.ogg similarity index 100% rename from characters/sherbet/chain.ogg rename to default_data/characters/sherbet/chain.ogg diff --git a/default_data/characters/sherbet/config.json b/default_data/characters/sherbet/config.json new file mode 100644 index 00000000..2f71f73e --- /dev/null +++ b/default_data/characters/sherbet/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_sherbet", + "name":"Sherbet" +} \ No newline at end of file diff --git a/characters/bumpty/doubleface.png b/default_data/characters/sherbet/doubleface.png similarity index 100% rename from characters/bumpty/doubleface.png rename to default_data/characters/sherbet/doubleface.png diff --git a/characters/bumpty/face.png b/default_data/characters/sherbet/face.png similarity index 100% rename from characters/bumpty/face.png rename to default_data/characters/sherbet/face.png diff --git a/characters/sherbet/filler1.png b/default_data/characters/sherbet/filler1.png similarity index 100% rename from characters/sherbet/filler1.png rename to default_data/characters/sherbet/filler1.png diff --git a/characters/sherbet/filler2.png b/default_data/characters/sherbet/filler2.png similarity index 100% rename from characters/sherbet/filler2.png rename to default_data/characters/sherbet/filler2.png diff --git a/characters/sherbet/flash.png b/default_data/characters/sherbet/flash.png similarity index 100% rename from characters/sherbet/flash.png rename to default_data/characters/sherbet/flash.png diff --git a/characters/sherbet/icon.png b/default_data/characters/sherbet/icon.png similarity index 100% rename from characters/sherbet/icon.png rename to default_data/characters/sherbet/icon.png diff --git a/characters/sherbet/left.png b/default_data/characters/sherbet/left.png similarity index 100% rename from characters/sherbet/left.png rename to default_data/characters/sherbet/left.png diff --git a/characters/bumpty/pop.png b/default_data/characters/sherbet/pop.png similarity index 100% rename from characters/bumpty/pop.png rename to default_data/characters/sherbet/pop.png diff --git a/characters/sherbet/portrait.png b/default_data/characters/sherbet/portrait.png similarity index 100% rename from characters/sherbet/portrait.png rename to default_data/characters/sherbet/portrait.png diff --git a/characters/sherbet/right.png b/default_data/characters/sherbet/right.png similarity index 100% rename from characters/sherbet/right.png rename to default_data/characters/sherbet/right.png diff --git a/characters/sherbet/top.png b/default_data/characters/sherbet/top.png similarity index 100% rename from characters/sherbet/top.png rename to default_data/characters/sherbet/top.png diff --git a/characters/sherbet/topleft.png b/default_data/characters/sherbet/topleft.png similarity index 100% rename from characters/sherbet/topleft.png rename to default_data/characters/sherbet/topleft.png diff --git a/characters/sherbet/topright.png b/default_data/characters/sherbet/topright.png similarity index 100% rename from characters/sherbet/topright.png rename to default_data/characters/sherbet/topright.png diff --git a/characters/thanatos/bot.png b/default_data/characters/thanatos/bot.png similarity index 100% rename from characters/thanatos/bot.png rename to default_data/characters/thanatos/bot.png diff --git a/characters/thanatos/botleft.png b/default_data/characters/thanatos/botleft.png similarity index 100% rename from characters/thanatos/botleft.png rename to default_data/characters/thanatos/botleft.png diff --git a/characters/thanatos/botright.png b/default_data/characters/thanatos/botright.png similarity index 100% rename from characters/thanatos/botright.png rename to default_data/characters/thanatos/botright.png diff --git a/default_data/characters/thanatos/burst.png b/default_data/characters/thanatos/burst.png new file mode 100644 index 00000000..610ed7d3 Binary files /dev/null and b/default_data/characters/thanatos/burst.png differ diff --git a/characters/thanatos/chain.ogg b/default_data/characters/thanatos/chain.ogg similarity index 100% rename from characters/thanatos/chain.ogg rename to default_data/characters/thanatos/chain.ogg diff --git a/default_data/characters/thanatos/config.json b/default_data/characters/thanatos/config.json new file mode 100644 index 00000000..87de6f00 --- /dev/null +++ b/default_data/characters/thanatos/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_thanatos", + "name":"Thanatos" +} \ No newline at end of file diff --git a/characters/thanatos/doubleface.png b/default_data/characters/thanatos/doubleface.png similarity index 100% rename from characters/thanatos/doubleface.png rename to default_data/characters/thanatos/doubleface.png diff --git a/characters/thanatos/face.png b/default_data/characters/thanatos/face.png similarity index 100% rename from characters/thanatos/face.png rename to default_data/characters/thanatos/face.png diff --git a/characters/thanatos/filler1.png b/default_data/characters/thanatos/filler1.png similarity index 100% rename from characters/thanatos/filler1.png rename to default_data/characters/thanatos/filler1.png diff --git a/characters/thanatos/filler2.png b/default_data/characters/thanatos/filler2.png similarity index 100% rename from characters/thanatos/filler2.png rename to default_data/characters/thanatos/filler2.png diff --git a/characters/thanatos/flash.png b/default_data/characters/thanatos/flash.png similarity index 100% rename from characters/thanatos/flash.png rename to default_data/characters/thanatos/flash.png diff --git a/characters/thanatos/icon.png b/default_data/characters/thanatos/icon.png similarity index 100% rename from characters/thanatos/icon.png rename to default_data/characters/thanatos/icon.png diff --git a/characters/thanatos/left.png b/default_data/characters/thanatos/left.png similarity index 100% rename from characters/thanatos/left.png rename to default_data/characters/thanatos/left.png diff --git a/characters/thanatos/pop.png b/default_data/characters/thanatos/pop.png similarity index 100% rename from characters/thanatos/pop.png rename to default_data/characters/thanatos/pop.png diff --git a/characters/thanatos/portrait.png b/default_data/characters/thanatos/portrait.png similarity index 100% rename from characters/thanatos/portrait.png rename to default_data/characters/thanatos/portrait.png diff --git a/characters/thanatos/right.png b/default_data/characters/thanatos/right.png similarity index 100% rename from characters/thanatos/right.png rename to default_data/characters/thanatos/right.png diff --git a/characters/thanatos/top.png b/default_data/characters/thanatos/top.png similarity index 100% rename from characters/thanatos/top.png rename to default_data/characters/thanatos/top.png diff --git a/characters/thanatos/topleft.png b/default_data/characters/thanatos/topleft.png similarity index 100% rename from characters/thanatos/topleft.png rename to default_data/characters/thanatos/topleft.png diff --git a/characters/thanatos/topright.png b/default_data/characters/thanatos/topright.png similarity index 100% rename from characters/thanatos/topright.png rename to default_data/characters/thanatos/topright.png diff --git a/characters/thiana/bot.png b/default_data/characters/thiana/bot.png similarity index 100% rename from characters/thiana/bot.png rename to default_data/characters/thiana/bot.png diff --git a/characters/thiana/botleft.png b/default_data/characters/thiana/botleft.png similarity index 100% rename from characters/thiana/botleft.png rename to default_data/characters/thiana/botleft.png diff --git a/characters/thiana/botright.png b/default_data/characters/thiana/botright.png similarity index 100% rename from characters/thiana/botright.png rename to default_data/characters/thiana/botright.png diff --git a/default_data/characters/thiana/burst.png b/default_data/characters/thiana/burst.png new file mode 100644 index 00000000..7649abc4 Binary files /dev/null and b/default_data/characters/thiana/burst.png differ diff --git a/characters/thiana/chain.ogg b/default_data/characters/thiana/chain.ogg similarity index 100% rename from characters/thiana/chain.ogg rename to default_data/characters/thiana/chain.ogg diff --git a/default_data/characters/thiana/config.json b/default_data/characters/thiana/config.json new file mode 100644 index 00000000..297a54dc --- /dev/null +++ b/default_data/characters/thiana/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_thiana", + "name":"Thiana" +} \ No newline at end of file diff --git a/characters/poochy/doubleface.png b/default_data/characters/thiana/doubleface.png similarity index 100% rename from characters/poochy/doubleface.png rename to default_data/characters/thiana/doubleface.png diff --git a/characters/poochy/face.png b/default_data/characters/thiana/face.png similarity index 100% rename from characters/poochy/face.png rename to default_data/characters/thiana/face.png diff --git a/characters/thiana/filler1.png b/default_data/characters/thiana/filler1.png similarity index 100% rename from characters/thiana/filler1.png rename to default_data/characters/thiana/filler1.png diff --git a/characters/thiana/filler2.png b/default_data/characters/thiana/filler2.png similarity index 100% rename from characters/thiana/filler2.png rename to default_data/characters/thiana/filler2.png diff --git a/characters/thiana/flash.png b/default_data/characters/thiana/flash.png similarity index 100% rename from characters/thiana/flash.png rename to default_data/characters/thiana/flash.png diff --git a/characters/thiana/icon.png b/default_data/characters/thiana/icon.png similarity index 100% rename from characters/thiana/icon.png rename to default_data/characters/thiana/icon.png diff --git a/characters/thiana/left.png b/default_data/characters/thiana/left.png similarity index 100% rename from characters/thiana/left.png rename to default_data/characters/thiana/left.png diff --git a/characters/poochy/pop.png b/default_data/characters/thiana/pop.png similarity index 100% rename from characters/poochy/pop.png rename to default_data/characters/thiana/pop.png diff --git a/characters/thiana/portrait.png b/default_data/characters/thiana/portrait.png similarity index 100% rename from characters/thiana/portrait.png rename to default_data/characters/thiana/portrait.png diff --git a/characters/thiana/right.png b/default_data/characters/thiana/right.png similarity index 100% rename from characters/thiana/right.png rename to default_data/characters/thiana/right.png diff --git a/characters/thiana/top.png b/default_data/characters/thiana/top.png similarity index 100% rename from characters/thiana/top.png rename to default_data/characters/thiana/top.png diff --git a/characters/thiana/topleft.png b/default_data/characters/thiana/topleft.png similarity index 100% rename from characters/thiana/topleft.png rename to default_data/characters/thiana/topleft.png diff --git a/characters/thiana/topright.png b/default_data/characters/thiana/topright.png similarity index 100% rename from characters/thiana/topright.png rename to default_data/characters/thiana/topright.png diff --git a/default_data/characters/wiggler/bot.png b/default_data/characters/wiggler/bot.png new file mode 100644 index 00000000..85fb91f6 Binary files /dev/null and b/default_data/characters/wiggler/bot.png differ diff --git a/default_data/characters/wiggler/botleft.png b/default_data/characters/wiggler/botleft.png new file mode 100644 index 00000000..1acd7bb7 Binary files /dev/null and b/default_data/characters/wiggler/botleft.png differ diff --git a/default_data/characters/wiggler/botright.png b/default_data/characters/wiggler/botright.png new file mode 100644 index 00000000..7ac60c79 Binary files /dev/null and b/default_data/characters/wiggler/botright.png differ diff --git a/default_data/characters/wiggler/burst.png b/default_data/characters/wiggler/burst.png new file mode 100644 index 00000000..8901f197 Binary files /dev/null and b/default_data/characters/wiggler/burst.png differ diff --git a/characters/wiggler/chain.ogg b/default_data/characters/wiggler/chain.ogg similarity index 100% rename from characters/wiggler/chain.ogg rename to default_data/characters/wiggler/chain.ogg diff --git a/default_data/characters/wiggler/config.json b/default_data/characters/wiggler/config.json new file mode 100644 index 00000000..63bab023 --- /dev/null +++ b/default_data/characters/wiggler/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_wiggler", + "name":"Wiggler" +} \ No newline at end of file diff --git a/default_data/characters/wiggler/doubleface.png b/default_data/characters/wiggler/doubleface.png new file mode 100644 index 00000000..c82d6389 Binary files /dev/null and b/default_data/characters/wiggler/doubleface.png differ diff --git a/default_data/characters/wiggler/face.png b/default_data/characters/wiggler/face.png new file mode 100644 index 00000000..ff5db913 Binary files /dev/null and b/default_data/characters/wiggler/face.png differ diff --git a/default_data/characters/wiggler/filler1.png b/default_data/characters/wiggler/filler1.png new file mode 100644 index 00000000..1dccf284 Binary files /dev/null and b/default_data/characters/wiggler/filler1.png differ diff --git a/default_data/characters/wiggler/filler2.png b/default_data/characters/wiggler/filler2.png new file mode 100644 index 00000000..a68bf4f0 Binary files /dev/null and b/default_data/characters/wiggler/filler2.png differ diff --git a/characters/yoshi/flash.png b/default_data/characters/wiggler/flash.png similarity index 100% rename from characters/yoshi/flash.png rename to default_data/characters/wiggler/flash.png diff --git a/characters/wiggler/icon.png b/default_data/characters/wiggler/icon.png similarity index 100% rename from characters/wiggler/icon.png rename to default_data/characters/wiggler/icon.png diff --git a/default_data/characters/wiggler/left.png b/default_data/characters/wiggler/left.png new file mode 100644 index 00000000..3f75d751 Binary files /dev/null and b/default_data/characters/wiggler/left.png differ diff --git a/default_data/characters/wiggler/pop.png b/default_data/characters/wiggler/pop.png new file mode 100644 index 00000000..4159f2bb Binary files /dev/null and b/default_data/characters/wiggler/pop.png differ diff --git a/characters/wiggler/portrait.png b/default_data/characters/wiggler/portrait.png similarity index 100% rename from characters/wiggler/portrait.png rename to default_data/characters/wiggler/portrait.png diff --git a/default_data/characters/wiggler/right.png b/default_data/characters/wiggler/right.png new file mode 100644 index 00000000..c89ff873 Binary files /dev/null and b/default_data/characters/wiggler/right.png differ diff --git a/default_data/characters/wiggler/top.png b/default_data/characters/wiggler/top.png new file mode 100644 index 00000000..5a7e8e1c Binary files /dev/null and b/default_data/characters/wiggler/top.png differ diff --git a/default_data/characters/wiggler/topleft.png b/default_data/characters/wiggler/topleft.png new file mode 100644 index 00000000..80979ec5 Binary files /dev/null and b/default_data/characters/wiggler/topleft.png differ diff --git a/default_data/characters/wiggler/topright.png b/default_data/characters/wiggler/topright.png new file mode 100644 index 00000000..c14b044a Binary files /dev/null and b/default_data/characters/wiggler/topright.png differ diff --git a/characters/windy/bot.png b/default_data/characters/windy/bot.png similarity index 100% rename from characters/windy/bot.png rename to default_data/characters/windy/bot.png diff --git a/characters/windy/botleft.png b/default_data/characters/windy/botleft.png similarity index 100% rename from characters/windy/botleft.png rename to default_data/characters/windy/botleft.png diff --git a/characters/windy/botright.png b/default_data/characters/windy/botright.png similarity index 100% rename from characters/windy/botright.png rename to default_data/characters/windy/botright.png diff --git a/default_data/characters/windy/burst.png b/default_data/characters/windy/burst.png new file mode 100644 index 00000000..b95de274 Binary files /dev/null and b/default_data/characters/windy/burst.png differ diff --git a/characters/windy/chain.ogg b/default_data/characters/windy/chain.ogg similarity index 100% rename from characters/windy/chain.ogg rename to default_data/characters/windy/chain.ogg diff --git a/default_data/characters/windy/config.json b/default_data/characters/windy/config.json new file mode 100644 index 00000000..0d9df8d7 --- /dev/null +++ b/default_data/characters/windy/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_windy", + "name":"Windy" +} \ No newline at end of file diff --git a/characters/lakitu/doubleface.png b/default_data/characters/windy/doubleface.png similarity index 100% rename from characters/lakitu/doubleface.png rename to default_data/characters/windy/doubleface.png diff --git a/characters/lakitu/face.png b/default_data/characters/windy/face.png similarity index 100% rename from characters/lakitu/face.png rename to default_data/characters/windy/face.png diff --git a/characters/windy/filler1.png b/default_data/characters/windy/filler1.png similarity index 100% rename from characters/windy/filler1.png rename to default_data/characters/windy/filler1.png diff --git a/characters/windy/filler2.png b/default_data/characters/windy/filler2.png similarity index 100% rename from characters/windy/filler2.png rename to default_data/characters/windy/filler2.png diff --git a/characters/windy/flash.png b/default_data/characters/windy/flash.png similarity index 100% rename from characters/windy/flash.png rename to default_data/characters/windy/flash.png diff --git a/characters/windy/icon.png b/default_data/characters/windy/icon.png similarity index 100% rename from characters/windy/icon.png rename to default_data/characters/windy/icon.png diff --git a/characters/windy/left.png b/default_data/characters/windy/left.png similarity index 100% rename from characters/windy/left.png rename to default_data/characters/windy/left.png diff --git a/characters/lakitu/pop.png b/default_data/characters/windy/pop.png similarity index 100% rename from characters/lakitu/pop.png rename to default_data/characters/windy/pop.png diff --git a/characters/windy/portrait.png b/default_data/characters/windy/portrait.png similarity index 100% rename from characters/windy/portrait.png rename to default_data/characters/windy/portrait.png diff --git a/characters/windy/right.png b/default_data/characters/windy/right.png similarity index 100% rename from characters/windy/right.png rename to default_data/characters/windy/right.png diff --git a/characters/windy/top.png b/default_data/characters/windy/top.png similarity index 100% rename from characters/windy/top.png rename to default_data/characters/windy/top.png diff --git a/characters/windy/topleft.png b/default_data/characters/windy/topleft.png similarity index 100% rename from characters/windy/topleft.png rename to default_data/characters/windy/topleft.png diff --git a/characters/windy/topright.png b/default_data/characters/windy/topright.png similarity index 100% rename from characters/windy/topright.png rename to default_data/characters/windy/topright.png diff --git a/default_data/characters/yoshi/bot.png b/default_data/characters/yoshi/bot.png new file mode 100644 index 00000000..f7fdfb30 Binary files /dev/null and b/default_data/characters/yoshi/bot.png differ diff --git a/default_data/characters/yoshi/botleft.png b/default_data/characters/yoshi/botleft.png new file mode 100644 index 00000000..4f2b9239 Binary files /dev/null and b/default_data/characters/yoshi/botleft.png differ diff --git a/default_data/characters/yoshi/botright.png b/default_data/characters/yoshi/botright.png new file mode 100644 index 00000000..9fc847d9 Binary files /dev/null and b/default_data/characters/yoshi/botright.png differ diff --git a/default_data/characters/yoshi/burst.png b/default_data/characters/yoshi/burst.png new file mode 100644 index 00000000..1ca7d480 Binary files /dev/null and b/default_data/characters/yoshi/burst.png differ diff --git a/characters/yoshi/chain.ogg b/default_data/characters/yoshi/chain.ogg similarity index 100% rename from characters/yoshi/chain.ogg rename to default_data/characters/yoshi/chain.ogg diff --git a/default_data/characters/yoshi/config.json b/default_data/characters/yoshi/config.json new file mode 100644 index 00000000..e61c5452 --- /dev/null +++ b/default_data/characters/yoshi/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_characters_yoshi", + "name":"Yoshi" +} \ No newline at end of file diff --git a/characters/yoshi/danger_music.it b/default_data/characters/yoshi/danger_music.it similarity index 100% rename from characters/yoshi/danger_music.it rename to default_data/characters/yoshi/danger_music.it diff --git a/characters/yoshi/danger_start_music.it b/default_data/characters/yoshi/danger_start_music.it similarity index 100% rename from characters/yoshi/danger_start_music.it rename to default_data/characters/yoshi/danger_start_music.it diff --git a/default_data/characters/yoshi/doubleface.png b/default_data/characters/yoshi/doubleface.png new file mode 100644 index 00000000..898e41b7 Binary files /dev/null and b/default_data/characters/yoshi/doubleface.png differ diff --git a/default_data/characters/yoshi/face.png b/default_data/characters/yoshi/face.png new file mode 100644 index 00000000..7b64fb9f Binary files /dev/null and b/default_data/characters/yoshi/face.png differ diff --git a/default_data/characters/yoshi/filler1.png b/default_data/characters/yoshi/filler1.png new file mode 100644 index 00000000..70a34b3e Binary files /dev/null and b/default_data/characters/yoshi/filler1.png differ diff --git a/default_data/characters/yoshi/filler2.png b/default_data/characters/yoshi/filler2.png new file mode 100644 index 00000000..48b65767 Binary files /dev/null and b/default_data/characters/yoshi/filler2.png differ diff --git a/default_data/characters/yoshi/flash.png b/default_data/characters/yoshi/flash.png new file mode 100644 index 00000000..3cc54ffb Binary files /dev/null and b/default_data/characters/yoshi/flash.png differ diff --git a/characters/yoshi/icon.png b/default_data/characters/yoshi/icon.png similarity index 100% rename from characters/yoshi/icon.png rename to default_data/characters/yoshi/icon.png diff --git a/default_data/characters/yoshi/left.png b/default_data/characters/yoshi/left.png new file mode 100644 index 00000000..531e7d22 Binary files /dev/null and b/default_data/characters/yoshi/left.png differ diff --git a/characters/yoshi/normal_music.it b/default_data/characters/yoshi/normal_music.it similarity index 100% rename from characters/yoshi/normal_music.it rename to default_data/characters/yoshi/normal_music.it diff --git a/characters/yoshi/normal_music_start.it b/default_data/characters/yoshi/normal_music_start.it similarity index 100% rename from characters/yoshi/normal_music_start.it rename to default_data/characters/yoshi/normal_music_start.it diff --git a/default_data/characters/yoshi/pop.png b/default_data/characters/yoshi/pop.png new file mode 100644 index 00000000..17050e6d Binary files /dev/null and b/default_data/characters/yoshi/pop.png differ diff --git a/characters/yoshi/portrait.png b/default_data/characters/yoshi/portrait.png similarity index 100% rename from characters/yoshi/portrait.png rename to default_data/characters/yoshi/portrait.png diff --git a/default_data/characters/yoshi/right.png b/default_data/characters/yoshi/right.png new file mode 100644 index 00000000..193eaec2 Binary files /dev/null and b/default_data/characters/yoshi/right.png differ diff --git a/default_data/characters/yoshi/top.png b/default_data/characters/yoshi/top.png new file mode 100644 index 00000000..8c5346d7 Binary files /dev/null and b/default_data/characters/yoshi/top.png differ diff --git a/default_data/characters/yoshi/topleft.png b/default_data/characters/yoshi/topleft.png new file mode 100644 index 00000000..91feb858 Binary files /dev/null and b/default_data/characters/yoshi/topleft.png differ diff --git a/default_data/characters/yoshi/topright.png b/default_data/characters/yoshi/topright.png new file mode 100644 index 00000000..d923c34a Binary files /dev/null and b/default_data/characters/yoshi/topright.png differ diff --git a/default_data/stages/cave/config.json b/default_data/stages/cave/config.json new file mode 100644 index 00000000..2a772347 --- /dev/null +++ b/default_data/stages/cave/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_cave", + "name":"Cave" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/cave/danger_music.it b/default_data/stages/cave/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/cave/danger_music.it rename to default_data/stages/cave/danger_music.it diff --git a/sounds/Stock PdP_TA/music/cave/normal_music.it b/default_data/stages/cave/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/cave/normal_music.it rename to default_data/stages/cave/normal_music.it diff --git a/default_data/stages/fire/config.json b/default_data/stages/fire/config.json new file mode 100644 index 00000000..00054ed7 --- /dev/null +++ b/default_data/stages/fire/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_fire", + "name":"Fire" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/fire/danger_music.it b/default_data/stages/fire/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/fire/danger_music.it rename to default_data/stages/fire/danger_music.it diff --git a/sounds/Stock PdP_TA/music/fire/normal_music.it b/default_data/stages/fire/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/fire/normal_music.it rename to default_data/stages/fire/normal_music.it diff --git a/sounds/Stock PdP_TA/music/fire/normal_music_start.it b/default_data/stages/fire/normal_music_start.it similarity index 100% rename from sounds/Stock PdP_TA/music/fire/normal_music_start.it rename to default_data/stages/fire/normal_music_start.it diff --git a/default_data/stages/flower/config.json b/default_data/stages/flower/config.json new file mode 100644 index 00000000..acde2674 --- /dev/null +++ b/default_data/stages/flower/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_flower", + "name":"Flower" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/flower/danger_music.it b/default_data/stages/flower/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/flower/danger_music.it rename to default_data/stages/flower/danger_music.it diff --git a/sounds/Stock PdP_TA/music/flower/normal_music.it b/default_data/stages/flower/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/flower/normal_music.it rename to default_data/stages/flower/normal_music.it diff --git a/default_data/stages/forest/config.json b/default_data/stages/forest/config.json new file mode 100644 index 00000000..4e133280 --- /dev/null +++ b/default_data/stages/forest/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_forest", + "name":"Forest" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/forest/danger_music.it b/default_data/stages/forest/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/forest/danger_music.it rename to default_data/stages/forest/danger_music.it diff --git a/sounds/Stock PdP_TA/music/forest/danger_music_start.it b/default_data/stages/forest/danger_music_start.it similarity index 100% rename from sounds/Stock PdP_TA/music/forest/danger_music_start.it rename to default_data/stages/forest/danger_music_start.it diff --git a/sounds/Stock PdP_TA/music/forest/normal_music.it b/default_data/stages/forest/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/forest/normal_music.it rename to default_data/stages/forest/normal_music.it diff --git a/default_data/stages/ice/config.json b/default_data/stages/ice/config.json new file mode 100644 index 00000000..eb872502 --- /dev/null +++ b/default_data/stages/ice/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_ice", + "name":"Ice" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/ice/danger_music.it b/default_data/stages/ice/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/ice/danger_music.it rename to default_data/stages/ice/danger_music.it diff --git a/sounds/Stock PdP_TA/music/ice/normal_music.it b/default_data/stages/ice/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/ice/normal_music.it rename to default_data/stages/ice/normal_music.it diff --git a/default_data/stages/jewel/config.json b/default_data/stages/jewel/config.json new file mode 100644 index 00000000..f0052850 --- /dev/null +++ b/default_data/stages/jewel/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_jewel", + "name":"Jewel" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/jewel/danger_music.it b/default_data/stages/jewel/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/jewel/danger_music.it rename to default_data/stages/jewel/danger_music.it diff --git a/sounds/Stock PdP_TA/music/jewel/normal_music.it b/default_data/stages/jewel/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/jewel/normal_music.it rename to default_data/stages/jewel/normal_music.it diff --git a/default_data/stages/king/config.json b/default_data/stages/king/config.json new file mode 100644 index 00000000..1d76e4f7 --- /dev/null +++ b/default_data/stages/king/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_king", + "name":"King" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/king/danger_music.it b/default_data/stages/king/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/king/danger_music.it rename to default_data/stages/king/danger_music.it diff --git a/sounds/Stock PdP_TA/music/king/normal_music.it b/default_data/stages/king/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/king/normal_music.it rename to default_data/stages/king/normal_music.it diff --git a/default_data/stages/moon/config.json b/default_data/stages/moon/config.json new file mode 100644 index 00000000..7440ee86 --- /dev/null +++ b/default_data/stages/moon/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_moon", + "name":"Moon" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/moon/danger_music.it b/default_data/stages/moon/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/moon/danger_music.it rename to default_data/stages/moon/danger_music.it diff --git a/sounds/Stock PdP_TA/music/moon/normal_music.it b/default_data/stages/moon/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/moon/normal_music.it rename to default_data/stages/moon/normal_music.it diff --git a/sounds/Stock PdP_TA/music/moon/normal_music_start.it b/default_data/stages/moon/normal_music_start.it similarity index 100% rename from sounds/Stock PdP_TA/music/moon/normal_music_start.it rename to default_data/stages/moon/normal_music_start.it diff --git a/default_data/stages/sea/config.json b/default_data/stages/sea/config.json new file mode 100644 index 00000000..ae6e84de --- /dev/null +++ b/default_data/stages/sea/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_sea", + "name":"Sea" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/sea/danger_music.it b/default_data/stages/sea/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/sea/danger_music.it rename to default_data/stages/sea/danger_music.it diff --git a/sounds/Stock PdP_TA/music/sea/normal_music.it b/default_data/stages/sea/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/sea/normal_music.it rename to default_data/stages/sea/normal_music.it diff --git a/default_data/stages/water/config.json b/default_data/stages/water/config.json new file mode 100644 index 00000000..7e190528 --- /dev/null +++ b/default_data/stages/water/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_water", + "name":"Water" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/water/danger_music.it b/default_data/stages/water/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/water/danger_music.it rename to default_data/stages/water/danger_music.it diff --git a/sounds/Stock PdP_TA/music/water/danger_music_start.it b/default_data/stages/water/danger_music_start.it similarity index 100% rename from sounds/Stock PdP_TA/music/water/danger_music_start.it rename to default_data/stages/water/danger_music_start.it diff --git a/sounds/Stock PdP_TA/music/water/normal_music.it b/default_data/stages/water/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/water/normal_music.it rename to default_data/stages/water/normal_music.it diff --git a/sounds/Stock PdP_TA/music/water/normal_music_start.it b/default_data/stages/water/normal_music_start.it similarity index 100% rename from sounds/Stock PdP_TA/music/water/normal_music_start.it rename to default_data/stages/water/normal_music_start.it diff --git a/default_data/stages/wind/config.json b/default_data/stages/wind/config.json new file mode 100644 index 00000000..bf02f6cd --- /dev/null +++ b/default_data/stages/wind/config.json @@ -0,0 +1,4 @@ +{ + "id":"pa_stages_wind", + "name":"Wind" +} \ No newline at end of file diff --git a/sounds/Stock PdP_TA/music/wind/danger_music.it b/default_data/stages/wind/danger_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/wind/danger_music.it rename to default_data/stages/wind/danger_music.it diff --git a/sounds/Stock PdP_TA/music/wind/normal_music.it b/default_data/stages/wind/normal_music.it similarity index 100% rename from sounds/Stock PdP_TA/music/wind/normal_music.it rename to default_data/stages/wind/normal_music.it diff --git a/engine.lua b/engine.lua index 2b1007bc..d9d9abf6 100644 --- a/engine.lua +++ b/engine.lua @@ -1,3 +1,5 @@ +local analytics = require("analytics") + -- Stuff defined in this file: -- . the data structures that store the configuration of -- the stack of panels @@ -15,9 +17,10 @@ local current_music_is_casual = false -- must be false so that casual music star Stack = class(function(s, which, mode, panels_dir, speed, difficulty, player_number) s.character = config.character s.max_health = 1 - s.panels_dir = panels_dir or config.panels_dir - if IMG_panels[panels_dir] == nil then - s.panels_dir = config.panels_dir + s.panels_dir = panels_dir or config.panels + s.portraitFade = 0 + if not panels[panels_dir] then + s.panels_dir = config.panels end s.mode = mode or "endless" if mode ~= "puzzle" then @@ -150,7 +153,13 @@ Stack = class(function(s, which, mode, panels_dir, speed, difficulty, player_num s.swap_1 = false -- attempt to initiate a swap on this frame s.swap_2 = false - s.cur_wait_time = 25 -- number of ticks to wait before the cursor begins + s.taunt_up = nil -- will hold an index + s.taunt_down = nil -- will hold an index + s.wait_for_not_taunting = nil -- will hold either "taunt_up" or "taunt_down" + s.wait_for_not_pausing = false -- wait for end of input + s.taunt_queue = Queue() + + s.cur_wait_time = config.input_repeat_delay -- number of ticks to wait before the cursor begins -- to move quickly... it's based on P1CurSensitivity s.cur_timer = 0 -- number of ticks for which a new direction's been pressed s.cur_dir = nil -- the direction pressed @@ -166,9 +175,13 @@ Stack = class(function(s, which, mode, panels_dir, speed, difficulty, player_num s.lastPopIndexPlayed = s.lastPopIndexPlayed or 1 s.combo_chain_play = nil s.game_over = false + s.sfx_land = false + s.sfx_garbage_thud = 0 s.card_q = Queue() + s.pop_q = Queue() + s.which = which or 1 -- Pk.which == k s.player_number = player_number or s.which --player number according to the multiplayer server, for game outcome reporting @@ -264,13 +277,27 @@ function Stack.fromcpy(self, other) self:remove_extra_rows() end +local MAX_TAUNT_PER_10_SEC = 4 + +function Stack.can_taunt(self) + return self.taunt_queue:len() < MAX_TAUNT_PER_10_SEC or self.taunt_queue:peek() + 10 < love.timer.getTime() +end + +function Stack.taunt(self,taunt_type) + while self.taunt_queue:len() >= MAX_TAUNT_PER_10_SEC do + self.taunt_queue:pop() + end + self.taunt_queue:push(love.timer.getTime()) + self.wait_for_not_taunting = taunt_type -- to avoid taunting multiple times with the same input +end + Panel = class(function(p) p:clear() end) function Panel.clear(self) -- color 0 is an empty panel. - -- colors 1-7 are normal colors, 8 is [!]. + -- colors 1-7 are normal colors, 8 is [!], 9 is garbage. self.color = 0 -- A panel's timer indicates for how many more frames it will: -- . be swapping @@ -457,7 +484,7 @@ do end function Panel.dangerous(self) - return self.color ~= 0 and (self.state ~= "falling" or not self.garbage) + return self.color ~= 0 and not (self.state == "falling" and self.garbage) end end @@ -591,6 +618,11 @@ end --local_run is for the stack that belongs to this client. function Stack.local_run(self) + if game_is_paused then + return + end + + self:update_popfxs() self:update_cards() self.input_state = self:send_controls() self:prep_rollback() @@ -601,6 +633,10 @@ end --foreign_run is for a stack that belongs to another client. function Stack.foreign_run(self) + if game_is_paused then -- foreign_run in replays! + return + end + -- Decide how many frames of input we should run. local times_to_run = 0 local buffer_len = string.len(self.input_buffer) @@ -624,6 +660,7 @@ function Stack.foreign_run(self) end end for i=1,times_to_run do + self:update_popfxs() self:update_cards() self.input_state = string.sub(self.input_buffer,1,1) self:prep_rollback() @@ -635,7 +672,32 @@ function Stack.foreign_run(self) end function Stack.enqueue_card(self, chain, x, y, n) - self.card_q:push({frame=1, chain=chain, x=x, y=y, n=n}) + card_burstAtlas = nil + card_burstParticle = nil + if config.popfx == true then + card_burstAtlas = characters[self.character].images["burst"] + card_burstFrameDimension = card_burstAtlas:getWidth()/9 + card_burstParticle = love.graphics.newQuad(card_burstFrameDimension, 0, card_burstFrameDimension, card_burstFrameDimension, card_burstAtlas:getDimensions()) + end + self.card_q:push({frame=1, chain=chain, x=x, y=y, n=n, burstAtlas = card_burstAtlas, burstParticle = card_burstParticle}) + +end + +function Stack.enqueue_popfx(self, x, y, popsize) + if characters[self.character].images["burst"] then + burstAtlas = characters[self.character].images["burst"] + burstFrameDimension = burstAtlas:getWidth()/9 + burstParticle = love.graphics.newQuad(burstFrameDimension, 0, burstFrameDimension, burstFrameDimension, burstAtlas:getDimensions()) + bigParticle = love.graphics.newQuad(0, 0, burstFrameDimension, burstFrameDimension, burstAtlas:getDimensions()) + end + if characters[self.character].images["fade"] then + fadeAtlas = characters[self.character].images["fade"] + fadeFrameDimension = fadeAtlas:getWidth()/9 + fadeParticle = love.graphics.newQuad(fadeFrameDimension, 0, fadeFrameDimension, fadeFrameDimension, fadeAtlas:getDimensions()) + end + poptype = "small" + self.pop_q:push({frame=1, burstAtlas = burstAtlas, burstFrameDimension = burstFrameDimension, burstParticle = burstParticle, + fadeAtlas = fadeAtlas, fadeFrameDimension = fadeFrameDimension, fadeParticle = fadeParticle, bigParticle = bigParticle, bigTimer = 0, popsize = popsize, x=x, y=y}) end local d_col = {up=0, down=0, left=-1, right=1} @@ -784,18 +846,22 @@ function Stack.PdP(self) self.top_cur_row = self.height self:new_row() end - + self.prev_rise_lock = self.rise_lock self.rise_lock = self.n_active_panels ~= 0 or self.prev_active_panels ~= 0 or self.shake_time ~= 0 or self.do_countdown or self.do_swap + if self.prev_rise_lock and not self.rise_lock then + self.prevent_manual_raise = false + end -- Increase the speed if applicable if self.speed_times then local time = self.speed_times[self.speed_times.idx] if self.CLOCK == time then self.speed = min(self.speed + 1, 99) + self.FRAMECOUNT_RISE = speed_to_rise_time[self.speed] if self.speed_times.idx ~= #self.speed_times then self.speed_times.idx = self.speed_times.idx + 1 else @@ -803,7 +869,7 @@ function Stack.PdP(self) end end elseif self.panels_to_speedup <= 0 then - self.speed = self.speed + 1 + self.speed = min(self.speed + 1, 99) self.panels_to_speedup = self.panels_to_speedup + panels_to_next_speed[self.speed] self.FRAMECOUNT_RISE = speed_to_rise_time[self.speed] @@ -864,6 +930,7 @@ function Stack.PdP(self) local skip_col = 0 local fallen_garbage = 0 local shake_time = 0 + popsize = "small" for row=1,#panels do for col=1,width do local cntinue = false @@ -877,7 +944,8 @@ function Stack.PdP(self) if panel.state == "matched" then panel.timer = panel.timer - 1 if panel.timer == panel.pop_time then - SFX_Garbage_Pop_Play = panel.pop_index + if config.popfx == true then self:enqueue_popfx(col, row, popsize) end + SFX_Garbage_Pop_Play = panel.pop_index end if panel.timer == 0 then if panel.y_offset == -1 then @@ -921,8 +989,8 @@ function Stack.PdP(self) if panel.shake_time and panel.state == "normal" then if row <= self.height then if panel.height > 3 then - SFX_GarbageThud_Play = 3 - else SFX_GarbageThud_Play = panel.height + self.sfx_garbage_thud = 3 + else self.sfx_garbage_thud = panel.height end shake_time = max(shake_time, panel.shake_time, self.peak_shake_time or 0) --a smaller garbage block landing should renew the largest of the previous blocks' shake times since our shake time was last zero. @@ -947,7 +1015,7 @@ function Stack.PdP(self) if row == 1 then panel.state = "landing" panel.timer = 12 - SFX_Land_Play=1; + self.sfx_land = true -- if there's a panel below, this panel's gonna land -- unless the panel below is falling. @@ -962,7 +1030,7 @@ function Stack.PdP(self) panel.state = "landing" panel.timer = 12 end - SFX_Land_Play=1; + self.sfx_land = true else panels[row-1][col], panels[row][col] = panels[row][col], panels[row-1][col] @@ -1040,6 +1108,11 @@ function Stack.PdP(self) panel.state = "popping" panel.timer = panel.combo_index*self.FRAMECOUNT_POP elseif panel.state == "popping" then + --print("POP") + if (panel.combo_size > 6) or self.chain_counter > 1 then popsize = "normal" end + if self.chain_counter > 2 then popsize = "big" end + if self.chain_counter > 3 then popsize = "giant" end + if config.popfx == true then self:enqueue_popfx(col, row, popsize) end self.score = self.score + 10; -- self.score_render=1; -- TODO: What is self.score_render? @@ -1142,14 +1215,32 @@ function Stack.PdP(self) (self.cur_timer == 0 or self.cur_timer == self.cur_wait_time) and (self.cur_row ~= prev_row or self.cur_col ~= prev_col)) then SFX_Cur_Move_Play=1 + if self.enable_analytics then + analytics.register_move() + end end else self.cur_row = bound(1, self.cur_row, self.top_cur_row) end + if self.cur_timer ~= self.cur_wait_time then self.cur_timer = self.cur_timer + 1 - - + end + -- TAUNTING + if self.taunt_up ~= nil then + for _,t in ipairs(characters[self.character].sounds.taunt_ups) do + t:stop() + end + characters[self.character].sounds.taunt_ups[self.taunt_up]:play() + self:taunt("taunt_up") + self.taunt_up = nil + elseif self.taunt_down ~= nil then + for _,t in ipairs(characters[self.character].sounds.taunt_downs) do + t:stop() + end + characters[self.character].sounds.taunt_downs[self.taunt_down]:play() + self:taunt("taunt_down") + self.taunt_down = nil end -- SWAPPING @@ -1195,6 +1286,9 @@ function Stack.PdP(self) if do_swap then self.do_swap = true + if self.enable_analytics then + analytics.register_swap() + end end self.swap_1 = false self.swap_2 = false @@ -1230,7 +1324,7 @@ function Stack.PdP(self) self:set_chain_garbage(self.chain_counter) SFX_Fanfare_Play = self.chain_counter if self.enable_analytics then - analytics_register_chain(self.chain_counter) + analytics.register_chain(self.chain_counter) end self.chain_counter=0 end @@ -1290,32 +1384,28 @@ function Stack.PdP(self) end end self.later_garbage[self.CLOCK-409] = nil - - --double-check panels_in_top_row + + -- Check for panels at or above the top. self.panels_in_top_row = false - local prow = panels[top_row] - for idx=1,width do - if prow[idx]:dangerous() then + -- If any dangerous panels are in the top row, garbage should not fall. + for col_idx = 1, width do + if panels[top_row][col_idx]:dangerous() then self.panels_in_top_row = true end end - local garbage_fits_in_populated_top_row - if self.garbage_q:len() > 0 then - --even if there are some panels in the top row, - --check if the next block in the garbage_q would fit anyway - --ie. 3-wide garbage might fit if there are three empty spaces where it would spawn - garbage_fits_in_populated_top_row = true - local next_garbage_block_width, next_garbage_block_height, _metal, from_chain = unpack(self.garbage_q:peek()) - local cols = self.garbage_cols[next_garbage_block_width] - local spawn_col = cols[cols.idx] - local spawn_row = #self.panels - for idx=spawn_col, spawn_col+next_garbage_block_width-1 do - if prow[idx]:dangerous() then - garbage_fits_in_populated_top_row = nil + -- If any panels (dangerous or not) are in rows above the top row, garbage should not fall. + for row_idx = top_row + 1, #self.panels do + for col_idx = 1, width do + if panels[row_idx][col_idx].color ~= 0 then + self.panels_in_top_row = true end end + end + + if self.garbage_q:len() > 0 then + local next_garbage_block_width, next_garbage_block_height, _metal, from_chain = unpack(self.garbage_q:peek()) local drop_it = - (not self.panels_in_top_row or garbage_fits_in_populated_top_row) + not self.panels_in_top_row and not self:has_falling_garbage() and ( (from_chain and next_garbage_block_height > 1) or @@ -1323,95 +1413,87 @@ function Stack.PdP(self) self.prev_active_panels == 0) ) if drop_it and self.garbage_q:len() > 0 then - self:drop_garbage(unpack(self.garbage_q:pop())) + if self:drop_garbage(unpack(self.garbage_q:peek())) then + self.garbage_q:pop() + end end end --Play Sounds / music - if not music_mute and not (P1 and P1.play_to_end) and not (P2 and P2.play_to_end) then + if not music_mute and not game_is_paused and not (P1 and P1.play_to_end) and not (P2 and P2.play_to_end) then if self.do_countdown then if SFX_Go_Play == 1 then - sounds.SFX.go:stop() - sounds.SFX.go:play() + themes[config.theme].sounds.go:stop() + themes[config.theme].sounds.go:play() SFX_Go_Play=0 elseif SFX_Countdown_Play == 1 then - sounds.SFX.countdown:stop() - sounds.SFX.countdown:play() + themes[config.theme].sounds.countdown:stop() + themes[config.theme].sounds.countdown:play() SFX_Go_Play=0 end - elseif (self.danger_music or (self.garbage_target and self.garbage_target.danger_music)) then --may have to rethink this bit if we do more than 2 players - if (current_music_is_casual or table.getn(currently_playing_tracks) == 0) - and characters[winningPlayer().character].musics["danger_music"] then -- disabled when danger_music is unspecified - print("Music is now critical") - if table.getn(currently_playing_tracks) == 0 then print("There were no sounds playing") end - stop_the_music() - find_and_add_music(winningPlayer().character, "danger_music") - current_music_is_casual = false - elseif table.getn(currently_playing_tracks) == 0 then - print("Music is now casual") - if table.getn(currently_playing_tracks) == 0 then print("There were no sounds playing") end - stop_the_music() - find_and_add_music(winningPlayer().character, "normal_music") - current_music_is_casual = true + else + local musics_to_use = (current_use_music_from == "stage") and stages[current_stage].musics or characters[winningPlayer().character].musics + if not musics_to_use["normal_music"] then -- use the other one as fallback + musics_to_use = (current_use_music_from ~= "stage") and stages[current_stage].musics or characters[winningPlayer().character].musics end - else --we should be playing normal_music or normal_music_start - if (not current_music_is_casual or table.getn(currently_playing_tracks) == 0) then - print("Music is now casual") - if table.getn(currently_playing_tracks) == 0 then print("There were no sounds playing") end - stop_the_music() - find_and_add_music(winningPlayer().character, "normal_music") - current_music_is_casual = true + if (self.danger_music or (self.garbage_target and self.garbage_target.danger_music)) then --may have to rethink this bit if we do more than 2 players + if (current_music_is_casual or table.getn(currently_playing_tracks) == 0) + and musics_to_use["danger_music"] then -- disabled when danger_music is unspecified + stop_the_music() + find_and_add_music(musics_to_use, "danger_music") + current_music_is_casual = false + elseif table.getn(currently_playing_tracks) == 0 and musics_to_use["normal_music"] then + stop_the_music() + find_and_add_music(musics_to_use, "normal_music") + current_music_is_casual = true + end + else --we should be playing normal_music or normal_music_start + if (not current_music_is_casual or table.getn(currently_playing_tracks) == 0) and musics_to_use["normal_music"] then + stop_the_music() + find_and_add_music(musics_to_use, "normal_music") + current_music_is_casual = true + end end end end if not SFX_mute and not (P1 and P1.play_to_end) and not (P2 and P2.play_to_end) then if SFX_Swap_Play == 1 then - sounds.SFX.swap:stop() - sounds.SFX.swap:play() + themes[config.theme].sounds.swap:stop() + themes[config.theme].sounds.swap:play() SFX_Swap_Play=0 end if SFX_Cur_Move_Play == 1 then - if not (self.mode == "vs" and sounds.SFX.swap:isPlaying()) + if not (self.mode == "vs" and themes[config.theme].sounds.swap:isPlaying()) and not self.do_countdown then - sounds.SFX.cur_move:stop() - sounds.SFX.cur_move:play() + themes[config.theme].sounds.cur_move:stop() + themes[config.theme].sounds.cur_move:play() end SFX_Cur_Move_Play=0 end - if SFX_Land_Play == 1 then - sounds.SFX.land:stop() - sounds.SFX.land:play() - SFX_Land_Play=0 + if self.sfx_land then + themes[config.theme].sounds.land:stop() + themes[config.theme].sounds.land:play() + self.sfx_land = false end if SFX_Countdown_Play == 1 then if self.which == 1 then - sounds.SFX.countdown:stop() - sounds.SFX.countdown:play() + themes[config.theme].sounds.countdown:stop() + themes[config.theme].sounds.countdown:play() end SFX_Countdown_Play=0 end if SFX_Go_Play == 1 then if self.which == 1 then - sounds.SFX.go:stop() - sounds.SFX.go:play() + themes[config.theme].sounds.go:stop() + themes[config.theme].sounds.go:play() end SFX_Go_Play=0 end if self.combo_chain_play then - sounds.SFX.land:stop() - sounds.SFX.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() - for _,v in pairs(characters[self.character].sounds.combos) do - v:stop() - end - for _,v in pairs(characters[self.character].sounds.combo_echos) do - v:stop() - end - characters[self.character].sounds.others["chain"]:stop() - characters[self.character].sounds.others["chain2"]:stop() - characters[self.character].sounds.others["chain_echo"]:stop() - characters[self.character].sounds.others["chain2_echo"]:stop() - self.combo_chain_play[1][self.combo_chain_play[2]]:play() + themes[config.theme].sounds.land:stop() + themes[config.theme].sounds.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() + characters[self.character]:play_combo_chain_sfx(self.combo_chain_play) self.combo_chain_play = nil end if SFX_garbage_match_play then @@ -1426,29 +1508,36 @@ function Stack.PdP(self) if SFX_Fanfare_Play == 0 then --do nothing elseif SFX_Fanfare_Play >= 6 then - sounds.SFX.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() - sounds.SFX.fanfare3:play() + themes[config.theme].sounds.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() + themes[config.theme].sounds.fanfare3:play() elseif SFX_Fanfare_Play >= 5 then - sounds.SFX.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() - sounds.SFX.fanfare2:play() + themes[config.theme].sounds.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() + themes[config.theme].sounds.fanfare2:play() elseif SFX_Fanfare_Play >= 4 then - sounds.SFX.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() - sounds.SFX.fanfare1:play() + themes[config.theme].sounds.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() + themes[config.theme].sounds.fanfare1:play() end SFX_Fanfare_Play=0 - if SFX_GarbageThud_Play >= 1 and SFX_GarbageThud_Play <= 3 then + if self.sfx_garbage_thud >= 1 and self.sfx_garbage_thud <= 3 then local interrupted_thud = nil for i=1,3 do - if sounds.SFX.garbage_thud[i]:isPlaying() and self.shake_time > prev_shake_time then - sounds.SFX.garbage_thud[i]:stop() + if themes[config.theme].sounds.garbage_thud[i]:isPlaying() and self.shake_time > prev_shake_time then + themes[config.theme].sounds.garbage_thud[i]:stop() interrupted_thud = i end end - if interrupted_thud and interrupted_thud > SFX_GarbageThud_Play then - sounds.SFX.garbage_thud[interrupted_thud]:play() - else sounds.SFX.garbage_thud[SFX_GarbageThud_Play]:play() + if interrupted_thud and interrupted_thud > self.sfx_garbage_thud then + themes[config.theme].sounds.garbage_thud[interrupted_thud]:play() + else + themes[config.theme].sounds.garbage_thud[self.sfx_garbage_thud]:play() end - SFX_GarbageThud_Play = 0 + if #characters[self.character].sounds.garbage_lands ~= 0 and interrupted_thud == nil then + for _,v in pairs(characters[self.character].sounds.garbage_lands) do + v:stop() + end + characters[self.character].sounds.garbage_lands[math.random(#characters[self.character].sounds.garbage_lands)]:play() + end + self.sfx_garbage_thud = 0 end if SFX_Pop_Play or SFX_Garbage_Pop_Play then local popLevel = min(max(self.chain_counter,1),4) @@ -1459,9 +1548,9 @@ function Stack.PdP(self) popIndex = min(self.poppedPanelIndex,10) end --stop the previous pop sound - sounds.SFX.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() + themes[config.theme].sounds.pops[self.lastPopLevelPlayed][self.lastPopIndexPlayed]:stop() --play the appropriate pop sound - sounds.SFX.pops[popLevel][popIndex]:play() + themes[config.theme].sounds.pops[popLevel][popIndex]:play() self.lastPopLevelPlayed = popLevel self.lastPopIndexPlayed = popIndex SFX_Pop_Play = nil @@ -1571,13 +1660,33 @@ end -- drops a width x height garbage. function Stack.drop_garbage(self, width, height, metal) - local spawn_row = #self.panels - for i=#self.panels+1,#self.panels+height+1 do - self.panels[i] = {} - for j=1,self.width do - self.panels[i][j] = Panel() + local spawn_row = self.height + 1 + + -- Do one last check for panels in the way. + for i = spawn_row, #self.panels do + if self.panels[i] then + for j = 1, self.width do + if self.panels[i][j] then + if self.panels[i][j].color ~= 0 then + print("Aborting garbage drop: panel found at row " .. tostring(i) .. " column " .. tostring(j)) + return false + end + end + end + end + end + + print(string.format("Dropping garbage on player %d - height %d width %d %s", self.player_number, height, width, metal and "Metal" or "")) + + for i = self.height + 1, spawn_row + height - 1 do + if not self.panels[i] then + self.panels[i] = {} + for j = 1, self.width do + self.panels[i][j] = Panel() + end end end + local cols = self.garbage_cols[width] local spawn_col = cols[cols.idx] cols.idx = wrap(1, cols.idx+1, #cols) @@ -1598,6 +1707,8 @@ function Stack.drop_garbage(self, width, height, metal) end end end + + return true end -- prepare to send some garbage! @@ -1716,8 +1827,6 @@ function Stack.check_matches(self) local first_panel_col = 0 local combo_index, garbage_index = 0, 0 local combo_size, garbage_size = 0, 0 - local something = 0 - local whatever = 0 local panels = self.panels local q, garbage = Queue(), {} local seen, seenm = {}, {} @@ -1900,7 +2009,7 @@ function Stack.check_matches(self) if(combo_size~=0) then if self.enable_analytics then - analytics_register_destroyed_panels(combo_size) + analytics.register_destroyed_panels(combo_size) end if(combo_size>3) then if(score_mode == SCOREMODE_TA) then @@ -1928,12 +2037,12 @@ function Stack.check_matches(self) --EnqueueConfetti(first_panel_col<<4+P1StackPosX+4, -- first_panel_row<<4+P1StackPosY+self.displacement-9); end - something = self.chain_counter + local chain_bonus = self.chain_counter if(score_mode == SCOREMODE_TA) then if(self.chain_counter>13) then - something=0 + chain_bonus = 0 end - self.score = self.score + score_chain_TA[something] + self.score = self.score + score_chain_TA[chain_bonus] end if((combo_size>3) or is_chain) then local stop_time @@ -1973,20 +2082,11 @@ function Stack.check_matches(self) --TODO: Mr Stop ^ -- @CardsOfTheHeart says there are 4 chain sfx: --x2/x3, --x4, --x5 is x2/x3 with an echo effect, --x6+ is x4 with an echo effect if is_chain then - local length = min(self.chain_counter, 13) - if length < 4 then - self.combo_chain_play = { characters[self.character].sounds.others, "chain" } - elseif length == 4 then - self.combo_chain_play = { characters[self.character].sounds.others, "chain2" } - elseif length == 5 then - self.combo_chain_play = { characters[self.character].sounds.others, "chain_echo" } - elseif length >= 6 then - self.combo_chain_play = { characters[self.character].sounds.others, "chain2_echo" } - end + self.combo_chain_play = { e_chain_or_combo.chain, self.chain_counter } elseif combo_size > 3 then - self.combo_chain_play = { characters[self.character].sounds.combos, math.random(#characters[self.character].sounds.combos) } + self.combo_chain_play = { e_chain_or_combo.combo, "combos" } end - SFX_Land_Play=0 + self.sfx_land = false end --if garbage_size > 0 then self.pre_stop_time = max(self.pre_stop_time, pre_stop_time) @@ -1996,9 +2096,9 @@ function Stack.check_matches(self) --self.score_render=1; --Nope. if metal_count > 5 then - self.combo_chain_play = { characters[self.character].sounds.combo_echos, math.random(#characters[self.character].sounds.combo_echos) } + self.combo_chain_play = { e_chain_or_combo.combo, "combo_echos" } elseif metal_count > 2 then - self.combo_chain_play = { characters[self.character].sounds.combos, math.random(#characters[self.character].sounds.combos) } + self.combo_chain_play = { e_chain_or_combo.combo, "combos" } end self:set_combo_garbage(combo_size, metal_count) end diff --git a/gen_panels.lua b/gen_panels.lua index 4c56899f..be635a1e 100644 --- a/gen_panels.lua +++ b/gen_panels.lua @@ -4,7 +4,7 @@ local random = math.random -- stuff should have first_seven, metal, vs_mode, metal_col, prev_metal_col function make_panels(ncolors, prev_panels, stuff) - --print("make_panels("..ncolors..", "..prev_panels..", "..(stuff.first_seven or "")..")") + print("make_panels("..ncolors..", "..prev_panels..", "..(stuff.first_seven or "")..")") local ret = prev_panels local rows_to_make = 20 local rows_to_place_metal_locations = rows_to_make @@ -41,7 +41,7 @@ function make_panels(ncolors, prev_panels, stuff) local prev_row for i=2,rows_to_place_metal_locations+1 do current_row_from_ret = string.sub(ret,(i-1)*row_width+1, (i-1)*row_width+row_width) - --print("current_row_from_ret: "..current_row_from_ret) + print("current_row_from_ret: "..current_row_from_ret) if tonumber(current_row_from_ret) then --doesn't already have letters in it for metal panel locations prev_row = string.sub(new_ret,0-row_width,-1) local first, second --locations of potential metal panels diff --git a/globals.lua b/globals.lua index c445542f..a6b5b49e 100644 --- a/globals.lua +++ b/globals.lua @@ -1,27 +1,130 @@ require("consts") require("queue") +require("server_queue") +require("sound_util") -- keyboard assignment vars K = {{up="up", down="down", left="left", right="right", - swap1="z", swap2="x", raise1="c", raise2="v"}, + swap1="z", swap2="x", taunt_up="y", taunt_down="u", raise1="c", raise2="v", pause="p"}, {},{},{}} keys = {} this_frame_keys = {} +this_frame_released_keys = {} this_frame_unicodes = {} this_frame_messages = {} +server_queue = ServerQueue(20) score_mode = SCOREMODE_TA gfx_q = Queue() +themes = {} -- initialized in theme.lua + characters = {} -- initialized in character.lua characters_ids = {} -- initialized in character.lua characters_ids_for_current_theme = {} -- initialized in character.lua characters_ids_by_display_names = {} -- initialized in character.lua +stages = {} -- initialized in stage.lua +stages_ids = {} -- initialized in stage.lua +stages_ids_for_current_theme = {} -- initialized in stage.lua + +panels = {} -- initialized in panels.lua +panels_ids = {} -- initialized in panels.lua + +current_stage = nil + +background_overlay = nil +foreground_overlay = nil + -- win counters my_win_count = 0 op_win_count = 0 +-- sfx play +SFX_Fanfare_Play = 0 +SFX_GarbageThud_Play = 0 +SFX_GameOver_Play = 0 + global_my_state = nil -global_op_state = nil \ No newline at end of file +global_op_state = nil + +-- Warning messages +display_warning_message = false + +-- game can be paused while playing on local +game_is_paused = false + +large_font = 10 -- large font base+10 +small_font = -3 -- small font base-3 + +default_input_repeat_delay = 20 +default_portrait_darkness = 70 + +zero_sound = load_sound_from_supported_extensions("zero_music") + + -- Default configuration values +config = { + -- The lastly used version + version = VERSION, + + -- Lang used for localization + language_code = "EN", + + theme = default_theme_dir, + panels = default_panels_dir, + character = random_character_special_value, + stage = random_stage_special_value, + + ranked = true, + + vsync = true, + + use_music_from = "either", + -- Level (2P modes / 1P vs yourself mode) + level = 5, + endless_speed = 1, + endless_difficulty = 1, + -- Player name + name = "defaultname", + -- Volume settings + master_volume = 100, + SFX_volume = 100, + music_volume = 100, + -- Debug mode flag + debug_mode = false, + -- Show FPS in the top-left corner of the screen + show_fps = false, + -- Show ingame infos while playing the game + show_ingame_infos = false, + -- Enable ready countdown flag + ready_countdown_1P = true, + -- Change danger music back later flag + danger_music_changeback_delay = false, + input_repeat_delay = default_input_repeat_delay, + -- analytics + enable_analytics = false, + -- Save replays setting + save_replays_publicly = "with my name", + portrait_darkness = default_portrait_darkness, + popfx = true, + cardfx_scale = 100, +} + +current_use_music_from = "stage" -- either "stage" or "characters", no other values! + +function warning(msg) + err = "=================================================================\n["..os.date("%x %X").."]\nError: "..msg..debug.traceback("").."\n" + love.filesystem.append("warnings.txt", err) + if display_warning_message then + display_warning_message = false + local loc_warning = "You've had a bug. Please report this on Discord with file:" + if loc ~= nil then + local str = loc("warning_msg") + if str:sub(1, 1) ~= "#" then + loc_warning = str + end + end + love.window.showMessageBox("Warning", loc_warning.."\n%appdata%\\Panel Attack\\warnings.txt") + end +end \ No newline at end of file diff --git a/graphics.lua b/graphics.lua old mode 100755 new mode 100644 index 47723b0b..dc2f66be --- a/graphics.lua +++ b/graphics.lua @@ -1,5 +1,6 @@ require("input") require("util") +local analytics = require("analytics") local floor = math.floor local ceil = math.ceil @@ -17,276 +18,23 @@ for i=14,6,-1 do end end -print("#shake arr "..#shake_arr) - -- 1 -> 1 -- #shake -> 0 local shake_step = 1/(#shake_arr - 1) local shake_mult = 1 for i=1,#shake_arr do shake_arr[i] = shake_arr[i] * shake_mult - print(shake_arr[i]) + -- print(shake_arr[i]) shake_mult = shake_mult - shake_step end -function load_img(path_and_name,config_dir,default_dir) - default_dir = default_dir or "assets/"..default_assets_dir - - local img - if pcall(function () - config_dir = config_dir or "assets/"..config.assets_dir - img = love.image.newImageData(config_dir.."/"..path_and_name) - end) then - print("loaded asset: "..config_dir.."/"..path_and_name) - else - if pcall(function () - img = love.image.newImageData(default_dir.."/"..path_and_name) - end) then - print("loaded asset:"..default_dir.."/"..path_and_name) - else - img = nil - end - end - - if img == nil then - return nil - end - - local ret = love.graphics.newImage(img) - ret:setFilter("nearest","nearest") - return ret -end - -function draw(img, x, y, rot, x_scale,y_scale) - rot = rot or 0 - x_scale = x_scale or 1 - y_scale = y_scale or 1 - gfx_q:push({love.graphics.draw, {img, x*GFX_SCALE, y*GFX_SCALE, - rot, x_scale*GFX_SCALE, y_scale*GFX_SCALE}}) -end - -function menu_draw(img, x, y, rot, x_scale,y_scale) - rot = rot or 0 - x_scale = x_scale or 1 - y_scale = y_scale or 1 - gfx_q:push({love.graphics.draw, {img, x, y, - rot, x_scale, y_scale}}) -end - -function menu_drawf(img, x, y, halign, valign, rot, x_scale, y_scale) - rot = rot or 0 - x_scale = x_scale or 1 - y_scale = y_scale or 1 - halign = halign or "left" - if halign == "center" then - x = x - math.floor(img:getWidth() * 0.5 * x_scale) - elseif halign == "right" then - x = x - math.floor(img:getWidth() * x_scale) - end - valign = valign or "top" - if valign == "center" then - y = y - math.floor(img:getHeight() * 0.5 * y_scale) - elseif valign == "bottom" then - y = y - math.floor(img:getHeight() * y_scale) - end - gfx_q:push({love.graphics.draw, {img, x, y, - rot, x_scale, y_scale}}) -end - -function menu_drawq(img, quad, x, y, rot, x_scale,y_scale) - rot = rot or 0 - x_scale = x_scale or 1 - y_scale = y_scale or 1 - gfx_q:push({love.graphics.draw, {img, quad, x, y, - rot, x_scale, y_scale}}) -end - -function grectangle(mode, x, y, w, h) - gfx_q:push({love.graphics.rectangle, {mode, x, y, w, h}}) -end - -function gprint(str, x, y, color, scale) - x = x or 0 - y = y or 0 - scale = scale or 1 - color = color or nil - set_color(0, 0, 0, 1) - gfx_q:push({love.graphics.print, {str, x+1, y+1, 0, scale}}) - local r, g, b, a = 1,1,1,1 - if color ~= nil then - r,g,b,a = unpack(color) - end - set_color(r,g,b,a) - gfx_q:push({love.graphics.print, {str, x, y, 0, scale}}) -end - -function gprintf(str, x, y, limit, halign, color, scale) - x = x or 0 - y = y or 0 - scale = scale or 1 - color = color or nil - limit = limit or canvas_width - halign = halign or "left" - set_color(0, 0, 0, 1) - gfx_q:push({love.graphics.printf, {str, x+1, y+1, limit, halign, 0, scale}}) - local r, g, b, a = 1,1,1,1 - if color ~= nil then - r,g,b,a = unpack(color) - end - set_color(r,g,b,a) - gfx_q:push({love.graphics.printf, {str, x, y, limit, halign, 0, scale}}) -end - -local _r, _g, _b, _a -function set_color(r, g, b, a) - a = a or 1 - -- only do it if this color isn't the same as the previous one... - if _r~=r or _g~=g or _b~=b or _a~=a then - _r,_g,_b,_a = r,g,b,a - gfx_q:push({love.graphics.setColor, {r, g, b, a}}) - end -end - -function file_exists(name) - local f=io.open(name,"r") - if f~=nil then io.close(f) return true else return false end -end - -IMG_stagecount = 1 -function graphics_init() - title = load_img("menu/title.png") - charselect = load_img("menu/charselect.png") - IMG_stages = {} - - IMG_stagecount = 1 - i = 0 - while i > -1 do - IMG_stages[IMG_stagecount] = load_img("stages/"..tostring(IMG_stagecount)..".png") - if IMG_stages[IMG_stagecount] == nil then - i=-1 - break - else - IMG_stagecount=IMG_stagecount+1 - end - end - - IMG_level_cursor = load_img("level_cursor.png") - IMG_levels = {} - IMG_levels_unfocus = {} - IMG_levels[1] = load_img("level1.png") - IMG_levels_unfocus[1] = nil -- meaningless by design - for i=2,11 do - IMG_levels[i] = load_img("level"..i..".png") - IMG_levels_unfocus[i] = load_img("level"..i.."unfocus.png") - end - - IMG_ready = load_img("ready.png") - IMG_loading = load_img("loading.png") - IMG_numbers = {} - for i=1,3 do - IMG_numbers[i] = load_img(i..".png") - end - IMG_cursor = { load_img("cur0.png"), - load_img("cur1.png")} - - IMG_players = { load_img("player_1.png"), - load_img("player_2.png")} - - IMG_frame = load_img("frame.png") - IMG_wall = load_img("wall.png") - - IMG_cards = {} - IMG_cards[true] = {} - IMG_cards[false] = {} - for i=4,66 do - IMG_cards[false][i] = load_img("combo" - ..tostring(floor(i/10))..tostring(i%10)..".png") - end - for i=2,13 do - IMG_cards[true][i] = load_img("chain" - ..tostring(floor(i/10))..tostring(i%10)..".png") - end - for i=14,99 do - IMG_cards[true][i] = load_img("chain00.png") - end - local MAX_SUPPORTED_PLAYERS = 2 - IMG_char_sel_cursors = {} - for player_num=1,MAX_SUPPORTED_PLAYERS do - IMG_char_sel_cursors[player_num] = {} - for position_num=1,2 do - IMG_char_sel_cursors[player_num][position_num] = load_img("char_sel_cur_"..player_num.."P_pos"..position_num..".png") - end - end - IMG_char_sel_cursor_halves = {left={}, right={}} - for player_num=1,MAX_SUPPORTED_PLAYERS do - IMG_char_sel_cursor_halves.left[player_num] = {} - for position_num=1,2 do - local cur_width, cur_height = IMG_char_sel_cursors[player_num][position_num]:getDimensions() - local half_width, half_height = cur_width/2, cur_height/2 -- TODO: is these unused vars an error ??? -Endu - IMG_char_sel_cursor_halves["left"][player_num][position_num] = love.graphics.newQuad(0,0,half_width,cur_height,cur_width, cur_height) - end - IMG_char_sel_cursor_halves.right[player_num] = {} - for position_num=1,2 do - local cur_width, cur_height = IMG_char_sel_cursors[player_num][position_num]:getDimensions() - local half_width, half_height = cur_width/2, cur_height/2 - IMG_char_sel_cursor_halves.right[player_num][position_num] = love.graphics.newQuad(half_width,0,half_width,cur_height,cur_width, cur_height) - end - end -end - -function panels_init() - IMG_panels = {} - IMG_panels_dirs = {} - - IMG_metals = {} - - local function load_panels_dir(dir, full_dir, default_dir) - default_dir = default_dir or "panels/"..default_panels_dir - - IMG_panels[dir] = {} - IMG_panels_dirs[#IMG_panels_dirs+1] = dir - - for i=1,8 do - IMG_panels[dir][i] = {} - for j=1,7 do - IMG_panels[dir][i][j] = load_img("panel"..tostring(i)..tostring(j)..".png",full_dir,default_dir) - end - end - IMG_panels[dir][9] = {} - for j=1,7 do - IMG_panels[dir][9][j] = load_img("panel00.png",full_dir,default_dir) - end - - IMG_metals[dir] = { left = load_img("metalend0.png",full_dir,default_dir), - mid = load_img("metalmid.png",full_dir,default_dir), - right = load_img("metalend1.png",full_dir,default_dir), - flash = load_img("garbageflash.png",full_dir,default_dir) } - end - - if config.use_panels_from_assets_folder then - load_panels_dir(config.assets_dir, "assets/"..config.assets_dir) - else - -- default ones - load_panels_dir(default_panels_dir, "panels/"..default_panels_dir) - - -- custom ones - local raw_dir_list = love.filesystem.getDirectoryItems("panels") - for k,v in ipairs(raw_dir_list) do - local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) - if love.filesystem.getInfo("panels/"..v) and v ~= "Example folder structure" and v ~= default_panels_dir and start_of_v ~= prefix_of_ignored_dirs then - load_panels_dir(v, "panels/"..v) - end - end - end - -end - function Stack.update_cards(self) for i=self.card_q.first,self.card_q.last do local card = self.card_q[i] if card_animation[card.frame] then card.frame = card.frame + 1 if(card_animation[card.frame]==nil) then + if config.popfx == true then card.burstParticle:release() end self.card_q:pop() end else @@ -299,10 +47,135 @@ function Stack.draw_cards(self) for i=self.card_q.first,self.card_q.last do local card = self.card_q[i] if card_animation[card.frame] then - local draw_x = 4 + (card.x-1) * 16 - local draw_y = 4 + (11-card.y) * 16 + self.displacement + local draw_x = (self.pos_x) + (card.x-1) * 16 + local draw_y = (self.pos_y) + (11-card.y) * 16 + self.displacement - card_animation[card.frame] - draw(IMG_cards[card.chain][card.n], draw_x, draw_y) + if config.popfx == true and card.frame then + burstFrameDimension = card.burstAtlas:getWidth()/9 + -- draw cardfx + if card.frame <= 21 then radius = (200 - (card.frame * 7))*(config.cardfx_scale/100) end + if card.frame > 21 then radius = (100 - (card.frame * 3))*(config.cardfx_scale/100) end + if radius < 10 then radius = 10 end + for i=1, 6, 1 do + local cardfx_x = draw_x + math.cos(math.rad((i*60)+(card.frame*5)))*radius + local cardfx_y = draw_y + math.sin(math.rad((i*60)+(card.frame*5)))*radius + qdraw(card.burstAtlas, card.burstParticle, cardfx_x, cardfx_y, 0, 16/burstFrameDimension, 16/burstFrameDimension) + end + end + -- draw card + draw(themes[config.theme].images.IMG_cards[card.chain][card.n], draw_x, draw_y) + end + end +end + +function Stack.update_popfxs(self) + for i=self.pop_q.first,self.pop_q.last do + local popfx = self.pop_q[i] + if characters[self.character].popfx_style == "burst" or characters[self.character].popfx_style == "fadeburst" then popfx_animation = popfx_burst_animation end + if characters[self.character].popfx_style == "fade" then popfx_animation = popfx_fade_animation end + if popfx_burst_animation[popfx.frame] then + popfx.frame = popfx.frame + 1 + if(popfx_burst_animation[popfx.frame]==nil) then + if characters[self.character].images["burst"] then popfx.burstParticle:release() end + if characters[self.character].images["fade"] then popfx.fadeParticle:release() end + if characters[self.character].images["burst"] then popfx.bigParticle:release() end + self.pop_q:pop() + end + else + popfx.frame = popfx.frame + 1 + end + end +end + +function Stack.draw_popfxs(self) + for i=self.pop_q.first,self.pop_q.last do + local popfx = self.pop_q[i] + local draw_x = (self.pos_x) + (popfx.x-1) * 16 + local draw_y = (self.pos_y) + (11-popfx.y) * 16 + self.displacement + local burstScale = characters[self.character].popfx_burstScale + local fadeScale = characters[self.character].popfx_fadeScale + local burstParticle_atlas = popfx.burstAtlas + local burstParticle = popfx.burstParticle + local burstFrameDimension = popfx.burstFrameDimension + local fadeParticle_atlas = popfx.fadeAtlas + local fadeParticle = popfx.fadeParticle + local fadeFrameDimension = popfx.fadeFrameDimension + if characters[self.character].popfx_style == "burst" or characters[self.character].popfx_style == "fadeburst" then + if characters[self.character].images["burst"] then + burstFrame = popfx_burst_animation[popfx.frame] + if popfx_burst_animation[popfx.frame] then + burstParticle:setViewport(burstFrame[2]*burstFrameDimension, 0, burstFrameDimension, burstFrameDimension, burstParticle_atlas:getDimensions()) + positions = { + -- four corner + {x = draw_x-burstFrame[1], y = draw_y-burstFrame[1]}, + {x = draw_x+15+burstFrame[1], y = draw_y-burstFrame[1]}, + {x = draw_x-burstFrame[1], y = draw_y+15+burstFrame[1]}, + {x = draw_x+15+burstFrame[1], y = draw_y+15+burstFrame[1]}, + -- top and bottom + {x = draw_x, y = draw_y-(burstFrame[1]*2)}, + {x = draw_x, y = draw_y+10+(burstFrame[1]*2)}, + -- left and right + {x = draw_x+5-(burstFrame[1]*2), y = draw_y}, + {x = draw_x+10+(burstFrame[1]*2), y = draw_y} + } + + if characters[self.character].popfx_burstrotate == true then + topRot = {math.rad(45), (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + bottomRot = {math.rad(-135), (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + leftRot = {math.rad(-45), (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + rightRot = {math.rad(135), (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + else + topRot = {0, (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + bottomRot = {0, (16/burstFrameDimension)*burstScale, -(16/burstFrameDimension)*burstScale} + leftRot = {0, (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + rightRot = {0, -(16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale} + end + + randomMax = 0 + + if popsize == "normal" then randomMax = 4 end + if popsize == "big" then randomMax = 6 end + if popsize == "giant" then randomMax = 8 end + if popsize ~= "small" and popfx.bigTimer == 0 then + big_position = math.random(randomMax) + big_position = 0 + popfx.bigTimer = 2 + end + popfx.bigTimer = popfx.bigTimer - 1 + + -- four corner + if big_position ~= 1 then qdraw(burstParticle_atlas, burstParticle, positions[1].x, positions[1].y, 0, (16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale, (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + if big_position ~= 2 then qdraw(burstParticle_atlas, burstParticle, positions[2].x, positions[2].y, 0, -(16/burstFrameDimension)*burstScale, (16/burstFrameDimension)*burstScale, (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + if big_position ~= 3 then qdraw(burstParticle_atlas, burstParticle, positions[3].x, positions[3].y, 0, (16/burstFrameDimension)*burstScale, -(16/burstFrameDimension)*burstScale, (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + if big_position ~= 4 then qdraw(burstParticle_atlas, burstParticle, positions[4].x, positions[4].y, 0, -(16/burstFrameDimension)*burstScale, -16/burstFrameDimension*burstScale, (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + -- top and bottom + if popfx.popsize == "big" or popfx.popsize == "giant" then + if big_position ~= 5 then qdraw(burstParticle_atlas, burstParticle, positions[5].x+8, positions[5].y, topRot[1], topRot[2], topRot[3], (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + if big_position ~= 6 then qdraw(burstParticle_atlas, burstParticle, positions[6].x+8, positions[6].y, bottomRot[1], bottomRot[2], bottomRot[3], (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + end + -- left and right + if popfx.popsize == "giant" then + if big_position ~= 7 then qdraw(burstParticle_atlas, burstParticle, positions[7].x, positions[7].y+8, leftRot[1], leftRot[2], leftRot[3], (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + if big_position ~= 8 then qdraw(burstParticle_atlas, burstParticle, positions[8].x, positions[8].y+8, rightRot[1], rightRot[2], rightRot[3], (burstFrameDimension*burstScale)/2, (burstFrameDimension*burstScale)/2) end + end + --big particle + --[[ + if popsize ~= "small" then + qdraw(particle_atlas, popfx.bigParticle, + positions[big_position].x, positions[big_position].y, 0, 16/frameDimension, 16/frameDimension, frameDimension/2, frameDimension/2) + end + ]] + end + end + end + if characters[self.character].popfx_style == "fade" or characters[self.character].popfx_style == "fadeburst" then + if characters[self.character].images["fade"] then + fadeFrame = popfx_fade_animation[popfx.frame] + if(fadeFrame ~= nil) then + fadeParticle:setViewport(fadeFrame*fadeFrameDimension, 0, fadeFrameDimension, fadeFrameDimension, fadeParticle_atlas:getDimensions()) + qdraw(fadeParticle_atlas, fadeParticle, draw_x+8, draw_y+8, 0, (32/fadeFrameDimension)*fadeScale, (32/fadeFrameDimension)*fadeScale, fadeFrameDimension/2, fadeFrameDimension/2) + end + end end end end @@ -312,9 +185,18 @@ function move_stack(stack, player_num) if player_num == 1 then stack.pos_x = 4 + stack_padding_x_for_legacy_pos/GFX_SCALE stack.score_x = 315 + stack_padding_x_for_legacy_pos + stack.mirror_x = 1 + stack.origin_x = stack.pos_x + stack.multiplication = 0 + stack.id = "_1P" + stack.VAR_numbers = "" elseif player_num == 2 then stack.pos_x = 172 + stack_padding_x_for_legacy_pos/GFX_SCALE stack.score_x = 410 + stack_padding_x_for_legacy_pos + stack.mirror_x = -1 + stack.origin_x = stack.pos_x + (stack.canvas:getWidth()/GFX_SCALE) - 8 + stack.multiplication = 1 + stack.id = "_2P" end stack.pos_y = 4 + (canvas_height-legacy_canvas_height)/GFX_SCALE stack.score_y = 100 + (canvas_height-legacy_canvas_height) @@ -345,19 +227,56 @@ function Stack.render(self) gfx_q:push({love.graphics.stencil, {frame_mask, "replace", 1}}) gfx_q:push({love.graphics.setStencilTest, {"greater", 0}}) + time_quads = {} + move_quads = {} + score_quads = {} + speed_quads = {} + level_quad = love.graphics.newQuad(0, 0, themes[config.theme].images["IMG_levelNumber_atlas"..self.id]:getWidth()/11 , themes[config.theme].images["IMG_levelNumber_atlas"..self.id]:getHeight(), themes[config.theme].images["IMG_levelNumber_atlas"..self.id]:getDimensions()) + win_quads = {} + healthQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_healthbar:getWidth(), themes[config.theme].images.IMG_healthbar:getHeight(), + themes[config.theme].images.IMG_healthbar:getWidth(), themes[config.theme].images.IMG_healthbar:getHeight()) + prestopQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_prestop_bar:getWidth(), themes[config.theme].images.IMG_prestop_bar:getHeight(), + themes[config.theme].images.IMG_prestop_bar:getWidth(), themes[config.theme].images.IMG_prestop_bar:getHeight()) + stopQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_stop_bar:getWidth(), themes[config.theme].images.IMG_stop_bar:getHeight(), + themes[config.theme].images.IMG_stop_bar:getWidth(), themes[config.theme].images.IMG_stop_bar:getHeight()) + shakeQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_shake_bar:getWidth(), themes[config.theme].images.IMG_shake_bar:getHeight(), + themes[config.theme].images.IMG_shake_bar:getWidth(), themes[config.theme].images.IMG_shake_bar:getHeight()) + prestop_quads = {} + stop_quads = {} + shake_quads = {} +multi_prestopQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_multibar_prestop_bar:getWidth(), themes[config.theme].images.IMG_multibar_prestop_bar:getHeight(), + themes[config.theme].images.IMG_multibar_prestop_bar:getWidth(), themes[config.theme].images.IMG_multibar_prestop_bar:getHeight()) +multi_stopQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_multibar_stop_bar:getWidth(), themes[config.theme].images.IMG_multibar_stop_bar:getHeight(), + themes[config.theme].images.IMG_multibar_stop_bar:getWidth(), themes[config.theme].images.IMG_multibar_stop_bar:getHeight()) +multi_shakeQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_multibar_shake_bar:getWidth(), themes[config.theme].images.IMG_multibar_shake_bar:getHeight(), + themes[config.theme].images.IMG_multibar_shake_bar:getWidth(), themes[config.theme].images.IMG_multibar_shake_bar:getHeight()) + -- draw inside stack's frame canvas local portrait_w, portrait_h = characters[self.character].images["portrait"]:getDimensions() + if self.do_countdown == false then + self.portraitFade = 0.3 + else + if self.countdown_CLOCK then + if self.countdown_CLOCK > 50 and self.countdown_CLOCK < 80 then + self.portraitFade = ((config.portrait_darkness/100)/79)*self.countdown_CLOCK + end + elseif self.CLOCK > 200 then + self.portraitFade = config.portrait_darkness/100 + end + end if P1 == self then draw(characters[self.character].images["portrait"], 4, 4, 0, 96/portrait_w, 192/portrait_h) + grectangle_color("fill", 4, 4, 96, 192, 0, 0, 0, self.portraitFade) else draw(characters[self.character].images["portrait"], 100, 4, 0, (96/portrait_w)*-1, 192/portrait_h) + grectangle_color("fill", 4, 4, 96, 192, 0, 0, 0, self.portraitFade) end local metals if self.garbage_target then - metals = IMG_metals[self.garbage_target.panels_dir] + metals = panels[self.garbage_target.panels_dir].images.metals else - metals = IMG_metals[self.panels_dir] + metals = panels[self.panels_dir].images.metals end local metal_w, metal_h = metals.mid:getDimensions() local metall_w, metall_h = metals.left:getDimensions() @@ -430,8 +349,8 @@ function Stack.render(self) draw(imgs.pop, draw_x, draw_y, 0, 16/popped_w, 16/popped_h) end elseif panel.y_offset == -1 then - local p_w, p_h = IMG_panels[self.panels_dir][panel.color][1]:getDimensions() - draw(IMG_panels[self.panels_dir][panel.color][1], draw_x, draw_y, 0, 16/p_w, 16/p_h) + local p_w, p_h = panels[self.panels_dir].images.classic[panel.color][1]:getDimensions() + draw(panels[self.panels_dir].images.classic[panel.color][1], draw_x, draw_y, 0, 16/p_w, 16/p_h) end elseif flash_time % 2 == 1 then if panel.metal then @@ -476,26 +395,33 @@ function Stack.render(self) else draw_frame = 1 end - local panel_w, panel_h = IMG_panels[self.panels_dir][panel.color][draw_frame]:getDimensions() - draw(IMG_panels[self.panels_dir][panel.color][draw_frame], draw_x, draw_y, 0, 16/panel_w, 16/panel_h) + local panel_w, panel_h = panels[self.panels_dir].images.classic[panel.color][draw_frame]:getDimensions() + draw(panels[self.panels_dir].images.classic[panel.color][draw_frame], draw_x, draw_y, 0, 16/panel_w, 16/panel_h) end end end end - draw(IMG_frame,0,0) - draw(IMG_wall, 4, 4 - shake + self.height*16) + if P1 == self then + draw(themes[config.theme].images.IMG_frame1P,0,0) + draw(themes[config.theme].images.IMG_wall1P, 4, 4 - shake + self.height*16) + else + draw(themes[config.theme].images.IMG_frame2P,0,0) + draw(themes[config.theme].images.IMG_wall2P, 4, 4 - shake + self.height*16) + end - self:draw_cards() self:render_cursor() if self.do_countdown then self:render_countdown() end -- ends here - + gfx_q:push({love.graphics.setStencilTest, {}}) gfx_q:push({love.graphics.setCanvas, {global_canvas}}) gfx_q:push({love.graphics.draw, {self.canvas, (self.pos_x-4)*GFX_SCALE, (self.pos_y-4)*GFX_SCALE }}) - + + self:draw_popfxs() + self:draw_cards() + if config.debug_mode then local mx, my = love.mouse.getPosition() for row=0,self.height do @@ -515,7 +441,7 @@ function Stack.render(self) end if mx >= draw_x and mx < draw_x + 16*GFX_SCALE and my >= draw_y and my < draw_y + 16*GFX_SCALE then debug_mouse_panel = {row, col, panel} - draw(IMG_panels[self.panels_dir][9][1], draw_x+16, draw_y+16) + draw(panels[self.panels_dir].images.classic[9][1], draw_x+16, draw_y+16) end end end @@ -523,12 +449,26 @@ function Stack.render(self) -- draw outside of stack's frame canvas if self.mode == "puzzle" then - gprint("Moves: "..self.puzzle_moves, self.score_x, self.score_y) - gprint("Frame: "..self.CLOCK, self.score_x, self.score_y+30) + --gprint(loc("pl_moves", self.puzzle_moves), self.score_x, self.score_y) + draw_label(themes[config.theme].images.IMG_moves, (self.origin_x+themes[config.theme].moveLabel_Pos[1])/GFX_SCALE, (self.pos_y+themes[config.theme].moveLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].moveLabel_Scale) + draw_number(self.puzzle_moves, themes[config.theme].images.IMG_number_atlas_1P, 10, move_quads, self.score_x+themes[config.theme].move_Pos[1], self.score_y+themes[config.theme].move_Pos[2], themes[config.theme].move_Scale, + (30/themes[config.theme].images.numberWidth_1P*themes[config.theme].move_Scale), (38/themes[config.theme].images.numberHeight_1P*themes[config.theme].move_Scale), "center", self.multiplication) + if config.show_ingame_infos then + --gprint(loc("pl_frame", self.CLOCK), self.score_x, self.score_y+30) + end else - gprint("Score: "..self.score, self.score_x, self.score_y) - gprint("Speed: "..self.speed, self.score_x, self.score_y+30) - gprint("Frame: "..self.CLOCK, self.score_x, self.score_y+45) + if config.show_ingame_infos then + --gprint(loc("pl_score", self.score), self.score_x, self.score_y-40) + draw_label(themes[config.theme].images["IMG_score"..self.id], self.origin_x+(themes[config.theme].scoreLabel_Pos[1]*self.mirror_x), self.pos_y+themes[config.theme].scoreLabel_Pos[2], 0, themes[config.theme].scoreLabel_Scale, self.multiplication) + draw_number(self.score, themes[config.theme].images["IMG_number_atlas"..self.id], 10, score_quads, (self.origin_x+(themes[config.theme].score_Pos[1]*self.mirror_x))*GFX_SCALE, (self.pos_y+themes[config.theme].score_Pos[2])*GFX_SCALE, themes[config.theme].score_Scale, + (15/themes[config.theme].images["numberWidth"..self.id]*themes[config.theme].score_Scale), (19.5/themes[config.theme].images["numberHeight"..self.id]*themes[config.theme].score_Scale), "center", self.multiplication) + --gprint(loc("pl_speed", self.speed), self.score_x, self.score_y+45) + draw_label(themes[config.theme].images["IMG_speed"..self.id], self.origin_x+themes[config.theme].speedLabel_Pos[1]*self.mirror_x, (self.pos_y+themes[config.theme].speedLabel_Pos[2]), 0, themes[config.theme].speedLabel_Scale, self.multiplication) + draw_number(self.speed, themes[config.theme].images["IMG_number_atlas"..self.id], 10, speed_quads, (self.origin_x+(themes[config.theme].speed_Pos[1]*self.mirror_x))*GFX_SCALE, (self.pos_y+themes[config.theme].speed_Pos[2])*GFX_SCALE, themes[config.theme].speed_Scale, + (15/themes[config.theme].images['numberWidth'..self.id]*themes[config.theme].speed_Scale), (19/themes[config.theme].images["numberHeight"..self.id]*themes[config.theme].speed_Scale), "center", self.multiplication) + --gprint(loc("pl_frame", self.CLOCK), self.score_x, self.score_y+100) + end + local main_infos_screen_pos = { x=375 + (canvas_width-legacy_canvas_width)/2, y=10 + (canvas_height-legacy_canvas_height) } if self.mode == "time" then local time_left = 120 - (self.game_stopwatch or 120)/60 local mins = math.floor(time_left/60) @@ -537,47 +477,145 @@ function Stack.render(self) secs = 0 mins = mins+1 end - gprint("Time: "..string.format("%01d:%02d",mins,secs), self.score_x, self.score_y+60) + --gprint(loc("pl_time", string.format("%01d:%02d",mins,secs)), self.score_x, self.score_y+60) + draw_label(themes[config.theme].images.IMG_time, (main_infos_screen_pos.x+themes[config.theme].timeLabel_Pos[1])/GFX_SCALE, (main_infos_screen_pos.y+themes[config.theme].timeLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].timeLabel_Scale) + draw_time(string.format("%01d:%02d",mins,secs), time_quads, main_infos_screen_pos.x+themes[config.theme].time_Pos[1], main_infos_screen_pos.y+themes[config.theme].time_Pos[2], + 20/themes[config.theme].images.timeNumberWidth*themes[config.theme].time_Scale, 26/themes[config.theme].images.timeNumberHeight*themes[config.theme].time_Scale) elseif self.level then - gprint("Level: "..self.level, self.score_x, self.score_y+60) + --gprint(loc("pl_level", self.level), self.score_x, self.score_y+70) + draw_label(themes[config.theme].images["IMG_level"..self.id], self.origin_x+themes[config.theme].levelLabel_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].levelLabel_Pos[2], 0, themes[config.theme].levelLabel_Scale, self.multiplication) + + level_atlas = themes[config.theme].images["IMG_levelNumber_atlas"..self.id] + level_quad:setViewport(tonumber(self.level-1)*(level_atlas:getWidth()/11), 0, level_atlas:getWidth()/11, level_atlas:getHeight(), level_atlas:getDimensions()) + qdraw(level_atlas, level_quad, (self.origin_x+themes[config.theme].level_Pos[1]*self.mirror_x), (self.pos_y+themes[config.theme].level_Pos[2]), 0, + (28/themes[config.theme].images["levelNumberWidth"..self.id]*themes[config.theme].level_Scale)/GFX_SCALE, (26/themes[config.theme].images["levelNumberHeight"..self.id]*themes[config.theme].level_Scale/GFX_SCALE), 0, 0, self.multiplication) end - gprint("Health: "..self.health, self.score_x, self.score_y+75) - gprint("Shake: "..self.shake_time, self.score_x, self.score_y+90) - gprint("Stop: "..self.stop_time, self.score_x, self.score_y+105) - gprint("Pre stop: "..self.pre_stop_time, self.score_x, self.score_y+120) - if config.debug_mode and self.danger then gprint("danger", self.score_x,self.score_y+135) end - if config.debug_mode and self.danger_music then gprint("danger music", self.score_x, self.score_y+150) end - if config.debug_mode then - gprint("cleared: "..(self.panels_cleared or 0), self.score_x, self.score_y+165) + if config.show_ingame_infos then + --gprint(loc("pl_health", self.health), self.score_x, self.score_y-40) + --(self.pos_x-4)*GFX_SCALE, (self.pos_y-4)*GFX_SCALE + --if healthQuad == nil then local healthQuad = love.graphics.newQuad(0, 0, themes[config.theme].images.IMG_healthbar:getWidth(), themes[config.theme].images.IMG_healthbar:getHeight(), + -- themes[config.theme].images.IMG_healthbar:getWidth(), themes[config.theme].images.IMG_healthbar:getHeight()) end + -- Healthbar frame + draw_label(themes[config.theme].images["IMG_healthbar_frame"..self.id], self.origin_x+themes[config.theme].healthbar_frame_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].healthbar_frame_Pos[2], 0, themes[config.theme].healthbar_frame_Scale, self.multiplication) + -- Healthbar + healthbar = self.health*(themes[config.theme].images.IMG_healthbar:getHeight()/self.max_health) + healthQuad:setViewport(0, themes[config.theme].images.IMG_healthbar:getHeight()-healthbar, themes[config.theme].images.IMG_healthbar:getWidth(), healthbar) + qdraw(themes[config.theme].images.IMG_healthbar, healthQuad, self.origin_x+themes[config.theme].healthbar_Pos[1]*self.mirror_x, (self.pos_y+themes[config.theme].healthbar_Pos[2])+(themes[config.theme].images.IMG_healthbar:getHeight()-healthbar), + themes[config.theme].healthbar_Rotate, themes[config.theme].healthbar_Scale, themes[config.theme].healthbar_Scale, 0, 0, self.multiplication) + + --gprint(loc("pl_stop", self.stop_time), self.score_x, self.score_y+300) + --gprint(loc("pl_shake", self.shake_time), self.score_x, self.score_y+320) + --gprint(loc("pl_pre_stop", self.pre_stop_time), self.score_x, self.score_y+340) + -- Prestop frame + draw_label(themes[config.theme].images.IMG_prestop_frame, self.origin_x+themes[config.theme].prestop_frame_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].prestop_frame_Pos[2], 0, themes[config.theme].prestop_frame_Scale, self.multiplication) + -- Prestop bar + if self.pre_stop_time == 0 or self.maxPrestop == nil then self.maxPrestop = 0 end + if self.pre_stop_time > self.maxPrestop then self.maxPrestop = self.pre_stop_time end + + prestop_frame_Pos = {(self.origin_x+themes[config.theme].prestop_frame_Pos[1]*self.mirror_x)+((themes[config.theme].images.IMG_prestop_frame:getWidth()-10)/GFX_SCALE*self.multiplication*self.mirror_x), self.pos_y+themes[config.theme].prestop_frame_Pos[2]} + if self.maxPrestop > 0 then prestop_bar = self.pre_stop_time*(themes[config.theme].images.IMG_prestop_bar:getHeight()/self.maxPrestop) else prestop_bar = 0 end + prestopQuad:setViewport(0, themes[config.theme].images.IMG_prestop_bar:getHeight()-prestop_bar, themes[config.theme].images.IMG_prestop_bar:getWidth(), prestop_bar) + qdraw(themes[config.theme].images.IMG_prestop_bar, prestopQuad, self.origin_x+(themes[config.theme].prestop_bar_Pos[1]*self.mirror_x), ((self.pos_y+themes[config.theme].prestop_bar_Pos[2])+((themes[config.theme].images.IMG_prestop_bar:getHeight()-prestop_bar)/GFX_SCALE)), + themes[config.theme].prestop_bar_Rotate, themes[config.theme].prestop_bar_Scale/GFX_SCALE, themes[config.theme].prestop_bar_Scale/GFX_SCALE, 0, 0, self.multiplication) + -- Prestop number + draw_number(self.pre_stop_time, themes[config.theme].images.IMG_timeNumber_atlas, 12, prestop_quads, (self.origin_x+(themes[config.theme].prestop_Pos[1]*self.mirror_x))*GFX_SCALE, (self.pos_y+themes[config.theme].prestop_Pos[2])*GFX_SCALE, themes[config.theme].prestop_Scale, + (15/themes[config.theme].images.timeNumberWidth*themes[config.theme].prestop_Scale), (19/themes[config.theme].images.timeNumberHeight*themes[config.theme].prestop_Scale), "center", self.multiplication) + + -- Stop frame + draw_label(themes[config.theme].images.IMG_stop_frame, self.origin_x+themes[config.theme].stop_frame_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].stop_frame_Pos[2], 0, themes[config.theme].stop_frame_Scale, self.multiplication) + -- Stop bar + if self.stop_time == 0 or self.maxStop == nil then self.maxStop = 0 end + if self.stop_time > self.maxStop then self.maxStop = self.stop_time end + if self.maxStop > 0 then stop_bar = self.stop_time*(themes[config.theme].images.IMG_stop_bar:getHeight()/self.maxStop) else stop_bar = 0 end + stopQuad:setViewport(0, themes[config.theme].images.IMG_stop_bar:getHeight()-stop_bar, themes[config.theme].images.IMG_stop_bar:getWidth(), stop_bar) + qdraw(themes[config.theme].images.IMG_stop_bar, stopQuad, self.origin_x+themes[config.theme].stop_bar_Pos[1]*self.mirror_x, ((self.pos_y+themes[config.theme].stop_bar_Pos[2])+((themes[config.theme].images.IMG_stop_bar:getHeight()-stop_bar)/GFX_SCALE)), + themes[config.theme].stop_bar_Rotate, themes[config.theme].stop_bar_Scale/GFX_SCALE, themes[config.theme].stop_bar_Scale/GFX_SCALE, 0, 0, self.multiplication) + -- Stop number + draw_number(self.stop_time, themes[config.theme].images.IMG_timeNumber_atlas, 12, stop_quads, (self.origin_x+(themes[config.theme].stop_Pos[1]*self.mirror_x))*GFX_SCALE, (self.pos_y+themes[config.theme].stop_Pos[2])*GFX_SCALE, themes[config.theme].stop_Scale, + (15/themes[config.theme].images.timeNumberWidth*themes[config.theme].stop_Scale), (19/themes[config.theme].images.timeNumberHeight*themes[config.theme].stop_Scale), "center", self.multiplication) + + -- Shake frame + draw_label(themes[config.theme].images.IMG_shake_frame, self.origin_x+themes[config.theme].shake_frame_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].shake_frame_Pos[2], 0, themes[config.theme].shake_frame_Scale, self.multiplication) + -- Shake bar + if self.shake_time == 0 or self.maxShake == nil then self.maxShake = 0 end + if self.shake_time > self.maxShake then self.maxShake = self.shake_time end + if self.maxShake > 0 then shake_bar = self.shake_time*(themes[config.theme].images.IMG_shake_bar:getHeight()/self.maxShake) else shake_bar = 0 end + shakeQuad:setViewport(0, themes[config.theme].images.IMG_shake_bar:getHeight()-shake_bar, themes[config.theme].images.IMG_shake_bar:getWidth(), shake_bar) + qdraw(themes[config.theme].images.IMG_shake_bar, shakeQuad, self.origin_x+themes[config.theme].shake_bar_Pos[1]*self.mirror_x, ((self.pos_y+themes[config.theme].shake_bar_Pos[2])+((themes[config.theme].images.IMG_shake_bar:getHeight()-shake_bar)/GFX_SCALE)), + themes[config.theme].shake_bar_Rotate, themes[config.theme].shake_bar_Scale/GFX_SCALE, themes[config.theme].shake_bar_Scale/GFX_SCALE, 0, 0, self.multiplication) + -- Shake number + draw_number(self.shake_time, themes[config.theme].images.IMG_timeNumber_atlas, 12, shake_quads, (self.origin_x+(themes[config.theme].shake_Pos[1]*self.mirror_x))*GFX_SCALE, (self.pos_y+themes[config.theme].shake_Pos[2])*GFX_SCALE, themes[config.theme].shake_Scale, + (15/themes[config.theme].images.timeNumberWidth*themes[config.theme].shake_Scale), (19/themes[config.theme].images.timeNumberHeight*themes[config.theme].shake_Scale), "center", self.multiplication) + + -- Multibar + + if self.maxShake > 0 then multi_shake_bar = self.shake_time*(themes[config.theme].images.IMG_multibar_shake_bar:getHeight()/self.maxShake) else multi_shake_bar = 0 end + if self.maxStop > 0 then multi_stop_bar = self.stop_time*(themes[config.theme].images.IMG_multibar_stop_bar:getHeight()/self.maxStop) else multi_stop_bar = 0 end + if self.maxPrestop > 0 then multi_prestop_bar = self.pre_stop_time*(themes[config.theme].images.IMG_multibar_prestop_bar:getHeight()/self.maxPrestop) else multi_prestop_bar = 0 end + multi_shakeQuad:setViewport(0, themes[config.theme].images.IMG_multibar_shake_bar:getHeight()-multi_shake_bar, themes[config.theme].images.IMG_multibar_shake_bar:getWidth(), multi_shake_bar) + multi_stopQuad:setViewport(0, themes[config.theme].images.IMG_multibar_stop_bar:getHeight()-multi_stop_bar, themes[config.theme].images.IMG_multibar_stop_bar:getWidth(), multi_stop_bar) + multi_prestopQuad:setViewport(0, themes[config.theme].images.IMG_multibar_prestop_bar:getHeight()-multi_prestop_bar, themes[config.theme].images.IMG_multibar_prestop_bar:getWidth(), multi_prestop_bar) + + draw_label(themes[config.theme].images.IMG_multibar_frame, self.origin_x+themes[config.theme].multibar_frame_Pos[1]*self.mirror_x, self.pos_y+themes[config.theme].multibar_frame_Pos[2], 0, themes[config.theme].multibar_frame_Scale, self.multiplication) + --Shake + qdraw(themes[config.theme].images.IMG_multibar_shake_bar, multi_shakeQuad, self.origin_x+themes[config.theme].multibar_Pos[1]*self.mirror_x, ((self.pos_y+themes[config.theme].multibar_Pos[2])+((themes[config.theme].images.IMG_multibar_shake_bar:getHeight()-multi_shake_bar)/GFX_SCALE)), + 0, themes[config.theme].multibar_Scale/GFX_SCALE, themes[config.theme].multibar_Scale/GFX_SCALE, 0, 0, self.multiplication) + --Stop + qdraw(themes[config.theme].images.IMG_multibar_stop_bar, multi_stopQuad, self.origin_x+themes[config.theme].multibar_Pos[1]*self.mirror_x, (((self.pos_y-(multi_shake_bar/GFX_SCALE))+themes[config.theme].multibar_Pos[2])+((themes[config.theme].images.IMG_multibar_stop_bar:getHeight()-multi_stop_bar)/GFX_SCALE)), + 0, themes[config.theme].multibar_Scale/GFX_SCALE, themes[config.theme].multibar_Scale/GFX_SCALE, 0, 0, self.multiplication) + -- Prestop + qdraw(themes[config.theme].images.IMG_multibar_prestop_bar, multi_prestopQuad, self.origin_x+(themes[config.theme].multibar_Pos[1]*self.mirror_x), (((self.pos_y-(multi_shake_bar/GFX_SCALE+multi_stop_bar/GFX_SCALE))+themes[config.theme].multibar_Pos[2])+((themes[config.theme].images.IMG_multibar_prestop_bar:getHeight()-multi_prestop_bar)/GFX_SCALE)), + 0, themes[config.theme].multibar_Scale/GFX_SCALE, themes[config.theme].multibar_Scale/GFX_SCALE, 0, 0, self.multiplication) + + if config.debug_mode and self.danger then gprint("danger", self.score_x,self.score_y+135) end + if config.debug_mode and self.danger_music then gprint("danger music", self.score_x, self.score_y+150) end + if config.debug_mode then + gprint(loc("pl_cleared", (self.panels_cleared or 0)), self.score_x, self.score_y+165) + end + if config.debug_mode then + gprint(loc("pl_metal", (self.metal_panels_queued or 0)), self.score_x, self.score_y+180) + end + if config.debug_mode and (self.input_state or self.taunt_up or self.taunt_down) then + local iraise, iswap, iup, idown, ileft, iright = unpack(base64decode[self.input_state]) + local inputs_to_print = "inputs:" + if iraise then inputs_to_print = inputs_to_print.."\nraise" end --◄▲▼► + if iswap then inputs_to_print = inputs_to_print.."\nswap" end + if iup then inputs_to_print = inputs_to_print.."\nup" end + if idown then inputs_to_print = inputs_to_print.."\ndown" end + if ileft then inputs_to_print = inputs_to_print.."\nleft" end + if iright then inputs_to_print = inputs_to_print.."\nright" end + if self.taunt_down then inputs_to_print = inputs_to_print.."\ntaunt_down" end + if self.taunt_up then inputs_to_print = inputs_to_print.."\ntaunt_up" end + gprint(inputs_to_print, self.score_x, self.score_y+195) + end end - if config.debug_mode then - gprint("metal q: "..(self.metal_panels_queued or 0), self.score_x, self.score_y+180) + --local main_infos_screen_pos = { x=375 + (canvas_width-legacy_canvas_width)/2, y=10 + (canvas_height-legacy_canvas_height) } + if match_type ~= "" then + --gprint(match_type, main_infos_screen_pos.x, main_infos_screen_pos.y-50) + if match_type == "Ranked" then IMG_match = themes[config.theme].images.IMG_ranked end + if match_type == "Casual" then IMG_match = themes[config.theme].images.IMG_casual end + draw_label(IMG_match, (main_infos_screen_pos.x+themes[config.theme].matchtypeLabel_Pos[1])/GFX_SCALE, (main_infos_screen_pos.y+themes[config.theme].matchtypeLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].matchtypeLabel_Scale) + --[[ + if self.win_counts == nil then win = 0 else win = self.win_counts end + draw(themes[config.theme].images.IMG_wins, (self.score_x+themes[config.theme].winLabel_Pos[1])/GFX_SCALE, (self.score_y+themes[config.theme].winLabel_Pos[2])/GFX_SCALE, 0, + (60/themes[config.theme].images.IMG_wins:getWidth()*themes[config.theme].winLabel_Scale)/GFX_SCALE, (28/themes[config.theme].images.IMG_wins:getHeight()*themes[config.theme].winLabel_Scale)/GFX_SCALE) + draw_number(win, themes[config.theme].images.IMG_timeNumber_atlas, 12, win_quads, self.score_x+themes[config.theme].win_Pos[1], self.score_y+themes[config.theme].win_Pos[2], themes[config.theme].win_Scale, + 20/themes[config.theme].images.timeNumberWidth*themes[config.theme].time_Scale, 26/themes[config.theme].images.timeNumberHeight*themes[config.theme].time_Scale, "center") + ]] end - if config.debug_mode and self.input_state then - -- print(self.input_state) - -- print(base64decode[self.input_state]) - local iraise, iswap, iup, idown, ileft, iright = unpack(base64decode[self.input_state]) - -- print(tostring(raise)) - local inputs_to_print = "inputs:" - if iraise then inputs_to_print = inputs_to_print.."\nraise" end --◄▲▼► - if iswap then inputs_to_print = inputs_to_print.."\nswap" end - if iup then inputs_to_print = inputs_to_print.."\nup" end - if idown then inputs_to_print = inputs_to_print.."\ndown" end - if ileft then inputs_to_print = inputs_to_print.."\nleft" end - if iright then inputs_to_print = inputs_to_print.."\nright" end - gprint(inputs_to_print, self.score_x, self.score_y+195) - end - local main_infos_screen_pos = { x=375 + (canvas_width-legacy_canvas_width)/2, y=10 + (canvas_height-legacy_canvas_height) } - if match_type then gprint(match_type, main_infos_screen_pos.x, main_infos_screen_pos.y) end - if P1 and P1.game_stopwatch and tonumber(P1.game_stopwatch) then - gprint(frames_to_time_string(P1.game_stopwatch, P1.mode == "endless"), main_infos_screen_pos.x+10, main_infos_screen_pos.y+16) + if P1 and P1.game_stopwatch and tonumber(P1.game_stopwatch) and self.mode ~= "time" then + --gprint(frames_to_time_string(P1.game_stopwatch, P1.mode == "endless"), main_infos_screen_pos.x+10, main_infos_screen_pos.y+6) + draw_label(themes[config.theme].images.IMG_time, (main_infos_screen_pos.x+themes[config.theme].timeLabel_Pos[1])/GFX_SCALE, (main_infos_screen_pos.y+themes[config.theme].timeLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].timeLabel_Scale) + draw_time(frames_to_time_string(P1.game_stopwatch, P1.mode == "endless"), time_quads, main_infos_screen_pos.x+themes[config.theme].time_Pos[1], main_infos_screen_pos.y+themes[config.theme].time_Pos[2], + 20/themes[config.theme].images.timeNumberWidth*themes[config.theme].time_Scale, 26/themes[config.theme].images.timeNumberHeight*themes[config.theme].time_Scale) end + if not config.debug_mode then gprint(join_community_msg or "", main_infos_screen_pos.x-45, main_infos_screen_pos.y+550) end end if self.enable_analytics then - analytics_draw(self.score_x-460,self.score_y) + analytics.draw(self.score_x-500,self.score_y) end -- ends here end @@ -596,12 +634,12 @@ function Stack.render_cursor(self) local shake = ceil((shake_arr[shake_idx] or 0) * 13) if self.countdown_timer then if self.CLOCK % 2 == 0 then - draw(IMG_cursor[1], + draw(themes[config.theme].images.IMG_cursor[1], (self.cur_col-1)*16, (11-(self.cur_row))*16+self.displacement-shake) end else - draw(IMG_cursor[(floor(self.CLOCK/16)%2)+1], + draw(themes[config.theme].images.IMG_cursor[(floor(self.CLOCK/16)%2)+1], (self.cur_col-1)*16, (11-(self.cur_row))*16+self.displacement-shake) end @@ -616,18 +654,23 @@ function Stack.render_countdown(self) local countdown_y = 68 if self.countdown_CLOCK <= 8 then local ready_y = initial_ready_y + (self.CLOCK - 1) * ready_y_drop_speed - draw(IMG_ready, ready_x, ready_y) + draw(themes[config.theme].images.IMG_ready, ready_x, ready_y) if self.countdown_CLOCK == 8 then self.ready_y = ready_y end elseif self.countdown_CLOCK >= 9 and self.countdown_timer and self.countdown_timer > 0 then if self.countdown_timer >= 100 then - draw(IMG_ready, ready_x, self.ready_y or initial_ready_y + 8 * 6) + draw(themes[config.theme].images.IMG_ready, ready_x, self.ready_y or initial_ready_y + 8 * 6) end - local IMG_number_to_draw = IMG_numbers[math.ceil(self.countdown_timer / 60)] + local IMG_number_to_draw = themes[config.theme].images.IMG_numbers[math.ceil(self.countdown_timer / 60)] if IMG_number_to_draw then draw(IMG_number_to_draw, countdown_x, countdown_y) end end end end +function draw_pause() + draw(themes[config.theme].images.pause,0,0) + gprintf(loc("pause"), 0, 330, canvas_width, "center",nil,1,large_font) + gprintf(loc("pl_pause_help"), 0, 360, canvas_width, "center",nil,1) +end diff --git a/graphics_util.lua b/graphics_util.lua new file mode 100644 index 00000000..ce324aa0 --- /dev/null +++ b/graphics_util.lua @@ -0,0 +1,279 @@ +require("consts") + +local function load_img(path_and_name) + local img = love.image.newImageData(path_and_name) + if img == nil then + return nil + end + -- print("loaded asset: "..path_and_name) + local ret = love.graphics.newImage(img) + ret:setFilter("nearest","nearest") + return ret +end + +function load_img_from_supported_extensions(path_and_name) + local supported_img_formats = { ".png", ".jpg" } + for _, extension in ipairs(supported_img_formats) do + if love.filesystem.getInfo(path_and_name..extension) then + return load_img(path_and_name..extension) + end + end + return nil +end + +function draw(img, x, y, rot, x_scale, y_scale) + rot = rot or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + gfx_q:push({love.graphics.draw, {img, x*GFX_SCALE, y*GFX_SCALE, + rot, x_scale*GFX_SCALE, y_scale*GFX_SCALE}}) +end + +function draw_label(img, x, y, rot, scale, mirror) + rot = rot or 0 + mirror = mirror or 0 + x = x - (img:getWidth()/GFX_SCALE*scale)*mirror + gfx_q:push({love.graphics.draw, {img, x*GFX_SCALE, y*GFX_SCALE, + rot, scale, scale}}) +end + +function draw_number(number, atlas, frameCount, quads, x, y, scale, x_scale, y_scale, align, mirror) + x_scale = x_scale or 1 + y_scale = y_scale or 1 + align = align or "left" + mirror = mirror or 0 + + local width = atlas:getWidth() + local height = atlas:getHeight() + local numberWidth = atlas:getWidth()/frameCount + local numberHeight = atlas:getHeight() + + x = x - (numberWidth*GFX_SCALE*scale)*mirror + + if number == nil or atlas == nil or numberHeight == nil or numberWidth == nil then return end + + while #quads < #tostring(number) do + table.insert(quads, love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height)) + end + + for i = 1, #tostring(number), 1 do + local c = tostring(number):sub(i,i) + if c == nil then return end + quads[i]:setViewport(tonumber(c)*numberWidth, 0, numberWidth, numberHeight, width, height) + if align == "left" then + gfx_q:push({love.graphics.draw, {atlas, quads[i], ((x+(i*(13*scale)))-(13*scale)), y, + 0, x_scale, y_scale}}) + end + if align == "center" then + gfx_q:push({love.graphics.draw, {atlas, quads[i], (x+((i-(#tostring(number)/2))*(13*scale))), y, + 0, x_scale, y_scale}}) + end + if align == "right" then + gfx_q:push({love.graphics.draw, {atlas, quads[i], (x+((i-#tostring(number))*(13*scale))), y, + 0, x_scale, y_scale}}) + end + end + +end + +function draw_time(time, quads, x, y, x_scale, y_scale) + x_scale = x_scale or 1 + y_scale = y_scale or 1 + + if #quads == 0 then + width = themes[config.theme].images.IMG_timeNumber_atlas:getWidth() + height = themes[config.theme].images.IMG_timeNumber_atlas:getHeight() + numberWidth = themes[config.theme].images.timeNumberWidth + numberHeight = themes[config.theme].images.timeNumberHeight + quads = + { + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height), + love.graphics.newQuad(0, 0, numberWidth, numberHeight, width, height) + } + + symbolEnum = {[":"]=10, ["'"]=11} + for i = 1, #time, 1 do + local c = time:sub(i,i) + + if c ~= ":" and c ~= "'" then + quads[i]:setViewport(tonumber(c)*numberWidth, 0, numberWidth, numberHeight, width, height) + else + quads[i]:setViewport(symbolEnum[c]*numberWidth, 0, numberWidth, numberHeight, width, height) + end + gfx_q:push({love.graphics.draw, {themes[config.theme].images.IMG_timeNumber_atlas, quads[i], ((x+(i*(20*themes[config.theme].time_Scale)))-(20*themes[config.theme].time_Scale))+((7-#time)*10), y, + 0, x_scale, y_scale}}) + end + + end +end + +function qdraw(img, quad, x, y, rot, x_scale, y_scale, x_offset, y_offset, mirror) + rot = rot or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + x_offset = x_offset or 0 + y_offset = y_offset or 0 + mirror = mirror or 0 + + qX, qY, qW, qH = quad:getViewport() + if mirror == 1 then + x = x - (qW*x_scale) + end + gfx_q:push({love.graphics.draw, {img, quad, x*GFX_SCALE, y*GFX_SCALE, + rot, x_scale*GFX_SCALE, y_scale*GFX_SCALE, x_offset, y_offset}}) +end + +function menu_draw(img, x, y, rot, x_scale,y_scale) + rot = rot or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + gfx_q:push({love.graphics.draw, {img, x, y, + rot, x_scale, y_scale}}) +end + +function menu_drawf(img, x, y, halign, valign, rot, x_scale, y_scale) + rot = rot or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + halign = halign or "left" + if halign == "center" then + x = x - math.floor(img:getWidth() * 0.5 * x_scale) + elseif halign == "right" then + x = x - math.floor(img:getWidth() * x_scale) + end + valign = valign or "top" + if valign == "center" then + y = y - math.floor(img:getHeight() * 0.5 * y_scale) + elseif valign == "bottom" then + y = y - math.floor(img:getHeight() * y_scale) + end + gfx_q:push({love.graphics.draw, {img, x, y, + rot, x_scale, y_scale}}) +end + +function menu_drawq(img, quad, x, y, rot, x_scale,y_scale) + rot = rot or 0 + x_scale = x_scale or 1 + y_scale = y_scale or 1 + gfx_q:push({love.graphics.draw, {img, quad, x, y, + rot, x_scale, y_scale}}) +end + +function grectangle(mode, x, y, w, h) + gfx_q:push({love.graphics.rectangle, {mode, x, y, w, h}}) +end + +function grectangle_color(mode, x, y, w, h, r, g, b, a) + a = a or 1 + gfx_q:push({love.graphics.setColor, {r, g, b, a}}) + gfx_q:push({love.graphics.rectangle, {mode, x*GFX_SCALE, y*GFX_SCALE, w*GFX_SCALE, h*GFX_SCALE}}) + gfx_q:push({love.graphics.setColor, {1, 1, 1, 1}}) +end + +function gprint(str, x, y, color, scale) + x = x or 0 + y = y or 0 + scale = scale or 1 + color = color or nil + set_color(0, 0, 0, 1) + gfx_q:push({love.graphics.print, {str, x+1, y+1, 0, scale}}) + local r, g, b, a = 1,1,1,1 + if color ~= nil then + r,g,b,a = unpack(color) + end + set_color(r,g,b,a) + gfx_q:push({love.graphics.print, {str, x, y, 0, scale}}) +end + +-- font file to use +local font_file = nil +local font_size = 12 +local font_cache = {} + +function set_global_font(filepath, size) + font_cache = {} + font_file = filepath + font_size = size + local f + if font_file then + f = love.graphics.newFont(font_file, font_size) + else + f = love.graphics.newFont(font_size) + end + f:setFilter("nearest", "nearest") + love.graphics.setFont(f) +end + +local function get_font_delta(with_delta_size) + local font_size = font_size + with_delta_size + local f = font_cache[font_size] + if not f then + if font_file then + f = love.graphics.newFont(font_file, font_size) + else + f = love.graphics.newFont(font_size) + end + font_cache[font_size] = f + end + return f +end + +function set_font(font) + gfx_q:push({love.graphics.setFont, {font}}) +end + +function set_shader(shader) + gfx_q:push({love.graphics.setShader, {shader}}) +end + +function gprintf(str, x, y, limit, halign, color, scale, font_delta_size) + x = x or 0 + y = y or 0 + scale = scale or 1 + color = color or nil + limit = limit or canvas_width + font_delta_size = font_delta_size or 0 + halign = halign or "left" + set_color(0, 0, 0, 1) + local old_font = love.graphics.getFont() + if font_delta_size ~= 0 then + set_font(get_font_delta(font_delta_size)) + end + gfx_q:push({love.graphics.printf, {str, x+1, y+1, limit, halign, 0, scale}}) + local r, g, b, a = 1,1,1,1 + if color ~= nil then + r,g,b,a = unpack(color) + end + set_color(r,g,b,a) + gfx_q:push({love.graphics.printf, {str, x, y, limit, halign, 0, scale}}) + if font_delta_size ~= 0 then set_font(old_font) end +end + +local _r, _g, _b, _a +function set_color(r, g, b, a) + a = a or 1 + -- only do it if this color isn't the same as the previous one... + if _r~=r or _g~=g or _b~=b or _a~=a then + _r,_g,_b,_a = r,g,b,a + gfx_q:push({love.graphics.setColor, {r, g, b, a}}) + end +end + +function file_exists(name) + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end +end + +function reset_filters() + background_overlay = nil + foreground_overlay = nil +end \ No newline at end of file diff --git a/input.lua b/input.lua index 344456ae..cec6a1a0 100644 --- a/input.lua +++ b/input.lua @@ -1,3 +1,5 @@ +require("util") + local jpexists, jpname, jrname for k,v in pairs(love.handlers) do if k=="jp" then @@ -102,6 +104,7 @@ function love.textinput(text) end function love.keyreleased(key, unicode) + this_frame_released_keys[key] = keys[key] -- retains state in this_frame_released_keys keys[key] = nil end @@ -110,3 +113,108 @@ function key_counts() keys[key] = value + 1 end end + +-- Keys that have a fixed function in menus can be bound to other +-- meanings, but should continue working the same way in menus. +local menu_reserved_keys = {} + +-- Changes the behavior of menu_foo functions. +-- In a menu that doesn't specifically pertain to multiple players, +-- up, down, left, right should always work. But in a multiplayer +-- menu, those keys should definitely not move many cursors each. +local multi = false +local function multi_func(func) + return function(...) + multi = true + local res = {func(...)} + multi = false + return unpack(res) + end +end + +function repeating_key(key) + local key_time = keys[key] + return this_frame_keys[key] or (key_time and key_time > default_input_repeat_delay and key_time % 2 == 0) -- menues key repeat delay 20 frames then every 2 frames +end + +local function normal_key(key) return this_frame_keys[key] end + +local function released_key_before_time(key, time) + return this_frame_released_keys[key] and this_frame_released_keys[key] < time +end + +local function released_key_after_time(key, time) + return this_frame_released_keys[key] and this_frame_released_keys[key] >= time +end + +local function menu_key_func(fixed, configurable, query, sound, ...) + sound = sound or nil + local other_args = ... + return function(k, silent) + silent = silent or false + local res = false + if multi then + for i=1,#configurable do + res = res or query(k[configurable[i]], other_args) + end + else + for i=1,#fixed do + res = res or query(fixed[i], other_args) + end + for i=1,#configurable do + local keyname = k[configurable[i]] + res = res or query(keyname, other_args) and + not menu_reserved_keys[keyname] + end + end + local sfx_callback = function() + if sound ~= nil then + play_optional_sfx(sound()) + end + end + if res and not silent then + sfx_callback() + end + return res, sfx_callback + end +end + +local function get_pressed_ratio(key,time) + return keys[key] and keys[key]/time or (this_frame_released_keys[key] and this_frame_released_keys[key]/time or 0) +end + +local function get_being_pressed_for_duration_ratio(fixed, configurable, time) + return function(k) + local res = 0 + if multi then + for i=1,#configurable do + res = math.max(get_pressed_ratio(k[configurable[i]], time),res) + end + else + for i=1,#fixed do + res = math.max(get_pressed_ratio(fixed[i], time),res) + end + for i=1,#configurable do + local keyname = k[configurable[i]] + if not menu_reserved_keys[keyname] then + res = math.max(get_pressed_ratio(k[configurable[i]], time),res) + end + end + end + return bound(0,res,1) + end +end + +menu_reserved_keys = {"up", "down", "left", "right", "escape","x", "pageup", "pagedown", "backspace", "return","kenter","z"} +menu_up = menu_key_func({"up"}, {"up"}, repeating_key, function() return themes[config.theme].sounds.menu_move end ) +menu_down = menu_key_func({"down"}, {"down"}, repeating_key, function() return themes[config.theme].sounds.menu_move end) +menu_left = menu_key_func({"left"}, {"left"}, repeating_key, function() return themes[config.theme].sounds.menu_move end) +menu_right = menu_key_func({"right"}, {"right"}, repeating_key, function() return themes[config.theme].sounds.menu_move end) +menu_escape = menu_key_func({"escape","x"}, {"swap2"}, normal_key, function() return themes[config.theme].sounds.menu_cancel end) +menu_prev_page = menu_key_func({"pageup"}, {"raise1"}, repeating_key, function() return themes[config.theme].sounds.menu_move end) +menu_next_page = menu_key_func({"pagedown"}, {"raise2"}, repeating_key, function() return themes[config.theme].sounds.menu_move end) +menu_backspace = menu_key_func({"backspace"}, {"backspace"}, repeating_key) +menu_long_enter = menu_key_func({"return","kenter","z"}, {"swap1"}, released_key_after_time, function() return themes[config.theme].sounds.menu_validate end, super_selection_duration) +menu_enter = menu_key_func({"return","kenter","z"}, {"swap1"}, released_key_before_time, function() return themes[config.theme].sounds.menu_validate end, super_selection_duration) +menu_enter_one_press = menu_key_func({"return","kenter","z"}, {"swap1"}, normal_key, function() return themes[config.theme].sounds.menu_validate end, super_selection_duration) +menu_pressing_enter = get_being_pressed_for_duration_ratio({"return","kenter","z"}, {"swap1"}, super_selection_duration) diff --git a/jp.ttf b/jp.ttf new file mode 100644 index 00000000..9648b721 Binary files /dev/null and b/jp.ttf differ diff --git a/localization.csv b/localization.csv new file mode 100644 index 00000000..dbdd071e --- /dev/null +++ b/localization.csv @@ -0,0 +1,389 @@ +CODE,EN,FR,PT,JP,ES,GE,IT +LANG,English,Français,Português,日本語,Español,Deutsch,Italiano +back,Back,Retour,Voltar,もどる,Volver,Zurück,Indietro +difficulty,Difficulty,Difficulté,Dificuldade,難易度,Dificultad,Schwierigkeitsgrad,Difficoltà +easy,Easy,Facile,Fácil,EASY,Fácil,Leicht,Facile +hard,Hard,Difficile,Difícil,HARD,Difícil,Schwer,Difficile +level,Level,Niveau,Nível,Lv,Nivel,Level,Livello +normal,Normal,Normal,Normal,NORMAL,Normal,Normal,Normale +speed,Speed,Vitesse,Velocidade,スピード,Velocidad,Geschwindigkeit,Velocità +vs,"vs +",vs,vs,対,vs,vs,vs +up,Up,Haut,Cima,上,Arriba,Hoch,Su +down,Down,Bas,Baixo,下,Abajo,runter,Giù +left,Left,Gauche,Esquerda,左,Izquierda,links,Sinistra +right,Right,Droite,Direita,右,Derecha,rechts,Destra +start,Start,Start,Começar,START,Empezar,Start,Avvio +player,Player,Joueur,Jogador,プレイヤー,Jugador,Spieler,Giocatore +player_n,Player %1,Joueur %1,Jogador %1,プレイヤー%1,Jugador %1,Spieler %1,Giocatore %1 +page,Page,Page,Página,ページ,Página,Seite,Pagina +panels,Panels,"Panels +",Painéis,パネル,Paneles,Feld,Tessere +stage,Stage,Terrain,Cenário,ステージ,Escenario,Bühne,Scenario +mode,Mode,Mode,Modo,モード,Modo,Modus,Modalità +ready,Ready,Prêt,Pronto,READY,Listo,Bereit,Pronto +random,Random,Aléatoire,Aleatório,ランダム,Aleatorio,Zufall,Casuale +leave,Leave,Quitter,Sair,もどる,Salir,Verlassen,Esci +pause,Pause,Pause,Pausa,ポーズ,Pausa,Pause,Pausa +go_,Go!,Commencer,Vai!,GO!,¡Comenzar!,Los!,Via! +anonymous,Anonymous,Anonyme,Anônimo,匿名,Anónimo,Anonym,Anonimo +stages,stages,terrains,Cenários,ステージ,escenarios,Bühnen,Scenario +characters,characters,personnages,Personagens,キャラクター,personajes,Charaktere,Personaggi +join_community, Join the community at,Rejoignez la communauté sur,Participe da comunidade em,コミュニティに参加しよう,Únete a nuestra comunidad en,Tritt unserer community bei unter,Unisciti alla community su +warning_msg,You've had a bug. Please report this on Discord with file:,"Vous venez de rencontrer un problème. +S'il vous plaît, signalez le sur Discord avec le fichier :","Você encontrou um problema. Por favor, reporte no Discord o arquivo em anexo:",You've had a bug. Please report this on Discord with file:,"Hubo un problema. Por favor, reporta esto en Discord con el siguiente archivo:",Ein Bug ist aufgetreten. Bitte melde diesen in unserem Discord mit der Datei:,"Hai riscontrato un bug. Per favore, segnalalo su Discord con il file: " +,,,,,,, +lb_alone,You are all alone in the lobby :(,Vous êtes seul dans le salon :(,Você está sozinho(a) no lobby :(,だれもロビーにいません。(´・ω・`),No hay nadie en el salón :(,Du bist ganz alleine in der Lobby :<,Sei tutto solo nella lobby :( +lb_back,Back to main menu,Revenir au menu principal,Voltar ao menu principal,メインメニューに戻る,Volver al menú principal,Zurück zum Hauptmenü,Torna al menù principale +lb_connecting,Connecting...,Connexion en cours...,Conectando...,接続中...,Conectando,Verbinde...,Connessione... +lb_error_msg,Error message received from the server:,Message d'erreur du serveur : ,Mensagem de erro recebida do servidor:,サーバーから受信したエラーメッセージ:,Mensaje de error recibido del servidor:,Fehlernachricht vom Server erhalten.,Messaggio d'errore ricevuto dal server: +lb_header_board," Leaderboard + Rank Rating Player"," Classement + Rang Score Joueur"," Placar de Classificação +      Classificação Pontuação Jogador "," リーダーボード +      順位 レート プレイヤー"," Clasificación +      Rango Puntaje Jugador", ," Classifica + Posizione Punteggio Giocatore" +lb_show_board,Show Leaderboard,Afficher le classement,Mostrar Placar de Classificação,リーダーボードを表示,Mostrar Tabla de Puntuación, ,Mostra la classifica +lb_login,Logging in...,Identification...,Iniciando sessão ...,ログインしています...,Iniciando Sesión, ,Accesso in corso... +lb_login_timeout,"Login for ranked matches timed out. +This server probably doesn't support ranking. + +You may continue unranked.","Délai de connexion à la partie classée dépassé. +Ce serveur ne gère peut être pas les parties classées. + +Vous pouvez continuer en non classé.","O tempo de acesso à partidas ranqueadas expirou. +Este servidor provavelmente não suporta partidas ranqueadas. + +Você pode continuar em partidas não ranqueadas. ","レートマッチのログインがタイムアウトしました。 +このサーバーはおそらくランキングをサポートしていません。 + +ランクなしで続行できます。","Se ha agotado el tiempo de espera para iniciar sesión +Es posible que el servidor no soporte partidas clasificatorias. + +Solamente puedes jugar partidas normales (unranked).", ,"L'accesso alle partite classificate è scaduto. +Questo server probabilmente non supporta le partite classificate. + +Puoi continuare a giocare in modalità non clasificata." +lb_new_id,need a new user id,besoin d'un nouvel identifiant utilisateur,necessário novo ID de usuário,新しいユーザーIDが必要です,Se necesita un nuevo ID de usuario, ,Necessario nuovo ID utente. +lb_received,(wants to play with you :o),(vous invite à jouer :o),(quer jogar com você :o),(対戦を申し込まれました!(・o・)),(quiere jugar contigo :o),,(vuole giocare con te :o) +lb_request,(request sent),(demande envoyée),(solicitação enviada),(対戦申請中),(petición enviada), ,(richiesta inviata) +lb_select_player,Select a player name to ask for a match.,Selectionnez un joueur avec qui jouer.,Selecione um nome de jogador para solicitar uma partida.,プレイヤー名を選択して試合を申し込んで下さい。,Selecciona el nombre de un jugador para enviarle solicitud de partida., ,Seleziona il nome di un giocatore con cui giocare. +lb_set_connect,Setting up connection...,Préparation de la connexion...,Estabelecendo conexão...,接続しています…,Estableciendo conexión..., ,Stabilendo la connessione... +lb_hide_board,Hide Leaderboard,Cacher le classement,Ocultar Placar de Classificação,リーダーボードを非表示,Ocultar Tabla de Puntuación, ,Nascondi la classifica +lb_spectate,spectate,regarder,assistir,観戦する,espectar, ,Assisti +lb_used_name,"Error: name is taken :< + +If you had just left the server, +it may not have realized it yet, try joining again. + +This can also happen if you have two +instances of Panel Attack open. +Press Swap or Back to continue.","Erreur : ce pseudo est déjà pris :< + +Si vous venez de quitter le serveur, +il n'est peut être pas encore à jour, réessayez. + +Cela peut aussi arriver si vous avez +lancé deux instances du jeu. + +Appuyer sur Valider ou Retour pour continuer.","Erro: Apelido já utilizado :< + +Se você acabou de sair do servidor, +talvez não tenha reparado, tente entrar novamente. + +Isso também pode acontecer se você tiver duas +instâncias do Panel Attack abertas. +Pressione Trocar ou Voltar para continuar. ","エラー:名前が既に使用されています ・へ・ + +サーバーを離れたばかりの場合、 +まだ気付いていない可能性があります。 +もう一度参加してみて下さい。 + +これは、 +Panel Attackが同時に2回起動されている場合発生する可能性もあります。 + +入れ替えボタン、またはもどるボタンを押して下さい。","Error:El nombre ya está siendo usado :< + +Si te fuiste del servidor, +puede que este no se haya dado cuenta, intenta unirte de nuevo. + +Esto también puede ocurrir si tienes dos +Instancias abiertas del juego. +Pulsa ""Cambiar"" o ""Volver"" para continuar.", ,"Errore: nome già in uso :< + +Se hai appena lasciato il server, +questo potrebbe non averlo ancora realizzato, prova ad accedere di nuovo. + +Questo può anche succedere se hai +due istanze di Panel Attack aperte. +Premi ""Swap"" o Indietro per continuare. +" +lb_user_new,"Welcome, new user: %1","Bienvenue, nouvel utilisateur : %1","Bem-vindo(a), novo usuário(a): %1",ようこそ、新しいユーザー: %1,Bienvenido: nuevo usuario %1, ,"Benvenuto, nuovo utente: %1" +lb_user_update,"Welcome, your username has been updated. + +Old name: ""%1"" + +New name: ""%2""","Bienvenue, votre pseudo a été mis à jour. + +Ancien: ""%1"" + +Nouveau: ""%2""","Bem-vindo(a), seu apelido foi atualizado. + +Nome antigo: ""%1"" + +Novo nome: ""%2"" ","ようこそ、ユーザー名が更新されました。 + +以前のユーザー名: ""%1"" + +新しいユーザー名: ""%2"" ","Bienvenido, tu nombre de usuario fue actualizado. + +Nombre antiguo: ""%1"" + +Nombre nuevo: ""%2"" ", ,"Benvenuto, il tuo nome utente è stato aggiornato. + +Vecchio nome: ""%1"" + +Nuovo nome: ""%2""" +lb_welcome_back,"Welcome back, %1","Vous êtes de retour, %1","Bem-vindo(a) de volta, %1",おかえりなさい、%1,"Bienvenido de vuelta, %1", ,"Bentornato, %1" +lb_you,You,Vous,Você,あなた,Tú, ,Tu +ld_puzzles,Copying puzzles readme...,Copie du fichier readme des puzzles...,Cópia dos quebra-cabeças no leia-me,パズルのREADMEをコピーしています...,"Copiando el archivo ""readme"" de puzzles ",,Copia dell'archivio readme dei puzzle... +ld_replay,Loading replays...,Chargement des replays...,Carregando replays,リプレイを読み込みしています…,Cargando repeticiones...,,Caricamento dei replay... +ld_theme,Loading theme...,Chargement du thème...,Carregando temas,テーマを読み込みしています…,Cargando tema...,,Caricamento dei temi... +ld_characters,Preloading characters...,Préchargement des personnages...,Pré-carregando personagens,キャラクターを読み込みしています…,Pre-cargando personajes...,,Pre-caricamento dei personaggi... +ld_stages,Preloading stages...,Préchargement des terrains...,Pré-carregando cenários...,ステージを読み込みしています…,Pre-cargando escenarios...,,Pre-caricamento degli scenari... +ld_panels,Loading panels...,Chargement des blocs...,Carregando painéis...,パネルを読み込みしています…,Cargando paneles...,,Caricamento delle tessere... +ld_analytics,Loading analytics...,Chargement des statistiques...,Carregando estatísticas...,分析を読み込みしています…,Cargando estadísticas....,,Caricamento delle statistiche... +mm_1_endless,1P endless,1J infini,1J infinito,1P エンドレス,1J interminable, ,1P infinito +mm_1_puzzle,1P puzzle,1J puzzle,1J quebra-cabeça,1P パズル,1J Puzzle, ,1P puzzle +mm_1_time,1P time attack,1J contre la montre,1J contra o tempo,1P スコアアタック,1J Contrareloj, ,1P a tempo +mm_1_vs,1P vs yourself,1J vs soi-même,1J vs você mesmo,1P じぶんとのたたかい,1J vs Tú, ,1P vs te stesso +mm_2_vs_local,2P vs local game,2J vs en local,2J vs jogo local,2P ローカルVS,2J vs partida local, ,2P vs in locale +mm_2_vs_online,2P vs online %1,2J vs en ligne %1,2J vs online %1,2P オンラインVS %1,2J vs partida en línea %1, ,2P vs online %1 +mm_2_vs,2P vs,2J vs,2J vs,2P VS,2J vs,,2P vs +mm_configure,Configure input,Configurer les touches,Configurar teclas,コンフィグ設定,Configurar teclas, ,Configura i tasti +mm_fullscreen,Fullscreen %1,Plein Écran %1,Tela cheia %1,フルスクリーン %1,Pantalla completa %1, ,Schermo inteto 1% +mm_music_test,Music test,Musique test,Teste de música,サウンドテスト,Prueba de música, ,Testa la musica +mm_no_support_fullscreen,Your graphics card doesn't support canvases for fullscreen,Votre carte graphique ne supporte pas le plein écran,Sua placa de vídeo não suporta tela cheia,グラフィックカードはフルスクリーンのキャンバスをサポートしていません,Tu tarjeta gráfica no soporta bordes en pantalla completa., ,La tua scheda grafica non supporta la modalità schermo intero. +mm_options,Options,Options,Opções,オプション,Opciones, ,Opzioni +mm_quit,Quit,Quitter,Sair,おわる,Salir, ,Esci +mm_replay_of,Replay of %1,Replay de %1,Replay de %1,%1のリプレイ,Ver repetición de %1, ,Replay di 1% +mm_set_name,Set name,Changer de pseudo,Trocar apelido,名前設定,Definir nombre, ,Imposta il nome +nt_ver_err,PLEASE DOWNLOAD the latest version of the game from #welcome-getting-started at the TetrisAttackOnline Discord http://discord.panelattack.com,S'IL VOUS PLAIT TELECHARGER la dernière version du jeu sur le discord TetrisAttackOnline http://discord.panelattack.com dans #welcome-getting-started,FAÇA O DOWNLOAD da versão mais recente do jogo em # Welcome-Getting-Started no TetrisAttackOnline Discord http://discord.panelattack.com,TetrisAttackOnline Discord http://discord.panelattack.com で、ゲームの最新バージョンを#welcome-getting-startedからダウンロードして下さい。,POR FAVOR DESCARGA la última version del juego en #welcome-getting-started En el servidor de discord de TetrisAttackOnline:http://discord.panelattack.com, ,"Per favore, scarica l'ultima versione del gioco da #welcome-getting-started sul server Discord TetrisAttackOnline http://discord.panelattack.com" +nt_conn_timeout,Failed to connect to the server.,Impossible de se connecter au serveur.,Falha ao conectar o servidor.,Failed to connect to the server.,Error al conectarse al servidor.,,Errore di connessione al server. +nt_msg_err,"Network error, message could not be decoded: ""%1""","Erreur réseau, le message ne peut pas être décodé: ""%1""","Erro de conexão, a mensagem não pôde ser codificada: ""%1""","Network error, message could not be decoded: ""%1""","Error de red, el mensaje no pudo ser decodificado: ""%1""",,"Errore di network, il messaggio non può essere decodificato: ""%1""" +nt_player_err,Server did not send number of players.,Le serveur n'a pas envoyé le nombre de joueurs.,O servidor não emitiu nº de jogadores.,Server did not send number of players.,El servidor no envio la cantidad de jugadores.,,Il server non ha inviato il numero di giocatori. +op_none,none,rien,Nenhum,無し,Nada, ,Niente +op_on,On,On,Ativ.,ON,On,,On +op_off,Off,Off,Desat.,OFF,Off,,Off +op_all_keys,Set all keys,Définir toutes les touches,Definir todas as teclas,すべてのキーを設定する,Definir todas las teclas, ,Imposta tutti i tasti +op_about_characters,About custom characters,À propos des personnages personnalisés,Sobre personagens personalizados,カスタムキャラクターについて,Acerca de personajes personalizados, ,A propostio dei personaggi personalizzabili +op_about_themes,About custom themes,À propos des thèmes personnalisés,Sobre temas personalizados,カスタムテーマについて,Acerca de temas personalizados,,A proposito dei temi personalizzabili +op_about_stages,About custom stages,À propos des terrains personnalisés,Sobre cenários personalizados,カスタムステージについて,Acerca de escenarios personalizados,,A proposito degli scenari personalizzabili +op_about_panels,About custom panels,À propos des blocs personnalisés,Sobre painéis personalizados,カスタムパネルについて,Acerca de paneles personalizados,,A propostio delle tessere personalizzabili +op_analytics,Enable analytics,Activer les statistiques,Ativar estatísticas,分析を有効にする,Habilitar estadísticas, ,Attiva statistiche +op_copy_files,"Hold on. Copying an example folder to make this easier... + +This may take a few seconds or maybe even a minute or two. + +Don't worry if the window goes inactive or ""not responding""","Veuillez patientez, nous copions les fichiers exemples. + +Cette opération peut prendre quelques secondes à 2 min. + +La fenêtre peut devenir inactive et ne plus répondre.","Um momento. Criando uma pasta modelo para facilitar isso... + +Isto pode levar alguns segundos ou talvez até um minuto ou dois. + +Não se preocupe se a janela ficar inativa ou ""não está respondendo"" ","しばらくお待ちください。 +これを簡単にするためにサンプルフォルダをコピーしています... + +これには数秒、場合によっては1〜2分かかる場合があります。 + +ウィンドウが非アクティブになったり、 +応答しない""ことを心配する必要はありません","Un momento. Copiando la carpeta de ejemplo para hacerlo más facil... + +Esto tomará algunos segundos o quizás un minuto o dos. + +No te preocupes si la ventana se pone inactiva, se bloquea o ""No responde""", ,"Un momento. Copia di una cartella d'esempio per rendere il tutto più facile... + +Ci potrebbe volere qualche secondo, o forse anche un minuto o due. + +Non preoccuparti se la finestra diventa inattiva o ""non risponde""." +op_countdown,Ready countdown,Compte à rebours avant une partie,Contagem regressiva pronta,READYカウントダウン,Cuenta regresiva lista, ,Pronto per il conto alla rovescia +op_debug,Debug Mode,Mode Debug,Modo de Depuração,デバッグモード,Modo Depuración, ,Modalità Debug +op_default_char,Use default characters,Utiliser les personnages par défaut,Usar personagens padrão,デフォルトのキャラクターを使用する,Usar personajes por defecto., ,Usa i personaggi di base +op_enter_name,Enter your name:,Entrez votre pseudo :,Digite seu apelido:,名前を入力して下さい:,Ingresa tu nombre:, ,Inserisci il tuo nome: +op_fps,Show FPS,Afficher les FPS,Mostrar FPS,FPS表示,Mostrar FPS (cuadros por segundo), ,Mostra FPS (frame al secondo) +op_language,Language,Langue,Idioma,言語,Idioma, ,Lingua +op_theme,Theme,Thème,Tema,テーマ,Tema,,Tema +op_ingame_infos,Show informations during gameplay,Afficher les données pendant la partie,Mostrar informações durante partida,プレイ中情報表示,Mostrar información durante la partida,,Mostra informazioni nel corso della partita +op_use_music_from,Use music from,Utiliser les musiques des,Utilizar música de ,使用音楽,Usar música desde,,Usa musica da +op_only_stage,Stage 100% | Character 0%,Terrain 100% | Personnage 0%,Cenário 100% | Personagens 0%,ステージ 100% | キャラクター 0%,Escenario 100% | Personaje 0%,,Scenario 100% | Personaggio 0% +op_often_stage,Stage 75% | Character 25%,Terrain 75% | Personnage 25%,Cenário 75% | Personagens 25%,ステージ 75% | キャラクター 25%,Escenario 75% | Personaje 25%,,Scenario 75% | Personaggio 25% +op_stage_characters,Stage 50% | Character 50%,Terrain 50% | Personnage 50%,Cenário 50% | Personagens 50%,ステージ 50% | キャラクター 50%,Escenario 50% | Personaje 50%,,Scenario 50% | Personaggio 50% +op_often_characters,Stage 25% | Character 75%,Terrain 25% | Personnage 75%,Cenário 25% | Personagens 75%,ステージ 25% | キャラクター 75%,Escenario 25% | Personaje 75%,,Scenario 25% | Personaggio 75% +op_only_characters,Stage 0% | Character 100%,Terrain 0% | Personnage 100%,Cenário 0% | Personagens 100%,ステージ 0% | キャラクター 100%,Escenario 0% | Personaje 100%,,Scenario 0% | Personaggio 100% +op_music_current,Currently playing: ,Music actuel : ,Reproduzindo agora:,現在再生中:,Música actual:, ,Musica in riproduzone: +op_music_delay,Danger music change-back delay,Délai du changement de la musique en mode danger,Atraso na mudança de música de perigo,ピンチ音楽チェンジバック遅延,Retrasar cambio de melodía de peligro, ,Ritardo del cambiamento della musica di pericolo +op_music_intro,Playing the intro,Joue l'intro,Reproduzindo introdução,イントロ表示,Reproduciendo la intro, ,Riproduci la intro +op_music_load,Loading required sounds... (this may take a while),Chargement des sons nécessaires... (cela peut prendre un certain temps),Carregando sons necessários ... (isso pode demorar um pouco),必要なサウンドを読み込んでいます...(時間がかかるかもしれません),Cargando los sonidos requeridos...(esto tomará un momento), ,Caricamento dei suoni necessari (ci potrebbe volere un po'...) +op_music_loop,Playing main loop,Joue la boucle,Reproduzindo loop principal,メインループを再生しています,Reproduciendo bucle principal, ,Riproduzione del loop principale +op_music_nav,"%1 and %2 to navigate songs +%3 to leave","%1 et %2 pour naviguer entre les musiques +%3 pour quitter","%1 e %2 para navegar pelas músicas +%3 para sair ","%1と%2で曲を選択 +%3で戻る","%1 y %2 para navegar entre melodías +%3 para salir", ,"%1 e %2 per navigare tra le canzoni +%3 per uscire" +op_panels,Panels set,Choisir les blocs,Definir painéis,パネルセット,Set de paneles, ,Scelta delle tessere +op_reload_analytics,reloading analytics...,rechargement des statistiques...,recarregando estatísticas...,分析を再読み込みしています...,Recargando estadísticas..., ,Ricaricamento delle statistiche... +op_reload_characters,reloading characters...,rechargement des personnages...,recarregando personagens...,キャラクターを再読み込みしています...,Recargando personajes..., ,Ricaricamento dei personaggi... +op_reload_stages,reloading stages...,rechargement des terrains...,recarregando cenários...,ステージを再読み込みしています…,recargando escenarios...,,Ricarimento degli scenari... +op_reload_theme,reloading theme...,rechargement du thème...,recarregando tema...,テーマを再読み込みしています…,recargando temas...,,Ricaricamento dei temi... +op_replay_public,Save replays publicly,Sauvegarder les replays publiquement,Salvar replays publicamente,リプレイを,Grabar repeticiones públicamente, ,Salva i replay pubblicamente +op_replay_public_no,No,Non,Não,公開しない,No,,No +op_replay_public_anonymously,Anonymously,Anonymement,Anônimo,無名で公開する,Anónimamente,,Anonimamente +op_replay_public_with_name,With my name,Avec mon pseudo,Com meu apelido,名前付きで公開する,Con mi nombre,,Con il mio nome +op_save,writing config to file...,sauvegarde de la configuration...,salvando configuração no arquivo...,ファイルに設定を書き込んでいます...,Escribiendo configuración al archivo..., ,Salvataggio delle configurazioni su file... +op_sonds,Sounds theme,Thème des sons,Tema dos sons,サウンドセット,Set de sonidos, ,Tema dei suoni +op_vol,Master Volume,Volume Global,Vol principal,マスターボリューム,Volumen principal, ,Volume principale +op_vol_music,Music Volume,Volume Musique,Vol da música,音楽ボリューム,Volumen de música, ,Volume della musica +op_vol_sfx,SFX Volume,Volume Effets,Vol Efeitos Esp.,SFXボリューム,Volumen de sonido, ,Volume SFX +op_vsync,V-Sync,V-Sync,V-Sync,垂直同期,Sincronización Vertical (V-Sync),,V-Sync +op_input_delay,Key repeat delay (offline only),Délai de répétition des touches,Atraso na repetição das teclas,DAS (オフライン専用),Retraso de repetición de las teclas (Solo fuera de línea),,Ritardo della ripetizione dell'input (solo offline) +opponent_level,Opponent's level,Niveau adverse,Nível do oponente,相手のレベル,Nivel del oponente, ,Livello dell'avversario +pl_pause_help,Press Start again to resume,Appuyer sur Start pour continuer,Pressione Começar novamente para continuar,再開するにはもう一度スタートボタン押してください。,"Presiona ""Start"" para resumir",,"Premi ""Start"" di nuovo per riprendere" +pl_1_win,P1 wins ^^,J1 gagne ^^,J1 vence ^^,1Pの勝ち! ^^,J1 gana ^^, ,P1 vince ^^ +pl_2_win,P2 wins ^^,J2 gagne ^^,J2 vence ^^,2Pの勝ち! ^^,J2 gana ^^, ,P2 vince ^^ +pl_cleared,cleared: %1,détruit : %1,destruido: %1,消したパネル:%1,despejado: %1, ,Completato: %1 +pl_frame,Frame: %1,Frame : %1,Frame: %1,フレーム:%1,Cuadro: %1, ,Frame: %1 +pl_game_start,Game is starting!,La partie va commencer !,A partida está começando!,ゲームが始まります!,¡La partida va a comenzar!, ,Il gioco sta per inziare! +pl_gameover,Game Over,Terminé,Fim de jogo,ゲームオーバー,Fin del juego, ,Gioco finito +pl_health,Health: %1,Vie : %1,Vida: %1,猶予:%1,Salud: %1, ,Vita: %1 +pl_level,Level: %1,Niveau : %1,Nível %1,レベル:%1,Nivel: %1, ,Livello: %1 +pl_metal,metal q: %1,blocs métal : %1,blocos de metal q: %1,金属q:%1,blq. de metal: %1, ,Blocchi di metallo: %1 +pl_moves,Moves: %1,Déplacements : %1,Movimentos: %1,手数:%1,Movimientos: %1, ,Movimenti: %1 +pl_panel_info,"Panel info: +row: %1 +col: %2","Info grille : +ligne: %1 +colonne: %2","Info do painel: +linha: %1 +coluna: %2 ","パネル情報: +行:%1 +col:%2 ","Info. panel: +fila: %1 +col: %2", ,"Informazioni sulle tessere +riga: %1 +colonna: %2" +pl_pre_stop,Pre stop: %1,Délai pré stop : %1,Atraso ante-parada: %1,ストップ前:%1,Detención pre. :%1, ,Pre stop: %1 +pl_score,Score: %1,Score : %1,Pontuação: %1,スコア:%1,Puntaje: %1, ,Punteggio: %1 +pl_shake,Shake: %1,Délai secousse : %1,Tremor: %1,床揺れ:%1,Sacudida: %1, ,Scossa: %1 +pl_spectate_join,"Joined a match in progress. +Catching up...","Chargement du match en cours. +Synchronisation...","Entrou em partida em andamento. +Sincronizando...","進行中の試合に参加しました。 +キャッチアップ...","Te uniste a una partida en progreso. +Sincronizando...", ,"Caricamento di una partita in corso. +Sincronizzazione..." +pl_spectators,Spectator(s):,Spectateurs(s) :,Espectador(es):,観客:,Espectador(es):, ,Spettatori: +pl_speed,Speed: %1,Vitesse : %1,Velocidade: %1,速度:%1,Velocidad: %1, ,Velocità: %1 +pl_stop,Stop: %1,Délai stop : %1,Parada: %1,ストップ:%1,Detención: %1, ,Stop: %1 +pl_time,Time: %1,Temps : %1,Tempo: %1,時間:%1,Tiempo: %1, ,Tempo: %1 +pl_time_out,"game start timed out. +This is a known bug, but you may post it in #panel-attack-bugs-features +if you'd like.","Le chargement de la partie a pris trop de temps. +Ceci est un bug connu, mais vous pouvez le signaler sur notre discord sur : +#panel-attack-bugs-features","Tempo esgotado para partida começar. +Este é um bug conhecido, mas você pode postar em #panel-attack-bugs-features +se quiser. ","ゲームの開始がタイムアウトしました。 +これは既知のバグですが、#panel-attack-bugs-featuresに報告してもいいです。","se agotó el tiempo para empezar la partida. +Este es un bug conocido, pero si queres puedes postearlo en #panel-attack-bugs-features", ,"Il caricamento della partita ha richiesto troppo tempo. +Questo è un bug gà noto, ma puoi postarlo su #panel-attack-bugs-features +se ti va." +pl_you_lose,You lose :(,Vous avez perdu :(,Você perdeu :(,あなたの負け・・・。 -_-,Has perdido :(, ,Hai perso :( +pl_you_win,You win!,Vous avez gagné !,Você venceu!,あなたの勝ち!!^^,¡Has ganado!, ,Hai vinto! +pz_info,"Note: you may place new custom puzzles in + +%appdata%\Panel Attack\puzzles + +See the README and example puzzle set there +for instructions","Note: vous pouvez placer des puzzles personnalisés dans : + +%appdata%\Panel Attack\puzzles + +Lisez le fichier README, et alle voir les exemples.","Nota: você pode alocar novos quebra-cabeças personalizados em + +%appdata%\Panel Attack\puzzles + +Leia o README e o exemplo de quebra-cabeça definido lá +para instruções ","注:新しいカスタムパズルを + +%appdata%\Panel Attack\puzzles において下さい + +使い方についてREADMEとサンプルパズルをご覧ください","Nota: Puedes añadir puzzles personalizados en + +%appdata%\Panel Attack\puzzles + +Revisa el archivo README y el set de puzzles de ejemplo en su correspondiente carpeta +para más infomación.", ,"Nota: puoi inserire nuovi puzzle in: + +%appdata%\Panel Attack\puzzles + +Leggi il README e guarda i puzzle d'esempio che si trovano lì per avere istruzioni." +pz_puzzles,Puzzles:,Puzzles :,Quebra-cabeças:,パズル:,Puzzles:, ,Puzzles: +rp_endless,endless,infini,Infinito,エンドレス,Interminable, ,infinito +rp_no_endless,I don't have an endless replay :(,Je n'ai pas de replay infini :(,Não possuo replay sem fim :(,エンドレスのリプレイはありません -_-,No encuentro ninguna repetición del modo Interminable :(, ,Non ho un replay della modalità infinito :( +rp_no_puzzle,I don't have a puzzle replay :(,Je n'ai pas de replay puzzle :(,Não possuo replay de quebra-cabeça :(,パズルのリプレイはありません -_-,No encuentro ninguna repetición del modo Puzzle :(, ,Non ho un replay della modalità puzzle :( +rp_no_replay,I don't have a vs replay :(,Je n'ai pas de replay :(,Não possuo replay vs :(,VSリプレイはありません -_-,No encuentro ninguna repetición del modo VS :(, ,Non ho un replay della modalità vs :( +rp_score,"You scored %1 +in %2","Vous avez fait %1 +en %2","Você pontuou %1 +em %2","スコア %1 +時間  %2","Obtuviste %1 puntos +en %2", ,"Il tuo punteggio è %1 +in 2%" +ss_casual,Casual,Normal,Casual,カジュアル,Casual, ,Normale +ss_current_rating,current:,courant :,atual:,現在:,Actual:, ,corrente: +ss_disconnect,Disconnected from server.,Vous êtes deconnecté du serveur.,Desconectado do servidor.,サーバーから切断しました。,Desconectado del servidor., ,Disconnesso dal server. +ss_draw,Draw,Egalité,Empate,引き分け,Empate, ,Pareggio +ss_err_no_reason,Reason unknown,Erreur inconnue,Motivo desconhecido,理由不明,Razón desconocida, ,Ragione sconosciuta +ss_error_leave,Error when leaving online,Erreur en quittant le serveur,Erro ao sair do servidor,オンラインを離れる際のエラー,Error al salir de en línea, ,Errore nell'uscita dal server +ss_expected_rating,expected:,attendu :,expectativa:,予想:,Esperado:, ,atteso: +ss_init,Waiting for room initialization...,En attente de la création de la salle...,Aguardando inicialização da sala...,ルームの初期化を待っています...,Esperando la inicialización de la sala., ,Attesa per l'inizializzazione della stanza... +ss_init_fail,Room initialization failed.,Problème lors de la création de la salle.,Falha na inicialização da sala.,ルームの初期化に失敗しました。,Falló la inicialización de la sala., ,Inizializzazione della stanza fallita. +ss_not_ranked,Not ranked.,Non classé.,Casual,ランク付けされていません。,Sin clasificar., ,Non classificato. +ss_p_wins,%1 wins,%1 gagne,%1 vence,%1の勝ち,%1 Gana, ,%1 vince +ss_ranked,Ranked,Classé,Ranqueada,ランクマッチ,Clasificado, ,Classificato +ss_rating,Rating:,Classement :,Classificação:,レート:,Clasificación:, ,Classificazione: +ss_return,Returning to main menu...,Retour au menu principal...,Voltar ao menu principal...,メインメニューに戻ります...,Regresando al menú principal..., ,Ritorno al menù principale... +ss_winrate,Winrate:,Taux de victoire :,Taxa de vitórias:,勝率:,Racha de victorias:, ,Rateo delle vittorie: +ss_wins,Wins:,Victoires :,Vitórias:,勝ち:,Gana:, ,Vittorie: +ss_ex_mode,EX Mode,EX Mode,Modo EX,EXモード,Modo EX, ,Modalità EX +rp_browser_header,~ Replay Browser ~,~ Bibliothèque des replays ~,~ Navegador de Replays ~,,~ Explorador de Repeticiones ~,,~ Navigatore Replay ~ +rp_browser_info_header,~ Replay Information ~,~ Information des replays ~,~ Informações de Replays ~,,~ Información de la repetición ~,,~ Informazioni Replay ~ +rp_browser_current_dir,Current directory: %1,Dossier actuel : %1,Pasta atual: %1,,Directorio actual: %1,,Elenco corrente: %1 +rp_browser_up,up,monter,acima,,volver,,su +rp_browser_root,root,racine,raiz,,raíz,,sorgente +rp_browser_more,more,plus,mais,,más,,altro +rp_browser_info_2p_vs,2P VS,2J VS,2J VS,,2J VS,,2P VS +rp_browser_info_1p_vs,VS Yourself,VS soi-même,VS Você mesmo,,VS Tú,,VS Te stesso +rp_browser_info_1p,1P,1J,1J,,1J,,1P +rp_browser_info_2p,2P,2J,2J,,2J,,2P +rp_browser_info_name,Name: %1,Nom : %1,Apelido %1,,Nombre: %1,,Nome: 1% +rp_browser_info_level,Level: %1,Niveau : %1,Nível: %1,,Nivel: %1,,Livello: 1% +rp_browser_info_character,Character: %1,Personnage : %1,Personagem: 1%,,Personaje: %1,,Personaggio: 1% +rp_browser_info_casual,Casual Match,Match Normal,Partida Casual,,Partida Casual,,Partita Normale +rp_browser_info_ranked,Ranked Match,Match Classé,Partida Ranqueada,,Partida Clasificatoria,,Partita Classificata +rp_browser_info_endless,Endless,Infini,Infinito,,Interminable,,Infinito +rp_browser_info_time_trial,Time Trial,Contre la montre,Contra o Tempo,,Contrareloj,,A Tempo +rp_browser_info_speed,Speed: %1,Vitesse : %1,Velocidade: %1,,Velocidad: %1,,Velocità: 1% +rp_browser_info_difficulty,Difficulty: %1,Difficulté : %1,Dificuldade: %1,,Dificultad: %1,,Difficoltà: 1% +rp_browser_info_puzzle,Puzzle,Puzzle,Quebra-Cabeça,,Puzzle,,Puzzle +rp_browser_watch,"Press A/Confirm to view this replay, or B/Cancel to exit.","Appuyer sur A pour voir le replay, ou B pour quitter.",Pressione A/Confirme para ver este replay ou B/Cancelar para sair,,"Presiona A/Confirmar para ver esta repetición, o B/Cancelar para salir.",,"Premi A/Conferma per vedere questo replay, o B/Annulla per uscire." +rp_browser_no_info,No information available...,Pas d'informations disponibles...,Sem informações disponíveis...,,Sin información disponible...,,Nessuna informazione disponibile... +rp_browser_error_unknown_replay_type,Unknown replay type,Type de replay inconnu,Motivo do tipo desconhecido,,Tipo de repetición desconocida.,,Tipo di replay sconosciuto. +rp_browser_error_loading,Error loading replay: %1,Erreur au chargement du replay : %1,Erro ao carregar replay: %1,,Error cargando la repetición: %1,,Errore nel caricamento del replay: 1% +rp_browser_error_unknown_filetype,Unknown filetype %1. (%2),Type de fichier inconnu %1. (%2),Tipo de arquivo desconhecido %1. (%2),,Formato de archivo desconocido %1. (%2),,Tipo di file sconosciuto %1. (%2) +rp_browser_error_file_not_found,File not found! (%1),Fichier introuvable ! (%1),Arquivo não encontrado! (%1),,¡No se encontró el archivo! (%1),,File non trovato! (%1) +mm_replay_browser,Replay Browser,Bibliothèque des replays,Navegador de Replays,,,,Navigatore Replay \ No newline at end of file diff --git a/localization.lua b/localization.lua new file mode 100644 index 00000000..547f863f --- /dev/null +++ b/localization.lua @@ -0,0 +1,187 @@ +local FILENAME = "localization.csv" + +Localization = class(function(self) + self.data = {} + self.langs = {} + self.codes = {} + self.lang_index = 1 + self.init = false +end) + +localization = Localization() + +function Localization.get_list_codes(self) + return self.codes +end + +function Localization.get_language(self) + return self.codes[self.lang_index] +end + +function Localization.refresh_global_strings(self) + join_community_msg = loc("join_community").."\ndiscord.panelattack.com" +end + +function Localization.set_language(self, lang_code) + for i, v in ipairs(self.codes) do + if v == lang_code then + self.lang_index = i + break + end + end + config.language_code = self.codes[self.lang_index] + + if config.language_code == "JP" then + set_global_font("jp.ttf", 14) + else + set_global_font(nil, 12) + end + + self:refresh_global_strings() +end + +function Localization.init(self) + + local function csv_line(line, acc) + local function trim(a, b) + if line:sub(a, a) == '"' then + a = a+1 + end + if line:sub(b, b) == '"' then + b = b-1 + end + return line:sub(a, b) + end + + local tokens = {} + local leftover = nil + local cur = 1 + local stop_cur = 1 + local escape = (acc ~= nil) + local ch + + while cur <= line:len() do + ch = line:sub(cur, cur) + + if ch == '"' then + if line:sub(cur+1, cur+1) == '"' then + cur = cur + 1 + else + escape = not escape + end + elseif not escape and ch == ',' then + tokens[#tokens+1] = trim(stop_cur, cur-1) + + if acc then + tokens[#tokens] = acc..tokens[#tokens] + acc = nil + end + + tokens[#tokens] = tokens[#tokens]:gsub('""', '"') + + stop_cur = cur + 1 + end + + cur = cur + 1 + end + + if escape then + if not acc then + leftover = line:sub(stop_cur+1, cur) + else + leftover = acc..line:sub(stop_cur, cur) + end + leftover = leftover.."\n" + else + tokens[#tokens+1] = trim(stop_cur, cur) + end + + return tokens, leftover + end + + self.init = true + local num_line = 1 + local tokens, leftover + local i = 1 + local key = nil + if love.filesystem.getInfo(FILENAME) then + for line in love.filesystem.lines(FILENAME) do + + if num_line == 1 then + tokens = csv_line(line) + for i, v in ipairs(tokens) do + if i > 1 and v:gsub("%s", ""):len() > 0 then + self.codes[#self.codes+1] = v + self.data[v] = {} + end + end + else + tokens, leftover = csv_line(line, leftover) + for _, v in ipairs(tokens) do + + if not key then + key = v + if key == "" or key:match("%s+") then + break + end + else + if v ~= "" and not v:match("^%s+$") then + if num_line == 2 then + self.langs[#self.langs+1] = v + end + self.data[self.codes[i]][key] = v + end + i = i+1 + end + if i > #self.codes then + break + end + end + + if not leftover then + i = 1 + key = nil + end + end + + num_line = num_line + 1 + end + end + +--[[ for k, v in pairs(self.data) do + print("LANG "..k) + for a, b in pairs(v) do + print(a..": "..b) + end + end--]] + + self:set_language(config.language_code) +end + +function loc(text_key, ...) + local self = localization + local code = self.codes[self.lang_index] + + if not code or not self.data[code] then + code = self.codes[1] + end + + local ret = nil + if self.init then + ret = self.data[code][text_key] + end + + if ret then + for i = 1, select('#', ...) do + local tmp = select(i, ...) + ret = ret:gsub("%%"..i, tmp) + end + else + ret = "#"..text_key + for i = 1, select('#', ...) do + ret = ret.." "..select(i, ...) + end + end + + return ret +end diff --git a/main.lua b/main.lua index 8d94b268..d9dcedae 100644 --- a/main.lua +++ b/main.lua @@ -6,9 +6,10 @@ require("class") require("queue") require("globals") require("character") -- after globals! -require("analytics") +require("stage") -- after globals! require("save") require("engine") +require("localization") require("graphics") require("input") require("network") @@ -52,8 +53,6 @@ function love.update(dt) end end - - leftover_time = leftover_time + dt local status, err = coroutine.resume(mainloop) @@ -62,29 +61,20 @@ function love.update(dt) end this_frame_messages = {} - --Play music here - for k, v in pairs(music_t) do - if v and k - love.timer.getTime() < 0.007 then - v.t:stop() - v.t:play() - currently_playing_tracks[#currently_playing_tracks+1]=v.t - -- Manual looping code - --if v.l then - --music_t[love.timer.getTime() + v.t:getDuration()] = make_music_t(v.t, true) - --end - music_t[k] = nil - end - end + update_music() end -bg = load_img("menu/title.png") function love.draw() -- if not main_font then -- main_font = love.graphics.newFont("Oswald-Light.ttf", 15) -- end -- main_font:setLineHeight(0.66) -- love.graphics.setFont(main_font) - love.graphics.getFont():setFilter("nearest", "nearest") + if foreground_overlay then + local scale = canvas_width/math.max(foreground_overlay:getWidth(),foreground_overlay:getHeight()) -- keep image ratio + menu_drawf(foreground_overlay, canvas_width/2, canvas_height/2, "center", "center", 0, scale, scale ) + end + love.graphics.setBlendMode("alpha", "alphamultiply") love.graphics.setCanvas(global_canvas) love.graphics.setBackgroundColor(unpack(global_background_color)) @@ -103,6 +93,12 @@ function love.draw() x, y, w, h = scale_letterbox(love.graphics.getWidth(), love.graphics.getHeight(), 16, 9) love.graphics.setBlendMode("alpha","premultiplied") love.graphics.draw(global_canvas, x, y, 0, w / canvas_width, h / canvas_height) - local scale = canvas_width/math.max(bg:getWidth(),bg:getHeight()) -- keep image ratio - menu_drawf(bg, canvas_width/2, canvas_height/2, "center", "center", 0, scale, scale ) + + -- draw background and its overlay + local scale = canvas_width/math.max(background:getWidth(),background:getHeight()) -- keep image ratio + menu_drawf(background, canvas_width/2, canvas_height/2, "center", "center", 0, scale, scale ) + if background_overlay then + local scale = canvas_width/math.max(background_overlay:getWidth(),background_overlay:getHeight()) -- keep image ratio + menu_drawf(background_overlay, canvas_width/2, canvas_height/2, "center", "center", 0, scale, scale ) + end end diff --git a/mainloop.lua b/mainloop.lua index 849108aa..cfa4bb31 100644 --- a/mainloop.lua +++ b/mainloop.lua @@ -1,19 +1,24 @@ +require("panels") +require("theme") +require("click_menu") +local select_screen = require("select_screen") +local replay_browser = require("replay_browser") +local options = require("options") local utf8 = require("utf8") +local analytics = require("analytics") local wait, resume = coroutine.yield, coroutine.resume -local main_select_mode, main_endless, make_main_puzzle, main_net_vs_setup, - main_replay_endless, main_replay_puzzle, main_net_vs, - main_config_input, main_dumb_transition, main_select_puzz, - menu_up, menu_down, menu_left, menu_right, menu_enter, menu_escape, menu_backspace, - main_replay_vs, main_local_vs_setup, main_local_vs, menu_key_func, - multi_func, normal_key, main_set_name, main_character_select, main_net_vs_lobby, - main_local_vs_yourself_setup, main_local_vs_yourself, - main_options, exit_options_menu, main_music_test, exit_game +local playground, main_endless, make_main_puzzle, main_net_vs_setup, + main_config_input, main_select_puzz, + main_local_vs_setup, main_set_name, main_local_vs_yourself_setup, + main_options, main_music_test, + main_replay_browser, exit_game +-- main_select_mode, main_dumb_transition, main_net_vs, main_net_vs_lobby, main_local_vs_yourself, main_local_vs, main_replay_endless, main_replay_puzzle, main_replay_vs are not local since they are also used elsewhere local PLAYING = "playing" -- room states local CHARACTERSELECT = "character select" --room states -local currently_spectating = false +currently_spectating = false connection_up_time = 0 logged_in = 0 connected_server_ip = nil @@ -23,86 +28,69 @@ replay_of_match_so_far = nil spectator_list = nil spectators_string = "" leftover_time = 0 +main_menu_screen_pos = { 300 + (canvas_width-legacy_canvas_width)/2, 220 + (canvas_height-legacy_canvas_height)/2 } +wait_game_update = nil +has_game_update = false +local arrow_padding = 12 -local main_menu_screen_pos = { 300 + (canvas_width-legacy_canvas_width)/2, 280 + (canvas_height-legacy_canvas_height)/2 } + +P1_win_quads = {} +P1_rating_quads = {} +P1_health_quad = {} + +P2_rating_quads = {} +P2_win_quads = {} +P2_health_quad = {} function fmainloop() local func, arg = main_select_mode, nil replay = {} - -- Default configuration values - config = { - -- The lastly used version - version = VERSION, - -- Player character - character = "lip", - -- Vsync - vsync = true, - -- Level (2P modes / 1P vs yourself mode) - level = 5, - endless_speed = 1, - endless_difficulty = 1, - -- Player name - name = "defaultname", - -- Volume settings - master_volume = 100, - SFX_volume = 100, - music_volume = 100, - -- Debug mode flag - debug_mode = false, - -- Show FPS in the top-left corner of the screen - show_fps = false, - -- Enable ready countdown flag - ready_countdown_1P = true, - -- Change danger music back later flag - danger_music_changeback_delay = false, - -- analytics - enable_analytics = false, - -- Save replays setting - save_replays_publicly = "with my name", - -- Default directories for graphics/panels/sounds - assets_dir = default_assets_dir, - sounds_dir = default_sounds_dir, - - panels_dir = default_assets_dir, - -- Retrocompatibility, please remove whenever possible, it's so ugly! - panels_dir_when_not_using_set_from_assets_folder = default_panels_dir, - use_panels_from_assets_folder = true, - - -- Retrocompatibility - use_default_characters = false, - } + gprint("Reading config file", unpack(main_menu_screen_pos)) wait() - read_conf_file() -- TODO: stop making new config files - local x,y, display = love.window.getPosition() - love.window.setPosition( - config.window_x or x, - config.window_y or y, - config.display or display) - gprint("Copying Puzzles Readme") + read_conf_file() + local x, y, display = love.window.getPosition() + love.window.setPosition( config.window_x or x, config.window_y or y, config.display or display ) + love.window.setFullscreen(config.fullscreen or false) + love.window.setVSync( config.vsync and 1 or 0 ) + gprint("Loading localization...", unpack(main_menu_screen_pos)) wait() - copy_file("Custom Puzzles Readme.txt", "puzzles/README.txt") - gprint("Reading replay file", unpack(main_menu_screen_pos)) + Localization.init(localization) + gprint(loc("ld_puzzles"), unpack(main_menu_screen_pos)) + wait() + copy_file("readme_puzzles.txt", "puzzles/README.txt") + gprint(loc("ld_replay"), unpack(main_menu_screen_pos)) wait() read_replay_file() - gprint("Preloading characters...", unpack(main_menu_screen_pos)) + gprint(loc("ld_theme"), unpack(main_menu_screen_pos)) wait() - characters_init() -- load images and set up stuff - gprint("Loading graphics...", unpack(main_menu_screen_pos)) + theme_init() + -- stages and panels before characters since they are part of their loading! + gprint(loc("ld_stages"), unpack(main_menu_screen_pos)) wait() - graphics_init() -- load images and set up stuff - gprint("Loading panels...", unpack(main_menu_screen_pos)) + stages_init() + gprint(loc("ld_panels"), unpack(main_menu_screen_pos)) wait() - panels_init() -- load panels - gprint("Loading sounds...", unpack(main_menu_screen_pos)) + panels_init() + gprint(loc("ld_characters"), unpack(main_menu_screen_pos)) wait() - sound_init() - gprint("Loading analytics...", unpack(main_menu_screen_pos)) + characters_init() + gprint(loc("ld_analytics"), unpack(main_menu_screen_pos)) wait() - analytics_init() + analytics.init() + apply_config_volume() + -- create folders in appdata for those who don't have them already + love.filesystem.createDirectory("characters") + love.filesystem.createDirectory("panels") + love.filesystem.createDirectory("themes") + love.filesystem.createDirectory("stages") + + if GAME_UPDATER_CHECK_UPDATE_INGAME then + wait_game_update = GAME_UPDATER:async_download_latest_version() + end + while true do leftover_time = 1/120 - consuming_timesteps = false func,arg = func(unpack(arg or {})) collectgarbage("collect") end @@ -117,167 +105,126 @@ function variable_step(f) f() key_counts() this_frame_keys = {} + this_frame_released_keys = {} this_frame_unicodes = {} leftover_time = leftover_time - 1/60 end end end --- Changes the behavior of menu_foo functions. --- In a menu that doesn't specifically pertain to multiple players, --- up, down, left, right should always work. But in a multiplayer --- menu, those keys should definitely not move many cursors each. -local multi = false -function multi_func(func) - return function(...) - multi = true - local res = {func(...)} - multi = false - return unpack(res) - end -end - --- Keys that have a fixed function in menus can be bound to other --- meanings, but should continue working the same way in menus. -local menu_reserved_keys = {} - -function repeating_key(key) - local key_time = keys[key] - return this_frame_keys[key] or - (key_time and key_time > 25 and key_time % 3 ~= 0) -end - -function normal_key(key) return this_frame_keys[key] end - -function menu_key_func(fixed, configurable, rept, sound) - sound = sound or nil - local query = normal_key - if rept then - query = repeating_key - end - for i=1,#fixed do - menu_reserved_keys[#menu_reserved_keys+1] = fixed[i] - end - return function(k) - local res = false - if multi then - for i=1,#configurable do - res = res or query(k[configurable[i]]) - end - else - for i=1,#fixed do - res = res or query(fixed[i]) - end - for i=1,#configurable do - local keyname = k[configurable[i]] - res = res or query(keyname) and - not menu_reserved_keys[keyname] - end - end - if res and sound ~= nil then - play_optional_sfx(sound()) - end - return res - end -end - -menu_up = menu_key_func({"up"}, {"up"}, true, function() return sounds.SFX.menu_move end ) -menu_down = menu_key_func({"down"}, {"down"}, true, function() return sounds.SFX.menu_move end) -menu_left = menu_key_func({"left"}, {"left"}, true, function() return sounds.SFX.menu_move end) -menu_right = menu_key_func({"right"}, {"right"}, true, function() return sounds.SFX.menu_move end) -menu_enter = menu_key_func({"return","kenter","z"}, {"swap1"}, false, function() return sounds.SFX.menu_validate end) -menu_escape = menu_key_func({"escape","x"}, {"swap2"}, false, function() return sounds.SFX.menu_cancel end) -menu_prev_page = menu_key_func({"pageup"}, {"raise1"}, true, function() return sounds.SFX.menu_move end) -menu_next_page = menu_key_func({"pagedown"}, {"raise2"}, true, function() return sounds.SFX.menu_move end) -menu_backspace = menu_key_func({"backspace"}, {"backspace"}, true) - do - local active_idx = 1 function main_select_mode() - love.audio.stop() + click_menus = {} currently_spectating = false - stop_the_music() + if themes[config.theme].musics["main"] then + find_and_add_music(themes[config.theme].musics, "main") + end character_loader_clear() + stage_loader_clear() close_socket() - bg = title + background = themes[config.theme].images.bg_main + reset_filters() logged_in = 0 connection_up_time = 0 connected_server_ip = "" current_server_supports_ranking = false match_type = "" + match_type_message = "" local items = { - {"1P endless", main_select_speed_99, {main_endless}}, - {"1P puzzle", main_select_puzz}, - {"1P time attack", main_select_speed_99, {main_time_attack}}, - {"1P vs yourself", main_local_vs_yourself_setup}, - --{"2P vs online at burke.ro", main_net_vs_setup, {"burke.ro"}}, - {"2P vs online at Jon's server", main_net_vs_setup, {"18.188.43.50"}}, - --{"2P vs online at betaserver.panelattack.com", main_net_vs_setup, {"betaserver.panelattack.com"}}, - --{"2P vs online (USE ONLY WITH OTHER CLIENTS ON THIS TEST BUILD 025beta)", main_net_vs_setup, {"18.188.43.50"}}, - --{"This test build is for offline-use only"--[["2P vs online at Jon's server"]], main_select_mode}, - --{"2P vs online at domi1819.xyz (Europe, beta for spectating and ranking)", main_net_vs_setup, {"domi1819.xyz"}}, - --{"2P vs online at localhost (development-use only)", main_net_vs_setup, {"localhost"}}, - --{"2P vs online at LittleEndu's server", main_net_vs_setup, {"51.15.207.223"}}, - {"2P vs local game", main_local_vs_setup}, - {"Replay of 1P endless", main_replay_endless}, - {"Replay of 1P puzzle", main_replay_puzzle}, - {"Replay of 2P vs", main_replay_vs}, - {"Configure input", main_config_input}, - {"Set name", main_set_name}, - {"Options", main_options}, - {"Music test", main_music_test} + --{"playground", main_select_speed_99, {playground}}, + {loc("mm_1_endless"), main_select_speed_99, {main_endless}}, + {loc("mm_1_puzzle"), main_select_puzz}, + {loc("mm_1_time"), main_select_speed_99, {main_time_attack}}, + {loc("mm_1_vs"), main_local_vs_yourself_setup}, + --{loc("mm_2_vs_online", "burke.ro"), main_net_vs_setup, {"burke.ro"}}, + {loc("mm_2_vs_online", "Jon's server"), main_net_vs_setup, {"18.188.43.50"}}, + --{loc("mm_2_vs_online", "betaserver.panelattack.com"), main_net_vs_setup, {"betaserver.panelattack.com"}}, + --{loc("mm_2_vs_online", "(USE ONLY WITH OTHER CLIENTS ON THIS TEST BUILD 025beta)"), main_net_vs_setup, {"18.188.43.50"}}, + --{loc("mm_2_vs_online", "This test build is for offline-use only"), main_select_mode}, + --{loc("mm_2_vs_online", "domi1819.xyz"), main_net_vs_setup, {"domi1819.xyz"}}, + --{loc("mm_2_vs_online", "(development-use only)"), main_net_vs_setup, {"localhost"}}, + --{loc("mm_2_vs_online", "LittleEndu's server"), main_net_vs_setup, {"51.15.207.223"}}, + {loc("mm_2_vs_online", "server for ranked Ex Mode"), main_net_vs_setup, {"exserver.panelattack.com",49568}}, + {loc("mm_2_vs_local"), main_local_vs_setup}, + --{loc("mm_replay_of", loc("mm_1_endless")), main_replay_endless}, + --{loc("mm_replay_of", loc("mm_1_puzzle")), main_replay_puzzle}, + --{loc("mm_replay_of", loc("mm_2_vs")), main_replay_vs}, + {loc("mm_replay_browser"), replay_browser.main}, + {loc("mm_configure"), main_config_input}, + {loc("mm_set_name"), main_set_name}, + {loc("mm_options"), options.main}, + {loc("mm_music_test"), main_music_test} } if love.graphics.getSupported("canvas") then - items[#items+1] = {"Fullscreen (LAlt+Enter)", fullscreen} + items[#items+1] = {loc("mm_fullscreen", "(LAlt+Enter)"), fullscreen} else - items[#items+1] = {"Your graphics card doesn't support canvases for fullscreen", main_select_mode} + items[#items+1] = {loc("mm_no_support_fullscreen"), main_select_mode} end - items[#items+1] = {"Quit", exit_game } + items[#items+1] = {loc("mm_quit"), exit_game } local k = K[1] + local menu_x, menu_y = unpack(main_menu_screen_pos) + local main_menu = Click_menu(nil, menu_x, menu_y, nil, love.graphics.getHeight()-menu_y-80, 8, 1, true, 2) + for i=1,#items do + main_menu:add_button(items[i][1]) + end while true do - local to_print = "" - local arrow = "" - for i=1,#items do - if active_idx == i then - arrow = arrow .. ">" - else - arrow = arrow .. "\n" + main_menu:draw() + if wait_game_update ~= nil then + has_game_update = wait_game_update:pop() + if has_game_update ~= nil and has_game_update then + wait_game_update = nil + GAME_UPDATER_GAME_VERSION = "NEW VERSION FOUND! RESTART THE GAME!" + end + end + + if GAME_UPDATER_GAME_VERSION then + gprintf("version: "..GAME_UPDATER_GAME_VERSION, -2, 705, canvas_width, "right") + if has_game_update then + menu_draw(panels[config.panels].images.classic[1][1], 1262, 685) end - to_print = to_print .. " " .. items[i][1] .. "\n" end - gprint(arrow, unpack(main_menu_screen_pos)) - gprint(to_print, unpack(main_menu_screen_pos)) + wait() local ret = nil variable_step(function() if menu_up(k) then - active_idx = wrap(1, active_idx-1, #items) + main_menu:set_active_idx(wrap(1, main_menu.active_idx-1, #items)) elseif menu_down(k) then - active_idx = wrap(1, active_idx+1, #items) + main_menu:set_active_idx(wrap(1, main_menu.active_idx+1, #items)) elseif menu_enter(k) then - ret = {items[active_idx][2], items[active_idx][3]} + ret = {items[main_menu.active_idx][2], items[main_menu.active_idx][3]} elseif menu_escape(k) then - if active_idx == #items then - ret = {items[active_idx][2], items[active_idx][3]} + if main_menu.active_idx == #items then + ret = {items[main_menu.active_idx][2], items[main_menu.active_idx][3]} else - active_idx = #items + main_menu:set_active_idx(#items) end end end) if ret then return unpack(ret) end + if main_menu.idx_selected then + local active_idx = main_menu.idx_selected + main_menu.idx_selected = nil + main_menu:remove_self() + return items[active_idx][2], items[active_idx][3] + end end end end function main_select_speed_99(next_func, ...) local difficulties = {"Easy", "Normal", "Hard", "EX Mode"} + local loc_difficulties = { loc("easy"), loc("normal"), loc("hard"), "EX Mode" } -- TODO: localize "EX Mode" + local items = {{"Speed"}, {"Difficulty"}, {"Go!", next_func}, {"Back", main_select_mode}} + local loc_items = {loc("speed"), loc("difficulty"), loc("go_"), loc("back")} + local speed = config.endless_speed or 1 local difficulty = config.endless_difficulty or 1 local active_idx = 1 @@ -291,10 +238,10 @@ function main_select_speed_99(next_func, ...) else arrow = arrow .. "\n" end - to_print = to_print .. " " .. items[i][1] .. "\n" + to_print = to_print .. " " .. loc_items[i] .. "\n" end to_print2 = " " .. speed .. "\n " - .. difficulties[difficulty] + .. loc_difficulties[difficulty] gprint(arrow, unpack(main_menu_screen_pos)) gprint(to_print, unpack(main_menu_screen_pos)) gprint(to_print2, unpack(main_menu_screen_pos)) @@ -319,6 +266,7 @@ function main_select_speed_99(next_func, ...) wait() write_conf_file() end + stop_the_music() ret = {items[active_idx][2], {speed, difficulty}} elseif active_idx == 4 then ret = {items[active_idx][2], items[active_idx][3]} @@ -339,35 +287,107 @@ function main_select_speed_99(next_func, ...) end end +local function use_current_stage() + stage_loader_load(current_stage) + stage_loader_wait() + background = stages[current_stage].images.background + background_overlay = themes[config.theme].images.bg_overlay + foreground_overlay = themes[config.theme].images.fg_overlay +end + +local function pick_random_stage() + current_stage = uniformly(stages_ids_for_current_theme) + if stages[current_stage]:is_bundle() then -- may pick a bundle! + current_stage = uniformly(stages[current_stage].sub_stages) + end + use_current_stage() +end + +local function pick_use_music_from() + if config.use_music_from == "stage" or config.use_music_from == "characters" then + current_use_music_from = config.use_music_from + return + end + local percent = math.random(1,4) + if config.use_music_from == "either" then + current_use_music_from = percent <= 2 and "stage" or "characters" + elseif config.use_music_from == "often_stage" then + current_use_music_from = percent == 1 and "characters" or "stage" + else + current_use_music_from = percent == 1 and "stage" or "characters" + end +end + +function Stack.wait_for_random_character(self) + if self.character == random_character_special_value then + self.character = uniformly(characters_ids_for_current_theme) + elseif characters[self.character]:is_bundle() then -- may have picked a bundle + self.character = uniformly(characters[self.character].sub_characters) + end + character_loader_load(self.character) + character_loader_wait() +end + +function Stack.handle_pause(self) + local k = K[self.which] + + if self.wait_for_not_pausing then + if not keys[k.pause] and not this_frame_keys[k.pause] then + self.wait_for_not_pausing = false + else + return + end + end + + if keys[k.pause] or this_frame_keys[k.pause] then + game_is_paused = not game_is_paused + self.wait_for_not_pausing = true + + if game_is_paused then + stop_the_music() + end + end + +end + function main_endless(...) - bg = IMG_stages[math.random(#IMG_stages)] - consuming_timesteps = true + pick_random_stage() + pick_use_music_from() replay.endless = {} local replay=replay.endless replay.pan_buf = "" replay.in_buf = "" replay.gpan_buf = "" replay.mode = "endless" - P1 = Stack(1, "endless", config.panels_dir, ...) + P1 = Stack(1, "endless", config.panels, ...) + P1:wait_for_random_character() P1.do_countdown = config.ready_countdown_1P or false P1.enable_analytics = true replay.do_countdown = P1.do_countdown or false replay.speed = P1.speed replay.difficulty = P1.difficulty + replay.cur_wait_time = P1.cur_wait_time or default_input_repeat_delay make_local_panels(P1, "000000") make_local_gpanels(P1, "000000") P1:starting_state() while true do - P1:render() + if game_is_paused then + draw_pause() + else + P1:render() + end wait() if P1.game_over then -- TODO: proper game over. write_replay_file() - local end_text = "You scored "..P1.score.."\nin "..frames_to_time_string(P1.game_stopwatch, true) - analytics_game_ends() - return main_dumb_transition, {main_select_mode, end_text, 60} + local end_text = loc("rp_score", P1.score, frames_to_time_string(P1.game_stopwatch, true)) + analytics.game_ends() + return main_dumb_transition, {main_select_mode, end_text, 0, -1, P1:pick_win_sfx()} end - variable_step(function() P1:local_run() end) + variable_step(function() + P1:local_run() + P1:handle_pause() + end) --groundhogday mode --[[if P1.CLOCK == 1001 then local prev_states = P1.prev_states @@ -378,943 +398,48 @@ function main_endless(...) end function main_time_attack(...) - bg = IMG_stages[math.random(#IMG_stages)] - consuming_timesteps = true - P1 = Stack(1, "time", config.panels_dir, ...) + pick_random_stage() + pick_use_music_from() + P1 = Stack(1, "time", config.panels, ...) + P1:wait_for_random_character() P1.enable_analytics = true make_local_panels(P1, "000000") P1:starting_state() while true do - P1:render() + if game_is_paused then + draw_pause() + else + P1:render() + end wait() if P1.game_over or (P1.game_stopwatch and P1.game_stopwatch == 120*60) then -- TODO: proper game over. - local end_text = "You scored "..P1.score.."\nin "..frames_to_time_string(P1.game_stopwatch) - analytics_game_ends() - return main_dumb_transition, {main_select_mode, end_text, 30} + local end_text = loc("rp_score", P1.score, frames_to_time_string(P1.game_stopwatch)) + analytics.game_ends() + return main_dumb_transition, {main_select_mode, end_text, 30, -1, P1:pick_win_sfx()} end variable_step(function() - if (not P1.game_over) and P1.game_stopwatch and P1.game_stopwatch < 120 * 60 then - P1:local_run() end end) + if not P1.game_over and P1.game_stopwatch and P1.game_stopwatch < 120 * 60 then + P1:local_run() + P1:handle_pause() + end + end) end end function main_net_vs_room() - character_select_mode = "2p_net_vs" - return main_character_select() -end - --- fills the provided map based on the provided template and return the amount of pages. __Empty values will be replaced by character_ids -local function fill_map(template_map,map) - local X,Y = 5,7 - local pages_amount = 0 - local character_id_index = 1 - while true do - -- new page handling - pages_amount = pages_amount+1 - map[pages_amount] = deepcpy(template_map) - - -- go through the page and replace __Empty with characters_ids_for_current_theme - for i=1,X do - for j=1,Y do - if map[pages_amount][i][j] == "__Empty" then - map[pages_amount][i][j] = characters_ids_for_current_theme[character_id_index] - character_id_index = character_id_index+1 - -- end case: no more characters_ids_for_current_theme to add - if character_id_index == #characters_ids_for_current_theme+1 then - print("filled "..#characters_ids_for_current_theme.." characters across "..pages_amount.." page(s)") - return pages_amount - end - end - end - end - end -end - -local fallback_when_missing = nil - -local function refresh_based_on_own_mods(refreshed,ask_change_fallback) - ask_change_fallback = ask_change_fallback or false - if refreshed ~= nil then - if refreshed.panels_dir == nil or IMG_panels[refreshed.panels_dir] == nil then - refreshed.panels_dir = config.panels_dir - end - if characters[refreshed.character] == nil then - if refreshed.character_display_name and characters_ids_by_display_names[refreshed.character_display_name] then - refreshed.character = characters_ids_by_display_names[refreshed.character_display_name][1] - else - if not fallback_when_missing or ask_change_fallback then - fallback_when_missing = uniformly(characters_ids_for_current_theme) - end - refreshed.character = fallback_when_missing - end - end - end -end - -local current_page = 1 - -function main_character_select() - love.audio.stop() - stop_the_music() - bg = charselect - fallback_when_missing = nil - - local function add_client_data(state) - state.loaded = characters[state.character] and characters[state.character].fully_loaded - state.wants_ready = state.ready - end - - local function refresh_loaded_and_ready(state_1,state_2) - state_1.loaded = characters[state_1.character] and characters[state_1.character].fully_loaded - state_2.loaded = characters[state_2.character] and characters[state_2.character].fully_loaded - - if character_select_mode == "2p_net_vs" then - state_1.ready = state_1.wants_ready and state_1.loaded and state_2.loaded - else - state_1.ready = state_1.wants_ready and state_1.loaded - state_2.ready = state_2.wants_ready and state_2.loaded - end - end - - print("character_select_mode = "..(character_select_mode or "nil")) - - - -- map is composed of special values prefixed by __ and character ids - local template_map = {} - local map = {} - if character_select_mode == "2p_net_vs" then - local opponent_connected = false - local retries, retry_limit = 0, 250 - while not global_initialize_room_msg and retries < retry_limit do - for _,msg in ipairs(this_frame_messages) do - if msg.create_room or msg.character_select or msg.spectate_request_granted then - global_initialize_room_msg = msg - end - end - gprint("Waiting for room initialization...", unpack(main_menu_screen_pos)) - wait() - if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} - end - retries = retries + 1 - end - -- if room_number_last_spectated and retries >= retry_limit and currently_spectating then - -- request_spectate(room_number_last_spectated) - -- retries = 0 - -- while not global_initialize_room_msg and retries < retry_limit do - -- for _,msg in ipairs(this_frame_messages) do - -- if msg.create_room or msg.character_select or msg.spectate_request_granted then - -- global_initialize_room_msg = msg - -- end - -- end - -- gprint("Lost connection. Trying to rejoin...", unpack(main_menu_screen_pos)) - -- wait() - -- if not do_messages() then - -- return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} - -- end - -- retries = retries + 1 - -- end - -- end - if not global_initialize_room_msg then - return main_dumb_transition, {main_select_mode, "Room initialization failed.\n\nReturning to main menu...", 60, 300} - end - msg = global_initialize_room_msg - global_initialize_room_msg = nil - if msg.ratings then - global_current_room_ratings = msg.ratings - end - - if msg.your_player_number then - my_player_number = msg.your_player_number - elseif currently_spectating then - my_player_number = 1 - elseif my_player_number and my_player_number ~= 0 then - print("We assumed our player number is still "..my_player_number) - else - error("We never heard from the server as to what player number we are") - print("Error: The server never told us our player number. Assuming it is 1") - my_player_number = 1 - end - - if msg.op_player_number then - op_player_number = msg.op_player_number or op_player_number - elseif currently_spectating then - op_player_number = 2 - elseif op_player_number and op_player_number ~= 0 then - print("We assumed op player number is still "..op_player_number) - else - error("We never heard from the server as to what player number we are") - print("Error: The server never told us our player number. Assuming it is 2") - op_player_number = 2 - end - - if my_player_number == 2 and msg.a_menu_state ~= nil and msg.b_menu_state ~= nil then - print("inverting the states to match player number!") - msg.a_menu_state, msg.b_menu_state = msg.b_menu_state, msg.a_menu_state - end - - global_my_state = msg.a_menu_state - refresh_based_on_own_mods(global_my_state) - global_op_state = msg.b_menu_state - refresh_based_on_own_mods(global_op_state) - - if msg.win_counts then - update_win_counts(msg.win_counts) - end - if msg.replay_of_match_so_far then - replay_of_match_so_far = msg.replay_of_match_so_far - end - if msg.ranked then - match_type = "Ranked" - match_type_message = "" - else - match_type = "Casual" - end - if currently_spectating then - P1 = {panel_buffer="", gpanel_buffer=""} - print("we reset P1 buffers at start of main_character_select()") - end - P2 = {panel_buffer="", gpanel_buffer=""} - print("we reset P2 buffers at start of main_character_select()") - print("current_server_supports_ranking: "..tostring(current_server_supports_ranking)) - - if current_server_supports_ranking then - template_map = {{"__Mode", "__Mode", "__Level", "__Level", "__Panels", "__Panels", "__Ready"}, - {"__Random", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Leave"}} - else - template_map = {{"__Level", "__Level", "__Level", "__Panels", "__Panels", "__Panels", "__Ready"}, - {"__Random", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Leave"}} - end - end - if character_select_mode == "2p_local_vs" or character_select_mode == "1p_vs_yourself" then - template_map = {{"__Level", "__Level", "__Level", "__Panels", "__Panels", "__Panels", "__Ready"}, - {"__Random", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, - {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Leave"}} - end - - local pages_amount = fill_map(template_map, map) - if current_page > pages_amount then - current_page = 1 - end - - op_win_count = op_win_count or 0 - - if character_select_mode == "2p_net_vs" then - global_current_room_ratings = global_current_room_ratings or {{new=0,old=0,difference=0},{new=0,old=0,difference=0}} - my_expected_win_ratio = nil - op_expected_win_ratio = nil - print("my_player_number = "..my_player_number) - print("op_player_number = "..op_player_number) - if global_current_room_ratings[my_player_number].new - and global_current_room_ratings[my_player_number].new ~= 0 - and global_current_room_ratings[op_player_number] - and global_current_room_ratings[op_player_number].new ~= 0 then - my_expected_win_ratio = (100*round(1/(1+10^ - ((global_current_room_ratings[op_player_number].new - -global_current_room_ratings[my_player_number].new) - /RATING_SPREAD_MODIFIER)) - ,2)) - op_expected_win_ratio = (100*round(1/(1+10^ - ((global_current_room_ratings[my_player_number].new - -global_current_room_ratings[op_player_number].new) - /RATING_SPREAD_MODIFIER)) - ,2)) - end - match_type = match_type or "Casual" - if match_type == "" then match_type = "Casual" end - end - - match_type_message = match_type_message or "" - - local function do_leave() - my_win_count = 0 - op_win_count = 0 - return json_send({leave_room=true}) - end - - -- be wary: name_to_xy_per_page is kinda buggy for larger blocks as they span multiple positions (we retain the last one), and is completely broken with __Empty - local name_to_xy_per_page = {} - local X,Y = 5,7 - for p=1,pages_amount do - name_to_xy_per_page[p] = {} - for i=1,X do - for j=1,Y do - if map[p][i][j] then - name_to_xy_per_page[p][map[p][i][j]] = {i,j} - end - end - end - end - - my_win_count = my_win_count or 0 - - local cursor_data = {{position=shallowcpy(name_to_xy_per_page[current_page]["__Ready"]),selected=false},{position=shallowcpy(name_to_xy_per_page[current_page]["__Ready"]),selected=false}} - if global_my_state ~= nil then - cursor_data[1].state = shallowcpy(global_my_state) - global_my_state = nil - else - cursor_data[1].state = {character=config.character, character_display_name=characters[config.character].display_name, level=config.level, panels_dir=config.panels_dir, cursor="__Ready", ready=false, ranked=config.ranked} - end - if global_op_state ~= nil then - cursor_data[2].state = shallowcpy(global_op_state) - if character_select_mode ~= "2p_local_vs" then - global_op_state = nil -- retains state of the second player, also: don't unload its character when going back and forth - end - else - cursor_data[2].state = {character=config.character, character_display_name=characters[config.character].display_name, level=config.level, panels_dir=config.panels_dir, cursor="__Ready", ready=false, ranked=false} - end - add_client_data(cursor_data[1].state) - add_client_data(cursor_data[2].state) - refresh_loaded_and_ready(cursor_data[1].state, cursor_data[2].state) - - local prev_state = shallowcpy(cursor_data[1].state) - - local function draw_button(x,y,w,h,str,halign,valign,no_rect) - no_rect = no_rect or str == "__Empty" - halign = halign or "center" - valign = valign or "top" - local menu_width = Y*100 - local menu_height = X*80 - local spacing = 8 - local text_height = 13 - local x_padding = math.floor((canvas_width-menu_width)/2) - local y_padding = math.floor((canvas_height-menu_height)/2) - set_color(unpack(colors.white)) - render_x = x_padding+(y-1)*100+spacing - render_y = y_padding+(x-1)*100+spacing - button_width = w*100-2*spacing - button_height = h*100-2*spacing - if no_rect == false then - grectangle("line", render_x, render_y, button_width, button_height) - end - local character = characters[str] - if str == "P1" then - character = characters[cursor_data[1].state.character] - elseif str == "P2" then - character = characters[cursor_data[2].state.character] - end - local width_for_alignment = button_width - local x_add,y_add = 0,0 - if valign == "center" then - y_add = math.floor(0.5*button_height-0.5*text_height)-3 - elseif valign == "bottom" then - y_add = math.floor(button_height-text_height) - end - if character and character.images["icon"] then - x_add = 0.025*button_width - width_for_alignment = 0.95*button_width - local orig_w, orig_h = character.images["icon"]:getDimensions() - local scale = button_width/math.max(orig_w,orig_h) -- keep image ratio - menu_drawf(character.images["icon"], render_x+0.5*button_width, render_y+0.5*button_height,"center","center", 0, scale, scale ) - end - - local function draw_cursor(button_height, spacing, player_num,ready) - local cur_blink_frequency = 4 - local cur_pos_change_frequency = 8 - local draw_cur_this_frame = false - local cursor_frame = 1 - if ready then - if (math.floor(menu_clock/cur_blink_frequency)+player_num)%2+1 == player_num then - draw_cur_this_frame = true - end - else - draw_cur_this_frame = true - cursor_frame = (math.floor(menu_clock/cur_pos_change_frequency)+player_num)%2+1 - end - if draw_cur_this_frame then - local cur_img = IMG_char_sel_cursors[player_num][cursor_frame] - local cur_img_left = IMG_char_sel_cursor_halves.left[player_num][cursor_frame] - local cur_img_right = IMG_char_sel_cursor_halves.right[player_num][cursor_frame] - local cur_img_w, cur_img_h = cur_img:getDimensions() - local cursor_scale = (button_height+(spacing*2))/cur_img_h - menu_drawq(cur_img, cur_img_left, render_x-spacing, render_y-spacing, 0, cursor_scale , cursor_scale) - menu_drawq(cur_img, cur_img_right, render_x+button_width+spacing-cur_img_w*cursor_scale/2, render_y-spacing, 0, cursor_scale, cursor_scale) - end - end - - local function draw_player_state(cursor_data,player_number) - if characters[cursor_data.state.character] and not characters[cursor_data.state.character].fully_loaded then - menu_drawf(IMG_loading, render_x+button_width*0.5, render_y+button_height*0.5, "center", "center" ) - elseif cursor_data.state.wants_ready then - menu_drawf(IMG_ready, render_x+button_width*0.5, render_y+button_height*0.5, "center", "center" ) - end - local scale = 0.25*button_width/math.max(IMG_players[player_number]:getWidth(),IMG_players[player_number]:getHeight()) -- keep image ratio - menu_drawf(IMG_players[player_number], render_x+1, render_y+button_height-1, "left", "bottom", 0, scale, scale ) - scale = 0.25*button_width/math.max(IMG_levels[cursor_data.state.level]:getWidth(),IMG_levels[cursor_data.state.level]:getHeight()) -- keep image ratio - menu_drawf(IMG_levels[cursor_data.state.level], render_x+button_width-1, render_y+button_height-1, "right", "bottom", 0, scale, scale ) - end - - local function draw_panels(cursor_data,player_number,y_padding) - local panels_max_width = 0.25*button_height - local panels_width = math.min(panels_max_width,IMG_panels[cursor_data.state.panels_dir][1][1]:getWidth()) - local padding_x = 0.5*button_width-3*panels_width -- center them, not 3.5 mysteriously? - if cursor_data.state.level >= 9 then - padding_x = padding_x-0.5*panels_width - end - local is_selected = cursor_data.selected and cursor_data.state.cursor == "__Panels" - if is_selected then - padding_x = padding_x-panels_width - end - local panels_scale = panels_width/IMG_panels[cursor_data.state.panels_dir][1][1]:getWidth() - menu_drawf(IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) - padding_x = padding_x + panels_width - if is_selected then - gprintf("<", render_x+padding_x-0.5*panels_width, render_y+y_padding-0.5*text_height,panels_width,"center") - padding_x = padding_x + panels_width - end - for i=1,8 do - if i ~= 7 and (i ~= 6 or cursor_data.state.level >= 9) then - menu_drawf(IMG_panels[cursor_data.state.panels_dir][i][1], render_x+padding_x, render_y+y_padding, "center", "center", 0, panels_scale, panels_scale ) - padding_x = padding_x + panels_width - end - end - if is_selected then - gprintf(">", render_x+padding_x-0.5*panels_width, render_y+y_padding-0.5*text_height,panels_width,"center") - end - end - - local function draw_levels(cursor_data,player_number,y_padding) - local level_max_width = 0.2*button_height - local level_width = math.min(level_max_width,IMG_levels[1]:getWidth()) - local padding_x = 0.5*button_width-6*level_width - local is_selected = cursor_data.selected and cursor_data.state.cursor == "__Level" - if is_selected then - padding_x = padding_x-level_width - end - local level_scale = level_width/IMG_levels[1]:getWidth() - menu_drawf(IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) - local ex_scaling = level_width/IMG_levels[11]:getWidth() - menu_drawf(IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center") - padding_x = padding_x + level_width + 1 -- [[Thank you Eole!]] - if is_selected then - gprintf("<", render_x+padding_x-0.5*level_width, render_y+y_padding-0.5*text_height,level_width,"center") - padding_x = padding_x + level_width - end - for i=1,11 do - local use_unfocus = cursor_data.state.level < i - if use_unfocus then - menu_drawf(IMG_levels_unfocus[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, (i == 11 and ex_scaling or level_scale), (i == 11 and ex_scaling or level_scale)) - --[[if i >= 11 then - menu_drawf(IMG_levels_unfocus[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, ex_scaling, ex_scaling) - end]] - else - menu_drawf(IMG_levels[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, (i == 11 and ex_scaling or level_scale), (i == 11 and ex_scaling or level_scale)) - --[[if i >= 11 then - menu_drawf(IMG_levels[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, ex_scaling, ex_scaling) - end]] - end - padding_x = padding_x + level_width + 1 - --[[if i == 11 then - padding_x = padding_x + 16 - end]] - end - if is_selected then - gprintf(">", render_x+padding_x-0.5*level_width, render_y+y_padding-0.5*text_height,level_width,"center") - end - end - - local function draw_match_type(cursor_data,player_number,y_padding) - local padding_x = math.floor(0.5*button_width - IMG_players[player_number]:getWidth()*0.5 - 46) -- ty GIMP; no way to know the size of the text? - menu_drawf(IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) - padding_x = padding_x+IMG_players[player_number]:getWidth() - local to_print - if cursor_data.state.ranked then - to_print = "casual [ranked]" - else - to_print = "[casual] ranked" - end - if cursor_data.state.level >= 11 then - to_print = "[EX Mode]" - end - gprint(to_print, render_x+padding_x, render_y+y_padding-0.5*text_height-1) - end - - local pstr - if string.sub(str, 1, 2) == "__" then - pstr = string.sub(str, 3) - end - if str == "__Mode" then - if (character_select_mode == "2p_net_vs" or character_select_mode == "2p_local_vs") then - draw_match_type(cursor_data[1],1,0.4*button_height) - draw_match_type(cursor_data[2],2,0.7*button_height) - else - draw_match_type(cursor_data[1],1,0.5*button_height) - end - elseif str == "__Panels" then - if (character_select_mode == "2p_net_vs" or character_select_mode == "2p_local_vs") then - draw_panels(cursor_data[1],1,0.4*button_height) - draw_panels(cursor_data[2],2,0.7*button_height) - else - draw_panels(cursor_data[1],1,0.5*button_height) - end - elseif str == "__Level" then - if (character_select_mode == "2p_net_vs" or character_select_mode == "2p_local_vs") then - draw_levels(cursor_data[1],1,0.4*button_height) - draw_levels(cursor_data[2],2,0.7*button_height) - else - draw_levels(cursor_data[1],1,0.5*button_height) - end - elseif str == "P1" then - draw_player_state(cursor_data[1],1) - pstr = my_name - elseif str == "P2" then - draw_player_state(cursor_data[2],2) - pstr = op_name - elseif character then - pstr = character.display_name - elseif string.sub(str, 1, 2) ~= "__" then - pstr = str:gsub("^%l", string.upper) - end - if x ~= 0 then - if cursor_data[1].state and cursor_data[1].state.cursor == str - and ( str ~= "__Empty" or ( cursor_data[1].position[1] == x and cursor_data[1].position[2] == y ) ) then - draw_cursor(button_height, spacing, 1, cursor_data[1].state.ready) - end - if (character_select_mode == "2p_net_vs" or character_select_mode == "2p_local_vs") - and cursor_data[2].state and cursor_data[2].state.cursor == str - and ( str ~= "__Empty" or ( cursor_data[2].position[1] == x and cursor_data[2].position[2] == y ) ) then - draw_cursor(button_height, spacing, 2, cursor_data[2].state.ready) - end - end - if str ~= "__Empty" then - gprintf(pstr, render_x+x_add, render_y+y_add,width_for_alignment,halign) - end - end - - print("got to LOC before net_vs_room character select loop") - menu_clock = 0 - - while true do - if character_select_mode == "2p_net_vs" then - for _,msg in ipairs(this_frame_messages) do - if msg.win_counts then - update_win_counts(msg.win_counts) - end - if msg.menu_state then - if currently_spectating then - if msg.player_number == 1 or msg.player_number == 2 then - cursor_data[msg.player_number].state = msg.menu_state - refresh_based_on_own_mods(cursor_data[msg.player_number].state) - character_loader_load(cursor_data[msg.player_number].state.character) - end - else - cursor_data[2].state = msg.menu_state - refresh_based_on_own_mods(cursor_data[2].state) - character_loader_load(cursor_data[2].state.character) - end - refresh_loaded_and_ready(cursor_data[1],cursor_data[2]) - end - if msg.ranked_match_approved then - match_type = "Ranked" - match_type_message = "" - if msg.caveats then - match_type_message = match_type_message..(msg.caveats[1] or "") - end - elseif msg.ranked_match_denied then - match_type = "Casual" - match_type_message = "Not ranked. " - if msg.reasons then - match_type_message = match_type_message..(msg.reasons[1] or "Reason unknown") - end - --[[elseif msg.ranked_match_denied and cursor_data.state.level >= 11 then - match_type = "Casual" - match_type_message = "EX Mode Activated " - if msg.reasons then - match_type_message = match_type_message..(msg.reasons[1] or "Reason unknown") - end]] - end - if msg.leave_room then - my_win_count = 0 - op_win_count = 0 - return main_net_vs_lobby - end - if msg.match_start or replay_of_match_so_far then - print("currently_spectating: "..tostring(currently_spectating)) - local fake_P1 = P1 - local fake_P2 = P2 - refresh_based_on_own_mods(msg.opponent_settings) - refresh_based_on_own_mods(msg.player_settings, true) - -- mainly for spectator mode, those characters have already been loaded otherwise - character_loader_load(msg.player_settings.character) - character_loader_load(msg.opponent_settings.character) - character_loader_wait() - P1 = Stack(1, "vs", msg.player_settings.panels_dir, msg.player_settings.level, msg.player_settings.character, msg.player_settings.player_number) - P1.enable_analytics = not currently_spectating and not replay_of_match_so_far - P2 = Stack(2, "vs", msg.opponent_settings.panels_dir, msg.opponent_settings.level, msg.opponent_settings.character, msg.opponent_settings.player_number) - if currently_spectating then - P1.panel_buffer = fake_P1.panel_buffer - P1.gpanel_buffer = fake_P1.gpanel_buffer - end - P2.panel_buffer = fake_P2.panel_buffer - P2.gpanel_buffer = fake_P2.gpanel_buffer - P1.garbage_target = P2 - P2.garbage_target = P1 - move_stack(P2,2) - replay.vs = {P="",O="",I="",Q="",R="",in_buf="", - P1_level=P1.level,P2_level=P2.level, - P1_name=my_name, P2_name=op_name, - P1_char=P1.character,P2_char=P2.character, - ranked=msg.ranked, do_countdown=true} - if currently_spectating and replay_of_match_so_far then --we joined a match in progress - replay.vs = replay_of_match_so_far.vs - P1.input_buffer = replay_of_match_so_far.vs.in_buf - P1.panel_buffer = replay_of_match_so_far.vs.P - P1.gpanel_buffer = replay_of_match_so_far.vs.Q - P2.input_buffer = replay_of_match_so_far.vs.I - P2.panel_buffer = replay_of_match_so_far.vs.O - P2.gpanel_buffer = replay_of_match_so_far.vs.R - if replay.vs.ranked then - match_type = "Ranked" - match_type_message = "" - else - match_type = "Casual" - end - replay_of_match_so_far = nil - P1.play_to_end = true --this makes foreign_run run until caught up - P2.play_to_end = true - end - if not currently_spectating then - ask_for_gpanels("000000") - ask_for_panels("000000") - end - to_print = "Game is starting!\n".."Level: "..P1.level.."\nOpponent's level: "..P2.level - if P1.play_to_end or P2.play_to_end then - to_print = "Joined a match in progress.\nCatching up..." - end - for i=1,30 do - gprint(to_print,unpack(main_menu_screen_pos)) - if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} - end - wait() - end - local game_start_timeout = 0 - while P1.panel_buffer == "" or P2.panel_buffer == "" - or P1.gpanel_buffer == "" or P2.gpanel_buffer == "" do - --testing getting stuck here at "Game is starting" - game_start_timeout = game_start_timeout + 1 - print("game_start_timeout = "..game_start_timeout) - print("P1.panel_buffer = "..P1.panel_buffer) - print("P2.panel_buffer = "..P2.panel_buffer) - print("P1.gpanel_buffer = "..P1.gpanel_buffer) - print("P2.gpanel_buffer = "..P2.gpanel_buffer) - gprint(to_print,unpack(main_menu_screen_pos)) - if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} - end - wait() - if game_start_timeout > 250 then - return main_dumb_transition, {main_select_mode, - "game start timed out.\n This is a known bug, but you may post it in #panel-attack-bugs-features \nif you'd like.\n" - .."\n".."msg.match_start = "..(tostring(msg.match_start) or "nil") - .."\n".."replay_of_match_so_far = "..(tostring(replay_of_match_so_far) or "nil") - .."\n".."P1.panel_buffer = "..P1.panel_buffer - .."\n".."P2.panel_buffer = "..P2.panel_buffer - .."\n".."P1.gpanel_buffer = "..P1.gpanel_buffer - .."\n".."P2.gpanel_buffer = "..P2.gpanel_buffer, - 180} - end - end - P1:starting_state() - P2:starting_state() - return main_net_vs - end - end - end - - -- those values span multiple 'map blocks' - if current_server_supports_ranking then - draw_button(1,1,2,1,"__Mode","center","top") - draw_button(1,3,2,1,"__Level","center","top") - draw_button(1,5,2,1,"__Panels","center","top") - else - draw_button(1,1,3,1,"__Level","center","top") - draw_button(1,4,3,1,"__Panels","center","top") - end - draw_button(1,7,1,1,"__Ready","center","center") - - for i=2,X do - for j=1,Y do - local valign = "top" - if map[current_page][i][j] == "__Leave" or map[current_page][i][j] == "__Random" then - valign = "center" - end - draw_button(i,j,1,1,map[current_page][i][j],"center",valign) - end - end - local my_rating_difference = "" - local op_rating_difference = "" - if current_server_supports_ranking and not global_current_room_ratings[my_player_number].placement_match_progress then - if global_current_room_ratings[my_player_number].difference then - if global_current_room_ratings[my_player_number].difference>= 0 then - my_rating_difference = "(+"..global_current_room_ratings[my_player_number].difference..") " - else - my_rating_difference = "("..global_current_room_ratings[my_player_number].difference..") " - end - end - if global_current_room_ratings[op_player_number].difference then - if global_current_room_ratings[op_player_number].difference >= 0 then - op_rating_difference = "(+"..global_current_room_ratings[op_player_number].difference..") " - else - op_rating_difference = "("..global_current_room_ratings[op_player_number].difference..") " - end - end - end - local function get_player_state_str(player_number, rating_difference, win_count, op_win_count, expected_win_ratio) - local state = "" - if current_server_supports_ranking then - state = state.."Rating: "..(global_current_room_ratings[player_number].league or "") - if not global_current_room_ratings[player_number].placement_match_progress then - state = state.."\n"..rating_difference..global_current_room_ratings[player_number].new - elseif global_current_room_ratings[player_number].placement_match_progress - and global_current_room_ratings[player_number].new - and global_current_room_ratings[player_number].new == 0 then - state = state.."\n"..global_current_room_ratings[player_number].placement_match_progress - end - end - if character_select_mode == "2p_net_vs" or character_select_mode == "2p_local_vs" then - if current_server_supports_ranking then - state = state.."\n" - end - state = state.."Wins: "..win_count - if (current_server_supports_ranking and expected_win_ratio) or win_count + op_win_count > 0 then - state = state.."\nWinrate:" - local need_line_return = false - if win_count + op_win_count > 0 then - state = state.." actual: "..(100*round(win_count/(op_win_count+win_count),2)).."%" - need_line_return = true - end - if current_server_supports_ranking and expected_win_ratio then - if need_line_return then - state = state.."\n " - end - state = state.." expected: "..expected_win_ratio.."%" - end - end - end - return state - end - draw_button(0,1,1,1,"P1") - draw_button(0,2,2,1,get_player_state_str(my_player_number,my_rating_difference,my_win_count,op_win_count,my_expected_win_ratio),"left","top",true) - if cursor_data[1].state and op_name then - draw_button(0,5,1,1,"P2") - draw_button(0,6,2,1,get_player_state_str(op_player_number,op_rating_difference,op_win_count,my_win_count,op_expected_win_ratio),"left","top",true) - --state = state.." "..json.encode(op_state) - end - if character_select_mode == "2p_net_vs" then - if not cursor_data[1].state.ranked and not cursor_data[2].state.ranked then - match_type_message = "" - end - gprintf(match_type, 0, 15, canvas_width, "center") - gprintf(match_type_message, 0, 30, canvas_width, "center") - end - if pages_amount ~= 1 then - gprintf("Page "..current_page.."/"..pages_amount, 0, 660, canvas_width, "center") - end - wait() - - local ret = nil - - local function move_cursor(cursor_pos,direction) - local dx,dy = unpack(direction) - local can_x,can_y = wrap(1, cursor_pos[1]+dx, X), wrap(1, cursor_pos[2]+dy, Y) - while can_x ~= cursor_pos[1] or can_y ~= cursor_pos[2] do - if map[current_page][can_x][can_y] and ( map[current_page][can_x][can_y] ~= map[current_page][cursor_pos[1]][cursor_pos[2]] or - map[current_page][can_x][can_y] == "__Empty" ) then - break - end - can_x,can_y = wrap(1, can_x+dx, X), wrap(1, can_y+dy, Y) - end - cursor_pos[1],cursor_pos[2] = can_x,can_y - end - - local function change_panels_dir(panels_dir,increment) - local current = 0 - for k,v in ipairs(IMG_panels_dirs) do - if v == panels_dir then - current = k - break - end - end - local dir_count = #IMG_panels_dirs - local new_theme_idx = ((current - 1 + increment) % dir_count) + 1 - for k,v in ipairs(IMG_panels_dirs) do - if k == new_theme_idx then - return v - end - end - return panels_dir - end - - variable_step(function() - menu_clock = menu_clock + 1 - - character_loader_update() - refresh_loaded_and_ready(cursor_data[1].state,cursor_data[2].state) - - local up,down,left,right = {-1,0}, {1,0}, {0,-1}, {0,1} - local selectable = {__Panels=true, __Level=true, __Ready=true} - if not currently_spectating then - local KMax = 1 - if character_select_mode == "2p_local_vs" then - KMax = 2 - end - for i=1,KMax do - local k=K[i] - local cursor = cursor_data[i] - if menu_prev_page(k) then - if not cursor.selected then current_page = bound(1, current_page-1, pages_amount) end - elseif menu_next_page(k) then - if not cursor.selected then current_page = bound(1, current_page+1, pages_amount) end - elseif menu_up(k) then - if not cursor.selected then move_cursor(cursor.position,up) end - elseif menu_down(k) then - if not cursor.selected then move_cursor(cursor.position,down) end - elseif menu_left(k) then - if cursor.selected then - if cursor.state.cursor == "__Level" then - cursor.state.level = bound(1, cursor.state.level-1, 11) - if cursor.state.level >= 11 and cursor.state.ranked then - cursor.state.ranked = false - end - elseif cursor.state.cursor == "__Panels" then - cursor.state.panels_dir = change_panels_dir(cursor.state.panels_dir,-1) - end - end - if not cursor.selected then move_cursor(cursor.position,left) end - elseif menu_right(k) then - if cursor.selected then - if cursor.state.cursor == "__Level" then - cursor.state.level = bound(1, cursor.state.level+1, 11) - if cursor.state.level >= 11 and cursor.state.ranked then - cursor.state.ranked = false - end - elseif cursor.state.cursor == "__Panels" then - cursor.state.panels_dir = change_panels_dir(cursor.state.panels_dir,1) - end - end - if not cursor.selected then move_cursor(cursor.position,right) end - elseif menu_enter(k) then - if selectable[cursor.state.cursor] then - cursor.selected = not cursor.selected - elseif cursor.state.cursor == "__Leave" then - if character_select_mode == "2p_net_vs" then - if not do_leave() then - ret = {main_dumb_transition, {main_select_mode, "Error when leaving online"}} - end - else - ret = {main_select_mode} - end - elseif cursor.state.cursor == "__Random" then - cursor.state.character = uniformly(characters_ids_for_current_theme) - characters[cursor.state.character]:play_selection_sfx() - character_loader_load(cursor.state.character) - elseif cursor.state.cursor == "__Mode" then - if cursor.state.level < 11 then - cursor.state.ranked = not cursor.state.ranked - end - elseif cursor.state.cursor ~= "__Empty" then - cursor.state.character = cursor.state.cursor - cursor.state.character_display_name = characters[cursor.state.character].display_name - characters[cursor.state.character]:play_selection_sfx() - character_loader_load(cursor.state.character) - --When we select a character, move cursor to "__Ready" - cursor.state.cursor = "__Ready" - cursor.position = shallowcpy(name_to_xy_per_page[current_page]["__Ready"]) - end - elseif menu_escape(k) then - if cursor.state.cursor == "__Leave" then - if character_select_mode == "2p_net_vs" then - if not do_leave() then - ret = {main_dumb_transition, {main_select_mode, "Error when leaving online"}} - end - else - ret = {main_select_mode} - end - end - cursor.selected = false - cursor.position = shallowcpy(name_to_xy_per_page[current_page]["__Leave"]) - end - if cursor.state ~= nil then - cursor.state.cursor = map[current_page][cursor.position[1]][cursor.position[2]] - cursor.state.wants_ready = cursor.selected and cursor.state.cursor=="__Ready" - end - end - -- update config, does not redefine it - config.character = cursor_data[1].state.character - config.level = cursor_data[1].state.level - config.ranked = cursor_data[1].state.ranked - if config.use_panels_from_assets_folder == false then - config.panels_dir_when_not_using_set_from_assets_folder = cursor_data[1].state.panels_dir - config.panels_dir = config.panels_dir_when_not_using_set_from_assets_folder - end - - if character_select_mode == "2p_local_vs" then -- this is registered for future entering of the lobby - global_op_state = shallowcpy(cursor_data[2].state) - global_op_state.wants_ready = false - end - - if character_select_mode == "2p_net_vs" and not content_equal(cursor_data[1].state, prev_state) and not currently_spectating then - json_send({menu_state=cursor_data[1].state}) - end - prev_state = shallowcpy(cursor_data[1].state) - - else -- (we are are spectating) - if menu_escape(K[1]) then - do_leave() - ret = {main_net_vs_lobby} - end - end - end) - if ret then - return unpack(ret) - end - if cursor_data[1].state.ready and character_select_mode == "1p_vs_yourself" then - P1 = Stack(1, "vs", cursor_data[1].state.panels_dir, cursor_data[1].state.level, cursor_data[1].state.character) - P1.enable_analytics = true - P1.garbage_target = P1 - make_local_panels(P1, "000000") - make_local_gpanels(P1, "000000") - P1:starting_state() - return main_dumb_transition, {main_local_vs_yourself, "Game is starting...", 30, 30} - elseif cursor_data[1].state.ready and character_select_mode == "2p_local_vs" and cursor_data[2].state.ready then - P1 = Stack(1, "vs", cursor_data[1].state.panels_dir, cursor_data[1].state.level, cursor_data[1].state.character) - P1.enable_analytics = true - P2 = Stack(2, "vs", cursor_data[2].state.panels_dir, cursor_data[2].state.level, cursor_data[2].state.character) - P1.garbage_target = P2 - P2.garbage_target = P1 - move_stack(P2,2) - -- TODO: this does not correctly implement starting configurations. - -- Starting configurations should be identical for visible blocks, and - -- they should not be completely flat. - -- - -- In general the block-generation logic should be the same as the server's, so - -- maybe there should be only one implementation. - make_local_panels(P1, "000000") - make_local_gpanels(P1, "000000") - make_local_panels(P2, "000000") - make_local_gpanels(P2, "000000") - P1:starting_state() - P2:starting_state() - return main_local_vs - elseif character_select_mode == "2p_net_vs" then - if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} - end - end - end + select_screen.character_select_mode = "2p_net_vs" + return select_screen.main() end function main_net_vs_lobby() + if themes[config.theme].musics.main then + find_and_add_music(themes[config.theme].musics, "main") + end + background = themes[config.theme].images.bg_main + reset_filters() + character_loader_clear() + stage_loader_clear() local active_name, active_idx, active_back = "", 1 local items local unpaired_players = {} -- list @@ -1323,11 +448,9 @@ function main_net_vs_lobby() local k = K[1] my_player_number = nil op_player_number = nil - local notice = {[true]="Select a player name to ask for a match.", [false]="You are all alone in the lobby :("} + local notice = {[true]=loc("lb_select_player"), [false]=loc("lb_alone")} local leaderboard_string = "" local my_rank - love.audio.stop() - stop_the_music() match_type = "" match_type_message = "" --attempt login @@ -1335,58 +458,69 @@ function main_net_vs_lobby() if not my_user_id then my_user_id = "need a new user id" end - json_send({login_request=true, user_id=my_user_id}) - local login_status_message = " Logging in..." + local login_status_message = " "..loc("lb_login") local login_status_message_duration = 2 local login_denied = false local prev_act_idx = active_idx local showing_leaderboard = false local lobby_menu_x = {[true]=main_menu_screen_pos[1]-200, [false]=main_menu_screen_pos[1]} --will be used to make room in case the leaderboard should be shown. - local lobby_menu_y = main_menu_screen_pos[2]-120 + local lobby_menu_y = main_menu_screen_pos[2] + 50 local sent_requests = {} + if connection_up_time <= login_status_message_duration then + json_send({login_request=true, user_id=my_user_id}) + end + local lobby_menu = Click_menu() + local items = {} + local lastPlayerIndex = 0 + local updated = false while true do - if connection_up_time <= login_status_message_duration then - gprint(login_status_message, lobby_menu_x[showing_leaderboard], lobby_menu_y) - for _,msg in ipairs(this_frame_messages) do - if msg.login_successful then - current_server_supports_ranking = true - logged_in = true - if msg.new_user_id then - my_user_id = msg.new_user_id - print("about to write user id file") - write_user_id_file() - login_status_message = "Welcome, new user: "..my_name - elseif msg.name_changed then - login_status_message = "Welcome, your username has been updated. \n\nOld name: \""..msg.old_name.."\"\n\nNew name: \""..msg.new_name.."\"" - login_status_message_duration = 5 - else - login_status_message = "Welcome back, "..my_name - end - elseif msg.login_denied then - current_server_supports_ranking = true - login_denied = true - --TODO: create a menu here to let the user choose "continue unranked" or "get a new user_id" - --login_status_message = "Login for ranked matches failed.\n"..msg.reason.."\n\nYou may continue unranked,\nor delete your invalid user_id file to have a new one assigned." - login_status_message_duration = 10 - return main_dumb_transition, {main_select_mode, "Error message received from the server:\n\n"..json.encode(msg),60,600} - end - end - if connection_up_time == 2 and not current_server_supports_ranking then - login_status_message = "Login for ranked matches timed out.\nThis server probably doesn't support ranking.\n\nYou may continue unranked." - login_status_message_duration = 7 + if connection_up_time <= login_status_message_duration then + gprint(login_status_message, lobby_menu_x[showing_leaderboard], lobby_menu_y-120) + local messages = server_queue:pop_all_with("login_successful", "login_denied") + for _,msg in ipairs(messages) do + if msg.login_successful then + current_server_supports_ranking = true + logged_in = true + if msg.new_user_id then + my_user_id = msg.new_user_id + print("about to write user id file") + write_user_id_file() + login_status_message = loc("lb_user_new", my_name) + elseif msg.name_changed then + login_status_message = loc("lb_user_update", msg.old_name, msg.new_name) + login_status_message_duration = 5 + else + login_status_message = loc("lb_welcome_back", my_name) + end + elseif msg.login_denied then + current_server_supports_ranking = true + login_denied = true + --TODO: create a menu here to let the user choose "continue unranked" or "get a new user_id" + --login_status_message = "Login for ranked matches failed.\n"..msg.reason.."\n\nYou may continue unranked,\nor delete your invalid user_id file to have a new one assigned." + login_status_message_duration = 10 + return main_dumb_transition, {main_select_mode, loc("lb_error_msg").."\n\n"..json.encode(msg),60,600} end end - for _,msg in ipairs(this_frame_messages) do + if connection_up_time == 2 and not current_server_supports_ranking then + login_status_message = loc("lb_login_timeout") + login_status_message_duration = 7 + end + end + local messages = server_queue:pop_all_with("choose_another_name", "create_room", "unpaired", "game_request", "leaderboard_report", "spectate_request_granted") + for _,msg in ipairs(messages) do + updated = true + items = {} if msg.choose_another_name and msg.choose_another_name.used_names then - return main_dumb_transition, {main_select_mode, "Error: name is taken :<\n\nIf you had just left the server,\nit may not have realized it yet, try joining again.\n\nThis can also happen if you have two\ninstances of Panel Attack open.\n\nPress Swap or Back to continue.", 60, 600} + return main_dumb_transition, {main_select_mode, loc("lb_used_name"), 60, 600} elseif msg.choose_another_name and msg.choose_another_name.reason then - return main_dumb_transition, {main_select_mode, "Error: ".. msg.choose_another_name.reason, 60} + return main_dumb_transition, {main_select_mode, "Error: ".. msg.choose_another_name.reason, 60, 300} end if msg.create_room or msg.spectate_request_granted then global_initialize_room_msg = msg - character_select_mode = "2p_net_vs" + select_screen.character_select_mode = "2p_net_vs" love.window.requestAttention() - return main_character_select + play_optional_sfx(themes[config.theme].sounds.notification) + return select_screen.main end if msg.unpaired then unpaired_players = msg.unpaired @@ -1400,16 +534,18 @@ function main_net_vs_lobby() end willing_players = new_willing sent_requests = new_sent_requests - end - if msg.spectatable then - spectatable_rooms = msg.spectatable + if msg.spectatable then + spectatable_rooms = msg.spectatable + end end if msg.game_request then willing_players[msg.game_request.sender] = true love.window.requestAttention() + play_optional_sfx(themes[config.theme].sounds.notification) end if msg.leaderboard_report then showing_leaderboard = true + lobby_menu:show_controls(true) leaderboard_report = msg.leaderboard_report for k,v in ipairs(leaderboard_report) do if v.is_you then @@ -1421,59 +557,64 @@ function main_net_vs_lobby() leaderboard_string = build_viewable_leaderboard_string(leaderboard_report, leaderboard_first_idx_to_show, leaderboard_last_idx_to_show) end end + local print_x, print_y = unpack(main_menu_screen_pos) local to_print = "" local arrow = "" - items = {} - for _,v in ipairs(unpaired_players) do - if v ~= config.name then - items[#items+1] = v + + if updated then + local last_lobby_menu_active_idx = lobby_menu.active_idx + lobby_menu:remove_self() + items = {} + for _,v in ipairs(unpaired_players) do + if v ~= config.name then + items[#items+1] = v + end end - end - local lastPlayerIndex = #items --the rest of the items will be spectatable rooms, except the last two items (leaderboard and back to main menu) - for _,v in ipairs(spectatable_rooms) do - items[#items+1] = v - end - if showing_leaderboard then - items[#items+1] = "Hide Leaderboard" - else - items[#items+1] = "Show Leaderboard" -- the second to last item is "Leaderboard" - end - items[#items+1] = "Back to main menu" -- the last item is "Back to the main menu" - if active_back then - active_idx = #items - elseif showing_leaderboard then - active_idx = #items - 1 --the position of the "hide leaderboard" menu item - else - while active_idx > #items do - print("active_idx > #items. Decrementing active_idx") - active_idx = active_idx - 1 + lastPlayerIndex = #items --the rest of the items will be spectatable rooms, except the last two items (leaderboard and back to main menu) + for _,v in ipairs(spectatable_rooms) do + items[#items+1] = v end - active_name = items[active_idx] - end - for i=1,#items do - if active_idx == i then - arrow = arrow .. ">" + if showing_leaderboard then + items[#items+1] = loc("lb_hide_board") else - arrow = arrow .. "\n" + items[#items+1] = loc("lb_show_board") -- the second to last item is "Leaderboard" + end + items[#items+1] = loc("lb_back") -- the last item is "Back to the main menu" + local items_to_print = {} + for i=1,#items do + if i <= lastPlayerIndex then + items_to_print[i] = items[i] ..(sent_requests[items[i]] and " "..loc("lb_request") or "").. (willing_players[items[i]] and " "..loc("lb_received") or "") + elseif i < #items - 1 and items[i].name then + items_to_print[i] = loc("lb_spectate").." " .. items[i].name .. " (".. items[i].state .. ")" --printing room names + elseif i < #items then + items_to_print[i] = items[i] + else + items_to_print[i] = items[i] + end end - if i <= lastPlayerIndex then - to_print = to_print .. " " .. items[i] ..(sent_requests[items[i]] and " (Request sent)" or "").. (willing_players[items[i]] and " (Wants to play with you :o)" or "") .. "\n" - elseif i < #items - 1 and items[i].name then - to_print = to_print .. " spectate " .. items[i].name .. " (".. items[i].state .. ")\n" --printing room names - elseif i < #items then - to_print = to_print .. " " .. items[i] .. "\n" + + lobby_menu = Click_menu(items_to_print, lobby_menu_x[showing_leaderboard], lobby_menu_y, nil, love.graphics.getHeight() - lobby_menu_y - 90, 8, last_lobby_menu_active_idx) + lobby_menu:set_active_idx(last_active_idx) + if active_back then + lobby_menu:set_active_idx(#items) + elseif showing_leaderboard then + lobby_menu:set_active_idx(#items - 1) --the position of the "hide leaderboard" menu item else - to_print = to_print .. " " .. items[i] + while lobby_menu.active_idx > #items do + lobby_menu:set_active_idx(lobby_menu.active_idx - 1) + end + active_name = items[lobby_menu.active_idx] end end - gprint(notice[#items > 2], lobby_menu_x[showing_leaderboard], lobby_menu_y+90) - gprint(arrow, lobby_menu_x[showing_leaderboard], lobby_menu_y+120) - gprint(to_print, lobby_menu_x[showing_leaderboard], lobby_menu_y+120) + gprint(notice[#items > 2], lobby_menu_x[showing_leaderboard], lobby_menu_y-30) + gprint(arrow, lobby_menu_x[showing_leaderboard], lobby_menu_y) + gprint(to_print, lobby_menu_x[showing_leaderboard], lobby_menu_y) if showing_leaderboard then - gprint(leaderboard_string, lobby_menu_x[showing_leaderboard]+400, lobby_menu_y) + gprint(leaderboard_string, lobby_menu_x[showing_leaderboard]+400, lobby_menu_y - 120) end - gprint(join_community_msg, main_menu_screen_pos[1]+30, main_menu_screen_pos[2]+280) - + gprint(join_community_msg, main_menu_screen_pos[1]+30, love.graphics.getHeight() - 50) + lobby_menu:draw() + updated = false wait() local ret = nil variable_step(function() @@ -1485,7 +626,7 @@ function main_net_vs_lobby() leaderboard_string = build_viewable_leaderboard_string(leaderboard_report, leaderboard_first_idx_to_show, leaderboard_last_idx_to_show) end else - active_idx = wrap(1, active_idx-1, #items) + lobby_menu:set_active_idx(wrap(1, lobby_menu.active_idx-1, #items)) end elseif menu_down(k) then if showing_leaderboard then @@ -1495,53 +636,60 @@ function main_net_vs_lobby() leaderboard_string = build_viewable_leaderboard_string(leaderboard_report, leaderboard_first_idx_to_show, leaderboard_last_idx_to_show) end else - active_idx = wrap(1, active_idx+1, #items) + lobby_menu:set_active_idx(wrap(1, lobby_menu.active_idx+1, #items)) end - elseif menu_enter(k) then + elseif menu_enter(k) or lobby_menu.idx_selected then + updated = true + lobby_menu:set_active_idx(lobby_menu.idx_selected or lobby_menu.active_idx) + lobby_menu.idx_selected = nil spectator_list = {} spectators_string = "" - if active_idx == #items then + if lobby_menu.active_idx == #items then ret = {main_select_mode} end - if active_idx == #items - 1 then + if lobby_menu.active_idx == #items - 1 then if not showing_leaderboard then json_send({leaderboard_request=true}) else showing_leaderboard = false --toggle it off + lobby_menu:show_controls(false) + lobby_menu:move(lobby_menu_x[showing_leaderboard], lobby_menu_y) end - elseif active_idx <= lastPlayerIndex then + elseif lobby_menu.active_idx <= lastPlayerIndex then my_name = config.name - op_name = items[active_idx] + op_name = items[lobby_menu.active_idx] currently_spectating = false sent_requests[op_name] = true - request_game(items[active_idx]) + request_game(items[lobby_menu.active_idx]) else - my_name = items[active_idx].a - op_name = items[active_idx].b + my_name = items[lobby_menu.active_idx].a + op_name = items[lobby_menu.active_idx].b currently_spectating = true - room_number_last_spectated = items[active_idx].roomNumber - request_spectate(items[active_idx].roomNumber) + room_number_last_spectated = items[lobby_menu.active_idx].roomNumber + request_spectate(items[lobby_menu.active_idx].roomNumber) end elseif menu_escape(k) then - if active_idx == #items then + if lobby_menu.active_idx == #items then ret = {main_select_mode} elseif showing_leaderboard then showing_leaderboard = false + lobby_menu:show_controls(#lobby_menu.buttons > lobby_menu.button_limit) + lobby_menu:move(lobby_menu_x[showing_leaderboard], lobby_menu_y) else - active_idx = #items + lobby_menu:set_active_idx(#items) end end end) if ret then + json_send({logout=true}) return unpack(ret) end - active_back = active_idx == #items - if active_idx ~= prev_act_idx then - print("#items: "..#items.." idx_old: "..prev_act_idx.." idx_new: "..active_idx.." active_back: "..tostring(active_back)) - prev_act_idx = active_idx + active_back = lobby_menu.active_idx == #items + if lobby_menu.active_idx ~= prev_act_idx then + prev_act_idx = lobby_menu.active_idx end if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} end end end @@ -1550,7 +698,7 @@ function update_win_counts(win_counts) if (P1 and P1.player_number == 1) or currently_spectating then my_win_count = win_counts[1] or 0 op_win_count = win_counts[2] or 0 - elseif P1.player_number == 2 then + elseif P1 and P1.player_number == 2 then my_win_count = win_counts[2] or 0 op_win_count = win_counts[1] or 0 end @@ -1565,18 +713,18 @@ function spectator_list_string(list) end end if str ~= "" then - str = "Spectator(s):\n"..str + str = loc("pl_spectators").."\n"..str end return str end function build_viewable_leaderboard_string(report, first_viewable_idx, last_viewable_idx) - str = " Leaderboard\n Rank Rating Player\n" + str = loc("lb_header_board").."\n" first_viewable_idx = math.max(first_viewable_idx,1) last_viewable_idx = math.min(last_viewable_idx, #report) for i=first_viewable_idx,last_viewable_idx do if report[i].is_you then - str = str.."You-> " + str = str..loc("lb_you").."-> " else str = str.." " end @@ -1588,22 +736,27 @@ function build_viewable_leaderboard_string(report, first_viewable_idx, last_view return str end -function main_net_vs_setup(ip) +function main_net_vs_setup(ip, network_port) if not config.name then return main_set_name else my_name = config.name end + while config.name == "defaultname" do + if main_set_name() == {main_select_mode} and config.name ~= "defaultname" then + return main_net_vs_setup + end + end P1, P1_level, P2_level, got_opponent = nil P2 = {panel_buffer="", gpanel_buffer=""} - gprint("Setting up connection...", unpack(main_menu_screen_pos)) + gprint(loc("lb_set_connect"), unpack(main_menu_screen_pos)) wait() - network_init(ip) + network_init(ip, network_port) local timeout_counter = 0 while not connection_is_ready() do - gprint("Connecting...", unpack(main_menu_screen_pos)) + gprint(loc("lb_connecting"), unpack(main_menu_screen_pos)) wait() if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} end end connected_server_ip = ip @@ -1614,10 +767,14 @@ end function main_net_vs() --STONER_MODE = true - bg = IMG_stages[math.random(#IMG_stages)] + if current_stage then + use_current_stage() + else + pick_random_stage() + end + pick_use_music_from() local k = K[1] --may help with spectators leaving games in progress local end_text = nil - consuming_timesteps = true local op_name_y = 40 if string.len(my_name) > 12 then op_name_y = 55 @@ -1625,50 +782,88 @@ function main_net_vs() while true do -- Uncomment this to cripple your game :D -- love.timer.sleep(0.030) - for _,msg in ipairs(this_frame_messages) do - if msg.leave_room then - return main_net_vs_lobby + local messages = server_queue:pop_all_with("taunt", "leave_room") + for _,msg in ipairs(messages) do + if msg.taunt then + local taunts = nil + -- P1.character and P2.character are supposed to be already filtered with current mods, taunts may differ though! + if msg.player_number == my_player_number then + taunts = characters[P1.character].sounds[msg.type] + elseif msg.player_number == op_player_number then + taunts = characters[P2.character].sounds[msg.type] + end + if taunts then + for _,t in ipairs(taunts) do + t:stop() + end + if msg.index <= #taunts then + taunts[msg.index]:play() + elseif #taunts ~= 0 then + taunts[math.random(#taunts)]:play() + end + end + elseif msg.leave_room then + my_win_count = 0 + op_win_count = 0 + return main_dumb_transition, {main_net_vs_lobby, "", 0, 0} end end - local name_and_score = { (my_name or "").."\nWins: "..my_win_count, (op_name or "").."\nWins: "..op_win_count} - gprint(name_and_score[1], P1.score_x, P1.score_y-48) - gprint(name_and_score[2], P2.score_x, P2.score_y-48) + local name_and_score = { (my_name or "").."\n"..loc("ss_wins").." "..my_win_count, (op_name or "").."\n"..loc("ss_wins").." "..op_win_count} + gprint((my_name or ""), P1.score_x+themes[config.theme].name_Pos[1], P1.score_y+themes[config.theme].name_Pos[2]) + gprint((op_name or ""), P2.score_x+themes[config.theme].name_Pos[1], P2.score_y+themes[config.theme].name_Pos[2]) + draw_label(themes[config.theme].images.IMG_wins, (P1.score_x+themes[config.theme].winLabel_Pos[1])/GFX_SCALE, (P1.score_y+themes[config.theme].winLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].winLabel_Scale) + draw_number(my_win_count, themes[config.theme].images.IMG_timeNumber_atlas, 12, P1_win_quads, P1.score_x+themes[config.theme].win_Pos[1], P1.score_y+themes[config.theme].win_Pos[2], themes[config.theme].win_Scale, + 20/themes[config.theme].images.timeNumberWidth*themes[config.theme].time_Scale, 26/themes[config.theme].images.timeNumberHeight*themes[config.theme].time_Scale, "center") + + draw_label(themes[config.theme].images.IMG_wins, (P2.score_x+themes[config.theme].winLabel_Pos[1])/GFX_SCALE, (P2.score_y+themes[config.theme].winLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].winLabel_Scale) + draw_number(op_win_count, themes[config.theme].images.IMG_timeNumber_atlas, 12, P2_win_quads, P2.score_x+themes[config.theme].win_Pos[1], P2.score_y+themes[config.theme].win_Pos[2], themes[config.theme].win_Scale, + 20/themes[config.theme].images.timeNumberWidth*themes[config.theme].time_Scale, 26/themes[config.theme].images.timeNumberHeight*themes[config.theme].time_Scale, "center") + if not config.debug_mode then --this is printed in the same space as the debug details - gprint(spectators_string, P1.score_x, P1.score_y+177) + gprint(spectators_string, themes[config.theme].spectators_Pos[1], themes[config.theme].spectators_Pos[2]) end if match_type == "Ranked" then if global_current_room_ratings[my_player_number] and global_current_room_ratings[my_player_number].new then - local rating_to_print = "Rating: " + local rating_to_print = loc("ss_rating").."\n" if global_current_room_ratings[my_player_number].new > 0 then - rating_to_print = rating_to_print.." "..global_current_room_ratings[my_player_number].new + rating_to_print = global_current_room_ratings[my_player_number].new + end + --gprint(rating_to_print, P1.score_x, P1.score_y-30) + draw_label(themes[config.theme].images.IMG_rating_1P, (P1.score_x+themes[config.theme].ratingLabel_Pos[1])/GFX_SCALE, (P1.score_y+themes[config.theme].ratingLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].ratingLabel_Scale) + if type(rating_to_print) == "number" then + draw_number(rating_to_print, themes[config.theme].images.IMG_number_atlas_1P, 10, P1_rating_quads, P1.score_x+themes[config.theme].rating_Pos[1], P1.score_y+themes[config.theme].rating_Pos[2], + themes[config.theme].rating_Scale, (15/themes[config.theme].images.numberWidth_1P*themes[config.theme].rating_Scale), (19/themes[config.theme].images.numberHeight_1P*themes[config.theme].rating_Scale), "center") end - gprint(rating_to_print, P1.score_x, P1.score_y-16) end if global_current_room_ratings[op_player_number] and global_current_room_ratings[op_player_number].new then - local op_rating_to_print = "Rating: " + local op_rating_to_print = loc("ss_rating").."\n" if global_current_room_ratings[op_player_number].new > 0 then - op_rating_to_print = op_rating_to_print.." "..global_current_room_ratings[op_player_number].new + op_rating_to_print = global_current_room_ratings[op_player_number].new + end + --gprint(op_rating_to_print, P2.score_x, P2.score_y-30) + draw_label(themes[config.theme].images.IMG_rating_2P, (P2.score_x+themes[config.theme].ratingLabel_Pos[1])/GFX_SCALE, (P2.score_y+themes[config.theme].ratingLabel_Pos[2])/GFX_SCALE, 0, themes[config.theme].ratingLabel_Scale) + if type(op_rating_to_print) == "number" then + draw_number(op_rating_to_print, themes[config.theme].images.IMG_number_atlas_2P, 10, P2_rating_quads, P2.score_x+themes[config.theme].rating_Pos[1], P2.score_y+themes[config.theme].rating_Pos[2], + themes[config.theme].rating_Scale, (15/themes[config.theme].images.numberWidth_2P*themes[config.theme].rating_Scale), (19/themes[config.theme].images.numberHeight_2P*themes[config.theme].rating_Scale), "center") end - gprint(op_rating_to_print, P2.score_x, P2.score_y-16) end end if not (P1 and P1.play_to_end) and not (P2 and P2.play_to_end) then P1:render() P2:render() wait() - if currently_spectating and this_frame_keys["escape"] then + if currently_spectating and menu_escape(K[1]) then print("spectator pressed escape during a game") - stop_the_music() my_win_count = 0 op_win_count = 0 json_send({leave_room=true}) - return main_net_vs_lobby + return main_dumb_transition, {main_net_vs_lobby, "", 0, 0} end if not do_messages() then - return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", unpack(main_menu_screen_pos)} + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} end end @@ -1702,21 +897,21 @@ function main_net_vs() local outcome_claim = nil local winSFX = nil if P1.game_over and P2.game_over and P1.CLOCK == P2.CLOCK then - end_text = "Draw" + end_text = loc("ss_draw") outcome_claim = 0 elseif P1.game_over and P1.CLOCK <= P2.CLOCK then winSFX = P2:pick_win_sfx() - end_text = op_name.." Wins" .. (currently_spectating and " " or " :(") + end_text = loc("ss_p_wins", op_name) op_win_count = op_win_count + 1 -- leaving these in just in case used with an old server that doesn't keep score. win_counts will get overwritten after this by the server anyway. outcome_claim = P2.player_number elseif P2.game_over and P2.CLOCK <= P1.CLOCK then winSFX = P1:pick_win_sfx() - end_text = my_name.." Wins" .. (currently_spectating and " " or " ^^") + end_text = loc("ss_p_wins", my_name) my_win_count = my_win_count + 1 -- leave this in outcome_claim = P1.player_number end if end_text then - analytics_game_ends() + analytics.game_ends() undo_stonermode() json_send({game_over=true, outcome=outcome_claim}) local now = os.date("*t",to_UTC(os.time())) @@ -1743,11 +938,11 @@ function main_net_vs() write_replay_file(path, filename) print("also saving replay as replay.txt") write_replay_file() - character_select_mode = "2p_net_vs" + select_screen.character_select_mode = "2p_net_vs" if currently_spectating then - return main_dumb_transition, {main_character_select, end_text, 45, 45, winSFX} + return main_dumb_transition, {select_screen.main, end_text, 30, 30, winSFX} else - return main_dumb_transition, {main_character_select, end_text, 45, 180, winSFX} + return main_dumb_transition, {select_screen.main, end_text, 30, 180, winSFX} end end end @@ -1758,78 +953,89 @@ function main_local_vs_setup() my_name = config.name or "Player 1" op_name = "Player 2" op_state = nil - character_select_mode = "2p_local_vs" - return main_character_select + select_screen.character_select_mode = "2p_local_vs" + return select_screen.main end function main_local_vs() -- TODO: replay! - bg = IMG_stages[math.random(#IMG_stages)] - consuming_timesteps = true + use_current_stage() + pick_use_music_from() local end_text = nil while true do - P1:render() - P2:render() + if game_is_paused then + draw_pause() + else + P1:render() + P2:render() + end wait() variable_step(function() if not P1.game_over and not P2.game_over then P1:local_run() P2:local_run() + P1:handle_pause() + P2:handle_pause() end end) local winSFX = nil if P1.game_over and P2.game_over and P1.CLOCK == P2.CLOCK then - end_text = "Draw" + end_text = loc("ss_draw") elseif P1.game_over and P1.CLOCK <= P2.CLOCK then winSFX = P2:pick_win_sfx() op_win_count = op_win_count + 1 - end_text = "P2 wins ^^" + end_text = loc("pl_2_win") elseif P2.game_over and P2.CLOCK <= P1.CLOCK then winSFX = P1:pick_win_sfx() my_win_count = my_win_count + 1 - end_text = "P1 wins ^^" + end_text = loc("pl_1_win") end if end_text then - analytics_game_ends() - return main_dumb_transition, {main_character_select, end_text, 45, nil, winSFX} + analytics.game_ends() + return main_dumb_transition, {select_screen.main, end_text, 45, -1, winSFX} end end end function main_local_vs_yourself_setup() currently_spectating = false - my_name = config.name or "Player 1" + my_name = config.name or loc("player_n", "1") op_name = nil op_state = nil - character_select_mode = "1p_vs_yourself" - return main_character_select + select_screen.character_select_mode = "1p_vs_yourself" + return select_screen.main end function main_local_vs_yourself() -- TODO: replay! - bg = IMG_stages[math.random(#IMG_stages)] - consuming_timesteps = true + use_current_stage() + pick_use_music_from() local end_text = nil while true do - P1:render() + if game_is_paused then + draw_pause() + else + P1:render() + end wait() variable_step(function() if not P1.game_over then P1:local_run() + P1:handle_pause() else - end_text = "Game Over" + end_text = loc("rp_score", P1.score, frames_to_time_string(P1.game_stopwatch)) end end) if end_text then - analytics_game_ends() - return main_dumb_transition, {main_character_select, end_text, 45} + analytics.game_ends() + return main_dumb_transition, {select_screen.main, end_text, 45, -1, P1:pick_win_sfx()} end end end local function draw_debug_mouse_panel() if debug_mouse_panel then - local str = "Panel info:\nrow: "..debug_mouse_panel[1].."\ncol: "..debug_mouse_panel[2] + local str = loc("pl_panel_info", debug_mouse_panel[1], debug_mouse_panel[2]) for k,v in spairs(debug_mouse_panel[3]) do str = str .. "\n".. k .. ": "..tostring(v) end @@ -1840,12 +1046,14 @@ end function main_replay_vs() local replay = replay.vs if replay == nil then - return main_dumb_transition, {main_select_mode, "I don't have a vs replay :("} + return main_dumb_transition, {main_select_mode, loc("rp_no_replay"), 0, -1} end - fallback_when_missing = nil - bg = IMG_stages[math.random(#IMG_stages)] - P1 = Stack(1, "vs", config.panels_dir, replay.P1_level or 5) - P2 = Stack(2, "vs", config.panels_dir, replay.P2_level or 5) + stop_the_music() + pick_random_stage() + pick_use_music_from() + select_screen.fallback_when_missing = { nil, nil } + P1 = Stack(1, "vs", config.panels, replay.P1_level or 5) + P2 = Stack(2, "vs", config.panels, replay.P2_level or 5) P1.do_countdown = replay.do_countdown or false P2.do_countdown = replay.do_countdown or false P1.ice = true @@ -1862,19 +1070,19 @@ function main_replay_vs() P2.max_runs_per_frame = 1 P1.character = replay.P1_char P2.character = replay.P2_char + P1.cur_wait_time = replay.P1_cur_wait_time or default_input_repeat_delay + P2.cur_wait_time = replay.P2_cur_wait_time or default_input_repeat_delay refresh_based_on_own_mods(P1) refresh_based_on_own_mods(P2, true) character_loader_load(P1.character) character_loader_load(P2.character) character_loader_wait() - my_name = replay.P1_name or "Player 1" - op_name = replay.P2_name or "Player 2" - if character_select_mode == "2p_net_vs" then - if replay.ranked then - match_type = "Ranked" - else - match_type = "Casual" - end + my_name = replay.P1_name or loc("player_n", "1") + op_name = replay.P2_name or loc("player_n", "2") + if replay.ranked then + match_type = "Ranked" + else + match_type = "Casual" end P1:starting_state() @@ -1888,13 +1096,16 @@ function main_replay_vs() P1:render() P2:render() draw_debug_mouse_panel() + if game_is_paused then + draw_pause() + end wait() local ret = nil variable_step(function() - if this_frame_keys["escape"] then - ret = {main_select_mode} + if menu_escape(K[1]) then + ret = {main_dumb_transition, {main_select_mode, "", 0, 0}} end - if this_frame_keys["return"] then + if menu_enter(K[1]) then run = not run end if this_frame_keys["\\"] then @@ -1903,6 +1114,7 @@ function main_replay_vs() if run or this_frame_keys["\\"] then if not P1.game_over then P1:foreign_run() + P1:handle_pause() end if not P2.game_over then P2:foreign_run() @@ -1914,36 +1126,38 @@ function main_replay_vs() end local winSFX = nil if P1.game_over and P2.game_over and P1.CLOCK == P2.CLOCK then - end_text = "Draw" + end_text = loc("ss_draw") elseif P1.game_over and P1.CLOCK <= P2.CLOCK then winSFX = P2:pick_win_sfx() if replay.P2_name and replay.P2_name ~= "anonymous" then - end_text = replay.P2_name.." wins" + end_text = loc("ss_p_wins", replay.P2_name) else - end_text = "P2 wins" + end_text = loc("pl_2_win") end elseif P2.game_over and P2.CLOCK <= P1.CLOCK then winSFX = P1:pick_win_sfx() if replay.P1_name and replay.P1_name ~= "anonymous" then - end_text = replay.P1_name.." wins" + end_text = loc("ss_p_wins", replay.P1_name) else - end_text = "P1 wins" + end_text = loc("pl_1_win") end end if end_text then - return main_dumb_transition, {main_select_mode, end_text, nil, nil, winSFX} + return main_dumb_transition, {main_select_mode, end_text, 0, -1, winSFX} end end end function main_replay_endless() - bg = IMG_stages[math.random(#IMG_stages)] local replay = replay.endless if replay == nil or replay.speed == nil then - return main_dumb_transition, - {main_select_mode, "I don't have an endless replay :("} + return main_dumb_transition, {main_select_mode, loc("rp_no_endless"), 0, -1} end - P1 = Stack(1, "endless", config.panels_dir, replay.speed, replay.difficulty) + stop_the_music() + pick_random_stage() + pick_use_music_from() + P1 = Stack(1, "endless", config.panels, replay.speed, replay.difficulty) + P1:wait_for_random_character() P1.do_countdown = replay.do_countdown or false P1.max_runs_per_frame = 1 P1.input_buffer = table.concat({replay.in_buf}) @@ -1951,17 +1165,21 @@ function main_replay_endless() P1.gpanel_buffer = replay.gpan_buf P1.speed = replay.speed P1.difficulty = replay.difficulty + P1.cur_wait_time = replay.cur_wait_time or default_input_repeat_delay P1:starting_state() local run = true while true do P1:render() + if game_is_paused then + draw_pause() + end wait() local ret = nil variable_step(function() - if this_frame_keys["escape"] then - ret = {main_select_mode} + if menu_escape(K[1]) then + ret = {main_dumb_transition, {main_select_mode, "", 0, 0}} end - if this_frame_keys["return"] then + if menu_enter(K[1]) then run = not run end if this_frame_keys["\\"] then @@ -1970,10 +1188,11 @@ function main_replay_endless() if run or this_frame_keys["\\"] then if P1.game_over then -- TODO: proper game over. - local end_text = "You scored "..P1.score.."\nin "..frames_to_time_string(P1.game_stopwatch, true) - ret = {main_dumb_transition, {main_select_mode, end_text, 30}} + local end_text = loc("rp_score", P1.score, frames_to_time_string(P1.game_stopwatch, true)) + ret = {main_dumb_transition, {main_select_mode, end_text, 30, -1, P1:pick_win_sfx()}} end P1:foreign_run() + P1:handle_pause() end end) if ret then @@ -1983,29 +1202,35 @@ function main_replay_endless() end function main_replay_puzzle() - bg = IMG_stages[math.random(#IMG_stages)] local replay = replay.puzzle if not replay or replay.in_buf == nil or replay.in_buf == "" then - return main_dumb_transition, - {main_select_mode, "I don't have a puzzle replay :("} + return main_dumb_transition, {main_select_mode, loc("rp_no_puzzle"), 0, -1} end - P1 = Stack(1, "puzzle", config.panels_dir) + stop_the_music() + pick_random_stage() + pick_use_music_from() + P1 = Stack(1, "puzzle", config.panels) + P1:wait_for_random_character() P1.do_countdown = replay.do_countdown or false P1.max_runs_per_frame = 1 P1.input_buffer = replay.in_buf + P1.cur_wait_time = replay.cur_wait_time or default_input_repeat_delay P1:set_puzzle_state(unpack(replay.puzzle)) local run = true while true do debug_mouse_panel = nil P1:render() draw_debug_mouse_panel() + if game_is_paused then + draw_pause() + end wait() local ret = nil variable_step(function() - if this_frame_keys["escape"] then - ret = {main_select_mode} + if menu_escape(K[1]) then + ret = {main_dumb_transition, {main_select_mode, "", 0, 0}} end - if this_frame_keys["return"] then + if menu_enter(K[1]) then run = not run end if this_frame_keys["\\"] then @@ -2015,12 +1240,13 @@ function main_replay_puzzle() if P1.n_active_panels == 0 and P1.prev_active_panels == 0 then if P1:puzzle_done() then - ret = {main_dumb_transition, {main_select_mode, "You win!"}} + ret = {main_dumb_transition, {main_select_mode, loc("pl_you_win"), 30, -1, P1:pick_win_sfx()}} elseif P1.puzzle_moves == 0 then - ret = {main_dumb_transition, {main_select_mode, "You lose :("}} + ret = {main_dumb_transition, {main_select_mode, loc("pl_you_lose"), 30, -1}} end end P1:foreign_run() + P1:handle_pause() end end) if ret then @@ -2032,45 +1258,54 @@ end function make_main_puzzle(puzzles) local awesome_idx, next_func = 1, nil function next_func() - bg = IMG_stages[math.random(#IMG_stages)] - consuming_timesteps = true + stop_the_music() + pick_random_stage() + pick_use_music_from() replay.puzzle = {} local replay = replay.puzzle - P1 = Stack(1, "puzzle", config.panels_dir) + P1 = Stack(1, "puzzle", config.panels) + P1:wait_for_random_character() P1.do_countdown = config.ready_countdown_1P or false local start_delay = 0 if awesome_idx == nil then awesome_idx = math.random(#puzzles) end P1:set_puzzle_state(unpack(puzzles[awesome_idx])) + replay.cur_wait_time = P1.cur_wait_time or default_input_repeat_delay replay.puzzle = puzzles[awesome_idx] replay.in_buf = "" while true do - P1:render() + if game_is_paused then + draw_pause() + else + P1:render() + end wait() local ret = nil variable_step(function() if this_frame_keys["escape"] then - ret = {main_select_puzz} - end - if P1.n_active_panels == 0 and - P1.prev_active_panels == 0 then - if P1:puzzle_done() then - awesome_idx = (awesome_idx % #puzzles) + 1 - write_replay_file() - if awesome_idx == 1 then - ret = {main_dumb_transition, {main_select_puzz, "You win!", 30}} - else - ret = {main_dumb_transition, {next_func, "You win!", 30}} + ret = {main_dumb_transition, {main_select_puzz, "", 0, 0}} + else + if P1.n_active_panels == 0 and + P1.prev_active_panels == 0 then + if P1:puzzle_done() then + awesome_idx = (awesome_idx % #puzzles) + 1 + write_replay_file() + if awesome_idx == 1 then + ret = {main_dumb_transition, {main_select_puzz, loc("pl_you_win"), 30, -1, P1:pick_win_sfx()}} + else + ret = {main_dumb_transition, {next_func, loc("pl_you_win"), 30, -1, P1:pick_win_sfx()}} + end + elseif P1.puzzle_moves == 0 then + write_replay_file() + ret = {main_dumb_transition, {main_select_puzz, loc("pl_you_lose"), 30, -1}} end - elseif P1.puzzle_moves == 0 then - write_replay_file() - ret = {main_dumb_transition, {main_select_puzz, "You lose :(", 30}} end - end - if P1.n_active_panels ~= 0 or P1.prev_active_panels ~= 0 or - P1.puzzle_moves ~= 0 then - P1:local_run() + if P1.n_active_panels ~= 0 or P1.prev_active_panels ~= 0 or + P1.puzzle_moves ~= 0 then + P1:local_run() + P1:handle_pause() + end end end) if ret then @@ -2086,11 +1321,13 @@ do for key,val in spairs(puzzle_sets) do items[#items+1] = {key, make_main_puzzle(val)} end - items[#items+1] = {"Back", main_select_mode} + items[#items+1] = {"back", main_select_mode} function main_select_puzz() - love.audio.stop() - stop_the_music() - bg = title + if themes[config.theme].musics.main then + find_and_add_music(themes[config.theme].musics, "main") + end + background = themes[config.theme].images.bg_main + reset_filters() local active_idx = last_puzzle_idx or 1 local k = K[1] while true do @@ -2102,10 +1339,11 @@ do else arrow = arrow .. "\n" end - to_print = to_print .. " " .. items[i][1] .. "\n" + local loc_item = (items[i][1] == "back") and loc("back") or items[i][1] + to_print = to_print .. " " .. loc_item .. "\n" end - gprint("Puzzles:", unpack(main_menu_screen_pos) ) - gprint("Note: you may place new custom puzzles in\n\n%appdata%\\Panel Attack\\puzzles\n\nSee the README and example puzzle set there\nfor instructions", main_menu_screen_pos[1]-280, main_menu_screen_pos[2]+220) + gprint(loc("pz_puzzles"), unpack(main_menu_screen_pos) ) + gprint(loc("pz_info"), main_menu_screen_pos[1]-280, main_menu_screen_pos[2]+220) gprint(arrow, main_menu_screen_pos[1]+100, main_menu_screen_pos[2]) gprint(to_print, main_menu_screen_pos[1]+100, main_menu_screen_pos[2]) wait() @@ -2134,38 +1372,37 @@ do end function main_config_input() - local pretty_names = {"Up", "Down", "Left", "Right", "A", "B", "L", "R"} - local items, active_idx = {}, 1 + local pretty_names = {loc("up"), loc("down"), loc("left"), loc("right"), "A", "B", "X", "Y", "L", "R", loc("start")} + local menu_x, menu_y = unpack(main_menu_screen_pos) + local input_menu = Click_menu(nil, menu_x, menu_y, nil, love.graphics.getHeight() - menu_y - 80, 8, 1, true, 2) + local items = {} local k = K[1] local active_player = 1 local function get_items() - items = {[0]={"Player ", ""..active_player}} + items = {[1]={loc("player").. " ", ""..active_player}} for i=1,#key_names do - items[#items+1] = {pretty_names[i], k[key_names[i]] or "none"} + items[#items+1] = {pretty_names[i], k[key_names[i]] or loc("op_none")} end - items[#items+1] = {"Set all keys", ""} - items[#items+1] = {"Back", "", main_select_mode} + items[#items+1] = {loc("op_all_keys"), ""} + items[#items+1] = {loc("back"), "", main_select_mode} + end + get_items() + for i=1,#items do + input_menu:add_button(items[i][1]) + input_menu:set_button_setting(i, items[i][2]) end local function print_stuff() - local to_print, to_print2, arrow = "", "", "" - for i=0,#items do - if active_idx == i then - arrow = arrow .. ">" - else - arrow = arrow .. "\n" - end - to_print = to_print .. " " .. items[i][1] .. "\n" - to_print2 = to_print2 .. " " .. items[i][2] .. "\n" - end - gprint(arrow, unpack(main_menu_screen_pos)) - gprint(to_print, unpack(main_menu_screen_pos)) - gprint(to_print2, unpack(main_menu_screen_pos)) + input_menu:draw() end local idxs_to_set = {} while true do get_items() + for i=1,#items do + input_menu:set_button_setting(i, items[i][2]) + end if #idxs_to_set > 0 then items[idxs_to_set[1]][2] = "___" + input_menu:set_button_setting(idxs_to_set[1], "___") end print_stuff() wait() @@ -2175,385 +1412,56 @@ function main_config_input() local idx = idxs_to_set[1] for key,val in pairs(this_frame_keys) do if val then - k[key_names[idx]] = key + k[key_names[idx-1]] = key table.remove(idxs_to_set, 1) if #idxs_to_set == 0 then write_key_file() end end end + elseif input_menu.idx_selected then + print("config menu had an idx_selected") + input_menu:set_active_idx(input_menu.idx_selected) + input_menu.idx_selected = nil + if input_menu.active_idx == 1 then + active_player = wrap(1, active_player+1, 2) + k=K[active_player] + elseif input_menu.active_idx <= #key_names + 1 then + idxs_to_set = {input_menu.active_idx} + elseif input_menu.active_idx == #key_names + 2 then + idxs_to_set = {2,3,4,5,6,7,8,9,10,11,12} + elseif input_menu.active_idx == #items then + ret = {items[input_menu.active_idx][3], items[input_menu.active_idx][4]} + end elseif menu_up(K[1]) then - active_idx = wrap(1, active_idx-1, #items) + input_menu:set_active_idx(wrap(1, input_menu.active_idx-1, #items)) elseif menu_down(K[1]) then - active_idx = wrap(1, active_idx+1, #items) + input_menu:set_active_idx(wrap(1, input_menu.active_idx+1, #items)) elseif menu_left(K[1]) then active_player = wrap(1, active_player-1, 2) k=K[active_player] elseif menu_right(K[1]) then active_player = wrap(1, active_player+1, 2) k=K[active_player] - elseif menu_enter(K[1]) then - if active_idx <= #key_names then - idxs_to_set = {active_idx} - elseif active_idx == #key_names + 1 then - idxs_to_set = {1,2,3,4,5,6,7,8} - else - ret = {items[active_idx][3], items[active_idx][4]} - end - elseif menu_escape(K[1]) then - if active_idx == #items then - ret = {items[active_idx][3], items[active_idx][4]} - else - active_idx = #items + elseif menu_enter_one_press(K[1]) then + if input_menu.active_idx == 1 then + active_player = wrap(1, active_player+1, 2) + k=K[active_player] + elseif input_menu.active_idx <= #key_names + 1 then + idxs_to_set = {input_menu.active_idx} + elseif input_menu.active_idx == #key_names + 2 then + idxs_to_set = {2,3,4,5,6,7,8,9,10,11,12} end - end - end) - if ret then - return unpack(ret) - end - end -end - -function main_show_custom_graphics_readme(idx) - if not love.filesystem.getInfo("assets/"..prefix_of_ignored_dirs..default_assets_dir) then - print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") - gprint("Hold on. Copying an example folder to make this easier...\n\nThis may take a few seconds or maybe even a minute or two.\n\nDon't worry if the window goes inactive or \"not responding\"", 280, 280) - wait() - recursive_copy("assets/"..default_assets_dir, "assets/"..prefix_of_ignored_dirs..default_assets_dir) - end - - -- add other defaults panels sets here so that anyone can update them if wanted - local default_panels_dirs = { default_panels_dir, "libre" } - - for _,panels_dir in ipairs(default_panels_dirs) do - if not love.filesystem.getInfo("panels/"..prefix_of_ignored_dirs..panels_dir) then - print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") - gprint("Hold on. Copying example folders to make this easier...\n\nThis may take a few seconds or maybe even a minute or two.\n\nDon't worry if the window goes inactive or \"not responding\"", 280, 280) - wait() - recursive_copy("panels/"..panels_dir, "panels/"..prefix_of_ignored_dirs..panels_dir) - end - end - - local custom_graphics_readme = read_txt_file("Custom Graphics Readme.txt") - while true do - gprint(custom_graphics_readme, 15, 15) - do_menu_function = false - wait() - local ret = nil - variable_step(function() - if menu_escape(K[1]) or menu_enter(K[1]) then - ret = {main_options, {idx}} - end - end) - if ret then - return unpack(ret) - end - end -end - -function main_show_custom_sounds_readme(idx) - if not love.filesystem.getInfo("sounds/"..prefix_of_ignored_dirs..default_sounds_dir)then - print("Hold on. Copying an example folder to make this easier...\n This make take a few seconds.") - gprint("Hold on. Copying an example folder to make this easier...\n\nThis may take a few seconds or maybe even a minute or two.\n\nDon't worry if the window goes inactive or \"not responding\"", 280, 280) - wait() - recursive_copy("sounds/"..default_sounds_dir, "sounds/"..prefix_of_ignored_dirs..default_sounds_dir) - end - local custom_sounds_readme = read_txt_file("Custom Sounds Readme.txt") - while true do - gprint(custom_sounds_readme, 15, 15) - do_menu_function = false - wait() - local ret = nil - variable_step(function() - if menu_escape(K[1]) or menu_enter(K[1]) then - ret = {main_options, {idx}} - end - end) - if ret then - return unpack(ret) - end - end -end - -function main_show_custom_characters_readme(idx) - for _,current_character in ipairs(default_characters_ids) do - if not love.filesystem.getInfo("characters/"..prefix_of_ignored_dirs..current_character) then - print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") - gprint("Hold on. Copying an example folder to make this easier...\n\nThis may take a few seconds or maybe even a minute or two.\n\nDon't worry if the window goes inactive or \"not responding\"", 280, 280) - wait() - recursive_copy("characters/"..current_character, "characters/"..prefix_of_ignored_dirs..current_character) - end - end - - local custom_characters_readme = read_txt_file("Custom Characters Readme.txt") - while true do - gprint(custom_characters_readme, 15, 15) - do_menu_function = false - wait() - local ret = nil - variable_step(function() - if menu_escape(K[1]) or menu_enter(K[1]) then - ret = {main_options, {idx}} - end - end) - if ret then - return unpack(ret) - end - end -end - -function main_options(starting_idx) - local items, active_idx = {}, starting_idx or 1 - local k = K[1] - local selected, deselected_this_frame, adjust_active_value = false, false, false - local save_replays_publicly_choices = {"with my name", "anonymously", "not at all"} - local on_off_text = {[true]="On", [false]="Off"} - local name, version, vendor, device = love.graphics.getRendererInfo() - memory_before_options_menu = { config.assets_dir or default_assets_dir, - config.panels_dir_when_not_using_set_from_assets_folder or default_panels_dir, - config.sounds_dir or default_sounds_dir, - config.use_panels_from_assets_folder, - config.use_default_characters, - config.enable_analytics } - --make so we can get "anonymously" from save_replays_publicly_choices["anonymously"] - for k,v in ipairs(save_replays_publicly_choices) do - save_replays_publicly_choices[v] = v - end - - local function get_dir_set(set,path) - local raw_dir_list = love.filesystem.getDirectoryItems(path) - for k,v in ipairs(raw_dir_list) do - local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) - if love.filesystem.getInfo(path.."/"..v) and v ~= "Example folder structure" and start_of_v ~= prefix_of_ignored_dirs then - set[#set+1] = v - end - end - end - - local asset_sets = {} - get_dir_set(asset_sets,"assets") - local panel_sets = {} - get_dir_set(panel_sets,"panels") - local sound_sets = {} - get_dir_set(sound_sets,"sounds") - - print("asset_sets:") - for k,v in ipairs(asset_sets) do - print(v) - end - items = { - --options menu table reference: - --{[1]"Option Name", [2]current or default value, [3]type, [4]min or bool value or choices_table, - -- [5]max, [6]sound_source, [7]selectable, [8]next_func, [9]play_while selected} - {"Master Volume", config.master_volume or 100, "numeric", 0, 100, characters[config.character].musics.normal_music, true, nil, true}, - {"SFX Volume", config.SFX_volume or 100, "numeric", 0, 100, sounds.SFX.cur_move, true}, - {"Music Volume", config.music_volume or 100, "numeric", 0, 100, characters[config.character].musics.normal_music, true, nil, true}, - {"Vsync", on_off_text[config.vsync], "bool", false, nil, nil,false}, - {"Debug Mode", on_off_text[config.debug_mode or false], "bool", false, nil, nil,false}, - {"Save replays publicly", - save_replays_publicly_choices[config.save_replays_publicly] - or save_replays_publicly_choices["with my name"], - "multiple choice", save_replays_publicly_choices}, - {"Graphics set", config.assets_dir or default_assets_dir, "multiple choice", asset_sets}, - {"Panels set", config.panels_dir_when_not_using_set_from_assets_folder or default_panels_dir, "multiple choice", panel_sets}, - {"About custom graphics", "", "function", nil, nil, nil, nil, main_show_custom_graphics_readme}, - {"Sounds set", config.sounds_dir or default_sounds_dir, "multiple choice", sound_sets}, - {"About custom sounds", "", "function", nil, nil, nil, nil, main_show_custom_sounds_readme}, - {"Ready countdown", on_off_text[config.ready_countdown_1P or false], "bool", true, nil, nil,false}, - {"Show FPS", on_off_text[config.show_fps or false], "bool", true, nil, nil,false}, - {"Use panels from assets folder", on_off_text[config.use_panels_from_assets_folder], "bool", true, nil, nil,false}, - {"Use default characters", on_off_text[config.use_default_characters], "bool", true, nil, nil,false}, - {"Danger music change-back delay", on_off_text[config.danger_music_changeback_delay or false], "bool", false, nil, nil, false}, - {"About custom characters", "", "function", nil, nil, nil, nil, main_show_custom_characters_readme}, - {"Enable analytics", on_off_text[config.enable_analytics or false], "bool", false, nil, nil, false}, - {"Back", "", nil, nil, nil, nil, false, main_select_mode} - } - local function print_stuff() - gprint("graphics card: "..(device or "nil"), 100, 0) - local to_print, to_print2, arrow = "", "", "" - for i=1,#items do - if active_idx == i then - arrow = arrow .. ">" - else - arrow = arrow .. "\n" - end - to_print = to_print .. " " .. items[i][1] .. "\n" - to_print2 = to_print2 .. " " - if active_idx == i and selected then - to_print2 = to_print2 .. " < " - else - to_print2 = to_print2 .. " " - end - to_print2 = to_print2.. items[i][2] - if active_idx == i and selected then - to_print2 = to_print2 .. " >" - end - to_print2 = to_print2 .. "\n" - end - local x,y = unpack(main_menu_screen_pos) - x = x - 60 --options menu is 'lefter' than main_menu - gprint(arrow, x, y) - gprint(to_print, x, y) - gprint(to_print2, x, y) - end - local function adjust_left() - if items[active_idx][3] == "numeric" then - if items[active_idx][2] > items[active_idx][4] then --value > minimum - items[active_idx][2] = items[active_idx][2] - 1 - end - elseif items[active_idx][3] == "multiple choice" then - adjust_backwards = true - adjust_active_value = true - end - --the following is enough for "bool" - adjust_active_value = true - if items[active_idx][6] and not items[active_idx][9] then - --sound_source for this menu item exists and not play_while_selected - items[active_idx][6]:stop() - items[active_idx][6]:play() - end - end - local function adjust_right() - if items[active_idx][3] == "numeric" then - if items[active_idx][2] < items[active_idx][5] then --value < maximum - items[active_idx][2] = items[active_idx][2] + 1 - end - elseif items[active_idx][3] == "multiple choice" then - adjust_active_value = true - end - --the following is enough for "bool" - adjust_active_value = true - if items[active_idx][6] and not items[active_idx][9] then - --sound_source for this menu item exists and not play_while_selected - items[active_idx][6]:stop() - items[active_idx][6]:play() - end - end - local do_menu_function = false - while true do - print_stuff() - wait() - local ret = nil - variable_step(function() - if menu_up(K[1]) and not selected then - active_idx = wrap(1, active_idx-1, #items) - elseif menu_down(K[1]) and not selected then - active_idx = wrap(1, active_idx+1, #items) - elseif menu_left(K[1]) and (selected or not items[active_idx][7]) then --or not selectable - adjust_left() - elseif menu_right(K[1]) and (selected or not items[active_idx][7]) then --or not selectable - adjust_right() elseif menu_enter(K[1]) then - if items[active_idx][7] then --is selectable - selected = not selected - if not selected then - deselected_this_frame = true - adjust_active_value = true - end - elseif items[active_idx][3] == "bool" or items[active_idx][3] == "multiple choice" then - adjust_active_value = true - elseif items[active_idx][3] == "function" then - do_menu_function = true - elseif active_idx == #items then - ret = {exit_options_menu} + if input_menu.active_idx > #items - 1 --[['Set all' or 'back']] then + ret = {items[input_menu.active_idx][3], items[input_menu.active_idx][4]} end elseif menu_escape(K[1]) then - if selected then - selected = not selected - deselected_this_frame = true - elseif active_idx == #items then - ret = {exit_options_menu} + if input_menu.active_idx == #items then + ret = {items[input_menu.active_idx][3], items[input_menu.active_idx][4]} else - active_idx = #items - end - end - if adjust_active_value and not ret then - if items[active_idx][3] == "bool" then - if active_idx == 4 then - config.debug_mode = not config.debug_mode - items[active_idx][2] = on_off_text[config.debug_mode or false] - end - if items[active_idx][1] == "Ready countdown" then - config.ready_countdown_1P = not config.ready_countdown_1P - items[active_idx][2] = on_off_text[config.ready_countdown_1P] - elseif items[active_idx][1] == "Vsync" then - config.vsync = not config.vsync - items[active_idx][2] = on_off_text[config.vsync] - love.window.setVSync(config.vsync and 1 or 0) - elseif items[active_idx][1] == "Show FPS" then - config.show_fps = not config.show_fps - items[active_idx][2] = on_off_text[config.show_fps] - elseif items[active_idx][1] == "Use panels from assets folder" then - config.use_panels_from_assets_folder = not config.use_panels_from_assets_folder - items[active_idx][2] = on_off_text[config.use_panels_from_assets_folder] - elseif items[active_idx][1] == "Use default characters" then - config.use_default_characters = not config.use_default_characters - items[active_idx][2] = on_off_text[config.use_default_characters] - elseif items[active_idx][1] == "Danger music change-back delay" then - config.danger_music_changeback_delay = not config.danger_music_changeback_delay - items[active_idx][2] = on_off_text[config.danger_music_changeback_delay] - elseif items[active_idx][1] == "Enable analytics" then - config.enable_analytics = not config.enable_analytics - items[active_idx][2] = on_off_text[config.enable_analytics] - end - --add any other bool config updates here - elseif items[active_idx][3] == "numeric" then - if config.master_volume ~= items[1][2] then - config.master_volume = items[1][2] - love.audio.setVolume(config.master_volume/100) - end - if config.SFX_volume ~= items[2][2] then --SFX volume should be updated - config.SFX_volume = items[2][2] - items[2][6]:setVolume(config.SFX_volume/100) --do just the one sound effect until we deselect - end - if config.music_volume ~= items[3][2] then --music volume should be updated - config.music_volume = items[3][2] - items[3][6]:setVolume(config.music_volume/100) --do just the one music source until we deselect - end - --add any other numeric config updates here - elseif items[active_idx][3] == "multiple choice" then - local active_choice_num = 1 - --find the key for the currently selected choice - for k,v in ipairs(items[active_idx][4]) do - if v == items[active_idx][2] then - active_choice_num = k - end - end - -- the next line of code means - -- current_choice_num = choices[wrap(1, next_choice_num, last_choice_num)] - if adjust_backwards then - items[active_idx][2] = items[active_idx][4][wrap(1,active_choice_num - 1, #items[active_idx][4])] - adjust_backwards = nil - else - items[active_idx][2] = items[active_idx][4][wrap(1,active_choice_num + 1, #items[active_idx][4])] - end - if active_idx == 5 then - config.save_replays_publicly = items[active_idx][2] - elseif active_idx == 6 then - config.assets_dir = items[active_idx][2] - elseif active_idx == 7 then - config.panels_dir_when_not_using_set_from_assets_folder = items[active_idx][2] - elseif active_idx == 9 then - config.sounds_dir = items[active_idx][2] - end - --add any other multiple choice config updates here - end - adjust_active_value = false - end - if items[active_idx][3] == "function" and do_menu_function and not ret then - ret = {items[active_idx][8], {active_idx}} - end - if not ret and selected and items[active_idx][9] and items[active_idx][6] and not items[active_idx][6]:isPlaying() then - --if selected and play_while_selected and sound source exists and it isn't playing - items[active_idx][6]:play() - end - if not ret and deselected_this_frame then - if items[active_idx][6] then --sound_source for this menu item exists - items[active_idx][6]:stop() - love.audio.stop() - stop_the_music() + input_menu:set_active_idx(#items) end - deselected_this_frame = false end end) if ret then @@ -2562,63 +1470,11 @@ function main_options(starting_idx) end end -function exit_options_menu() - gprint("writing config to file...", unpack(main_menu_screen_pos)) - wait() - if config.use_panels_from_assets_folder then - config.panels_dir = config.assets_dir - else - config.panels_dir = config.panels_dir_when_not_using_set_from_assets_folder - end - write_conf_file() - - if config.assets_dir ~= memory_before_options_menu[1] - or config.use_default_characters ~= memory_before_options_menu[5] - or config.sounds_dir ~= memory_before_options_menu[3] then - gprint("reloading characters...", unpack(main_menu_screen_pos)) - wait() - characters_init() - end - - if config.assets_dir ~= memory_before_options_menu[1] - or config.use_default_characters ~= memory_before_options_menu[5] then - gprint("reloading graphics...", unpack(main_menu_screen_pos)) - wait() - graphics_init() - end - - if config.panels_dir_when_not_using_set_from_assets_folder ~= memory_before_options_menu[2] - or config.use_panels_from_assets_folder ~= memory_before_options_menu[4] - or config.assets_dir ~= memory_before_options_menu[1] then - gprint("reloading panels...", unpack(main_menu_screen_pos)) - wait() - panels_init() - end - - if config.sounds_dir ~= memory_before_options_menu[3] - or config.use_default_characters ~= memory_before_options_menu[5] then - gprint("reloading sounds...", unpack(main_menu_screen_pos)) - wait() - sound_init() - else - apply_config_volume() - end - - if config.enable_analytics ~= memory_before_options_menu[6] then - print("loading analytics...") - gprint("loading analytics...", unpack(main_menu_screen_pos)) - wait() - analytics_init() - end - - memory_before_options_menu = nil - return main_select_mode -end - function main_set_name() local name = config.name or "" + love.keyboard.setTextInput(true) while true do - local to_print = "Enter your name:\n"..name + local to_print = loc("op_enter_name").."\n"..name if (love.timer.getTime()*3) % 2 > 1 then to_print = to_print .. "|" end @@ -2629,7 +1485,7 @@ function main_set_name() if this_frame_keys["escape"] then ret = {main_select_mode} end - if this_frame_keys["return"] or this_frame_keys["kenter"] then + if menu_enter(K[1]) then config.name = name write_conf_file() ret = {main_select_mode} @@ -2647,54 +1503,92 @@ function main_set_name() end end) if ret then + love.keyboard.setTextInput(false) return unpack(ret) end end + end function main_music_test() - gprint("Loading required sounds... (this may take a while)", unpack(main_menu_screen_pos)) + gprint(loc("op_music_load"), unpack(main_menu_screen_pos)) wait() - -- loads music for characters that are not fully loaded + -- load music for characters/stages that are not fully loaded for _,character_id in ipairs(characters_ids_for_current_theme) do if not characters[character_id].fully_loaded then characters[character_id]:sound_init(true,false) end end + for _,stage_id in ipairs(stages_ids_for_current_theme) do + if not stages[stage_id].fully_loaded then -- we perform the same although currently no stage are being loaded at this point + stages[stage_id]:sound_init(true,false) + end + end local index = 1 local tracks = {} for _,character_id in ipairs(characters_ids_for_current_theme) do local character = characters[character_id] - tracks[#tracks+1] = { - name = character.display_name .. ": normal_music", - char = character_id, - type = "normal_music", - start = character.musics.normal_music_start or zero_sound, - loop = character.musics.normal_music - } + if character.musics.normal_music then + tracks[#tracks+1] = { + is_character = true, + name = character.display_name .. ": normal_music", + id = character_id, + type = "normal_music", + start = character.musics.normal_music_start or zero_sound, + loop = character.musics.normal_music + } + end if character.musics.danger_music then tracks[#tracks+1] = { + is_character = true, name = character.display_name .. ": danger_music", - char = character_id, + id = character_id, type = "danger_music", start = character.musics.danger_music_start or zero_sound, loop = character.musics.danger_music } end end + for _,stage_id in ipairs(stages_ids_for_current_theme) do + local stage = stages[stage_id] + if stage.musics.normal_music then + tracks[#tracks+1] = { + is_character = false, + name = stage.display_name .. ": normal_music", + id = stage_id, + type = "normal_music", + start = stage.musics.normal_music_start or zero_sound, + loop = stage.musics.normal_music + } + end + if stage.musics.danger_music then + tracks[#tracks+1] = { + is_character = false, + name = stage.display_name .. ": danger_music", + id = stage_id, + type = "danger_music", + start = stage.musics.danger_music_start or zero_sound, + loop = stage.musics.danger_music + } + end + end + + -- stop main music + love.audio.stop() + stop_the_music() -- initial song starts here - find_and_add_music(tracks[index].char, tracks[index].type) + find_and_add_music(tracks[index].is_character and characters[tracks[index].id].musics or stages[tracks[index].id].musics, tracks[index].type) while true do - tp = "Currently playing: " .. tracks[index].name - tp = tp .. (table.getn(currently_playing_tracks) == 1 and "\nPlaying the intro\n" or "\nPlaying main loop\n") + tp = loc("op_music_current") .. tracks[index].name + tp = tp .. (table.getn(currently_playing_tracks) == 1 and "\n"..loc("op_music_intro").."\n" or "\n"..loc("op_music_loop").."\n") min_time = math.huge for k, _ in pairs(music_t) do if k and k < min_time then min_time = k end end tp = tp .. string.format("%d", min_time - love.timer.getTime() ) - tp = tp .. "\n\n\n< and > to play navigate themes\nESC to leave" + tp = tp .. "\n\n\n"..loc("op_music_nav", "<", ">", "ESC") gprint(tp,unpack(main_menu_screen_pos)) wait() local ret = nil @@ -2707,16 +1601,21 @@ function main_music_test() if index > #tracks then index = 1 end if index < 1 then index = #tracks end if menu_left(K[1]) or menu_right(K[1]) then - find_and_add_music(tracks[index].char, tracks[index].type) + find_and_add_music(tracks[index].is_character and characters[tracks[index].id].musics or stages[tracks[index].id].musics, tracks[index].type) end - if menu_escape(K[1]) then - -- unloads music for characters that are not fully loaded (it has been loaded when entering this submenu) + if menu_escape(K[1]) then + -- unloads music for characters/stages that are not fully loaded (they have been loaded when entering this submenu) for _,character_id in ipairs(characters_ids_for_current_theme) do if not characters[character_id].fully_loaded then characters[character_id]:sound_uninit() end end + for _,stage_id in ipairs(stages_ids_for_current_theme) do + if not stages[stage_id].fully_loaded then + stages[stage_id]:sound_uninit() + end + end ret = {main_select_mode} end @@ -2743,49 +1642,30 @@ function main_dumb_transition(next_func, text, timemin, timemax, winnerSFX) end love.audio.stop() stop_the_music() + reset_filters() + game_is_paused = false winnerSFX = winnerSFX or nil if not SFX_mute then if winnerSFX ~= nil then winnerSFX:play() elseif SFX_GameOver_Play == 1 then - sounds.SFX.game_over:play() + themes[config.theme].sounds.game_over:play() end end SFX_GameOver_Play = 0 text = text or "" timemin = timemin or 0 - timemax = timemax or 3600 + timemax = timemax or -1 -- negative values means the user needs to press enter/escape to continue local t = 0 local k = K[1] + local font = love.graphics.getFont() while true do - -- for _,msg in ipairs(this_frame_messages) do - -- if next_func == main_character_select then - -- if msg.menu_state then - -- if currently_spectating then - -- if msg.menu_state.player_number == 1 then - -- global_my_state = msg.menu_state - -- elseif msg.menu_state.player_number == 2 then - -- global_op_state = msg.menu_state - -- end - -- else - -- global_op_state = msg.menu_state - -- end - -- end - -- if msg.win_counts then - -- update_win_counts(msg.win_counts) - -- end - -- if msg.rating_updates then - -- global_current_room_ratings = msg.ratings - -- end - -- end - -- --TODO: anything else we should be listening for during main_dumb_transition? - -- end - gprint(text, unpack(main_menu_screen_pos)) + gprint(text, (canvas_width-font:getWidth(text))/2, (canvas_height-font:getHeight(text))/2) wait() local ret = nil variable_step(function() - if t >= timemin and (t >=timemax or (menu_enter(k) or menu_escape(k))) then + if t >= timemin and ( (t >=timemax and timemax >= 0) or (menu_enter(k) or menu_escape(k))) then ret = {next_func} end t = t + 1 @@ -2808,6 +1688,13 @@ end function love.quit() love.audio.stop() - config.window_x, config.window_y, config.display = love.window.getPosition() + if love.window.getFullscreen() == true then + null, null, config.display = love.window.getPosition() + else + config.window_x, config.window_y, config.display = love.window.getPosition() + config.window_x = math.max(config.window_x, 0) + config.window_y = math.max(config.window_y, 30) --don't let 'y' be zero, or the title bar will not be visible on next launch. + end + config.fullscreen = love.window.getFullscreen() write_conf_file() end diff --git a/network.lua b/network.lua index 2f6c661c..0c74509c 100644 --- a/network.lua +++ b/network.lua @@ -86,7 +86,7 @@ local process_message = { --G=function(s) got_opponent = true end, H=function(s) got_H = true end, --N=function(s) error("Server told us to upgrade the game at burke.ro/panel.zip (for burke.ro server) or the TetrisAttackOnline Discord (for Jon's Server)") end, - N=function(s) error("PLEASE DOWNLOAD the latest version of the game from #welcome-getting-started at the TetrisAttackOnline Discord http://discord.panelattack.com") end, + N=function(s) error(loc("nt_ver_err")) end, P=function(s) P1.panel_buffer = P1.panel_buffer..s end, O=function(s) P2.panel_buffer = P2.panel_buffer..s end, U=function(s) P1.input_buffer = P1.input_buffer..s end, -- used for P1's inputs when spectating. @@ -97,28 +97,35 @@ local process_message = { J=function(s) local current_message = json.decode(s) this_frame_messages[#this_frame_messages+1] = current_message - print("JSON LOL "..s) if not current_message then - error("Error in network.lua process_message\nMessage: \""..(s or "nil").."\"\ncould not be decoded") + error(loc("nt_msg_err", (s or "nil"))) end if current_message.spectators then spectator_list = current_message.spectators spectators_string = spectator_list_string(current_message.spectators) + return end + + server_queue:push(current_message) end} -function network_init(ip) +function network_init(ip, network_port) TCP_sock = socket.tcp() TCP_sock:settimeout(7) - if not TCP_sock:connect(ip,49569) then --for official server + if not TCP_sock:connect(ip,network_port or 49569) then --for official server --if not TCP_sock:connect(ip,59569) then --for beta server - error("Failed to connect =(") + error(loc("nt_conn_timeout")) end TCP_sock:settimeout(0) got_H = false net_send("H"..VERSION) - assert(config.name and config.level and config.panels_dir and config.character and config.save_replays_publicly) - json_send({name=config.name, level=config.level, panels_dir=config.panels_dir, character=config.character, character_display_name=characters[config.character].display_name, save_replays_publicly = config.save_replays_publicly}) + assert(config.name and config.save_replays_publicly) + local sent_json = {name=config.name, level=config.level, panels_dir=config.panels, + character=config.character, character_is_random=( ( config.character==random_character_special_value or characters[config.character]:is_bundle()) and config.character or nil ), + stage=config.stage, ranked=config.ranked, stage_is_random=( (config.stage==random_stage_special_value or stages[config.stage]:is_bundle()) and config.stage or nil ), + save_replays_publicly=config.save_replays_publicly} + sent_json.character_display_name = sent_json.character_is_random and "" or characters[config.character].display_name + json_send(sent_json) end function connection_is_ready() @@ -134,7 +141,7 @@ function do_messages() while true do local typ, data = get_message() if typ then - if typ ~= "I" and typ ~= "U" then + if typ ~= "I" and typ ~= "U" and typ ~= "E" then print("Got message "..typ.." "..data) end process_message[typ](data) @@ -202,6 +209,27 @@ function make_local_gpanels(stack, prev_panels) end end +function Stack.handle_input_taunt(self) + local k = K[self.which] + local taunt_keys = { taunt_up = (keys[k.taunt_up] or this_frame_keys[k.taunt_up]), taunt_down = (keys[k.taunt_down] or this_frame_keys[k.taunt_down]) } + + if self.wait_for_not_taunting ~= nil then + if not taunt_keys[self.wait_for_not_taunting] then + self.wait_for_not_taunting = nil + else + return + end + end + + if taunt_keys.taunt_up and self:can_taunt() and #characters[self.character].sounds.taunt_ups > 0 then + self.taunt_up = math.random(#characters[self.character].sounds.taunt_ups) + if TCP_sock then json_send({taunt=true,type="taunt_ups",index=self.taunt_up}) end + elseif taunt_keys.taunt_down and self:can_taunt() and #characters[self.character].sounds.taunt_downs > 0 then + self.taunt_down = math.random(#characters[self.character].sounds.taunt_downs) + if TCP_sock then json_send({taunt=true,type="taunt_downs",index=self.taunt_down}) end + end +end + function Stack.send_controls(self) local k = K[self.which] local to_send = base64encode[ @@ -212,9 +240,13 @@ function Stack.send_controls(self) ((keys[k.down] or this_frame_keys[k.down]) and 4 or 0) + ((keys[k.left] or this_frame_keys[k.left]) and 2 or 0) + ((keys[k.right] or this_frame_keys[k.right]) and 1 or 0)+1] + if TCP_sock then net_send("I"..to_send) end + + self:handle_input_taunt() + local replay = replay[self.mode] if replay and replay.in_buf then replay.in_buf = replay.in_buf .. to_send diff --git a/options.lua b/options.lua new file mode 100644 index 00000000..d735041e --- /dev/null +++ b/options.lua @@ -0,0 +1,486 @@ +local options = {} + +local analytics = require("analytics") +local wait = coroutine.yield + +local memory_before_options_menu = nil + +local function main_show_custom_themes_readme(idx) + background = themes[config.theme].images.bg_readme + reset_filters() + + if not love.filesystem.getInfo("themes/"..prefix_of_ignored_dirs..default_theme_dir) then + print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") + gprint(loc("op_copy_files"), 280, 280) + wait() + recursive_copy("themes/"..default_theme_dir, "themes/"..prefix_of_ignored_dirs..default_theme_dir) + end + + local readme = read_txt_file("readme_themes.txt") + while true do + gprint(readme, 15, 15) + do_menu_function = false + wait() + local ret = nil + variable_step(function() + if menu_escape(K[1]) or menu_enter(K[1]) then + ret = {options.main, {idx}} + end + end) + if ret then + return unpack(ret) + end + end +end + +local function main_show_custom_stages_readme(idx) + background = themes[config.theme].images.bg_readme + reset_filters() + + local default_stages_list = love.filesystem.getDirectoryItems("default_data/stages") + for _,stage in ipairs(default_stages_list) do + if not love.filesystem.getInfo("stages/"..prefix_of_ignored_dirs..stage) then + print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") + gprint(loc("op_copy_files"), 280, 280) + wait() + recursive_copy("default_data/stages/"..stage, "stages/"..prefix_of_ignored_dirs..stage) + end + end + + local readme = read_txt_file("readme_stages.txt") + while true do + gprint(readme, 15, 15) + do_menu_function = false + wait() + local ret = nil + variable_step(function() + if menu_escape(K[1]) or menu_enter(K[1]) then + ret = {options.main, {idx}} + end + end) + if ret then + return unpack(ret) + end + end +end + +local function main_show_custom_characters_readme(idx) + background = themes[config.theme].images.bg_readme + reset_filters() + + local default_characters_list = love.filesystem.getDirectoryItems("default_data/characters") + for _,current_character in ipairs(default_characters_list) do + if not love.filesystem.getInfo("characters/"..prefix_of_ignored_dirs..current_character) then + print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") + gprint(loc("op_copy_files"), 280, 280) + wait() + recursive_copy("default_data/characters/"..current_character, "characters/"..prefix_of_ignored_dirs..current_character) + end + end + + local readme = read_txt_file("readme_characters.txt") + while true do + gprint(readme, 15, 15) + do_menu_function = false + wait() + local ret = nil + variable_step(function() + if menu_escape(K[1]) or menu_enter(K[1]) then + ret = {options.main, {idx}} + end + end) + if ret then + return unpack(ret) + end + end +end + +local function main_show_custom_panels_readme(idx) + background = themes[config.theme].images.bg_readme + reset_filters() + + -- add other defaults panels sets here so that anyone can update them if wanted + local default_panels_dirs = { default_panels_dir, "pdp_ta" } + + for _,panels_dir in ipairs(default_panels_dirs) do + if not love.filesystem.getInfo("panels/"..prefix_of_ignored_dirs..panels_dir) then + print("Hold on. Copying example folders to make this easier...\n This make take a few seconds.") + gprint(loc("op_copy_files"), 280, 280) + wait() + recursive_copy("panels/"..panels_dir, "panels/"..prefix_of_ignored_dirs..panels_dir) + end + end + + local readme = read_txt_file("readme_panels.txt") + while true do + gprint(readme, 15, 15) + do_menu_function = false + wait() + local ret = nil + variable_step(function() + if menu_escape(K[1]) or menu_enter(K[1]) then + ret = {options.main, {idx}} + end + end) + if ret then + return unpack(ret) + end + end +end + +local function exit_options_menu() + gprint("writing config to file...", unpack(main_menu_screen_pos)) + wait() + + local selected_theme = memory_before_options_menu.theme + memory_before_options_menu.theme = config.theme + config.theme = selected_theme + + write_conf_file() + + if config.theme ~= memory_before_options_menu.theme then + gprint(loc("op_reload_theme"), unpack(main_menu_screen_pos)) + wait() + stop_the_music() + theme_init() + if themes[config.theme].musics["main"] then + find_and_add_music(themes[config.theme].musics, "main") + end + end + + -- stages before characters since they are part of their loading + if config.theme ~= memory_before_options_menu.theme then + gprint(loc("op_reload_stages"), unpack(main_menu_screen_pos)) + wait() + stages_init() + end + + if config.theme ~= memory_before_options_menu.theme then + gprint(loc("op_reload_characters"), unpack(main_menu_screen_pos)) + wait() + characters_init() + end + + if config.enable_analytics ~= memory_before_options_menu.enable_analytics then + gprint(loc("op_reload_analytics"), unpack(main_menu_screen_pos)) + wait() + analytics.init() + end + + apply_config_volume() + + memory_before_options_menu = nil + normal_music_for_sound_option = nil + return main_select_mode +end + +function options.main(starting_idx) + background = themes[config.theme].images.bg_main + reset_filters() + + local items, active_idx = {}, starting_idx or 1 + local k = K[1] + local selected, deselected_this_frame, adjust_active_value = false, false, false + local save_replays_publicly_choices = {{"with my name", "op_replay_public_with_name"}, {"anonymously", "op_replay_public_anonymously"}, {"not at all", "op_replay_public_no"}} + local use_music_from_choices = {{"stage","op_only_stage"}, {"often_stage","op_often_stage"}, {"either","op_stage_characters"}, {"often_characters", "op_often_characters"}, {"characters","op_only_characters"}} + local on_off_text = {[true]={"On","op_on"}, [false]={"Off","op_off"}} + local language_choices = {} + for k,v in ipairs(localization:get_list_codes()) do + language_choices[k] = {v, "LANG"} + end + + memory_before_options_menu = { theme=config.theme,--this one is actually updated with the menu and change upon leaving, be careful! + enable_analytics=config.enable_analytics } + + for k,v in ipairs(save_replays_publicly_choices) do + save_replays_publicly_choices[v[1]] = v + end + for k,v in ipairs(use_music_from_choices) do + use_music_from_choices[v[1]] = v + end + + local function get_dir_set(set,path) + local raw_dir_list = love.filesystem.getDirectoryItems(path) + for k,v in ipairs(raw_dir_list) do + local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) + if love.filesystem.getInfo(path.."/"..v) and start_of_v ~= prefix_of_ignored_dirs then + set[#set+1] = {v, nil} + end + end + end + + local themes_set = {} + get_dir_set(themes_set,"themes") + + local normal_music_for_sound_option = nil + local function update_normal_music_for_sound_volume_option() + if config.use_music_from == "stage" then + local stage_id = config.stage + if stage_id == random_stage_special_value then + stage_id = uniformly(stages_ids_for_current_theme) + if stages[stage_id]:is_bundle() then -- may pick a bundle + stage_id = uniformly(stages[stage_id].sub_stages) + end + elseif stages[stage_id]:is_bundle() then -- may pick a bundle + stage_id = uniformly(stages[stage_id].sub_stages) + end + stage_loader_load(stage_id) + stage_loader_wait() + normal_music_for_sound_option = stages[stage_id].musics.normal_music + else + if config.character == random_character_special_value then + local random_id = uniformly(characters_ids_for_current_theme) + character_loader_load(random_id) + character_loader_wait() + normal_music_for_sound_option = characters[random_id].musics.normal_music + else + -- config.character should already be loaded! + normal_music_for_sound_option = characters[config.character].musics.normal_music + end + end + + if not normal_music_for_sound_option then -- avoid crashes! + normal_music_for_sound_option = zero_sound + end + end + update_normal_music_for_sound_volume_option() + items = { + --options menu table reference: + --{[1] option id, [2] loc key, [3]current or default value, [4]type, [5]min or bool value or choices_table (composed of {value, loc_key}), + -- [6]max, [7]sound_source, [8]selectable, [9]next_func, [10]play_while selected} + --[[1]]{"language", "op_language", {localization:get_language(), "LANG"}, "multiple choice", language_choices}, + --[[2]]{"master_volume", "op_vol", config.master_volume, "numeric", 0, 100, normal_music_for_sound_option, true, nil, true}, + --[[3]]{"sfx_volume", "op_vol_sfx", config.SFX_volume, "numeric", 0, 100, themes[config.theme].sounds.cur_move, true}, + --[[4]]{"music_volume", "op_vol_music", config.music_volume, "numeric", 0, 100, normal_music_for_sound_option, true, nil, true}, + --[[5]]{"vsync", "op_vsync", on_off_text[config.vsync], "bool", true, nil, nil,false}, + --[[6]]{"debug", "op_debug", on_off_text[config.debug_mode], "bool", false, nil, nil,false}, + --[[7]]{"replays", "op_replay_public", save_replays_publicly_choices[config.save_replays_publicly] + or save_replays_publicly_choices["with my name"], "multiple choice", save_replays_publicly_choices}, + --[[8]]{"theme", "op_theme", {config.theme, nil}, "multiple choice", themes_set}, + --[[9]]{"countdown", "op_countdown", on_off_text[config.ready_countdown_1P], "bool", true, nil, nil,false}, + --[[10]]{"fps", "op_fps", on_off_text[config.show_fps], "bool", true, nil, nil,false}, + --[[11]]{"infos", "op_ingame_infos", on_off_text[config.show_ingame_infos], "bool", true, nil, nil,false}, + --[[12]]{"music_delay", "op_music_delay", on_off_text[config.danger_music_changeback_delay], "bool", false, nil, nil, false}, + --[[13]]{"analytics", "op_analytics", on_off_text[config.enable_analytics], "bool", false, nil, nil, false}, + --[[14]]{"input_repeat_delay", "op_input_delay", config.input_repeat_delay, "numeric", 1, 50, nil, true}, + --[[15]]{"portrait_darkness", "op_portrait_darkness", config.portrait_darkness, "numeric", 0, 100, nil, true}, + --[[16]]{"popfx", "op_popfx", on_off_text[config.popfx], "bool", true, nil, nil, false}, + --[[17]]{"cardfx_scale", "op_cardfx_scale", config.cardfx_scale, "numeric", 1, 200, nil, true}, + --[[18]]{"music_from", "op_use_music_from", use_music_from_choices[config.use_music_from], "multiple choice", use_music_from_choices}, + --[[19]]{"about_themes", "op_about_themes", "", "function", nil, nil, nil, nil, main_show_custom_themes_readme}, + --[[20]]{"about_chars", "op_about_characters", "", "function", nil, nil, nil, nil, main_show_custom_characters_readme}, + --[[21]]{"about_stages", "op_about_stages", "", "function", nil, nil, nil, nil, main_show_custom_stages_readme}, + --[[22]]{"about_panels", "op_about_panels", "", "function", nil, nil, nil, nil, main_show_custom_panels_readme}, + --[[23]]{"back", "back", "", nil, nil, nil, nil, false, main_select_mode} + } + local function print_stuff() + local to_print, to_print2, arrow = "", "", "" + for i=1,#items do + if active_idx == i then + arrow = arrow .. ">" + else + arrow = arrow .. "\n" + end + to_print = to_print .. " " .. loc(items[i][2]) .. "\n" + to_print2 = to_print2 .. " " + if active_idx == i and selected then + to_print2 = to_print2 .. " < " + else + to_print2 = to_print2 .. " " + end + if items[i][4] == "multiple choice" or items[i][4] == "bool" then + to_print2 = to_print2.. (items[i][3][2] and loc(items[i][3][2]) or items[i][3][1]) + else + to_print2 = to_print2..items[i][3] + end + if active_idx == i and selected then + to_print2 = to_print2 .. " >" + end + to_print2 = to_print2 .. "\n" + end + local x,y = unpack(main_menu_screen_pos) + x = x - 60 --options menu is 'lefter' than main_menu + gprint(arrow, x, y) + gprint(to_print, x, y) + gprint(to_print2, x, y) + end + local function adjust_left() + if items[active_idx][4] == "numeric" then + if items[active_idx][3] > items[active_idx][5] then --value > minimum + items[active_idx][3] = items[active_idx][3] - 1 + end + elseif items[active_idx][4] == "multiple choice" then + adjust_backwards = true + adjust_active_value = true + end + --the following is enough for "bool" + adjust_active_value = true + if items[active_idx][7] and not items[active_idx][10] then + --sound_source for this menu item exists and not play_while_selected + items[active_idx][7]:stop() + items[active_idx][7]:play() + end + end + local function adjust_right() + if items[active_idx][4] == "numeric" then + if items[active_idx][3] < items[active_idx][6] then --value < maximum + items[active_idx][3] = items[active_idx][3] + 1 + end + elseif items[active_idx][4] == "multiple choice" then + adjust_active_value = true + end + --the following is enough for "bool" + adjust_active_value = true + if items[active_idx][7] and not items[active_idx][10] then + --sound_source for this menu item exists and not play_while_selected + items[active_idx][7]:stop() + items[active_idx][7]:play() + end + end + local do_menu_function = false + while true do + print_stuff() + wait() + local ret = nil + variable_step(function() + if menu_up(K[1]) and not selected then + active_idx = wrap(1, active_idx-1, #items) + elseif menu_down(K[1]) and not selected then + active_idx = wrap(1, active_idx+1, #items) + elseif menu_left(K[1]) and (selected or not items[active_idx][8]) then --or not selectable + adjust_left() + elseif menu_right(K[1]) and (selected or not items[active_idx][8]) then --or not selectable + adjust_right() + elseif menu_enter(K[1]) then + if items[active_idx][8] then --is selectable + selected = not selected + if not selected then + deselected_this_frame = true + adjust_active_value = true + end + elseif items[active_idx][4] == "bool" or items[active_idx][4] == "multiple choice" then + adjust_active_value = true + elseif items[active_idx][4] == "function" then + do_menu_function = true + elseif active_idx == #items then + ret = {exit_options_menu} + end + elseif menu_escape(K[1]) then + if selected then + selected = not selected + deselected_this_frame = true + elseif active_idx == #items then + ret = {exit_options_menu} + else + active_idx = #items + end + end + if adjust_active_value and not ret then + if items[active_idx][4] == "bool" then + if items[active_idx][1] == "debug" then + config.debug_mode = not config.debug_mode + items[active_idx][3] = on_off_text[config.debug_mode] + elseif items[active_idx][1] == "countdown" then + config.ready_countdown_1P = not config.ready_countdown_1P + items[active_idx][3] = on_off_text[config.ready_countdown_1P] + elseif items[active_idx][1] == "vsync" then + config.vsync = not config.vsync + items[active_idx][3] = on_off_text[config.vsync] + love.window.setVSync( config.vsync and 1 or 0 ) + elseif items[active_idx][1] == "fps" then + config.show_fps = not config.show_fps + items[active_idx][3] = on_off_text[config.show_fps] + elseif items[active_idx][1] == "debug" then + config.debug_mode = not config.debug_mode + items[active_idx][3] = on_off_text[config.debug_mode] + elseif items[active_idx][1] == "infos" then + config.show_ingame_infos = not config.show_ingame_infos + items[active_idx][3] = on_off_text[config.show_ingame_infos] + elseif items[active_idx][1] == "music_delay" then + config.danger_music_changeback_delay = not config.danger_music_changeback_delay + items[active_idx][3] = on_off_text[config.danger_music_changeback_delay] + elseif items[active_idx][1] == "analytics" then + config.enable_analytics = not config.enable_analytics + items[active_idx][3] = on_off_text[config.enable_analytics] + elseif items[active_idx][1] == "popfx" then + config.popfx = not config.popfx + items[active_idx][3] = on_off_text[config.popfx] + end + --add any other bool config updates here + elseif items[active_idx][4] == "numeric" then + if config.master_volume ~= items[2][3] then + config.master_volume = items[2][3] + love.audio.setVolume(config.master_volume/100) + end + if config.SFX_volume ~= items[3][3] then --SFX volume should be updated + config.SFX_volume = items[3][3] + items[3][7]:setVolume(config.SFX_volume/100) --do just the one sound effect until we deselect + end + if config.music_volume ~= items[4][3] then --music volume should be updated + config.music_volume = items[4][3] + items[4][7]:setVolume(config.music_volume/100) --do just the one music source until we deselect + end + if config.input_repeat_delay ~= items[14][3] then --music volume should be updated + config.input_repeat_delay = items[14][3] + end + if config.portrait_darkness ~= items[15][3] then + config.portrait_darkness = items[15][3] + end + if config.cardfx_scale ~= items[17][3] then + config.cardfx_scale = items[17][3] + end + --add any other numeric config updates here + elseif items[active_idx][4] == "multiple choice" then + local active_choice_num = 1 + --find the key for the currently selected choice + for k,v in ipairs(items[active_idx][5]) do + if v == items[active_idx][3] then + active_choice_num = k + end + end + -- the next line of code means + -- current_choice_num = choices[wrap(1, next_choice_num, last_choice_num)] + if adjust_backwards then + items[active_idx][3] = items[active_idx][5][wrap(1,active_choice_num - 1, #items[active_idx][5])] + adjust_backwards = nil + else + items[active_idx][3] = items[active_idx][5][wrap(1,active_choice_num + 1, #items[active_idx][5])] + end + if items[active_idx][1] == "replays" then + config.save_replays_publicly = items[active_idx][3][1] + -- don't change config.theme directly here as it is used while being in this menu! instead we change it upon leaving + elseif items[active_idx][1] == "theme" then + memory_before_options_menu.theme = items[active_idx][3][1] + elseif items[active_idx][1] == "music_from" then + config.use_music_from = items[active_idx][3][1] + update_normal_music_for_sound_volume_option() + items[2][7] = normal_music_for_sound_option + items[4][7] = normal_music_for_sound_option + elseif items[active_idx][1] == "language" then + localization:set_language(items[active_idx][3][1]) + end + --add any other multiple choice config updates here + end + adjust_active_value = false + end + if items[active_idx][4] == "function" and do_menu_function and not ret then + ret = {items[active_idx][9], {active_idx}} + end + if not ret and selected and items[active_idx][10] and items[active_idx][7] and not items[active_idx][7]:isPlaying() then + --if selected and play_while_selected and sound source exists and it isn't playing + items[active_idx][7]:play() + end + if not ret and deselected_this_frame then + if items[active_idx][7] then --sound_source for this menu item exists + items[active_idx][7]:stop() + end + deselected_this_frame = false + end + end) + if ret then + return unpack(ret) + end + end +end + +return options \ No newline at end of file diff --git a/panels.lua b/panels.lua new file mode 100644 index 00000000..70e59c31 --- /dev/null +++ b/panels.lua @@ -0,0 +1,99 @@ +require("graphics_util") + +Panels = class(function(self, full_path, folder_name) + self.path = full_path -- string | path to the panels folder content + self.id = folder_name -- string | id of the panel set, is also the name of its folder by default, may change in id_init + self.images = {} + end) + +function Panels.id_init(self) + local read_data = {} + local config_file, err = love.filesystem.newFile(self.path.."/config.json", "r") + if config_file then + local teh_json = config_file:read(config_file:getSize()) + for k,v in pairs(json.decode(teh_json)) do + read_data[k] = v + end + end + + if read_data.id then + self.id = read_data.id + return true + end + + return false +end + +local function add_panels_from_dir_rec(path) + local lfs = love.filesystem + local raw_dir_list = lfs.getDirectoryItems(path) + for i,v in ipairs(raw_dir_list) do + local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) + if start_of_v ~= prefix_of_ignored_dirs then + local current_path = path.."/"..v + if lfs.getInfo(current_path) and lfs.getInfo(current_path).type == "directory" then + -- call recursively: facade folder + add_panels_from_dir_rec(current_path) + + -- init stage: 'real' folder + local panel_set = Panels(current_path,v) + local success = panel_set:id_init() + + if success then + if panels[panel_set.id] ~= nil then + print(current_path.." has been ignored since a panel set with this id has already been found") + else + panels[panel_set.id] = panel_set + panels_ids[#panels_ids+1] = panel_set.id + -- print(current_path.." has been added to the character list!") + end + end + end + end + end +end + +function panels_init() + panels = {} -- holds all panels, all of them will be fully loaded + panels_ids = {} -- holds all panels ids + + add_panels_from_dir_rec("panels") + + -- fix config panel set if it's missing + if not config.panels or not panels[config.panels] then + config.panels = uniformly(panels_ids) + end + + for _,panel in pairs(panels) do + panel:load() + end +end + +function Panels.load(self) + print("loading panels "..self.id) + + local function load_panel_img(name) + local img = load_img_from_supported_extensions(self.path.."/"..name) + if not img then + img = load_img_from_supported_extensions("panels/"..default_panels_dir.."/"..name) + end + return img + end + + self.images.classic = {} + for i=1,8 do + self.images.classic[i] = {} + for j=1,7 do + self.images.classic[i][j] = load_panel_img("panel"..tostring(i)..tostring(j)) + end + end + self.images.classic[9] = {} + for j=1,7 do + self.images.classic[9][j] = load_panel_img("panel00") + end + + self.images.metals = { left = load_panel_img("metalend0"), + mid = load_panel_img("metalmid"), + right = load_panel_img("metalend1"), + flash = load_panel_img("garbageflash") } +end \ No newline at end of file diff --git a/panels/Panel Attack/config.json b/panels/Panel Attack/config.json new file mode 100644 index 00000000..66a9abbd --- /dev/null +++ b/panels/Panel Attack/config.json @@ -0,0 +1,3 @@ +{ + "id":"Panel Attack", +} \ No newline at end of file diff --git a/panels/Stock PdP_TA/garbageflash.png b/panels/Panel Attack/garbageflash.png similarity index 100% rename from panels/Stock PdP_TA/garbageflash.png rename to panels/Panel Attack/garbageflash.png diff --git a/panels/libre/metalend0.png b/panels/Panel Attack/metalend0.png similarity index 100% rename from panels/libre/metalend0.png rename to panels/Panel Attack/metalend0.png diff --git a/panels/libre/metalend1.png b/panels/Panel Attack/metalend1.png similarity index 100% rename from panels/libre/metalend1.png rename to panels/Panel Attack/metalend1.png diff --git a/panels/libre/metalmid.png b/panels/Panel Attack/metalmid.png similarity index 100% rename from panels/libre/metalmid.png rename to panels/Panel Attack/metalmid.png diff --git a/panels/libre/panel00.png b/panels/Panel Attack/panel00.png similarity index 100% rename from panels/libre/panel00.png rename to panels/Panel Attack/panel00.png diff --git a/panels/libre/panel11.png b/panels/Panel Attack/panel11.png similarity index 100% rename from panels/libre/panel11.png rename to panels/Panel Attack/panel11.png diff --git a/panels/libre/panel12.png b/panels/Panel Attack/panel12.png similarity index 100% rename from panels/libre/panel12.png rename to panels/Panel Attack/panel12.png diff --git a/panels/libre/panel13.png b/panels/Panel Attack/panel13.png similarity index 100% rename from panels/libre/panel13.png rename to panels/Panel Attack/panel13.png diff --git a/panels/libre/panel14.png b/panels/Panel Attack/panel14.png similarity index 100% rename from panels/libre/panel14.png rename to panels/Panel Attack/panel14.png diff --git a/panels/libre/panel15.png b/panels/Panel Attack/panel15.png similarity index 100% rename from panels/libre/panel15.png rename to panels/Panel Attack/panel15.png diff --git a/panels/libre/panel16.png b/panels/Panel Attack/panel16.png similarity index 100% rename from panels/libre/panel16.png rename to panels/Panel Attack/panel16.png diff --git a/panels/libre/panel17.png b/panels/Panel Attack/panel17.png similarity index 100% rename from panels/libre/panel17.png rename to panels/Panel Attack/panel17.png diff --git a/panels/libre/panel21.png b/panels/Panel Attack/panel21.png similarity index 100% rename from panels/libre/panel21.png rename to panels/Panel Attack/panel21.png diff --git a/panels/libre/panel22.png b/panels/Panel Attack/panel22.png similarity index 100% rename from panels/libre/panel22.png rename to panels/Panel Attack/panel22.png diff --git a/panels/libre/panel23.png b/panels/Panel Attack/panel23.png similarity index 100% rename from panels/libre/panel23.png rename to panels/Panel Attack/panel23.png diff --git a/panels/libre/panel24.png b/panels/Panel Attack/panel24.png similarity index 100% rename from panels/libre/panel24.png rename to panels/Panel Attack/panel24.png diff --git a/panels/libre/panel25.png b/panels/Panel Attack/panel25.png similarity index 100% rename from panels/libre/panel25.png rename to panels/Panel Attack/panel25.png diff --git a/panels/libre/panel26.png b/panels/Panel Attack/panel26.png similarity index 100% rename from panels/libre/panel26.png rename to panels/Panel Attack/panel26.png diff --git a/panels/libre/panel27.png b/panels/Panel Attack/panel27.png similarity index 100% rename from panels/libre/panel27.png rename to panels/Panel Attack/panel27.png diff --git a/panels/libre/panel31.png b/panels/Panel Attack/panel31.png similarity index 100% rename from panels/libre/panel31.png rename to panels/Panel Attack/panel31.png diff --git a/panels/libre/panel32.png b/panels/Panel Attack/panel32.png similarity index 100% rename from panels/libre/panel32.png rename to panels/Panel Attack/panel32.png diff --git a/panels/libre/panel33.png b/panels/Panel Attack/panel33.png similarity index 100% rename from panels/libre/panel33.png rename to panels/Panel Attack/panel33.png diff --git a/panels/libre/panel34.png b/panels/Panel Attack/panel34.png similarity index 100% rename from panels/libre/panel34.png rename to panels/Panel Attack/panel34.png diff --git a/panels/libre/panel35.png b/panels/Panel Attack/panel35.png similarity index 100% rename from panels/libre/panel35.png rename to panels/Panel Attack/panel35.png diff --git a/panels/libre/panel36.png b/panels/Panel Attack/panel36.png similarity index 100% rename from panels/libre/panel36.png rename to panels/Panel Attack/panel36.png diff --git a/panels/libre/panel37.png b/panels/Panel Attack/panel37.png similarity index 100% rename from panels/libre/panel37.png rename to panels/Panel Attack/panel37.png diff --git a/panels/libre/panel41.png b/panels/Panel Attack/panel41.png similarity index 100% rename from panels/libre/panel41.png rename to panels/Panel Attack/panel41.png diff --git a/panels/libre/panel42.png b/panels/Panel Attack/panel42.png similarity index 100% rename from panels/libre/panel42.png rename to panels/Panel Attack/panel42.png diff --git a/panels/libre/panel43.png b/panels/Panel Attack/panel43.png similarity index 100% rename from panels/libre/panel43.png rename to panels/Panel Attack/panel43.png diff --git a/panels/libre/panel44.png b/panels/Panel Attack/panel44.png similarity index 100% rename from panels/libre/panel44.png rename to panels/Panel Attack/panel44.png diff --git a/panels/libre/panel45.png b/panels/Panel Attack/panel45.png similarity index 100% rename from panels/libre/panel45.png rename to panels/Panel Attack/panel45.png diff --git a/panels/libre/panel46.png b/panels/Panel Attack/panel46.png similarity index 100% rename from panels/libre/panel46.png rename to panels/Panel Attack/panel46.png diff --git a/panels/libre/panel47.png b/panels/Panel Attack/panel47.png similarity index 100% rename from panels/libre/panel47.png rename to panels/Panel Attack/panel47.png diff --git a/panels/libre/panel51.png b/panels/Panel Attack/panel51.png similarity index 100% rename from panels/libre/panel51.png rename to panels/Panel Attack/panel51.png diff --git a/panels/libre/panel52.png b/panels/Panel Attack/panel52.png similarity index 100% rename from panels/libre/panel52.png rename to panels/Panel Attack/panel52.png diff --git a/panels/libre/panel53.png b/panels/Panel Attack/panel53.png similarity index 100% rename from panels/libre/panel53.png rename to panels/Panel Attack/panel53.png diff --git a/panels/libre/panel54.png b/panels/Panel Attack/panel54.png similarity index 100% rename from panels/libre/panel54.png rename to panels/Panel Attack/panel54.png diff --git a/panels/libre/panel55.png b/panels/Panel Attack/panel55.png similarity index 100% rename from panels/libre/panel55.png rename to panels/Panel Attack/panel55.png diff --git a/panels/libre/panel56.png b/panels/Panel Attack/panel56.png similarity index 100% rename from panels/libre/panel56.png rename to panels/Panel Attack/panel56.png diff --git a/panels/libre/panel57.png b/panels/Panel Attack/panel57.png similarity index 100% rename from panels/libre/panel57.png rename to panels/Panel Attack/panel57.png diff --git a/panels/libre/panel61.png b/panels/Panel Attack/panel61.png similarity index 100% rename from panels/libre/panel61.png rename to panels/Panel Attack/panel61.png diff --git a/panels/libre/panel62.png b/panels/Panel Attack/panel62.png similarity index 100% rename from panels/libre/panel62.png rename to panels/Panel Attack/panel62.png diff --git a/panels/libre/panel63.png b/panels/Panel Attack/panel63.png similarity index 100% rename from panels/libre/panel63.png rename to panels/Panel Attack/panel63.png diff --git a/panels/libre/panel64.png b/panels/Panel Attack/panel64.png similarity index 100% rename from panels/libre/panel64.png rename to panels/Panel Attack/panel64.png diff --git a/panels/libre/panel65.png b/panels/Panel Attack/panel65.png similarity index 100% rename from panels/libre/panel65.png rename to panels/Panel Attack/panel65.png diff --git a/panels/libre/panel66.png b/panels/Panel Attack/panel66.png similarity index 100% rename from panels/libre/panel66.png rename to panels/Panel Attack/panel66.png diff --git a/panels/libre/panel67.png b/panels/Panel Attack/panel67.png similarity index 100% rename from panels/libre/panel67.png rename to panels/Panel Attack/panel67.png diff --git a/panels/libre/panel71.png b/panels/Panel Attack/panel71.png similarity index 100% rename from panels/libre/panel71.png rename to panels/Panel Attack/panel71.png diff --git a/panels/libre/panel72.png b/panels/Panel Attack/panel72.png similarity index 100% rename from panels/libre/panel72.png rename to panels/Panel Attack/panel72.png diff --git a/panels/libre/panel73.png b/panels/Panel Attack/panel73.png similarity index 100% rename from panels/libre/panel73.png rename to panels/Panel Attack/panel73.png diff --git a/panels/libre/panel74.png b/panels/Panel Attack/panel74.png similarity index 100% rename from panels/libre/panel74.png rename to panels/Panel Attack/panel74.png diff --git a/panels/libre/panel75.png b/panels/Panel Attack/panel75.png similarity index 100% rename from panels/libre/panel75.png rename to panels/Panel Attack/panel75.png diff --git a/panels/libre/panel76.png b/panels/Panel Attack/panel76.png similarity index 100% rename from panels/libre/panel76.png rename to panels/Panel Attack/panel76.png diff --git a/panels/libre/panel77.png b/panels/Panel Attack/panel77.png similarity index 100% rename from panels/libre/panel77.png rename to panels/Panel Attack/panel77.png diff --git a/panels/libre/panel81.png b/panels/Panel Attack/panel81.png similarity index 100% rename from panels/libre/panel81.png rename to panels/Panel Attack/panel81.png diff --git a/panels/libre/panel82.png b/panels/Panel Attack/panel82.png similarity index 100% rename from panels/libre/panel82.png rename to panels/Panel Attack/panel82.png diff --git a/panels/libre/panel83.png b/panels/Panel Attack/panel83.png similarity index 100% rename from panels/libre/panel83.png rename to panels/Panel Attack/panel83.png diff --git a/panels/libre/panel84.png b/panels/Panel Attack/panel84.png similarity index 100% rename from panels/libre/panel84.png rename to panels/Panel Attack/panel84.png diff --git a/panels/libre/panel85.png b/panels/Panel Attack/panel85.png similarity index 100% rename from panels/libre/panel85.png rename to panels/Panel Attack/panel85.png diff --git a/panels/libre/panel86.png b/panels/Panel Attack/panel86.png similarity index 100% rename from panels/libre/panel86.png rename to panels/Panel Attack/panel86.png diff --git a/panels/libre/panel87.png b/panels/Panel Attack/panel87.png similarity index 100% rename from panels/libre/panel87.png rename to panels/Panel Attack/panel87.png diff --git a/panels/Panels HD - Basic/config.json b/panels/Panels HD - Basic/config.json new file mode 100644 index 00000000..0aa5506f --- /dev/null +++ b/panels/Panels HD - Basic/config.json @@ -0,0 +1,3 @@ +{ + "id":"panelhd_basic_mizunoketsuban", +} \ No newline at end of file diff --git a/panels/Panels HD - Basic/garbageflash.png b/panels/Panels HD - Basic/garbageflash.png new file mode 100644 index 00000000..4d5a79ba Binary files /dev/null and b/panels/Panels HD - Basic/garbageflash.png differ diff --git a/panels/Panels HD - Basic/metalend0.png b/panels/Panels HD - Basic/metalend0.png new file mode 100644 index 00000000..edae9700 Binary files /dev/null and b/panels/Panels HD - Basic/metalend0.png differ diff --git a/panels/Panels HD - Basic/metalend1.png b/panels/Panels HD - Basic/metalend1.png new file mode 100644 index 00000000..e3141f29 Binary files /dev/null and b/panels/Panels HD - Basic/metalend1.png differ diff --git a/panels/Panels HD - Basic/metalmid.png b/panels/Panels HD - Basic/metalmid.png new file mode 100644 index 00000000..3961bcc7 Binary files /dev/null and b/panels/Panels HD - Basic/metalmid.png differ diff --git a/panels/Panels HD - Basic/panel00.png b/panels/Panels HD - Basic/panel00.png new file mode 100644 index 00000000..2a8282ac Binary files /dev/null and b/panels/Panels HD - Basic/panel00.png differ diff --git a/panels/Panels HD - Basic/panel11.png b/panels/Panels HD - Basic/panel11.png new file mode 100644 index 00000000..9e78ea1a Binary files /dev/null and b/panels/Panels HD - Basic/panel11.png differ diff --git a/panels/Panels HD - Basic/panel12.png b/panels/Panels HD - Basic/panel12.png new file mode 100644 index 00000000..fc65439b Binary files /dev/null and b/panels/Panels HD - Basic/panel12.png differ diff --git a/panels/Panels HD - Basic/panel13.png b/panels/Panels HD - Basic/panel13.png new file mode 100644 index 00000000..c6d0d350 Binary files /dev/null and b/panels/Panels HD - Basic/panel13.png differ diff --git a/panels/Panels HD - Basic/panel14.png b/panels/Panels HD - Basic/panel14.png new file mode 100644 index 00000000..dd2119ba Binary files /dev/null and b/panels/Panels HD - Basic/panel14.png differ diff --git a/panels/Panels HD - Basic/panel15.png b/panels/Panels HD - Basic/panel15.png new file mode 100644 index 00000000..03a68734 Binary files /dev/null and b/panels/Panels HD - Basic/panel15.png differ diff --git a/panels/Panels HD - Basic/panel16.png b/panels/Panels HD - Basic/panel16.png new file mode 100644 index 00000000..14f2aefb Binary files /dev/null and b/panels/Panels HD - Basic/panel16.png differ diff --git a/panels/Panels HD - Basic/panel17.png b/panels/Panels HD - Basic/panel17.png new file mode 100644 index 00000000..314ae641 Binary files /dev/null and b/panels/Panels HD - Basic/panel17.png differ diff --git a/panels/Panels HD - Basic/panel21.png b/panels/Panels HD - Basic/panel21.png new file mode 100644 index 00000000..26ca3716 Binary files /dev/null and b/panels/Panels HD - Basic/panel21.png differ diff --git a/panels/Panels HD - Basic/panel22.png b/panels/Panels HD - Basic/panel22.png new file mode 100644 index 00000000..dac77339 Binary files /dev/null and b/panels/Panels HD - Basic/panel22.png differ diff --git a/panels/Panels HD - Basic/panel23.png b/panels/Panels HD - Basic/panel23.png new file mode 100644 index 00000000..b95e0a90 Binary files /dev/null and b/panels/Panels HD - Basic/panel23.png differ diff --git a/panels/Panels HD - Basic/panel24.png b/panels/Panels HD - Basic/panel24.png new file mode 100644 index 00000000..70aef994 Binary files /dev/null and b/panels/Panels HD - Basic/panel24.png differ diff --git a/panels/Panels HD - Basic/panel25.png b/panels/Panels HD - Basic/panel25.png new file mode 100644 index 00000000..dc4c18e4 Binary files /dev/null and b/panels/Panels HD - Basic/panel25.png differ diff --git a/panels/Panels HD - Basic/panel26.png b/panels/Panels HD - Basic/panel26.png new file mode 100644 index 00000000..d214c4bd Binary files /dev/null and b/panels/Panels HD - Basic/panel26.png differ diff --git a/panels/Panels HD - Basic/panel27.png b/panels/Panels HD - Basic/panel27.png new file mode 100644 index 00000000..8060919a Binary files /dev/null and b/panels/Panels HD - Basic/panel27.png differ diff --git a/panels/Panels HD - Basic/panel31.png b/panels/Panels HD - Basic/panel31.png new file mode 100644 index 00000000..c6a6a12b Binary files /dev/null and b/panels/Panels HD - Basic/panel31.png differ diff --git a/panels/Panels HD - Basic/panel32.png b/panels/Panels HD - Basic/panel32.png new file mode 100644 index 00000000..1ef1242a Binary files /dev/null and b/panels/Panels HD - Basic/panel32.png differ diff --git a/panels/Panels HD - Basic/panel33.png b/panels/Panels HD - Basic/panel33.png new file mode 100644 index 00000000..5f2659e1 Binary files /dev/null and b/panels/Panels HD - Basic/panel33.png differ diff --git a/panels/Panels HD - Basic/panel34.png b/panels/Panels HD - Basic/panel34.png new file mode 100644 index 00000000..6091d593 Binary files /dev/null and b/panels/Panels HD - Basic/panel34.png differ diff --git a/panels/Panels HD - Basic/panel35.png b/panels/Panels HD - Basic/panel35.png new file mode 100644 index 00000000..3a1e67a4 Binary files /dev/null and b/panels/Panels HD - Basic/panel35.png differ diff --git a/panels/Panels HD - Basic/panel36.png b/panels/Panels HD - Basic/panel36.png new file mode 100644 index 00000000..e9b477a4 Binary files /dev/null and b/panels/Panels HD - Basic/panel36.png differ diff --git a/panels/Panels HD - Basic/panel37.png b/panels/Panels HD - Basic/panel37.png new file mode 100644 index 00000000..c2a5c0a9 Binary files /dev/null and b/panels/Panels HD - Basic/panel37.png differ diff --git a/panels/Panels HD - Basic/panel41.png b/panels/Panels HD - Basic/panel41.png new file mode 100644 index 00000000..f92b0202 Binary files /dev/null and b/panels/Panels HD - Basic/panel41.png differ diff --git a/panels/Panels HD - Basic/panel42.png b/panels/Panels HD - Basic/panel42.png new file mode 100644 index 00000000..bf6a6d6f Binary files /dev/null and b/panels/Panels HD - Basic/panel42.png differ diff --git a/panels/Panels HD - Basic/panel43.png b/panels/Panels HD - Basic/panel43.png new file mode 100644 index 00000000..3d62af96 Binary files /dev/null and b/panels/Panels HD - Basic/panel43.png differ diff --git a/panels/Panels HD - Basic/panel44.png b/panels/Panels HD - Basic/panel44.png new file mode 100644 index 00000000..5bf739a1 Binary files /dev/null and b/panels/Panels HD - Basic/panel44.png differ diff --git a/panels/Panels HD - Basic/panel45.png b/panels/Panels HD - Basic/panel45.png new file mode 100644 index 00000000..203e74b5 Binary files /dev/null and b/panels/Panels HD - Basic/panel45.png differ diff --git a/panels/Panels HD - Basic/panel46.png b/panels/Panels HD - Basic/panel46.png new file mode 100644 index 00000000..45a2ab9a Binary files /dev/null and b/panels/Panels HD - Basic/panel46.png differ diff --git a/panels/Panels HD - Basic/panel47.png b/panels/Panels HD - Basic/panel47.png new file mode 100644 index 00000000..e8e46b8f Binary files /dev/null and b/panels/Panels HD - Basic/panel47.png differ diff --git a/panels/Panels HD - Basic/panel51.png b/panels/Panels HD - Basic/panel51.png new file mode 100644 index 00000000..ba6627b2 Binary files /dev/null and b/panels/Panels HD - Basic/panel51.png differ diff --git a/panels/Panels HD - Basic/panel52.png b/panels/Panels HD - Basic/panel52.png new file mode 100644 index 00000000..0911992c Binary files /dev/null and b/panels/Panels HD - Basic/panel52.png differ diff --git a/panels/Panels HD - Basic/panel53.png b/panels/Panels HD - Basic/panel53.png new file mode 100644 index 00000000..26c7507e Binary files /dev/null and b/panels/Panels HD - Basic/panel53.png differ diff --git a/panels/Panels HD - Basic/panel54.png b/panels/Panels HD - Basic/panel54.png new file mode 100644 index 00000000..be11ce80 Binary files /dev/null and b/panels/Panels HD - Basic/panel54.png differ diff --git a/panels/Panels HD - Basic/panel55.png b/panels/Panels HD - Basic/panel55.png new file mode 100644 index 00000000..4d14a703 Binary files /dev/null and b/panels/Panels HD - Basic/panel55.png differ diff --git a/panels/Panels HD - Basic/panel56.png b/panels/Panels HD - Basic/panel56.png new file mode 100644 index 00000000..33c825de Binary files /dev/null and b/panels/Panels HD - Basic/panel56.png differ diff --git a/panels/Panels HD - Basic/panel57.png b/panels/Panels HD - Basic/panel57.png new file mode 100644 index 00000000..02797ab6 Binary files /dev/null and b/panels/Panels HD - Basic/panel57.png differ diff --git a/panels/Panels HD - Basic/panel61.png b/panels/Panels HD - Basic/panel61.png new file mode 100644 index 00000000..df5c3a01 Binary files /dev/null and b/panels/Panels HD - Basic/panel61.png differ diff --git a/panels/Panels HD - Basic/panel62.png b/panels/Panels HD - Basic/panel62.png new file mode 100644 index 00000000..7a9a6697 Binary files /dev/null and b/panels/Panels HD - Basic/panel62.png differ diff --git a/panels/Panels HD - Basic/panel63.png b/panels/Panels HD - Basic/panel63.png new file mode 100644 index 00000000..5a7fa304 Binary files /dev/null and b/panels/Panels HD - Basic/panel63.png differ diff --git a/panels/Panels HD - Basic/panel64.png b/panels/Panels HD - Basic/panel64.png new file mode 100644 index 00000000..fc22e820 Binary files /dev/null and b/panels/Panels HD - Basic/panel64.png differ diff --git a/panels/Panels HD - Basic/panel65.png b/panels/Panels HD - Basic/panel65.png new file mode 100644 index 00000000..72df3bd5 Binary files /dev/null and b/panels/Panels HD - Basic/panel65.png differ diff --git a/panels/Panels HD - Basic/panel66.png b/panels/Panels HD - Basic/panel66.png new file mode 100644 index 00000000..5738f4da Binary files /dev/null and b/panels/Panels HD - Basic/panel66.png differ diff --git a/panels/Panels HD - Basic/panel67.png b/panels/Panels HD - Basic/panel67.png new file mode 100644 index 00000000..3aee9fb0 Binary files /dev/null and b/panels/Panels HD - Basic/panel67.png differ diff --git a/panels/Panels HD - Basic/panel71.png b/panels/Panels HD - Basic/panel71.png new file mode 100644 index 00000000..04e81bf7 Binary files /dev/null and b/panels/Panels HD - Basic/panel71.png differ diff --git a/panels/Panels HD - Basic/panel72.png b/panels/Panels HD - Basic/panel72.png new file mode 100644 index 00000000..4fa6eaaf Binary files /dev/null and b/panels/Panels HD - Basic/panel72.png differ diff --git a/panels/Panels HD - Basic/panel73.png b/panels/Panels HD - Basic/panel73.png new file mode 100644 index 00000000..32440492 Binary files /dev/null and b/panels/Panels HD - Basic/panel73.png differ diff --git a/panels/Panels HD - Basic/panel74.png b/panels/Panels HD - Basic/panel74.png new file mode 100644 index 00000000..865731ec Binary files /dev/null and b/panels/Panels HD - Basic/panel74.png differ diff --git a/panels/Panels HD - Basic/panel75.png b/panels/Panels HD - Basic/panel75.png new file mode 100644 index 00000000..d3bf31d3 Binary files /dev/null and b/panels/Panels HD - Basic/panel75.png differ diff --git a/panels/Panels HD - Basic/panel76.png b/panels/Panels HD - Basic/panel76.png new file mode 100644 index 00000000..d3960de4 Binary files /dev/null and b/panels/Panels HD - Basic/panel76.png differ diff --git a/panels/Panels HD - Basic/panel77.png b/panels/Panels HD - Basic/panel77.png new file mode 100644 index 00000000..2898778c Binary files /dev/null and b/panels/Panels HD - Basic/panel77.png differ diff --git a/panels/Panels HD - Basic/panel81.png b/panels/Panels HD - Basic/panel81.png new file mode 100644 index 00000000..0c33e34d Binary files /dev/null and b/panels/Panels HD - Basic/panel81.png differ diff --git a/panels/Panels HD - Basic/panel82.png b/panels/Panels HD - Basic/panel82.png new file mode 100644 index 00000000..859bf7e7 Binary files /dev/null and b/panels/Panels HD - Basic/panel82.png differ diff --git a/panels/Panels HD - Basic/panel83.png b/panels/Panels HD - Basic/panel83.png new file mode 100644 index 00000000..7028fac5 Binary files /dev/null and b/panels/Panels HD - Basic/panel83.png differ diff --git a/panels/Panels HD - Basic/panel84.png b/panels/Panels HD - Basic/panel84.png new file mode 100644 index 00000000..025fa236 Binary files /dev/null and b/panels/Panels HD - Basic/panel84.png differ diff --git a/panels/Panels HD - Basic/panel85.png b/panels/Panels HD - Basic/panel85.png new file mode 100644 index 00000000..5a76d417 Binary files /dev/null and b/panels/Panels HD - Basic/panel85.png differ diff --git a/panels/Panels HD - Basic/panel86.png b/panels/Panels HD - Basic/panel86.png new file mode 100644 index 00000000..ff8bc010 Binary files /dev/null and b/panels/Panels HD - Basic/panel86.png differ diff --git a/panels/Panels HD - Basic/panel87.png b/panels/Panels HD - Basic/panel87.png new file mode 100644 index 00000000..eb74c121 Binary files /dev/null and b/panels/Panels HD - Basic/panel87.png differ diff --git a/panels/pdp_ta/config.json b/panels/pdp_ta/config.json new file mode 100644 index 00000000..39335258 --- /dev/null +++ b/panels/pdp_ta/config.json @@ -0,0 +1,3 @@ +{ + "id":"pdp_ta_common", +} \ No newline at end of file diff --git a/panels/libre/garbageflash.png b/panels/pdp_ta/garbageflash.png similarity index 100% rename from panels/libre/garbageflash.png rename to panels/pdp_ta/garbageflash.png diff --git a/panels/Stock PdP_TA/metalend0.png b/panels/pdp_ta/metalend0.png similarity index 100% rename from panels/Stock PdP_TA/metalend0.png rename to panels/pdp_ta/metalend0.png diff --git a/panels/Stock PdP_TA/metalend1.png b/panels/pdp_ta/metalend1.png similarity index 100% rename from panels/Stock PdP_TA/metalend1.png rename to panels/pdp_ta/metalend1.png diff --git a/panels/Stock PdP_TA/metalmid.png b/panels/pdp_ta/metalmid.png similarity index 100% rename from panels/Stock PdP_TA/metalmid.png rename to panels/pdp_ta/metalmid.png diff --git a/panels/Stock PdP_TA/panel00.png b/panels/pdp_ta/panel00.png similarity index 100% rename from panels/Stock PdP_TA/panel00.png rename to panels/pdp_ta/panel00.png diff --git a/panels/Stock PdP_TA/panel11.png b/panels/pdp_ta/panel11.png similarity index 100% rename from panels/Stock PdP_TA/panel11.png rename to panels/pdp_ta/panel11.png diff --git a/panels/Stock PdP_TA/panel12.png b/panels/pdp_ta/panel12.png similarity index 100% rename from panels/Stock PdP_TA/panel12.png rename to panels/pdp_ta/panel12.png diff --git a/panels/Stock PdP_TA/panel13.png b/panels/pdp_ta/panel13.png similarity index 100% rename from panels/Stock PdP_TA/panel13.png rename to panels/pdp_ta/panel13.png diff --git a/panels/Stock PdP_TA/panel14.png b/panels/pdp_ta/panel14.png similarity index 100% rename from panels/Stock PdP_TA/panel14.png rename to panels/pdp_ta/panel14.png diff --git a/panels/Stock PdP_TA/panel15.png b/panels/pdp_ta/panel15.png similarity index 100% rename from panels/Stock PdP_TA/panel15.png rename to panels/pdp_ta/panel15.png diff --git a/panels/Stock PdP_TA/panel16.png b/panels/pdp_ta/panel16.png similarity index 100% rename from panels/Stock PdP_TA/panel16.png rename to panels/pdp_ta/panel16.png diff --git a/panels/Stock PdP_TA/panel17.png b/panels/pdp_ta/panel17.png similarity index 100% rename from panels/Stock PdP_TA/panel17.png rename to panels/pdp_ta/panel17.png diff --git a/panels/Stock PdP_TA/panel21.png b/panels/pdp_ta/panel21.png similarity index 100% rename from panels/Stock PdP_TA/panel21.png rename to panels/pdp_ta/panel21.png diff --git a/panels/Stock PdP_TA/panel22.png b/panels/pdp_ta/panel22.png similarity index 100% rename from panels/Stock PdP_TA/panel22.png rename to panels/pdp_ta/panel22.png diff --git a/panels/Stock PdP_TA/panel23.png b/panels/pdp_ta/panel23.png similarity index 100% rename from panels/Stock PdP_TA/panel23.png rename to panels/pdp_ta/panel23.png diff --git a/panels/Stock PdP_TA/panel24.png b/panels/pdp_ta/panel24.png similarity index 100% rename from panels/Stock PdP_TA/panel24.png rename to panels/pdp_ta/panel24.png diff --git a/panels/Stock PdP_TA/panel25.png b/panels/pdp_ta/panel25.png similarity index 100% rename from panels/Stock PdP_TA/panel25.png rename to panels/pdp_ta/panel25.png diff --git a/panels/Stock PdP_TA/panel26.png b/panels/pdp_ta/panel26.png similarity index 100% rename from panels/Stock PdP_TA/panel26.png rename to panels/pdp_ta/panel26.png diff --git a/panels/Stock PdP_TA/panel27.png b/panels/pdp_ta/panel27.png similarity index 100% rename from panels/Stock PdP_TA/panel27.png rename to panels/pdp_ta/panel27.png diff --git a/panels/Stock PdP_TA/panel31.png b/panels/pdp_ta/panel31.png similarity index 100% rename from panels/Stock PdP_TA/panel31.png rename to panels/pdp_ta/panel31.png diff --git a/panels/Stock PdP_TA/panel32.png b/panels/pdp_ta/panel32.png similarity index 100% rename from panels/Stock PdP_TA/panel32.png rename to panels/pdp_ta/panel32.png diff --git a/panels/Stock PdP_TA/panel33.png b/panels/pdp_ta/panel33.png similarity index 100% rename from panels/Stock PdP_TA/panel33.png rename to panels/pdp_ta/panel33.png diff --git a/panels/Stock PdP_TA/panel34.png b/panels/pdp_ta/panel34.png similarity index 100% rename from panels/Stock PdP_TA/panel34.png rename to panels/pdp_ta/panel34.png diff --git a/panels/Stock PdP_TA/panel35.png b/panels/pdp_ta/panel35.png similarity index 100% rename from panels/Stock PdP_TA/panel35.png rename to panels/pdp_ta/panel35.png diff --git a/panels/Stock PdP_TA/panel36.png b/panels/pdp_ta/panel36.png similarity index 100% rename from panels/Stock PdP_TA/panel36.png rename to panels/pdp_ta/panel36.png diff --git a/panels/Stock PdP_TA/panel37.png b/panels/pdp_ta/panel37.png similarity index 100% rename from panels/Stock PdP_TA/panel37.png rename to panels/pdp_ta/panel37.png diff --git a/panels/Stock PdP_TA/panel41.png b/panels/pdp_ta/panel41.png similarity index 100% rename from panels/Stock PdP_TA/panel41.png rename to panels/pdp_ta/panel41.png diff --git a/panels/Stock PdP_TA/panel42.png b/panels/pdp_ta/panel42.png similarity index 100% rename from panels/Stock PdP_TA/panel42.png rename to panels/pdp_ta/panel42.png diff --git a/panels/Stock PdP_TA/panel43.png b/panels/pdp_ta/panel43.png similarity index 100% rename from panels/Stock PdP_TA/panel43.png rename to panels/pdp_ta/panel43.png diff --git a/panels/Stock PdP_TA/panel44.png b/panels/pdp_ta/panel44.png similarity index 100% rename from panels/Stock PdP_TA/panel44.png rename to panels/pdp_ta/panel44.png diff --git a/panels/Stock PdP_TA/panel45.png b/panels/pdp_ta/panel45.png similarity index 100% rename from panels/Stock PdP_TA/panel45.png rename to panels/pdp_ta/panel45.png diff --git a/panels/Stock PdP_TA/panel46.png b/panels/pdp_ta/panel46.png similarity index 100% rename from panels/Stock PdP_TA/panel46.png rename to panels/pdp_ta/panel46.png diff --git a/panels/Stock PdP_TA/panel47.png b/panels/pdp_ta/panel47.png similarity index 100% rename from panels/Stock PdP_TA/panel47.png rename to panels/pdp_ta/panel47.png diff --git a/panels/Stock PdP_TA/panel51.png b/panels/pdp_ta/panel51.png similarity index 100% rename from panels/Stock PdP_TA/panel51.png rename to panels/pdp_ta/panel51.png diff --git a/panels/Stock PdP_TA/panel52.png b/panels/pdp_ta/panel52.png similarity index 100% rename from panels/Stock PdP_TA/panel52.png rename to panels/pdp_ta/panel52.png diff --git a/panels/Stock PdP_TA/panel53.png b/panels/pdp_ta/panel53.png similarity index 100% rename from panels/Stock PdP_TA/panel53.png rename to panels/pdp_ta/panel53.png diff --git a/panels/Stock PdP_TA/panel54.png b/panels/pdp_ta/panel54.png similarity index 100% rename from panels/Stock PdP_TA/panel54.png rename to panels/pdp_ta/panel54.png diff --git a/panels/Stock PdP_TA/panel55.png b/panels/pdp_ta/panel55.png similarity index 100% rename from panels/Stock PdP_TA/panel55.png rename to panels/pdp_ta/panel55.png diff --git a/panels/Stock PdP_TA/panel56.png b/panels/pdp_ta/panel56.png similarity index 100% rename from panels/Stock PdP_TA/panel56.png rename to panels/pdp_ta/panel56.png diff --git a/panels/Stock PdP_TA/panel57.png b/panels/pdp_ta/panel57.png similarity index 100% rename from panels/Stock PdP_TA/panel57.png rename to panels/pdp_ta/panel57.png diff --git a/panels/Stock PdP_TA/panel61.png b/panels/pdp_ta/panel61.png similarity index 100% rename from panels/Stock PdP_TA/panel61.png rename to panels/pdp_ta/panel61.png diff --git a/panels/Stock PdP_TA/panel62.png b/panels/pdp_ta/panel62.png similarity index 100% rename from panels/Stock PdP_TA/panel62.png rename to panels/pdp_ta/panel62.png diff --git a/panels/Stock PdP_TA/panel63.png b/panels/pdp_ta/panel63.png similarity index 100% rename from panels/Stock PdP_TA/panel63.png rename to panels/pdp_ta/panel63.png diff --git a/panels/Stock PdP_TA/panel64.png b/panels/pdp_ta/panel64.png similarity index 100% rename from panels/Stock PdP_TA/panel64.png rename to panels/pdp_ta/panel64.png diff --git a/panels/Stock PdP_TA/panel65.png b/panels/pdp_ta/panel65.png similarity index 100% rename from panels/Stock PdP_TA/panel65.png rename to panels/pdp_ta/panel65.png diff --git a/panels/Stock PdP_TA/panel66.png b/panels/pdp_ta/panel66.png similarity index 100% rename from panels/Stock PdP_TA/panel66.png rename to panels/pdp_ta/panel66.png diff --git a/panels/Stock PdP_TA/panel67.png b/panels/pdp_ta/panel67.png similarity index 100% rename from panels/Stock PdP_TA/panel67.png rename to panels/pdp_ta/panel67.png diff --git a/panels/Stock PdP_TA/panel71.png b/panels/pdp_ta/panel71.png similarity index 100% rename from panels/Stock PdP_TA/panel71.png rename to panels/pdp_ta/panel71.png diff --git a/panels/Stock PdP_TA/panel72.png b/panels/pdp_ta/panel72.png similarity index 100% rename from panels/Stock PdP_TA/panel72.png rename to panels/pdp_ta/panel72.png diff --git a/panels/Stock PdP_TA/panel73.png b/panels/pdp_ta/panel73.png similarity index 100% rename from panels/Stock PdP_TA/panel73.png rename to panels/pdp_ta/panel73.png diff --git a/panels/Stock PdP_TA/panel74.png b/panels/pdp_ta/panel74.png similarity index 100% rename from panels/Stock PdP_TA/panel74.png rename to panels/pdp_ta/panel74.png diff --git a/panels/Stock PdP_TA/panel75.png b/panels/pdp_ta/panel75.png similarity index 100% rename from panels/Stock PdP_TA/panel75.png rename to panels/pdp_ta/panel75.png diff --git a/panels/Stock PdP_TA/panel76.png b/panels/pdp_ta/panel76.png similarity index 100% rename from panels/Stock PdP_TA/panel76.png rename to panels/pdp_ta/panel76.png diff --git a/panels/Stock PdP_TA/panel77.png b/panels/pdp_ta/panel77.png similarity index 100% rename from panels/Stock PdP_TA/panel77.png rename to panels/pdp_ta/panel77.png diff --git a/panels/Stock PdP_TA/panel81.png b/panels/pdp_ta/panel81.png similarity index 100% rename from panels/Stock PdP_TA/panel81.png rename to panels/pdp_ta/panel81.png diff --git a/panels/Stock PdP_TA/panel82.png b/panels/pdp_ta/panel82.png similarity index 100% rename from panels/Stock PdP_TA/panel82.png rename to panels/pdp_ta/panel82.png diff --git a/panels/Stock PdP_TA/panel83.png b/panels/pdp_ta/panel83.png similarity index 100% rename from panels/Stock PdP_TA/panel83.png rename to panels/pdp_ta/panel83.png diff --git a/panels/Stock PdP_TA/panel84.png b/panels/pdp_ta/panel84.png similarity index 100% rename from panels/Stock PdP_TA/panel84.png rename to panels/pdp_ta/panel84.png diff --git a/panels/Stock PdP_TA/panel85.png b/panels/pdp_ta/panel85.png similarity index 100% rename from panels/Stock PdP_TA/panel85.png rename to panels/pdp_ta/panel85.png diff --git a/panels/Stock PdP_TA/panel86.png b/panels/pdp_ta/panel86.png similarity index 100% rename from panels/Stock PdP_TA/panel86.png rename to panels/pdp_ta/panel86.png diff --git a/panels/Stock PdP_TA/panel87.png b/panels/pdp_ta/panel87.png similarity index 100% rename from panels/Stock PdP_TA/panel87.png rename to panels/pdp_ta/panel87.png diff --git a/queue.lua b/queue.lua index 934c006e..a7ebac9d 100644 --- a/queue.lua +++ b/queue.lua @@ -12,7 +12,7 @@ end function Queue.pop(self) local first = self.first if first > self.last then - error("q is empty") + error("Queue is empty") end local ret = self[first] self[first] = nil diff --git a/readme_characters.txt b/readme_characters.txt new file mode 100644 index 00000000..0be5e408 --- /dev/null +++ b/readme_characters.txt @@ -0,0 +1,55 @@ +Adding/modding characters: step by step instructions (Windows example): + +1. Press the Windows key then type "%appdata%" without quotes and hit enter. +2. Look at the folders starting with "__" located in: %appdata%\Panel Attack\characters\ for a reference of where your files should go and how they should be named. + Folders starting with "__" will be ignored upon loading. You may choose to remove those "__" to mod the default characters +3. Create a folder with your character. The name of this folder is different from your character id and is kinda meaningless (see config.json below). +4. Place assets, sounds and json file in that folder with the proper names to add your data. Exhaustive list below. +5. Add/update characters.txt file in your theme folder. All characters will be loaded. This file specifies which ones get to be displayed in the lobby. + +~~~~ Exhaustive list of a character folder data! ~~~~ + +Note: non-optional data that are missing will automatically get replaced by default ones so everything is optional in that sense + +~~ [.json] ~~ + +- "config": this file holds data for the configuration of your character. The inside should look like that: + - name: display name of this character, this value will be displayed in the lobby and will also serve as a fallback when trying to match your opponent's character + - id: unique identifier of this character, this id should be specific (see note). [IF MISSING YOUR CHARACTER WILL BE IGNORED!] + - (stage): this specifies which stage to select along the character when 'super selecting' it + - (panels): this specifies which panels to select along the character when 'super selecting' it + - (sub_ids): identifiers for other characters, this allows you to define a character bundle that encompasses multiple other characters (picked at random) + - (visible): true/false, make it so the character is automatically hidden in the select screen (useful for character bundles) + - (chain_style): "classic"/"per_chain", change the way the chain sfx are being used (classic mode refers to PPL style while per_chain is puyo puyo) + - (flag): a flag may be displayed in the select screen based on that parameter, values are lowercase versions of the country codes from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2, not all flags are available. + - (popfx_style): The style of popfx to use, options: "burst"(default), "fade", "fadeburst" + - (popfx_rotation): If this option set to true, the burst popfx up, down, left, and right particles gonna rotate to point to the direciton they moving. Default is false. + - (burst_scale): The scale of the burst popfx, default is 1, 2 means twice the size, 0.5 half the size, etc. + - (fade_scale): The scale of the fade popfx, default is 1, 2 means twice the size, 0.5 half the size, etc. + +Note: providing a specific, long enough id is a very good idea so that people renaming your mods folders still get properly match with other users regarding your mods. e.g. "mycharacter_myname" + +~~ [.png, .jpg] ~~ + +- "topleft", "botleft", "topright", "botright", "top", "bot", "left", "right", "face", "pop", "doubleface", "filler1", "filler2", "flash": assets for garbages +- "portrait", "icon": display of your character ingame and in the lobby +- "burst": The image used for burst popfx. The image should be 9 equal sized frame in a row, first frame is the particle fly trough the screen, 2 to 9 frames are burst animation +- "fade": The image used for fade popfx. The image should be 9 equal sized frame in a row, first frame is the particle fly trough the screen (if popfx_style is fade), 2 to 9 frames are burst animation + +~~ [.mp3, .ogg, .wav, .it, .flac] optional sounds are in parenthesis ~~ + +- "combo" (,"combo2", "combo3"...): combo [selected at random if more than one] +- ("combo_echo", ("combo_echo2", "combo_echo3"...)): six metal blocks combo [selected at random if more than one] +- Chain: depending on the current chain length and the defined mode, the appopriate sound file will be played: + classic system: x2/3 plays "chain", x4 plays "chain2", x5 plays "chain_echo", x6+ plays "chain2_echo" + per_chain system: x2 "chain2"(, "chain2_2", "chain2_3"...), x3 "chain3"(, "chain3_2", "chain3_3"...), ..., x13 "chain13"(, "chain13_2", "chain13_3"...), x13+ "chain0"(, "chain0_2", "chain0_3"...) [selected at random if more than one] +- ("garbage_match" (,"garbage_match2", "garbage_match3"...)): played when clearing garbage [selected at random if more than one] +- ("garbage_land" (,"garbage_land2", "garbage_land3"...)): played when garbage lands on your side [selected at random if more than one] +- ("selection", ("selection2", "selection3"...)): upon selection [selected at random if more than one] +- ("win"(, "win2", "win3"...)): upon winning in 2P [selected at random if more than one] +- ("taunt_up"(, "taunt_up2", "taunt_up3"...)), ("taunt_down"(, "taunt_down2", "taunt_down3"...)): upon taunting with either inputs [selected at random if more than one] +- "normal_music": music that will be played while playing with this character if the option use_music_from's value is characters and your character gets picked +- ("danger_music"): music that will be used when a player is in danger (top of the screen) if the option use_music_from's value is characters and your character gets picked + +Note: providing just a "chain" or just a "combo" SFX is OK. It would get used for all combos and chains. +Note: if your music has an intro, cut it from the main music file, and name it "normal_music_start" or "danger_music_start" \ No newline at end of file diff --git a/readme_panels.txt b/readme_panels.txt new file mode 100644 index 00000000..20f6ba7f --- /dev/null +++ b/readme_panels.txt @@ -0,0 +1,34 @@ +Adding/modding panels: + +Step by step instructions (Windows example): + +1. Press the Windows key then type "%appdata%" without quotes and hit enter. + +2. Look at the folders starting with "__" located in: %appdata%\Panel Attack\panels\ for a reference of where your files should go and how they should be named. + +Note: folders starting with "__" will be ignored upon loading. You may choose to remove those "__" to mod the default panels + +3. Create a folder with your panels. The name of this folder is different from your panels id and is kinda meaningless (see config.json below). + +4. Place assets and json file in that folder with the proper names to add your data. Exhaustive list below. + +Note: all panels will be loaded. + +~~~~ Exhaustive list of a panels folder data! ~~~~ + +Note: non-optional data that are missing will automatically get replaced by default ones so they are kinda optional in that sense + +~~ [.json] ~~ + +- "config": this file holds data for the configuration of your panel set. The inside should look like that: + - id: unique identifier of this panel set, this id should be specific (see note). [IF MISSING YOUR PANELS WILL BE IGNORED!] + +Note: providing a specific, long enough id is a very good idea so that people renaming your mods folders still get properly match with other users regarding your mods. e.g. "mypanels_myname" + +~~ [.png, .jpg] ~~ + +- "garbageflash", "metalend0", "metalend1", "metalmid": assets for garbage metals panels: those will be displayed on your opponent side when sending garbage +- "panel11", ..., "panel17", "panel21", ..., "panel27", ... , "panel61", ..., "panel67": 'classic' panels, the sixth one is used above level 8 only +- "panel71", ..., "panel77": seventh panel only used in puzzles +- "panel00": used in puzzle (represents a to-be-ignored panel) and for debug purpose +- "panel81", ..., "panel87": metal panel \ No newline at end of file diff --git a/Custom Puzzles Readme.txt b/readme_puzzles.txt similarity index 100% rename from Custom Puzzles Readme.txt rename to readme_puzzles.txt diff --git a/readme_stages.txt b/readme_stages.txt new file mode 100644 index 00000000..196c16fa --- /dev/null +++ b/readme_stages.txt @@ -0,0 +1,46 @@ +Adding/modding stages: + +Step by step instructions (Windows example): + +1. Press the Windows key then type "%appdata%" without quotes and hit enter. + +2. See the folders located in: %appdata%\Panel Attack\stages\ for a reference of where your assets should go and what files should be named. + +Note: folders starting with "__" will be ignored upon loading. You may choose to remove those "__" to mod/override default stages. + +Note: inner folders are also supported. + +3. Create a folder with your stage. The name of this folder is different from your stage id and is kinda meaningless (see config.json below). + +Note: while playing online, stages will be looked up by id, meaning you'll get to see your opponent's stages as long as you have one with the same id + +4. Place assets, sounds and json file in that folder with the proper names to add your data. Exhaustive list below. + +5. Add/update stages.txt file in the theme folder. All stages will be loaded. This file specifies which ones get to be displayed in the select screen. + +~~~~ Exhaustive list of a stage folder data! ~~~~ + +Note: non-optional data that are missing will automatically get replaced by default ones so they are kinda optional in that sense + +~~ [.json] ~~ + +- "config": this file holds data for the configuration of your stage. The inside should look like that: + - name: display name of this stage, this value will be displayed in the select screen + - id: unique identifier of this stage, this id should be specific (see note). [IF MISSING YOUR STAGE WILL BE IGNORED!] + - (sub_ids): identifiers for other stages, this allows you to define a stage bundle that encompasses multiple other stages (picked at random) + - (visible): true/false, make it so the stage is automatically hidden in the select screen (useful for stage bundles) + +Note: providing a specific, long enough id is a very good idea so that people renaming your mods folders still get properly match with other users regarding your mods +e.g. "mystage_myname" + +~~ [.png, .jpg] ~~ + +- "background": background for your stage, to be displayed while playing, should be 1280x720 px +- "thumbnail": thumbnail, to be displayed in the select screen, should be 80x45 px + +~~ [.mp3, .ogg, .wav, .it, .flac] optional sounds are in parenthesis ~~ + +- "normal_music": music that will be played while playing on this stage if the option use_music_from's value is stage +- ("danger_music"): music that will be used when a player is in danger (top of the screen) if the option use_music_from's value is stage + +Note: if your music has an intro, cut it from the main music file, and name it "normal_music_start" or "danger_music_start" \ No newline at end of file diff --git a/readme_themes.txt b/readme_themes.txt new file mode 100644 index 00000000..ef458130 --- /dev/null +++ b/readme_themes.txt @@ -0,0 +1,46 @@ +Adding/modding themes: + +Step by step instructions (Windows example): + +1. Press the Windows key then type "%appdata%" without quotes and hit enter. + +2. See the folder located in: %appdata%\Panel Attack\themes\__Panel Attack for a reference of where your files should go and how they should be named. + +Note: folders starting with "__" will be ignored upon loading. You may choose to remove those "__" to mod default themes + +3. Create a folder with your theùe. The name of the folder will be the id of your theme. + +4. Place assets, sounds and txt files in that folder with the proper names to add your data. Exhaustive list below. + +~~~~ Exhaustive list of a theme folder data! ~~~~ + +Note: non-optional data that are missing will automatically get replaced by default ones so they are kinda optional in that sense + +~~ [.txt] ~~ + +- "characters": list of the characters to be displayed in the select screen +- "stages": list of the stages to be displayed in the select screen + +~~ [.png, .jpg] ~~ + +- "background/main", "background/select_screen", "background/readme": backgrounds used in the menus +- ("background/bg_overlay"), ("background/fg_overlay"): overlays: the first one is on top of the stage's background while the other one is up front +- "pause": overlay during the pause +- "chain/chain00", "chain/chain02", ... "chain/chain19", "combo/combo04", ..., "combo/combo66": chains and combo counter +- "flags/": flags to be displayed in the select screen (based on the character's specified flags). Values are mostly the country codes from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2, not all flags are available +- "1", "2", "3": countdown +- "p1", "p1_cursor", "p1_select_screen_cursor1", "p1_select_screen_cursor2": cursors for player 1, change p1 by p2 for those of player 2 +- "ready", "loading", "super": displayed when a player is ready, loading, or super selecting something in the select screen +- "frame", "wall": layout ingame +- "random_stage", "random_character": thumbnail and icon for random stage and random character + +~~ [.mp3, .ogg, .wav, .it, .flac] optional sounds are in parenthesis ~~ + +- "sfx/countdown", "sfx/go", "sfx/move", "sfx/swap", "sfx/land", "sfx/gameover": game sfx +- "sfx/fanfare1", "sfx/fanfare2", "sfx/fanfare3": fanfare +- "sfx/thud_1", "sfx/thud_2", "sfx/thud_3": garbage thuds +- "sfx/menu_move", "sfx/menu_validate", "sfx/menu_cancel": menu +- "sfx/notification": will play upon receiving a request or a request's answer while playing online +- "sfx/pop1-1", "sfx/pop1-2", ..., "sfx/pop1-10", "sfx/pop2-1", ..., "sfx/pop2-10", ..., "sfx/pop4-10": panel pops +- ("music/main", ("music/main_start")), ("music/select_screen", ("music/select_screen_start")): musics that will be used in those menus, "main" will be used as fallback if "select_screen" is missing. +"_start"s are played before the normal versions, once. diff --git a/replay_browser.lua b/replay_browser.lua new file mode 100644 index 00000000..9e9ea783 --- /dev/null +++ b/replay_browser.lua @@ -0,0 +1,218 @@ +local replay_browser = {} + +replay_browser.selection = nil +replay_browser.base_path = "replays" +replay_browser.current_path = "/" +replay_browser.path_contents = {} +replay_browser.filename = nil +replay_browser.state = "browser" + +replay_browser.menu_x = 400 +replay_browser.menu_y = 280 +replay_browser.menu_h = 14 +replay_browser.menu_cursor_offset = 16 + +replay_browser.cursor_pos = 0 + +replay_browser.replay_id_top = 0 + +function replay_browser.main() + + local function replay_browser_menu() + if (replay_browser.replay_id_top == 0) then + if replay_browser.current_path ~= "/" then + gprint("< " .. loc("rp_browser_up") .. " >", replay_browser.menu_x, replay_browser.menu_y) + else + gprint("< " .. loc("rp_browser_root") .. " >", replay_browser.menu_x, replay_browser.menu_y) + end + else + gprint("^ " .. loc("rp_browser_more") .. " ^", replay_browser.menu_x, replay_browser.menu_y) + end + + for i,p in pairs(replay_browser.path_contents) do + if (i > replay_browser.replay_id_top) and (i <= replay_browser.replay_id_top + 20) then + gprint(p, replay_browser.menu_x, replay_browser.menu_y + (i - replay_browser.replay_id_top) * replay_browser.menu_h) + end + end + + if #replay_browser.path_contents > replay_browser.replay_id_top + 20 then + gprint("v " .. loc("rp_browser_more") .. " v", replay_browser.menu_x, replay_browser.menu_y + 21 * replay_browser.menu_h) + end + + gprint(">", replay_browser.menu_x - replay_browser.menu_cursor_offset + math.sin(love.timer.getTime() * 8) * 5, replay_browser.menu_y + (replay_browser.cursor_pos - replay_browser.replay_id_top) * replay_browser.menu_h) + end + + local function replay_browser_cursor(move) + replay_browser.cursor_pos = wrap(0, replay_browser.cursor_pos + move, #replay_browser.path_contents) + if replay_browser.cursor_pos <= replay_browser.replay_id_top then + replay_browser.replay_id_top = math.max(replay_browser.cursor_pos, 1) - 1 + end + if replay_browser.replay_id_top < replay_browser.cursor_pos - 20 then + replay_browser.replay_id_top = replay_browser.cursor_pos - 20 + end + end + + local function replay_browser_update(new_path) + if new_path then + replay_browser.cursor_pos = 0 + replay_browser.replay_id_top = 0 + if new_path == "" then + new_path = "/" + end + replay_browser.current_path = new_path + end + replay_browser.path_contents = get_directory_contents(replay_browser.base_path .. replay_browser.current_path) + end + + local function replay_browser_go_up() + replay_browser_update(replay_browser.current_path:gsub("(.*/).*/$","%1")) + end + + + local function replay_browser_load_details(path) + + replay_browser.filename = path + local file, error_msg = love.filesystem.read(replay_browser.filename) + + if file == nil then + print(loc("rp_browser_error_loading", error_msg)) + return false + end + + replay = json.decode(file) + if type(replay.in_buf) == "table" then + replay.in_buf = table.concat(replay.in_buf) + end + return true + + end + + + local function replay_browser_select() + if replay_browser.cursor_pos == 0 then + replay_browser_go_up() + + else + replay_browser.selection = replay_browser.base_path .. replay_browser.current_path .. replay_browser.path_contents[replay_browser.cursor_pos] + local file_info = love.filesystem.getInfo(replay_browser.selection) + if file_info then + if file_info.type == "file" then + return replay_browser_load_details(replay_browser.selection) + elseif file_info.type == "directory" then + replay_browser_update(replay_browser.current_path .. replay_browser.path_contents[replay_browser.cursor_pos] .."/") + else + print(loc("rp_browser_error_unknown_filetype", file_info.type, replay_browser.selection)) + end + else + print(loc("rp_browser_error_file_not_found", replay_browser.selection)) + end + end + end + + replay_browser.state = "browser" + replay_browser_update() + + coroutine.yield() + + while true do + local ret = nil + + + if replay_browser.state == "browser" then + + gprint(loc("rp_browser_header"), replay_browser.menu_x + 170, replay_browser.menu_y - 40) + gprint(loc("rp_browser_current_dir", replay_browser.base_path .. replay_browser.current_path), replay_browser.menu_x, replay_browser.menu_y - 40 + replay_browser.menu_h) + replay_browser_menu() + + variable_step(function() + local k = K[1] + if menu_escape(k) then + ret = {main_select_mode} + elseif menu_enter(k) then + if replay_browser_select() then + replay_browser.state = "info" + end + elseif menu_backspace(k) then + replay_browser_go_up() + else + if menu_up(k) then + replay_browser_cursor(-1) + end + if menu_down(k) then + replay_browser_cursor(1) + end + end + end) + + elseif replay_browser.state == "info" then + local next_func = nil + + gprint(loc("rp_browser_info_header"), replay_browser.menu_x + 170, replay_browser.menu_y - 40) + gprint(replay_browser.filename, replay_browser.menu_x - 150, replay_browser.menu_y - 40 + replay_browser.menu_h) + + if replay.vs then + gprint(loc("rp_browser_info_2p_vs"), replay_browser.menu_x + 220, replay_browser.menu_y + 20) + + gprint(loc("rp_browser_info_1p"), replay_browser.menu_x, replay_browser.menu_y + 50) + gprint(loc("rp_browser_info_name", replay.vs.P1_name), replay_browser.menu_x, replay_browser.menu_y + 65) + gprint(loc("rp_browser_info_level", replay.vs.P1_level), replay_browser.menu_x, replay_browser.menu_y + 80) + gprint(loc("rp_browser_info_character", replay.vs.P1_char), replay_browser.menu_x, replay_browser.menu_y + 95) + + gprint(loc("rp_browser_info_2p"), replay_browser.menu_x + 300, replay_browser.menu_y + 50) + gprint(loc("rp_browser_info_name", replay.vs.P2_name), replay_browser.menu_x + 300, replay_browser.menu_y + 65) + gprint(loc("rp_browser_info_level", replay.vs.P2_level), replay_browser.menu_x + 300, replay_browser.menu_y + 80) + gprint(loc("rp_browser_info_character", replay.vs.P2_char), replay_browser.menu_x + 300, replay_browser.menu_y + 95) + + if replay.vs.ranked then + gprint(loc("rp_browser_info_ranked"), replay_browser.menu_x + 200, replay_browser.menu_y + 120) + end + + next_func = main_replay_vs + + elseif replay.endless then + gprint(loc("rp_browser_info_endless"), replay_browser.menu_x + 220, replay_browser.menu_y + 20) + + gprint(loc("rp_browser_info_speed", replay.endless.speed), replay_browser.menu_x + 150, replay_browser.menu_y + 50) + gprint(loc("rp_browser_info_difficulty", replay.endless.difficulty), replay_browser.menu_x + 150, replay_browser.menu_y + 65) + + next_func = main_replay_endless + + elseif replay.puzzle then + gprint(loc("rp_browser_info_puzzle"), replay_browser.menu_x + 220, replay_browser.menu_y + 20) + + gprint(loc("rp_browser_no_info"), replay_browser.menu_x + 150, replay_browser.menu_y + 50) + + next_func = main_replay_puzzle + + else + gprint(loc("rp_browser_error_unknown_replay_type"), replay_browser.menu_x + 220, replay_browser.menu_y + 20) + + end + + gprint(loc("rp_browser_watch"), replay_browser.menu_x + 75, replay_browser.menu_y + 150) + + variable_step(function() + local k = K[1] + if menu_backspace(k) or menu_escape(k) then + replay_browser.state = "browser" + elseif menu_enter(k) then + if next_func then + ret = {next_func} + end + end + end) + + end + + + if ret then + return unpack(ret) + end + + coroutine.yield() + + end + +end + +return replay_browser diff --git a/save.lua b/save.lua index 3891d87a..aa9fe96f 100644 --- a/save.lua +++ b/save.lua @@ -49,60 +49,71 @@ function write_conf_file() pcall(function() file:close() end) end -function has_any_custom_character() - local function belong_to_characters_ids(character_id) - for _,v in pairs(default_characters_ids) do - if v == character_id then - return true - end - end - return false - end - - local raw_dir_list = love.filesystem.getDirectoryItems("characters") - for _,v in ipairs(raw_dir_list) do - local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) - if start_of_v ~= prefix_of_ignored_dirs and not belong_to_characters_ids(v) then - return true - end - end - return false -end +local use_music_from_values = { stage=true, often_stage=true, either=true, often_characters=true, characters=true } +local save_replays_values = { ["with my name"]=true, anonymously=true, ["not at all"]=true } function read_conf_file() pcall(function() + -- config current values are defined in globals.lua, + -- we consider those values are currently in config + local file = love.filesystem.newFile("conf.json") file:open("r") + local read_data = {} local teh_json = file:read(file:getSize()) for k,v in pairs(json.decode(teh_json)) do - config[k] = v - end - if love.filesystem.getInfo("assets/"..config.assets_dir) == nil then - config.assets_dir = default_assets_dir + read_data[k] = v end - if love.filesystem.getInfo("panels/"..config.panels_dir_when_not_using_set_from_assets_folder) == nil then - config.panels_dir_when_not_using_set_from_assets_folder = default_panels_dir - end - if love.filesystem.getInfo("sounds/"..config.sounds_dir) == nil then - config.sounds_dir = default_sounds_dir - end - if config.use_panels_from_assets_folder == nil then - config.use_panels_from_assets_folder = true + + -- do stuff using read_data.version for retrocompatibility here + + if type(read_data.theme) == "string" and love.filesystem.getInfo("themes/"..read_data.theme) then + config.theme = read_data.theme end - if config.use_panels_from_assets_folder then - config.panels_dir = config.assets_dir - else - config.panels_dir = config.panels_dir_when_not_using_set_from_assets_folder + + -- language_code, panels, character and stage are patched later on by their own subsystems, we store their values in config for now! + if type(read_data.language_code) == "string" then config.language_code = read_data.language_code end + if type(read_data.panels) == "string" then config.panels = read_data.panels end + if type(read_data.character) == "string" then config.character = read_data.character end + if type(read_data.stage) == "string" then config.stage = read_data.stage end + + if type(read_data.ranked) == "boolean" then config.ranked = read_data.ranked end + + if type(read_data.vsync) == "boolean" then config.vsync = read_data.vsync end + + if type(read_data.use_music_from) == "string" and use_music_from_values[read_data.use_music_from] then + config.use_music_from = read_data.use_music_from end - if love.filesystem.getInfo("assets/"..config.assets_dir.."/lip") - and not has_any_custom_character() then - print("retrocompatibility applied!") - config.use_default_characters = true + + if type(read_data.level) == "number" then config.level = bound(1,read_data.level,10) end + if type(read_data.endless_speed) == "number" then config.endless_speed = bound(1,read_data.endless_speed,99) end + if type(read_data.endless_difficulty) == "number" then config.endless_difficulty = bound(1,read_data.endless_difficulty,3) end + + if type(read_data.name) == "string" then config.name = read_data.name end + + if type(read_data.master_volume) == "number" then config.master_volume = bound(0,read_data.master_volume,100) end + if type(read_data.SFX_volume) == "number" then config.SFX_volume = bound(0,read_data.SFX_volume,100) end + if type(read_data.music_volume) == "number" then config.music_volume = bound(0,read_data.music_volume,100) end + if type(read_data.input_repeat_delay) == "number" then config.input_repeat_delay = bound(1,read_data.input_repeat_delay,50) end + if type(read_data.portrait_darkness) == "number" then config.portrait_darkness = bound(0,read_data.portrait_darkness,100) end + if type(read_data.cardfx_scale) == "number" then config.cardfx_scale = bound(1,read_data.cardfx_scale,200) end + + if type(read_data.debug_mode) == "boolean" then config.debug_mode = read_data.debug_mode end + if type(read_data.show_fps) == "boolean" then config.show_fps = read_data.show_fps end + if type(read_data.show_ingame_infos) == "boolean" then config.show_ingame_infos = read_data.show_ingame_infos end + if type(read_data.ready_countdown_1P) == "boolean" then config.ready_countdown_1P = read_data.ready_countdown_1P end + if type(read_data.danger_music_changeback_delay) == "boolean" then config.danger_music_changeback_delay = read_data.danger_music_changeback_delay end + if type(read_data.enable_analytics) == "boolean" then config.enable_analytics = read_data.enable_analytics end + if type(read_data.popfx) == "boolean" then config.popfx = read_data.popfx end + + if type(read_data.save_replays_publicly) == "string" and save_replays_values[read_data.save_replays_publicly] then + config.save_replays_publicly = read_data.save_replays_publicly end - love.window.setVSync(config.vsync and 1 or 0) - -- do stuff regarding version compatibility here, before we patch it + if type(read_data.window_x) == "number" then config.window_x = read_data.window_x end + if type(read_data.window_y) == "number" then config.window_y = read_data.window_y end + if type(read_data.display) == "number" then config.display = read_data.display end + if type(read_data.fullscreen) == "boolean" then config.fullscreen = read_data.fullscreen end - config.version = VERSION file:close() end) end @@ -142,6 +153,7 @@ function read_user_id_file() pcall(function() local file = love.filesystem.newFile("servers/"..connected_server_ip.."/user_id.txt") file:open("r") my_user_id = file:read() + my_user_id = my_user_id:match("^%s*(.-)%s*$") file:close() end) end @@ -204,13 +216,15 @@ function recursive_copy(source, destination) local names = lfs.getDirectoryItems(source) local temp for i, name in ipairs(names) do - if lfs.isDirectory(source.."/"..name) then + local info = lfs.getInfo(source.."/"..name) + if info and info.type == "directory" then print("calling recursive_copy(source".."/"..name..", ".. destination.."/"..name..")") recursive_copy(source.."/"..name, destination.."/"..name) - elseif lfs.isFile(source.."/"..name) then - if not lfs.isDirectory(destination) then - love.filesystem.createDirectory(destination) + elseif info and info.type == "file" then + local destination_info = lfs.getInfo(destination) + if not destination_info or destination_info.type ~= "directory" then + love.filesystem.createDirectory(destination) end print("copying file: "..source.."/"..name.." to "..destination.."/"..name) diff --git a/select_screen.lua b/select_screen.lua new file mode 100644 index 00000000..d1dba028 --- /dev/null +++ b/select_screen.lua @@ -0,0 +1,1313 @@ +local select_screen = {} + +select_screen.fallback_when_missing = { nil, nil } +select_screen.character_select_mode = "1p_vs_yourself" + +local wait = coroutine.yield +local current_page = 1 + +-- fills the provided map based on the provided template and return the amount of pages. __Empty values will be replaced by character_ids +local function fill_map(template_map,map) + local X,Y = 5,9 + local pages_amount = 0 + local character_id_index = 1 + while true do + -- new page handling + pages_amount = pages_amount+1 + map[pages_amount] = deepcpy(template_map) + + -- go through the page and replace __Empty with characters_ids_for_current_theme + for i=1,X do + for j=1,Y do + if map[pages_amount][i][j] == "__Empty" then + map[pages_amount][i][j] = characters_ids_for_current_theme[character_id_index] + character_id_index = character_id_index+1 + -- end case: no more characters_ids_for_current_theme to add + if character_id_index == #characters_ids_for_current_theme+1 then + print("filled "..#characters_ids_for_current_theme.." characters across "..pages_amount.." page(s)") + return pages_amount + end + end + end + end + end +end + +local function patch_is_random(refreshed) -- retrocompatibility + if refreshed ~= nil then + if refreshed.stage_is_random == true then + refreshed.stage_is_random = random_stage_special_value + elseif refreshed.stage_is_random == false then + refreshed.stage_is_random = nil + elseif refreshed.stage_is_random ~= nil and refreshed.stage_is_random ~= random_stage_special_value and stages[refreshed.stage_is_random] == nil then + refreshed.stage_is_random = random_stage_special_value + end + if refreshed.character_is_random == true then + refreshed.character_is_random = random_character_special_value + elseif refreshed.character_is_random == false then + refreshed.character_is_random = nil + elseif refreshed.character_is_random ~= nil and refreshed.character_is_random ~= random_character_special_value and characters[refreshed.character_is_random] == nil then + refreshed.character_is_random = random_character_special_value + end + end +end + +function refresh_based_on_own_mods(refreshed,ask_change_fallback) + patch_is_random(refreshed) + ask_change_fallback = ask_change_fallback or false + if refreshed ~= nil then + -- panels + if refreshed.panels_dir == nil or panels[refreshed.panels_dir] == nil then + refreshed.panels_dir = config.panels + end + + -- stage + if refreshed.stage == nil or ( refreshed.stage ~= random_stage_special_value and stages[refreshed.stage] == nil ) then + if not select_screen.fallback_when_missing[1] or ask_change_fallback then + select_screen.fallback_when_missing[1] = uniformly(stages_ids_for_current_theme) + if stages[select_screen.fallback_when_missing[1]]:is_bundle() then -- may pick a bundle! + select_screen.fallback_when_missing[1] = uniformly(stages[select_screen.fallback_when_missing[1]].sub_stages) + end + end + refreshed.stage = select_screen.fallback_when_missing[1] + end + + -- character + if refreshed.character == nil or ( refreshed.character ~= random_character_special_value and characters[refreshed.character] == nil ) then + if refreshed.character_display_name and characters_ids_by_display_names[refreshed.character_display_name] + and not characters[characters_ids_by_display_names[refreshed.character_display_name][1]]:is_bundle() then + refreshed.character = characters_ids_by_display_names[refreshed.character_display_name][1] + else + if not select_screen.fallback_when_missing[2] or ask_change_fallback then + select_screen.fallback_when_missing[2] = uniformly(characters_ids_for_current_theme) + if characters[select_screen.fallback_when_missing[2]]:is_bundle() then -- may pick a bundle + select_screen.fallback_when_missing[2] = uniformly(characters[select_screen.fallback_when_missing[2]].sub_characters) + end + end + refreshed.character = select_screen.fallback_when_missing[2] + end + end + end +end + +local function resolve_character_random(state) + if state.character_is_random ~= nil then + if state.character_is_random == random_character_special_value then + state.character = uniformly(characters_ids_for_current_theme) + if characters[state.character]:is_bundle() then -- may pick a bundle + state.character = uniformly(characters[state.character].sub_characters) + end + else + state.character = uniformly(characters[state.character_is_random].sub_characters) + end + return true + end + return false +end + +local function resolve_stage_random(state) + if state.stage_is_random ~= nil then + if state.stage_is_random == random_stage_special_value then + state.stage = uniformly(stages_ids_for_current_theme) + if stages[state.stage]:is_bundle() then + state.stage = uniformly(stages[state.stage].sub_stages) + end + else + state.stage = uniformly(stages[state.stage_is_random].sub_stages) + end + end +end + +function select_screen.main() + if themes[config.theme].musics.select_screen then + stop_the_music() + find_and_add_music(themes[config.theme].musics, "select_screen") + elseif themes[config.theme].musics.main then + find_and_add_music(themes[config.theme].musics, "main") + end + + background = themes[config.theme].images.bg_select_screen + reset_filters() + + select_screen.fallback_when_missing = { nil, nil } + + local function add_client_data(state) + state.loaded = characters[state.character] and characters[state.character].fully_loaded and stages[state.stage] and stages[state.stage].fully_loaded + state.wants_ready = state.ready + end + + local function refresh_loaded_and_ready(state_1,state_2) + state_1.loaded = characters[state_1.character] and characters[state_1.character].fully_loaded and stages[state_1.stage] and stages[state_1.stage].fully_loaded + if state_2 then + state_2.loaded = characters[state_2.character] and characters[state_2.character].fully_loaded and stages[state_2.stage] and stages[state_2.stage].fully_loaded + end + + if select_screen.character_select_mode == "2p_net_vs" then + state_1.ready = state_1.wants_ready and state_1.loaded and state_2.loaded + else + state_1.ready = state_1.wants_ready and state_1.loaded + if state_2 then + state_2.ready = state_2.wants_ready and state_2.loaded + end + end + end + + -- map is composed of special values prefixed by __ and character ids + local template_map = {{"__Panels", "__Panels", "__Stage", "__Stage", "__Stage", "__Level", "__Level", "__Level", "__Ready"}, + {"__Random", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Leave"}} + local map = {} + if select_screen.character_select_mode == "2p_net_vs" then + local opponent_connected = false + local retries, retry_limit = 0, 250 + while not global_initialize_room_msg and retries < retry_limit do + local msg = server_queue:pop_next_with("create_room", "character_select", "spectate_request_granted") + if msg then + global_initialize_room_msg = msg + end + gprint(loc("ss_init"), unpack(main_menu_screen_pos)) + wait() + if not do_messages() then + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} + end + retries = retries + 1 + end + -- if room_number_last_spectated and retries >= retry_limit and currently_spectating then + -- request_spectate(room_number_last_spectated) + -- retries = 0 + -- while not global_initialize_room_msg and retries < retry_limit do + -- for _,msg in ipairs(this_frame_messages) do + -- if msg.create_room or msg.character_select or msg.spectate_request_granted then + -- global_initialize_room_msg = msg + -- end + -- end + -- gprint("Lost connection. Trying to rejoin...", unpack(main_menu_screen_pos)) + -- wait() + -- if not do_messages() then + -- return main_dumb_transition, {main_select_mode, "Disconnected from server.\n\nReturning to main menu...", 60, 300} + -- end + -- retries = retries + 1 + -- end + -- end + if not global_initialize_room_msg then + return main_dumb_transition, {main_select_mode, loc("ss_init_fail").."\n\n"..loc("ss_return"), 60, 300} + end + msg = global_initialize_room_msg + if msg.ratings then + global_current_room_ratings = msg.ratings + end + + if msg.your_player_number then + my_player_number = msg.your_player_number + elseif currently_spectating then + my_player_number = 1 + elseif my_player_number and my_player_number ~= 0 then + print("We assumed our player number is still "..my_player_number) + else + error(loc("nt_player_err")) + print("Error: The server never told us our player number. Assuming it is 1") + my_player_number = 1 + end + + if msg.op_player_number then + op_player_number = msg.op_player_number or op_player_number + elseif currently_spectating then + op_player_number = 2 + elseif op_player_number and op_player_number ~= 0 then + print("We assumed op player number is still "..op_player_number) + else + error("We never heard from the server as to what player number we are") + print("Error: The server never told us our player number. Assuming it is 2") + op_player_number = 2 + end + + if my_player_number == 2 and msg.a_menu_state ~= nil and msg.b_menu_state ~= nil then + print("inverting the states to match player number!") + msg.a_menu_state, msg.b_menu_state = msg.b_menu_state, msg.a_menu_state + end + + global_my_state = msg.a_menu_state + refresh_based_on_own_mods(global_my_state) + global_op_state = msg.b_menu_state + refresh_based_on_own_mods(global_op_state) + + if msg.win_counts then + update_win_counts(msg.win_counts) + end + if msg.replay_of_match_so_far then + replay_of_match_so_far = msg.replay_of_match_so_far + end + if msg.ranked then + match_type = "Ranked" + match_type_message = "" + else + match_type = "Casual" + end + if currently_spectating then + P1 = {panel_buffer="", gpanel_buffer=""} + print("we reset P1 buffers at start of main_character_select()") + end + P2 = {panel_buffer="", gpanel_buffer=""} + print("we reset P2 buffers at start of main_character_select()") + print("current_server_supports_ranking: "..tostring(current_server_supports_ranking)) + + if current_server_supports_ranking then + template_map = {{"__Panels", "__Panels", "__Mode", "__Mode", "__Stage", "__Stage", "__Level", "__Level", "__Ready"}, + {"__Random", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty"}, + {"__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Empty", "__Leave"}} + end + end + + local pages_amount = fill_map(template_map, map) + if current_page > pages_amount then + current_page = 1 + end + + op_win_count = op_win_count or 0 + + if select_screen.character_select_mode == "2p_net_vs" then + global_current_room_ratings = global_current_room_ratings or {{new=0,old=0,difference=0},{new=0,old=0,difference=0}} + my_expected_win_ratio = nil + op_expected_win_ratio = nil + print("my_player_number = "..my_player_number) + print("op_player_number = "..op_player_number) + if global_current_room_ratings[my_player_number].new + and global_current_room_ratings[my_player_number].new ~= 0 + and global_current_room_ratings[op_player_number] + and global_current_room_ratings[op_player_number].new ~= 0 then + my_expected_win_ratio = (100*round(1/(1+10^ + ((global_current_room_ratings[op_player_number].new + -global_current_room_ratings[my_player_number].new) + /RATING_SPREAD_MODIFIER)) + ,2)) + op_expected_win_ratio = (100*round(1/(1+10^ + ((global_current_room_ratings[my_player_number].new + -global_current_room_ratings[op_player_number].new) + /RATING_SPREAD_MODIFIER)) + ,2)) + end + match_type = match_type or "Casual" + if match_type == "" then match_type = "Casual" end + end + + match_type_message = match_type_message or "" + + local function do_leave() + stop_the_music() + my_win_count = 0 + op_win_count = 0 + return json_send({leave_room=true}) + end + + -- be wary: name_to_xy_per_page is kinda buggy for larger blocks as they span multiple positions (we retain the last one), and is completely broken with __Empty + local name_to_xy_per_page = {} + local X,Y = 5,9 + for p=1,pages_amount do + name_to_xy_per_page[p] = {} + for i=1,X do + for j=1,Y do + if map[p][i][j] then + name_to_xy_per_page[p][map[p][i][j]] = {i,j} + end + end + end + end + + my_win_count = my_win_count or 0 + + local cursor_data = {{position=shallowcpy(name_to_xy_per_page[current_page]["__Ready"]),can_super_select=false,selected=false},{position=shallowcpy(name_to_xy_per_page[current_page]["__Ready"]),can_super_select=false,selected=false}} + + -- our data (first player in local) + if global_my_state ~= nil then + cursor_data[1].state = shallowcpy(global_my_state) + global_my_state = nil + else + cursor_data[1].state = {stage=config.stage, stage_is_random=( (config.stage==random_stage_special_value or stages[config.stage]:is_bundle()) and config.stage or nil ), + character=config.character, character_is_random=( ( config.character==random_character_special_value or characters[config.character]:is_bundle()) and config.character or nil ), level=config.level, panels_dir=config.panels, cursor="__Ready", ready=false, ranked=config.ranked} + end + + if resolve_character_random(cursor_data[1].state) then + character_loader_load(cursor_data[1].state.character) + end + cursor_data[1].state.character_display_name = characters[cursor_data[1].state.character].display_name + + resolve_stage_random(cursor_data[1].state) + stage_loader_load(cursor_data[1].state.stage) + + add_client_data(cursor_data[1].state) + + if select_screen.character_select_mode ~= "1p_vs_yourself" then + if global_op_state ~= nil then + cursor_data[2].state = shallowcpy(global_op_state) + if select_screen.character_select_mode ~= "2p_local_vs" then + global_op_state = nil -- retains state of the second player, also: don't unload its character when going back and forth + else + resolve_character_random(cursor_data[2].state) + cursor_data[2].state.character_display_name = characters[cursor_data[2].state.character].display_name + resolve_stage_random(cursor_data[2].state) + end + else + cursor_data[2].state = {stage=config.stage, stage_is_random=( (config.stage==random_stage_special_value or stages[config.stage]:is_bundle()) and config.stage or nil ), + character=config.character, character_is_random=( ( config.character==random_character_special_value or characters[config.character]:is_bundle()) and config.character or nil ), level=config.level, panels_dir=config.panels, cursor="__Ready", ready=false, ranked=false} + resolve_character_random(cursor_data[2].state) + cursor_data[2].state.character_display_name = characters[cursor_data[2].state.character].display_name + resolve_stage_random(cursor_data[2].state) + end + if cursor_data[2].state.character ~= random_character_special_value and not characters[cursor_data[2].state.character]:is_bundle() then -- while playing online, we'll wait for them to send us the new pick + character_loader_load(cursor_data[2].state.character) + end + if cursor_data[2].state.stage ~= random_stage_special_value and not stages[cursor_data[2].state.stage]:is_bundle() then -- while playing online, we'll wait for them to send us the new pick + stage_loader_load(cursor_data[2].state.stage) + end + add_client_data(cursor_data[2].state) + end + refresh_loaded_and_ready(cursor_data[1].state, cursor_data[2] and cursor_data[2].state or nil) + + local prev_state = shallowcpy(cursor_data[1].state) + + local super_select_pixelcode = [[ + uniform float percent; + vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords ) + { + vec4 c = Texel(tex, texture_coords) * color; + if( texture_coords.x < percent ) + { + return c; + } + float ret = (c.x+c.y+c.z)/3.0; + return vec4(ret, ret, ret, c.a); + } + ]] + + -- one per player, should we put them into cursor_data even though it's meaningless? + local super_select_shaders = { love.graphics.newShader(super_select_pixelcode), love.graphics.newShader(super_select_pixelcode) } + + local function draw_button(x,y,w,h,str,halign,valign,no_rect) + no_rect = no_rect or str == "__Empty" or str == "__Reserved" + halign = halign or "center" + valign = valign or "top" + local menu_width = Y*100 + local menu_height = X*80 + local spacing = 8 + local text_height = 13 + local x_padding = math.floor((canvas_width-menu_width)/2) + local y_padding = math.floor((canvas_height-menu_height)/2) + set_color(unpack(colors.white)) + render_x = x_padding+(y-1)*100+spacing + render_y = y_padding+(x-1)*100+spacing + button_width = w*100-2*spacing + button_height = h*100-2*spacing + if no_rect == false then + grectangle("line", render_x, render_y, button_width, button_height) + end + local character = characters[str] + if str == "P1" then + if cursor_data[1].state.character_is_random then + if cursor_data[1].state.character_is_random == random_character_special_value then + character = random_character_special_value + else + character = characters[cursor_data[1].state.character_is_random] + end + else + character = characters[cursor_data[1].state.character] + end + elseif str == "P2" then + if cursor_data[2].state.character_is_random then + if cursor_data[2].state.character_is_random == random_character_special_value then + character = random_character_special_value + else + character = characters[cursor_data[2].state.character_is_random] + end + else + character = characters[cursor_data[2].state.character] + end + end + local width_for_alignment = button_width + local x_add,y_add = 0,0 + if valign == "center" then + y_add = math.floor(0.5*button_height-0.5*text_height)-3 + elseif valign == "bottom" then + y_add = math.floor(button_height-text_height) + end + + local function draw_character(character) + -- draw character icon with its super selection or bundle character icon + if character == random_character_special_value + or not character:is_bundle() + or character.images.icon then + local icon_to_use = character == random_character_special_value and themes[config.theme].images.IMG_random_character or character.images.icon + local orig_w, orig_h = icon_to_use:getDimensions() + local scale = button_width/math.max(orig_w,orig_h) -- keep image ratio + menu_drawf(icon_to_use, render_x+0.5*button_width, render_y+0.5*button_height,"center","center", 0, scale, scale ) + if str ~= "P1" and str ~= "P2" then + if character.stage then + local orig_w, orig_h = stages[character.stage].images.thumbnail:getDimensions() + menu_drawf(stages[character.stage].images.thumbnail, render_x+10, render_y+button_height-7,"center","center", 0, 16/orig_w, 9/orig_h ) + end + if character.panels then + local orig_w, orig_h = panels[character.panels].images.classic[1][1]:getDimensions() + menu_drawf(panels[character.panels].images.classic[1][1], render_x+7, character.stage and render_y+button_height-19 or render_y+button_height-6,"center","center", 0, 12/orig_w, 12/orig_h ) + end + end + elseif character and character:is_bundle() then -- draw bundle character generated thumbnails + local sub_characters = character.sub_characters + local sub_characters_count = math.min(4, #sub_characters) -- between 2 and 4 (inclusive), by design + + local thumbnail_1 = characters[sub_characters[1]].images.icon + local thumb_y_padding = 0.25*button_height + local thumb_1_and_2_y_padding = sub_characters_count >= 3 and -thumb_y_padding or 0 + local scale_1 = button_width*0.5/math.max(thumbnail_1:getWidth(), thumbnail_1:getHeight()) + menu_drawf(thumbnail_1, render_x+0.25*button_width, render_y+0.5*button_height+thumb_1_and_2_y_padding, "center", "center", 0, scale_1, scale_1 ) + + local thumbnail_2 = characters[sub_characters[2]].images.icon + local scale_2 = button_width*0.5/math.max(thumbnail_2:getWidth(), thumbnail_2:getHeight()) + menu_drawf(thumbnail_2, render_x+0.75*button_width, render_y+0.5*button_height+thumb_1_and_2_y_padding, "center", "center", 0, scale_2, scale_2 ) + + if sub_characters_count >= 3 then + local thumbnail_3 = characters[sub_characters[3]].images.icon + local scale_3 = button_width*0.5/math.max(thumbnail_3:getWidth(), thumbnail_3:getHeight()) + local thumb_3_x_padding = sub_characters_count == 3 and 0.25*button_width or 0 + menu_drawf(thumbnail_3, render_x+0.25*button_width+thumb_3_x_padding, render_y+0.75*button_height, "center", "center", 0, scale_3, scale_3 ) + end + if sub_characters_count == 4 then + local thumbnail_4 = characters[sub_characters[4]].images.icon + local scale_4 = button_width*0.5/math.max(thumbnail_4:getWidth(), thumbnail_4:getHeight()) + menu_drawf(thumbnail_4, render_x+0.75*button_width, render_y+0.75*button_height, "center", "center", 0, scale_4, scale_4 ) + end + end + + -- draw flag in the bottom-right corner + if character and character ~= random_character_special_value and character.flag then + local flag_icon = themes[config.theme].images.flags[character.flag] + if flag_icon then + local orig_w, orig_h = flag_icon:getDimensions() + local scale = 0.2*button_width/orig_w -- keep image ratio + menu_drawf(flag_icon, render_x+button_width-1, render_y+button_height-1,"right","bottom", 0, scale, scale ) + end + end + end + + local function draw_super_select(player_num) + local ratio = menu_pressing_enter(K[player_num]) + if ratio > super_selection_enable_ratio then + super_select_shaders[player_num]:send("percent", linear_smooth(ratio,super_selection_enable_ratio,1.0)) + set_shader(super_select_shaders[player_num]) + menu_drawf(themes[config.theme].images.IMG_super, render_x+button_width*0.5, render_y+button_height*0.5, "center", "center" ) + set_shader() + end + end + + local function draw_cursor(button_height, spacing, player_num,ready) + local cur_blink_frequency = 4 + local cur_pos_change_frequency = 8 + local draw_cur_this_frame = false + local cursor_frame = 1 + if ready then + if (math.floor(menu_clock/cur_blink_frequency)+player_num)%2+1 == player_num then + draw_cur_this_frame = true + end + else + draw_cur_this_frame = true + cursor_frame = (math.floor(menu_clock/cur_pos_change_frequency)+player_num)%2+1 + end + if draw_cur_this_frame then + local cur_img = themes[config.theme].images.IMG_char_sel_cursors[player_num][cursor_frame] + local cur_img_left = themes[config.theme].images.IMG_char_sel_cursor_halves.left[player_num][cursor_frame] + local cur_img_right = themes[config.theme].images.IMG_char_sel_cursor_halves.right[player_num][cursor_frame] + local cur_img_w, cur_img_h = cur_img:getDimensions() + local cursor_scale = (button_height+(spacing*2))/cur_img_h + menu_drawq(cur_img, cur_img_left, render_x-spacing, render_y-spacing, 0, cursor_scale , cursor_scale) + menu_drawq(cur_img, cur_img_right, render_x+button_width+spacing-cur_img_w*cursor_scale/2, render_y-spacing, 0, cursor_scale, cursor_scale) + end + end + + local function draw_player_state(cursor_data,player_number) + if characters[cursor_data.state.character] and not characters[cursor_data.state.character].fully_loaded then + menu_drawf(themes[config.theme].images.IMG_loading, render_x+button_width*0.5, render_y+button_height*0.5, "center", "center" ) + elseif cursor_data.state.wants_ready then + menu_drawf(themes[config.theme].images.IMG_ready, render_x+button_width*0.5, render_y+button_height*0.5, "center", "center" ) + end + local scale = 0.25*button_width/math.max(themes[config.theme].images.IMG_players[player_number]:getWidth(),themes[config.theme].images.IMG_players[player_number]:getHeight()) -- keep image ratio + menu_drawf(themes[config.theme].images.IMG_players[player_number], render_x+1, render_y+button_height-1, "left", "bottom", 0, scale, scale ) + scale = 0.25*button_width/math.max(themes[config.theme].images.IMG_levels[cursor_data.state.level]:getWidth(),themes[config.theme].images.IMG_levels[cursor_data.state.level]:getHeight()) -- keep image ratio + menu_drawf(themes[config.theme].images.IMG_levels[cursor_data.state.level], render_x+button_width-1, render_y+button_height-1, "right", "bottom", 0, scale, scale ) + end + + local function draw_panels(cursor_data,player_number,y_padding) + local panels_max_width = 0.25*button_height + local panels_width = math.min(panels_max_width,panels[cursor_data.state.panels_dir].images.classic[1][1]:getWidth()) + local padding_x = 0.5*button_width-3*panels_width -- center them, not 3.5 mysteriously? + if cursor_data.state.level >= 9 then + padding_x = padding_x-0.5*panels_width + end + local is_selected = cursor_data.selected and cursor_data.state.cursor == "__Panels" + if is_selected then + padding_x = padding_x-panels_width + end + local panels_scale = panels_width/panels[cursor_data.state.panels_dir].images.classic[1][1]:getWidth() + menu_drawf(themes[config.theme].images.IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) + padding_x = padding_x + panels_width + if is_selected then + gprintf("<", render_x+padding_x-0.5*panels_width, render_y+y_padding-0.5*text_height,panels_width,"center") + padding_x = padding_x + panels_width + end + for i=1,8 do + if i ~= 7 and (i ~= 6 or cursor_data.state.level >= 9) then + menu_drawf(panels[cursor_data.state.panels_dir].images.classic[i][1], render_x+padding_x, render_y+y_padding, "center", "center", 0, panels_scale, panels_scale ) + padding_x = padding_x + panels_width + end + end + if is_selected then + gprintf(">", render_x+padding_x-0.5*panels_width, render_y+y_padding-0.5*text_height,panels_width,"center") + end + end + + local function draw_levels(cursor_data,player_number,y_padding) + local level_max_width = 0.2*button_height + local level_width = math.min(level_max_width,themes[config.theme].images.IMG_levels[1]:getWidth()) + local padding_x = math.floor(0.5*button_width-5.5*level_width) + local is_selected = cursor_data.selected and cursor_data.state.cursor == "__Level" + if is_selected then + padding_x = padding_x-level_width + end + local level_scale = level_width/themes[config.theme].images.IMG_levels[1]:getWidth() + menu_drawf(themes[config.theme].images.IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) + local ex_scaling = level_width/themes[config.theme].images.IMG_levels[11]:getWidth() + menu_drawf(themes[config.theme].images.IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) + padding_x = padding_x + level_width + if is_selected then + gprintf("<", render_x+padding_x-0.5*level_width, render_y+y_padding-0.5*text_height,level_width,"center") + padding_x = padding_x + level_width + end + for i=1,#level_to_starting_speed do --which should equal the number of levels in the game + local additional_padding = math.floor(0.5*(themes[config.theme].images.IMG_levels[i]:getWidth()-level_width)) + padding_x = padding_x + additional_padding + local use_unfocus = cursor_data.state.level < i + if use_unfocus then + menu_drawf(themes[config.theme].images.IMG_levels_unfocus[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, (i == 11 and ex_scaling or level_scale), (i == 11 and ex_scaling or level_scale) ) + else + menu_drawf(themes[config.theme].images.IMG_levels[i], render_x+padding_x, render_y+y_padding, "center", "center", 0, (i == 11 and ex_scaling or level_scale), (i == 11 and ex_scaling or level_scale) ) + end + if i == cursor_data.state.level then + menu_drawf(themes[config.theme].images.IMG_level_cursor, render_x+padding_x, render_y+y_padding+themes[config.theme].images.IMG_levels[i]:getHeight()*0.5, "center", "top", 0, (i == 11 and ex_scaling or level_scale), (i == 11 and ex_scaling or level_scale) ) + end + padding_x = padding_x + level_width + additional_padding + end + if is_selected then + gprintf(">", render_x+padding_x-0.5*level_width, render_y+y_padding-0.5*text_height,level_width,"center") + end + end + + local function draw_match_type(cursor_data,player_number,y_padding) + local padding_x = math.floor(0.5*button_width - themes[config.theme].images.IMG_players[player_number]:getWidth()*0.5 - 46) -- ty GIMP; no way to know the size of the text? + menu_drawf(themes[config.theme].images.IMG_players[player_number], render_x+padding_x, render_y+y_padding, "center", "center" ) + padding_x = padding_x+themes[config.theme].images.IMG_players[player_number]:getWidth() + local to_print + if cursor_data.state.ranked then + to_print = loc("ss_casual").." ["..loc("ss_ranked").."]" + else + to_print = "["..loc("ss_casual").."] "..loc("ss_ranked") + end + gprint(to_print, render_x+padding_x, render_y+y_padding-0.5*text_height-1) + end + + local function draw_stage(cursor_data,player_number,x_padding) + local stage_dimensions = { 80, 45 } + local y_padding = math.floor(0.5*button_height) + local padding_x = math.floor(x_padding-0.5*stage_dimensions[1]) + local is_selected = cursor_data.selected and cursor_data.state.cursor == "__Stage" + if is_selected then + local arrow_pos = select_screen.character_select_mode == "2p_net_vs" + and { math.floor(render_x+x_padding-20), math.floor(render_y+y_padding-stage_dimensions[2]*0.5-15) } + or { math.floor(render_x+padding_x-13), math.floor(render_y+y_padding+0.25*text_height) } + gprintf("<", arrow_pos[1], arrow_pos[2],10,"center") + end + -- background for thumbnail + grectangle("line", render_x+padding_x, math.floor(render_y+y_padding-stage_dimensions[2]*0.5), stage_dimensions[1], stage_dimensions[2]) + + -- thumbnail or composed thumbnail (for bundles without thumbnails) + if cursor_data.state.stage_is_random == random_stage_special_value + or ( cursor_data.state.stage_is_random and not stages[cursor_data.state.stage_is_random] ) + or ( cursor_data.state.stage_is_random and stages[cursor_data.state.stage_is_random] and stages[cursor_data.state.stage_is_random].images.thumbnail ) + or ( not cursor_data.state.stage_is_random and stages[cursor_data.state.stage].images.thumbnail ) then + local thumbnail = themes[config.theme].images.IMG_random_stage + if cursor_data.state.stage_is_random and stages[cursor_data.state.stage_is_random] and stages[cursor_data.state.stage_is_random].images.thumbnail then + thumbnail = stages[cursor_data.state.stage_is_random].images.thumbnail + elseif not cursor_data.state.stage_is_random and stages[cursor_data.state.stage].images.thumbnail then + thumbnail = stages[cursor_data.state.stage].images.thumbnail + end + menu_drawf(thumbnail, render_x+padding_x, render_y+y_padding-1, "left", "center", 0, stage_dimensions[1]/thumbnail:getWidth(), stage_dimensions[2]/thumbnail:getHeight() ) + elseif cursor_data.state.stage_is_random and stages[cursor_data.state.stage_is_random]:is_bundle() then + local half_stage_dimensions = { math.floor(stage_dimensions[1]*0.5), math.floor(stage_dimensions[2]*0.5) } + local sub_stages = stages[cursor_data.state.stage_is_random].sub_stages + local sub_stages_count = math.min(4, #sub_stages) -- between 2 and 4 (inclusive), by design + + local thumbnail_1 = stages[sub_stages[1]].images.thumbnail + local thumb_y_padding = math.floor(half_stage_dimensions[2]*0.5) + local thumb_1_and_2_y_padding = sub_stages_count >= 3 and -thumb_y_padding or 0 + menu_drawf(thumbnail_1, render_x+padding_x, render_y+y_padding-1+thumb_1_and_2_y_padding, "left", "center", 0, half_stage_dimensions[1]/thumbnail_1:getWidth(), half_stage_dimensions[2]/thumbnail_1:getHeight() ) + + local thumbnail_2 = stages[sub_stages[2]].images.thumbnail + menu_drawf(thumbnail_2, render_x+padding_x+half_stage_dimensions[1], render_y+y_padding-1+thumb_1_and_2_y_padding, "left", "center", 0, half_stage_dimensions[1]/thumbnail_2:getWidth(), half_stage_dimensions[2]/thumbnail_2:getHeight() ) + + if sub_stages_count >= 3 then + local thumbnail_3 = stages[sub_stages[3]].images.thumbnail + local thumb_3_x_padding = sub_stages_count == 3 and math.floor(half_stage_dimensions[1]*0.5) or 0 + menu_drawf(thumbnail_3, render_x+padding_x+thumb_3_x_padding, render_y+y_padding-1+thumb_y_padding, "left", "center", 0, half_stage_dimensions[1]/thumbnail_3:getWidth(), half_stage_dimensions[2]/thumbnail_3:getHeight() ) + end + if sub_stages_count == 4 then + local thumbnail_4 = stages[sub_stages[4]].images.thumbnail + menu_drawf(thumbnail_4, render_x+padding_x+half_stage_dimensions[1], render_y+y_padding-1+thumb_y_padding, "left", "center", 0, half_stage_dimensions[1]/thumbnail_4:getWidth(), half_stage_dimensions[2]/thumbnail_4:getHeight() ) + end + end + + -- player image + local player_icon_pos = select_screen.character_select_mode == "2p_net_vs" + and { math.floor(render_x+padding_x+stage_dimensions[1]*0.5), math.floor(render_y+y_padding-stage_dimensions[2]*0.5-7) } + or { math.floor(render_x+padding_x-10), math.floor(render_y+y_padding-stage_dimensions[2]*0.25) } + menu_drawf(themes[config.theme].images.IMG_players[player_number], player_icon_pos[1], player_icon_pos[2], "center", "center" ) + -- display name + local display_name = nil + if cursor_data.state.stage_is_random == random_stage_special_value + or ( cursor_data.state.stage_is_random and not stages[cursor_data.state.stage_is_random] ) then + display_name = loc("random") + elseif cursor_data.state.stage_is_random then + display_name = stages[cursor_data.state.stage_is_random].display_name + else + display_name = stages[cursor_data.state.stage].display_name + end + gprintf(display_name, render_x+padding_x, math.floor(render_y+y_padding+stage_dimensions[2]*0.5),stage_dimensions[1],"center",nil,1,small_font) + + padding_x = padding_x+stage_dimensions[1] + + if is_selected then + local arrow_pos = select_screen.character_select_mode == "2p_net_vs" + and { math.floor(render_x+x_padding+11), math.floor(render_y+y_padding-stage_dimensions[2]*0.5-15) } + or { math.floor(render_x+padding_x+3), math.floor(render_y+y_padding+0.25*text_height) } + gprintf(">", arrow_pos[1], arrow_pos[2], 10,"center") + end + end + + if character then + x_add = 0.025*button_width + width_for_alignment = 0.95*button_width + draw_character(character) + end + + local pstr + if string.sub(str, 1, 2) == "__" then + pstr = string.sub(str, 3) + end + if str == "__Mode" then + if (select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs") then + draw_match_type(cursor_data[1],1,0.4*button_height) + draw_match_type(cursor_data[2],2,0.7*button_height) + else + draw_match_type(cursor_data[1],1,0.5*button_height) + end + elseif str == "__Panels" then + if (select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs") then + draw_panels(cursor_data[1],1,0.4*button_height) + draw_panels(cursor_data[2],2,0.7*button_height) + else + draw_panels(cursor_data[1],1,0.5*button_height) + end + elseif str == "__Stage" then + if (select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs") then + draw_stage(cursor_data[1],1,0.25*button_width) + draw_stage(cursor_data[2],2,0.75*button_width) + else + draw_stage(cursor_data[1],1,0.5*button_width) + end + elseif str == "__Level" then + if (select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs") then + draw_levels(cursor_data[1],1,0.4*button_height) + draw_levels(cursor_data[2],2,0.7*button_height) + else + draw_levels(cursor_data[1],1,0.5*button_height) + end + elseif str == "P1" then + draw_player_state(cursor_data[1],1) + pstr = my_name + elseif str == "P2" then + draw_player_state(cursor_data[2],2) + pstr = op_name + elseif character and character ~= random_character_special_value then + pstr = character.display_name + elseif string.sub(str, 1, 2) ~= "__" then -- catch random_character_special_value case + pstr = str:gsub("^%l", string.upper) + end + if x ~= 0 then + if cursor_data[1].state and cursor_data[1].state.cursor == str + and ( (str ~= "__Empty" and str ~= "__Reserved") or ( cursor_data[1].position[1] == x and cursor_data[1].position[2] == y ) ) then + draw_cursor(button_height, spacing, 1, cursor_data[1].state.ready) + if cursor_data[1].can_super_select then + draw_super_select(1) + end + end + if (select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs") + and cursor_data[2].state and cursor_data[2].state.cursor == str + and ( (str ~= "__Empty" and str ~= "__Reserved") or ( cursor_data[2].position[1] == x and cursor_data[2].position[2] == y ) ) then + draw_cursor(button_height, spacing, 2, cursor_data[2].state.ready) + if cursor_data[2].can_super_select then + draw_super_select(2) + end + end + end + if str ~= "__Empty" and str ~= "__Reserved" then + local loc_str = {Level= loc("level"), Mode=loc("mode"), Stage=loc("stage"), Panels=loc("panels"), Ready=loc("ready"), Random=loc("random"), Leave=loc("leave")} + local to_p = loc_str[pstr] + gprintf( not to_p and pstr or to_p, render_x+x_add, render_y+y_add,width_for_alignment,halign) + end + end + + print("got to LOC before net_vs_room character select loop") + menu_clock = 0 + + local v_align_center = { __Ready=true, __Random=true, __Leave=true } + local is_special_value = { __Leave=true, __Level=true, __Panels=true, __Ready=true, __Stage=true, __Mode=true, __Random=true } + + while true do + -- draw the buttons, handle horizontal spans + for i=1,X do + for j=1,Y do + local value = map[current_page][i][j] + local span_width = 1 + if is_special_value[value] then + if j == 1 or map[current_page][i][j-1] ~= value then + -- detect how many blocks the special value spans + if j ~= Y then + for u=j+1,Y do + if map[current_page][i][u] == value then + span_width = span_width + 1 + else + break + end + end + end + else + -- has already been drawn + span_width = 0 + end + end + + if span_width ~= 0 then + draw_button(i,j,span_width,1,value,"center", v_align_center[value] and "center" or "top" ) + end + end + end + + if select_screen.character_select_mode == "2p_net_vs" then + local messages = server_queue:pop_all_with("win_counts", "menu_state", "ranked_match_approved", "leave_room", "match_start", "ranked_match_denied") + if global_initialize_room_msg then + messages[#messages+1] = global_initialize_room_msg + global_initialize_room_msg = nil + end + for _,msg in ipairs(messages) do + if msg.win_counts then + update_win_counts(msg.win_counts) + end + if msg.menu_state then + if currently_spectating then + if msg.player_number == 1 or msg.player_number == 2 then + cursor_data[msg.player_number].state = msg.menu_state + refresh_based_on_own_mods(cursor_data[msg.player_number].state) + character_loader_load(cursor_data[msg.player_number].state.character) + stage_loader_load(cursor_data[msg.player_number].state.stage) + end + else + cursor_data[2].state = msg.menu_state + refresh_based_on_own_mods(cursor_data[2].state) + character_loader_load(cursor_data[2].state.character) + stage_loader_load(cursor_data[2].state.stage) + end + refresh_loaded_and_ready(cursor_data[1],cursor_data[2]) + end + if msg.ranked_match_approved then + match_type = "Ranked" + match_type_message = "" + if msg.caveats then + match_type_message = match_type_message..(msg.caveats[1] or "") + end + elseif msg.ranked_match_denied then + match_type = "Casual" + match_type_message = (loc("ss_not_ranked") or "").." " + if msg.reasons then + match_type_message = match_type_message..(msg.reasons[1] or loc("ss_err_no_reason")) + end + end + if msg.leave_room then + my_win_count = 0 + op_win_count = 0 + return main_dumb_transition, {main_net_vs_lobby, "", 0, 0} + end + if msg.match_start or replay_of_match_so_far then + print("currently_spectating: "..tostring(currently_spectating)) + local fake_P1 = P1 + local fake_P2 = P2 + refresh_based_on_own_mods(msg.opponent_settings) + refresh_based_on_own_mods(msg.player_settings, true) + refresh_based_on_own_mods(msg) -- for stage only, other data are meaningless to us + -- mainly for spectator mode, those characters have already been loaded otherwise + character_loader_load(msg.player_settings.character) + character_loader_load(msg.opponent_settings.character) + current_stage = msg.stage + stage_loader_load(msg.stage) + character_loader_wait() + stage_loader_wait() + P1 = Stack(1, "vs", msg.player_settings.panels_dir, msg.player_settings.level, msg.player_settings.character, msg.player_settings.player_number) + P1.cur_wait_time = default_input_repeat_delay -- this enforces default cur_wait_time for online games. It is yet to be decided if we want to allow this to be custom online. + P1.enable_analytics = not currently_spectating and not replay_of_match_so_far + P2 = Stack(2, "vs", msg.opponent_settings.panels_dir, msg.opponent_settings.level, msg.opponent_settings.character, msg.opponent_settings.player_number) + P2.cur_wait_time = default_input_repeat_delay -- this enforces default cur_wait_time for online games. It is yet to be decided if we want to allow this to be custom online. + if currently_spectating then + P1.panel_buffer = fake_P1.panel_buffer + P1.gpanel_buffer = fake_P1.gpanel_buffer + end + P2.panel_buffer = fake_P2.panel_buffer + P2.gpanel_buffer = fake_P2.gpanel_buffer + P1.garbage_target = P2 + P2.garbage_target = P1 + move_stack(P2,2) + replay.vs = {P="",O="",I="",Q="",R="",in_buf="", + P1_level=P1.level,P2_level=P2.level, + P1_name=my_name, P2_name=op_name, + P1_char=P1.character,P2_char=P2.character, + P1_cur_wait_time=P1.cur_wait_time, P2_cur_wait_time=P2.cur_wait_time, + ranked=msg.ranked, do_countdown=true} + if currently_spectating and replay_of_match_so_far then --we joined a match in progress + replay.vs = replay_of_match_so_far.vs + P1.input_buffer = replay_of_match_so_far.vs.in_buf + P1.panel_buffer = replay_of_match_so_far.vs.P + P1.gpanel_buffer = replay_of_match_so_far.vs.Q + P2.input_buffer = replay_of_match_so_far.vs.I + P2.panel_buffer = replay_of_match_so_far.vs.O + P2.gpanel_buffer = replay_of_match_so_far.vs.R + if replay.vs.ranked then + match_type = "Ranked" + match_type_message = "" + else + match_type = "Casual" + end + replay_of_match_so_far = nil + P1.play_to_end = true --this makes foreign_run run until caught up + P2.play_to_end = true + end + if not currently_spectating then + ask_for_gpanels("000000") + ask_for_panels("000000") + end + to_print = loc("pl_game_start").."\n"..loc("level")..": "..P1.level.."\n"..loc("opponent_level")..": "..P2.level + if P1.play_to_end or P2.play_to_end then + to_print = loc("pl_spectate_join") + end + for i=1,30 do + gprint(to_print,unpack(main_menu_screen_pos)) + if not do_messages() then + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} + end + wait() + end + local game_start_timeout = 0 + while P1.panel_buffer == "" or P2.panel_buffer == "" + or P1.gpanel_buffer == "" or P2.gpanel_buffer == "" do + --testing getting stuck here at "Game is starting" + game_start_timeout = game_start_timeout + 1 + print("game_start_timeout = "..game_start_timeout) + print("P1.panel_buffer = "..P1.panel_buffer) + print("P2.panel_buffer = "..P2.panel_buffer) + print("P1.gpanel_buffer = "..P1.gpanel_buffer) + print("P2.gpanel_buffer = "..P2.gpanel_buffer) + gprint(to_print,unpack(main_menu_screen_pos)) + if not do_messages() then + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} + end + wait() + if game_start_timeout > 250 then + return main_dumb_transition, {main_select_mode, + loc("pl_time_out").."\n" + .."\n".."msg.match_start = "..(tostring(msg.match_start) or "nil") + .."\n".."replay_of_match_so_far = "..(tostring(replay_of_match_so_far) or "nil") + .."\n".."P1.panel_buffer = "..P1.panel_buffer + .."\n".."P2.panel_buffer = "..P2.panel_buffer + .."\n".."P1.gpanel_buffer = "..P1.gpanel_buffer + .."\n".."P2.gpanel_buffer = "..P2.gpanel_buffer, + 180} + end + love.timer.sleep(0.017) + end + P1:starting_state() + P2:starting_state() + return main_dumb_transition, {main_net_vs, "", 0, 0} + end + end + end + + local my_rating_difference = "" + local op_rating_difference = "" + if current_server_supports_ranking and not global_current_room_ratings[my_player_number].placement_match_progress then + if global_current_room_ratings[my_player_number].difference then + if global_current_room_ratings[my_player_number].difference>= 0 then + my_rating_difference = "(+"..global_current_room_ratings[my_player_number].difference..") " + else + my_rating_difference = "("..global_current_room_ratings[my_player_number].difference..") " + end + end + if global_current_room_ratings[op_player_number].difference then + if global_current_room_ratings[op_player_number].difference >= 0 then + op_rating_difference = "(+"..global_current_room_ratings[op_player_number].difference..") " + else + op_rating_difference = "("..global_current_room_ratings[op_player_number].difference..") " + end + end + end + local function get_player_state_str(player_number, rating_difference, win_count, op_win_count, expected_win_ratio) + local state = "" + if current_server_supports_ranking then + state = state..loc("ss_rating").." "..(global_current_room_ratings[player_number].league or "") + if not global_current_room_ratings[player_number].placement_match_progress then + state = state.."\n"..rating_difference..global_current_room_ratings[player_number].new + elseif global_current_room_ratings[player_number].placement_match_progress + and global_current_room_ratings[player_number].new + and global_current_room_ratings[player_number].new == 0 then + state = state.."\n"..global_current_room_ratings[player_number].placement_match_progress + end + end + if select_screen.character_select_mode == "2p_net_vs" or select_screen.character_select_mode == "2p_local_vs" then + if current_server_supports_ranking then + state = state.."\n" + end + state = state..loc("ss_wins").." "..win_count + if (current_server_supports_ranking and expected_win_ratio) or win_count + op_win_count > 0 then + state = state.."\n"..loc("ss_winrate").."\n" + local need_line_return = false + if win_count + op_win_count > 0 then + state = state.." "..loc("ss_current_rating").." "..(100*round(win_count/(op_win_count+win_count),2)).."%" + need_line_return = true + end + if current_server_supports_ranking and expected_win_ratio then + if need_line_return then + state = state.."\n" + end + state = state.." "..loc("ss_expected_rating").." "..expected_win_ratio.."%" + end + end + end + return state + end + draw_button(0,1,1,1,"P1") + draw_button(0,2,2,1,get_player_state_str(my_player_number,my_rating_difference,my_win_count,op_win_count,my_expected_win_ratio),"left","top",true) + if cursor_data[1].state and op_name then + draw_button(0,7,1,1,"P2") + draw_button(0,8,2,1,get_player_state_str(op_player_number,op_rating_difference,op_win_count,my_win_count,op_expected_win_ratio),"left","top",true) + --state = state.." "..json.encode(op_state) + end + if select_screen.character_select_mode == "2p_net_vs" then + if not cursor_data[1].state.ranked and not cursor_data[2].state.ranked then + match_type_message = "" + end + local match_type_str = "" + if match_type == "Casual" then + match_type_str = loc("ss_casual") + elseif match_type == "Ranked" then + match_type_str = loc("ss_ranked") + end + gprintf(match_type_str, 0, 15, canvas_width, "center") + gprintf(match_type_message, 0, 30, canvas_width, "center") + end + if pages_amount ~= 1 then + gprintf(loc("page").." "..current_page.."/"..pages_amount, 0, 660, canvas_width, "center") + end + wait() + + local ret = nil + + local function move_cursor(cursor,direction) + local cursor_pos = cursor.position + local dx,dy = unpack(direction) + local can_x,can_y = wrap(1, cursor_pos[1]+dx, X), wrap(1, cursor_pos[2]+dy, Y) + while can_x ~= cursor_pos[1] or can_y ~= cursor_pos[2] do + if map[current_page][can_x][can_y] and ( map[current_page][can_x][can_y] ~= map[current_page][cursor_pos[1]][cursor_pos[2]] or + map[current_page][can_x][can_y] == "__Empty" or map[current_page][can_x][can_y] == "__Reserved" ) then + break + end + can_x,can_y = wrap(1, can_x+dx, X), wrap(1, can_y+dy, Y) + end + cursor_pos[1],cursor_pos[2] = can_x,can_y + local character = characters[map[current_page][can_x][can_y]] + cursor.can_super_select = character and ( character.stage or character.panels ) + end + + local function change_panels_dir(panels_dir,increment) + local current = 0 + for k,v in ipairs(panels_ids) do + if v == panels_dir then + current = k + break + end + end + local dir_count = #panels_ids + local new_theme_idx = ((current - 1 + increment) % dir_count) + 1 + for k,v in ipairs(panels_ids) do + if k == new_theme_idx then + return v + end + end + return panels_dir + end + + local function change_stage(state,increment) + -- random_stage_special_value is placed at the end of the list and is 'replaced' by a random pick and stage_is_random=true + local current = nil + for k,v in ipairs(stages_ids_for_current_theme) do + if ( not state.stage_is_random and v == state.stage ) + or ( state.stage_is_random and v == state.stage_is_random ) then + current = k + break + end + end + if state.stage == nil or state.stage_is_random == random_stage_special_value then + current = #stages_ids_for_current_theme+1 + end + if current == nil then -- stage belonged to another set of stages, it's no more in the list + current = 0 + end + local dir_count = #stages_ids_for_current_theme + 1 + local new_stage_idx = ((current - 1 + increment) % dir_count) + 1 + if new_stage_idx <= #stages_ids_for_current_theme then + local new_stage = stages_ids_for_current_theme[new_stage_idx] + if stages[new_stage]:is_bundle() then + state.stage_is_random = new_stage + state.stage = uniformly(stages[new_stage].sub_stages) + else + state.stage_is_random = nil + state.stage = new_stage + end + else + state.stage_is_random = random_stage_special_value + state.stage = uniformly(stages_ids_for_current_theme) + if stages[state.stage]:is_bundle() then -- may pick a bundle! + state.stage = uniformly(stages[state.stage].sub_stages) + end + end + print("stage and stage_is_random: "..state.stage.." / "..(state.stage_is_random or "nil")) + end + + local function on_quit() + if themes[config.theme].musics.select_screen then + stop_the_music() + end + if select_screen.character_select_mode == "2p_net_vs" then + if not do_leave() then + ret = {main_dumb_transition, {main_select_mode, loc("ss_error_leave"), 60, 300}} + end + else + ret = {main_select_mode} + end + end + + local function on_select(cursor,super) + local noisy = false + local selectable = {__Stage=true, __Panels=true, __Level=true, __Ready=true} + if selectable[cursor.state.cursor] then + if cursor.selected and cursor.state.cursor == "__Stage" then + -- load stage even if hidden! + stage_loader_load(cursor.state.stage) + end + cursor.selected = not cursor.selected + elseif cursor.state.cursor == "__Leave" then + on_quit() + elseif cursor.state.cursor == "__Random" then + cursor.state.character_is_random = random_character_special_value + cursor.state.character = uniformly(characters_ids_for_current_theme) + if characters[cursor.state.character]:is_bundle() then -- may pick a bundle + cursor.state.character = uniformly(characters[cursor.state.character].sub_characters) + end + cursor.state.character_display_name = characters[cursor.state.character].display_name + character_loader_load(cursor.state.character) + cursor.state.cursor = "__Ready" + cursor.position = shallowcpy(name_to_xy_per_page[current_page]["__Ready"]) + cursor.can_super_select = false + elseif cursor.state.cursor == "__Mode" then + cursor.state.ranked = not cursor.state.ranked + elseif ( cursor.state.cursor ~= "__Empty" and cursor.state.cursor ~= "__Reserved" ) then + cursor.state.character_is_random = nil + cursor.state.character = cursor.state.cursor + if characters[cursor.state.character]:is_bundle() then -- may pick a bundle + cursor.state.character_is_random = cursor.state.character + cursor.state.character = uniformly(characters[cursor.state.character_is_random].sub_characters) + end + cursor.state.character_display_name = characters[cursor.state.character].display_name + local character = characters[cursor.state.character] + if not cursor.state.character_is_random then + noisy = character:play_selection_sfx() + elseif characters[cursor.state.character_is_random] then + noisy = characters[cursor.state.character_is_random]:play_selection_sfx() + end + character_loader_load(cursor.state.character) + if super then + if character.stage then + cursor.state.stage = character.stage + stage_loader_load(cursor.state.stage) + cursor.state.stage_is_random = false + end + if character.panels then + cursor.state.panels_dir = character.panels + end + end + --When we select a character, move cursor to "__Ready" + cursor.state.cursor = "__Ready" + cursor.position = shallowcpy(name_to_xy_per_page[current_page]["__Ready"]) + cursor.can_super_select = false + end + return noisy + end + + variable_step(function() + menu_clock = menu_clock + 1 + + character_loader_update() + stage_loader_update() + refresh_loaded_and_ready(cursor_data[1].state,cursor_data[2] and cursor_data[2].state or nil) + + local up,down,left,right = {-1,0}, {1,0}, {0,-1}, {0,1} + if not currently_spectating then + local KMax = 1 + if select_screen.character_select_mode == "2p_local_vs" then + KMax = 2 + end + for i=1,KMax do + local k=K[i] + local cursor = cursor_data[i] + if menu_prev_page(k) then + if not cursor.selected then current_page = bound(1, current_page-1, pages_amount) end + elseif menu_next_page(k) then + if not cursor.selected then current_page = bound(1, current_page+1, pages_amount) end + elseif menu_up(k) then + if not cursor.selected then move_cursor(cursor,up) end + elseif menu_down(k) then + if not cursor.selected then move_cursor(cursor,down) end + elseif menu_left(k) then + if cursor.selected then + if cursor.state.cursor == "__Level" then + cursor.state.level = bound(1, cursor.state.level-1, #level_to_starting_speed) --which should equal the number of levels in the game + elseif cursor.state.cursor == "__Panels" then + cursor.state.panels_dir = change_panels_dir(cursor.state.panels_dir,-1) + elseif cursor.state.cursor == "__Stage" then + change_stage(cursor.state,-1) + end + end + if not cursor.selected then move_cursor(cursor,left) end + elseif menu_right(k) then + if cursor.selected then + if cursor.state.cursor == "__Level" then + cursor.state.level = bound(1, cursor.state.level+1, #level_to_starting_speed) --which should equal the number of levels in the game + elseif cursor.state.cursor == "__Panels" then + cursor.state.panels_dir = change_panels_dir(cursor.state.panels_dir,1) + elseif cursor.state.cursor == "__Stage" then + change_stage(cursor.state,1) + end + end + if not cursor.selected then move_cursor(cursor,right) end + else + -- code below is bit hard to read: basically we are storing the default sfx callbacks until it's needed (or not!) based on the on_select method + local long_enter, long_enter_callback = menu_long_enter(k, true) + local normal_enter, normal_enter_callback = menu_enter(k, true) + if long_enter then + if not on_select(cursor, true) then + long_enter_callback() + end + elseif normal_enter and (not cursor.can_super_select or menu_pressing_enter(k) < super_selection_enable_ratio) then + if not on_select(cursor, false) then + normal_enter_callback() + end + elseif menu_escape(k) then + if cursor.state.cursor == "__Leave" then + on_quit() + end + cursor.selected = false + cursor.position = shallowcpy(name_to_xy_per_page[current_page]["__Leave"]) + cursor.can_super_select = false + end + end + if cursor.state ~= nil then + cursor.state.cursor = map[current_page][cursor.position[1]][cursor.position[2]] + cursor.state.wants_ready = cursor.selected and cursor.state.cursor=="__Ready" + end + end + -- update config, does not redefine it + config.character = cursor_data[1].state.character_is_random and cursor_data[1].state.character_is_random or cursor_data[1].state.character + config.stage = cursor_data[1].state.stage_is_random and cursor_data[1].state.stage_is_random or cursor_data[1].state.stage + config.level = cursor_data[1].state.level + config.ranked = cursor_data[1].state.ranked + config.panels = cursor_data[1].state.panels_dir + + if select_screen.character_select_mode == "2p_local_vs" then -- this is registered for future entering of the lobby + global_op_state = shallowcpy(cursor_data[2].state) + global_op_state.character = global_op_state.character_is_random and global_op_state.character_is_random or global_op_state.character + global_op_state.stage = global_op_state.stage_is_random and global_op_state.stage_is_random or global_op_state.stage + global_op_state.wants_ready = false + end + + if select_screen.character_select_mode == "2p_net_vs" and not content_equal(cursor_data[1].state, prev_state) and not currently_spectating then + json_send({menu_state=cursor_data[1].state}) + end + prev_state = shallowcpy(cursor_data[1].state) + + else -- (we are spectating) + if menu_escape(K[1]) then + do_leave() + ret = {main_net_vs_lobby} + end + end + end) + if ret then + return unpack(ret) + end + if cursor_data[1].state.ready and select_screen.character_select_mode == "1p_vs_yourself" then + P1 = Stack(1, "vs", cursor_data[1].state.panels_dir, cursor_data[1].state.level, cursor_data[1].state.character) + P1.enable_analytics = true + P1.garbage_target = P1 + make_local_panels(P1, "000000") + make_local_gpanels(P1, "000000") + current_stage = cursor_data[1].state.stage + stage_loader_load(current_stage) + stage_loader_wait() + P1:starting_state() + return main_dumb_transition, {main_local_vs_yourself, "", 0, 0} + elseif cursor_data[1].state.ready and select_screen.character_select_mode == "2p_local_vs" and cursor_data[2].state.ready then + P1 = Stack(1, "vs", cursor_data[1].state.panels_dir, cursor_data[1].state.level, cursor_data[1].state.character) + P1.enable_analytics = true + P2 = Stack(2, "vs", cursor_data[2].state.panels_dir, cursor_data[2].state.level, cursor_data[2].state.character) + P1.garbage_target = P2 + P2.garbage_target = P1 + current_stage = cursor_data[math.random(1,2)].state.stage + stage_loader_load(current_stage) + stage_loader_wait() + move_stack(P2,2) + -- TODO: this does not correctly implement starting configurations. + -- Starting configurations should be identical for visible blocks, and + -- they should not be completely flat. + -- + -- In general the block-generation logic should be the same as the server's, so + -- maybe there should be only one implementation. + make_local_panels(P1, "000000") + make_local_gpanels(P1, "000000") + make_local_panels(P2, "000000") + make_local_gpanels(P2, "000000") + P1:starting_state() + P2:starting_state() + return main_dumb_transition, {main_local_vs, "", 0, 0} + elseif select_screen.character_select_mode == "2p_net_vs" then + if not do_messages() then + return main_dumb_transition, {main_select_mode, loc("ss_disconnect").."\n\n"..loc("ss_return"), 60, 300} + end + end + end +end + +return select_screen diff --git a/server.lua b/server.lua index 7ad87cbd..19774e7f 100644 --- a/server.lua +++ b/server.lua @@ -20,14 +20,10 @@ local floor = math.floor local TIMEOUT = 10 local CHARACTERSELECT = "character select" -- room states local PLAYING = "playing" -- room states -local DEFAULT_RATING = 1600 -local RATING_SPREAD_MODIFIER = 400 -local PLACEMENT_MATCH_K = 50 -local NAME_LENGTH_LIMIT = 16 local sep = package.config:sub(1, 1) --determines os directory separator (i.e. "/" or "\") -local VERSION = "037" +local VERSION = "045" local type_to_length = {H=4, E=4, F=4, P=8, I=2, L=2, Q=8, U=2} local INDEX = 1 local connections = {} @@ -49,9 +45,9 @@ function lobby_state() end local spectatableRooms = {} for _,v in pairs(rooms) do - spectatableRooms[#spectatableRooms+1] = {roomNumber = v.roomNumber, name = v.name , a = v.a.name, b = v.b.name, state = v:state()} + spectatableRooms[#spectatableRooms+1] = {roomNumber=v.roomNumber, name=v.name , a=v.a.name, b=v.b.name, state=v:state()} end - return {unpaired = names, spectatable = spectatableRooms} + return {unpaired = names, spectatable=spectatableRooms} end function propose_game(sender, receiver, message) @@ -95,20 +91,26 @@ function create_room(a, b) a_msg.your_player_number = 1 a_msg.op_player_number = 2 a_msg.opponent = new_room.b.name + b_msg.opponent = new_room.a.name new_room.b.cursor = "__Ready" - a_msg.menu_state = new_room.b:menu_state() + new_room.a.cursor = "__Ready" b_msg.your_player_number = 2 b_msg.op_player_number = 1 - b_msg.opponent = new_room.a.name - new_room.a.cursor = "__Ready" - b_msg.menu_state = new_room.a:menu_state() - a_msg.ratings = new_room.ratings - b_msg.ratings = new_room.ratings + a_msg.a_menu_state = new_room.a:menu_state() + a_msg.b_menu_state = new_room.b:menu_state() + b_msg.b_menu_state = new_room.b:menu_state() + b_msg.a_menu_state = new_room.a:menu_state() new_room.a.opponent = new_room.b new_room.b.opponent = new_room.a + + new_room:prepare_character_select() + a_msg.ratings = new_room.ratings + b_msg.ratings = new_room.ratings + a_msg.rating_updates = true + b_msg.rating_updates = true + new_room.a:send(a_msg) new_room.b:send(b_msg) - new_room:character_select() end @@ -122,7 +124,9 @@ function start_match(a, b) print("ERROR: player a still doesn't have player_number 1.") end end - local msg = {match_start = true, ranked = false, + + a.room.stage = math.random(1,2)==1 and a.stage or b.stage + local msg = {match_start = true, ranked = false, stage=a.room.stage, player_settings = {character = a.character, character_display_name=a.character_display_name, level = a.level, panels_dir = a.panels_dir, player_number = a.player_number}, opponent_settings = {character = b.character, character_display_name=b.character_display_name, level = b.level, panels_dir = b.panels_dir, player_number = b.player_number}} local room_is_ranked, reasons = a.room:rating_adjustment_approved() @@ -163,6 +167,7 @@ Room = class(function(self, a, b) --TODO: it would be nice to call players a and b something more like self.players[1] and self.players[2] self.a = a --player a self.b = b --player b + self.stage = nil self.name = a.name.." vs "..b.name if not self.a.room or not self.b.room then self.roomNumber = ROOMNUMBER @@ -206,6 +211,11 @@ Room = class(function(self, a, b) end) function Room.character_select(self) + self:prepare_character_select() + self:send({character_select=true, create_room=true, rating_updates=true, ratings=self.ratings, a_menu_state=self.a:menu_state(), b_menu_state=self.b:menu_state()}) +end + +function Room.prepare_character_select(self) print("Called Server.lua Room.character_select") self.a.state = "character select" self.b.state = "character select" @@ -225,7 +235,6 @@ function Room.character_select(self) self.b.cursor = "__Ready" self.a.ready = false self.b.ready = false - self:send({character_select=true, create_room=true, rating_updates=true, ratings=self.ratings, a_menu_state=self.a:menu_state(), b_menu_state=self.b:menu_state()}) -- local msg = {spectate_request_granted = true, spectate_request_rejected = false, rating_updates=true, ratings=self.ratings, a_menu_state=self.a:menu_state(), b_menu_state=self.b:menu_state()} -- for k,v in ipairs(self.spectators) do -- self.spectators[k]:send(msg) @@ -251,7 +260,7 @@ function Room.add_spectator(self, new_spectator_connection) new_spectator_connection.room = self self.spectators[#self.spectators+1] = new_spectator_connection print(new_spectator_connection.name .. " joined " .. self.name .. " as a spectator") - msg = {spectate_request_granted = true, spectate_request_rejected = false, rating_updates=true, ratings=self.ratings, a_menu_state=self.a:menu_state(), b_menu_state=self.b:menu_state(), win_counts=self.win_counts, match_start=replay_of_match_so_far~=nil, replay_of_match_so_far = self.replay, ranked = self:rating_adjustment_approved(), + msg = {spectate_request_granted = true, spectate_request_rejected = false, rating_updates=true, ratings=self.ratings, a_menu_state=self.a:menu_state(), b_menu_state=self.b:menu_state(), win_counts=self.win_counts, match_start=replay_of_match_so_far~=nil, stage=self.stage, replay_of_match_so_far = self.replay, ranked = self:rating_adjustment_approved(), player_settings = {character = self.a.character, character_display_name=self.a.character_display_name, level = self.a.level, player_number = self.a.player_number}, opponent_settings = {character = self.b.character, character_display_name=self.b.character_display_name, level = self.b.level, player_number = self.b.player_number}} new_spectator_connection:send(msg) @@ -276,7 +285,6 @@ function Room.remove_spectator(self, connection) print(connection.name .. " left " .. self.name .. " as a spectator") self.spectators[k] = nil lobby_changed = true - connection:send(lobby_state()) end end msg = {spectators=self:spectator_names()} @@ -285,32 +293,26 @@ function Room.remove_spectator(self, connection) end function Room.close(self) - --TODO: notify spectators that the room has closed. - if self.a then - self.a.player_number = 0 - self.a.state = "lobby" - print("In Room.close. Setting room for Player A "..(self.a.name or "nil").." as nil") - self.a.room = nil - end - if self.b then - self.b.player_number = 0 - self.b.state = "lobby" - print("In Room.close. Setting room for Player B "..(self.b.name or "nil").." as nil") - self.b.room = nil - end - for k,v in pairs(self.spectators) do - if v.room then - print("In Room.close. Setting room for spectator "..(v.name or "nil").." as nil") - v.room = nil - v.state = "lobby" - end - end - if rooms[self.roomNumber] then - rooms[self.roomNumber] = nil + if self.a then + self.a.player_number = 0 + self.a.state = "lobby" + self.a.room = nil + end + if self.b then + self.b.player_number = 0 + self.b.state = "lobby" + self.b.room = nil + end + for k,v in pairs(self.spectators) do + if v.room then + v.room = nil + v.state = "lobby" end - local msg = lobby_state() - msg.leave_room = true - self:send_to_spectators(msg) + end + if rooms[self.roomNumber] then + rooms[self.roomNumber] = nil + end + self:send_to_spectators({leave_room = true}) end function roomNumberToRoom(roomNr) @@ -436,7 +438,7 @@ Connection = class(function(s, socket) end) function Connection.menu_state(self) - state = {cursor=self.cursor, ready=self.ready, character=self.character, character_display_name=self.character_display_name, panels_dir=self.panels_dir, level=self.level, ranked=self.wants_ranked_match} + state = {cursor=self.cursor, stage=self.stage, stage_is_random=self.stage_is_random, ready=self.ready, character=self.character, character_is_random=self.character_is_random, character_display_name=self.character_display_name, panels_dir=self.panels_dir, level=self.level, ranked=self.wants_ranked_match} return state --note: player_number here is the player_number of the connection as according to the server, not the "which" of any Stack end @@ -446,11 +448,11 @@ function Connection.send(self, stuff) local json = json.encode(stuff) local len = json:len() local prefix = "J"..char(floor(len/65536))..char(floor((len/256)%256))..char(len%256) - print(byte(prefix[1]), byte(prefix[2]), byte(prefix[3]), byte(prefix[4])) + --print(byte(prefix[1]), byte(prefix[2]), byte(prefix[3]), byte(prefix[4])) print("sending json "..json) stuff = prefix..json else - if stuff[1] ~= "I" and stuff[1] ~= "U" then + if stuff[1] ~= "I" and stuff[1] ~= "U" and stuff[1] ~= "E" then print("sending non-json "..stuff) end end @@ -458,11 +460,8 @@ function Connection.send(self, stuff) local times_to_retry = 5 local foo = {} while not foo[1] and retry_count <= 5 do - if retry_count ~= 0 then - print("retry number: "..retry_count) - end foo = {self.socket:send(stuff)} - if stuff[1] ~= "I" and stuff[1] ~= "U" then + if stuff[1] ~= "I" and stuff[1] ~= "U" and stuff[1] ~= "E" then print(unpack(foo)) end if not foo[1] then @@ -471,13 +470,8 @@ function Connection.send(self, stuff) end end if not foo[1] then - print("About to close connection for "..(self.name or "nil")..". During Connection.send, foo[1] was nil after "..times_to_retry.." retries were attempted") - print("foo:") - print(unpack(foo)) - print("closing connection") + print("Closing connection for "..(self.name or "nil")..". During Connection.send, foo[1] was nil after "..times_to_retry.." retries were attempted") self:close() - elseif retry_count ~= 0 then - print("SUCCESS after retries: connection.send for "..(self.name or "nil").." took "..retry_count.." retries") end end @@ -522,6 +516,11 @@ function Connection.login(self, user_id) else deny_login(self, "Unknown") end + + if self.logged_in then + self:send(lobby_state()) + end + return self.logged_in end @@ -566,13 +565,11 @@ function Connection.opponent_disconnected(self) self.opponent = nil self.state = "lobby" lobby_changed = true - local msg = lobby_state() - msg.leave_room = true if self.room then - print("about to close room for "..(self.name or "nil").." because opponent disconnected.") + print("Closing room for "..(self.name or "nil").." because opponent disconnected.") self.room:close() end - self:send(msg) + self:send({leave_room = true}) end function Connection.setup_game(self) @@ -745,13 +742,18 @@ function Room.rating_adjustment_approved(self) local caveats = {} local prev_player_level = players[1].level local both_players_are_placed = nil - if leaderboard.players[players[1].user_id] and leaderboard.players[players[1].user_id].placement_done - and leaderboard.players[players[2].user_id] and leaderboard.players[players[2].user_id].placement_done then - both_players_are_placed = true + + if PLACEMENT_MATCHES_ENABLED then + if leaderboard.players[players[1].user_id] and leaderboard.players[players[1].user_id].placement_done + and leaderboard.players[players[2].user_id] and leaderboard.players[players[2].user_id].placement_done then + both_players_are_placed = true --both players are placed on the leaderboard. - elseif not (leaderboard.players[players[1].user_id] and leaderboard.players[players[1].user_id].placement_done) - and not (leaderboard.players[players[2].user_id] and leaderboard.players[players[2].user_id].placement_done) then - reasons[#reasons+1] = "Neither player has finished enough placement matches against already ranked players" + elseif not (leaderboard.players[players[1].user_id] and leaderboard.players[players[1].user_id].placement_done) + and not (leaderboard.players[players[2].user_id] and leaderboard.players[players[2].user_id].placement_done) then + reasons[#reasons+1] = "Neither player has finished enough placement matches against already ranked players" + end + else + both_players_are_placed = true end --don't let players too far apart in rating play ranked @@ -769,10 +771,19 @@ function Room.rating_adjustment_approved(self) ratings[k] = DEFAULT_RATING end end - if math.abs(ratings[1] - ratings[2]) > RATING_SPREAD_MODIFIER * .9 then + if math.abs(ratings[1] - ratings[2]) > RATING_SPREAD_MODIFIER * ALLOWABLE_RATING_SPREAD_MULITPLIER then reasons[#reasons+1] = "Players' ratings are too far apart" end - + + local player_level_out_of_bounds_for_ranked = false + for i=1,2 do --we'll change 2 here when more players are allowed. + if (players[i].level < MIN_LEVEL_FOR_RANKED or players[i].level > MAX_LEVEL_FOR_RANKED) then + player_level_out_of_bounds_for_ranked = true + end + end + if player_level_out_of_bounds_for_ranked then + reasons[#reasons+1] = "Only levels between "..MIN_LEVEL_FOR_RANKED.." and "..MAX_LEVEL_FOR_RANKED.." are allowed for ranked play." + end if players[1].level ~= players[2].level then reasons[#reasons+1] = "Levels don't match" end @@ -787,7 +798,7 @@ function Room.rating_adjustment_approved(self) if reasons[1] then return false, reasons else - if not both_players_are_placed + if PLACEMENT_MATCHES_ENABLED and not both_players_are_placed and ((leaderboard.players[players[1].user_id] and leaderboard.players[players[1].user_id].placement_done) or (leaderboard.players[players[2].user_id] and leaderboard.players[players[2].user_id].placement_done)) then @@ -839,6 +850,9 @@ function adjust_ratings(room, winning_player_number) if not leaderboard.players[players[player_number].user_id] or not leaderboard.players[players[player_number].user_id].rating then leaderboard.players[players[player_number].user_id] = {user_name=playerbase.players[players[player_number].user_id], rating=DEFAULT_RATING} print("Gave "..playerbase.players[players[player_number].user_id].." a new rating of "..DEFAULT_RATING) + if not PLACEMENT_MATCHES_ENABLED then + leaderboard.players[players[player_number].user_id].placement_done = true + end write_leaderboard_file() end end @@ -1012,7 +1026,9 @@ function qualifies_for_placement(user_id) --local placement_match_win_ratio_requirement = .2 load_placement_matches(user_id) local placement_matches_played = #loaded_placement_matches.incomplete[user_id] - if leaderboard.players[user_id] and leaderboard.players[user_id].placement_done then + if not PLACEMENT_MATCHES_ENABLED then + return false, "" + elseif (leaderboard.players[user_id] and leaderboard.players[user_id].placement_done) then return false, "user is already placed" elseif placement_matches_played < PLACEMENT_MATCH_COUNT_REQUIREMENT then return false, placement_matches_played.."/"..PLACEMENT_MATCH_COUNT_REQUIREMENT.." placement matches played." @@ -1103,7 +1119,6 @@ end function Connection.F(self, message) end - local ok_ncolors = {} for i=2,7 do ok_ncolors[i..""] = true @@ -1178,16 +1193,26 @@ function Connection.J(self, message) else self.name = message.name self.character = message.character + self.character_is_random = message.character_is_random self.character_display_name = message.character_display_name + self.stage = message.stage + self.stage_is_random = message.stage_is_random self.panels_dir = message.panels_dir self.level = message.level self.save_replays_publicly = message.save_replays_publicly + self.wants_ranked_match = message.ranked lobby_changed = true self.state = "lobby" name_to_idx[self.name] = self.index end + elseif message.taunt then + message.player_number = self.player_number + self.opponent:send(message) + self.room:send_to_spectators(message) elseif message.login_request then self:login(message.user_id) + elseif message.logout then + self:close() elseif self.state == "lobby" and message.game_request then if message.game_request.sender == self.name then propose_game(message.game_request.sender, message.game_request.receiver, message) @@ -1222,7 +1247,10 @@ function Connection.J(self, message) elseif self.state == "character select" and message.menu_state then self.level = message.menu_state.level self.character = message.menu_state.character + self.character_is_random = message.menu_state.character_is_random self.character_display_name = message.menu_state.character_display_name + self.stage = message.menu_state.stage + self.stage_is_random = message.menu_state.stage_is_random self.ready = message.menu_state.ready self.cursor = message.menu_state.cursor self.panels_dir = message.menu_state.panels_dir @@ -1257,10 +1285,7 @@ function Connection.J(self, message) else self.opponent:send(message) message.player_number = self.player_number - print("about to send match start to spectators of ") - print(self.name) - print("and") - print(self.opponent.name) + print("about to send match start to spectators of "..(self.name or "nil").. " and "..(self.opponent.name or "nil")) self.room:send_to_spectators(message) -- TODO: may need to include in the message who is sending the message end elseif self.state == "playing" and message.game_over then @@ -1281,6 +1306,10 @@ function Connection.J(self, message) v:opponent_disconnected() end end + --[[local msg = lobby_state() + msg.leave_room = true + self:send(msg) + op:send(msg)--]] elseif (self.state == "spectating") and message.leave_room then self.room:remove_spectator(self) end @@ -1289,7 +1318,7 @@ end -- TODO: this should not be O(n^2) lol function Connection.data_received(self, data) self.last_read = time() - if data:len() ~= 2 then + if data:len() ~= 2 and data[1] ~= "F" then print("got raw data "..data) end data = self.leftovers .. data @@ -1313,7 +1342,7 @@ function Connection.data_received(self, data) end)) data = data:sub(msg_len+5) else - if msg_type ~= "I" then + if msg_type ~= "I" and msg_type ~= "F" then print("using non-J type "..msg_type) end total_len = type_to_length[msg_type] @@ -1329,7 +1358,7 @@ function Connection.data_received(self, data) res = {pcall(function() self[msg_type](self, data:sub(2,total_len)) end)} - if msg_type ~= "I" or not res[1] then + if ( msg_type ~= "I" and msg_type ~= "F" ) or not res[1] then print("got message "..msg_type.." "..data:sub(2,total_len)) print("Pcall results for "..msg_type..": ", unpack(res)) end @@ -1381,7 +1410,7 @@ end end --]] -local server_socket = socket.bind("*", 49569) --for official server +local server_socket = socket.bind("*", SERVER_PORT or 49569) --for official server --local server_socket = socket.bind("*", 59569) --for beta server local sep = package.config:sub(1, 1) print("sep: "..sep) diff --git a/server_file_io.lua b/server_file_io.lua index 7af8fc22..12f88908 100644 --- a/server_file_io.lua +++ b/server_file_io.lua @@ -73,13 +73,17 @@ function write_leaderboard_file() pcall(function() --local csv = "user_id,user_name,rating,placement_done,placement_rating,ranked_games_played,ranked_games_won" local sep = package.config:sub(1, 1) local leaderboard_table = {} + local public_leaderboard_table = {} leaderboard_table[#leaderboard_table+1] = {"user_id","user_name","rating","placement_done","placement_rating","ranked_games_played","ranked_games_won"} - + public_leaderboard_table[#public_leaderboard_table+1] = {"user_name","rating","ranked_games_played"} --excluding ranked_games_won for now because it doesn't track properly, and user_id because they are secret. for user_id,v in pairs(leaderboard.players) do leaderboard_table[#leaderboard_table+1] = {user_id, v.user_name,v.rating,tostring(v.placement_done or ""),v.placement_rating,v.ranked_games_played,v.ranked_games_won} + public_leaderboard_table[#public_leaderboard_table+1] = + {v.user_name,v.rating,v.ranked_games_played} end csvfile.write('.'..sep..'leaderboard.csv', leaderboard_table) + csvfile.write('.'..sep..'ftp'..sep..'PA_public_leaderboard.csv', public_leaderboard_table) end) end diff --git a/server_globals.lua b/server_globals.lua index 5d55bb1f..57cc04d4 100644 --- a/server_globals.lua +++ b/server_globals.lua @@ -6,12 +6,12 @@ panel_color_to_number = { ["A"]=1, ["B"]=2, ["C"]=3, ["D"]=4, ["E"]=5, ["F"]=6, ["0"]=0} leagues = { {league="Newcomer", min_rating = -1000}, {league="Bronze", min_rating = 1}, - {league="Silver", min_rating = 1300}, - {league="Gold", min_rating = 1450}, - {league="Platinum", min_rating = 1650}, - {league="Diamond", min_rating = 1900}, - {league="Master", min_rating = 2250}, - {league="Grandmaster", min_rating = 2350} + {league="Silver", min_rating = 1176}, + {league="Gold", min_rating = 1376}, + {league="Platinum", min_rating = 1519}, + {league="Diamond", min_rating = 1670}, + {league="Master", min_rating = 1926}, + {league="Grandmaster", min_rating = 2139} } --[[leagues = { {league="Newcomer", min_rating = -1000}, {league="Bronze", min_rating = 1}, @@ -22,4 +22,13 @@ leagues = { {league="Newcomer", min_rating = -1000}, {league="Master", min_rating = 2225}, {league="Grandmaster", min_rating = 2475} }]] -PLACEMENT_MATCH_COUNT_REQUIREMENT = 50 \ No newline at end of file +PLACEMENT_MATCH_COUNT_REQUIREMENT = 50 +DEFAULT_RATING = 1500 +RATING_SPREAD_MODIFIER = 400 +ALLOWABLE_RATING_SPREAD_MULITPLIER = .9 --set this to a huge number like 100 if you want everyone to be able to play with anyone, regardless of rating gap +PLACEMENT_MATCH_K = 50 +NAME_LENGTH_LIMIT = 16 +PLACEMENT_MATCHES_ENABLED = false +MIN_LEVEL_FOR_RANKED = 1 +MAX_LEVEL_FOR_RANKED = 10 +SERVER_PORT = 49569 -- default: 49569 \ No newline at end of file diff --git a/server_queue.lua b/server_queue.lua new file mode 100644 index 00000000..75653af8 --- /dev/null +++ b/server_queue.lua @@ -0,0 +1,170 @@ +ServerQueue = class(function(self, capacity) + if not capacity then error("ServerQueue: you need to specify a capacity") end + self.capacity = capacity + self.data = {} + self.first = 0 + self.last = -1 + self.empties = 0 + end) + +function ServerQueue.to_string(self) + ret = "QUEUE: " + for k,v in pairs(self.data) do + ret = ret.."\n"..k..": {" + for a,b in pairs(v) do + ret = ret..a..", " + end + ret = ret.."}" + end + + return ret +end + +function ServerQueue.has_expired(self, msg) + if os.time() > msg._expiration then + str = "ServerQueue: a message has expired ("..(os.time() - msg._expiration)..")\n" + for k, v in pairs(msg) do + str = str..k..", " + end + warning(str.."\n"..self:to_string()) + return true + end + return false +end + +-- push a server message in queue +function ServerQueue.push(self, msg) + local last = self.last + 1 + self.last = last + msg._expiration = os.time() + 5 -- add an expiration date of 5s + self.data[last] = msg + if self:size() > self.capacity then + local first = self.first + self.data[first] = nil + self.first = first + 1 + end +end + +-- pop oldest server message in queue +function ServerQueue.pop(self) + local first = self.first + local ret = nil + + while ret == nil do + if first >= self.last then + first = 0 + self.last = -1 + break + else + ret = self.data[first] + self.data[first] = nil + if ret == nil then + self.empties = self.empties - 1 + else + if self:has_expired(ret) then + self:remove(first) + ret = nil + end + end + first = first + 1 + end + end + + self.first = first + self:check_empty() + + return ret +end + +-- pop first element found with a message containing any specified keys... +function ServerQueue.pop_next_with(self, ...) + if self.first > self.last then + return + end + + local still_empty = true + for i=self.first,self.last do + local msg = self.data[i] + if msg ~= nil then + still_empty = false + for j=1,select('#', ...) do + if msg[select(j, ...)] ~= nil then + --print("POP "..select(j, ...)) + self:remove(i) + if not self:has_expired(msg) then + return msg + end + end + end + elseif still_empty then + self.first = self.first + 1 + self.empties = self.empties - 1 + end + end +end + +-- pop all messages containing any specified keys... +function ServerQueue.pop_all_with(self, ...) + local ret = {} + + if self.first <= self.last then + local still_empty = true + for i=self.first,self.last do + local msg = self.data[i] + if msg ~= nil then + still_empty = false + for j=1,select('#', ...) do + if msg[select(j, ...)] ~= nil then + --print("POP "..select(j, ...)) + ret[#ret+1] = msg + self:remove(i) + if not self:has_expired(msg) then + break + end + end + end + elseif still_empty then + self.first = self.first + 1 + self.empties = self.empties - 1 + end + end + end + return ret +end + +function ServerQueue.remove(self, index) + if self.data[index] then + self.data[index] = nil + self.empties = self.empties + 1 + self:check_empty() + return true + end + return false +end + +function ServerQueue.top(self) + return self.data[self.first] +end + +function ServerQueue.size(self) + return self.last - self.first - self.empties + 1 +end + +function ServerQueue.check_empty(self) + if self:size() == 0 then + self.first = 0 + self.last = -1 + self.empties = 0 + end +end + +function ServerQueue.clear(self) + if self.first >= self.last then + for i=self.first,self.last do + self.data[i]=nil + end + end + self.first = 0 + self.last = -1 + self.empties = 0 +end diff --git a/sound.lua b/sound.lua index 90b602c9..66ad3e6a 100644 --- a/sound.lua +++ b/sound.lua @@ -1,77 +1,14 @@ require("sound_util") -local function find_generic_SFX(SFX_name) - local dirs_to_check = {"sounds/"..config.sounds_dir.."/SFX/", - "sounds/"..default_sounds_dir.."/SFX/"} - return find_sound(SFX_name, dirs_to_check) -end - -local function assert_requirements_met() - --assert we have all required generic sound effects - local SFX_requirements = {"cur_move", "swap", "fanfare1", "fanfare2", "fanfare3", "game_over", "countdown", "go"} - for k,v in ipairs(SFX_requirements) do - assert(sounds.SFX[v], "SFX \""..v.."\" was not loaded") - end - local NUM_REQUIRED_GARBAGE_THUDS = 3 - for i=1, NUM_REQUIRED_GARBAGE_THUDS do - assert(sounds.SFX.garbage_thud[i], "SFX garbage_thud "..i.."was not loaded") - end - for popLevel=1,4 do - for popIndex=1,10 do - assert(sounds.SFX.pops[popLevel][popIndex], "SFX pop"..popLevel.."-"..popIndex.." was not loaded") - end - end -end - -function sound_init() - --sounds: SFX, music - SFX_Fanfare_Play = 0 - SFX_GameOver_Play = 0 - SFX_GarbageThud_Play = 0 - sounds = { - SFX = { - cur_move = find_generic_SFX("move"), - swap = find_generic_SFX("swap"), - land = find_generic_SFX("land"), - fanfare1 = find_generic_SFX("fanfare1"), - fanfare2 = find_generic_SFX("fanfare2"), - fanfare3 = find_generic_SFX("fanfare3"), - game_over = find_generic_SFX("gameover"), - countdown = find_generic_SFX("countdown"), - go = find_generic_SFX("go"), - menu_move = find_generic_SFX("menu_move"), - menu_validate = find_generic_SFX("menu_validate"), - menu_cancel = find_generic_SFX("menu_cancel"), - garbage_thud = { - find_generic_SFX("thud_1"), - find_generic_SFX("thud_2"), - find_generic_SFX("thud_3") - }, - pops = {} - }, - music = { - } - } - zero_sound = get_from_supported_extensions("zero_music") - - for popLevel=1,4 do - sounds.SFX.pops[popLevel] = {} - for popIndex=1,10 do - sounds.SFX.pops[popLevel][popIndex] = find_generic_SFX("pop"..popLevel.."-"..popIndex) - end - end - - assert_requirements_met() - apply_config_volume() -end - function apply_config_volume() love.audio.setVolume(config.master_volume/100) - set_volume(sounds.SFX, config.SFX_volume/100) - set_volume(sounds.music, config.music_volume/100) + themes[config.theme]:apply_config_volume() for _,character in pairs(characters) do character:apply_config_volume() end + for _,stage in pairs(stages) do + stage:apply_config_volume() + end end function play_optional_sfx(sfx) @@ -84,7 +21,21 @@ end -- New music engine stuff here music_t = {} currently_playing_tracks = {} -- needed because we clone the tracks below + +function update_music() + for k, v in pairs(music_t) do + if v and k - love.timer.getTime() < 0.007 then + if not v.t:isPlaying() then + v.t:play() + currently_playing_tracks[#currently_playing_tracks+1]=v.t + end + music_t[k] = nil + end + end +end + function stop_the_music() + print("musics have been stopped") for k, v in pairs(currently_playing_tracks) do v:stop() currently_playing_tracks[k] = nil @@ -92,9 +43,17 @@ function stop_the_music() music_t = {} end -function find_and_add_music(character_id, music_type) - local start_music = characters[character_id].musics[music_type .. "_start"] or zero_sound - local loop_music = characters[character_id].musics[music_type] +local function make_music_t(source, loop) + return {t = source, l = loop or false} +end + +function find_and_add_music(musics, music_type) + print("music "..music_type.." is now playing") + local start_music = musics[music_type .. "_start"] or zero_sound + local loop_music = musics[music_type] + if loop_music:isPlaying() or start_music:isPlaying() then + return + end music_t[love.timer.getTime()] = make_music_t( start_music ) @@ -102,7 +61,3 @@ function find_and_add_music(character_id, music_type) loop_music, true ) end - -function make_music_t(source, loop) - return {t = source, l = loop or false} -end diff --git a/sound_util.lua b/sound_util.lua index 4c34ffb4..cd5394f7 100644 --- a/sound_util.lua +++ b/sound_util.lua @@ -1,4 +1,4 @@ -local supported_sound_formats = { ".mp3",".ogg", ".it" } +local supported_sound_formats = { ".mp3", ".ogg", ".wav", ".it", ".flac" } --sets the volume of a single source or table of sources function set_volume(source, new_volume) @@ -16,7 +16,7 @@ function find_sound(sound_name, dirs_to_check, streamed) streamed = streamed or false local found_source for k,dir in ipairs(dirs_to_check) do - found_source = get_from_supported_extensions(dir..sound_name,streamed) + found_source = load_sound_from_supported_extensions(dir..sound_name,streamed) if found_source then return found_source end @@ -25,25 +25,11 @@ function find_sound(sound_name, dirs_to_check, streamed) end --returns a source, or nil if it could not find a file -function get_from_supported_extensions(path_and_filename,streamed) +function load_sound_from_supported_extensions(path_and_filename,streamed) for k, extension in ipairs(supported_sound_formats) do if love.filesystem.getInfo(path_and_filename..extension) then - if streamed then - return love.audio.newSource(path_and_filename..extension, "stream") - else - return love.audio.newSource(path_and_filename..extension, "static") - end + return love.audio.newSource(path_and_filename..extension, streamed and "stream" or "static") end end return nil -end - ---check whether a sound file exists -function any_supported_extension(path_and_filename) - for k, extension in ipairs(supported_sound_formats) do - if love.filesystem.getInfo(path_and_filename..extension) then - return true - end - end - return false end \ No newline at end of file diff --git a/stage.lua b/stage.lua new file mode 100644 index 00000000..93ef0066 --- /dev/null +++ b/stage.lua @@ -0,0 +1,262 @@ +require("stage_loader") + + -- Stuff defined in this file: + -- . the data structure that store a stage's data + +local basic_images = {"thumbnail"} +local other_images = {"background"} +local defaulted_images = { thumbnail=true, background=true } -- those images will be defaulted if missing +local basic_musics = {} +local other_musics = {"normal_music", "danger_music", "normal_music_start", "danger_music_start"} +local defaulted_musics = {} -- those musics will be defaulted if missing + +local default_stage = nil -- holds default assets fallbacks + +Stage = class(function(s, full_path, folder_name) + s.path = full_path -- string | path to the stage folder content + s.id = folder_name -- string | id of the stage, specified in config.json + s.display_name = s.id -- string | display name of the stage + s.sub_stages = {} -- stringS | either empty or with two elements at least; holds the sub stages IDs for bundle stages + s.images = {} + s.musics = {} + s.fully_loaded = false + s.is_visible = true + end) + +function Stage.json_init(self) + local read_data = {} + local config_file, err = love.filesystem.newFile(self.path.."/config.json", "r") + if config_file then + local teh_json = config_file:read(config_file:getSize()) + for k,v in pairs(json.decode(teh_json)) do + read_data[k] = v + end + end + + if read_data.id and type(read_data.id) == "string" then + self.id = read_data.id + + -- sub ids for bundles + if read_data.sub_ids and type(read_data.sub_ids) == "table"then + self.sub_stages = read_data.sub_ids + end + + -- display name + if read_data.name and type(read_data.name) == "string" then + self.display_name = read_data.name + end + -- is visible + if read_data.visible ~= nil and type(read_data.visible) == "boolean" then + self.is_visible = read_data.visible + elseif read_data.visible and type(read_data.visible) == "string" then + self.is_visible = read_data.visible=="true" + end + + return true + end + + return false +end + +function Stage.stop_sounds(self) + -- music + for _, music in ipairs(self.musics) do + if self.musics[music] then + self.musics[music]:stop() + end + end +end + +function Stage.preload(self) + print("preloading stage "..self.id) + self:graphics_init(false,false) + self:sound_init(false,false) +end + +function Stage.load(self,instant) + print("loading stage "..self.id) + self:graphics_init(true,(not instant)) + self:sound_init(true,(not instant)) + self.fully_loaded = true + print("loaded stage "..self.id) +end + +function Stage.unload(self) + print("unloading stage "..self.id) + self:graphics_uninit() + self:sound_uninit() + self.fully_loaded = false + print("unloaded stage "..self.id) +end + +local function add_stages_from_dir_rec(path) + local lfs = love.filesystem + local raw_dir_list = lfs.getDirectoryItems(path) + for i,v in ipairs(raw_dir_list) do + local start_of_v = string.sub(v,0,string.len(prefix_of_ignored_dirs)) + if start_of_v ~= prefix_of_ignored_dirs then + local current_path = path.."/"..v + if lfs.getInfo(current_path) and lfs.getInfo(current_path).type == "directory" then + -- call recursively: facade folder + add_stages_from_dir_rec(current_path) + + -- init stage: 'real' folder + local stage = Stage(current_path,v) + local success = stage:json_init() + + if success then + if stages[stage.id] ~= nil then + print(current_path.." has been ignored since a stage with this id has already been found") + else + stages[stage.id] = stage + stages_ids[#stages_ids+1] = stage.id + end + end + end + end + end +end + +local function fill_stages_ids() + -- check validity of bundle stages + local invalid_stages = {} + local copy_of_stages_ids = shallowcpy(stages_ids) + stages_ids = {} -- clean up + for _,stage_id in ipairs(copy_of_stages_ids) do + local stage = stages[stage_id] + if #stage.sub_stages > 0 then -- bundle stage (needs to be filtered if invalid) + local copy_of_sub_stages = shallowcpy(stage.sub_stages) + stage.sub_stages = {} + for _,sub_stage in ipairs(copy_of_sub_stages) do + if stages[sub_stage] and #stages[sub_stage].sub_stages == 0 then -- inner bundles are prohibited + stage.sub_stages[#stage.sub_stages+1] = sub_stage + print(stage.id.." has "..sub_stage.." as part of its substages.") + end + end + + if #stage.sub_stages < 2 then + invalid_stages[#invalid_stages+1] = stage_id -- stage is invalid + print(stage.id.." (bundle) is being ignored since it's invalid!") + else + stages_ids[#stages_ids+1] = stage_id + print(stage.id.." (bundle) has been added to the stage list!") + end + else -- normal stage + stages_ids[#stages_ids+1] = stage_id + print(stage.id.." has been added to the stage list!") + end + end + + -- stages are removed outside of the loop since erasing while iterating isn't working + for _,invalid_stage in pairs(invalid_stages) do + stages[invalid_stage] = nil + end +end + +function stages_init() + stages = {} -- holds all stages, most of them will not be fully loaded + stages_ids = {} -- holds all stages ids + stages_ids_for_current_theme = {} -- holds stages ids for the current theme, those stages will appear in the selection + default_stage = nil + + add_stages_from_dir_rec("stages") + fill_stages_ids() + + if #stages_ids == 0 then + recursive_copy("default_data/stages", "stages") + add_stages_from_dir_rec("stages") + fill_stages_ids() + end + + if love.filesystem.getInfo("themes/"..config.theme.."/stages.txt") then + for line in love.filesystem.lines("themes/"..config.theme.."/stages.txt") do + line = trim(line) -- remove whitespace + -- found at least a valid stage in a stages.txt file + if stages[line] then + stages_ids_for_current_theme[#stages_ids_for_current_theme+1] = line + end + end + else + for _,stage_id in ipairs(stages_ids) do + if stages[stage_id].is_visible then + stages_ids_for_current_theme[#stages_ids_for_current_theme+1] = stage_id + end + end + end + + -- all stages case + if #stages_ids_for_current_theme == 0 then + stages_ids_for_current_theme = shallowcpy(stages_ids) + end + + -- fix config stage if it's missing + if not config.stage or ( config.stage ~= random_stage_special_value and not stages[config.stage] ) then + config.stage = uniformly(stages_ids_for_current_theme) -- it's legal to pick a bundle here, no need to go further + end + + -- actual init for all stages, starting with the default one + default_stage = Stage("stages/__default", "__default") + default_stage:preload() + default_stage:load(true) + + for _,stage in pairs(stages) do + stage:preload() + end +end + +function Stage.is_bundle(self) + return #self.sub_stages > 1 +end + +function Stage.graphics_init(self,full,yields) + local stage_images = full and other_images or basic_images + for _,image_name in ipairs(stage_images) do + self.images[image_name] = load_img_from_supported_extensions(self.path.."/"..image_name) + if not self.images[image_name] and defaulted_images[image_name] and not self:is_bundle() then + self.images[image_name] = default_stage.images[image_name] + end + if yields then coroutine.yield() end + end +end + +function Stage.graphics_uninit(self) + for _,image_name in ipairs(other_images) do + self.images[image_name] = nil + end +end + +function Stage.apply_config_volume(self) + set_volume(self.musics, config.music_volume/100) +end + +function Stage.sound_init(self,full,yields) + if self:is_bundle() then + return + end + local stage_musics = full and other_musics or basic_musics + for _, music in ipairs(stage_musics) do + self.musics[music] = load_sound_from_supported_extensions(self.path.."/"..music, true) + -- Set looping status for music. + -- Intros won't loop, but other parts should. + if self.musics[music] then + if not string.find(music, "start") then + self.musics[music]:setLooping(true) + else + self.musics[music]:setLooping(false) + end + elseif not self.musics[music] and defaulted_musics[music] then + self.musics[music] = default_stage.musics[music] or zero_sound + end + + if yields then coroutine.yield() end + end + + self:apply_config_volume() +end + +function Stage.sound_uninit(self) + -- music + for _,music in ipairs(other_musics) do + self.musics[music] = nil + end +end \ No newline at end of file diff --git a/stage_loader.lua b/stage_loader.lua new file mode 100644 index 00000000..c2fe4c0d --- /dev/null +++ b/stage_loader.lua @@ -0,0 +1,56 @@ +require("queue") +require("globals") + +local loading_queue = Queue() + +local loading_stage = nil + +function stage_loader_load(stage_id) + if stages[stage_id] and not stages[stage_id].fully_loaded then + loading_queue:push(stage_id) + end +end + +local instant_load_enabled = false + +-- return true if there is still data to load +function stage_loader_update() + if not loading_stage and loading_queue:len() > 0 then + local stage_id = loading_queue:pop() + loading_stage = { stage_id, coroutine.create( function() + stages[stage_id]:load(instant_load_enabled) + end) } + end + + if loading_stage then + if coroutine.status(loading_stage[2]) == "suspended" then + coroutine.resume(loading_stage[2]) + return true + elseif coroutine.status(loading_stage[2]) == "dead" then + loading_stage = nil + return loading_queue:len() > 0 + -- TODO: unload stages if too much data have been loaded (be careful not to release currently-used stages) + end + end + + return false +end + +function stage_loader_wait() + instant_load_enabled = true + while true do + if not stage_loader_update() then + break + end + end + instant_load_enabled = false +end + +function stage_loader_clear() + local p2_local_stage = global_op_state and global_op_state.stage or nil + for stage_id,stage in pairs(stages) do + if stage.fully_loaded and stage_id ~= config.stage and stage_id ~= p2_local_stage then + stage:unload() + end + end +end \ No newline at end of file diff --git a/stages/__default/background.png b/stages/__default/background.png new file mode 100644 index 00000000..c7c16f67 Binary files /dev/null and b/stages/__default/background.png differ diff --git a/stages/__default/thumbnail.png b/stages/__default/thumbnail.png new file mode 100644 index 00000000..c7c16f67 Binary files /dev/null and b/stages/__default/thumbnail.png differ diff --git a/theme.lua b/theme.lua new file mode 100644 index 00000000..a759d06c --- /dev/null +++ b/theme.lua @@ -0,0 +1,664 @@ +require("graphics_util") +require("sound_util") + +local musics = {"main", "select_screen", "main_start", "select_screen_start"} + +-- from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +local flags = +{ +"cn", -- China +"de", -- Germany +"es", -- Spain +"fr", -- France +"gb", -- United Kingdom of Great Britain and Northern Ireland +"in", -- India +"it", -- Italy +"jp", -- Japan +"pt", -- Portugal +"us", -- United States of America +} + +local function load_theme_img(name) + local img = load_img_from_supported_extensions("themes/"..config.theme.."/"..name) + if not img then + img = load_img_from_supported_extensions("themes/"..default_theme_dir.."/"..name) + end + return img +end + +Theme = class(function(self) + self.images = {} + self.sounds = {} + self.musics = {} + self.matchtypeLabel_Pos = {-40, -30} + self.matchtypeLabel_Scale = 3 + self.timeLabel_Pos = {0, 10} + self.timeLabel_Scale = 2 + self.time_Pos = {-40, 25} + self.time_Scale = 1 + self.name_Pos = {20, -30} + self.moveLabel_Pos = {465, 170} + self.moveLabel_Scale = 2 + self.move_Pos = {20, 35} + self.move_Scale = 1 + self.scoreLabel_Pos = {102, 25} + self.scoreLabel_Scale = 2 + self.score_Pos = {108, 31} + self.score_Scale = 1.31 + self.speedLabel_Pos = {104, 42} + self.speedLabel_Scale = 2 + self.speed_Pos = {108, 48} + self.speed_Scale = 1.35 + self.levelLabel_Pos = {101, 59} + self.levelLabel_Scale = 2 + self.level_Pos = {110, 65} + self.level_Scale = 1 + self.winLabel_Pos = {10, 230} + self.winLabel_Scale = 2 + self.win_Pos = {20, 260} + self.win_Scale = 1 + self.ratingLabel_Pos = {5, 180} + self.ratingLabel_Scale = 2 + self.rating_Pos = {25, 200} + self.rating_Scale = 1 + self.spectators_Pos = {10, 400} + self.healthbar_frame_Pos = {-20, -4} + self.healthbar_frame_Scale = 3 + self.healthbar_Pos = {-16, 0} + self.healthbar_Scale = 1 + self.healthbar_Rotate = 0 + self.prestop_frame_Pos = {100, 1190} + self.prestop_frame_Scale = 1 + self.prestop_bar_Pos = {110, 1197} + self.prestop_bar_Scale = 1 + self.prestop_bar_Rotate = 0 + self.prestop_Pos = {120, 1105} + self.prestop_Scale = 1 + self.stop_frame_Pos = {100, 130} + self.stop_frame_Scale = 1 + self.stop_bar_Pos = {106, 137} + self.stop_bar_Scale = 1 + self.stop_bar_Rotate = 0 + self.stop_Pos = {106, 161} + self.stop_Scale = 1 + self.shake_frame_Pos = {100, 1150} + self.shake_frame_Scale = 1 + self.shake_bar_Pos = {110, 1157} + self.shake_bar_Scale = 1 + self.shake_bar_Rotate = 0 + self.shake_Pos = {120, 1165} + self.shake_Scale = 1 + self.multibar_frame_Pos = {100, 1100} + self.multibar_frame_Scale = 1 + self.multibar_Pos = {106, 1148} + self.multibar_Scale = 1 + end) + +background = load_theme_img("background/main") + +function Theme.graphics_init(self) + self.images = {} + + self.images.flags = {} + for _, flag in ipairs(flags) do + self.images.flags[flag] = load_theme_img("flags/"..flag) + end + + self.images.bg_main = load_theme_img("background/main") + self.images.bg_select_screen = load_theme_img("background/select_screen") + self.images.bg_readme = load_theme_img("background/readme") + + self.images.bg_overlay = load_theme_img("background/bg_overlay") + self.images.fg_overlay = load_theme_img("background/fg_overlay") + + self.images.pause = load_theme_img("pause") + + self.images.IMG_level_cursor = load_theme_img("level/level_cursor") + self.images.IMG_levels = {} + self.images.IMG_levels_unfocus = {} + self.images.IMG_levels[1] = load_theme_img("level/level1") + self.images.IMG_levels_unfocus[1] = nil -- meaningless by design + for i=2,#level_to_starting_speed do --which should equal the number of levels in the game + self.images.IMG_levels[i] = load_theme_img("level/level"..i.."") + self.images.IMG_levels_unfocus[i] = load_theme_img("level/level"..i.."unfocus") + end + + self.images.IMG_ready = load_theme_img("ready") + self.images.IMG_loading = load_theme_img("loading") + self.images.IMG_super = load_theme_img("super") + self.images.IMG_numbers = {} + for i=1,3 do + self.images.IMG_numbers[i] = load_theme_img(i.."") + end + + self.images.burst = load_theme_img("burst") + + self.images.fade = load_theme_img("fade") + + self.images.IMG_number_atlas_1P = load_theme_img("numbers_1P") + self.images.numberWidth_1P = self.images.IMG_number_atlas_1P:getWidth()/10 + self.images.numberHeight_1P = self.images.IMG_number_atlas_1P:getHeight() + self.images.IMG_number_atlas_2P = load_theme_img("numbers_2P") + self.images.numberWidth_2P = self.images.IMG_number_atlas_2P:getWidth()/10 + self.images.numberHeight_2P = self.images.IMG_number_atlas_2P:getHeight() + + self.images.IMG_time = load_theme_img("time") + + self.images.IMG_timeNumber_atlas = load_theme_img("time_numbers") + self.images.timeNumberWidth = self.images.IMG_timeNumber_atlas:getWidth()/12 + self.images.timeNumberHeight = self.images.IMG_timeNumber_atlas:getHeight() + + self.images.IMG_moves = load_theme_img("moves") + + self.images.IMG_score_1P = load_theme_img("score_1P") + self.images.IMG_score_2P = load_theme_img("score_2P") + + self.images.IMG_speed_1P = load_theme_img("speed_1P") + self.images.IMG_speed_2P = load_theme_img("speed_2P") + + self.images.IMG_level_1P = load_theme_img("level_1P") + self.images.IMG_level_2P = load_theme_img("level_2P") + + self.images.IMG_wins = load_theme_img("wins") + + self.images.IMG_levelNumber_atlas_1P = load_theme_img("level_numbers_1P") + self.images.levelNumberWidth_1P = self.images.IMG_levelNumber_atlas_1P:getWidth()/11 + self.images.levelNumberHeight_1P = self.images.IMG_levelNumber_atlas_1P:getHeight() + self.images.IMG_levelNumber_atlas_2P = load_theme_img("level_numbers_2P") + self.images.levelNumberWidth_2P = self.images.IMG_levelNumber_atlas_2P:getWidth()/11 + self.images.levelNumberHeight_2P = self.images.IMG_levelNumber_atlas_2P:getHeight() + + self.images.IMG_casual = load_theme_img("casual") + + self.images.IMG_ranked = load_theme_img("ranked") + + self.images.IMG_rating_1P= load_theme_img("rating_1P") + self.images.IMG_rating_2P = load_theme_img("rating_2P") + + self.images.IMG_random_stage = load_theme_img("random_stage") + self.images.IMG_random_character = load_theme_img("random_character") + + self.images.IMG_healthbar_frame_1P = load_theme_img("healthbar_frame_1P") + self.images.IMG_healthbar_frame_2P = load_theme_img("healthbar_frame_2P") + self.images.IMG_healthbar = load_theme_img("healthbar") + + self.images.IMG_prestop_frame = load_theme_img("prestop_frame") + self.images.IMG_prestop_bar = load_theme_img("prestop_bar") + + self.images.IMG_stop_frame = load_theme_img("stop_frame") + self.images.IMG_stop_bar = load_theme_img("stop_bar") + + self.images.IMG_shake_frame = load_theme_img("shake_frame") + self.images.IMG_shake_bar = load_theme_img("shake_bar") + + self.images.IMG_multibar_frame = load_theme_img("multibar_frame") + self.images.IMG_multibar_prestop_bar = load_theme_img("multibar_prestop_bar") + self.images.IMG_multibar_stop_bar = load_theme_img("multibar_stop_bar") + self.images.IMG_multibar_shake_bar = load_theme_img("multibar_shake_bar") + + + --play field frames, plus the wall at the bottom. + self.images.IMG_frame1P = load_theme_img("frame/frame1P") + self.images.IMG_wall1P = load_theme_img("frame/wall1P") + self.images.IMG_frame2P = load_theme_img("frame/frame2P") + self.images.IMG_wall2P = load_theme_img("frame/wall2P") + --the game currently only supports 2 players, but since 3P+ support is on the "to-do-eventually" list, include assets for more players + --special thanks to TheWolfBunny on DeviantArt for 3P+ frame and wall sprites + self.images.IMG_frame3P = load_theme_img("frame/frame3P") + self.images.IMG_wall3P = load_theme_img("frame/wall3P") + self.images.IMG_frame4P = load_theme_img("frame/frame4P") + self.images.IMG_wall4P = load_theme_img("frame/wall4P") + --5P-8P might be overkill, but just imagine... + --self.images.IMG_frame5P = load_theme_img("frame/frame5P") + --self.images.IMG_wall5P = load_theme_img("frame/wall5P") + --self.images.IMG_frame6P = load_theme_img("frame/frame6P") + --self.images.IMG_wall6P = load_theme_img("frame/wall6P") + --self.images.IMG_frame7P = load_theme_img("frame/frame7P") + --self.images.IMG_wall7P = load_theme_img("frame/wall7P") + --self.images.IMG_frame8P = load_theme_img("frame/frame8P") + --self.images.IMG_wall8P = load_theme_img("frame/wall8P") + + + self.images.IMG_cards = {} + self.images.IMG_cards[true] = {} + self.images.IMG_cards[false] = {} + for i=4,66 do + self.images.IMG_cards[false][i] = load_theme_img("combo/combo" + ..tostring(math.floor(i/10))..tostring(i%10).."") + end + for i=2,13 do + self.images.IMG_cards[true][i] = load_theme_img("chain/chain" + ..tostring(math.floor(i/10))..tostring(i%10).."") + end + + self.images.IMG_cards[true][14] = load_theme_img("chain/chain00") + for i=15,99 do + self.images.IMG_cards[true][i] = self.images.IMG_cards[true][14] + end + + local MAX_SUPPORTED_PLAYERS = 2 + self.images.IMG_char_sel_cursors = {} + self.images.IMG_players = {} + self.images.IMG_cursor = {} + for player_num=1,MAX_SUPPORTED_PLAYERS do + self.images.IMG_players[player_num] = load_theme_img("p"..player_num) + self.images.IMG_cursor[player_num] = load_theme_img("p"..player_num.."_cursor") + self.images.IMG_char_sel_cursors[player_num] = {} + for position_num=1,2 do + self.images.IMG_char_sel_cursors[player_num][position_num] = load_theme_img("p"..player_num.."_select_screen_cursor"..position_num) + end + end + + self.images.IMG_char_sel_cursor_halves = {left={}, right={}} + for player_num=1,MAX_SUPPORTED_PLAYERS do + self.images.IMG_char_sel_cursor_halves.left[player_num] = {} + for position_num=1,2 do + local cur_width, cur_height = self.images.IMG_char_sel_cursors[player_num][position_num]:getDimensions() + local half_width, half_height = cur_width/2, cur_height/2 -- TODO: is these unused vars an error ??? -Endu + self.images.IMG_char_sel_cursor_halves["left"][player_num][position_num] = love.graphics.newQuad(0,0,half_width,cur_height,cur_width, cur_height) + end + self.images.IMG_char_sel_cursor_halves.right[player_num] = {} + for position_num=1,2 do + local cur_width, cur_height = self.images.IMG_char_sel_cursors[player_num][position_num]:getDimensions() + local half_width, half_height = cur_width/2, cur_height/2 + self.images.IMG_char_sel_cursor_halves.right[player_num][position_num] = love.graphics.newQuad(half_width,0,half_width,cur_height,cur_width, cur_height) + end + end +end + +function Theme.apply_config_volume(self) + set_volume(self.sounds, config.SFX_volume/100) + set_volume(self.musics, config.music_volume/100) +end + +function Theme.sound_init(self) + local function load_theme_sfx(SFX_name) + local dirs_to_check = {"themes/"..config.theme.."/sfx/", + "themes/"..default_theme_dir.."/sfx/"} + return find_sound(SFX_name, dirs_to_check) + end + + -- SFX + self.sounds = { + cur_move = load_theme_sfx("move"), + swap = load_theme_sfx("swap"), + land = load_theme_sfx("land"), + fanfare1 = load_theme_sfx("fanfare1"), + fanfare2 = load_theme_sfx("fanfare2"), + fanfare3 = load_theme_sfx("fanfare3"), + game_over = load_theme_sfx("gameover"), + countdown = load_theme_sfx("countdown"), + go = load_theme_sfx("go"), + menu_move = load_theme_sfx("menu_move"), + menu_validate = load_theme_sfx("menu_validate"), + menu_cancel = load_theme_sfx("menu_cancel"), + notification = load_theme_sfx("notification"), + garbage_thud = { + load_theme_sfx("thud_1"), + load_theme_sfx("thud_2"), + load_theme_sfx("thud_3") + }, + pops = {} + } + + for popLevel=1,4 do + self.sounds.pops[popLevel] = {} + for popIndex=1,10 do + self.sounds.pops[popLevel][popIndex] = load_theme_sfx("pop"..popLevel.."-"..popIndex) + end + end + + -- music + self.musics = {} + for _, music in ipairs(musics) do + self.musics[music] = load_sound_from_supported_extensions("themes/"..config.theme.."/music/"..music, true) + if self.musics[music] then + if not string.find(music, "start") then + self.musics[music]:setLooping(true) + else + self.musics[music]:setLooping(false) + end + end + end + + self:apply_config_volume() +end + +function Theme.json_init(self) + local read_data = {} + local config_file, err = love.filesystem.newFile("themes/"..config.theme.."/config.json", "r") + if config_file then + local teh_json = config_file:read(config_file:getSize()) + for k,v in pairs(json.decode(teh_json)) do + read_data[k] = v + end + end + + + -- Matchtype label position + if read_data.matchtypeLabel_Pos and type(read_data.matchtypeLabel_Pos) == "table" then + self.matchtypeLabel_Pos = read_data.matchtypeLabel_Pos + end + + -- Matchtype label scale + if read_data.matchtypeLabel_Scale and type(read_data.matchtypeLabel_Scale) == "number" then + self.matchtypeLabel_Scale = read_data.matchtypeLabel_Scale + end + + -- Time label position + if read_data.timeLabel_Pos and type(read_data.timeLabel_Pos) == "table" then + self.timeLabel_Pos = read_data.timeLabel_Pos + end + + -- Time label scale + if read_data.timeLabel_Scale and type(read_data.timeLabel_Scale) == "number" then + self.timeLabel_Scale = read_data.timeLabel_Scale + end + + -- Time position + if read_data.time_Pos and type(read_data.time_Pos) == "table" then + self.time_Pos = read_data.time_Pos + end + + -- Time scale + if read_data.time_Scale and type(read_data.time_Scale) == "number" then + self.time_Scale = read_data.time_Scale + end + + -- Move label position + if read_data.moveLabel_Pos and type(read_data.moveLabel_Pos) == "table" then + self.moveLabel_Pos = read_data.moveLabel_Pos + end + + -- Move label scale + if read_data.moveLabel_Scale and type(read_data.moveLabel_Scale) == "number" then + self.moveLabel_Scale = read_data.moveLabel_Scale + end + + -- Move position + if read_data.move_Pos and type(read_data.move_Pos) == "table" then + self.move_Pos = read_data.move_Pos + end + + -- Move scale + if read_data.move_Scale and type(read_data.move_Scale) == "number" then + self.move_Scale = read_data.move_Scale + end + + -- Score label position + if read_data.scoreLabel_Pos and type(read_data.scoreLabel_Pos) == "table" then + self.scoreLabel_Pos = read_data.scoreLabel_Pos + end + + -- Score label scale + if read_data.scoreLabel_Scale and type(read_data.scoreLabel_Scale) == "number" then + self.scoreLabel_Scale = read_data.scoreLabel_Scale + end + + -- Score position + if read_data.score_Pos and type(read_data.score_Pos) == "table" then + self.score_Pos = read_data.score_Pos + end + + -- Score scale + if read_data.score_Scale and type(read_data.score_Scale) == "number" then + self.score_Scale = read_data.score_Scale + end + + -- Speed label position + if read_data.speedLabel_Pos and type(read_data.speedLabel_Pos) == "table" then + self.speedLabel_Pos = read_data.speedLabel_Pos + end + + -- Speed label scale + if read_data.speedLabel_Scale and type(read_data.speedLabel_Scale) == "number" then + self.speedLabel_Scale = read_data.speedLabel_Scale + end + + -- Speed position + if read_data.speed_Pos and type(read_data.speed_Pos) == "table" then + self.speed_Pos = read_data.speed_Pos + end + + -- Speed scale + if read_data.speed_Scale and type(read_data.speed_Scale) == "number" then + self.speed_Scale = read_data.speed_Scale + end + + -- Level label position + if read_data.levelLabel_Pos and type(read_data.levelLabel_Pos) == "table" then + self.levelLabel_Pos = read_data.levelLabel_Pos + end + + -- Level label scale + if read_data.levelLabel_Scale and type(read_data.levelLabel_Scale) == "number" then + self.levelLabel_Scale = read_data.levelLabel_Scale + end + + -- Level position + if read_data.level_Pos and type(read_data.level_Pos) == "table" then + self.level_Pos = read_data.level_Pos + end + + -- Level scale + if read_data.level_Scale and type(read_data.level_Scale) == "number" then + self.level_Scale = read_data.level_Scale + end + + -- Wins label position + if read_data.winLabel_Pos and type(read_data.winLabel_Pos) == "table" then + self.winLabel_Pos = read_data.winLabel_Pos + end + + -- Wins label scale + if read_data.winLabel_Scale and type(read_data.winLabel_Scale) == "number" then + self.winLabel_Scale = read_data.winLabel_Scale + end + + -- Wins position + if read_data.win_Pos and type(read_data.win_Pos) == "table" then + self.win_Pos = read_data.win_Pos + end + + -- Wins scale + if read_data.win_Scale and type(read_data.win_Scale) == "number" then + self.win_Scale = read_data.win_Scale + end + + -- Name position + if read_data.name_Pos and type(read_data.name_Pos) == "table" then + self.name_Pos = read_data.name_Pos + end + + -- Ratin label position + if read_data.ratingLabel_Pos and type(read_data.ratingLabel_Pos) == "table" then + self.ratingLabel_Pos = read_data.ratingLabel_Pos + end + + -- Rating label scale + if read_data.ratingLabel_Scale and type(read_data.ratingLabel_Scale) == "number" then + self.ratingLabel_Scale = read_data.ratingLabel_Scale + end + + -- Rating position + if read_data.rating_Pos and type(read_data.rating_Pos) == "table" then + self.rating_Pos = read_data.rating_Pos + end + + -- Rating scale + if read_data.rating_Scale and type(read_data.rating_Scale) == "number" then + self.rating_Scale = read_data.rating_Scale + end + + -- Spectators position + if read_data.spectators_Pos and type(read_data.spectators_Pos) == "table" then + self.spectators_Pos = read_data.spectators_Pos + end + + -- Healthbar frame position + if read_data.healthbar_frame_Pos and type(read_data.healthbar_frame_Pos) == "table" then + self.healthbar_frame_Pos = read_data.healthbar_frame_Pos + end + + -- Healthbar frame scale + if read_data.healthbar_frame_Scale and type(read_data.healthbar_frame_Scale) == "number" then + self.healthbar_frame_Scale = read_data.healthbar_frame_Scale + end + + -- Healthbar position + if read_data.healthbar_Pos and type(read_data.healthbar_Pos) == "table" then + self.healthbar_Pos = read_data.healthbar_Pos + end + + -- Healthbar scale + if read_data.healthbar_Scale and type(read_data.healthbar_Scale) == "number" then + self.healthbar_Scale = read_data.healthbar_Scale + end + + -- Healthbar Rotate + if read_data.healthbar_Rotate and type(read_data.healthbar_Rotate) == "number" then + self.healthbar_Rotate = read_data.healthbar_Rotate + end + + -- Prestop frame position + if read_data.prestop_frame_Pos and type(read_data.prestop_frame_Pos) == "table" then + self.prestop_frame_Pos = read_data.prestop_frame_Pos + end + + -- Prestop frame scale + if read_data.prestop_frame_Scale and type(read_data.prestop_frame_Scale) == "number" then + self.prestop_frame_Scale = read_data.prestop_frame_Scale + end + + -- Prestop bar position + if read_data.prestop_bar_Pos and type(read_data.prestop_bar_Pos) == "table" then + self.prestop_bar_Pos = read_data.prestop_bar_Pos + end + + -- Prestop bar scale + if read_data.prestop_bar_Scale and type(read_data.prestop_bar_Scale) == "number" then + self.prestop_bar_Scale = read_data.prestop_bar_Scale + end + + -- Prestop bar Rotate + if read_data.prestop_bar_Rotate and type(read_data.prestop_bar_Rotate) == "number" then + self.prestop_bar_Rotate = read_data.prestop_bar_Rotate + end + + -- Prestop position + if read_data.prestop_Pos and type(read_data.prestop_Pos) == "table" then + self.prestop_Pos = read_data.prestop_Pos + end + + -- Prestop scale + if read_data.prestop_Scale and type(read_data.prestop_Scale) == "number" then + self.prestop_Scale = read_data.prestop_Scale + end + + -- Stop frame position + if read_data.stop_frame_Pos and type(read_data.stop_frame_Pos) == "table" then + self.stop_frame_Pos = read_data.stop_frame_Pos + end + + -- Stop frame scale + if read_data.stop_frame_Scale and type(read_data.stop_frame_Scale) == "number" then + self.stop_frame_Scale = read_data.stop_frame_Scale + end + + -- Stop bar position + if read_data.stop_bar_Pos and type(read_data.stop_bar_Pos) == "table" then + self.stop_bar_Pos = read_data.stop_bar_Pos + end + + -- Stop bar scale + if read_data.stop_bar_Scale and type(read_data.stop_bar_Scale) == "number" then + self.stop_bar_Scale = read_data.stop_bar_Scale + end + + -- Stop bar Rotate + if read_data.stop_bar_Rotate and type(read_data.stop_bar_Rotate) == "number" then + self.stop_bar_Rotate = read_data.stop_bar_Rotate + end + + -- Stop position + if read_data.stop_Pos and type(read_data.stop_Pos) == "table" then + self.stop_Pos = read_data.stop_Pos + end + + -- Stop scale + if read_data.stop_Scale and type(read_data.stop_Scale) == "number" then + self.stop_Scale = read_data.stop_Scale + end + + -- Shake frame position + if read_data.shake_frame_Pos and type(read_data.shake_frame_Pos) == "table" then + self.shake_frame_Pos = read_data.shake_frame_Pos + end + + -- Shake frame scale + if read_data.shake_frame_Scale and type(read_data.shake_frame_Scale) == "number" then + self.shake_frame_Scale = read_data.shake_frame_Scale + end + + -- Shake bar position + if read_data.shake_bar_Pos and type(read_data.shake_bar_Pos) == "table" then + self.shake_bar_Pos = read_data.shake_bar_Pos + end + + -- Shake bar scale + if read_data.shake_bar_Scale and type(read_data.shake_bar_Scale) == "number" then + self.shake_bar_Scale = read_data.shake_bar_Scale + end + + -- Shake bar Rotate + if read_data.shake_bar_Rotate and type(read_data.shake_bar_Rotate) == "number" then + self.shake_bar_Rotate = read_data.shake_bar_Rotate + end + + -- Shake position + if read_data.shake_Pos and type(read_data.shake_Pos) == "table" then + self.shake_Pos = read_data.shake_Pos + end + + -- Shake scale + if read_data.shake_Scale and type(read_data.shake_Scale) == "number" then + self.shake_Scale = read_data.shake_Scale + end + + -- Multibar frame position + if read_data.multibar_frame_Pos and type(read_data.multibar_frame_Pos) == "table" then + self.multibar_frame_Pos = read_data.multibar_frame_Pos + end + + -- Multibar frame scale + if read_data.multibar_frame_Scale and type(read_data.multibar_frame_Scale) == "number" then + self.multibar_frame_Scale = read_data.multibar_frame_Scale + end + + -- Multibar position + if read_data.multibar_Pos and type(read_data.multibar_Pos) == "table" then + self.multibar_Pos = read_data.multibar_Pos + end + + -- Multibar scale + if read_data.multibar_Scale and type(read_data.multibar_Scale) == "number" then + self.multibar_Scale = read_data.multibar_Scale + end + +end + + +function Theme.load(self, id) + print("loading theme "..id) + self:graphics_init() + self:sound_init() + self:json_init() + print("loaded theme "..id) +end + +function theme_init() + -- only one theme at a time for now, but we may decide to allow different themes in the future + themes = {} + themes[config.theme] = Theme() + themes[config.theme]:load(config.theme) +end diff --git a/assets/Stock PdP_TA/1.png b/themes/Panel Attack/1.png similarity index 100% rename from assets/Stock PdP_TA/1.png rename to themes/Panel Attack/1.png diff --git a/assets/Stock PdP_TA/2.png b/themes/Panel Attack/2.png similarity index 100% rename from assets/Stock PdP_TA/2.png rename to themes/Panel Attack/2.png diff --git a/assets/Stock PdP_TA/3.png b/themes/Panel Attack/3.png similarity index 100% rename from assets/Stock PdP_TA/3.png rename to themes/Panel Attack/3.png diff --git a/themes/Panel Attack/background/main.png b/themes/Panel Attack/background/main.png new file mode 100644 index 00000000..ea5ab4b3 Binary files /dev/null and b/themes/Panel Attack/background/main.png differ diff --git a/assets/Stock PdP_TA/menu/charselect.png b/themes/Panel Attack/background/readme.png similarity index 100% rename from assets/Stock PdP_TA/menu/charselect.png rename to themes/Panel Attack/background/readme.png diff --git a/themes/Panel Attack/background/select_screen.png b/themes/Panel Attack/background/select_screen.png new file mode 100644 index 00000000..e64eafe9 Binary files /dev/null and b/themes/Panel Attack/background/select_screen.png differ diff --git a/themes/Panel Attack/burst.png b/themes/Panel Attack/burst.png new file mode 100644 index 00000000..c6280433 Binary files /dev/null and b/themes/Panel Attack/burst.png differ diff --git a/themes/Panel Attack/casual.png b/themes/Panel Attack/casual.png new file mode 100644 index 00000000..21a8ea54 Binary files /dev/null and b/themes/Panel Attack/casual.png differ diff --git a/assets/Stock PdP_TA/chain00.png b/themes/Panel Attack/chain/chain00.png similarity index 100% rename from assets/Stock PdP_TA/chain00.png rename to themes/Panel Attack/chain/chain00.png diff --git a/assets/Stock PdP_TA/chain02.png b/themes/Panel Attack/chain/chain02.png similarity index 100% rename from assets/Stock PdP_TA/chain02.png rename to themes/Panel Attack/chain/chain02.png diff --git a/assets/Stock PdP_TA/chain03.png b/themes/Panel Attack/chain/chain03.png similarity index 100% rename from assets/Stock PdP_TA/chain03.png rename to themes/Panel Attack/chain/chain03.png diff --git a/assets/Stock PdP_TA/chain04.png b/themes/Panel Attack/chain/chain04.png similarity index 100% rename from assets/Stock PdP_TA/chain04.png rename to themes/Panel Attack/chain/chain04.png diff --git a/assets/Stock PdP_TA/chain05.png b/themes/Panel Attack/chain/chain05.png similarity index 100% rename from assets/Stock PdP_TA/chain05.png rename to themes/Panel Attack/chain/chain05.png diff --git a/assets/Stock PdP_TA/chain06.png b/themes/Panel Attack/chain/chain06.png similarity index 100% rename from assets/Stock PdP_TA/chain06.png rename to themes/Panel Attack/chain/chain06.png diff --git a/assets/Stock PdP_TA/chain07.png b/themes/Panel Attack/chain/chain07.png similarity index 100% rename from assets/Stock PdP_TA/chain07.png rename to themes/Panel Attack/chain/chain07.png diff --git a/assets/Stock PdP_TA/chain08.png b/themes/Panel Attack/chain/chain08.png similarity index 100% rename from assets/Stock PdP_TA/chain08.png rename to themes/Panel Attack/chain/chain08.png diff --git a/assets/Stock PdP_TA/chain09.png b/themes/Panel Attack/chain/chain09.png similarity index 100% rename from assets/Stock PdP_TA/chain09.png rename to themes/Panel Attack/chain/chain09.png diff --git a/assets/Stock PdP_TA/chain10.png b/themes/Panel Attack/chain/chain10.png similarity index 100% rename from assets/Stock PdP_TA/chain10.png rename to themes/Panel Attack/chain/chain10.png diff --git a/assets/Stock PdP_TA/chain11.png b/themes/Panel Attack/chain/chain11.png similarity index 100% rename from assets/Stock PdP_TA/chain11.png rename to themes/Panel Attack/chain/chain11.png diff --git a/assets/Stock PdP_TA/chain12.png b/themes/Panel Attack/chain/chain12.png similarity index 100% rename from assets/Stock PdP_TA/chain12.png rename to themes/Panel Attack/chain/chain12.png diff --git a/assets/Stock PdP_TA/chain13.png b/themes/Panel Attack/chain/chain13.png similarity index 100% rename from assets/Stock PdP_TA/chain13.png rename to themes/Panel Attack/chain/chain13.png diff --git a/assets/Stock PdP_TA/chain14.png b/themes/Panel Attack/chain/chain14.png similarity index 100% rename from assets/Stock PdP_TA/chain14.png rename to themes/Panel Attack/chain/chain14.png diff --git a/assets/Stock PdP_TA/chain15.png b/themes/Panel Attack/chain/chain15.png similarity index 100% rename from assets/Stock PdP_TA/chain15.png rename to themes/Panel Attack/chain/chain15.png diff --git a/assets/Stock PdP_TA/chain16.png b/themes/Panel Attack/chain/chain16.png similarity index 100% rename from assets/Stock PdP_TA/chain16.png rename to themes/Panel Attack/chain/chain16.png diff --git a/assets/Stock PdP_TA/chain17.png b/themes/Panel Attack/chain/chain17.png similarity index 100% rename from assets/Stock PdP_TA/chain17.png rename to themes/Panel Attack/chain/chain17.png diff --git a/assets/Stock PdP_TA/chain18.png b/themes/Panel Attack/chain/chain18.png similarity index 100% rename from assets/Stock PdP_TA/chain18.png rename to themes/Panel Attack/chain/chain18.png diff --git a/assets/Stock PdP_TA/chain19.png b/themes/Panel Attack/chain/chain19.png similarity index 100% rename from assets/Stock PdP_TA/chain19.png rename to themes/Panel Attack/chain/chain19.png diff --git a/assets/Stock PdP_TA/combo04.png b/themes/Panel Attack/combo/combo04.png similarity index 100% rename from assets/Stock PdP_TA/combo04.png rename to themes/Panel Attack/combo/combo04.png diff --git a/assets/Stock PdP_TA/combo05.png b/themes/Panel Attack/combo/combo05.png similarity index 100% rename from assets/Stock PdP_TA/combo05.png rename to themes/Panel Attack/combo/combo05.png diff --git a/assets/Stock PdP_TA/combo06.png b/themes/Panel Attack/combo/combo06.png similarity index 100% rename from assets/Stock PdP_TA/combo06.png rename to themes/Panel Attack/combo/combo06.png diff --git a/assets/Stock PdP_TA/combo07.png b/themes/Panel Attack/combo/combo07.png similarity index 100% rename from assets/Stock PdP_TA/combo07.png rename to themes/Panel Attack/combo/combo07.png diff --git a/assets/Stock PdP_TA/combo08.png b/themes/Panel Attack/combo/combo08.png similarity index 100% rename from assets/Stock PdP_TA/combo08.png rename to themes/Panel Attack/combo/combo08.png diff --git a/assets/Stock PdP_TA/combo09.png b/themes/Panel Attack/combo/combo09.png similarity index 100% rename from assets/Stock PdP_TA/combo09.png rename to themes/Panel Attack/combo/combo09.png diff --git a/assets/Stock PdP_TA/combo10.png b/themes/Panel Attack/combo/combo10.png similarity index 100% rename from assets/Stock PdP_TA/combo10.png rename to themes/Panel Attack/combo/combo10.png diff --git a/assets/Stock PdP_TA/combo11.png b/themes/Panel Attack/combo/combo11.png similarity index 100% rename from assets/Stock PdP_TA/combo11.png rename to themes/Panel Attack/combo/combo11.png diff --git a/assets/Stock PdP_TA/combo12.png b/themes/Panel Attack/combo/combo12.png similarity index 100% rename from assets/Stock PdP_TA/combo12.png rename to themes/Panel Attack/combo/combo12.png diff --git a/assets/Stock PdP_TA/combo13.png b/themes/Panel Attack/combo/combo13.png similarity index 100% rename from assets/Stock PdP_TA/combo13.png rename to themes/Panel Attack/combo/combo13.png diff --git a/assets/Stock PdP_TA/combo14.png b/themes/Panel Attack/combo/combo14.png similarity index 100% rename from assets/Stock PdP_TA/combo14.png rename to themes/Panel Attack/combo/combo14.png diff --git a/assets/Stock PdP_TA/combo15.png b/themes/Panel Attack/combo/combo15.png similarity index 100% rename from assets/Stock PdP_TA/combo15.png rename to themes/Panel Attack/combo/combo15.png diff --git a/assets/Stock PdP_TA/combo16.png b/themes/Panel Attack/combo/combo16.png similarity index 100% rename from assets/Stock PdP_TA/combo16.png rename to themes/Panel Attack/combo/combo16.png diff --git a/assets/Stock PdP_TA/combo17.png b/themes/Panel Attack/combo/combo17.png similarity index 100% rename from assets/Stock PdP_TA/combo17.png rename to themes/Panel Attack/combo/combo17.png diff --git a/assets/Stock PdP_TA/combo18.png b/themes/Panel Attack/combo/combo18.png similarity index 100% rename from assets/Stock PdP_TA/combo18.png rename to themes/Panel Attack/combo/combo18.png diff --git a/assets/Stock PdP_TA/combo19.png b/themes/Panel Attack/combo/combo19.png similarity index 100% rename from assets/Stock PdP_TA/combo19.png rename to themes/Panel Attack/combo/combo19.png diff --git a/assets/Stock PdP_TA/combo20.png b/themes/Panel Attack/combo/combo20.png similarity index 100% rename from assets/Stock PdP_TA/combo20.png rename to themes/Panel Attack/combo/combo20.png diff --git a/assets/Stock PdP_TA/combo21.png b/themes/Panel Attack/combo/combo21.png similarity index 100% rename from assets/Stock PdP_TA/combo21.png rename to themes/Panel Attack/combo/combo21.png diff --git a/assets/Stock PdP_TA/combo22.png b/themes/Panel Attack/combo/combo22.png similarity index 100% rename from assets/Stock PdP_TA/combo22.png rename to themes/Panel Attack/combo/combo22.png diff --git a/assets/Stock PdP_TA/combo23.png b/themes/Panel Attack/combo/combo23.png similarity index 100% rename from assets/Stock PdP_TA/combo23.png rename to themes/Panel Attack/combo/combo23.png diff --git a/assets/Stock PdP_TA/combo24.png b/themes/Panel Attack/combo/combo24.png similarity index 100% rename from assets/Stock PdP_TA/combo24.png rename to themes/Panel Attack/combo/combo24.png diff --git a/assets/Stock PdP_TA/combo25.png b/themes/Panel Attack/combo/combo25.png similarity index 100% rename from assets/Stock PdP_TA/combo25.png rename to themes/Panel Attack/combo/combo25.png diff --git a/assets/Stock PdP_TA/combo26.png b/themes/Panel Attack/combo/combo26.png similarity index 100% rename from assets/Stock PdP_TA/combo26.png rename to themes/Panel Attack/combo/combo26.png diff --git a/assets/Stock PdP_TA/combo27.png b/themes/Panel Attack/combo/combo27.png similarity index 100% rename from assets/Stock PdP_TA/combo27.png rename to themes/Panel Attack/combo/combo27.png diff --git a/assets/Stock PdP_TA/combo28.png b/themes/Panel Attack/combo/combo28.png similarity index 100% rename from assets/Stock PdP_TA/combo28.png rename to themes/Panel Attack/combo/combo28.png diff --git a/assets/Stock PdP_TA/combo29.png b/themes/Panel Attack/combo/combo29.png similarity index 100% rename from assets/Stock PdP_TA/combo29.png rename to themes/Panel Attack/combo/combo29.png diff --git a/assets/Stock PdP_TA/combo30.png b/themes/Panel Attack/combo/combo30.png similarity index 100% rename from assets/Stock PdP_TA/combo30.png rename to themes/Panel Attack/combo/combo30.png diff --git a/assets/Stock PdP_TA/combo31.png b/themes/Panel Attack/combo/combo31.png similarity index 100% rename from assets/Stock PdP_TA/combo31.png rename to themes/Panel Attack/combo/combo31.png diff --git a/assets/Stock PdP_TA/combo32.png b/themes/Panel Attack/combo/combo32.png similarity index 100% rename from assets/Stock PdP_TA/combo32.png rename to themes/Panel Attack/combo/combo32.png diff --git a/assets/Stock PdP_TA/combo33.png b/themes/Panel Attack/combo/combo33.png similarity index 100% rename from assets/Stock PdP_TA/combo33.png rename to themes/Panel Attack/combo/combo33.png diff --git a/assets/Stock PdP_TA/combo34.png b/themes/Panel Attack/combo/combo34.png similarity index 100% rename from assets/Stock PdP_TA/combo34.png rename to themes/Panel Attack/combo/combo34.png diff --git a/assets/Stock PdP_TA/combo35.png b/themes/Panel Attack/combo/combo35.png similarity index 100% rename from assets/Stock PdP_TA/combo35.png rename to themes/Panel Attack/combo/combo35.png diff --git a/assets/Stock PdP_TA/combo36.png b/themes/Panel Attack/combo/combo36.png similarity index 100% rename from assets/Stock PdP_TA/combo36.png rename to themes/Panel Attack/combo/combo36.png diff --git a/assets/Stock PdP_TA/combo37.png b/themes/Panel Attack/combo/combo37.png similarity index 100% rename from assets/Stock PdP_TA/combo37.png rename to themes/Panel Attack/combo/combo37.png diff --git a/assets/Stock PdP_TA/combo38.png b/themes/Panel Attack/combo/combo38.png similarity index 100% rename from assets/Stock PdP_TA/combo38.png rename to themes/Panel Attack/combo/combo38.png diff --git a/assets/Stock PdP_TA/combo39.png b/themes/Panel Attack/combo/combo39.png similarity index 100% rename from assets/Stock PdP_TA/combo39.png rename to themes/Panel Attack/combo/combo39.png diff --git a/assets/Stock PdP_TA/combo40.png b/themes/Panel Attack/combo/combo40.png similarity index 100% rename from assets/Stock PdP_TA/combo40.png rename to themes/Panel Attack/combo/combo40.png diff --git a/assets/Stock PdP_TA/combo41.png b/themes/Panel Attack/combo/combo41.png similarity index 100% rename from assets/Stock PdP_TA/combo41.png rename to themes/Panel Attack/combo/combo41.png diff --git a/assets/Stock PdP_TA/combo42.png b/themes/Panel Attack/combo/combo42.png similarity index 100% rename from assets/Stock PdP_TA/combo42.png rename to themes/Panel Attack/combo/combo42.png diff --git a/assets/Stock PdP_TA/combo43.png b/themes/Panel Attack/combo/combo43.png similarity index 100% rename from assets/Stock PdP_TA/combo43.png rename to themes/Panel Attack/combo/combo43.png diff --git a/assets/Stock PdP_TA/combo44.png b/themes/Panel Attack/combo/combo44.png similarity index 100% rename from assets/Stock PdP_TA/combo44.png rename to themes/Panel Attack/combo/combo44.png diff --git a/assets/Stock PdP_TA/combo45.png b/themes/Panel Attack/combo/combo45.png similarity index 100% rename from assets/Stock PdP_TA/combo45.png rename to themes/Panel Attack/combo/combo45.png diff --git a/assets/Stock PdP_TA/combo46.png b/themes/Panel Attack/combo/combo46.png similarity index 100% rename from assets/Stock PdP_TA/combo46.png rename to themes/Panel Attack/combo/combo46.png diff --git a/assets/Stock PdP_TA/combo47.png b/themes/Panel Attack/combo/combo47.png similarity index 100% rename from assets/Stock PdP_TA/combo47.png rename to themes/Panel Attack/combo/combo47.png diff --git a/assets/Stock PdP_TA/combo48.png b/themes/Panel Attack/combo/combo48.png similarity index 100% rename from assets/Stock PdP_TA/combo48.png rename to themes/Panel Attack/combo/combo48.png diff --git a/assets/Stock PdP_TA/combo49.png b/themes/Panel Attack/combo/combo49.png similarity index 100% rename from assets/Stock PdP_TA/combo49.png rename to themes/Panel Attack/combo/combo49.png diff --git a/assets/Stock PdP_TA/combo50.png b/themes/Panel Attack/combo/combo50.png similarity index 100% rename from assets/Stock PdP_TA/combo50.png rename to themes/Panel Attack/combo/combo50.png diff --git a/assets/Stock PdP_TA/combo51.png b/themes/Panel Attack/combo/combo51.png similarity index 100% rename from assets/Stock PdP_TA/combo51.png rename to themes/Panel Attack/combo/combo51.png diff --git a/assets/Stock PdP_TA/combo52.png b/themes/Panel Attack/combo/combo52.png similarity index 100% rename from assets/Stock PdP_TA/combo52.png rename to themes/Panel Attack/combo/combo52.png diff --git a/assets/Stock PdP_TA/combo53.png b/themes/Panel Attack/combo/combo53.png similarity index 100% rename from assets/Stock PdP_TA/combo53.png rename to themes/Panel Attack/combo/combo53.png diff --git a/assets/Stock PdP_TA/combo54.png b/themes/Panel Attack/combo/combo54.png similarity index 100% rename from assets/Stock PdP_TA/combo54.png rename to themes/Panel Attack/combo/combo54.png diff --git a/assets/Stock PdP_TA/combo55.png b/themes/Panel Attack/combo/combo55.png similarity index 100% rename from assets/Stock PdP_TA/combo55.png rename to themes/Panel Attack/combo/combo55.png diff --git a/assets/Stock PdP_TA/combo56.png b/themes/Panel Attack/combo/combo56.png similarity index 100% rename from assets/Stock PdP_TA/combo56.png rename to themes/Panel Attack/combo/combo56.png diff --git a/assets/Stock PdP_TA/combo57.png b/themes/Panel Attack/combo/combo57.png similarity index 100% rename from assets/Stock PdP_TA/combo57.png rename to themes/Panel Attack/combo/combo57.png diff --git a/assets/Stock PdP_TA/combo58.png b/themes/Panel Attack/combo/combo58.png similarity index 100% rename from assets/Stock PdP_TA/combo58.png rename to themes/Panel Attack/combo/combo58.png diff --git a/assets/Stock PdP_TA/combo59.png b/themes/Panel Attack/combo/combo59.png similarity index 100% rename from assets/Stock PdP_TA/combo59.png rename to themes/Panel Attack/combo/combo59.png diff --git a/assets/Stock PdP_TA/combo60.png b/themes/Panel Attack/combo/combo60.png similarity index 100% rename from assets/Stock PdP_TA/combo60.png rename to themes/Panel Attack/combo/combo60.png diff --git a/assets/Stock PdP_TA/combo61.png b/themes/Panel Attack/combo/combo61.png similarity index 100% rename from assets/Stock PdP_TA/combo61.png rename to themes/Panel Attack/combo/combo61.png diff --git a/assets/Stock PdP_TA/combo62.png b/themes/Panel Attack/combo/combo62.png similarity index 100% rename from assets/Stock PdP_TA/combo62.png rename to themes/Panel Attack/combo/combo62.png diff --git a/assets/Stock PdP_TA/combo63.png b/themes/Panel Attack/combo/combo63.png similarity index 100% rename from assets/Stock PdP_TA/combo63.png rename to themes/Panel Attack/combo/combo63.png diff --git a/assets/Stock PdP_TA/combo64.png b/themes/Panel Attack/combo/combo64.png similarity index 100% rename from assets/Stock PdP_TA/combo64.png rename to themes/Panel Attack/combo/combo64.png diff --git a/assets/Stock PdP_TA/combo65.png b/themes/Panel Attack/combo/combo65.png similarity index 100% rename from assets/Stock PdP_TA/combo65.png rename to themes/Panel Attack/combo/combo65.png diff --git a/assets/Stock PdP_TA/combo66.png b/themes/Panel Attack/combo/combo66.png similarity index 100% rename from assets/Stock PdP_TA/combo66.png rename to themes/Panel Attack/combo/combo66.png diff --git a/themes/Panel Attack/config.json b/themes/Panel Attack/config.json new file mode 100644 index 00000000..28c4cf2b --- /dev/null +++ b/themes/Panel Attack/config.json @@ -0,0 +1,32 @@ +{ +"healthbar_frame_Pos": [-17, -4], +"healthbar_frame_Scale": 3, +"healthbar_Pos": [-13, 148], +"healthbar_Scale": 1, +"healthbar_Rotate": 0, +"prestop_frame_Pos": [100, 1090], +"prestop_frame_Scale": 1, +"prestop_bar_Pos": [110, 1097], +"prestop_bar_Scale": 1, +"prestop_bar_Rotate": 0, +"prestop_Pos": [120, 1105], +"prestop_Scale": 1, +"stop_frame_Pos": [100, 1120], +"stop_frame_Scale": 1, +"stop_bar_Pos": [110, 1127], +"stop_bar_Scale": 1, +"stop_bar_Rotate": 0, +"stop_Pos": [120, 1135], +"stop_Scale": 1, +"shake_frame_Pos": [100, 1150], +"shake_frame_Scale": 1, +"shake_bar_Pos": [110, 1157], +"shake_bar_Scale": 1, +"shake_bar_Rotate": 0, +"shake_Pos": [120, 1165], +"shake_Scale": 1, +"multibar_frame_Pos": [110, 1100], +"multibar_frame_Scale": 1, +"multibar_Pos": [-13, 96], +"multibar_Scale": 1 +} \ No newline at end of file diff --git a/themes/Panel Attack/fade.png b/themes/Panel Attack/fade.png new file mode 100644 index 00000000..b67e97c8 Binary files /dev/null and b/themes/Panel Attack/fade.png differ diff --git a/themes/Panel Attack/flags/cn.png b/themes/Panel Attack/flags/cn.png new file mode 100644 index 00000000..0dd92f09 Binary files /dev/null and b/themes/Panel Attack/flags/cn.png differ diff --git a/themes/Panel Attack/flags/de.png b/themes/Panel Attack/flags/de.png new file mode 100644 index 00000000..67fe8a74 Binary files /dev/null and b/themes/Panel Attack/flags/de.png differ diff --git a/themes/Panel Attack/flags/es.png b/themes/Panel Attack/flags/es.png new file mode 100644 index 00000000..8c6c5b57 Binary files /dev/null and b/themes/Panel Attack/flags/es.png differ diff --git a/themes/Panel Attack/flags/fr.png b/themes/Panel Attack/flags/fr.png new file mode 100644 index 00000000..be5ab154 Binary files /dev/null and b/themes/Panel Attack/flags/fr.png differ diff --git a/themes/Panel Attack/flags/gb.png b/themes/Panel Attack/flags/gb.png new file mode 100644 index 00000000..b9a865ea Binary files /dev/null and b/themes/Panel Attack/flags/gb.png differ diff --git a/themes/Panel Attack/flags/in.png b/themes/Panel Attack/flags/in.png new file mode 100644 index 00000000..78fa461f Binary files /dev/null and b/themes/Panel Attack/flags/in.png differ diff --git a/themes/Panel Attack/flags/it.png b/themes/Panel Attack/flags/it.png new file mode 100644 index 00000000..2934b72e Binary files /dev/null and b/themes/Panel Attack/flags/it.png differ diff --git a/themes/Panel Attack/flags/jp.png b/themes/Panel Attack/flags/jp.png new file mode 100644 index 00000000..73b5be72 Binary files /dev/null and b/themes/Panel Attack/flags/jp.png differ diff --git a/themes/Panel Attack/flags/pt.png b/themes/Panel Attack/flags/pt.png new file mode 100644 index 00000000..aefa42cb Binary files /dev/null and b/themes/Panel Attack/flags/pt.png differ diff --git a/themes/Panel Attack/flags/us.png b/themes/Panel Attack/flags/us.png new file mode 100644 index 00000000..44b57fb6 Binary files /dev/null and b/themes/Panel Attack/flags/us.png differ diff --git a/assets/Stock PdP_TA/frame.png b/themes/Panel Attack/frame/frame1P.png similarity index 100% rename from assets/Stock PdP_TA/frame.png rename to themes/Panel Attack/frame/frame1P.png diff --git a/themes/Panel Attack/frame/frame2P.png b/themes/Panel Attack/frame/frame2P.png new file mode 100644 index 00000000..97537008 Binary files /dev/null and b/themes/Panel Attack/frame/frame2P.png differ diff --git a/themes/Panel Attack/frame/frame3P.png b/themes/Panel Attack/frame/frame3P.png new file mode 100644 index 00000000..af19e36a Binary files /dev/null and b/themes/Panel Attack/frame/frame3P.png differ diff --git a/themes/Panel Attack/frame/frame4P.png b/themes/Panel Attack/frame/frame4P.png new file mode 100644 index 00000000..43259e2c Binary files /dev/null and b/themes/Panel Attack/frame/frame4P.png differ diff --git a/themes/Panel Attack/frame/frame5P.png b/themes/Panel Attack/frame/frame5P.png new file mode 100644 index 00000000..3421c574 Binary files /dev/null and b/themes/Panel Attack/frame/frame5P.png differ diff --git a/themes/Panel Attack/frame/frame6P.png b/themes/Panel Attack/frame/frame6P.png new file mode 100644 index 00000000..cb0eef5f Binary files /dev/null and b/themes/Panel Attack/frame/frame6P.png differ diff --git a/themes/Panel Attack/frame/frame7P.png b/themes/Panel Attack/frame/frame7P.png new file mode 100644 index 00000000..a600f4b6 Binary files /dev/null and b/themes/Panel Attack/frame/frame7P.png differ diff --git a/themes/Panel Attack/frame/frame8P.png b/themes/Panel Attack/frame/frame8P.png new file mode 100644 index 00000000..81e80f35 Binary files /dev/null and b/themes/Panel Attack/frame/frame8P.png differ diff --git a/assets/Stock PdP_TA/wall.png b/themes/Panel Attack/frame/wall1P.png similarity index 100% rename from assets/Stock PdP_TA/wall.png rename to themes/Panel Attack/frame/wall1P.png diff --git a/themes/Panel Attack/frame/wall2P.png b/themes/Panel Attack/frame/wall2P.png new file mode 100644 index 00000000..afc2d091 Binary files /dev/null and b/themes/Panel Attack/frame/wall2P.png differ diff --git a/themes/Panel Attack/frame/wall3P.png b/themes/Panel Attack/frame/wall3P.png new file mode 100644 index 00000000..d145a06a Binary files /dev/null and b/themes/Panel Attack/frame/wall3P.png differ diff --git a/themes/Panel Attack/frame/wall4P.png b/themes/Panel Attack/frame/wall4P.png new file mode 100644 index 00000000..6d717ed2 Binary files /dev/null and b/themes/Panel Attack/frame/wall4P.png differ diff --git a/themes/Panel Attack/frame/wall5P.png b/themes/Panel Attack/frame/wall5P.png new file mode 100644 index 00000000..85efbaea Binary files /dev/null and b/themes/Panel Attack/frame/wall5P.png differ diff --git a/themes/Panel Attack/frame/wall6P.png b/themes/Panel Attack/frame/wall6P.png new file mode 100644 index 00000000..3893a3fa Binary files /dev/null and b/themes/Panel Attack/frame/wall6P.png differ diff --git a/themes/Panel Attack/frame/wall7P.png b/themes/Panel Attack/frame/wall7P.png new file mode 100644 index 00000000..3c22cc83 Binary files /dev/null and b/themes/Panel Attack/frame/wall7P.png differ diff --git a/themes/Panel Attack/frame/wall8P.png b/themes/Panel Attack/frame/wall8P.png new file mode 100644 index 00000000..7b69002c Binary files /dev/null and b/themes/Panel Attack/frame/wall8P.png differ diff --git a/themes/Panel Attack/healthbar.png b/themes/Panel Attack/healthbar.png new file mode 100644 index 00000000..40138323 Binary files /dev/null and b/themes/Panel Attack/healthbar.png differ diff --git a/themes/Panel Attack/healthbar_frame_1P.png b/themes/Panel Attack/healthbar_frame_1P.png new file mode 100644 index 00000000..63f41ba4 Binary files /dev/null and b/themes/Panel Attack/healthbar_frame_1P.png differ diff --git a/themes/Panel Attack/healthbar_frame_2P.png b/themes/Panel Attack/healthbar_frame_2P.png new file mode 100644 index 00000000..83ff12ac Binary files /dev/null and b/themes/Panel Attack/healthbar_frame_2P.png differ diff --git a/assets/Stock PdP_TA/level1.png b/themes/Panel Attack/level/level1.png similarity index 100% rename from assets/Stock PdP_TA/level1.png rename to themes/Panel Attack/level/level1.png diff --git a/assets/Stock PdP_TA/level10.png b/themes/Panel Attack/level/level10.png similarity index 100% rename from assets/Stock PdP_TA/level10.png rename to themes/Panel Attack/level/level10.png diff --git a/assets/Stock PdP_TA/level10unfocus.png b/themes/Panel Attack/level/level10unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level10unfocus.png rename to themes/Panel Attack/level/level10unfocus.png diff --git a/assets/Stock PdP_TA/level11.png b/themes/Panel Attack/level/level11.png similarity index 100% rename from assets/Stock PdP_TA/level11.png rename to themes/Panel Attack/level/level11.png diff --git a/assets/Stock PdP_TA/level11unfocus.png b/themes/Panel Attack/level/level11unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level11unfocus.png rename to themes/Panel Attack/level/level11unfocus.png diff --git a/assets/Stock PdP_TA/level2.png b/themes/Panel Attack/level/level2.png similarity index 100% rename from assets/Stock PdP_TA/level2.png rename to themes/Panel Attack/level/level2.png diff --git a/assets/Stock PdP_TA/level2unfocus.png b/themes/Panel Attack/level/level2unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level2unfocus.png rename to themes/Panel Attack/level/level2unfocus.png diff --git a/assets/Stock PdP_TA/level3.png b/themes/Panel Attack/level/level3.png similarity index 100% rename from assets/Stock PdP_TA/level3.png rename to themes/Panel Attack/level/level3.png diff --git a/assets/Stock PdP_TA/level3unfocus.png b/themes/Panel Attack/level/level3unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level3unfocus.png rename to themes/Panel Attack/level/level3unfocus.png diff --git a/assets/Stock PdP_TA/level4.png b/themes/Panel Attack/level/level4.png similarity index 100% rename from assets/Stock PdP_TA/level4.png rename to themes/Panel Attack/level/level4.png diff --git a/assets/Stock PdP_TA/level4unfocus.png b/themes/Panel Attack/level/level4unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level4unfocus.png rename to themes/Panel Attack/level/level4unfocus.png diff --git a/assets/Stock PdP_TA/level5.png b/themes/Panel Attack/level/level5.png similarity index 100% rename from assets/Stock PdP_TA/level5.png rename to themes/Panel Attack/level/level5.png diff --git a/assets/Stock PdP_TA/level5unfocus.png b/themes/Panel Attack/level/level5unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level5unfocus.png rename to themes/Panel Attack/level/level5unfocus.png diff --git a/assets/Stock PdP_TA/level6.png b/themes/Panel Attack/level/level6.png similarity index 100% rename from assets/Stock PdP_TA/level6.png rename to themes/Panel Attack/level/level6.png diff --git a/assets/Stock PdP_TA/level6unfocus.png b/themes/Panel Attack/level/level6unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level6unfocus.png rename to themes/Panel Attack/level/level6unfocus.png diff --git a/assets/Stock PdP_TA/level7.png b/themes/Panel Attack/level/level7.png similarity index 100% rename from assets/Stock PdP_TA/level7.png rename to themes/Panel Attack/level/level7.png diff --git a/assets/Stock PdP_TA/level7unfocus.png b/themes/Panel Attack/level/level7unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level7unfocus.png rename to themes/Panel Attack/level/level7unfocus.png diff --git a/assets/Stock PdP_TA/level8.png b/themes/Panel Attack/level/level8.png similarity index 100% rename from assets/Stock PdP_TA/level8.png rename to themes/Panel Attack/level/level8.png diff --git a/assets/Stock PdP_TA/level8unfocus.png b/themes/Panel Attack/level/level8unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level8unfocus.png rename to themes/Panel Attack/level/level8unfocus.png diff --git a/assets/Stock PdP_TA/level9.png b/themes/Panel Attack/level/level9.png similarity index 100% rename from assets/Stock PdP_TA/level9.png rename to themes/Panel Attack/level/level9.png diff --git a/assets/Stock PdP_TA/level9unfocus.png b/themes/Panel Attack/level/level9unfocus.png similarity index 100% rename from assets/Stock PdP_TA/level9unfocus.png rename to themes/Panel Attack/level/level9unfocus.png diff --git a/assets/Stock PdP_TA/level_cursor.png b/themes/Panel Attack/level/level_cursor.png similarity index 100% rename from assets/Stock PdP_TA/level_cursor.png rename to themes/Panel Attack/level/level_cursor.png diff --git a/themes/Panel Attack/level_1P.png b/themes/Panel Attack/level_1P.png new file mode 100644 index 00000000..665ba854 Binary files /dev/null and b/themes/Panel Attack/level_1P.png differ diff --git a/themes/Panel Attack/level_2P.png b/themes/Panel Attack/level_2P.png new file mode 100644 index 00000000..f7ee23e8 Binary files /dev/null and b/themes/Panel Attack/level_2P.png differ diff --git a/themes/Panel Attack/level_numbers_1P.png b/themes/Panel Attack/level_numbers_1P.png new file mode 100644 index 00000000..045e7c7a Binary files /dev/null and b/themes/Panel Attack/level_numbers_1P.png differ diff --git a/themes/Panel Attack/level_numbers_2P.png b/themes/Panel Attack/level_numbers_2P.png new file mode 100644 index 00000000..d2bd58f7 Binary files /dev/null and b/themes/Panel Attack/level_numbers_2P.png differ diff --git a/assets/Stock PdP_TA/loading.png b/themes/Panel Attack/loading.png similarity index 100% rename from assets/Stock PdP_TA/loading.png rename to themes/Panel Attack/loading.png diff --git a/themes/Panel Attack/moves.png b/themes/Panel Attack/moves.png new file mode 100644 index 00000000..3b69dc31 Binary files /dev/null and b/themes/Panel Attack/moves.png differ diff --git a/themes/Panel Attack/multibar_frame.png b/themes/Panel Attack/multibar_frame.png new file mode 100644 index 00000000..b7f24a61 Binary files /dev/null and b/themes/Panel Attack/multibar_frame.png differ diff --git a/themes/Panel Attack/multibar_prestop_bar.png b/themes/Panel Attack/multibar_prestop_bar.png new file mode 100644 index 00000000..63d536a1 Binary files /dev/null and b/themes/Panel Attack/multibar_prestop_bar.png differ diff --git a/themes/Panel Attack/multibar_shake_bar.png b/themes/Panel Attack/multibar_shake_bar.png new file mode 100644 index 00000000..1d0d4a00 Binary files /dev/null and b/themes/Panel Attack/multibar_shake_bar.png differ diff --git a/themes/Panel Attack/multibar_stop_bar.png b/themes/Panel Attack/multibar_stop_bar.png new file mode 100644 index 00000000..3ec86c98 Binary files /dev/null and b/themes/Panel Attack/multibar_stop_bar.png differ diff --git a/themes/Panel Attack/numbers_1P.png b/themes/Panel Attack/numbers_1P.png new file mode 100644 index 00000000..18160047 Binary files /dev/null and b/themes/Panel Attack/numbers_1P.png differ diff --git a/themes/Panel Attack/numbers_2P.png b/themes/Panel Attack/numbers_2P.png new file mode 100644 index 00000000..be5b97b7 Binary files /dev/null and b/themes/Panel Attack/numbers_2P.png differ diff --git a/assets/Stock PdP_TA/player_1.png b/themes/Panel Attack/p1.png similarity index 100% rename from assets/Stock PdP_TA/player_1.png rename to themes/Panel Attack/p1.png diff --git a/assets/Stock PdP_TA/cur0.png b/themes/Panel Attack/p1_cursor.png similarity index 100% rename from assets/Stock PdP_TA/cur0.png rename to themes/Panel Attack/p1_cursor.png diff --git a/assets/Stock PdP_TA/char_sel_cur_1P_pos1.png b/themes/Panel Attack/p1_select_screen_cursor1.png similarity index 100% rename from assets/Stock PdP_TA/char_sel_cur_1P_pos1.png rename to themes/Panel Attack/p1_select_screen_cursor1.png diff --git a/assets/Stock PdP_TA/char_sel_cur_1P_pos2.png b/themes/Panel Attack/p1_select_screen_cursor2.png similarity index 100% rename from assets/Stock PdP_TA/char_sel_cur_1P_pos2.png rename to themes/Panel Attack/p1_select_screen_cursor2.png diff --git a/assets/Stock PdP_TA/player_2.png b/themes/Panel Attack/p2.png similarity index 100% rename from assets/Stock PdP_TA/player_2.png rename to themes/Panel Attack/p2.png diff --git a/assets/Stock PdP_TA/cur1.png b/themes/Panel Attack/p2_cursor.png similarity index 100% rename from assets/Stock PdP_TA/cur1.png rename to themes/Panel Attack/p2_cursor.png diff --git a/assets/Stock PdP_TA/char_sel_cur_2P_pos1.png b/themes/Panel Attack/p2_select_screen_cursor1.png similarity index 100% rename from assets/Stock PdP_TA/char_sel_cur_2P_pos1.png rename to themes/Panel Attack/p2_select_screen_cursor1.png diff --git a/assets/Stock PdP_TA/char_sel_cur_2P_pos2.png b/themes/Panel Attack/p2_select_screen_cursor2.png similarity index 100% rename from assets/Stock PdP_TA/char_sel_cur_2P_pos2.png rename to themes/Panel Attack/p2_select_screen_cursor2.png diff --git a/themes/Panel Attack/pause.png b/themes/Panel Attack/pause.png new file mode 100644 index 00000000..580ef4a8 Binary files /dev/null and b/themes/Panel Attack/pause.png differ diff --git a/themes/Panel Attack/prestop_bar.png b/themes/Panel Attack/prestop_bar.png new file mode 100644 index 00000000..71bfaeea Binary files /dev/null and b/themes/Panel Attack/prestop_bar.png differ diff --git a/themes/Panel Attack/prestop_frame.png b/themes/Panel Attack/prestop_frame.png new file mode 100644 index 00000000..2f313968 Binary files /dev/null and b/themes/Panel Attack/prestop_frame.png differ diff --git a/themes/Panel Attack/random_character.png b/themes/Panel Attack/random_character.png new file mode 100644 index 00000000..7585aacf Binary files /dev/null and b/themes/Panel Attack/random_character.png differ diff --git a/themes/Panel Attack/random_stage.png b/themes/Panel Attack/random_stage.png new file mode 100644 index 00000000..27fdc5b4 Binary files /dev/null and b/themes/Panel Attack/random_stage.png differ diff --git a/themes/Panel Attack/ranked.png b/themes/Panel Attack/ranked.png new file mode 100644 index 00000000..440b7f94 Binary files /dev/null and b/themes/Panel Attack/ranked.png differ diff --git a/themes/Panel Attack/rating_1P.png b/themes/Panel Attack/rating_1P.png new file mode 100644 index 00000000..36957e64 Binary files /dev/null and b/themes/Panel Attack/rating_1P.png differ diff --git a/themes/Panel Attack/rating_2P.png b/themes/Panel Attack/rating_2P.png new file mode 100644 index 00000000..e27765c5 Binary files /dev/null and b/themes/Panel Attack/rating_2P.png differ diff --git a/assets/Stock PdP_TA/ready.png b/themes/Panel Attack/ready.png similarity index 100% rename from assets/Stock PdP_TA/ready.png rename to themes/Panel Attack/ready.png diff --git a/themes/Panel Attack/score_1P.png b/themes/Panel Attack/score_1P.png new file mode 100644 index 00000000..ed97e5b4 Binary files /dev/null and b/themes/Panel Attack/score_1P.png differ diff --git a/themes/Panel Attack/score_2P.png b/themes/Panel Attack/score_2P.png new file mode 100644 index 00000000..fda8e407 Binary files /dev/null and b/themes/Panel Attack/score_2P.png differ diff --git a/sounds/Stock PdP_TA/SFX/countdown.ogg b/themes/Panel Attack/sfx/countdown.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/countdown.ogg rename to themes/Panel Attack/sfx/countdown.ogg diff --git a/sounds/Stock PdP_TA/SFX/fanfare1.ogg b/themes/Panel Attack/sfx/fanfare1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/fanfare1.ogg rename to themes/Panel Attack/sfx/fanfare1.ogg diff --git a/sounds/Stock PdP_TA/SFX/fanfare2.ogg b/themes/Panel Attack/sfx/fanfare2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/fanfare2.ogg rename to themes/Panel Attack/sfx/fanfare2.ogg diff --git a/sounds/Stock PdP_TA/SFX/fanfare3.ogg b/themes/Panel Attack/sfx/fanfare3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/fanfare3.ogg rename to themes/Panel Attack/sfx/fanfare3.ogg diff --git a/sounds/Stock PdP_TA/SFX/gameover.ogg b/themes/Panel Attack/sfx/gameover.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/gameover.ogg rename to themes/Panel Attack/sfx/gameover.ogg diff --git a/sounds/Stock PdP_TA/SFX/go.ogg b/themes/Panel Attack/sfx/go.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/go.ogg rename to themes/Panel Attack/sfx/go.ogg diff --git a/sounds/Stock PdP_TA/SFX/land.ogg b/themes/Panel Attack/sfx/land.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/land.ogg rename to themes/Panel Attack/sfx/land.ogg diff --git a/sounds/Stock PdP_TA/SFX/menu_cancel.ogg b/themes/Panel Attack/sfx/menu_cancel.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/menu_cancel.ogg rename to themes/Panel Attack/sfx/menu_cancel.ogg diff --git a/sounds/Stock PdP_TA/SFX/menu_move.ogg b/themes/Panel Attack/sfx/menu_move.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/menu_move.ogg rename to themes/Panel Attack/sfx/menu_move.ogg diff --git a/sounds/Stock PdP_TA/SFX/menu_validate.ogg b/themes/Panel Attack/sfx/menu_validate.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/menu_validate.ogg rename to themes/Panel Attack/sfx/menu_validate.ogg diff --git a/sounds/Stock PdP_TA/SFX/move.ogg b/themes/Panel Attack/sfx/move.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/move.ogg rename to themes/Panel Attack/sfx/move.ogg diff --git a/themes/Panel Attack/sfx/notification.ogg b/themes/Panel Attack/sfx/notification.ogg new file mode 100644 index 00000000..9ce5f7f8 Binary files /dev/null and b/themes/Panel Attack/sfx/notification.ogg differ diff --git a/sounds/Stock PdP_TA/SFX/pop1-1.ogg b/themes/Panel Attack/sfx/pop1-1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-1.ogg rename to themes/Panel Attack/sfx/pop1-1.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-10.ogg b/themes/Panel Attack/sfx/pop1-10.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-10.ogg rename to themes/Panel Attack/sfx/pop1-10.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-2.ogg b/themes/Panel Attack/sfx/pop1-2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-2.ogg rename to themes/Panel Attack/sfx/pop1-2.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-3.ogg b/themes/Panel Attack/sfx/pop1-3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-3.ogg rename to themes/Panel Attack/sfx/pop1-3.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-4.ogg b/themes/Panel Attack/sfx/pop1-4.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-4.ogg rename to themes/Panel Attack/sfx/pop1-4.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-5.ogg b/themes/Panel Attack/sfx/pop1-5.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-5.ogg rename to themes/Panel Attack/sfx/pop1-5.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-6.ogg b/themes/Panel Attack/sfx/pop1-6.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-6.ogg rename to themes/Panel Attack/sfx/pop1-6.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-7.ogg b/themes/Panel Attack/sfx/pop1-7.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-7.ogg rename to themes/Panel Attack/sfx/pop1-7.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-8.ogg b/themes/Panel Attack/sfx/pop1-8.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-8.ogg rename to themes/Panel Attack/sfx/pop1-8.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop1-9.ogg b/themes/Panel Attack/sfx/pop1-9.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop1-9.ogg rename to themes/Panel Attack/sfx/pop1-9.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-1.ogg b/themes/Panel Attack/sfx/pop2-1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-1.ogg rename to themes/Panel Attack/sfx/pop2-1.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-10.ogg b/themes/Panel Attack/sfx/pop2-10.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-10.ogg rename to themes/Panel Attack/sfx/pop2-10.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-2.ogg b/themes/Panel Attack/sfx/pop2-2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-2.ogg rename to themes/Panel Attack/sfx/pop2-2.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-3.ogg b/themes/Panel Attack/sfx/pop2-3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-3.ogg rename to themes/Panel Attack/sfx/pop2-3.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-4.ogg b/themes/Panel Attack/sfx/pop2-4.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-4.ogg rename to themes/Panel Attack/sfx/pop2-4.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-5.ogg b/themes/Panel Attack/sfx/pop2-5.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-5.ogg rename to themes/Panel Attack/sfx/pop2-5.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-6.ogg b/themes/Panel Attack/sfx/pop2-6.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-6.ogg rename to themes/Panel Attack/sfx/pop2-6.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-7.ogg b/themes/Panel Attack/sfx/pop2-7.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-7.ogg rename to themes/Panel Attack/sfx/pop2-7.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-8.ogg b/themes/Panel Attack/sfx/pop2-8.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-8.ogg rename to themes/Panel Attack/sfx/pop2-8.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop2-9.ogg b/themes/Panel Attack/sfx/pop2-9.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop2-9.ogg rename to themes/Panel Attack/sfx/pop2-9.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-1.ogg b/themes/Panel Attack/sfx/pop3-1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-1.ogg rename to themes/Panel Attack/sfx/pop3-1.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-10.ogg b/themes/Panel Attack/sfx/pop3-10.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-10.ogg rename to themes/Panel Attack/sfx/pop3-10.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-2.ogg b/themes/Panel Attack/sfx/pop3-2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-2.ogg rename to themes/Panel Attack/sfx/pop3-2.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-3.ogg b/themes/Panel Attack/sfx/pop3-3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-3.ogg rename to themes/Panel Attack/sfx/pop3-3.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-4.ogg b/themes/Panel Attack/sfx/pop3-4.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-4.ogg rename to themes/Panel Attack/sfx/pop3-4.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-5.ogg b/themes/Panel Attack/sfx/pop3-5.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-5.ogg rename to themes/Panel Attack/sfx/pop3-5.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-6.ogg b/themes/Panel Attack/sfx/pop3-6.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-6.ogg rename to themes/Panel Attack/sfx/pop3-6.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-7.ogg b/themes/Panel Attack/sfx/pop3-7.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-7.ogg rename to themes/Panel Attack/sfx/pop3-7.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-8.ogg b/themes/Panel Attack/sfx/pop3-8.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-8.ogg rename to themes/Panel Attack/sfx/pop3-8.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop3-9.ogg b/themes/Panel Attack/sfx/pop3-9.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop3-9.ogg rename to themes/Panel Attack/sfx/pop3-9.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-1.ogg b/themes/Panel Attack/sfx/pop4-1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-1.ogg rename to themes/Panel Attack/sfx/pop4-1.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-10.ogg b/themes/Panel Attack/sfx/pop4-10.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-10.ogg rename to themes/Panel Attack/sfx/pop4-10.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-2.ogg b/themes/Panel Attack/sfx/pop4-2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-2.ogg rename to themes/Panel Attack/sfx/pop4-2.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-3.ogg b/themes/Panel Attack/sfx/pop4-3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-3.ogg rename to themes/Panel Attack/sfx/pop4-3.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-4.ogg b/themes/Panel Attack/sfx/pop4-4.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-4.ogg rename to themes/Panel Attack/sfx/pop4-4.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-5.ogg b/themes/Panel Attack/sfx/pop4-5.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-5.ogg rename to themes/Panel Attack/sfx/pop4-5.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-6.ogg b/themes/Panel Attack/sfx/pop4-6.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-6.ogg rename to themes/Panel Attack/sfx/pop4-6.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-7.ogg b/themes/Panel Attack/sfx/pop4-7.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-7.ogg rename to themes/Panel Attack/sfx/pop4-7.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-8.ogg b/themes/Panel Attack/sfx/pop4-8.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-8.ogg rename to themes/Panel Attack/sfx/pop4-8.ogg diff --git a/sounds/Stock PdP_TA/SFX/pop4-9.ogg b/themes/Panel Attack/sfx/pop4-9.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/pop4-9.ogg rename to themes/Panel Attack/sfx/pop4-9.ogg diff --git a/sounds/Stock PdP_TA/SFX/swap.ogg b/themes/Panel Attack/sfx/swap.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/swap.ogg rename to themes/Panel Attack/sfx/swap.ogg diff --git a/sounds/Stock PdP_TA/SFX/thud_1.ogg b/themes/Panel Attack/sfx/thud_1.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/thud_1.ogg rename to themes/Panel Attack/sfx/thud_1.ogg diff --git a/sounds/Stock PdP_TA/SFX/thud_2.ogg b/themes/Panel Attack/sfx/thud_2.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/thud_2.ogg rename to themes/Panel Attack/sfx/thud_2.ogg diff --git a/sounds/Stock PdP_TA/SFX/thud_3.ogg b/themes/Panel Attack/sfx/thud_3.ogg similarity index 100% rename from sounds/Stock PdP_TA/SFX/thud_3.ogg rename to themes/Panel Attack/sfx/thud_3.ogg diff --git a/themes/Panel Attack/shake_bar.png b/themes/Panel Attack/shake_bar.png new file mode 100644 index 00000000..3b47547a Binary files /dev/null and b/themes/Panel Attack/shake_bar.png differ diff --git a/themes/Panel Attack/shake_frame.png b/themes/Panel Attack/shake_frame.png new file mode 100644 index 00000000..727b083a Binary files /dev/null and b/themes/Panel Attack/shake_frame.png differ diff --git a/themes/Panel Attack/speed_1P.png b/themes/Panel Attack/speed_1P.png new file mode 100644 index 00000000..d34ef8ce Binary files /dev/null and b/themes/Panel Attack/speed_1P.png differ diff --git a/themes/Panel Attack/speed_2P.png b/themes/Panel Attack/speed_2P.png new file mode 100644 index 00000000..25b88b2f Binary files /dev/null and b/themes/Panel Attack/speed_2P.png differ diff --git a/themes/Panel Attack/stop_bar.png b/themes/Panel Attack/stop_bar.png new file mode 100644 index 00000000..9fb98707 Binary files /dev/null and b/themes/Panel Attack/stop_bar.png differ diff --git a/themes/Panel Attack/stop_frame.png b/themes/Panel Attack/stop_frame.png new file mode 100644 index 00000000..089160b2 Binary files /dev/null and b/themes/Panel Attack/stop_frame.png differ diff --git a/themes/Panel Attack/super.png b/themes/Panel Attack/super.png new file mode 100644 index 00000000..f69bc51c Binary files /dev/null and b/themes/Panel Attack/super.png differ diff --git a/themes/Panel Attack/time.png b/themes/Panel Attack/time.png new file mode 100644 index 00000000..f38b107e Binary files /dev/null and b/themes/Panel Attack/time.png differ diff --git a/themes/Panel Attack/time_numbers.png b/themes/Panel Attack/time_numbers.png new file mode 100644 index 00000000..029d6c1b Binary files /dev/null and b/themes/Panel Attack/time_numbers.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/healthbar.png b/themes/Panel Attack/unused - might be used in the future/healthbar.png new file mode 100644 index 00000000..6f774882 Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/healthbar.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/healthbar_frame_1P.png b/themes/Panel Attack/unused - might be used in the future/healthbar_frame_1P.png new file mode 100644 index 00000000..2f114e5b Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/healthbar_frame_1P.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/healthbar_frame_2P.png b/themes/Panel Attack/unused - might be used in the future/healthbar_frame_2P.png new file mode 100644 index 00000000..f36e4218 Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/healthbar_frame_2P.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/multibar_prestop_bar.png b/themes/Panel Attack/unused - might be used in the future/multibar_prestop_bar.png new file mode 100644 index 00000000..71bfaeea Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/multibar_prestop_bar.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/multibar_shake_bar.png b/themes/Panel Attack/unused - might be used in the future/multibar_shake_bar.png new file mode 100644 index 00000000..3b47547a Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/multibar_shake_bar.png differ diff --git a/themes/Panel Attack/unused - might be used in the future/multibar_stop_bar.png b/themes/Panel Attack/unused - might be used in the future/multibar_stop_bar.png new file mode 100644 index 00000000..9fb98707 Binary files /dev/null and b/themes/Panel Attack/unused - might be used in the future/multibar_stop_bar.png differ diff --git a/themes/Panel Attack/wins.png b/themes/Panel Attack/wins.png new file mode 100644 index 00000000..2ae3e14f Binary files /dev/null and b/themes/Panel Attack/wins.png differ diff --git a/util.lua b/util.lua index 1fabc090..aeeef026 100644 --- a/util.lua +++ b/util.lua @@ -11,6 +11,10 @@ function bound(a, b, c) else return b end end +function linear_smooth(value, min, max) + return (value - min)/(max-min) +end + -- mods b so a<=b<=c function wrap(a, b, c) return (b-a)%(c-a+1)+a @@ -229,4 +233,11 @@ end function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) -end \ No newline at end of file +end + +function get_directory_contents(path) + local path = (path and path or "") + local results = love.filesystem.getDirectoryItems(path) + + return results +end