1-- GunFu Deadlands
2-- Copyright 2009-2011 Christiaan Janssen, September 2009-October 2011
3--
4-- This file is part of GunFu Deadlands.
5--
6--     GunFu Deadlands is free software: you can redistribute it and/or modify
7--     it under the terms of the GNU General Public License as published by
8--     the Free Software Foundation, either version 3 of the License, or
9--     (at your option) any later version.
10--
11--     GunFu Deadlands is distributed in the hope that it will be useful,
12--     but WITHOUT ANY WARRANTY; without even the implied warranty of
13--     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14--     GNU General Public License for more details.
15--
16--     You should have received a copy of the GNU General Public License
17--     along with GunFu Deadlands.  If not, see <http://www.gnu.org/licenses/>.
18
19
20function Editor.trytoloadcurrent()
21	if Editor.checklevelfile( Editor.currentfilename ) then
22		Editor.loadlevelfile( Editor.currentfilename )
23		Editor.mode = 4
24	else
25		Editor.fileerror_timer = 1.5
26		Editor.currentfilename = ""
27		Editor.listingtable = Editor.getFileList()
28	end
29end
30
31function Editor.trytosavecurrent()
32	if Editor.savelevelfile( Editor.currentfilename) then
33		Editor.addEntryInFileList( Editor.currentfilename )
34		Editor.currentfilename = ""
35		Editor.mode = 4
36	else
37		Editor.fileerror_timer = 1.5
38		Editor.currentfilename = ""
39		Editor.listingtable = Editor.getFileList()
40	end
41end
42
43
44function Editor.saveslotfile( slot )
45	-- generate file name
46	local filename = "slot"..slot..".gfd"
47	-- save version
48	local versionstring=Editor.versionstring.."\n"
49
50	-- save corresponding slot in memory
51	Editor.saveslot( slot )
52	local slottext=Editor.slottostring( slot )
53	-- save level map in file
54	local map = Editor.levelmapintext()
55	-- save level options
56	local options = Editor.levelOptionsToString()
57
58	-- save slot in file
59	local savedata=versionstring..map..slottext..options
60	Editor.saveinFile( filename, savedata )
61
62end
63
64function Editor.savelevelfile( filename )
65	-- generate file name
66	local filename = filename..".gfl"
67	-- save version
68	local versionstring=Editor.versionstring.."\n"
69
70	-- save level map in file
71	local map = Editor.levelmapintext()
72	-- save level options
73	local options = Editor.levelOptionsToString()
74
75	-- save slot in file
76	local savedata=versionstring..map..options
77	return Editor.saveinFile( filename, savedata )
78
79end
80
81function Editor.saveinFile( filename, savedata )
82	-- save it (overwrite file if necessary)
83	if love.filesystem.write( filename, savedata ) then
84		return true
85	end
86	return false
87end
88
89function Editor.loadfromFile( filename )
90	if not love.filesystem.exists( filename ) then
91		return ""
92	end
93
94	local d, size = love.filesystem.read( filename )
95	if  size > 0 then
96		return d
97	end
98
99	return ""
100end
101
102function Editor.loadslotfile( slot )
103	-- generate file name
104	local filename = "slot"..slot..".gfd"
105	local loaddata = Editor.loadfromFile( filename )
106
107	-- check version
108	if string.sub(loaddata,1,string.len(Editor.versionstring))~=Editor.versionstring then
109		return
110	end
111
112
113	-- fetch level map from file
114	Level.init()
115	Editor.parsemapstring( string.sub(loaddata,string.len(Editor.versionstring),-1) )
116	Level.restart()
117
118	-- fetch slot
119	Editor.currentslot = 1
120	Editor.getslotfromtext( string.sub(loaddata,string.len(Editor.versionstring),-1) )
121
122	-- recall slot
123	Editor.loadslot(Editor.currentslot)
124end
125
126function Editor.loadlevelfile( filename )
127
128	-- generate file name
129	local filename = filename..".gfl"
130	local loaddata = Editor.loadfromFile( filename )
131
132	-- check version
133	if string.sub(loaddata,1,string.len(Editor.versionstring))~=Editor.versionstring then
134		return
135	end
136
137
138	Level.init()
139	Editor.parsemapstring( string.sub(loaddata,string.len(Editor.versionstring),-1) )
140	Level.restart()
141end
142
143-- returns true if the file exists, if it does not, it purges the file list
144function Editor.checklevelfile( filename )
145	if love.filesystem.exists( filename..".gfl" ) then
146		local loaddata = Editor.loadfromFile( filename..".gfl" )
147
148		-- check version
149		if string.sub(loaddata,1,string.len(Editor.versionstring))~=Editor.versionstring then
150			return false
151		end
152		return true
153	else
154		Editor.removeEntryFromfilelist( filename )
155		return false
156	end
157
158end
159
160function Split(str, delim, maxNb)
161    -- Eliminate bad cases...
162    if string.find(str, delim) == nil then
163        return { str }
164    end
165    if maxNb == nil or maxNb < 1 then
166        maxNb = 0    -- No limit
167    end
168    local result = {}
169    local pat = "(.-)" .. delim .. "()"
170    local nb = 0
171    local lastPos
172    for part, pos in string.gfind(str, pat) do
173        nb = nb + 1
174        result[nb] = part
175        lastPos = pos
176        if nb == maxNb then break end
177    end
178    -- Handle the last field
179    if nb ~= maxNb then
180        result[nb + 1] = string.sub(str, lastPos)
181    end
182    return result
183end
184
185function Editor.levelmapintext()
186	local outputstring = ""
187	-- player
188	outputstring = outputstring..Entities.entitystrings[Entities.entitylist.player].."("..
189		math.floor(Player.starting_pos[1])..","..math.floor(Player.starting_pos[2])..")\n"
190
191	-- buildings
192	for i,b in ipairs(Level.buildings) do
193		if not b.len then
194			outputstring = outputstring..Entities.entitystrings[b.id].."("..
195				math.floor(b.pos[1])..","..math.floor(b.pos[2])..")\n"
196		else
197			outputstring = outputstring..Entities.entitystrings[b.id].."("..
198				math.floor(b.pos[1])..","..math.floor(b.pos[2])..","..b.len[2]..")\n"
199		end
200	end
201
202	-- enemies
203	for i,e in ipairs(Level.enemies) do
204		outputstring = outputstring..Entities.entitystrings[e.id].."("..
205			math.floor(e.starting_pos[1])..","..math.floor(e.starting_pos[2])..")\n"
206	end
207
208	return outputstring
209end
210
211function Editor.slottostring(slot)
212	local outputstring = "slotdata("..slot..","
213	for i,entry in ipairs(Editor.slots[slot]) do
214		outputstring = outputstring.."["
215		for j,val in ipairs(entry) do
216			if math.abs(val)<0.0001 and val~=0 then
217				if val>0 then val = -0.0001 else val = 0.0001 end
218			end
219			outputstring=outputstring..val
220			if j~=table.getn(entry) then
221				outputstring=outputstring..","
222			else
223				outputstring=outputstring.."]"
224			end
225		end
226		if i~=table.getn(Editor.slots[slot]) then
227			outputstring=outputstring..","
228		else
229			outputstring=outputstring..")\n"
230		end
231	end
232	return outputstring
233
234end
235
236function Editor.valuestoslot(values)
237	local slot = values[1]
238	table.remove(values,1)
239	Editor.slots[slot] = values
240	Editor.currentslot = slot
241end
242
243function Editor.parselineforslot( st )
244	local punt1 = string.find(st,"%(")
245	local punt2 = string.find(st,"%)")
246	local retlist = {}
247	if punt1~=nil and punt2~=nil and punt1+1<punt2-1 then
248		local aseparar = string.sub(st,punt1+1,punt2-1)
249		table.insert(retlist,string.match(aseparar,"[%.%d]+"))
250		local continuar = true
251		while continuar do
252			local clau1= string.find(aseparar,"%[")
253			local clau2= string.find(aseparar,"%]")
254			continuar = (clau1~=nil and clau2~=nil and clau1+1<clau2-1)
255			if continuar then
256				local appendage = {}
257				local porcio = string.sub(aseparar,clau1+1,clau2-1)
258				for number in string.gmatch(porcio,"[%-%.%d]+") do
259					table.insert(appendage,number+0)
260				end
261				table.insert(retlist,appendage)
262				aseparar=string.sub(aseparar,clau2+1,-1)
263			end
264		end
265	end
266	return retlist
267end
268
269function Editor.parseslotstring( line )
270	local values = Editor.parselineforslot( line )
271	local slot = values[1]
272	table.remove(values,1)
273	Editor.slots[slot] = values
274	Editor.currentslot = slot
275end
276
277function Editor.parselineforid( st )
278	return string.match(st,"[%a_]+")
279end
280
281function Editor.parselinefornumbers( st )
282	local punt1 = string.find(st,"%(")
283	local punt2 = string.find(st,"%)")
284	local valors = {}
285	if punt1~=nil and punt2~=nil and punt1+1<punt2-1 then
286		local aseparar = string.sub(st,punt1+1,punt2-1)
287		for number in string.gmatch(aseparar,"[%.%d]+") do
288			table.insert(valors,number+0)
289		end
290	end
291	return valors
292end
293
294
295function Editor.parsemapstring( st )
296	  -- parse the string into the structure
297		local lines = Split(st,"%c")
298
299		for i,line in ipairs(lines) do
300			-- first find the identifier
301			local identifier = Editor.parselineforid(line)
302			local entityid = Entities.getidfromstring( identifier )
303
304			-- if recognised, insert the element
305			if entityid ~= -1 then
306				local values = Editor.parselinefornumbers(line)
307				if not Entities.hasalength( entityid ) and table.getn(values) == 2 then
308					Entities.add_element( entityid, {values[1], values[2]} )
309				end
310				if Entities.hasalength( entityid ) and table.getn(values) == 3 then
311					Entities.add_element( entityid, {values[1], values[2]}, values[3] )
312				end
313			elseif identifier=="level_options" then
314			-- else, may be slot information or options
315				Editor.stringToLevelOptions( line )
316			end
317		end
318
319end
320
321function Editor.getslotfromtext( st )
322	  -- parse the string into the structure
323		local lines = Split(st,"%c")
324
325		for i,line in ipairs(lines) do
326			local identifier = Editor.parselineforid(line)
327			if identifier=="slotdata" then
328				Editor.parseslotstring( line )
329			end
330		end
331end
332
333function Editor.levelloaddata(data)
334	for i,d in ipairs(data) do
335		Entities.add_element( d[1], d[2], d[3] )
336	end
337end
338
339
340function boolToInt(b)
341	if b then
342		return 1
343	else
344		return 0
345	end
346	return 0
347end
348
349function intToBool(i)
350	if i*1>0 then
351		return true
352	else
353		return false
354	end
355	return false
356end
357
358function Editor.saveslot(slot)
359
360	Editor.slots[slot]={}
361	-- player state
362
363	Editor.slots[slot][1] = {
364		boolToInt(Player.alive),
365		Player.pos[1],Player.pos[2],
366		Player.dir[1],Player.dir[2],
367		Player.key_dir[1],Player.key_dir[2],
368		boolToInt(Player.firing) ,
369		Player.firing_timer,
370		boolToInt(Player.jumping),
371		boolToInt(Player.readytojump),
372		Player.spinning_dir[1],Player.spinning_dir[2],
373		Player.jump_timer,
374		Player.prediction_short[1],Player.prediction_short[2],
375		Player.bullet_pocket,
376		Player.reload_timer,
377		Player.prediction_timer,
378		Player.dir_prediction[1],Player.dir_prediction[2],
379		boolToInt(BulletTime.active),
380		BulletTime.timer,
381		BulletTime.charge_timer,
382		boolToInt(BulletTime.charged),
383		boolToInt(BulletTime.showprogress),
384		boolToInt(Editor.showprogress),
385	}
386
387	for i,enemy in ipairs(Level.enemies) do
388		table.insert( Editor.slots[slot], {
389			enemy.pos[1],enemy.pos[2],
390			enemy.dir[1],enemy.dir[2],
391			enemy.shoot_dir[1], enemy.shoot_dir[2],
392			enemy.target_dir[1], enemy.target_dir[2],
393			enemy.prediction_short[1], enemy.prediction_short[2],
394			enemy.dir_prediction[1], enemy.dir_prediction[2],
395			enemy.state,
396			enemy.former_state,
397			enemy.last_seen_bullet[1], enemy.last_seen_bullet[2],
398			enemy.blocked_timer,
399			enemy.changedir_timer,
400			enemy.dodging_timer,
401			enemy.shooting_timer,
402			enemy.death_timer,
403			enemy.wandering_timer,
404			enemy.suspicion_timer,
405			enemy.scared_timer,
406			enemy.lastplayerpos[1], enemy.lastplayerpos[2],
407			enemy.destination[1], enemy.destination[2],
408			enemy.lastpos[1], enemy.lastpos[2],
409			enemy.see_player_timer,
410			boolToInt(enemy.player_was_seen),
411			enemy.see_bullet_timer,
412			boolToInt(enemy.bullet_was_seen),
413			enemy.ninja_see_player_timer,
414			boolToInt(enemy.ninja_player_was_seen),
415			enemy.prediction_timer,
416		})
417	end
418
419end
420
421function Editor.loadslot(slot)
422	if not Editor.slots[slot] then return end
423	for i,elem in ipairs(Editor.slots[slot]) do
424		if i==1 then -- player
425			Player.alive = intToBool(elem[1])
426			Player.pos = {elem[2], elem[3]}
427			Player.dir = {elem[4], elem[5]}
428			Player.key_dir = {elem[6], elem[7]}
429			Player.firing = intToBool(elem[8])
430			Player.firing_timer = elem[9]
431			Player.jumping = intToBool(elem[10])
432			Player.readytojump = intToBool(elem[11])
433 			Player.spinning_dir = {elem[12],elem[13]}
434 			Player.jump_timer = elem[14]
435			Player.prediction_short = {elem[15],elem[16]}
436			Player.bullet_pocket = elem[17]
437			Player.reload_timer = elem[18]
438			Player.prediction_timer = elem[19]
439			Player.dir_prediction = {elem[20],elem[21]}
440			BulletTime.active = intToBool(elem[22])
441			BulletTime.timer = elem[23]
442			BulletTime.charge_timer = elem[24]
443			BulletTime.charged = intToBool(elem[25])
444			BulletTime.showprogress = intToBool(elem[26])
445			Editor.showprogress = intToBool(elem[27])
446
447		else -- enemy
448			if i-1 > table.getn(Level.enemies) then break end -- some enemies disappeared
449			Level.enemies[i-1].pos = {elem[1],elem[2]}
450			Level.enemies[i-1].dir = {elem[3],elem[4]}
451			Level.enemies[i-1].shoot_dir = {elem[5],elem[6]}
452			Level.enemies[i-1].target_dir = {elem[7],elem[8]}
453			Level.enemies[i-1].prediction_short = {elem[9],elem[10]}
454			Level.enemies[i-1].dir_prediction = {elem[11],elem[12]}
455			Level.enemies[i-1].state = elem[13]
456			Level.enemies[i-1].former_state = elem[14]
457			Level.enemies[i-1].last_seen_bullet = {elem[15], elem[16]}
458			Level.enemies[i-1].blocked_timer = elem[17]
459			Level.enemies[i-1].changedir_timer = elem[18]
460			Level.enemies[i-1].dodging_timer = elem[19]
461			Level.enemies[i-1].shooting_timer = elem[20]
462			Level.enemies[i-1].death_timer = elem[21]
463			Level.enemies[i-1].wandering_timer = elem[22]
464			Level.enemies[i-1].suspicion_timer = elem[23]
465			Level.enemies[i-1].scared_timer = elem[24]
466			Level.enemies[i-1].lastplayerpos = {elem[25], elem[26]}
467			Level.enemies[i-1].destination = {elem[27], elem[28]}
468			Level.enemies[i-1].lastpos = {elem[29], elem[30]}
469			Level.enemies[i-1].see_player_timer = elem[31]
470			Level.enemies[i-1].player_was_seen = intToBool(elem[32])
471			Level.enemies[i-1].see_bullet_timer = elem[33]
472			Level.enemies[i-1].bullet_was_seen = intToBool(elem[34])
473			Level.enemies[i-1].ninja_see_player_timer = elem[35]
474			Level.enemies[i-1].ninja_player_was_seen = intToBool(elem[36])
475			Level.enemies[i-1].prediction_timer = elem[37]
476		end
477	end
478end
479
480function Editor.levelOptionsToString()
481	local st = "level_options("
482	st = st .. boolToInt(Level.enemylessmode)..","
483	st = st .. boolToInt(Level.ninjamode)..","
484	st = st .. boolToInt(Level.enemiescanshoot)..","
485	st = st .. boolToInt(Level.autoturns)..","
486	st = st .. boolToInt(Level.onebullet)..")\n"
487	return st
488end
489
490function Editor.stringToLevelOptions( st )
491	local openparenthesis = string.find(st,"%(")
492	if not openparenthesis then return end
493	local tabl={}
494	for number in string.gmatch( string.sub(st,openparenthesis+1),"[%.%d]+") do
495		table.insert(tabl,number)
496	end
497	if table.getn(tabl)>=5 then
498		Level.enemylessmode = intToBool( tabl[1] )
499		Level.ninjamode = intToBool( tabl[2] )
500		Level.enemiescanshoot = intToBool( tabl[3] )
501		Level.autoturns = intToBool( tabl[4] )
502		Level.onebullet = intToBool( tabl[5] )
503	end
504end
505
506function Editor.scandir(dirname)
507        local tempname = os.tmpname()
508
509		if os.getenv("OS")=="Windows_NT" then
510			os.execute("dir /b "..dirname .. " >"..tempname.." 2>NUL")
511        else
512			os.execute("ls -a1 "..dirname .. " >"..tempname.." 2>/dev/null")
513		end
514		local f = io.open(tempname,"r")
515        local contents = f:read("*all")
516        f:close()
517        os.remove(tempname)
518
519        local tabby = {}
520          local from  = 1
521          local delim_from, delim_to = string.find( contents, "\n", from  )
522          while delim_from do
523                    table.insert( tabby, string.sub( contents, from , delim_from-1 ) )
524                    from  = delim_to + 1
525                    delim_from, delim_to = string.find( contents, "\n", from  )
526                  end
527        -- Comment out eliminates blank line on end!
528          return tabby
529        end
530
531function Editor.refreshList()
532-- scan directory
533	local extension=".gfl"
534	local listing = Editor.scandir( "\""..love.filesystem.getSaveDirectory( ).."\"" )
535	local results = ""
536
537	-- find files with wanted extension
538	for i,line in ipairs(listing) do
539		local puntpoint = string.find(line, extension)
540		if  puntpoint then
541			results = results..string.sub(line,1,puntpoint-1).."\n"
542		end
543	end
544
545	Editor.saveinFile( Editor.listingfilename, results )
546
547
548end
549
550function Editor.getFileList()
551	local filelist={}
552	local d = Editor.loadfromFile( Editor.listingfilename )
553
554		filelist = Split(d, "\n")
555
556		for i,l in ipairs(filelist) do
557			if string.len(l)==0 then
558				table.remove(filelist,i)
559			end
560		end
561
562	return filelist
563end
564
565function Editor.addEntryInFileList(entry)
566	local data = Editor.loadfromFile( Editor.listingfilename )
567
568	filelist = Split(data, "\n")
569	for i,v in ipairs(filelist) do
570		if v==entry then
571			return  -- break! the file was already there
572		end
573	end
574
575	data = data..entry.."\n"
576	Editor.saveinFile(Editor.listingfilename, data )
577end
578
579function Editor.removeEntryFromfilelist( entry )
580	local data = Editor.loadfromFile( Editor.listingfilename )
581	filelist = Split(data, "\n")
582	for i,v in ipairs(filelist) do
583		if v==entry then
584			table.remove(filelist,i)
585		end
586	end
587
588	data = ""
589	for i,v in ipairs(filelist) do
590		if string.len(v)>0 then data = data..v.."\n" end
591	end
592
593	Editor.saveinFile( Editor.listingfilename, data )
594end
595
596function Editor.deletefile( filename )
597
598	if love.filesystem.remove( filename..".gfl" ) then
599		Editor.removeEntryFromfilelist( filename )
600		Editor.currentfilename = ""
601		Editor.listingtable = Editor.getFileList()
602		if Editor.listingoffset+Editor.listinglength > table.getn(Editor.listingtable) and
603			Editor.listingoffset > 0 then
604			Editor.listingoffset = Editor.listingoffset - 1
605		end
606	end
607end
608