main.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. --[========================================================================[--
  2. Main file for Thrust II Reloaded.
  3. Copyright © 2015-2018 Pedro Gimeno Fortea
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. --]========================================================================]--
  20. -- This needs to be here because some versions don't use conf.lua
  21. love_version = love._version_major
  22. and love._version_major * 1000000
  23. + love._version_minor * 1000
  24. + love._version_revision
  25. or 0
  26. if love.default_moose then
  27. -- Version 0.2.1 doesn't close the window - force it to.
  28. -- Not the cleanest way but with no docs, that's the best
  29. -- we could do.
  30. function load()
  31. assert(love_version >= 0009001,
  32. "This game requires love2d version 0.9.1 or greater")
  33. end
  34. function draw() os.exit(1) end
  35. return
  36. end
  37. assert(love_version >= 0009000,
  38. "This game requires love2d version 0.9.1 or greater")
  39. -- For performance and licensing reasons, strict.lua is only run by the
  40. -- developer, not packaged. Include it in the strict/ directory if you want
  41. -- to develop using it.
  42. -- Must be activated after the assert, or 0.6.x crashes during error reporting
  43. pcall(require, 'strict.strict')
  44. local la,le,lfs,lf,lg,li,lj,lk,lm,lmo,lp,ls,lsys,lth,lt,lw = require'ns'()
  45. main = {}
  46. local active, canvas
  47. function main.activate(new)
  48. if active and active.deactivate then active.deactivate() end
  49. active = new
  50. if active.activate then active.activate() end
  51. end
  52. local DIV = love_version >= 11000000 and 255 or 1
  53. -- screens
  54. screens = {
  55. splash = require 'splash';
  56. menu = require 'menu';
  57. redef = require 'redef';
  58. getready = require 'getready';
  59. game = require 'game';
  60. gameover = require 'gameover';
  61. gamewon = require 'gamewon';
  62. orbexplode = require 'orbexplode';
  63. }
  64. -- modules that are not screens
  65. main.map,
  66. main.enemytypes,
  67. main.enemies,
  68. main.orbs,
  69. main.decoys,
  70. main.targets,
  71. main.respawn,
  72. main.agents = require 'layout' ()
  73. function main.setVolume(vol, save)
  74. vol = (vol < 0) and 0 or vol
  75. vol = (vol > 1) and 1 or vol
  76. la.setVolume(vol^2)
  77. main.vol = vol
  78. if save then
  79. local tmp = main.vol .. (main.music and "y" or "n")
  80. lfs.write("vol.txt", tmp, #tmp)
  81. end
  82. end
  83. local wparamtable = {fullscreen=true,vsync=love_version >= 11000000 and 0,
  84. resizable=true,fullscreentype="desktop"}
  85. function main.saveScreen()
  86. local tmp = string.format("%.17g\n%s\n%d\n%d\n",
  87. main.pixelsize,
  88. main.fullscreen and "y" or "n",
  89. main.wwNFS, main.whNFS)
  90. lfs.write("screen.txt", tmp, #tmp)
  91. end
  92. function main.setMode()
  93. wparamtable.fullscreen = main.fullscreen
  94. lw.setMode(main.wwNFS, main.whNFS, wparamtable)
  95. lw.setTitle("Thrust II reloaded")
  96. main.saveScreen()
  97. end
  98. function main.tomenu()
  99. main.activate(screens.menu)
  100. end
  101. -- deepcopy from http://lua-users.org/wiki/CopyTable
  102. -- minus the parts that we don't need
  103. function main.deepcopy(orig)
  104. local orig_type = type(orig)
  105. local copy
  106. if orig_type == 'table' then
  107. copy = {}
  108. for orig_key, orig_value in next, orig, nil do
  109. -- copy[deepcopy(orig_key)] = deepcopy(orig_value)
  110. copy[orig_key] = main.deepcopy(orig_value)
  111. end
  112. -- setmetatable(copy, deepcopy(getmetatable(orig)))
  113. else -- number, string, boolean, etc
  114. copy = orig
  115. end
  116. return copy
  117. end
  118. if love_version >= 11000000 then
  119. local info = {type = false, size = false, modtime = false}
  120. function main.isFile(f)
  121. return lfs.getInfo(f, info) ~= nil
  122. and (info.type == "file" or info.type == "symlink")
  123. end
  124. else
  125. function main.isFile(f)
  126. return lfs.isFile(f)
  127. end
  128. end
  129. function main.drawaligned(alignment, drawable, x, y, angle, xzoom, yzoom)
  130. -- alignment's value works as a numeric keypad ("normal" is 7):
  131. --
  132. -- 7 8 9
  133. -- 4 5 6
  134. -- 1 2 3
  135. if type(drawable) == "userdata" and drawable.typeOf and drawable:typeOf("Drawable") then
  136. -- it's a drawable
  137. local iw, ih = drawable:getDimensions()
  138. local ox, oy = 0, 0
  139. if alignment <= 3 then
  140. oy = ih
  141. elseif alignment <= 6 then
  142. oy = ih / 2
  143. end
  144. if alignment % 3 == 0 then
  145. ox = iw
  146. elseif alignment % 3 == 2 then
  147. ox = iw / 2
  148. end
  149. return lg.draw(drawable, math.floor(x), math.floor(y), angle, xzoom, yzoom, ox, oy)
  150. end
  151. end
  152. function love.load(args)
  153. lfs.setIdentity("ThrustIIreloaded")
  154. main.argv = args
  155. main.pixelsize = 1
  156. main.fullscreen = false
  157. main.wwNFS = 640 -- non-fullscreen width
  158. main.whNFS = 480 -- non-fullscreen height
  159. if main.isFile("screen.txt") then
  160. local lines = lfs.read("screen.txt")
  161. local pix, fs, ww, wh = lines:match("^(%d+%.?%d*[Ee]?%d*)\n([yn]?)\n(%d+)\n(%d+)\n")
  162. main.pixelsize = tonumber(pix)
  163. main.fullscreen = fs == "y"
  164. main.wwNFS = tonumber(ww)
  165. main.whNFS = tonumber(wh)
  166. else
  167. main.saveScreen()
  168. end
  169. main.setMode()
  170. lg.setBackgroundColor(0,0,0,0)
  171. lg.setDefaultFilter("nearest", "nearest")
  172. main.ww = math.ceil(lg.getWidth()/main.pixelsize)
  173. main.wh = math.ceil(lg.getHeight()/main.pixelsize)
  174. main.wcx, main.wcy = main.ww/2, main.wh/2
  175. canvas = lg.newCanvas(main.ww, main.wh)
  176. main.font = lg.newImageFont("img/Font16x16.png",
  177. " !\"#$%&'()*+,-./0123456789:;<=>?\127ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~@",
  178. 1) -- required for 0.10 compatibility, ignored by 0.9
  179. lg.setFont(main.font)
  180. local space = love._version_major == 0 and love._version_minor < 10 and " " or "space"
  181. main.keys =
  182. { left = "q", right = "w", thrust = "p", pickup = "l", fire = space }
  183. if main.isFile("keys.txt") then
  184. local line = 0
  185. for s in lfs.lines("keys.txt") do
  186. if s == " " or s == "space" then s = space end -- 0.10 compat
  187. line = line + 1
  188. if line == 1 then
  189. main.keys.left = s
  190. elseif line == 2 then
  191. main.keys.right = s
  192. elseif line == 3 then
  193. main.keys.thrust = s
  194. elseif line == 4 then
  195. main.keys.pickup = s
  196. elseif line == 5 then
  197. main.keys.fire = s
  198. else
  199. break
  200. end
  201. end
  202. else
  203. local s = main.keys.left .. "\n"
  204. .. main.keys.right .. "\n"
  205. .. main.keys.thrust .. "\n"
  206. .. main.keys.pickup .. "\n"
  207. .. main.keys.fire
  208. lfs.write("keys.txt", s, #s)
  209. end
  210. main.vol = 1
  211. main.music = true
  212. if main.isFile("vol.txt") then
  213. local line = lfs.read("vol.txt")
  214. local vol, music = line:match("^(%d+%.?%d*[Ee]?%d*)([yn]?)$")
  215. main.setVolume(tonumber(vol))
  216. main.music = music == "y"
  217. else
  218. main.setVolume(main.vol, true)
  219. end
  220. for k, mod in next, screens do
  221. if mod and mod.load then mod.load(arg) end
  222. end
  223. -- Bootstrap activation
  224. main.activate(screens.splash)
  225. -- Allow volume change with auto-repeat
  226. lk.setKeyRepeat(true)
  227. main.singlestep = false
  228. main.savedelay = false
  229. main.quitaccepted = false
  230. -- Dialog data
  231. main.dialogdata = {}
  232. main.dialogdata.waitrelease = false
  233. main.dialogactive = false
  234. end
  235. -- HACK: rather than altering love.run, we alter love.timer.sleep.
  236. -- This guarantees that it's run between lg.present and fetching
  237. -- events, giving maximum responsivity.
  238. main.sleep = lt.sleep
  239. main.lastTime = love.timer.getTime()
  240. function lt.sleep(t)
  241. main.sleep(main.lastTime + 0.02 - love.timer.getTime())
  242. main.lastTime = love.timer.getTime()
  243. end
  244. function love.update(dt)
  245. if main.savedelay then
  246. main.savedelay = main.savedelay - dt
  247. if main.savedelay <= 0 then
  248. main.savedelay = false
  249. end
  250. end
  251. if main.paused and not main.singlestep or main.dialogactive then return end
  252. if active.update then active.update(main.singlestep and 0.03125 or dt) end
  253. end
  254. local function roundrect(x1, y1, w, h, r)
  255. local nsegments = 8
  256. local x2, y2 = x1+w, y1+h
  257. local npoints = nsegments + 1 -- points per corner
  258. local poly = {}
  259. poly[1] = x2
  260. poly[2] = y2 - r
  261. poly[npoints*2-1] = x2 - r
  262. poly[npoints*2] = y2
  263. poly[npoints*2+1] = x1 + r
  264. poly[npoints*2+2] = y2
  265. poly[npoints*4-1] = x1
  266. poly[npoints*4] = y2 - r
  267. poly[npoints*4+1] = x1
  268. poly[npoints*4+2] = y1 + r
  269. poly[npoints*6-1] = x1 + r
  270. poly[npoints*6] = y1
  271. poly[npoints*6+1] = x2 - r
  272. poly[npoints*6+2] = y1
  273. poly[npoints*8-1] = x2
  274. poly[npoints*8] = y1 + r
  275. local angle, x, y
  276. for i = 2, (nsegments-1)*2, 2 do
  277. angle = math.pi * 0.25 * (i / nsegments)
  278. x = math.cos(angle) * r
  279. y = math.sin(angle) * r
  280. poly[i+1] = x2 - r + x
  281. poly[i+2] = y2 - r + y
  282. poly[npoints*4-i-1] = x1 + r - x
  283. poly[npoints*4-i] = y2 - r + y
  284. poly[npoints*4+i+1] = x1 + r - x
  285. poly[npoints*4+i+2] = y1 + r - y
  286. poly[npoints*8-i-1] = x2 - r + x
  287. poly[npoints*8-i] = y1 + r - y
  288. end
  289. lg.polygon("fill", poly)
  290. end
  291. function main.centertext(text, x, y)
  292. lg.print(text, math.floor(x-main.font:getWidth(text)/2), math.floor(y-main.font:getHeight()/2))
  293. end
  294. local function dummyfunction()
  295. end
  296. -- Show a modal yes/no dialog
  297. function main.dialog(text, cbyes, cbno)
  298. main.dialogdata.text = text
  299. main.dialogdata.cbyes = cbyes or dummyfunction
  300. main.dialogdata.cbno = cbno or dummyfunction
  301. main.dialogdata.waitrelease = false
  302. main.dialogactive = true
  303. -- If we're not in pause mode, pause now anyway since we're modal.
  304. if not main.paused and active.pause then active.pause(true) end
  305. end
  306. local function drawActive()
  307. -- HACK: Allow scroll-out in get ready screen, by not clearing the canvas
  308. if active ~= screens.getready then
  309. lg.clear()
  310. end
  311. active.draw()
  312. end
  313. function love.draw()
  314. lg.scale(main.pixelsize)
  315. if main.dialogactive then
  316. lg.setColor(85/DIV, 85/DIV, 85/DIV, 255/DIV)
  317. lg.rectangle("fill", 0, 0, main.ww, main.wh)
  318. lg.setColor(255/DIV, 255/DIV, 255/DIV, 120/DIV)
  319. lg.draw(canvas)
  320. lg.setColor(0/DIV, 0/DIV, 0/DIV, 200/DIV)
  321. local rectw = main.font:getWidth(main.dialogdata.text) + 68
  322. roundrect(main.wcx-rectw*0.5, main.wcy-50, rectw, 100, 7)
  323. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  324. main.centertext(main.dialogdata.text, main.wcx, main.wcy - 20)
  325. lg.setColor(160/DIV,160/DIV,160/DIV)
  326. roundrect(main.wcx-70, main.wcy+4, 60, 24, 4)
  327. roundrect(main.wcx+10, main.wcy+4, 60, 24, 4)
  328. lg.setColor(0/DIV, 0/DIV, 0/DIV, 255/DIV)
  329. main.centertext("YES", main.wcx-40, main.wcy+16)
  330. main.centertext("NO", main.wcx+40, main.wcy+16)
  331. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  332. else
  333. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  334. if main.paused and not main.singlestep then
  335. lg.draw(canvas)
  336. lg.setColor(64/DIV, 64/DIV, 64/DIV, 150/DIV)
  337. roundrect(main.wcx-60, main.wh-44, 120, 40, 6)
  338. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  339. main.centertext("PAUSED", main.wcx, main.wh - 24)
  340. else
  341. main.canvas = canvas
  342. if active.draw then
  343. lg.origin()
  344. canvas:renderTo(drawActive)
  345. lg.scale(main.pixelsize)
  346. end
  347. lg.draw(canvas)
  348. end
  349. if main.savedelay then
  350. lg.setColor(64/DIV, 64/DIV, 64/DIV, 200/DIV)
  351. roundrect(main.wcx-60, main.wh-44, 120, 40, 6)
  352. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  353. main.centertext("SAVED", main.wcx, main.wh - 24)
  354. end
  355. end
  356. if main.singlestep then main.singlestep = false end
  357. lg.origin()
  358. end
  359. local function quit_yes()
  360. main.quitaccepted = true
  361. le.quit()
  362. end
  363. function love.quit()
  364. if not main.quitaccepted then
  365. main.dialog("REALLY QUIT?", quit_yes)
  366. return true -- don't quit
  367. end
  368. end
  369. function love.mousepressed(x, y, b)
  370. x = x / main.pixelsize
  371. y = y / main.pixelsize
  372. if main.dialogactive then
  373. if x >= main.wcx-70 and x <= main.wcx-10
  374. and y >= main.wcy+4 and y <= main.wcy+28
  375. then
  376. main.dialogdata.cbyes()
  377. main.dialogactive = false
  378. elseif x >= main.wcx+10 and x <= main.wcx+70
  379. and y >= main.wcy+4 and y <= main.wcy+28
  380. then
  381. main.dialogdata.cbno()
  382. main.dialogactive = false
  383. end
  384. -- If we were not in pause mode then we paused because of the menu.
  385. -- If that's the case and we're closing the dialog, unpause now.
  386. if not main.dialogactive and not main.paused and active.pause then
  387. active.pause(false)
  388. end
  389. end
  390. end
  391. function love.keypressed(k, r, rr)
  392. -- 0.10+ compat
  393. if rr ~= nil then r = rr end
  394. if k == "kp-" or k == "kp+" then
  395. main.setVolume(main.vol + (k == "kp+" and 0.0625 or -0.0625), true)
  396. elseif r then
  397. -- nothing
  398. elseif main.dialogactive then
  399. if k == "y" then
  400. main.dialogdata.cbyes()
  401. main.dialogdata.waitrelease = true
  402. elseif k == "n" or k == "escape" then
  403. main.dialogdata.cbno()
  404. main.dialogdata.waitrelease = true
  405. end
  406. elseif k == "pause" and not main.dialogactive then
  407. main.paused = not main.paused
  408. if active.pause then active.pause(main.paused) end
  409. elseif active.keypressed and not main.paused then
  410. active.keypressed(k, r)
  411. end
  412. --[[ debug ]]
  413. --if k == "1" and main.paused then main.singlestep = true end
  414. --]]
  415. end
  416. function love.textinput(c)
  417. if not main.paused then
  418. if active.textinput then active.textinput(c) end
  419. end
  420. end
  421. function love.keyreleased(k)
  422. if main.dialogactive then
  423. if main.dialogdata.waitrelease then
  424. main.dialogdata.waitrelease = false
  425. main.dialogactive = false
  426. if not main.paused and active.pause then active.pause(false) end
  427. end
  428. elseif k ~= "pause" and not main.paused then
  429. if active.keyreleased then active.keyreleased(k) end
  430. end
  431. end
  432. function love.resize(neww, newh)
  433. main.ww = math.ceil(neww/main.pixelsize)
  434. main.wh = math.ceil(newh/main.pixelsize)
  435. main.wcx = main.ww/2
  436. main.wcy = main.wh/2
  437. local newcanvas = lg.newCanvas(main.ww, main.wh)
  438. local saveCanvas = lg.getCanvas()
  439. lg.setCanvas(newcanvas)
  440. main.drawaligned(5, canvas, main.wcx, main.wcy)
  441. lg.setCanvas(saveCanvas)
  442. canvas = newcanvas
  443. if not main.fullscreen then
  444. main.wwNFS = neww
  445. main.whNFS = newh
  446. main.saveScreen()
  447. end
  448. for k, mod in next, screens do
  449. if mod.resize then
  450. mod.resize(neww, newh)
  451. end
  452. end
  453. end