local floor=math.floor local char,byte=string.char,string.byte local ins=table.insert local GAME=GAME local DATA={} -- Sep symbol: 33 (!) -- Safe char: 34~126 --[[ Count: 34~96 Block: 97~125 Encode: A[B] sequence, A = block ID, B = repeat times, no B means do not repeat. Example: "abcdefg" is [SZJLTOI], "a^aDb)" is [Z*63,Z*37,S*10] ]] function DATA.copySequence(bag) local str="" local count=1 for i=1,#bag+1 do if bag[i+1]~=bag[i] or count==64 then str=str..char(96+bag[i]) if count>1 then str=str..char(32+count) count=1 end else count=count+1 end end return str end function DATA.pasteSequence(str) local bag={} local b,reg for i=1,#str do b=byte(str,i) if not reg then if b>=97 and b<=125 then reg=b-96 else return end else if b>=97 and b<=125 then ins(bag,reg) reg=b-96 elseif b>=34 and b<=96 then for _=1,b-32 do ins(bag,reg) end reg=false end end end if reg then ins(bag,reg) end return true,bag end local fieldMeta={__index=function(self,h) for i=#self+1,h do self[i]={0,0,0,0,0,0,0,0,0,0} end return self[h] end} function DATA.newBoard(f)-- Generate a new board return setmetatable(f and TABLE.shift(f) or{},fieldMeta) end function DATA.copyBoard(F)-- Copy the [page] board local str="" -- Encode field for y=1,#F do local S="" local L=F[y] for x=1,10 do S=S..char(L[x]+1) end str=str..S end return STRING.packBin(str) end function DATA.copyBoards(field) local out={} for i=1,#field do out[i]=DATA.copyBoard(field[i]) end return table.concat(out,"!") end function DATA.pasteBoard(str)-- Paste [str] data to [page] board local F=DATA.newBoard() -- Decode str=STRING.unpackBin(str) if not str then return end local fX,fY=1,1-- *ptr for Field(r*10+(c-1)) local p=1 local lineLimit=126 while true do local b=byte(str,p)-- 1byte -- Str end if not b then if fX~=1 then return else break end end local id=b%32-1-- Block id if id>26 then return end-- Illegal blockid b=floor(b/32)-- Mode id F[fY][fX]=id if fX<10 then fX=fX+1 else fY=fY+1 if fY>lineLimit then break end fX=1 end p=p+1 end return true,F,#str>lineLimit*10 end --[[ Mission: 34~114 Count: 115~126 Encode: [A] or [AB] sequence, A = mission ID, B = repeat times, no B means do not repeat. _1=01,_2=02,_3=03,_4=04, A1=05,A2=06,A3=07,A4=08, PC=09, Z1=11,Z2=12,Z3=13, S1=21,S2=22,S3=23, J1=31,J2=32,J3=33, L1=41,L2=42,L3=43, T1=51,T2=52,T3=53, O1=61,O2=62,O3=63,O4=64, I1=71,I2=72,I3=73,I4=74, ]] function DATA.copyMission(mission) local _ local str="" local count=1 for i=1,#mission+1 do if mission[i+1]~=mission[i] or count==13 then _=33+mission[i] str=str..char(_) if count>1 then str=str..char(113+count) count=1 end else count=count+1 end end return str end function DATA.pasteMission(str) local b local mission={} local reg for i=1,#str do b=byte(str,i) if not reg then if b>=34 and b<=114 then reg=b-33 else return end else if b>=34 and b<=114 then if ENUM_MISSION[reg] then ins(mission,reg) reg=b-33 else TABLE.cut(mission) return end elseif b>=115 and b<=126 then for _=1,b-113 do ins(mission,reg) end reg=false end end end if reg then ins(mission,reg) end return true,mission end function DATA.copyQuestArgs(custom_env) local ENV=custom_env local str="".. ENV.holdCount.. (ENV.ospin and "O" or "Z").. (ENV.missionKill and "M" or "Z").. ENV.sequence return str end function DATA.pasteQuestArgs(str) if #str<4 then return end local ENV={} ENV.holdCount= MATH.clamp(str:byte(1)-48,0,26) ENV.ospin= str:byte(2)~=90 ENV.missionKill=str:byte(3)~=90 ENV.sequence= str:sub(4) if select(2,require"parts.player.seqGenerators"(ENV.sequence)) then MES.new('warn',text.invalidSequence) ENV.sequence='bag' end return true,ENV end local function _encode(t) if t<128 then return char(t) end local buffer2=char(t%128) t=floor(t/128) while t>=128 do buffer2=char(128+t%128)..buffer2 t=floor(t/128) end return char(128+t)..buffer2 end local function _decode(str,p) local ret=0 repeat local b=byte(str,p) p=p+1 ret=ret*128+(b<128 and b or b-128) until b<128 return ret,p end --[[ Replay file: a zlib-compressed json table Replay data format (table): {frame,event, frame,event, ...} Replay data format (byte): (1 byte each period) dt, event, dt, event, ... all data range from 0 to 127 large value will be encoded as 1xxxxxxx(high)-1xxxxxxx-...-0xxxxxxx(low) Example (decoded): 26,1, 42,-1, ... This means: Press key1 at 26f Release key1 at 42f ... ]] function DATA.dumpRecording(list,ptr) local out="" local buffer="" if not ptr then ptr=1 end while list[ptr] do if #buffer>26 then out=out..buffer buffer="" end buffer=buffer.._encode(list[ptr]) ptr=ptr+1 end return out..buffer,ptr end function DATA.pumpRecording(str,L) local len=#str local p=1 local data while p<=len do data,p=_decode(str,p) ins(L,data) end end do-- function DATA.saveReplay() local function _getModList() local res={} for number,sel in next,GAME.mod do if sel>0 then ins(res,{MODOPT[number].no,sel}) end end return res end function DATA.saveReplay() -- Filtering modes that cannot be saved if GAME.initPlayerCount~=1 then MES.new('error',"Cannot save recording of more than 1 player now!") return end -- Write file local fileName=os.date("replay/%Y_%m_%d_%H%M%S.rep") if not love.filesystem.getInfo(fileName) then local metadata={ date=os.date("%Y/%m/%d %H:%M:%S"), mode=GAME.curModeName, version=VERSION.string, player=USERS.getUsername(USER.uid), seed=GAME.seed, setting=GAME.setting, mod=_getModList(), modApplyAt=GAME.modApplyAt, tasUsed=GAME.tasUsed, } if GAME.curMode.savePrivate then metadata.private=GAME.curMode.savePrivate() end love.filesystem.write(fileName, love.data.compress('string','zlib', JSON.encode(metadata).."\n".. DATA.dumpRecording(GAME.rep) ) ) ins(REPLAY,1,DATA.parseReplay(fileName)) return true else MES.new('error',"Save failed: File already exists") end end end function DATA.parseReplay(fileName,ifFull) local fileData -- Read file fileData=love.filesystem.read(fileName) return DATA.parseReplayData(fileName,fileData,ifFull) end function DATA.parseReplayData(fileName,fileData,ifFull) local success,metaData local rep={-- unavailable replay object fileName=fileName, available=false, } if not (fileData and #fileData>0) then return rep end-- goto BREAK_cannotParse -- Decompress file success,fileData=pcall(love.data.decompress,'string','zlib',fileData) if not success then return rep end-- goto BREAK_cannotParse -- Load metadata metaData,fileData=STRING.readLine(fileData) metaData=JSON.decode(metaData) if not metaData then return rep end-- goto BREAK_cannotParse -- Convert ancient replays metaData.mode=MODE_UPDATE_MAP[metaData.mode] or metaData.mode if not MODES[metaData.mode] then return rep end-- goto BREAK_cannotParse -- Create replay object rep={ fileName=fileName, available=true, date=metaData.date, mode=metaData.mode, version=metaData.version, player=metaData.player, seed=metaData.seed, setting=metaData.setting, mod=metaData.mod, modApplyAt=metaData.modApplyAt, tasUsed=metaData.tasUsed, } if ifFull then rep.data=fileData end if metaData.private then rep.private=metaData.private end return rep end return DATA