game.lua 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. --[========================================================================[--
  2. Game logic 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. local game = {}
  21. local la,le,lfs,lf,lg,li,lj,lk,lm,lmo,lp,ls,lsys,lth,lt,lw = require'ns'()
  22. local json = require '3rdparty.dkjson'
  23. local garbagetimer = 0
  24. local collision_canvas, no_collision
  25. local enemies, ship, cable, orbs, counters
  26. local tileset, tilecoll, spriteset, spritecoll, crashset, shotimg
  27. local tilebatch, orbbatch
  28. local tilequads, spritequads, crashquads
  29. local orb_sprite
  30. local lastxtile, lastytile
  31. local fineangle
  32. local was_turning, shooting, extending, thrusting
  33. local thrustvol = 0
  34. -- cached namespaces
  35. local map, enemytypes, decoys, targets, respawn, agents, keys
  36. local tgt_tile_start = 122
  37. local tgt_frames = 2 -- how many frames in animation
  38. local tgt_tile_current = tgt_tile_start
  39. local tgt_fps = 8/25
  40. local tgt_timer = 0
  41. local tgt_index -- index of current target in the targets table
  42. -- squared velocity threshold for being able to drop an orb into the target
  43. -- (can't drop if the orb crosses the target too fast)
  44. local tgt_vel_threshold2 = 10000 -- 100 px/s
  45. local cable_maxlen = 80
  46. local cable_maxlen2 = cable_maxlen * cable_maxlen
  47. local cable_extend_speed = 5
  48. local max_shoot_radius = 224
  49. local shoot_radius = 0
  50. local total_shoot_time = 4
  51. local latchsnd, orbdangersnd, crashsnd, cablesnd, enemykillsnd
  52. local getagentsnd, shootsnd, orbpickupsnd, orbdropsnd, thrustsnd, music
  53. local DIV = love_version >= 11000000 and 255 or 1
  54. function game.load()
  55. -- Game viewport
  56. game.vx, game.vy, game.vw, game.vh = 0, 0, main.ww, main.wh
  57. -- Cache some namespaces
  58. map, enemytypes, decoys, targets, respawn, agents, keys = main.map, main.enemytypes,
  59. main.decoys, main.targets, main.respawn, main.agents, main.keys
  60. tileset = lg.newImage("img/Tiles32x32.png")
  61. tilecoll = lg.newImage("img/Tiles32x32_collision.png")
  62. spriteset = lg.newImage("img/Sprites32x32.png")
  63. spritecoll = lg.newImage("img/Sprites32x32_collision.png")
  64. crashset = lg.newImage("img/Explosion32x32.png")
  65. shotimg = lg.newImage("img/Shot.png")
  66. -- Don't use nearest for the shot, as it's blurred to start with.
  67. shotimg:setFilter("linear", "linear")
  68. local tx, ty = tileset:getDimensions()
  69. local sx, sy = spriteset:getDimensions()
  70. -- Tiles are easy
  71. tilequads = {} -- used for both normal tiles and collision tiles
  72. -- which means the images must have the same size
  73. -- (same applies to spritequads below)
  74. for y = 0, (ty-1)/32 do
  75. for x = 0, (tx-1)/32 do
  76. tilequads[#tilequads + 1] = lg.newQuad(x*32, y*32, 32, 32, tx, ty)
  77. end
  78. end
  79. -- Sprites
  80. spritequads = {}
  81. -- Ship quads
  82. for y = 0, 1 do
  83. for x = 0, 15 do
  84. spritequads[#spritequads + 1] = lg.newQuad(x*32, y*32, 32, 32, sx, sy)
  85. end
  86. end
  87. -- Enemies quads
  88. -- Assumes that if there is a higher sprite in a row, then the space
  89. -- in the next rows until completing that height is empty.
  90. -- E.g. if X is 32x32 (1x1 cells) and Y is 64x64 (2x2 cells):
  91. -- X YY YY X X correct
  92. -- YY YY
  93. --
  94. -- X YY YY X X incorrect - don't reuse the empty spaces
  95. -- X YY YY (our algorithm isn't that clever)
  96. local idx = 32 -- 32x32 cell number - we start after the ship sprites
  97. local height = 1 -- height of the highest cell in the row
  98. for k, v in ipairs(enemytypes) do
  99. -- Cache the starting quad. The animation spans nframes from here.
  100. if v.nframes >= 1 then v.initsprite = #spritequads + 1 end
  101. for enemynum = 1, v.nframes do
  102. spritequads[#spritequads + 1] =
  103. lg.newQuad(idx%16*32, (idx-idx%16)/16*32, v.width*32, v.height*32, sx, sy)
  104. if v.height > height then height = v.height end
  105. idx = idx + v.width
  106. if idx % 16 == 0 then
  107. idx = idx + 32 * (height-1) -- skip row height
  108. height = 1
  109. end
  110. end
  111. end
  112. -- Finally, the orb
  113. orb_sprite = #spritequads + 1
  114. spritequads[orb_sprite] =
  115. lg.newQuad(idx%16*32, (idx-idx%16)/16*32, 32, 32, sx, sy)
  116. -- and its glow
  117. idx = idx + 1
  118. spritequads[orb_sprite+1] =
  119. lg.newQuad(idx%16*32, (idx-idx%16)/16*32, 32, 32, sx, sy)
  120. -- Ship crash animation
  121. sx, sy = crashset:getDimensions()
  122. crashquads = {}
  123. for idx = 0, 31 do
  124. crashquads[idx+1] = lg.newQuad(idx%4*32, (idx-idx%4)/4*32, 32, 32, sx, sy)
  125. end
  126. tilebatch = lg.newSpriteBatch(tileset,
  127. -- In 1D, a 33-pixel window can see up to two 32-pixel tiles
  128. -- simultaneously. One needs a 34-pixel window to be able
  129. -- to see three. So in 1D it would be: floor((widht+62)/32)
  130. -- which equals 2 for width=33 and 3 for width=34. In 2D the
  131. -- natural extension is the following.
  132. math.floor((game.vw + 62)*(game.vh + 62)/(32*32))
  133. -- For 640x480, that's 336 tiles. Less than the default 1000,
  134. -- so quite bearable.
  135. )
  136. collision_canvas = lg.newCanvas(32, 64) -- enough for the ship and orb sprite
  137. -- 0.10.0 compatibility
  138. if collision_canvas.newImageData then
  139. no_collision = string.rep("\0", #collision_canvas:newImageData():getString())
  140. else
  141. no_collision = string.rep("\0", #collision_canvas:getImageData():getString())
  142. end
  143. -- How many orbs we're going to draw max. It's pretty static, it only changes
  144. -- when an orb is picked up.
  145. orbbatch = lg.newSpriteBatch(spriteset, #main.orbs + #main.decoys)
  146. -- Load sounds
  147. latchsnd = la.newSource("snd/latch.wav", "static")
  148. latchsnd:setVolume(0.4) -- make it subtler
  149. getagentsnd = la.newSource("snd/LoadAgent.wav", "static")
  150. shootsnd = la.newSource("snd/AgentShot.wav", "static")
  151. orbdangersnd = la.newSource("snd/orbdanger.wav", "static")
  152. orbdangersnd:setLooping(true)
  153. orbpickupsnd = la.newSource("snd/click_one_22khz.wav", "static")
  154. orbdropsnd = la.newSource("snd/click_two_22khz.wav", "static")
  155. thrustsnd = la.newSource("snd/jet_lp.ogg", "static")
  156. thrustsnd:setLooping(true)
  157. -- Crash sound
  158. crashsnd = la.newSource("snd/Grenade-SoundBible.com-1777900486.ogg", "static")
  159. -- Enemy killed
  160. enemykillsnd = la.newSource("snd/supertank_plazma_fireball_22khz.mp3", "static")
  161. -- Make a duplicate so we can ply two at the same time
  162. -- Only 0.9.1+ has Source:clone(); 0.9.0 needs to load a new instance.
  163. local enemykillsnd2 = enemykillsnd.clone and enemykillsnd:clone()
  164. or la.newSource("snd/supertank_plazma_fireball_22khz.mp3", "static")
  165. -- Turn it into a table
  166. enemykillsnd = { enemykillsnd, enemykillsnd2 }
  167. -- Cable extend/retract sound
  168. cablesnd = la.newSource("snd/Cable_Sound.wav", "static")
  169. -- Music
  170. music = la.newSource("snd/Thrust II - in game - Loop 909924-6942804.ogg", "stream")
  171. music:setVolume(0.2)
  172. end
  173. local indent_true = {indent=true}
  174. function game.savegame()
  175. main.savedelay = 2.5
  176. local f = json.encode(game.state, indent_true)
  177. lfs.write("saved.txt", f, #f)
  178. end
  179. function game.activate()
  180. if cable.m ~= 0 then
  181. orbdangersnd:setVolume(0)
  182. orbdangersnd:play()
  183. end
  184. if main.music then
  185. music:play()
  186. end
  187. thrustvol = 0
  188. thrustsnd:setVolume(0)
  189. thrustsnd:play()
  190. end
  191. function game.deactivate()
  192. orbdangersnd:stop()
  193. thrustsnd:stop()
  194. music:stop()
  195. end
  196. function game.pause(pause)
  197. if pause then
  198. orbdangersnd:stop()
  199. music:pause()
  200. thrustsnd:setVolume(0)
  201. thrustvol = 0
  202. else
  203. if cable.m ~= 0 then
  204. orbdangersnd:play()
  205. end
  206. if main.music then
  207. music:play()
  208. end
  209. thrusting = lk.isDown(keys.thrust)
  210. if thrusting then
  211. thrustsnd:setVolume(1)
  212. thrustvol = 1
  213. end
  214. end
  215. end
  216. local function new_or_load_game(load)
  217. -- restore saved game or start new game
  218. if load and main.isFile("saved.txt") then
  219. local f, s = lfs.read("saved.txt")
  220. local tmp, err
  221. game.state, tmp, err = json.decode(f, 1, json.null, nil)
  222. if err then
  223. game.state = false
  224. else
  225. -- validate
  226. if #game.state.orbs > #main.orbs
  227. or #game.state.enemies > #main.enemies
  228. or game.state.counters.shields > 99
  229. then game.state = false end
  230. end
  231. else
  232. game.state = false
  233. end
  234. if not game.state then
  235. -- New game - copy initial state
  236. game.state = { enemies = main.deepcopy(main.enemies),
  237. dyingenemies = {},
  238. ship = { angle = 0 },
  239. orbs = main.deepcopy(main.orbs),
  240. cable = { length = 0, m = 0, latched = false },
  241. counters = { shields = 10, score = 0, respawn = 1, clock = 0,
  242. deadtimer = false, },
  243. }
  244. game.state.ship.x = main.respawn[game.state.counters.respawn].x
  245. game.state.ship.y = main.respawn[game.state.counters.respawn].y
  246. game.state.ship.oldx = game.state.ship.x -- initialize integrator with vel 0
  247. game.state.ship.oldy = game.state.ship.y
  248. game.state.cable.x = game.state.ship.x
  249. game.state.cable.y = game.state.ship.y
  250. game.state.cable.oldx = game.state.ship.x
  251. game.state.cable.oldy = game.state.ship.y
  252. -- Can't find a good place for this at the moment.
  253. game.state.counters.shields = game.state.counters.shields - 1
  254. end
  255. -- Compatibility fixup
  256. if not game.state.dyingenemies then
  257. game.state.dyingenemies = {}
  258. end
  259. if game.state.counters.agent and not game.state.counters.shoot_agent then
  260. game.state.counters.shoot_agent = game.state.counters.agent
  261. end
  262. -- shortcuts
  263. enemies, ship, cable, orbs, counters =
  264. game.state.enemies, game.state.ship, game.state.cable, game.state.orbs,
  265. game.state.counters
  266. -- Clean up to start again
  267. orbbatch:clear()
  268. -- Add decoys
  269. for k, v in ipairs(decoys) do
  270. orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  271. end
  272. -- Add normal orbs
  273. for k, v in ipairs(orbs) do
  274. v.sprite = orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  275. end
  276. -- Update map with current progress
  277. tgt_index = #orbs + (cable.m ~= 0 and 1 or 0)
  278. for k, v in ipairs(targets) do
  279. if k <= tgt_index then
  280. map[v.y*128+v.x+1] = v.empty
  281. else
  282. map[v.y*128+v.x+1] = v.tile
  283. end
  284. end
  285. -- force refresh
  286. lastxtile = false
  287. -- assume no key pressed
  288. was_turning = false
  289. shooting = false
  290. extending = false
  291. -- initialize first frame
  292. game.update(0)
  293. end
  294. local function update_ship(dt)
  295. if dt == 0 then return end
  296. --[[ update angle ]]
  297. local angvel = 16
  298. local kleft, kright = lk.isDown(keys.left), lk.isDown(keys.right)
  299. if kleft and not kright then
  300. if was_turning then
  301. fineangle = (fineangle - angvel*dt) % 32
  302. ship.angle = math.ceil(fineangle) % 32
  303. else
  304. was_turning = true -- sharp reaction upon pressing
  305. fineangle = (ship.angle - 1) % 32
  306. ship.angle = fineangle
  307. end
  308. elseif kright and not kleft then
  309. if was_turning then
  310. fineangle = (fineangle + angvel*dt) % 32
  311. ship.angle = math.floor(fineangle)
  312. else
  313. was_turning = true
  314. fineangle = (ship.angle + 1) % 32
  315. ship.angle = fineangle
  316. end
  317. else
  318. was_turning = false
  319. fineangle = ship.angle
  320. end
  321. --[[ update ship (Verlet integrator) ]]
  322. -- flight parameters
  323. local thrust = 600
  324. local gravity = 45
  325. local dragx = 0.2
  326. local dragy = 0.166
  327. local shipmass = 1
  328. local dt2 = dt*dt
  329. local shipforcex, shipforcey = 0, gravity * shipmass
  330. local was_thrusting = thrusting
  331. thrusting = lk.isDown(keys.thrust)
  332. if thrusting then
  333. thrustsnd:setVolume(1)
  334. thrustvol = 1
  335. shipforcex = shipforcex + math.sin(fineangle*math.pi/16) * thrust
  336. shipforcey = shipforcey - math.cos(fineangle*math.pi/16) * thrust
  337. --else
  338. -- sound stopping handled by game.update, to ramp down volume
  339. end
  340. local orbaccelx = 0
  341. local orbaccely = 0
  342. local newcablex
  343. local newcabley
  344. local newshipx = ship.x
  345. local newshipy = ship.y
  346. local orbmass
  347. orbmass = cable.m * shipmass -- orb mass is how heavier it is than the ship
  348. newcablex = cable.x
  349. newcabley = cable.y
  350. orbaccelx = 0
  351. orbaccely = gravity
  352. newcablex = newcablex + (cable.x - cable.oldx) * (1-dragx*dt) + orbaccelx * dt2
  353. newcabley = newcabley + (cable.y - cable.oldy) * (1-dragy*dt) + orbaccely * dt2
  354. local accelx = shipforcex / shipmass
  355. local accely = shipforcey / shipmass
  356. newshipx = newshipx + (newshipx - ship.oldx) * (1-dragx*dt) + accelx * dt2
  357. newshipy = newshipy + (newshipy - ship.oldy) * (1-dragy*dt) + accely * dt2
  358. -- Apply length constraint
  359. local cablex = newshipx - newcablex
  360. local cabley = newshipy - newcabley
  361. local actuallen = math.sqrt(cablex*cablex + cabley*cabley)
  362. if actuallen < 0.00001 then actuallen = 0.00001 end -- should never happen
  363. -- the distance to adjust for is cable_maxlen - actuallen
  364. -- ours is a rope, not a stick - remove this condition if original behavior wanted
  365. if actuallen > cable_maxlen then
  366. newshipx = newshipx + (cable_maxlen - actuallen) * cablex / actuallen
  367. * (orbmass / (orbmass + shipmass))
  368. newshipy = newshipy + (cable_maxlen - actuallen) * cabley / actuallen
  369. * (orbmass / (orbmass + shipmass))
  370. newcablex = newcablex + (cable_maxlen - actuallen) * cablex / actuallen
  371. * -(shipmass / (orbmass + shipmass))
  372. newcabley = newcabley + (cable_maxlen - actuallen) * cabley / actuallen
  373. * -(shipmass / (orbmass + shipmass))
  374. end
  375. -- "Scroll" orb position
  376. cable.oldx = cable.x
  377. cable.oldy = cable.y
  378. cable.x = newcablex
  379. cable.y = newcabley
  380. -- "Scroll" ship position
  381. ship.oldx = ship.x
  382. ship.oldy = ship.y
  383. ship.x = newshipx
  384. ship.y = newshipy
  385. -- Normalize if both orb and ship are equal modulo 4096
  386. if not cable or math.floor(ship.x / 4096) == math.floor(cable.x / 4096) then
  387. local k = math.floor(ship.x / 4096) * 4096
  388. ship.x = ship.x - k
  389. ship.oldx = ship.oldx - k
  390. if cable then
  391. cable.x = cable.x - k
  392. cable.oldx = cable.oldx - k
  393. end
  394. end
  395. end
  396. function game.newgame()
  397. new_or_load_game(main.restore)
  398. end
  399. local function collided()
  400. -- Collision test - paint the collision tiles/sprites to a canvas
  401. -- and multiply by ship's collision sprite to see if there are
  402. -- common pixels (all zeros = no)
  403. lg.setCanvas(collision_canvas)
  404. -- Clear collision canvas
  405. if collision_canvas.clear then
  406. collision_canvas:clear()
  407. else
  408. lg.clear()
  409. end
  410. -- In order to only do getImageData once, our canvas is
  411. -- divided into two halves vertically. The top part is for
  412. -- the ship, the bottom part is for the orb.
  413. for i = 0, (cable.m ~= 0 and 32 or 0), 32 do -- Draw twice if an orb is carried, else once.
  414. lg.setScissor(0, i, 32, 32) -- select which half to be drawn
  415. local topleftx = i == 0 and math.floor(ship.x-15.5)%4096 or math.floor(cable.x-16)%4096
  416. local toplefty = i == 0 and math.floor(ship.y-15.5) or math.floor(cable.y-16)
  417. -- Colision with tile
  418. local localx = -(topleftx%32)
  419. local localy = -(toplefty%32)
  420. local tilex = (topleftx+localx)/32
  421. local tiley = (toplefty+localy)/32
  422. if tiley > 62 then tiley = 62 end
  423. if tiley < 0 then tiley = 0 end
  424. lg.draw(tilecoll, tilequads[map[tilex%128 + tiley*128 + 1]], localx, localy+i)
  425. if localx + 32 < 32 then
  426. lg.draw(tilecoll, tilequads[map[(tilex+1)%128 + tiley*128 + 1]], localx+32, localy+i)
  427. end
  428. if localy + 32 < 32 then
  429. lg.draw(tilecoll, tilequads[map[tilex%128 + (tiley+1)*128 + 1]], localx, localy+32+i)
  430. end
  431. if localx + 32 < 32 and localy + 32 < 32 then
  432. lg.draw(tilecoll, tilequads[map[(tilex+1)%128 + (tiley+1)*128 + 1]], localx+32, localy+32+i)
  433. end
  434. -- Collision with decoys
  435. local vx, vy
  436. for k, v in ipairs(decoys) do
  437. vx = v.x-16 - topleftx - (topleftx < 32 and v.x >= 4064 and 4096 or 0)
  438. vy = v.y-16 - toplefty
  439. if vx > -32 and vx < 32 and vy > -32 and vy < 32 then -- only draw if in range
  440. lg.draw(spritecoll, spritequads[orb_sprite], vx, vy + i)
  441. end
  442. end
  443. -- Same for orbs
  444. for k, v in ipairs(orbs) do
  445. vx = v.x-16 - topleftx - (topleftx < 32 and v.x >= 4064 and 4096 or 0)
  446. vy = v.y-16 - toplefty
  447. if vx > -32 and vx < 32 and vy > -32 and vy < 32 then -- only draw if in range
  448. lg.draw(spritecoll, spritequads[orb_sprite], vx, vy + i)
  449. end
  450. end
  451. if i ~= 0 then
  452. -- only collide the orb with the ship, not with itself
  453. -- they are guaranteed to be in the same multiple of 4096
  454. vx = math.floor(ship.x-15.5) - topleftx
  455. vy = math.floor(ship.y-15.5) - toplefty
  456. lg.draw(spritecoll, spritequads[ship.angle + 1], vx, vy + i)
  457. end
  458. -- Collision with enemies
  459. local et
  460. for k, v in ipairs(enemies) do
  461. et = enemytypes[v.type]
  462. vx = math.floor(v.x/2)*2 - topleftx - (topleftx < et.width*32 and v.x >= 4096-et.width*32 and 4096 or 0)
  463. vy = math.floor(v.y/2)*2 - toplefty
  464. if vx > -(et.width*32) and vx < 32 and vy > -(et.height*32) and vy < 32 then
  465. -- visible
  466. lg.draw(spriteset, spritequads[v.f], vx, vy + i)
  467. end
  468. end
  469. -- Draw ship/orb in multiplicative mode
  470. if love_version < 0010000 then
  471. lg.setBlendMode("multiplicative")
  472. else
  473. lg.setBlendMode("multiply", "premultiplied")
  474. end
  475. lg.draw(spritecoll, spritequads[i == 0 and ship.angle+1 or orb_sprite], 0, i)
  476. lg.setBlendMode("alpha", "alphamultiply") -- return blend mode to normal
  477. end
  478. lg.setScissor()
  479. lg.setCanvas()
  480. if collision_canvas.newImageData then
  481. return collision_canvas:newImageData():getString() ~= no_collision
  482. else
  483. return collision_canvas:getImageData():getString() ~= no_collision
  484. end
  485. end
  486. function game.update(dt)
  487. garbagetimer = garbagetimer + dt
  488. if garbagetimer >= 5 then
  489. -- playing with Canvas:getImageData tends to generate garbage that isn't
  490. -- collected, so we help Lua a bit here
  491. collectgarbage()
  492. garbagetimer = 0
  493. end
  494. if not counters.deadtimer and dt ~= 0 and collided() then
  495. -- Player died - start death timer
  496. counters.deadtimer = 2.5
  497. ship.crashframe = 1
  498. ship.crashinterval = 0
  499. thrusting = false
  500. crashsnd:play()
  501. end
  502. if not thrusting then
  503. thrustvol = (thrustvol * 0.33) - 0.00001
  504. if thrustvol < 0 then
  505. thrustvol = 0
  506. end
  507. thrustsnd:setVolume(thrustvol)
  508. end
  509. if counters.deadtimer then
  510. -- refresh ship crash animation
  511. if ship.crashframe then
  512. -- when crashframe exists, crashinterval must exist too
  513. ship.crashinterval = ship.crashinterval + dt
  514. if ship.crashinterval >= 0.1 then
  515. ship.crashinterval = ship.crashinterval - 0.1
  516. ship.crashframe = ship.crashframe + 1
  517. if ship.crashframe > 16 then
  518. ship.crashframe = nil
  519. ship.crashinterval = nil
  520. end
  521. end
  522. end
  523. counters.deadtimer = counters.deadtimer - dt
  524. if counters.deadtimer <= 0 then
  525. counters.deadtimer = false
  526. ship.crashframe = nil
  527. ship.crashinterval = nil
  528. -- Respawn point for the player
  529. ship.x = respawn[counters.respawn].x
  530. ship.y = respawn[counters.respawn].y
  531. ship.oldx = ship.x
  532. ship.oldy = ship.y
  533. ship.angle = 0
  534. was_turning = false
  535. if cable then
  536. cable.x = ship.x
  537. cable.y = ship.y + cable_maxlen
  538. cable.oldx = cable.x
  539. cable.oldy = cable.y
  540. end
  541. if counters.shields == 0 then
  542. main.activate(screens.gameover)
  543. return
  544. else
  545. counters.shields = counters.shields - 1
  546. screens.getready.fromstart = false
  547. main.activate(screens.getready)
  548. return
  549. end
  550. end
  551. end
  552. -- Update dead enemies' animations
  553. do
  554. local l = #game.state.dyingenemies
  555. for i = l, 1, -1 do
  556. local v = game.state.dyingenemies[i]
  557. v.t = v.t + dt
  558. if v.t >= 0.1 then
  559. v.t = v.t - 0.1
  560. v.f = v.f + 1
  561. if v.f > 8 then
  562. game.state.dyingenemies[i] = game.state.dyingenemies[l]
  563. game.state.dyingenemies[l] = nil
  564. l = l - 1
  565. end
  566. end
  567. end
  568. end
  569. if dt > 0.05 then dt = 0.05 end -- smoothen the movement
  570. -- read keys
  571. local was_shooting = shooting
  572. shooting = lk.isDown(keys.fire)
  573. local pickup = lk.isDown(keys.pickup)
  574. if not pickup and cable.latched then cable.latched = false end
  575. -- Update orb timer
  576. if counters.orbinterval and not counters.deadtimer then
  577. counters.orbinterval = counters.orbinterval + dt
  578. if counters.orbinterval >= 0.625 then
  579. counters.orbinterval = counters.orbinterval - 0.625
  580. counters.orbtimer = counters.orbtimer - 1
  581. end
  582. if counters.orbtimer <= 0 then
  583. -- Kaboom!
  584. main.activate(screens.orbexplode)
  585. return
  586. end
  587. end
  588. -- Check if an orb was picked up
  589. if cable.latched then
  590. cable.x = orbs[cable.latched].x
  591. cable.y = orbs[cable.latched].y
  592. cable.oldx = cable.x
  593. cable.oldy = cable.y
  594. if (orbs[cable.latched].x-ship.x)^2 + (orbs[cable.latched].y-ship.y)^2 >= cable_maxlen2 then
  595. -- Latched to orb
  596. cable.m = orbs[cable.latched].m
  597. orbpickupsnd:play()
  598. orbdangersnd:setVolume(0)
  599. orbdangersnd:play()
  600. -- Start the unstable orb timer
  601. counters.orbinterval = 0
  602. counters.orbtimer = 500
  603. -- Remove the orb. The orbs table is unsorted, so for performance, to
  604. -- avoid scrolling (if removing a middle element) or creating a hole
  605. -- (if setting it to nil), we move the last element to this place.
  606. --table.remove(orbs, cable.latched) -- works, but this is presumably faster:
  607. orbs[cable.latched] = orbs[#orbs]
  608. orbs[#orbs] = nil
  609. cable.oldx = cable.x
  610. cable.oldy = cable.y
  611. -- Sprites can't be deleted, so regenerate the batch.
  612. orbbatch:clear()
  613. -- Add decoys
  614. for k, v in ipairs(decoys) do
  615. orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  616. end
  617. -- Add normal orbs
  618. for k, v in ipairs(orbs) do
  619. v.sprite = orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  620. end
  621. cable.latched = false
  622. end
  623. end
  624. -- Update player
  625. if not counters.deadtimer then
  626. update_ship(dt)
  627. end
  628. -- Clamp vertically
  629. if ship.y < 64 then ship.y = 64 ship.oldy = 64 - dt*100 end -- make a bouncing effect
  630. if ship.y > 2038 then ship.y = 2038 end
  631. -- Assign respawn zone
  632. for k, v in ipairs(respawn) do
  633. if ship.x%4096 >= v.topleftx and ship.x%4096 < v.topleftx + v.w
  634. and ship.y >= v.toplefty and ship.y < v.toplefty + v.h
  635. then
  636. counters.respawn = k
  637. break
  638. end
  639. end
  640. -- Target animation timer
  641. tgt_timer = tgt_timer + dt
  642. if tgt_timer / tgt_fps >= 1 then
  643. tgt_timer = tgt_timer % tgt_fps
  644. tgt_tile_current = (tgt_tile_current - tgt_tile_start + 1) % tgt_frames + tgt_tile_start
  645. end
  646. -- Deposit orb in target
  647. local tgt = targets[tgt_index]
  648. if cable.m ~= 0 and tgt and dt > 0 then
  649. -- Check if orb is in target
  650. local orbvelx = (cable.x - cable.oldx) / dt
  651. local orbvely = (cable.y - cable.oldy) / dt
  652. if orbvelx*orbvelx + orbvely*orbvely < tgt_vel_threshold2 then
  653. if cable.x >= tgt.x*32 and cable.x < tgt.x*32+32
  654. and cable.y >= tgt.y*32 and cable.y < tgt.y*32+32
  655. then
  656. orbdropsnd:play()
  657. counters.score = counters.score + counters.orbtimer * 10
  658. cable.m = 0
  659. map[tgt.y*128+tgt.x+1] = tgt.tile
  660. tgt_index = tgt_index - 1
  661. if tgt_index == 0 then
  662. -- Last target achieved!
  663. main.activate(screens.gamewon)
  664. return
  665. end
  666. -- Force refresh of map
  667. lastxtile = false
  668. -- extra shield every 4 orbs deposited
  669. if (#main.orbs - #orbs) % 4 == 0 then
  670. counters.shields = counters.shields + 1
  671. end
  672. counters.orbinterval = nil
  673. counters.orbtimer = nil
  674. orbdangersnd:stop()
  675. end
  676. end
  677. end
  678. -- Agent timer
  679. if counters.agent then
  680. counters.agentinterval = counters.agentinterval + dt
  681. if counters.agentinterval >= 0.625 then
  682. counters.agentinterval = counters.agentinterval - 0.625
  683. counters.agenttime = counters.agenttime - 1
  684. if counters.agenttime <= 0 then
  685. counters.agenttime = nil
  686. counters.agent = nil
  687. counters.agentinterval = nil
  688. end
  689. end
  690. end
  691. -- Check if agent picked up.
  692. if pickup and (not counters.agenttime or counters.agenttime < 290) then
  693. local x, y = ship.x % 4096, ship.y + 64
  694. local t
  695. if y >= 0 and y < 2048 then
  696. t = agents[map[math.floor(x/32) + math.floor(y/32)*128 + 1]]
  697. if t then
  698. counters.agent = t
  699. counters.agenttime = 300
  700. counters.agentinterval = 0
  701. getagentsnd:play()
  702. end
  703. end
  704. end
  705. -- Shoot timer
  706. if counters.shoot_timer then
  707. counters.shoot_timer = counters.shoot_timer - dt
  708. if counters.shoot_timer <= 0 then
  709. counters.shoot_timer = nil
  710. counters.shoot_x = nil
  711. counters.shoot_y = nil
  712. counters.shoot_agent = nil
  713. end
  714. end
  715. -- Check if shooting
  716. if shooting and not was_shooting and counters.agent and not counters.shoot_timer and not counters.deadtimer then
  717. counters.shoot_timer = total_shoot_time
  718. counters.shoot_x = ship.x
  719. counters.shoot_y = ship.y
  720. counters.shoot_agent = counters.agent
  721. shootsnd:play()
  722. end
  723. if counters.shoot_timer then
  724. shoot_radius = counters.shoot_timer / total_shoot_time
  725. shoot_radius = (1 - shoot_radius ^ 5) * max_shoot_radius
  726. if counters.shoot_timer > 1.5 then -- past 2.5 seconds it's too diluted
  727. local x1, y1, x2, y2
  728. local radius2 = shoot_radius*shoot_radius*(0.85*0.85) -- (the .85 compensates the diffuse image radius)
  729. -- did it hit any enemies?
  730. local k, v, l
  731. k = 1
  732. l = #enemies
  733. while k <= l do -- iterate manually to safely delete elements
  734. v = enemies[k]
  735. if v.type == counters.shoot_agent then
  736. -- the centre of the bounding box must be within the radius
  737. -- (not too realistic, but a collision analysis for this sounds
  738. -- overkill)
  739. x1 = v.x - counters.shoot_x
  740. y1 = v.y - counters.shoot_y
  741. x2 = x1 + enemytypes[v.type].width*32
  742. y2 = y1 + enemytypes[v.type].height*32
  743. -- Consider wraparound if the distance is too large
  744. if x1 < -2048 then
  745. x1 = x1 + 4096
  746. x2 = x2 + 4096
  747. elseif x1 > 2048 then
  748. x1 = x1 - 4096
  749. x2 = x2 - 4096
  750. end
  751. x1 = (x1 + x2) * 0.5
  752. y1 = (y1 + y2) * 0.5
  753. x1 = x1*x1
  754. y1 = y1*y1
  755. if x1 + y1 < radius2 then
  756. -- Killed enemy
  757. counters.score = counters.score + counters.agenttime * 10
  758. enemies[k].f = 1 -- explosion frame
  759. enemies[k].t = 0 -- timer to advance frame
  760. game.state.dyingenemies[#game.state.dyingenemies + 1] = enemies[k]
  761. enemies[k] = enemies[l]
  762. enemies[l] = nil
  763. l = l - 1
  764. -- Play kill enemy sound
  765. if enemykillsnd[1]:isPlaying() then
  766. enemykillsnd[2]:play()
  767. else
  768. enemykillsnd[1]:play()
  769. end
  770. else
  771. k = k + 1
  772. end
  773. else
  774. k = k + 1
  775. end
  776. end
  777. end
  778. end
  779. if cable.m == 0 then
  780. local was_extending = extending
  781. extending = (pickup and 1 or -1)
  782. if cable.length > 0 or extending == 1 then
  783. cable.length = cable.length + extending * dt * cable_extend_speed
  784. else
  785. extending = false
  786. end
  787. if extending ~= was_extending and not counters.deadtimer then
  788. cablesnd:play()
  789. end
  790. if cable.length > 1 then cable.length = 1 end
  791. if cable.length <= 0 then
  792. cable.length = 0
  793. -- fixup position x and y
  794. cable.x = ship.x
  795. cable.y = ship.y + cable_maxlen
  796. -- equate speed to ship's speed
  797. cable.oldx = ship.oldx - ship.x + cable.x
  798. cable.oldy = ship.oldy - ship.y + cable.y
  799. end
  800. end
  801. -- Check if cable reaches orb
  802. if cable.m == 0 and cable.length == 1 and not cable.latched then
  803. -- Window for consideration
  804. local sx1, sy1, sx2, sy2 = ship.x-cable_maxlen, ship.y-cable_maxlen,
  805. ship.x+cable_maxlen, ship.y+cable_maxlen
  806. for k, v in ipairs(orbs) do
  807. if v.x >= sx1 and v.x <= sx2 and v.y >= sy1 and v.y <= sy2 then
  808. -- *Might* be in range - do the more expensive Euclidean check
  809. if (v.x-ship.x)^2 + (v.y-ship.y)^2 <= cable_maxlen2 then
  810. -- In range - latch to orb
  811. cable.latched = k
  812. cable.x = v.x
  813. cable.y = v.y
  814. latchsnd:play()
  815. break
  816. end
  817. end
  818. end
  819. end
  820. -- Update enemies' sprites and positions
  821. counters.clock = counters.clock + dt
  822. local t, tpos, et
  823. for k, v in ipairs(enemies) do
  824. -- Position
  825. tpos = (v.t + counters.clock) % v.period
  826. if tpos * 2 >= v.period then
  827. -- going back
  828. t = (v.period - tpos)*2 / v.period
  829. else
  830. t = tpos * 2 / v.period
  831. end
  832. -- Hack because our horizontal positions are broken
  833. local vx1
  834. if v.x0 == v.x1 then vx1 = v.x1 else vx1 = v.x1+2 end
  835. v.x = t < 0.5 and v.x0 + ( vx1 - v.x0) * t or vx1 - ( vx1 - v.x0) * (1 - t)
  836. v.y = t < 0.5 and v.y0 + (v.y1 - v.y0) * t or v.y1 - (v.y1 - v.y0) * (1 - t)
  837. -- Frame
  838. et = enemytypes[v.type]
  839. -- t = (v.frame + counters.clock * v.fps) % et.nframes
  840. if vx1 ~= v.x0 then
  841. t = (tpos/(v.period*16)*(vx1-v.x0)*v.fps) % et.nframes
  842. else
  843. t = (tpos/(v.period*16)*(v.y1-v.y0)*v.fps) % et.nframes
  844. end
  845. if et.pingpong then
  846. if tpos * 2 >= v.period then -- going backwards
  847. t = (et.nframes-t) % et.nframes
  848. end
  849. end
  850. v.f = et.initsprite + math.floor(t)
  851. end
  852. -- Debug string
  853. -- game.DEBUG=tostring(collided()).. " " .. 1/dt
  854. -- Loop music
  855. if music:isPlaying() then
  856. local pos = music:tell("samples")
  857. -- Loop it back to a sensible position
  858. if pos >= 6942804 then music:seek(pos - (6942804 - 909924), "samples") end
  859. end
  860. end
  861. function game.tiles_draw(x, y)
  862. -- Clamp coordinates to acceptable values
  863. --if y < 0 then y = 0 end
  864. --if y > 2048 - game.vh then y = 2048 - game.vh end
  865. x = x % 4096
  866. local xtile = math.floor(x/32)
  867. local ytile = math.floor(y/32)
  868. local xtiles = math.floor(game.vw+62)/32 -- max visible tiles
  869. local ytiles = math.floor(game.vh+62)/32
  870. if ytile + ytiles > 64 then
  871. -- clamp vertically
  872. ytiles = 64 - ytile
  873. end
  874. if xtile ~= lastxtile or ytile ~= lastytile then
  875. -- update required
  876. lastxtile, lastytile = xtile, ytile
  877. tilebatch:clear()
  878. for yt = ytile, ytile + ytiles - 1 do
  879. for xt = xtile, xtile + xtiles - 1 do
  880. tilebatch:add(tilequads[map[yt*128 + xt%128 + 1]],
  881. (xt-xtile)*32, (yt-ytile)*32)
  882. end
  883. end
  884. end
  885. lg.draw(tilebatch, -(x%32), -(y%32))
  886. end
  887. function game.orbs_draw(x, y)
  888. x = x % 4096
  889. lg.draw(orbbatch, -x, -y)
  890. -- Draw 1 screen to the left and/or 1 screen to the right if necessary
  891. if x < 32 then
  892. lg.draw(orbbatch, -x-4096, -y)
  893. end
  894. if x + game.vh >= 4064 then
  895. lg.draw(orbbatch, -x+4096, -y)
  896. end
  897. end
  898. function game.draw()
  899. local vpx = math.floor(ship.x+0.5-game.vw/2)
  900. local vpy = math.floor(ship.y+0.5-game.vh/2)
  901. if vpy < 0 then vpy = 0 end
  902. if vpy > 2048-game.vh then vpy = 2048-game.vh end
  903. lg.setScissor(game.vx, game.vy, game.vw, game.vh)
  904. -- draw tiles
  905. game.tiles_draw(vpx, vpy)
  906. -- HACK: draw target in white first (we will colorize it later using multiplicative)
  907. local tgt = targets[tgt_index]
  908. if tgt then
  909. lg.draw(tileset, tilequads[75], tgt.x*32-vpx, tgt.y*32-vpy)
  910. end
  911. -- draw enemies
  912. local et
  913. for k, v in ipairs(enemies) do
  914. et = enemytypes[v.type]
  915. for x = v.x - 4096, vpx + game.vw, 4096 do
  916. if not (x >= vpx + game.vw
  917. or x+et.width*32 < vpx
  918. or v.y >= vpy + game.vh
  919. or v.y+et.height*32 < vpy)
  920. then
  921. -- visible
  922. lg.draw(spriteset, spritequads[v.f],
  923. (math.floor(x/2)*2 - vpx), (math.floor(v.y/2)*2 - vpy)
  924. )
  925. end
  926. end
  927. end
  928. -- Draw enemy crash animations
  929. for k, v in ipairs(game.state.dyingenemies) do
  930. lg.draw(crashset, crashquads[v.f], v.x - vpx, v.y - vpy)
  931. lg.draw(crashset, crashquads[v.f], v.x - 4096 - vpx, v.y - vpy)
  932. end
  933. -- draw cable line
  934. if (cable.m ~= 0 or cable.length ~= 0) and not counters.deadtimer then
  935. lg.setLineStyle("smooth")
  936. -- draw cable with length cable.length
  937. local cx = (cable.x-ship.x)*cable.length + ship.x
  938. local cy = (cable.y-ship.y)*cable.length + ship.y
  939. if not counters.deadtimer then
  940. lg.line(cx-vpx, cy-vpy, ship.x-vpx, ship.y-vpy)
  941. end
  942. end
  943. -- draw orbs/decoys
  944. game.orbs_draw(vpx, vpy)
  945. -- draw carried orb, if any
  946. if cable.m ~= 0 then
  947. -- draw orb
  948. if counters.deadtimer then
  949. if ship.crashframe then
  950. lg.draw(crashset, crashquads[ship.crashframe+16], cable.x-vpx-15, cable.y-vpy-18)
  951. end
  952. else
  953. local orbx = math.floor(cable.x-vpx-16)
  954. local orby = math.floor(cable.y-vpy-16)
  955. lg.draw(spriteset, spritequads[orb_sprite], orbx, orby)
  956. -- draw glow
  957. --lg.setBlendMode("additive")
  958. local orbtime = (500 - counters.orbtimer)*0.625 + counters.orbinterval
  959. local freq = .01/(1.01-orbtime*0.0032)
  960. if freq > 10 then freq = 10 end
  961. local amp = (1-math.cos(freq*orbtime))*freq*0.5
  962. lg.setColor(255/DIV,255/DIV,255/DIV, amp^0.7*(255/DIV))
  963. orbdangersnd:setVolume(amp^2)
  964. lg.draw(spriteset, spritequads[orb_sprite+1], orbx, orby)
  965. lg.setColor(255/DIV,255/DIV,255/DIV,255/DIV)
  966. lg.setBlendMode("alpha", "alphamultiply")
  967. end
  968. end
  969. -- draw ship
  970. if counters.deadtimer then
  971. if ship.crashframe then
  972. lg.draw(crashset, crashquads[ship.crashframe], ship.x-vpx-16, ship.y-vpy-16)
  973. end
  974. else
  975. lg.draw(spriteset, spritequads[ship.angle+1], math.floor(ship.x-vpx-15.5), math.floor(ship.y-vpy-15.5))
  976. end
  977. -- HACK: draw target in multiplicative mode (colorizes other sprites)
  978. if tgt then
  979. if love_version < 0010000 then
  980. lg.setBlendMode("multiplicative")
  981. else
  982. lg.setBlendMode("multiply", "premultiplied")
  983. end
  984. lg.draw(tileset, tilequads[tgt_tile_current], tgt.x*32-vpx, tgt.y*32-vpy)
  985. lg.setBlendMode("alpha", "alphamultiply")
  986. end
  987. -- draw shooting explosion
  988. if counters.shoot_timer then
  989. -- colorize explosion - red stays at 255,
  990. -- green diminishes slow and blue diminishes quick
  991. local red, green, blue
  992. local t = counters.shoot_timer/total_shoot_time -- from 1 to 0
  993. red = 255
  994. green = 255*t^1.5
  995. blue = 255*t^6
  996. lg.setColor(red/DIV, green/DIV, blue/DIV, 255/DIV*(counters.shoot_timer/total_shoot_time))
  997. local drawx = counters.shoot_x - vpx
  998. if drawx < -2048 then drawx = drawx + 4096 end
  999. if drawx > 2048 then drawx = drawx - 4096 end
  1000. lg.draw(shotimg, drawx, counters.shoot_y-vpy, 0, shoot_radius/256, shoot_radius/256, 256, 256)
  1001. lg.setColor(255/DIV, 255/DIV, 255/DIV, 255/DIV)
  1002. end
  1003. -- FIXME: draw current agent
  1004. if counters.agent then
  1005. for k, v in pairs(agents) do
  1006. if v == counters.agent then
  1007. lg.draw(tileset, tilequads[k], 0, main.wh-32)
  1008. lg.print(counters.agenttime, 35, main.wh-24)
  1009. local num = 0
  1010. for i = 1, #game.state.enemies do
  1011. if game.state.enemies[i].type == counters.agent then
  1012. num = num + 1
  1013. end
  1014. end
  1015. lg.print(num, 10, main.wh-48)
  1016. end
  1017. end
  1018. end
  1019. -- FIXME: draw current shields
  1020. lg.print(string.format("%02d", counters.shields), 96, main.wh-24)
  1021. -- FIXME: draw current orb timer
  1022. if counters.orbtimer then
  1023. lg.print(string.format("%03d", counters.orbtimer), 144, main.wh-24)
  1024. end
  1025. lg.print(string.format("%8d", counters.score), 160, main.wh - 24)
  1026. lg.setScissor()
  1027. --[[ debug
  1028. lg.print(game.DEBUG, 0, 0)
  1029. -- draw collision canvas
  1030. lg.setColor(100/DIV,100/DIV,100/DIV)
  1031. lg.rectangle("fill", 100, 100, 32, 64)
  1032. lg.setColor(255/DIV,255/DIV,255/DIV)
  1033. lg.draw(collision_canvas, 100, 100)
  1034. ]]
  1035. end
  1036. function game.keypressed(k, r)
  1037. if r then return end
  1038. if k == "escape" then
  1039. return main.dialog("EXIT TO MENU?", main.tomenu)
  1040. end
  1041. if k == "f10" then game.savegame() end
  1042. if k == "f3" then
  1043. new_or_load_game(true)
  1044. screens.getready.fromstart = false
  1045. return main.activate(screens.getready)
  1046. end
  1047. end
  1048. function game.resize(neww, newh)
  1049. game.vx = 0
  1050. game.vy = 0
  1051. game.vw = main.ww
  1052. game.vh = main.wh
  1053. tilebatch = lg.newSpriteBatch(tileset,
  1054. -- In 1D, a 33-pixel window can see up to two 32-pixel tiles
  1055. -- simultaneously. One needs a 34-pixel window to be able
  1056. -- to see three. So in 1D it would be: floor((widht+62)/32)
  1057. -- which equals 2 for width=33 and 3 for width=34. In 2D the
  1058. -- natural extension is the following.
  1059. math.floor((game.vw + 62)*(game.vh + 62)/(32*32))
  1060. -- For 640x480, that's 336 tiles. Less than the default 1000,
  1061. -- so quite bearable.
  1062. )
  1063. -- force refresh
  1064. lastxtile = false
  1065. end
  1066. return game