From ee3c595077b984c6febe42a54966adebd081f0f6 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 28 Feb 2020 03:20:05 -0600 Subject: [PATCH 1/3] creates a unique sharecode for any given combination of settings --- src/lib/bit.lua | 260 ++++++++++++++++++++++++++++++++++++++++++++ src/main.lua | 1 + src/randomizer.lua | 53 ++++++++- src/ui/draw.lua | 29 +++-- src/ui/settings.lua | 2 +- 5 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 src/lib/bit.lua diff --git a/src/lib/bit.lua b/src/lib/bit.lua new file mode 100644 index 0000000..d9fd4ce --- /dev/null +++ b/src/lib/bit.lua @@ -0,0 +1,260 @@ +--[[--------------- +LuaBit v0.4 +------------------- +a bitwise operation lib for lua. + +http://luaforge.net/projects/bit/ + +How to use: +------------------- + bit.bnot(n) -- bitwise not (~n) + bit.band(m, n) -- bitwise and (m & n) + bit.bor(m, n) -- bitwise or (m | n) + bit.bxor(m, n) -- bitwise xor (m ^ n) + bit.brshift(n, bits) -- right shift (n >> bits) + bit.blshift(n, bits) -- left shift (n << bits) + bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>) + +Please note that bit.brshift and bit.blshift only support number within +32 bits. + +2 utility functions are provided too: + bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence) + -- high bits first + bit.tonumb(bit_tbl) -- convert a bit table into a number +------------------- + +Under the MIT license. + +copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com) +--]]--------------- + +do + +------------------------ +-- bit lib implementions + +local function check_int(n) + -- checking not float + if(n - math.floor(n) > 0) then + error("trying to use bitwise operation on non-integer!") + end +end + +local function to_bits(n) + check_int(n) + if(n < 0) then + -- negative + return to_bits(bit.bnot(math.abs(n)) + 1) + end + -- to bits table + local tbl = {} + local cnt = 1 + while (n > 0) do + local last = math.mod(n,2) + if(last == 1) then + tbl[cnt] = 1 + else + tbl[cnt] = 0 + end + n = (n-last)/2 + cnt = cnt + 1 + end + + return tbl +end + +local function tbl_to_number(tbl) + local n = table.getn(tbl) + + local rslt = 0 + local power = 1 + for i = 1, n do + rslt = rslt + tbl[i]*power + power = power*2 + end + + return rslt +end + +local function expand(tbl_m, tbl_n) + local big = {} + local small = {} + if(table.getn(tbl_m) > table.getn(tbl_n)) then + big = tbl_m + small = tbl_n + else + big = tbl_n + small = tbl_m + end + -- expand small + for i = table.getn(small) + 1, table.getn(big) do + small[i] = 0 + end + +end + +local function bit_or(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 and tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_and(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 or tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_not(n) + + local tbl = to_bits(n) + local size = math.max(table.getn(tbl), 32) + for i = 1, size do + if(tbl[i] == 1) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + return tbl_to_number(tbl) +end + +local function bit_xor(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i] ~= tbl_n[i]) then + tbl[i] = 1 + else + tbl[i] = 0 + end + end + + --table.foreach(tbl, print) + + return tbl_to_number(tbl) +end + +local function bit_rshift(n, bits) + check_int(n) + + local high_bit = 0 + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + high_bit = 2147483648 -- 0x80000000 + end + + for i=1, bits do + n = n/2 + n = bit_or(math.floor(n), high_bit) + end + return math.floor(n) +end + +-- logic rightshift assures zero filling shift +local function bit_logic_rshift(n, bits) + check_int(n) + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + for i=1, bits do + n = n/2 + end + return math.floor(n) +end + +local function bit_lshift(n, bits) + check_int(n) + + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + + for i=1, bits do + n = n*2 + end + return bit_and(n, 4294967295) -- 0xFFFFFFFF +end + +local function bit_xor2(m, n) + local rhs = bit_or(bit_not(m), bit_not(n)) + local lhs = bit_or(m, n) + local rslt = bit_and(lhs, rhs) + return rslt +end + +-------------------- +-- bit lib interface + +bit = { + -- bit operations + bnot = bit_not, + band = bit_and, + bor = bit_or, + bxor = bit_xor, + brshift = bit_rshift, + blshift = bit_lshift, + bxor2 = bit_xor2, + blogic_rshift = bit_logic_rshift, + + -- utility func + tobits = to_bits, + tonumb = tbl_to_number, +} + +end + +--[[ +for i = 1, 100 do + for j = 1, 100 do + if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then + error("bit.xor failed.") + end + end +end +--]] + + + + + + + + + + + + + diff --git a/src/main.lua b/src/main.lua index 9ec3029..f2ecb68 100644 --- a/src/main.lua +++ b/src/main.lua @@ -12,6 +12,7 @@ ld = love.data U = require 'util' require 'log' +require 'lib.bit' local random = require 'randomizer' local settings = require 'settings' diff --git a/src/randomizer.lua b/src/randomizer.lua index 35cf103..464a408 100644 --- a/src/randomizer.lua +++ b/src/randomizer.lua @@ -35,6 +35,7 @@ function C:new() self.customseed = nil self.puppy = false self.obj = "" + self.sharecode = "" end function C:setPath(path) @@ -53,7 +54,11 @@ function C:randomize() return "Could not find \"data\" subfolder.\n\nMaybe try dropping your Cave Story \"data\" folder in directly?" end + self:_logSettings() + local seed = self:_seedRngesus() + self:_updateSharecode(seed) + local tscFiles = self:_createTscFiles(dirStage) -- self:_writePlaintext(tscFiles) self:_shuffleItems(tscFiles) @@ -64,7 +69,7 @@ function C:randomize() self:_updateSettings() - return self:_getStatusMessage(seed) + return self:_getStatusMessage(seed, self.sharecode) end function C:_mountDirectory(path) @@ -257,17 +262,57 @@ function C:_unmountDirectory(path) assert(lf.unmount(path)) end +function C:_logSettings() + local obj = "Best Ending" + if self.obj == "objBadEnd" then + obj = "Bad Ending" + elseif self.obj == "objNormalEnd" then + obj = "Normal Ending" + elseif self.obj == "objAllBosses" then + obj = "All Bosses" + end + + local puppy = self.puppy and ", Puppysanity" or "" + logNotice(("Game settings: %s"):format(obj .. puppy)) +end + function C:_updateSettings() Settings.settings.puppy = self.puppy Settings.settings.obj = self.obj Settings:update() end -function C:_getStatusMessage(seed) +function C:_updateSharecode(seed) + local settings = 0 -- 0b00000000 + -- 0bXXXXXPOO + -- P: single bit used for puppysanity + -- O: two bits used for objective + -- X: unused + + if self.obj == "objBadEnd" then + settings = bit.bor(settings, 1) + elseif self.obj == "objNormalEnd" then + settings = bit.bor(settings, 2) + elseif self.obj == "objAllBosses" then + settings = bit.bor(settings, 3) + end + if self.puppy then settings = bit.bor(settings, 4) end + + if #seed < 20 then + seed = seed .. (" "):rep(20-#seed) + end + + local packed = love.data.pack("data", "sB", seed, settings) + self.sharecode = love.data.encode("string", "base64", packed) + + logNotice(("Sharecode: %s"):format(self.sharecode)) +end + +function C:_getStatusMessage(seed, sharecode) local warnings, errors = countLogWarningsAndErrors() local line1 if warnings == 0 and errors == 0 then - line1 = ("Randomized data successfully created!\nSeed: %s"):format(seed) + line1 = ("Randomized data successfully created!\nSeed: %s\nSharecode: %s"):format(seed, sharecode) elseif warnings ~= 0 and errors == 0 then line1 = ("Randomized data was created with %d warning(s)."):format(warnings) else @@ -275,7 +320,7 @@ function C:_getStatusMessage(seed) end local line2 = "Next overwrite the files in your copy of Cave Story with the versions in the newly created \"data\" folder. Don't forget to save a backup of the originals!" local line3 = "Then play and have a fun!" - local status = ("%s\n%s\n\n%s"):format(line1, line2, line3) + local status = ("%s\n%s\n%s"):format(line1, line2, line3) return status end diff --git a/src/ui/draw.lua b/src/ui/draw.lua index a5413d1..ba41d25 100644 --- a/src/ui/draw.lua +++ b/src/ui/draw.lua @@ -12,19 +12,27 @@ layout:setTheme(require 'lib.luigi.theme.dark') settings:setTheme(require 'lib.luigi.theme.dark') function C:setup() - settings.puppy.value = Settings.settings.puppy - local obj = Settings.settings.obj - if obj == "objBadEnd" then + self:loadSettings(Settings.settings.puppy, Settings.settings.obj) + + background = lg.newImage('assets/background.png') + self:draw() + layout:show() +end + +function C:loadSettings(puppy, obj, seed) + settings.puppy.value = puppy + + if obj == "objBadEnd" or obj == 1 then settings.bad.value = true settings.norm.value = false settings.boss.value = false settings.best.value = false - elseif obj == "objNormalEnd" then + elseif obj == "objNormalEnd" or obj == 2 then settings.bad.value = false settings.norm.value = true settings.boss.value = false settings.best.value = false - elseif obj == "objAllBosses" then + elseif obj == "objAllBosses" or obj == 3 then settings.bad.value = false settings.norm.value = false settings.boss.value = true @@ -36,9 +44,7 @@ function C:setup() settings.best.value = true end - background = lg.newImage('assets/background.png') - self:draw() - layout:show() + settings.customseed.value = seed or "" end layout.version.text = 'Cave Story Randomizer [Open Mode] v' .. VERSION @@ -81,6 +87,13 @@ settings.closeButton:onPress(function() layout:show() end) +settings.customseed:onChange(function() + if #settings.customseed.value > 20 then + settings.customseed.value = settings.customseed.value:sub(1, 20) + end + settings.seedcount.text = ("%s/20"):format(#settings.customseed.value) +end) + function C:draw() lg.draw(background, 0, 0) --layout:show() diff --git a/src/ui/settings.lua b/src/ui/settings.lua index 5c4409b..41444ef 100644 --- a/src/ui/settings.lua +++ b/src/ui/settings.lua @@ -5,7 +5,7 @@ return { style = 'dialog', { type = 'label', text = 'Seed' }, { { type = 'radio', group = 'seed', text = 'Use random seed', value = true }, - { flow = 'y', { type = 'radio', group = 'seed', text = 'Use custom seed', id = 'seedselect'}, { type = 'text', id = 'customseed', width = 150 }, {height = false} } + { flow = 'y', { type = 'radio', group = 'seed', text = 'Use custom seed', id = 'seedselect'}, {{ type = 'text', id = 'customseed', width = 200}, flow = 'x', { type = 'label', id = 'seedcount' }}, {height = false} } }, { type = 'label', text = 'Randomization Options' }, { type = 'check', value = false, id = 'puppy', text = "Puppysanity"}, From d7969ecd6585ff5c6ffa7565b55f06ea7f186264 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 28 Feb 2020 03:28:46 -0600 Subject: [PATCH 2/3] can now copy sharecode after randomization --- src/ui/draw.lua | 12 +++++++++++- src/ui/main.lua | 10 +++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ui/draw.lua b/src/ui/draw.lua index ba41d25..1ef40f4 100644 --- a/src/ui/draw.lua +++ b/src/ui/draw.lua @@ -54,6 +54,8 @@ layout.twitter.text = '(@shruuu and @duncathan_salt)' layout.footer.text = 'Original randomizer:\r\nshru.itch.io/cave-story-randomizer' layout.go:onPress(function() + Randomizer:new() + if Randomizer:ready() then if settings.seedselect.value and settings.customseed.value ~= "" then Randomizer.customseed = settings.customseed.value @@ -71,7 +73,8 @@ layout.go:onPress(function() Randomizer.puppy = settings.puppy.value C:setStatus(Randomizer:randomize()) - Randomizer:new() + + layout.sharecode.text = "Copy Sharecode" else C:setStatus("No Cave Story folder found!\r\nDrag and drop your Cave Story folder here.") end @@ -87,6 +90,13 @@ settings.closeButton:onPress(function() layout:show() end) +layout.sharecode:onPress(function() + if Randomizer.sharecode ~= "" then + love.system.setClipboardText(Randomizer.sharecode) + layout.sharecode.text = "Copied!" + end +end) + settings.customseed:onChange(function() if #settings.customseed.value > 20 then settings.customseed.value = settings.customseed.value:sub(1, 20) diff --git a/src/ui/main.lua b/src/ui/main.lua index 92717d5..657e3a4 100644 --- a/src/ui/main.lua +++ b/src/ui/main.lua @@ -37,7 +37,15 @@ return { id = 'window', type = 'button', style = 'button', id = 'go', - text = 'Randomize', + text = 'Randomize!', + width = 100, + height = 32, + }, + { + type = 'button', + style = 'button', + id = 'sharecode', + text = "Copy Sharecode", width = 100, height = 32, }, From f8f994b4ae9f77d45ef327ec7661a69d3df67a82 Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 28 Feb 2020 04:27:42 -0600 Subject: [PATCH 3/3] can now import sharecodes --- src/randomizer.lua | 2 -- src/ui/draw.lua | 33 ++++++++++++++++++++++++++++++--- src/ui/settings.lua | 35 +++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/randomizer.lua b/src/randomizer.lua index 464a408..3ae5727 100644 --- a/src/randomizer.lua +++ b/src/randomizer.lua @@ -151,8 +151,6 @@ function C:_shuffleItems(tscFiles) -- place the bomb on MALCO for bad end if self.obj == "objBadEnd" then self.worldGraph:getMALCO()[1]:setItem(self.itemDeck:getByKey("bomb")) - else - logWarning(self.obj) end diff --git a/src/ui/draw.lua b/src/ui/draw.lua index 1ef40f4..1f5bfbe 100644 --- a/src/ui/draw.lua +++ b/src/ui/draw.lua @@ -44,7 +44,11 @@ function C:loadSettings(puppy, obj, seed) settings.best.value = true end - settings.customseed.value = seed or "" + if seed ~= nil then + settings.customseed.value = seed or "" + settings.seedselect.value = true + settings.seedrandom.value = false + end end layout.version.text = 'Cave Story Randomizer [Open Mode] v' .. VERSION @@ -55,10 +59,10 @@ layout.footer.text = 'Original randomizer:\r\nshru.itch.io/cave-story-randomizer layout.go:onPress(function() Randomizer:new() - + if Randomizer:ready() then if settings.seedselect.value and settings.customseed.value ~= "" then - Randomizer.customseed = settings.customseed.value + Randomizer.customseed = settings.customseed.value:gsub("^%s*(.-)%s*$", "%1") -- trim any leading/trailing whitespace end if settings.bad.value then @@ -88,6 +92,7 @@ end) settings.closeButton:onPress(function() settings:hide() layout:show() + settings.sharecode.value = "" end) layout.sharecode:onPress(function() @@ -104,6 +109,28 @@ settings.customseed:onChange(function() settings.seedcount.text = ("%s/20"):format(#settings.customseed.value) end) +settings.importshare:onPress(function() + local success, seed, sharesettings = pcall(function() + local packed = love.data.decode("data", "base64", settings.sharecode.value) + local seed, settings = love.data.unpack("sB", packed) + return seed, settings + end) + + if success then + settings.importshare.text = "Sharecode Imported" + local pup = bit.band(sharesettings, 4) ~= 0 + local obj = bit.band(sharesettings, 3) + seed = seed:gsub("^%s*(.-)%s*$", "%1") -- trim any leading or trailing whitespace + Screen:loadSettings(pup, obj, seed) + else + settings.importshare.text = "Invalid Sharecode!" + end +end) + +settings.sharecode:onChange(function() + settings.importshare.text = "Import Sharecode" +end) + function C:draw() lg.draw(background, 0, 0) --layout:show() diff --git a/src/ui/settings.lua b/src/ui/settings.lua index 41444ef..bacce45 100644 --- a/src/ui/settings.lua +++ b/src/ui/settings.lua @@ -1,23 +1,30 @@ return { style = 'dialog', { style = 'dialogHead', text = 'Settings' }, - { style = 'dialogBody', padding = 24, flow = 'x', - { - { type = 'label', text = 'Seed' }, + { style = 'dialogBody', padding = 24, + { flow = 'x', minheight = 260, { - { type = 'radio', group = 'seed', text = 'Use random seed', value = true }, - { flow = 'y', { type = 'radio', group = 'seed', text = 'Use custom seed', id = 'seedselect'}, {{ type = 'text', id = 'customseed', width = 200}, flow = 'x', { type = 'label', id = 'seedcount' }}, {height = false} } + { type = 'label', text = 'Seed', minheight = 32 }, + { + { type = 'radio', group = 'seed', text = 'Use random seed', value = true, id = 'seedrandom', minheight = 27 }, + { flow = 'y', { type = 'radio', group = 'seed', text = 'Use custom seed', id = 'seedselect', minheight = 27 }, {{ type = 'text', id = 'customseed', width = 200, minheight = 32}, flow = 'x', { type = 'label', id = 'seedcount' }} } + }, + { type = 'label', text = 'Randomization Options', minheight = 32 }, + { type = 'check', value = false, id = 'puppy', text = "Puppysanity", minheight = 27 }, + { height = 64 }, + }, + { + { type = 'label', text = 'Objective', minheight = 32 }, + { type = 'radio', group = 'objective', text = 'Bad ending', id = 'bad', minheight = 27 }, + { type = 'radio', group = 'objective', text = 'Normal ending', id = 'norm', minheight = 27 }, + { type = 'radio', group = 'objective', text = 'Best ending', id = 'best', value = true, minheight = 27 }, + { type = 'radio', group = 'objective', text = 'All bosses', id = 'boss', minheight = 27 }, }, - { type = 'label', text = 'Randomization Options' }, - { type = 'check', value = false, id = 'puppy', text = "Puppysanity"}, - { height = false }, }, { - { type = 'label', text = 'Objective' }, - { type = 'radio', group = 'objective', text = 'Bad ending', id = 'bad' }, - { type = 'radio', group = 'objective', text = 'Normal ending', id = 'norm'}, - { type = 'radio', group = 'objective', text = 'Best ending', id = 'best', value = true }, - { type = 'radio', group = 'objective', text = 'All bosses', id = 'boss' }, - }, + flow = 'x', + { type = 'text', id = 'sharecode', width = 350 }, + { type = 'button', style = 'dialogButton', text = "Import Sharecode", id = 'importshare', width = 180 } + } }, { style = 'dialogFoot', {},