387 lines
9.3 KiB
Lua
387 lines
9.3 KiB
Lua
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
|