main.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. --[========================================================================[--
  2. Main file for Thrust II Reloaded.
  3. Copyright © 2015-2017 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. -- For performance and licensing reasons, strict.lua is only run by the
  21. -- developer, not packaged. Include it in the strict/ directory if you want
  22. -- to develop using it.
  23. pcall(function() require 'strict.strict' end)
  24. require('lovespaces')
  25. local volscale = 40
  26. local volgrabbed = false
  27. main = {}
  28. local active, canvas
  29. function main.activate(new)
  30. if active and active.deactivate then active.deactivate() end
  31. active = new
  32. if active.activate then active.activate() end
  33. end
  34. -- screens
  35. screens = {
  36. splash = require 'splash';
  37. menu = require 'menu';
  38. redef = require 'redef';
  39. getready = require 'getready';
  40. game = require 'game';
  41. gameover = require 'gameover';
  42. gamewon = require 'gamewon';
  43. orbexplode = require 'orbexplode';
  44. }
  45. -- modules that are not screens
  46. main.map,
  47. main.enemytypes,
  48. main.enemies,
  49. main.orbs,
  50. main.decoys,
  51. main.targets,
  52. main.respawn,
  53. main.agents = require 'layout' ()
  54. function main.setVolume(vol, save)
  55. vol = (vol < 0) and 0 or vol
  56. vol = (vol > 1) and 1 or vol
  57. la.setVolume(vol^2)
  58. main.vol = vol
  59. if save then
  60. local tmp = main.vol .. (main.music and "y" or "n")
  61. lfs.write("vol.txt", tmp, #tmp)
  62. end
  63. end
  64. local wparamtable = {fullscreen=true,vsync=false,resizable=true,fullscreentype="desktop"}
  65. function main.saveScreen()
  66. local tmp = string.format("%.17g\n%s\n%d\n%d\n",
  67. main.pixelsize,
  68. main.fullscreen and "y" or "n",
  69. main.wwNFS, main.whNFS)
  70. lfs.write("screen.txt", tmp, #tmp)
  71. end
  72. function main.setMode()
  73. wparamtable.fullscreen = main.fullscreen
  74. lw.setMode(main.wwNFS, main.whNFS, wparamtable)
  75. lw.setTitle("Thrust II reloaded")
  76. main.saveScreen()
  77. end
  78. -- deepcopy from http://lua-users.org/wiki/CopyTable
  79. -- minus the parts that we don't need
  80. function main.deepcopy(orig)
  81. local orig_type = type(orig)
  82. local copy
  83. if orig_type == 'table' then
  84. copy = {}
  85. for orig_key, orig_value in next, orig, nil do
  86. -- copy[deepcopy(orig_key)] = deepcopy(orig_value)
  87. copy[orig_key] = main.deepcopy(orig_value)
  88. end
  89. -- setmetatable(copy, deepcopy(getmetatable(orig)))
  90. else -- number, string, boolean, etc
  91. copy = orig
  92. end
  93. return copy
  94. end
  95. function main.drawaligned(alignment, drawable, x, y, angle, xzoom, yzoom)
  96. -- alignment's value works as a numeric keypad ("normal" is 7):
  97. --
  98. -- 7 8 9
  99. -- 4 5 6
  100. -- 1 2 3
  101. if type(drawable) == "userdata" and drawable.typeOf and drawable:typeOf("Drawable") then
  102. -- it's a drawable
  103. local iw, ih = drawable:getDimensions()
  104. local ox, oy = 0, 0
  105. if alignment <= 3 then
  106. oy = ih
  107. elseif alignment <= 6 then
  108. oy = ih / 2
  109. end
  110. if alignment % 3 == 0 then
  111. ox = iw
  112. elseif alignment % 3 == 2 then
  113. ox = iw / 2
  114. end
  115. return lg.draw(drawable, math.floor(x), math.floor(y), angle, xzoom, yzoom, ox, oy)
  116. end
  117. end
  118. function love.load(args)
  119. lfs.setIdentity("ThrustIIreloaded")
  120. main.argv = args
  121. main.pixelsize = 1
  122. main.fullscreen = false
  123. main.wwNFS = 640 -- non-fullscreen width
  124. main.whNFS = 480 -- non-fullscreen height
  125. if lfs.isFile("screen.txt") then
  126. local lines = lfs.read("screen.txt")
  127. local pix, fs, ww, wh = lines:match("^(%d+%.?%d*[Ee]?%d*)\n([yn]?)\n(%d+)\n(%d+)\n")
  128. main.pixelsize = tonumber(pix)
  129. main.fullscreen = fs == "y"
  130. main.wwNFS = tonumber(ww)
  131. main.whNFS = tonumber(wh)
  132. else
  133. main.saveScreen()
  134. end
  135. main.setMode()
  136. lg.setDefaultFilter("nearest", "nearest")
  137. main.ww = math.ceil(lg.getWidth()/main.pixelsize)
  138. main.wh = math.ceil(lg.getHeight()/main.pixelsize)
  139. main.wcx, main.wcy = main.ww/2, main.wh/2
  140. canvas = lg.newCanvas(main.ww, main.wh)
  141. main.font = lg.newImageFont("img/Font16x16.png",
  142. " !\"#$%&'()*+,-./0123456789:;<=>?\127ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~@",
  143. 1) -- required for 0.10 compatibility, ignored by 0.9
  144. lg.setFont(main.font)
  145. local space = love._version_major == 0 and love._version_minor < 10 and " " or "space"
  146. main.keys =
  147. { left = "q", right = "w", thrust = "p", pickup = "l", fire = space }
  148. if lfs.isFile("keys.txt") then
  149. local line = 0
  150. for s in lfs.lines("keys.txt") do
  151. if s == " " or s == "space" then s = space end -- 0.10 compat
  152. line = line + 1
  153. if line == 1 then
  154. main.keys.left = s
  155. elseif line == 2 then
  156. main.keys.right = s
  157. elseif line == 3 then
  158. main.keys.thrust = s
  159. elseif line == 4 then
  160. main.keys.pickup = s
  161. elseif line == 5 then
  162. main.keys.fire = s
  163. else
  164. break
  165. end
  166. end
  167. else
  168. local s = main.keys.left .. "\n"
  169. .. main.keys.right .. "\n"
  170. .. main.keys.thrust .. "\n"
  171. .. main.keys.pickup .. "\n"
  172. .. main.keys.fire
  173. lfs.write("keys.txt", s, #s)
  174. end
  175. main.vol = 0.5
  176. main.music = true
  177. if lfs.isFile("vol.txt") then
  178. local line = lfs.read("vol.txt")
  179. local vol, music = line:match("^(%d+%.?%d*[Ee]?%d*)([yn]?)$")
  180. main.setVolume(tonumber(vol))
  181. main.music = music == "y"
  182. else
  183. main.setVolume(main.vol, true)
  184. end
  185. for k, mod in next, screens do
  186. if mod and mod.load then mod.load(arg) end
  187. end
  188. -- Bootstrap activation
  189. main.activate(screens.splash)
  190. -- Allow volume change with auto-repeat
  191. lk.setKeyRepeat(true)
  192. -- Used to end quit status on key release. Otherwise the key up event
  193. -- that led to releasing the quit status is registered.
  194. main.dontquit = false
  195. main.unpause = false
  196. main.singlestep = false
  197. main.savedelay = false
  198. end
  199. -- HACK: rather than altering love.run, we alter love.timer.sleep.
  200. -- This guarantees that it's run between lg.present and fetching
  201. -- events, giving maximum responsivity.
  202. do
  203. local ltisleep = love.timer.sleep
  204. function love.timer.sleep(t)
  205. ltisleep(0.02)
  206. end
  207. end
  208. function love.update(dt)
  209. if volgrabbed then
  210. main.setVolume((lmo.getX() / main.pixelsize - (main.wcx + 108)) / volscale)
  211. end
  212. if main.savedelay then
  213. main.savedelay = main.savedelay - dt
  214. if main.savedelay <= 0 then
  215. main.savedelay = false
  216. end
  217. end
  218. if main.paused and not main.singlestep or main.quitrequest then return end
  219. if active.update then active.update(main.singlestep and 0.03125 or dt) end
  220. end
  221. local function roundrect(x1, y1, w, h, r)
  222. local nsegments = 8
  223. local x2, y2 = x1+w, y1+h
  224. local npoints = nsegments + 1 -- points per corner
  225. local poly = {}
  226. poly[1] = x2
  227. poly[2] = y2 - r
  228. poly[npoints*2-1] = x2 - r
  229. poly[npoints*2] = y2
  230. poly[npoints*2+1] = x1 + r
  231. poly[npoints*2+2] = y2
  232. poly[npoints*4-1] = x1
  233. poly[npoints*4] = y2 - r
  234. poly[npoints*4+1] = x1
  235. poly[npoints*4+2] = y1 + r
  236. poly[npoints*6-1] = x1 + r
  237. poly[npoints*6] = y1
  238. poly[npoints*6+1] = x2 - r
  239. poly[npoints*6+2] = y1
  240. poly[npoints*8-1] = x2
  241. poly[npoints*8] = y1 + r
  242. local angle, x, y
  243. for i = 2, (nsegments-1)*2, 2 do
  244. angle = math.pi * 0.25 * (i / nsegments)
  245. x = math.cos(angle) * r
  246. y = math.sin(angle) * r
  247. poly[i+1] = x2 - r + x
  248. poly[i+2] = y2 - r + y
  249. poly[npoints*4-i-1] = x1 + r - x
  250. poly[npoints*4-i] = y2 - r + y
  251. poly[npoints*4+i+1] = x1 + r - x
  252. poly[npoints*4+i+2] = y1 + r - y
  253. poly[npoints*8-i-1] = x2 - r + x
  254. poly[npoints*8-i] = y1 + r - y
  255. end
  256. lg.polygon("fill", poly)
  257. end
  258. function main.centertext(text, x, y)
  259. lg.print(text, math.floor(x-main.font:getWidth(text)/2), math.floor(y-main.font:getHeight()/2))
  260. end
  261. local function drawActive()
  262. -- HACK: Allow scroll-out in get ready screen, by not clearing the canvas
  263. if active ~= screens.getready then
  264. lg.clear()
  265. end
  266. active.draw()
  267. end
  268. local function volumeKnobStencil()
  269. lg.setColor(255,255,255,255)
  270. lg.rectangle("fill", main.wcx + 103 + main.vol*volscale, main.wh-32, 10, 24)
  271. end
  272. function love.draw()
  273. if main.quitrequest then
  274. lg.push()
  275. lg.scale(main.pixelsize)
  276. lg.setColor(85, 85, 85, 255)
  277. lg.rectangle("fill", 0, 0, main.ww, main.wh)
  278. lg.setColor(255, 255, 255, 120)
  279. lg.draw(canvas)
  280. lg.setColor(0, 0, 0, 200)
  281. roundrect(main.wcx-130, main.wcy-50, 260, 100, 7)
  282. lg.setColor(255, 255, 255, 255)
  283. main.centertext("REALLY QUIT?", main.wcx, main.wcy - 20)
  284. lg.setColor(160,160,160)
  285. roundrect(main.wcx-70, main.wcy+4, 60, 24, 4)
  286. roundrect(main.wcx+10, main.wcy+4, 60, 24, 4)
  287. lg.setColor(0, 0, 0, 255)
  288. main.centertext("YES", main.wcx-40, main.wcy+16)
  289. main.centertext("NO", main.wcx+40, main.wcy+16)
  290. lg.setColor(255, 255, 255, 255)
  291. lg.pop()
  292. else
  293. lg.setColor(255, 255, 255, 255)
  294. if main.paused and not main.singlestep then
  295. lg.push()
  296. lg.scale(main.pixelsize)
  297. lg.draw(canvas)
  298. lg.setColor(64, 64, 64, 150)
  299. roundrect(main.wcx-60, main.wh-44, 120, 40, 6)
  300. lg.setColor(255, 255, 255, 255)
  301. main.centertext("PAUSED", main.wcx, main.wh - 24)
  302. lg.pop()
  303. else
  304. main.canvas = canvas
  305. if active.draw then
  306. canvas:renderTo(drawActive)
  307. end
  308. lg.push()
  309. lg.scale(main.pixelsize)
  310. lg.draw(canvas)
  311. lg.pop()
  312. end
  313. if main.savedelay then
  314. lg.push()
  315. lg.scale(main.pixelsize)
  316. lg.setColor(64, 64, 64, 200)
  317. roundrect(main.wcx-60, main.wh-44, 120, 40, 6)
  318. lg.setColor(255, 255, 255, 255)
  319. main.centertext("SAVED", main.wcx, main.wh - 24)
  320. lg.pop()
  321. end
  322. end
  323. -- volume control
  324. lg.push()
  325. lg.scale(main.pixelsize)
  326. local half = main.wcx
  327. local volx = main.vol*volscale
  328. if lg.stencil then
  329. -- 0.10 update
  330. lg.stencil(volumeKnobStencil)
  331. lg.setStencilTest("equal", 0) -- Inverted stencil
  332. else
  333. lg.setInvertedStencil(volumeKnobStencil)
  334. end
  335. lg.setColor(0,0,0, 40)
  336. lg.polygon("fill", half + 92, main.wh - 11,
  337. half + 155, main.wh - 11,
  338. half + 155, main.wh - 29)
  339. lg.setColor(255, 255, 255, 60)
  340. lg.polygon("fill", half + 100, main.wh - 12,
  341. half + 154, main.wh - 12,
  342. half + 154, main.wh - 28)
  343. if lg.setStencilTest then
  344. lg.setStencilTest()
  345. else
  346. lg.setStencil()
  347. end
  348. lg.setColor(0, 0, 0, 40)
  349. lg.rectangle("fill", half + 103 + volx, main.wh-32, 10, 24)
  350. lg.setColor(255, 255, 255, 70)
  351. lg.rectangle("fill", half + 104 + volx, main.wh-31, 8, 22)
  352. lg.setColor(255, 255, 255)
  353. if main.singlestep then main.singlestep = false end
  354. lg.pop()
  355. end
  356. function love.quit()
  357. if not main.quitaccepted then
  358. main.quitrequest = true
  359. main.dontquit = false
  360. if not main.paused and active.pause then active.pause(true) end
  361. return true -- don't quit
  362. end
  363. end
  364. function love.mousepressed(x, y, b)
  365. x = x / main.pixelsize
  366. y = y / main.pixelsize
  367. if main.quitrequest then
  368. if x >= main.wcx-70 and x <= main.wcx-10
  369. and y >= main.wcy+4 and y <= main.wcy+28
  370. then
  371. main.quitaccepted = true
  372. le.quit()
  373. elseif x >= main.wcx+10 and x <= main.wcx+70
  374. and y >= main.wcy+4 and y <= main.wcy+28
  375. then
  376. main.quitrequest = false
  377. if not main.paused and active.pause then active.pause(false) end
  378. end
  379. end
  380. if y >= main.wh-32 and y <= main.wh-8
  381. and x >= main.wcx + 104 + main.vol*volscale
  382. and x <= main.wcx + 112 + main.vol*volscale
  383. then
  384. lmo.setGrabbed(true)
  385. volgrabbed = true
  386. main.setVolume((x - (main.wcx + 108)) / volscale)
  387. end
  388. end
  389. --[[ (not in 0.9.1 - handled in love.update())
  390. function love.mousemoved(x, y, dx, dy)
  391. if volgrabbed then
  392. main.setVolume((x - (main.wcx + 108)) / volscale)
  393. end
  394. end
  395. ]]
  396. function love.mousereleased(x, y, b)
  397. if volgrabbed then
  398. lmo.setGrabbed(false)
  399. volgrabbed = false
  400. main.setVolume((x / main.pixelsize - (main.wcx + 108)) / volscale, true)
  401. end
  402. end
  403. function love.keypressed(k, r, rr)
  404. -- 0.10 compat
  405. if rr ~= nil then r = rr end
  406. if k == "kp-" or k == "kp+" then
  407. main.setVolume(main.vol + (k == "kp+" and 0.0625 or -0.0625), true)
  408. elseif r then
  409. -- nothing
  410. elseif main.quitrequest then
  411. if k == "escape" then
  412. main.dontquit = true
  413. end
  414. elseif k == "escape" then
  415. le.quit()
  416. elseif k == "pause" and not main.quitrequest and
  417. -- ctrl + pause = break, that breaks the game and must be passed down
  418. not (lk.isDown("lctrl") or lk.isDown("rctrl") or lk.isDown("ctrl"))
  419. then
  420. main.paused = not main.paused
  421. if active.pause then active.pause(main.paused) end
  422. elseif active.keypressed and not main.paused then
  423. active.keypressed(k, r)
  424. end
  425. --[[ debug ]]
  426. --if k == "1" and main.paused then main.singlestep = true end
  427. --]]
  428. end
  429. function love.textinput(c)
  430. if main.quitrequest then
  431. if c:lower() == "y" then
  432. main.quitaccepted = true
  433. le.quit()
  434. elseif c:lower() == "n" then
  435. main.dontquit = true
  436. end
  437. return
  438. end
  439. if not main.paused then
  440. if active.textinput then active.textinput(c) end
  441. end
  442. end
  443. function love.keyreleased(k)
  444. if main.quitrequest then
  445. if main.dontquit then
  446. main.dontquit = false
  447. main.quitrequest = false
  448. if not main.paused and active.pause then active.pause(false) end
  449. end
  450. elseif k ~= "pause" and not main.paused then
  451. if active.keyreleased then active.keyreleased(k) end
  452. end
  453. end
  454. function love.resize(neww, newh)
  455. main.ww = math.ceil(neww/main.pixelsize)
  456. main.wh = math.ceil(newh/main.pixelsize)
  457. main.wcx = main.ww/2
  458. main.wcy = main.wh/2
  459. local newcanvas = lg.newCanvas(main.ww, main.wh)
  460. local saveCanvas = lg.getCanvas()
  461. lg.setCanvas(newcanvas)
  462. main.drawaligned(5, canvas, main.wcx, main.wcy)
  463. lg.setCanvas(saveCanvas)
  464. canvas = newcanvas
  465. if not main.fullscreen then
  466. main.wwNFS = neww
  467. main.whNFS = newh
  468. main.saveScreen()
  469. end
  470. for k, mod in next, screens do
  471. if mod.resize then
  472. mod.resize(neww, newh)
  473. end
  474. end
  475. end