diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..5c43ac8 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,49 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Build and deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: + - master + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Install love.js + run: npm install -g love.js + - name: Run make recipe + run: make web + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'build/__site/' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitignore b/.gitignore index f30924e..718e86e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ !editor.p8 !test/*.p8 .DS_Store -build/*.love +build/* luacov.report.out luacov.stats.out +.luarc.json +node_modules/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2809036 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Love.js-Api-Player"] + path = Love.js-Api-Player + url = https://github.com/MrcSnm/Love.js-Api-Player +[submodule "keybindings.lua"] + path = keybindings.lua + url = https://github.com/ZatFinn/keybindings.lua diff --git a/.luacheckrc b/.luacheckrc index 4303ca9..ed274fa 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -16,6 +16,7 @@ globals = { "love.handlers", "love.graphics.newScreenshot", "love.graphics.isActive", + "ke", -- functions "warning", @@ -34,6 +35,8 @@ ignore = { } exclude_files = { + "Love.js-Api-Player", + "keybindings.lua", "lib", "spec", ".DS_Store", diff --git a/Love.js-Api-Player b/Love.js-Api-Player new file mode 160000 index 0000000..fbdf10c --- /dev/null +++ b/Love.js-Api-Player @@ -0,0 +1 @@ +Subproject commit fbdf10cd34875c642588c591aeb17c8b2764e131 diff --git a/README.md b/README.md index f31eff5..4ffe5d8 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,25 @@ In the center of the screen, you'll see the PICO-8 screen, displaying the curren On the left, you'll see the HUD, displaying the current frame number, and an input display, with the currently pressed inputs On the right, you'll see the pianoroll, which shows the inputs in the frames around the current one +### Web build + +Some changes were made to make this project more compatible with +web/emscripten/wasm. Right now it is a work-in-progress. For building, +[Davidobot's fork of love.js](https://github.com/Davidobot/love.js) is used, so +make sure to have the npm package for it installed. + +```shell +$ npm -g install love.js +$ git clone --recursive https://github.com/gonengazit/Celia && cd Celia +$ make web +$ python -m http.server -b 127.0.0.1 -d build/__site/ # example way to host +``` + +Building on Windows should be possible as well, since the js tools are +cross-platform. However without going to the lengths of installing cygwin or +git bash with info-zip, the [makefile](makefile) recipe won't run, and you'll +have to do the copying and zipping manually. + ## Controls * __L__ - advance 1 frame forward * __K__ - rewind 1 frame back @@ -91,9 +110,7 @@ Warning: making changes to variables in the PICO-8 instance, then rewinding befo * [Original Celeste TAS tool by akliant917](https://github.com/CelesteClassic/ClassicTAS) * [Lua parser taken and modified from LuaMinify](https://github.com/stravant/LuaMinify) - - - - - - +## Web specific +* [LuaJIT-like bitops implementation in pure lua by DavidM](https://github.com/davidm/lua-bit-numberlua) +* [Davidobot's fork of love.js](https://github.com/Davidobot/love.js) +* [globalizeFS.js from love.js-api-player by MrcSnm (included as a submodule)](https://github.com/MrcSnm/Love.js-Api-Player) diff --git a/api.lua b/api.lua index 1c8b809..11ca2b5 100644 --- a/api.lua +++ b/api.lua @@ -1,3 +1,5 @@ +local bit = require("numberlua").bit + local api = {} local flr = math.floor @@ -75,15 +77,15 @@ end function api._call(code) code = patch_lua(code) - local ok, f, e = pcall(load, code, "repl") - if not ok or f == nil then + local f, e = loadstring(code, "repl") + if f == nil then api.rectfill(0, api._getcursory(), 128, api._getcursory() + 5 + 6, 0) api.print("syntax error", 14) api.print(api.sub(e, 20), 6) return false else setfenv(f, pico8.cart) - ok, e = pcall(f) + local ok, e = pcall(f) if not ok then api.rectfill(0, api._getcursory(), 128, api._getcursory() + 5 + 6, 0) api.print("runtime error", 14) @@ -1451,8 +1453,6 @@ function api.atan2(x, y) return (0.75 + math.atan2(x, y) / (math.pi * 2)) % 1.0 end -local bit = require("bit") - function api.band(x, y) return bit.band(x*0x10000, y*0x10000)/0x10000 end @@ -1525,8 +1525,8 @@ function api.run() pico8.cartdata[i] = 0 end - local ok, f, e = pcall(load, loaded_code, cartname) - if not ok or f == nil then + local f, e = loadstring(loaded_code, cartname) + if f == nil then log("=======8<========") log(loaded_code) log("=======>8========") @@ -1537,7 +1537,7 @@ function api.run() love.graphics.setCanvas(pico8.screen) love.graphics.origin() restore_clip() - ok, e = pcall(f) + local ok, e = pcall(f) if not ok then error("Error running lua: " .. tostring(e)) else diff --git a/cart.lua b/cart.lua index a4986ad..d8e085e 100644 --- a/cart.lua +++ b/cart.lua @@ -1,3 +1,4 @@ +local bit = require("numberlua").bit local api = require("api") local parse = require("parser/ParseLua") diff --git a/cctas.lua b/cctas.lua index 60685fe..24a77de 100644 --- a/cctas.lua +++ b/cctas.lua @@ -7,7 +7,6 @@ local api = require("api") local console = require("console") - --TODO: probably call load_level directly --or at least make sure rng seeds are set etc function cctas:init() @@ -50,6 +49,7 @@ function cctas:init() self.full_game_playback = false self:state_changed() + self:update_working_file() end function cctas:perform_inject() @@ -118,14 +118,14 @@ function cctas:keypressed(key, isrepeat) self:loading_jank_keypress(key,isrepeat) elseif self.modify_rng_seeds then self:rng_seed_keypress(key,isrepeat) - elseif key=='a' and not isrepeat then + elseif ke.jank_offset and not isrepeat then -- TODO: telegraph this better? if not self.cart_settings.disable_loading_jank then self:full_rewind() self:push_undo_state() self.modify_loading_jank = true end - elseif key=='b' and not isrepeat then + elseif ke.rng_seeding and not isrepeat then self.rng_seed_idx = -1 -- don't enable rng mode if no seedable objects exist if self:advance_seeded_obj(1) then @@ -134,36 +134,33 @@ function cctas:keypressed(key, isrepeat) self:push_undo_state() self.modify_rng_seeds = true end - elseif key=='f' then + elseif ke.next_level then self:push_undo_state() self:next_level() - elseif key=='s' then + elseif ke.prev_level then self:push_undo_state() self:prev_level() - elseif key=='d' and love.keyboard.isDown('lshift', 'rshift') then + elseif ke.rewind then self:player_rewind() - elseif key=='g' and love.keyboard.isDown('lshift', 'rshift') then + elseif ke.level_gif then self:start_gif_recording() - elseif key == 'n' and love.keyboard.isDown('lshift', 'rshift') then + elseif ke.full_playback then self:push_undo_state() self:begin_full_game_playback() - elseif key == 'u' then + elseif ke.clean_save then self:push_undo_state() self:begin_cleanup_save() - elseif key == '=' then - if love.keyboard.isDown('lshift', 'rshift') then - -- + - if self.max_djump_overload ==-1 then - self.max_djump_overload = pico8.cart.max_djump + 1 - else - self.max_djump_overload = self.max_djump_overload + 1 - end - self:load_level(self:level_index(), false) + elseif ke.inc_djump then + if self.max_djump_overload ==-1 then + self.max_djump_overload = pico8.cart.max_djump + 1 else - self.max_djump_overload = -1 - self:load_level(self:level_index(), false) + self.max_djump_overload = self.max_djump_overload + 1 end - elseif key == '-' then + self:load_level(self:level_index(), false) + elseif ke.reset_djump then + self.max_djump_overload = -1 + self:load_level(self:level_index(), false) + elseif ke.dec_djump then if self.max_djump_overload ==-1 then self.max_djump_overload = pico8.cart.max_djump - 1 else @@ -171,12 +168,12 @@ function cctas:keypressed(key, isrepeat) end self.max_djump_overload = math.max(self.max_djump_overload, 0) self:load_level(self:level_index(), false) - elseif key=='y' then + elseif ke.print_pos then local p = self:find_player() if p then print(p) end - elseif key=='c' and love.keyboard.isDown('lctrl','rctrl','lgui','rgui') then + elseif ke.copy then --copy player position to clipboard local p = self:find_player() if p then @@ -188,11 +185,11 @@ function cctas:keypressed(key, isrepeat) end function cctas:loading_jank_keypress(key,isrepeat) - if key=='up' then + if ke.inc_jank then self.loading_jank_offset = math.min(self.loading_jank_offset +1, math.max(#pico8.cart.objects-self.prev_obj_count + 1,0)) - elseif key == 'down' then + elseif ke.dec_jank then self.loading_jank_offset = math.max(self.loading_jank_offset - 1, math.min(-self.prev_obj_count+1,0)) - elseif key == 'a' and not isrepeat then + elseif ke.quit_jank and not isrepeat then self.modify_loading_jank = false self:load_level(self:level_index(),false) end @@ -201,11 +198,11 @@ end function cctas:rng_seed_keypress(key,isrepeat) -- TODO: make seed visually update in the current frame, and make rewinding not visually broken - if key=='up' or key == 'down' then + if ke.inc_rng or ke.dec_rng then local obj = pico8.cart.objects[self.rng_seed_idx] local seed = self:get_seed_handler(obj) if seed ~= nil then - if key=='up' then + if ke.inc_rng then seed.increase_seed(obj) else seed.decrease_seed(obj) @@ -220,11 +217,11 @@ function cctas:rng_seed_keypress(key,isrepeat) -- self:rewind() -- self:step() end - elseif key == 'right' then + elseif ke.next_object then self:advance_seeded_obj(1) - elseif key == 'left' then + elseif ke.prev_object then self:advance_seeded_obj(-1) - elseif key == 'b' and not isrepeat then + elseif ke.quit_rng and not isrepeat then self.modify_rng_seeds = false end end @@ -336,7 +333,7 @@ function cctas:load_level(idx, reset_changes) -- for the first level, assume no objects get loading janked by default -- also do not apply loading jank if it is disable self.loading_jank_offset = - (self.cart_settings.disable_loading_jank or self:level_index() == self.first_level) and #pico8.cart.objects + 1 or 0 + (self.cart_settings.disable_loading_jank or self:level_index() == self.first_level) and #pico8.cart.objects + 1 or 0 end for i = self.prev_obj_count+ self.loading_jank_offset, #pico8.cart.objects do @@ -374,9 +371,11 @@ function cctas:level_index() end function cctas:next_level() self:load_level(self:level_index()+1,true) + self:update_working_file() end function cctas:prev_level() self:load_level(self:level_index()-1,true) + self:update_working_file() end function cctas:find_player() @@ -464,7 +463,7 @@ function cctas:player_rewind() else self.seek = { finish_condition = function() - return self.inputs_active + return self.inputs_active end, on_finish = function() end, fast_forward=true diff --git a/conf.lua b/conf.lua index be6a71e..738b100 100644 --- a/conf.lua +++ b/conf.lua @@ -11,7 +11,7 @@ function love.conf(t) t.version = "11.3" t.window.title = "Celia" - t.window.icon = "icon.png" + t.window.icon = "res/theme/icon.png" t.window.width = 1344 t.window.height = 768 t.window.resizable = true diff --git a/deepcopy.lua b/deepcopy.lua index dffd391..1ef2644 100644 --- a/deepcopy.lua +++ b/deepcopy.lua @@ -4,6 +4,10 @@ if not love11_4 then table_new=function() return {} end end +if not debug.upvalueid or not debug.upvaluejoin then + print("debug.upvalueid or debug.upvaluejoin are not available; expect breakage with rewinding") +end + local handle_funcs = { ["nil"] = function(orig) return orig end, number = function(orig) return orig end, @@ -42,14 +46,15 @@ local handle_funcs = { end debug.setupvalue(ret,i,deepcopy(val,seen,upvalues)) - local uid = debug.upvalueid(orig, i) - if upvalues[uid] then - local other_func, other_i = unpack(upvalues[uid]) - debug.upvaluejoin(ret, i , other_func, other_i) - else - upvalues[uid] = {ret, i} + if debug.upvalueid and debug.upvaluejoin then + local uid = debug.upvalueid(orig, i) + if upvalues[uid] then + local other_func, other_i = unpack(upvalues[uid]) + debug.upvaluejoin(ret, i , other_func, other_i) + else + upvalues[uid] = {ret, i} + end end - end return ret end, diff --git a/default.keys.conf b/default.keys.conf new file mode 100644 index 0000000..0e266d0 --- /dev/null +++ b/default.keys.conf @@ -0,0 +1,114 @@ +# +# keybindings config file +# delete this file to regenerate defaults +# +# helper keys +alt: ralt | lalt +ctrl: rctrl | lctrl +shift: rshift | lshift +gui: rgui | lgui +p8ctrl: ctrl | gui +# +# generic +# +fullscreen: alt + return +full_quit: ctrl + q +full_reload: ctrl + r +copy: p8ctrl + c +cut: p8ctrl + x +paste: p8ctrl + v +# +# pico-8 keys +# +k_left: left | kp4 +k_right: right | kp6 +k_up: up | kp8 +k_down: down | kp5 +k_jump: z | c | n | kp- | kp1 | insert +k_dash: x | v | m | 8 | kp2 | delete +k_pause: return | escape +k_seven: 7 +# held +hold_left: shift + k_left +hold_right: shift + k_right +hold_up: shift + k_up +hold_down: shift + k_down +hold_jump: shift + k_jump +hold_dash: shift + k_dash +hold_pause: shift + k_pause +hold_seven: shift + k_seven +# toggle all (visual mode) +all_left: alt + k_left +all_right: alt + k_right +all_up: alt + k_up +all_down: alt + k_down +all_jump: alt + k_jump +all_dash: alt + k_dash +all_pause: alt + k_pause +all_seven: alt + k_seven +# +# pico-8 tas +# +prev_frame: k +next_frame: l +full_rewind: d +playback: p +reset_tas: shift + r +save_tas: m +open_tas: shift + w +insert_blank: insert +duplicate: p8ctrl + insert +delete: delete +visual: shift + l +undo: p8ctrl + z +redo: shift + undo +screenshot: f1 | f6 +gif_rec_start: f3 | f8 | p8ctrl+8 +gif_rec_stop: f4 | f9 | p8ctrl+9 +# +# celeste tas +# +prev_level: s +next_level: f +rewind: shift + d +level_gif: shift + g +clean_save: u +full_playback: shift + n +inc_djump: shift + = +dec_djump: - +reset_djump: = +print_pos: y +# jank offset editing mode +jank_offset: a +inc_jank: up +dec_jank: down +quit_jank: jank_offset +# rng seeding mode +rng_seeding: b +dec_rng: down +inc_rng: up +prev_object: left +next_object: right +quit_rng: rng_seeding +# +# visual mode +# +go_to_start: home +go_to_end: end +exit_visual: escape +# +# console +# +console: p8ctrl + t +del_backward: backspace +prev_char: left +next_char: right +prev_word: alt + left +next_word: alt + right +cmd_go_to_end: ctrl + right +cmd_go_to_start: ctrl + left +next_command: down +prev_command: up +clear_line: ctrl + c +complete_command: tab +send_line: return diff --git a/excludelist.txt b/excludelist.txt deleted file mode 100644 index 93e1152..0000000 --- a/excludelist.txt +++ /dev/null @@ -1,17 +0,0 @@ -*.bak -*.bat -*.git* -*.love -*.p8 -*.sh -.editorconfig -.luacheckrc -.vscode/* -README.md -TODO.md -build/* -cart/* -demos/* -excludelist.txt -makefile -spec/* diff --git a/gif.lua b/gif.lua index 75aa69c..a3b097b 100644 --- a/gif.lua +++ b/gif.lua @@ -1,6 +1,8 @@ -- GIF encoder specialized for PICO-8 -- by gamax92. +local bit=require("numberlua").bit + local palmap={} for i=0, 15 do diff --git a/keybindings.lua b/keybindings.lua new file mode 160000 index 0000000..fad6777 --- /dev/null +++ b/keybindings.lua @@ -0,0 +1 @@ +Subproject commit fad6777e42d5511aa79abc4dfcf771b5ec3ab10a diff --git a/lib/QueueableSource.lua b/lib/QueueableSource.lua deleted file mode 100644 index a3b67bb..0000000 --- a/lib/QueueableSource.lua +++ /dev/null @@ -1,228 +0,0 @@ ---[[ - love-microphone - QueueableSource.lua - - Provides a QueueableSource object, pseudo-inheriting from Source. - See http://love2d.org/wiki/Source for better documentation on - most methods except queue and new. -]] - -local ffi = require("ffi") -local al = require("openal") - -local QueueableSource = {} -local typecheck = { - Object = true, - QueueableSource = true -} - ---[[ - alFormat getALFormat(SoundData data) - data: The SoundData to query. - - Returns the correct alFormat enum value for the given SoundData. -]] -local function getALFormat(data) - local stereo = data:getChannelCount() == 2 - local deep = data:getBitDepth() == 16 - - if (stereo) then - if (deep) then - return al.AL_FORMAT_STEREO16 - else - return al.AL_FORMAT_STEREO8 - end - end - - if (deep) then - return al.AL_FORMAT_MONO16 - else - return al.AL_FORMAT_MONO8 - end -end - ---[[ - QueueableSource QueueableSource:new(uint bufferCount=16) - bufferCount: The number of buffers to use to hold queued sounds. - - Creates a new QueueableSource object. -]] -function QueueableSource:new(bufferCount) - if (bufferCount) then - if (type(bufferCount) ~= "number" or bufferCount % 1 ~= 0 or bufferCount < 0) then - return nil, "Invalid argument #1: bufferCount must be a positive integer if given." - end - else - bufferCount = 16 - end - - local new = {} - - for key, value in pairs(self) do - if (key ~= "new") then - new[key] = value - end - end - - local pBuffers = ffi.new("ALuint[?]", bufferCount) - al.alGenBuffers(bufferCount, pBuffers) - - local freeBuffers = {} - for i = 0, bufferCount - 1 do - table.insert(freeBuffers, pBuffers[i]) - end - - local pSource = ffi.new("ALuint[1]") - al.alGenSources(1, pSource) - al.alSourcei(pSource[0], al.AL_LOOPING, 0) - - new._bufferCount = bufferCount - new._pBuffers = pBuffers - new._freeBuffers = freeBuffers - new._source = pSource[0] - new._pAvailable = ffi.new("ALint[1]") - new._pBufferHolder = ffi.new("ALuint[16]") - - local wrapper = newproxy(true) - getmetatable(wrapper).__index = new - getmetatable(wrapper).__gc = function(self) - al.alSourceStop(new._source) - al.alSourcei(new._source, al.AL_BUFFER, 0) - al.alDeleteSources(1, pSource) - al.alDeleteBuffers(bufferCount, pBuffers) - end - - return wrapper -end - ---[[ - string QueueableSource:type() - - Returns the string name of the class, "QueueableSource". -]] -function QueueableSource:type() - return "QueueableSource" -end - ---[[ - bool QueueableSource:typeOf(string type) - type: The type to check against. - - Returns whether the object matches the given type. -]] -function QueueableSource:typeOf(type) - return typecheck[type] -end - ---[[ - void QueueableSource:queue(SoundData data) (Success) - (void, string) QueueableSource:queue(SoundData data) (Failure) - data: The SoundData to queue for playback. - - Queues a new SoundData to play. - - Will fail and return nil and an error message if no buffers were available. -]] -function QueueableSource:queue(data) - self:step() - - if (#self._freeBuffers == 0) then - return nil, "No free buffers were available to playback the given audio." - end - - local top = table.remove(self._freeBuffers, 1) - - al.alBufferData(top, getALFormat(data), data:getPointer(), data:getSize(), data:getSampleRate()) - al.alSourceQueueBuffers(self._source, 1, ffi.new("ALuint[1]", top)) -end - ---[[ - void QueueableSource:step() - - Opens up queues that have been used. - Called automatically by queue. -]] -function QueueableSource:step() - al.alGetSourcei(self._source, al.AL_BUFFERS_PROCESSED, self._pAvailable) - - if (self._pAvailable[0] > 0) then - al.alSourceUnqueueBuffers(self._source, self._pAvailable[0], self._pBufferHolder) - - for i = 0, self._pAvailable[0] - 1 do - table.insert(self._freeBuffers, self._pBufferHolder[i]) - end - end -end - ---[[ - void QueueableSource:clear() - - Stops playback and clears all queued data. -]] -function QueueableSource:clear() - self:pause() - - for i = 0, self._bufferCount - 1 do - al.alSourceUnqueueBuffers(self._source, self._bufferCount, self._pBuffers) - table.insert(self._freeBuffers, self._pBuffers[i]) - end -end - ---[[ - uint QueueableSource:getFreeBufferCount() - - Returns the number of free buffers for queueing sounds with this QueueableSource. -]] -function QueueableSource:getFreeBufferCount() - return #self._freeBuffers -end - ---[[ - void QueueableSource:play() - - Begins playing audio. -]] -function QueueableSource:play() - if (not self:isPlaying()) then - al.alSourcePlay(self._source) - end -end - ---[[ - bool QueueableSource:isPlaying() - - Returns whether the source is playing audio. -]] -function QueueableSource:isPlaying() - local state = ffi.new("ALint[1]") - - al.alGetSourcei(self._source, al.AL_SOURCE_STATE, state) - - return (state[0] == al.AL_PLAYING) -end - ---[[ - void QueueableSource:pause() - - Stops playing audio. -]] -function QueueableSource:pause() - if (not self:isPaused()) then - al.alSourcePause(self._source) - end -end - ---[[ - void QueueableSource:isPaused() - - Returns whether the source is paused. -]] -function QueueableSource:isPaused() - local state = ffi.new("ALint[1]") - - al.alGetSourcei(self._source, al.AL_SOURCE_STATE, state) - - return (state[0] == al.AL_PAUSED) -end - -return QueueableSource diff --git a/lib/console.lua b/lib/console.lua index e7564fa..23e8ef4 100644 --- a/lib/console.lua +++ b/lib/console.lua @@ -385,9 +385,9 @@ function console.execute(command) -- Reprint the command + the prompt string. print(console.PROMPT .. command) - local chunk, error = load("return " .. command) + local chunk, error = loadstring("return " .. command) if not chunk then - chunk, error = load(command) + chunk, error = loadstring(command) end if chunk then @@ -410,39 +410,36 @@ function console.execute(command) end function console.keypressed(key, scancode, isrepeat) - local ctrl = love.keyboard.isDown("lctrl", "lgui") - local shift = love.keyboard.isDown("lshift") - local alt = love.keyboard.isDown("lalt") - if ctrl and key == "t" then + if ke.console then enabled = not enabled end -- Ignore if the console isn't enabled. if not enabled then return end - if key == 'backspace' then command:delete_backward() + if ke.del_backward then command:delete_backward() - elseif key == "up" then command:previous() - elseif key == "down" then command:next() + elseif ke.prev_command then command:previous() + elseif ke.next_command then command:next() - elseif alt and key == "left" then command:backward_word() - elseif alt and key == "right" then command:forward_word() + elseif ke.prev_word then command:backward_word() + elseif ke.next_word then command:forward_word() - elseif ctrl and key == "left" then command:beginning_of_line() - elseif ctrl and key == "right" then command:end_of_line() + elseif ke.cmd_go_to_start then command:beginning_of_line() + elseif ke.cmd_go_to_end then command:end_of_line() - elseif key == "left" then command:backward_character() - elseif key == "right" then command:forward_character() + elseif ke.prev_char then command:backward_character() + elseif ke.next_char then command:forward_character() - elseif key == "c" and ctrl then command:clear() + elseif ke.clear_line then command:clear() - elseif key == "return" then + elseif ke.send_line then console.addHistory(command.text) console.execute(command.text) command:clear() - elseif key == "tab" then + elseif ke.complete_command then command:complete() end end diff --git a/lib/js.lua b/lib/js.lua new file mode 120000 index 0000000..4c936bc --- /dev/null +++ b/lib/js.lua @@ -0,0 +1 @@ +../Love.js-Api-Player/js.lua \ No newline at end of file diff --git a/lib/keybindings.lua b/lib/keybindings.lua new file mode 120000 index 0000000..d8f0720 --- /dev/null +++ b/lib/keybindings.lua @@ -0,0 +1 @@ +../keybindings.lua/keybindings.lua \ No newline at end of file diff --git a/lib/numberlua.lua b/lib/numberlua.lua new file mode 100644 index 0000000..467bc60 --- /dev/null +++ b/lib/numberlua.lua @@ -0,0 +1,553 @@ +if jit then + return {bit=require("bit")} +end +--[[ + +LUA MODULE + + bit.numberlua - Bitwise operations implemented in pure Lua as numbers, + with Lua 5.2 'bit32' and (LuaJIT) LuaBitOp 'bit' compatibility interfaces. + +SYNOPSIS + + local bit = require 'bit.numberlua' + print(bit.band(0xff00ff00, 0x00ff00ff)) --> 0xffffffff + + -- Interface providing strong Lua 5.2 'bit32' compatibility + local bit32 = require 'bit.numberlua'.bit32 + assert(bit32.band(-1) == 0xffffffff) + + -- Interface providing strong (LuaJIT) LuaBitOp 'bit' compatibility + local bit = require 'bit.numberlua'.bit + assert(bit.tobit(0xffffffff) == -1) + +DESCRIPTION + + This library implements bitwise operations entirely in Lua. + This module is typically intended if for some reasons you don't want + to or cannot install a popular C based bit library like BitOp 'bit' [1] + (which comes pre-installed with LuaJIT) or 'bit32' (which comes + pre-installed with Lua 5.2) but want a similar interface. + + This modules represents bit arrays as non-negative Lua numbers. [1] + It can represent 32-bit bit arrays when Lua is compiled + with lua_Number as double-precision IEEE 754 floating point. + + The module is nearly the most efficient it can be but may be a few times + slower than the C based bit libraries and is orders or magnitude + slower than LuaJIT bit operations, which compile to native code. Therefore, + this library is inferior in performane to the other modules. + + The `xor` function in this module is based partly on Roberto Ierusalimschy's + post in http://lua-users.org/lists/lua-l/2002-09/msg00134.html . + + The included BIT.bit32 and BIT.bit sublibraries aims to provide 100% + compatibility with the Lua 5.2 "bit32" and (LuaJIT) LuaBitOp "bit" library. + This compatbility is at the cost of some efficiency since inputted + numbers are normalized and more general forms (e.g. multi-argument + bitwise operators) are supported. + +STATUS + + WARNING: Not all corner cases have been tested and documented. + Some attempt was made to make these similar to the Lua 5.2 [2] + and LuaJit BitOp [3] libraries, but this is not fully tested and there + are currently some differences. Addressing these differences may + be improved in the future but it is not yet fully determined how to + resolve these differences. + + The BIT.bit32 library passes the Lua 5.2 test suite (bitwise.lua) + http://www.lua.org/tests/5.2/ . The BIT.bit library passes the LuaBitOp + test suite (bittest.lua). However, these have not been tested on + platforms with Lua compiled with 32-bit integer numbers. + +API + + BIT.tobit(x) --> z + + Similar to function in BitOp. + + BIT.tohex(x, n) + + Similar to function in BitOp. + + BIT.band(x, y) --> z + + Similar to function in Lua 5.2 and BitOp but requires two arguments. + + BIT.bor(x, y) --> z + + Similar to function in Lua 5.2 and BitOp but requires two arguments. + + BIT.bxor(x, y) --> z + + Similar to function in Lua 5.2 and BitOp but requires two arguments. + + BIT.bnot(x) --> z + + Similar to function in Lua 5.2 and BitOp. + + BIT.lshift(x, disp) --> z + + Similar to function in Lua 5.2 (warning: BitOp uses unsigned lower 5 bits of shift), + + BIT.rshift(x, disp) --> z + + Similar to function in Lua 5.2 (warning: BitOp uses unsigned lower 5 bits of shift), + + BIT.extract(x, field [, width]) --> z + + Similar to function in Lua 5.2. + + BIT.replace(x, v, field, width) --> z + + Similar to function in Lua 5.2. + + BIT.bswap(x) --> z + + Similar to function in Lua 5.2. + + BIT.rrotate(x, disp) --> z + BIT.ror(x, disp) --> z + + Similar to function in Lua 5.2 and BitOp. + + BIT.lrotate(x, disp) --> z + BIT.rol(x, disp) --> z + + Similar to function in Lua 5.2 and BitOp. + + BIT.arshift + + Similar to function in Lua 5.2 and BitOp. + + BIT.btest + + Similar to function in Lua 5.2 with requires two arguments. + + BIT.bit32 + + This table contains functions that aim to provide 100% compatibility + with the Lua 5.2 "bit32" library. + + bit32.arshift (x, disp) --> z + bit32.band (...) --> z + bit32.bnot (x) --> z + bit32.bor (...) --> z + bit32.btest (...) --> true | false + bit32.bxor (...) --> z + bit32.extract (x, field [, width]) --> z + bit32.replace (x, v, field [, width]) --> z + bit32.lrotate (x, disp) --> z + bit32.lshift (x, disp) --> z + bit32.rrotate (x, disp) --> z + bit32.rshift (x, disp) --> z + + BIT.bit + + This table contains functions that aim to provide 100% compatibility + with the LuaBitOp "bit" library (from LuaJIT). + + bit.tobit(x) --> y + bit.tohex(x [,n]) --> y + bit.bnot(x) --> y + bit.bor(x1 [,x2...]) --> y + bit.band(x1 [,x2...]) --> y + bit.bxor(x1 [,x2...]) --> y + bit.lshift(x, n) --> y + bit.rshift(x, n) --> y + bit.arshift(x, n) --> y + bit.rol(x, n) --> y + bit.ror(x, n) --> y + bit.bswap(x) --> y + +DEPENDENCIES + + None (other than Lua 5.1 or 5.2). + +DOWNLOAD/INSTALLATION + + If using LuaRocks: + luarocks install lua-bit-numberlua + + Otherwise, download . + Alternately, if using git: + git clone git://github.com/davidm/lua-bit-numberlua.git + cd lua-bit-numberlua + Optionally unpack: + ./util.mk + or unpack and install in LuaRocks: + ./util.mk install + +REFERENCES + + [1] http://lua-users.org/wiki/FloatingPoint + [2] http://www.lua.org/manual/5.2/ + [3] http://bitop.luajit.org/ + +LICENSE + + (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + (end license) + +--]] + +local M = {_TYPE='module', _NAME='bit.numberlua', _VERSION='0.3.1.20120131'} + +local floor = math.floor + +local MOD = 2^32 +local MODM = MOD-1 +local function round(x) + return floor(x+0.0000005) -- Epsilon +end + +local function memoize(f) + local mt = {} + local t = setmetatable({}, mt) + function mt:__index(k) + local v = f(k); t[k] = v + return v + end + return t +end + +local function make_bitop_uncached(t, m) + local function bitop(a, b) + local res,p = 0,1 + while a ~= 0 and b ~= 0 do + local am, bm = a%m, b%m + am, bm = floor(am+0.5), floor(bm+0.5) -- Web index error fix + res = res + t[am][bm]*p + a = (a - am) / m + b = (b - bm) / m + a, b = floor(a+0.5), floor(b+0.5) -- Web inf-loop fix + p = p*m + end + res = res + (a+b)*p + return res + end + return bitop +end + +local function make_bitop(t) + local op1 = make_bitop_uncached(t,2^1) + local op2 = memoize(function(a) + return memoize(function(b) + return op1(a, b) + end) + end) + return make_bitop_uncached(op2, 2^(t.n or 1)) +end + +-- ok? probably not if running on a 32-bit int Lua number type platform +function M.tobit(x) + return x % 2^32 +end + +M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4} +local bxor = M.bxor + +function M.bnot(a) return MODM - a end +local bnot = M.bnot + +function M.band(a,b) return round( ((a+b) - bxor(a,b))/2 ) end +local band = M.band + +function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end +local bor = M.bor + +local lshift, rshift -- forward declare + +function M.rshift(a,disp) -- Lua5.2 insipred + if disp < 0 then return lshift(a,-disp) end + return floor(a % 2^32 / 2^disp) +end +rshift = M.rshift + +function M.lshift(a,disp) -- Lua5.2 inspired + if disp < 0 then return rshift(a,-disp) end + return (a * 2^disp) % 2^32 +end +lshift = M.lshift + +function M.tohex(x, n) -- BitOp style + n = n or 8 + local up + if n <= 0 then + if n == 0 then return '' end + up = true + n = - n + end + x = band(x, 16^n-1) + return ('%0'..n..(up and 'X' or 'x')):format(x) +end +local tohex = M.tohex + +function M.extract(n, field, width) -- Lua5.2 inspired + width = width or 1 + return band(rshift(n, field), 2^width-1) +end +local extract = M.extract + +function M.replace(n, v, field, width) -- Lua5.2 inspired + width = width or 1 + local mask1 = 2^width-1 + v = band(v, mask1) -- required by spec? + local mask = bnot(lshift(mask1, field)) + return band(n, mask) + lshift(v, field) +end +local replace = M.replace + +function M.bswap(x) -- BitOp style + local a = band(x, 0xff); x = rshift(x, 8) + local b = band(x, 0xff); x = rshift(x, 8) + local c = band(x, 0xff); x = rshift(x, 8) + local d = band(x, 0xff) + return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d +end +local bswap = M.bswap + +function M.rrotate(x, disp) -- Lua5.2 inspired + disp = disp % 32 + local low = band(x, 2^disp-1) + return rshift(x, disp) + lshift(low, 32-disp) +end +local rrotate = M.rrotate + +function M.lrotate(x, disp) -- Lua5.2 inspired + return rrotate(x, -disp) +end +local lrotate = M.lrotate + +M.rol = M.lrotate -- LuaOp inspired +M.ror = M.rrotate -- LuaOp insipred + + +function M.arshift(x, disp) -- Lua5.2 inspired + local z = rshift(x, disp) + if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end + return z +end +local arshift = M.arshift + +function M.btest(x, y) -- Lua5.2 inspired + return band(x, y) ~= 0 +end + +-- +-- Start Lua 5.2 "bit32" compat section. +-- + +M.bit32 = {} -- Lua 5.2 'bit32' compatibility + + +local function bit32_bnot(x) + return (-1 - x) % MOD +end +M.bit32.bnot = bit32_bnot + +local function bit32_bxor(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = bxor(a, b) + if c then + z = bit32_bxor(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return 0 + end +end +M.bit32.bxor = bit32_bxor + +local function bit32_band(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = ((a+b) - bxor(a,b)) / 2 + if c then + z = bit32_band(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return MODM + end +end +M.bit32.band = bit32_band + +local function bit32_bor(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = MODM - band(MODM - a, MODM - b) + if c then + z = bit32_bor(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return 0 + end +end +M.bit32.bor = bit32_bor + +function M.bit32.btest(...) + return bit32_band(...) ~= 0 +end + +function M.bit32.lrotate(x, disp) + return lrotate(x % MOD, disp) +end + +function M.bit32.rrotate(x, disp) + return rrotate(x % MOD, disp) +end + +function M.bit32.lshift(x,disp) + if disp > 31 or disp < -31 then return 0 end + return lshift(x % MOD, disp) +end + +function M.bit32.rshift(x,disp) + if disp > 31 or disp < -31 then return 0 end + return rshift(x % MOD, disp) +end + +function M.bit32.arshift(x,disp) + x = x % MOD + if disp >= 0 then + if disp > 31 then + return (x >= 0x80000000) and MODM or 0 + else + local z = rshift(x, disp) + if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end + return z + end + else + return lshift(x, -disp) + end +end + +function M.bit32.extract(x, field, ...) + local width = ... or 1 + if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end + x = x % MOD + return extract(x, field, ...) +end + +function M.bit32.replace(x, v, field, ...) + local width = ... or 1 + if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end + x = x % MOD + v = v % MOD + return replace(x, v, field, ...) +end + + +-- +-- Start LuaBitOp "bit" compat section. +-- + +M.bit = {} -- LuaBitOp "bit" compatibility + +function M.bit.tobit(x) + x = x % MOD + if x >= 0x80000000 then x = x - MOD end + return round(x) +end +local bit_tobit = M.bit.tobit + +function M.bit.tohex(x, ...) + return tohex(x % MOD, ...) +end + +function M.bit.bnot(x) + return bit_tobit(bnot(x % MOD)) +end + +local function bit_bor(a, b, c, ...) + if c then + return bit_bor(bit_bor(a, b), c, ...) + elseif b then + return bit_tobit(bor(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.bor = bit_bor + +local function bit_band(a, b, c, ...) + if c then + return bit_band(bit_band(a, b), c, ...) + elseif b then + return bit_tobit(band(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.band = bit_band + +local function bit_bxor(a, b, c, ...) + if c then + return bit_bxor(bit_bxor(a, b), c, ...) + elseif b then + return bit_tobit(bxor(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.bxor = bit_bxor + +function M.bit.lshift(x, n) + return bit_tobit(lshift(x % MOD, n % 32)) +end + +function M.bit.rshift(x, n) + return bit_tobit(rshift(x % MOD, n % 32)) +end + +function M.bit.arshift(x, n) + return bit_tobit(arshift(x % MOD, n % 32)) +end + +function M.bit.rol(x, n) + return bit_tobit(lrotate(x % MOD, n % 32)) +end + +function M.bit.ror(x, n) + return bit_tobit(rrotate(x % MOD, n % 32)) +end + +function M.bit.bswap(x) + return bit_tobit(bswap(x % MOD)) +end + +return M diff --git a/lib/openal.lua b/lib/openal.lua deleted file mode 100644 index c72a9c3..0000000 --- a/lib/openal.lua +++ /dev/null @@ -1,381 +0,0 @@ ---[[ - love-micrphone - openal.lua - - LuaJIT FFI binding for OpenAL-soft -]] - -local ffi = require("ffi") --- Load OpenAL32.dll on Windows (from LOVE) or use ffi.C -local openal = (ffi.os == "Windows") and ffi.load("openal32") or ffi.C - ---alc.h -ffi.cdef([[ -enum { - ALC_INVALID = 0, //Deprecated - - ALC_VERSION_0_1 = 1, - - ALC_FALSE = 0, - ALC_TRUE = 1, - ALC_FREQUENCY = 0x1007, - ALC_REFRESH = 0x1008, - ALC_SYNC = 0x1009, - - ALC_MONO_SOURCES = 0x1010, - ALC_STEREO_SOURCES = 0x1011, - - ALC_NO_ERROR = 0, - ALC_INVALID_DEVICE = 0xA001, - ALC_INVALID_CONTEXT = 0xA002, - ALC_INVALID_ENUM = 0xA003, - ALC_INVALID_VALUE = 0xA004, - ALC_OUT_OF_MEMORY = 0xA005, - - ALC_MAJOR_VERSION = 0x1000, - ALC_MINOR_VERSION = 0x1001, - - ALC_ATTRIBUTES_SIZE = 0x1002, - ALC_ALL_ATTRIBUTES = 0x1003, - - ALC_DEFAULT_DEVICE_SPECIFIER = 0x1004, - ALC_DEVICE_SPECIFIER = 0x1005, - ALC_EXTENSIONS = 0x1006, - - ALC_EXT_CAPTURE = 1, - ALC_CAPTURE_DEVICE_SPECIFIER = 0x310, - ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311, - ALC_CAPTURE_SAMPLES = 0x312, - - ALC_DEFAULT_ALL_DEVICES_SPECIFIER = 0x1012, - ALC_ALL_DEVICES_SPECIFIER = 0x1013 -}; - -typedef struct ALCdevice_struct ALCdevice; -typedef struct ALCcontext_struct ALCcontext; - -typedef char ALCboolean; -typedef char ALCchar; -typedef signed char ALCbyte; -typedef unsigned char ALCubyte; -typedef short ALCshort; -typedef unsigned short ALCushort; -typedef int ALCint; -typedef unsigned int ALCuint; -typedef int ALCsizei; -typedef int ALCenum; -typedef float ALCfloat; -typedef double ALCdouble; -typedef void ALCvoid; - -ALCcontext* alcCreateContext(ALCdevice *device, const ALCint* attrlist); -ALCboolean alcMakeContextCurrent(ALCcontext *context); -void alcProcessContext(ALCcontext *context); -void alcSuspendContext(ALCcontext *context); -void alcDestroyContext(ALCcontext *context); -ALCcontext* alcGetCurrentContext(void); -ALCdevice* alcGetContextsDevice(ALCcontext *context); - -ALCdevice* alcOpenDevice(const ALCchar *devicename); -ALCboolean alcCloseDevice(ALCdevice *device); - -ALCenum alcGetError(ALCdevice *device); -ALCboolean alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname); -void* alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); -ALCenum alcGetEnumValue(ALCdevice *device, const ALCchar *enumname); - -const ALCchar* alcGetString(ALCdevice *device, ALCenum param); -void alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); - -ALCdevice* alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); -ALCboolean alcCaptureCloseDevice(ALCdevice *device); -void alcCaptureStart(ALCdevice *device); -void alcCaptureStop(ALCdevice *device); -void alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); - -typedef ALCcontext* (*LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist); -typedef ALCboolean (*LPALCMAKECONTEXTCURRENT)(ALCcontext *context); -typedef void (*LPALCPROCESSCONTEXT)(ALCcontext *context); -typedef void (*LPALCSUSPENDCONTEXT)(ALCcontext *context); -typedef void (*LPALCDESTROYCONTEXT)(ALCcontext *context); -typedef ALCcontext* (*LPALCGETCURRENTCONTEXT)(void); -typedef ALCdevice* (*LPALCGETCONTEXTSDEVICE)(ALCcontext *context); -typedef ALCdevice* (*LPALCOPENDEVICE)(const ALCchar *devicename); -typedef ALCboolean (*LPALCCLOSEDEVICE)(ALCdevice *device); -typedef ALCenum (*LPALCGETERROR)(ALCdevice *device); -typedef ALCboolean (*LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname); -typedef void* (*LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); -typedef ALCenum (*LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname); -typedef const ALCchar* (*LPALCGETSTRING)(ALCdevice *device, ALCenum param); -typedef void (*LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); -typedef ALCdevice* (*LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); -typedef ALCboolean (*LPALCCAPTURECLOSEDEVICE)(ALCdevice *device); -typedef void (*LPALCCAPTURESTART)(ALCdevice *device); -typedef void (*LPALCCAPTURESTOP)(ALCdevice *device); -typedef void (*LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); -]]) - ---al.h -ffi.cdef([[ -enum { - AL_NONE = 0, - AL_FALSE = 0, - AL_TRUE = 1, - - AL_SOURCE_RELATIVE = 0x202, - AL_CONE_INNER_ANGLE = 0x1001, - AL_CONE_OUTER_ANGLE = 0x1002, - AL_PITCH = 0x1003, - AL_POSITION = 0x1004, - AL_DIRECTION = 0x1005, - AL_VELOCITY = 0x1006, - AL_LOOPING = 0x1007, - AL_BUFFER = 0x1009, - AL_GAIN = 0x100A, - AL_MIN_GAIN = 0x100D, - AL_MAX_GAIN = 0x100E, - AL_ORIENTATION = 0x100F, - AL_SOURCE_STATE = 0x1010, - - AL_INITIAL = 0x1011, - AL_PLAYING = 0x1012, - AL_PAUSED = 0x1013, - AL_STOPPED = 0x1014, - - AL_BUFFERS_QUEUED = 0x1015, - AL_BUFFERS_PROCESSED = 0x1016, - - AL_REFERENCE_DISTANCE = 0x1020, - AL_ROLLOFF_FACTOR = 0x1021, - AL_CONE_OUTER_GAIN = 0x1022, - AL_MAX_DISTANCE = 0x1023, - - AL_SEC_OFFSET = 0x1024, - AL_SAMPLE_OFFSET = 0x1025, - AL_BYTE_OFFSET = 0x1026, - - AL_SOURCE_TYPE = 0x1027, - - AL_STATIC = 0x1028, - AL_STREAMING = 0x1029, - AL_UNDETERMINED = 0x1030, - - AL_FORMAT_MONO8 = 0x1100, - AL_FORMAT_MONO16 = 0x1101, - AL_FORMAT_STEREO8 = 0x1102, - AL_FORMAT_STEREO16 = 0x1103, - - AL_FREQUENCY = 0x2001, - AL_BITS = 0x2002, - AL_CHANNELS = 0x2003, - AL_SIZE = 0x2004, - - AL_UNUSED = 0x2010, - AL_PENDING = 0x2011, - AL_PROCESSED = 0x2012, - - AL_NO_ERROR = 0, - AL_INVALID_NAME = 0xA001, - AL_INVALID_ENUM = 0xA002, - AL_INVALID_VALUE = 0xA003, - AL_INVALID_OPERATION = 0xA004, - AL_OUT_OF_MEMORY = 0xA005, - - AL_VENDOR = 0xB001, - AL_VERSION = 0xB002, - AL_RENDERER = 0xB003, - AL_EXTENSIONS = 0xB004, - - AL_DOPPLER_FACTOR = 0xC000, - AL_DOPPLER_VELOCITY = 0xC001, - AL_SPEED_OF_SOUND = 0xC003, - AL_DISTANCE_MODEL = 0xD000, - - AL_INVERSE_DISTANCE = 0xD001, - AL_INVERSE_DISTANCE_CLAMPED = 0xD002, - AL_LINEAR_DISTANCE = 0xD003, - AL_LINEAR_DISTANCE_CLAMPED = 0xD004, - AL_EXPONENT_DISTANCE = 0xD005, - AL_EXPONENT_DISTANCE_CLAMPED = 0xD006 -}; - -typedef char ALboolean; -typedef char ALchar; -typedef signed char ALbyte; -typedef unsigned char ALubyte; -typedef short ALshort; -typedef unsigned short ALushort; -typedef int ALint; -typedef unsigned int ALuint; -typedef int ALsizei; -typedef int ALenum; -typedef float ALfloat; -typedef double ALdouble; -typedef void ALvoid; - -void alDopplerFactor(ALfloat value); -void alDopplerVelocity(ALfloat value); -void alSpeedOfSound(ALfloat value); -void alDistanceModel(ALenum distanceModel); - -void alEnable(ALenum capability); -void alDisable(ALenum capability); -ALboolean alIsEnabled(ALenum capability); - -const ALchar* alGetString(ALenum param); -void alGetBooleanv(ALenum param, ALboolean *values); -void alGetIntegerv(ALenum param, ALint *values); -void alGetFloatv(ALenum param, ALfloat *values); -void alGetDoublev(ALenum param, ALdouble *values); -ALboolean alGetBoolean(ALenum param); -ALint alGetInteger(ALenum param); -ALfloat alGetFloat(ALenum param); -ALdouble alGetDouble(ALenum param); - -ALenum alGetError(void); - -ALboolean alIsExtensionPresent(const ALchar *extname); -void* alGetProcAddress(const ALchar *fname); -ALenum alGetEnumValue(const ALchar *ename); - -void alListenerf(ALenum param, ALfloat value); -void alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -void alListenerfv(ALenum param, const ALfloat *values); -void alListeneri(ALenum param, ALint value); -void alListener3i(ALenum param, ALint value1, ALint value2, ALint value3); -void alListeneriv(ALenum param, const ALint *values); - -void alGetListenerf(ALenum param, ALfloat *value); -void alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -void alGetListenerfv(ALenum param, ALfloat *values); -void alGetListeneri(ALenum param, ALint *value); -void alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3); -void alGetListeneriv(ALenum param, ALint *values); - -void alGenSources(ALsizei n, ALuint *sources); -void alDeleteSources(ALsizei n, const ALuint *sources); -ALboolean alIsSource(ALuint source); - -void alSourcef(ALuint source, ALenum param, ALfloat value); -void alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -void alSourcefv(ALuint source, ALenum param, const ALfloat *values); -void alSourcei(ALuint source, ALenum param, ALint value); -void alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -void alSourceiv(ALuint source, ALenum param, const ALint *values); - -void alGetSourcef(ALuint source, ALenum param, ALfloat *value); -void alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -void alGetSourcefv(ALuint source, ALenum param, ALfloat *values); -void alGetSourcei(ALuint source, ALenum param, ALint *value); -void alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -void alGetSourceiv(ALuint source, ALenum param, ALint *values); - -void alSourcePlayv(ALsizei n, const ALuint *sources); -void alSourceStopv(ALsizei n, const ALuint *sources); -void alSourceRewindv(ALsizei n, const ALuint *sources); -void alSourcePausev(ALsizei n, const ALuint *sources); - -void alSourcePlay(ALuint source); -void alSourceStop(ALuint source); -void alSourceRewind(ALuint source); -void alSourcePause(ALuint source); - -void alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers); -void alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers); - -void alGenBuffers(ALsizei n, ALuint *buffers); -void alDeleteBuffers(ALsizei n, const ALuint *buffers); -ALboolean alIsBuffer(ALuint buffer); - -void alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq); - -void alBufferf(ALuint buffer, ALenum param, ALfloat value); -void alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -void alBufferfv(ALuint buffer, ALenum param, const ALfloat *values); -void alBufferi(ALuint buffer, ALenum param, ALint value); -void alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -void alBufferiv(ALuint buffer, ALenum param, const ALint *values); - -void alGetBufferf(ALuint buffer, ALenum param, ALfloat *value); -void alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -void alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values); -void alGetBufferi(ALuint buffer, ALenum param, ALint *value); -void alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -void alGetBufferiv(ALuint buffer, ALenum param, ALint *values); - -typedef void (*LPALENABLE)(ALenum capability); -typedef void (*LPALDISABLE)(ALenum capability); -typedef ALboolean (*LPALISENABLED)(ALenum capability); -typedef const ALchar* (*LPALGETSTRING)(ALenum param); -typedef void (*LPALGETBOOLEANV)(ALenum param, ALboolean *values); -typedef void (*LPALGETINTEGERV)(ALenum param, ALint *values); -typedef void (*LPALGETFLOATV)(ALenum param, ALfloat *values); -typedef void (*LPALGETDOUBLEV)(ALenum param, ALdouble *values); -typedef ALboolean (*LPALGETBOOLEAN)(ALenum param); -typedef ALint (*LPALGETINTEGER)(ALenum param); -typedef ALfloat (*LPALGETFLOAT)(ALenum param); -typedef ALdouble (*LPALGETDOUBLE)(ALenum param); -typedef ALenum (*LPALGETERROR)(void); -typedef ALboolean (*LPALISEXTENSIONPRESENT)(const ALchar *extname); -typedef void* (*LPALGETPROCADDRESS)(const ALchar *fname); -typedef ALenum (*LPALGETENUMVALUE)(const ALchar *ename); -typedef void (*LPALLISTENERF)(ALenum param, ALfloat value); -typedef void (*LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (*LPALLISTENERFV)(ALenum param, const ALfloat *values); -typedef void (*LPALLISTENERI)(ALenum param, ALint value); -typedef void (*LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (*LPALLISTENERIV)(ALenum param, const ALint *values); -typedef void (*LPALGETLISTENERF)(ALenum param, ALfloat *value); -typedef void (*LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (*LPALGETLISTENERFV)(ALenum param, ALfloat *values); -typedef void (*LPALGETLISTENERI)(ALenum param, ALint *value); -typedef void (*LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (*LPALGETLISTENERIV)(ALenum param, ALint *values); -typedef void (*LPALGENSOURCES)(ALsizei n, ALuint *sources); -typedef void (*LPALDELETESOURCES)(ALsizei n, const ALuint *sources); -typedef ALboolean (*LPALISSOURCE)(ALuint source); -typedef void (*LPALSOURCEF)(ALuint source, ALenum param, ALfloat value); -typedef void (*LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (*LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values); -typedef void (*LPALSOURCEI)(ALuint source, ALenum param, ALint value); -typedef void (*LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (*LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values); -typedef void (*LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value); -typedef void (*LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (*LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values); -typedef void (*LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value); -typedef void (*LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (*LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values); -typedef void (*LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources); -typedef void (*LPALSOURCESTOPV)(ALsizei n, const ALuint *sources); -typedef void (*LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources); -typedef void (*LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources); -typedef void (*LPALSOURCEPLAY)(ALuint source); -typedef void (*LPALSOURCESTOP)(ALuint source); -typedef void (*LPALSOURCEREWIND)(ALuint source); -typedef void (*LPALSOURCEPAUSE)(ALuint source); -typedef void (*LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers); -typedef void (*LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers); -typedef void (*LPALGENBUFFERS)(ALsizei n, ALuint *buffers); -typedef void (*LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers); -typedef ALboolean (*LPALISBUFFER)(ALuint buffer); -typedef void (*LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq); -typedef void (*LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value); -typedef void (*LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (*LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values); -typedef void (*LPALBUFFERI)(ALuint buffer, ALenum param, ALint value); -typedef void (*LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (*LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values); -typedef void (*LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value); -typedef void (*LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (*LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values); -typedef void (*LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value); -typedef void (*LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (*LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values); -typedef void (*LPALDOPPLERFACTOR)(ALfloat value); -typedef void (*LPALDOPPLERVELOCITY)(ALfloat value); -typedef void (*LPALSPEEDOFSOUND)(ALfloat value); -typedef void (*LPALDISTANCEMODEL)(ALenum distanceModel); -]]) - -return openal \ No newline at end of file diff --git a/main.lua b/main.lua index 13018aa..6f74c7c 100644 --- a/main.lua +++ b/main.lua @@ -2,13 +2,19 @@ package.path = package.path .. ";?.lua;lib/?.lua" love.filesystem.setRequirePath(package.path) require("strict") -local QueueableSource = require("QueueableSource") -local bit = require("bit") +is_web = love.system.getOS()=="Web" +-- should be unnecessary, playing it safe anyway for linters and such +jit = pcall(require,"jit") + +local bit = require("numberlua").bit local api = require("api") local cart = require("cart") +local keybinds = require("keybindings") +ke = keybinds.k + local tas = require("tas") local cctas = require("cctas") @@ -62,24 +68,25 @@ pico8 = { kbdbuffer={}, keymap = { [0] = { - [0] = { "left", "kp4" }, - [1] = { "right", "kp6" }, - [2] = { "up", "kp8" }, - [3] = { "down", "kp5" }, - [4] = { "z", "c", "n", "kp-", "kp1", "insert" }, - [5] = { "x", "v", "m", "8", "kp2", "delete" }, - [6] = { "return", "escape" }, - [7] = {}, + [0] = "left", + [1] = "right", + [2] = "up", + [3] = "down", + [4] = "jump", + [5] = "dash", + [6] = "pause", + [7] = "seven", }, [1] = { - [0] = { "s" }, - [1] = { "f" }, - [2] = { "e" }, - [3] = { "d" }, - [4] = { "tab", "lshift", "w" }, - [5] = { "q", "a" }, - [6] = {}, - [7] = {}, + -- subject to renaming + [0] = "p2left", + [1] = "p2right", + [2] = "p2up", + [3] = "p2down", + [4] = "p2jump", + [5] = "p2dash", + [6] = "p2pause", + [7] = "p2seven", }, }, mwheel = 0, @@ -173,6 +180,21 @@ local function _allow_shutdown(value) pico8.can_shutdown = value end +local function guess_tool(e) + if e.begin_game and e._draw and + e.objects and ( + e.load_level or + e.load_room or + e.__tas_level_index + ) and e.player then + print("guessed cctas") + return cctas + else + print("guessed tas") + return tas + end +end + log = print function shdr_unpack(thing) @@ -192,6 +214,7 @@ function setColor(c) end function _load(_cartname) + local _cartname = _cartname or "celeste.p8" if type(_cartname) ~= "string" then return false end @@ -257,6 +280,28 @@ function love.load(argv) love.resize(love.graphics.getDimensions()) end + -- make sure necessary files exist + love.filesystem.createDirectory("carts/") + love.filesystem.createDirectory("tmp/") + love.filesystem.createDirectory("config/") + if not love.filesystem.getInfo("config/keys.conf","file") then + -- copy default config file to the user's configuration + local source_fh, source_e = love.filesystem.newFile((is_web and "web-" or "").."default.keys.conf","r") + local target_fh, target_e = love.filesystem.newFile("config/keys.conf","w") + if source_e or target_e then + if source_e then + log(source_e) + else + log(target_e) + end + else + target_fh:write(source_fh:read()) + source_fh:close() + target_fh:close() + end + end + keybinds.init{ file = "config/keys.conf" } + osc = {} -- tri osc[0] = function(x) @@ -314,10 +359,10 @@ function love.load(argv) end __audio_channels = { - [0] = QueueableSource:new(8), - QueueableSource:new(8), - QueueableSource:new(8), - QueueableSource:new(8), + [0] = love.audio.newQueueableSource(__sample_rate,bits,channels,8), + love.audio.newQueueableSource(__sample_rate,bits,channels,8), + love.audio.newQueueableSource(__sample_rate,bits,channels,8), + love.audio.newQueueableSource(__sample_rate,bits,channels,8), } for i = 0, 3 do @@ -366,7 +411,11 @@ extern float palette[16]; vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { int index = int(color.r*15.0+0.5); - return vec4(palette[index]/15.0, 0.0, 0.0, 1.0); + for (int x = 0; x < 16; x++) { + if (x == index) { + return vec4(palette[x]/15.0, 0.0, 0.0, 1.0); + } + } }]]) pico8.draw_shader:send("palette", shdr_unpack(pico8.draw_palette)) @@ -376,8 +425,12 @@ extern float transparent[16]; vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { int index = int(Texel(texture, texture_coords).r*15.0+0.5); - float alpha = transparent[index]; - return vec4(palette[index]/15.0, 0.0, 0.0 ,alpha); + for (int x = 0; x < 16; x++) { + if (x == index) { + float alpha = transparent[x]; + return vec4(palette[x]/15.0, 0.0, 0.0, alpha); + } + } }]]) pico8.sprite_shader:send("palette", shdr_unpack(pico8.draw_palette)) pico8.sprite_shader:send("transparent", shdr_unpack(pico8.pal_transparent)) @@ -392,7 +445,11 @@ vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) } int index = int(color.r*15.0+0.5); // lookup the color in the palette by index - return vec4(palette[index]/15.0, 0.0, 0.0, texcolor.a); + for (int x = 0; x < 16; x++) { + if (x == index) { + return vec4(palette[x]/15.0, 0.0, 0.0, texcolor.a); + } + } }]]) pico8.text_shader:send("palette", shdr_unpack(pico8.draw_palette)) @@ -402,7 +459,11 @@ extern vec4 palette[16]; vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { int index = int(Texel(texture, texture_coords).r*15.0+0.5); // lookup the color in the palette by index - return palette[index]/255.0; + for (int x = 0; x < 16; x++) { + if (x == index) { + return palette[x]/255.0; + } + } }]]) pico8.display_shader:send("palette", shdr_unpack(pico8.display_palette)) @@ -524,13 +585,16 @@ vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) end if initialcartname == nil or initialcartname == "" then - initialcartname = "nocart.p8" + initialcartname = false end _load(initialcartname) api.run() - if tas_tool_name == nil then + if not initialcartname then + print("nothing specified, possibly because the user doesn't have a console, defaulting to celestetas.") + tas_tool_name = "cctas" + elseif tas_tool_name == nil then print("no tas tool specified, defaulting to general pico-8 tas tool") tas_tool_name = "tas" end @@ -575,7 +639,27 @@ local function inside(x, y, x0, y0, w, h) -- luacheck: no unused return (x >= x0 and x < x0 + w and y >= y0 and y < y0 + h) end -function love.update(_) +function love.update(dt) + if is_web and cartname then + local filename = "tmp/file_pending" + if love.filesystem.getInfo(filename) then + local fh, e = love.filesystem.newFile(filename,'r') + if e then + print(e) + else + local data = fh:read() + fh:close() + if data=="tas" then + tastool:load_input_file() + else + _load(data) + api.run() + tastool=guess_tool(pico8.cart)() + end + end + love.filesystem.remove(filename) + end + end tastool:update() end @@ -843,7 +927,7 @@ local function update_audio(time) ch.bufferpos = ch.bufferpos + 1 if ch.bufferpos == __audio_buffer_size then -- queue buffer and reset - __audio_channels[channel]:queue(ch.buffer) + __audio_channels[channel]:queue(ch.buffer, ch.buffer:getSize()) __audio_channels[channel]:play() ch.bufferpos = 0 end @@ -851,17 +935,6 @@ local function update_audio(time) end end -local function isCtrlOrGuiDown() - return love.keyboard.isDown("lctrl") - or love.keyboard.isDown("lgui") - or love.keyboard.isDown("rctrl") - or love.keyboard.isDown("rgui") -end - -local function isAltDown() - return love.keyboard.isDown("lalt") or love.keyboard.isDown("ralt") -end - function start_gif_recording() -- start recording if not love.filesystem.getInfo("gifs", "directory") and not love.filesystem.createDirectory("gifs") then @@ -899,36 +972,36 @@ function love.keypressed(key, scancode, isrepeat) return end - if key == "r" and isCtrlOrGuiDown() and not isAltDown() then + if ke.full_reload and not ke.alt then api.reload_cart() api.run() tastool = tastool.class() -- elseif - -- key == "escape" - -- and cartname ~= nil - -- and cartname ~= initialcartname - -- and cartname ~= "nocart.p8" - -- and cartname ~= "editor.p8" + -- key == "escape" + -- and cartname ~= nil + -- and cartname ~= initialcartname + -- and cartname ~= "nocart.p8" + -- and cartname ~= "editor.p8" -- then -- api.load(initialcartname) -- api.run() -- return - elseif key == "q" and isCtrlOrGuiDown() and not isAltDown() then + elseif ke.full_quit then love.event.quit() -- elseif key == "v" and isCtrlOrGuiDown() and not isAltDown() then - -- pico8.clipboard = love.system.getClipboardText() + -- pico8.clipboard = love.system.getClipboardText() -- elseif pico8.can_pause and (key == "pause" or key == "p") then -- paused = not paused - elseif key == "f1" or key == "f6" then + elseif ke.screenshot then -- screenshot local filename = cartname .. "-" .. os.time() .. ".png" local screenshot = love.graphics.captureScreenshot(filename) log("saved screenshot to", filename) - elseif key == "f3" or key == "f8" or (key=="8" and isCtrlOrGuiDown()) then + elseif ke.gif_rec_start then start_gif_recording() - elseif key == "f4" or key == "f9" or (key=="9" and isCtrlOrGuiDown()) then + elseif ke.gif_rec_stop then stop_gif_recording() - elseif key == "return" and isAltDown() then + elseif ke.fullscreen then local canvas=love.graphics.getCanvas() love.graphics.setCanvas() love.window.setFullscreen(not love.window.getFullscreen(), "desktop") @@ -944,20 +1017,35 @@ function love.keypressed(key, scancode, isrepeat) end end +-- the existence of this function is itself a sign that keybindings.lua still sucks +function love.keyreleased(key) + if tastool.just_advanced then + local mem = keybinds.get_ongoing() + local active_chord = table.remove(mem) + local prev_chord = table.remove(mem) + for k in pairs(prev_chord) do + if type(k)=="string" and k~=key and k~="prev_frame" and k~="next_frame" then + active_chord[k] = true + end + end + tastool.just_advanced = false + end + tastool.dontrepeat = {} +end -- function love.keyreleased(key) --- for p = 0, 1 do --- for i = 0, #pico8.keymap[p] do --- for _, testkey in pairs(pico8.keymap[p][i]) do --- if key == testkey then --- pico8.keypressed[p][i] = nil --- break --- end --- end --- end --- end --- if pico8.cart and pico8.cart._keyup then --- return pico8.cart._keyup(key) --- end +-- for p = 0, 1 do +-- for i = 0, #pico8.keymap[p] do +-- for _, testkey in pairs(pico8.keymap[p][i]) do +-- if key == testkey then +-- pico8.keypressed[p][i] = nil +-- break +-- end +-- end +-- end +-- end +-- if pico8.cart and pico8.cart._keyup then +-- return pico8.cart._keyup(key) +-- end -- end function love.textinput(text) @@ -999,6 +1087,32 @@ function love.graphics.point(x, y) love.graphics.rectangle("fill", x, y, 1, 1) end +if love.system.getOS()=='Web' then + function love.system.getClipboardText() + local fh, e = love.filesystem.newFile("tmp/clipboard","r") + if e then + print(e) + return + else + local data = fh:read() + fh:close() + return data + end + end + + function love.system.setClipboardText(text) + local fh, e = love.filesystem.newFile("tmp/set_clipboard","w") + if e then + print(e) + return + else + fh:write(text) + fh:close() + end + end +end + + function love.run() if love.load then love.load(love.arg.parseGameArguments(arg), arg) diff --git a/makefile b/makefile index e2052d7..add02b9 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ -.PHONY: run all lint build clean format test +.PHONY: run all lint build clean format test web run_build -project_name := "Celia" +project_name := celia run: @love . --test @@ -14,21 +14,34 @@ lint: luacheck . format: - @sed -i s/0x1234\.abcd/0x1234abcd/g test.lua + sed -i s/0x1234\.abcd/0x1234abcd/g test.lua stylua . - @sed -i s/0x1234abcd/0x1234\.abcd/g test.lua + sed -i s/0x1234abcd/0x1234\.abcd/g test.lua clean: - @echo "deleting \"build/${project_name}.love\" ..." - @rm -f build/${project_name}.love + rm -rf build/* test: # todo implement test running build: clean @echo "building \"build/${project_name}.love\" ..." - @zip -9 -r build/"${project_name}".love ./nocart.p8 - @zip -9 -r -x@excludelist.txt build/${project_name}.love . + # include .lua, .p8, and .png files (needed for icon and font) + # exclude tests + find . \( -name '*.lua' -or -name '*.png' -or -name '*.p8' -or -name '*.conf' \) \ + -not -path '*test/*' -not -path '*Love.js-Api-Player/*' \ + -print0 \ + | cut -z -c3- \ + | xargs -0 zip -9 build/${project_name}.love + +web: build + @command -v love.js \ + && love.js -c -t ${project_name} build/${project_name}.love build/__site/ \ + || node_modules/love.js/index.js -c -t ${project_name} build/${project_name}.love build/__site/ \ + || ( echo "love.js executable not found"; exit 1 ) + cp -f res/index.html build/__site/index.html + cp -f -r res/theme/ build/__site/ + cd build/__site/ && node ../../Love.js-Api-Player/globalizeFS.js run_build: @echo "executing \"build/${project_name}.love\" ..." diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f45b73f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,95 @@ +{ + "name": "wCelia", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "love.js": "^11.4.1" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/klaw-sync": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-2.1.0.tgz", + "integrity": "sha512-lIxVCUMQIF7hfygFfdZgv4Z+e1smLroaYNQMUcf1TcJ5oqxj9m8qk19iIuMVl+tXQPr3CSE4V+4XjGEqmsth0Q==", + "optionalDependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/love.js": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/love.js/-/love.js-11.4.1.tgz", + "integrity": "sha512-NLMD2GpIHOCsflgulr18p4+Crizfixgo5ZMbJmDrqVuR7jdketbUDL7X+LCR2o2uMkVU8ZwXnRwT86S7A75yhA==", + "dependencies": { + "commander": "^2.9.0", + "fs-extra": "^3.0.1", + "klaw-sync": "^2.1.0", + "mustache": "^2.3.0", + "uuid": "^3.0.1" + }, + "bin": { + "love.js": "index.js" + }, + "engines": { + "node": ">=7.10.0", + "npm": ">=4.2.0" + } + }, + "node_modules/mustache": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", + "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", + "bin": { + "mustache": "bin/mustache" + }, + "engines": { + "npm": ">=1.4.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d0a8643 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "love.js": "^11.4.1" + } +} diff --git a/res/index.html b/res/index.html new file mode 100644 index 0000000..7ec69f0 --- /dev/null +++ b/res/index.html @@ -0,0 +1,183 @@ + + + + + + + Celia + + + + + + +
+

+ pico-8 0.2.5g
+ (c) 2014-23 lexaloffle games llp
+
+
+ > load celia +
+

WARNING: The web port of celia is rudimentary, unstable, innacurate, and outdated. It's fundamentally incompatible with some carts (ones that make extensive use of closures in code) that desktop celia has no issues with. Custom carts don't work on chromium-based browsers at the moment. There is currently no way to restore backups (even though they're still saved) or change keybindings (even though the functionality is there). Due to how Celia works it consumes a lot of memory and is likely to freeze. There have been a couple reports of crashes that I can't even explain. It's also many commits behind upstream, missing some small fixes done there. Use the upstream (offline) if possible.

+
+
+ + +
+
+ + + + + +

Drag and drop pico-8 carts or TAS files on the game window to open them.

+

This program also runs offline, provided you install LOVE2D.

+

Built with love.js
Hint: Reload the page if screen is blank

+
+ Back to Homepage + + diff --git a/res/theme/font/pico-8-smooth-webfont.woff b/res/theme/font/pico-8-smooth-webfont.woff new file mode 100644 index 0000000..dea7f6a Binary files /dev/null and b/res/theme/font/pico-8-smooth-webfont.woff differ diff --git a/res/theme/font/pico-8-smooth-webfont.woff2 b/res/theme/font/pico-8-smooth-webfont.woff2 new file mode 100644 index 0000000..b36976d Binary files /dev/null and b/res/theme/font/pico-8-smooth-webfont.woff2 differ diff --git a/res/theme/font/pico-8-smooth.otf b/res/theme/font/pico-8-smooth.otf new file mode 100644 index 0000000..222fe16 Binary files /dev/null and b/res/theme/font/pico-8-smooth.otf differ diff --git a/res/theme/font/pico-8-webfont.woff b/res/theme/font/pico-8-webfont.woff new file mode 100644 index 0000000..a659967 Binary files /dev/null and b/res/theme/font/pico-8-webfont.woff differ diff --git a/res/theme/font/pico-8-webfont.woff2 b/res/theme/font/pico-8-webfont.woff2 new file mode 100644 index 0000000..48c614a Binary files /dev/null and b/res/theme/font/pico-8-webfont.woff2 differ diff --git a/res/theme/font/pico-8.ttf b/res/theme/font/pico-8.ttf new file mode 100644 index 0000000..93ec11f Binary files /dev/null and b/res/theme/font/pico-8.ttf differ diff --git a/icon.png b/res/theme/icon.png similarity index 100% rename from icon.png rename to res/theme/icon.png diff --git a/res/theme/love.css b/res/theme/love.css new file mode 100644 index 0000000..cdbd2b7 --- /dev/null +++ b/res/theme/love.css @@ -0,0 +1,74 @@ +@font-face { + font-family: 'pico-8'; + src: url('font/pico-8-webfont.woff') format('woff'), + url('font/pico-8-webfont.woff2') format('woff2'), + url('font/pico-8.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'pico-8-smooth'; + src: url('font/pico-8-smooth-webfont.woff') format('woff'), + url('font/pico-8-smooth-webfont.woff2') format('woff2'), + url('font/pico-8-smooth.otf') format('opentype'); + font-weight: normal; + font-style: normal; +} + +body { + box-sizing: border-box; +} + +h1 { + font-family: "pico-8-smooth"; +} + +body { + margin: 8px; + padding: 8px; + + width: 100vw - 8px; + height: 100vh - 8px; + + font-family: 'pico-8'; + line-height: 1.25; + background-color: #000; + color: #fff1e8; +} + +hr { + border-top: 0.19em solid #fff1e8; + border-bottom: 0; + border-left: 0; + border-right: 0; + text-align: left; + margin-left: 0; + width: min(720px, 80vw); +} + +.bootup { + color: #c2c3c7; +} + +footer { + font-size: 12px; + padding-left: 10px; + position:absolute; + bottom: 0; + width: 100%; +} + +/* Links */ +a:link, a:visited, a:active { + color: #29adff; + text-decoration: none; +} + +/* the canvas *must not* have any border or padding, or mouse coords will be wrong */ +#canvas { + padding-right: 0; + display: block; + border: 0px none; + visibility: hidden; +} diff --git a/res/theme/p8logo.png b/res/theme/p8logo.png new file mode 100644 index 0000000..7d2cf6f Binary files /dev/null and b/res/theme/p8logo.png differ diff --git a/tas.lua b/tas.lua index 7375eb7..ca4f485 100644 --- a/tas.lua +++ b/tas.lua @@ -1,3 +1,5 @@ +local bit = require("numberlua").bit + require "deepcopy" local class = require("30log") @@ -7,6 +9,8 @@ tas.hud_w = 48 tas.hud_h = 0 tas.scale = 6 tas.pianoroll_w=65 +tas.dontrepeat = {} +tas.just_advanced = false --wrapper functions @@ -88,15 +92,13 @@ end -- -- i.e, if the input is currently right, and up is held, up+right will be returned function tas:advance_keystate(curr_keystate) + self.just_advanced = true curr_keystate = curr_keystate or 0 curr_keystate= bit.bor(curr_keystate, self.hold) if not self.realtime_playback then for i=0, #pico8.keymap[0] do - for _, testkey in pairs(pico8.keymap[0][i]) do - if love.keyboard.isDown(testkey) then - curr_keystate = bit.bor(curr_keystate, 2^i) - break - end + if ke["k_"..pico8.keymap[0][i]] then + curr_keystate = bit.bor(curr_keystate, 2^i) end end end @@ -144,6 +146,15 @@ function tas:state_iter() end end +function tas:update_working_file() + if is_web then + local file = love.filesystem.newFile("tmp/working_file") + file:open("w") + file:write(self:get_input_file_obj():getFilename()) + file:close() + end +end + -- advance the pico8 state ignoring buttons or backing up the state local function rawstep() pico8.frames = pico8.frames + 1 @@ -234,6 +245,7 @@ function tas:init() --(func)on_finish, (func)finish_condition, (bool)fast_forward, (bool)finish_on_interrupt self.seek=nil + self:update_working_file() end function tas:update() @@ -461,7 +473,6 @@ function tas:draw_gif_overlay() end function tas:keypressed(key, isrepeat) - local ctrl = love.keyboard.isDown('lctrl', 'rctrl', 'lgui', 'rgui') if self.realtime_playback then -- pressing any key during realtime playback stops it self.realtime_playback = false @@ -471,62 +482,55 @@ function tas:keypressed(key, isrepeat) self.seek.on_finish() end self.seek=nil - elseif key=='p' then + elseif ke.playback then self.realtime_playback = not self.realtime_playback --TODO: block keypresses even when overloading this func elseif self.last_selected_frame ~= -1 then self:selection_keypress(key, isrepeat) - elseif key=='l' then - if love.keyboard.isDown('lshift', 'rshift') then - if self:frame_count() + 1 < #self.keystates then - self.last_selected_frame = self:frame_count() + 2 - end - else - self:step() + elseif ke.visual then + if self:frame_count() + 1 < #self.keystates then + self.last_selected_frame = self:frame_count() + 2 end - elseif key=='k' then + elseif ke.next_frame then + self:step() + elseif ke.prev_frame then self:rewind() - elseif key=='d' then + elseif ke.full_rewind then self:full_rewind() - elseif key=='r' and love.keyboard.isDown('lshift','rshift') then + elseif ke.reset_tas then self:push_undo_state() self:full_reset() - elseif key=='m' then + elseif ke.save_tas then self:save_input_file() - elseif key=='w' and love.keyboard.isDown('lshift', 'rshift') then + elseif ke.open_tas then self:push_undo_state() self:load_input_file() - elseif key=='insert' then + elseif ke.insert_blank then self:push_undo_state() - if ctrl then - self:duplicate_keystate() - else - self:insert_keystate() - end - elseif key=='delete' then + self:insert_keystate() + elseif ke.duplicate then + self:duplicate_keystate() + elseif ke.delete then self:push_undo_state() self:delete_keystate() - elseif key == 'v' and ctrl then + elseif ke.copy then self:push_undo_state() self:paste_inputs() - elseif key == 'z' and ctrl then - if love.keyboard.isDown('lshift', 'rshift') then - self:perform_redo() - else - self:preform_undo() - end - else + elseif ke.redo then + self:perform_redo() + elseif ke.undo then + self:preform_undo() + elseif not isrepeat then for i = 0, #pico8.keymap[0] do - for _, testkey in pairs(pico8.keymap[0][i]) do - if key == testkey and not isrepeat then - if love.keyboard.isDown("lshift", "rshift") then - self:push_undo_state() - self:toggle_hold(i) - else - self:push_undo_state() - self:toggle_key(i) - end - break + if not self.dontrepeat[i] then + if ke["hold_"..pico8.keymap[0][i]] then + self:push_undo_state() + self:toggle_hold(i) + self.dontrepeat[i]=true + elseif ke["k_"..pico8.keymap[0][i]] then + self:push_undo_state() + self:toggle_key(i) + self.dontrepeat[i]=true end end end @@ -535,55 +539,51 @@ end function tas:selection_keypress(key, isrepeat) local ctrl = love.keyboard.isDown("lctrl", "rctrl", "lgui", "rgui") - if key == 'l' then + if ke.next_frame then self.last_selected_frame = math.min(self.last_selected_frame + 1, #self.keystates) - elseif key == 'k' then + elseif ke.prev_frame then self.last_selected_frame = self.last_selected_frame - 1 if self.last_selected_frame <= self:frame_count() + 1 then self.last_selected_frame = -1 end - elseif key == 'escape' then + elseif ke.exit_visual then self.last_selected_frame = -1 - elseif key=='delete' then + elseif ke.delete then self:push_undo_state() self:delete_selection() - elseif key == 'c' and ctrl then + elseif ke.copy then love.system.setClipboardText(self:get_input_str(self:frame_count() + 1, self.last_selected_frame)) - elseif key == 'x' and ctrl then + elseif ke.cut then love.system.setClipboardText(self:get_input_str(self:frame_count() + 1, self.last_selected_frame)) self:push_undo_state() self:delete_selection() - elseif key == 'v' and ctrl then + elseif ke.paste then self:push_undo_state() self:delete_selection() self:paste_inputs() - elseif key == 'z' and ctrl then - if love.keyboard.isDown('lshift', 'rshift') then - self:perform_redo() - else - self:preform_undo() - end - elseif key == 'home' then + elseif ke.redo then + self:perform_redo() + elseif ke.undo then + self:preform_undo() + elseif ke.go_to_start then self.last_selected_frame = self:frame_count() + 2 - elseif key=='end' then + elseif ke.go_to_end then self.last_selected_frame = #self.keystates elseif not isrepeat then -- change the state of the key in all selected frames -- if alt is held, toggle the state in all the frames -- otherwise, toggle it in the first frame, and set all other selected frames to match it for i = 0, #pico8.keymap[0] do - for _, testkey in pairs(pico8.keymap[0][i]) do - if key == testkey then + if ke["k_"..pico8.keymap[0][i]] and not self.dontrepeat[i] then self:push_undo_state() self:toggle_key(i) for frame = self:frame_count() + 2, self.last_selected_frame do - if love.keyboard.isDown("lalt", "ralt") or self:key_down(i,frame) ~= self:key_down(i) then + if ke["all_"..pico8.keymap[0][i]] or self:key_down(i,frame) ~= self:key_down(i) then self:toggle_key(i, frame) end end - break - end + self.dontrepeat[i] = true end end end @@ -692,6 +692,14 @@ function tas:save_input_file() end if f:open("w") then f:write(self:get_input_str()) + if is_web then + local t,e = love.filesystem.newFile("tmp/save_pending","w") + if e then + print(e) + return + end + t:close() + end print("saved file to ".. love.filesystem.getRealDirectory(f:getFilename()).."/"..f:getFilename()) else print("error saving input file") diff --git a/test.lua b/test.lua index de71e50..f659919 100644 --- a/test.lua +++ b/test.lua @@ -1,3 +1,5 @@ +local bit = require("numberlua").bit + local api = require("api") local lust = require("lust") diff --git a/web-default.keys.conf b/web-default.keys.conf new file mode 100644 index 0000000..324ef00 --- /dev/null +++ b/web-default.keys.conf @@ -0,0 +1,114 @@ +# +# keybindings config file +# delete this file to regenerate defaults +# +# helper keys +alt: ralt | lalt +ctrl: rctrl | lctrl +shift: rshift | lshift +gui: rgui | lgui +p8ctrl: ctrl | gui +# +# generic +# +fullscreen: alt + return +full_quit: ctrl + q +full_reload: ctrl + r +copy: p8ctrl + c +cut: p8ctrl + x +paste: p8ctrl + v +# +# pico-8 keys +# +k_left: left | kp4 +k_right: right | kp6 +k_up: up | kp8 +k_down: down | kp5 +k_jump: z | c | n | kp- | kp1 | insert +k_dash: x | v | m | 8 | kp2 | delete +k_pause: return | escape +k_seven: 7 +# held +hold_left: shift + k_left +hold_right: shift + k_right +hold_up: shift + k_up +hold_down: shift + k_down +hold_jump: shift + k_jump +hold_dash: shift + k_dash +hold_pause: shift + k_pause +hold_seven: shift + k_seven +# toggle all (visual mode) +all_left: a + k_left +all_right: a + k_right +all_up: a + k_up +all_down: a + k_down +all_jump: a + k_jump +all_dash: a + k_dash +all_pause: a + k_pause +all_seven: a + k_seven +# +# pico-8 tas +# +prev_frame: k +next_frame: l +full_rewind: d +playback: p +reset_tas: shift + r +save_tas: m +open_tas: shift + w +insert_blank: insert +duplicate: p8ctrl + insert +delete: delete +visual: shift + l +undo: p8ctrl + z +redo: shift + undo +screenshot: f1 | f6 +gif_rec_start: f3 | f8 | p8ctrl+8 +gif_rec_stop: f4 | f9 | p8ctrl+9 +# +# celeste tas +# +prev_level: s +next_level: f +rewind: shift + d +level_gif: shift + g +clean_save: u +full_playback: shift + n +inc_djump: shift + = +dec_djump: - +reset_djump: = +print_pos: y +# jank offset editing mode +jank_offset: a +inc_jank: up +dec_jank: down +quit_jank: jank_offset +# rng seeding mode +rng_seeding: b +dec_rng: down +inc_rng: up +prev_object: left +next_object: right +quit_rng: rng_seeding +# +# visual mode +# +go_to_start: home +go_to_end: end +exit_visual: escape +# +# console +# +console: ctrl + ~ +del_backward: backspace +prev_char: left +next_char: right +prev_word: ctrl + left +next_word: ctrl + right +cmd_go_to_end: ctrl + e +cmd_go_to_start: ctrl + a +next_command: down +prev_command: up +clear_line: ctrl + c +complete_command: tab +send_line: return