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