game.lua 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. --[========================================================================[--
  2. Game logic for Thrust II Reloaded.
  3. Copyright © 2015 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. game = {}
  21. local json = require "3rdparty/dkjson"
  22. local garbagetimer = 0
  23. local collision_canvas, no_collision
  24. local enemies, ship, orb, orbs, counters
  25. local tileset, tilecoll, spriteset, spritecoll
  26. local tilequads, spritequads
  27. local orb_sprite
  28. local lastxtile, lastytile
  29. local fineangle
  30. local was_turning, shooting
  31. -- cached namespaces
  32. local map, enemytypes, decoys, targets, respawn, agents, keys
  33. local tractor = false -- may contain index of orb tracted
  34. local tgt_tile_start = 122
  35. local tgt_frames = 2 -- how many frames in animation
  36. local tgt_tile_current = tgt_tile_start
  37. local tgt_fps = 8/25
  38. local tgt_timer = 0
  39. local tgt_index -- index of current target in the targets table
  40. -- squared velocity threshold for being able to drop an orb into the target
  41. -- (can't drop if the orb crosses the target too fast)
  42. local tgt_vel_threshold2 = 10000 -- 100 px/s
  43. local tractor_length = 80
  44. local tractor_length2 = tractor_length * tractor_length
  45. local shoot_radius = 224
  46. function game.load()
  47. -- Game viewport
  48. game.vx, game.vy, game.vw, game.vh = 0, 0, main.ww, main.wh
  49. -- Cache some namespaces
  50. map, enemytypes, decoys, targets, respawn, agents, keys = main.map, main.enemytypes,
  51. main.decoys, main.targets, main.respawn, main.agents, main.keys
  52. tileset = lg.newImage("img/Tiles32x32.png")
  53. tilecoll = lg.newImage("img/Tiles32x32_collision.png")
  54. spriteset = lg.newImage("img/Sprites32x32.png")
  55. spritecoll = lg.newImage("img/Sprites32x32_collision.png")
  56. local tx, ty = tileset:getDimensions()
  57. local sx, sy = spriteset:getDimensions()
  58. -- Tiles are easy
  59. tilequads = {} -- used for both normal tiles and collision tiles
  60. -- which means the images must have the same size
  61. -- (same applies to spritequads below)
  62. for y = 0, 7 do
  63. for x = 0, 15 do
  64. tilequads[#tilequads + 1] = lg.newQuad(x*32, y*32, 32, 32, tx, ty)
  65. end
  66. end
  67. -- Sprites
  68. spritequads = {}
  69. -- Ship quads
  70. for y = 0, 1 do
  71. for x = 0, 15 do
  72. spritequads[#spritequads + 1] = lg.newQuad(x*32, y*32, 32, 32, sx, sy)
  73. end
  74. end
  75. -- Enemies quads
  76. -- Assumes that if there is a higher sprite in a row, then the space
  77. -- in the next rows until completing that height is empty.
  78. -- E.g. if X is 32x32 (1x1 cells) and Y is 64x64 (2x2 cells):
  79. -- X YY YY X X correct
  80. -- YY YY
  81. --
  82. -- X YY YY X X incorrect - don't reuse the empty spaces
  83. -- X YY YY (our algorithm isn't that clever)
  84. local idx = 32 -- 32x32 cell number - we start after the ship sprites
  85. local height = 1 -- height of the highest cell in the row
  86. for k, v in ipairs(enemytypes) do
  87. -- Cache the starting quad. The animation spans nframes from here.
  88. if v.nframes >= 1 then v.initsprite = #spritequads + 1 end
  89. for enemynum = 1, v.nframes do
  90. spritequads[#spritequads + 1] =
  91. lg.newQuad(idx%16*32, (idx-idx%16)/16*32, v.width*32, v.height*32, sx, sy)
  92. if v.height > height then height = v.height end
  93. idx = idx + v.width
  94. if idx % 16 == 0 then
  95. idx = idx + 32 * (height-1) -- skip row height
  96. height = 1
  97. end
  98. end
  99. end
  100. -- Finally, the orb
  101. orb_sprite = #spritequads + 1
  102. spritequads[orb_sprite] =
  103. lg.newQuad(idx%16*32, (idx-idx%16)/16*32, 32, 32, sx, sy)
  104. tilebatch = lg.newSpriteBatch(tileset,
  105. -- In 1D, a 33-pixel window can see up to two 32-pixel tiles
  106. -- simultaneously. One needs a 34-pixel window to be able
  107. -- to see three. So in 1D it would be: floor((widht+62)/32)
  108. -- which equals 2 for width=33 and 3 for width=34. In 2D the
  109. -- natural extension is the following.
  110. math.floor((game.vw + 62)*(game.vh + 62)/(32*32))
  111. -- For 640x480, that's 336 tiles. Less than the default 1000,
  112. -- so quite bearable.
  113. )
  114. collision_canvas = lg.newCanvas(32, 64) -- enough for the ship and orb sprite
  115. no_collision = string.rep("\0", #collision_canvas:getImageData():getString())
  116. -- How many orbs we're going to draw max. It's pretty static, it only changes
  117. -- when an orb is picked up.
  118. orbbatch = lg.newSpriteBatch(spriteset, #main.orbs + #main.decoys, "static")
  119. end
  120. function game.savegame()
  121. game.state.orb = orb -- it can be set to nil
  122. local f = json.encode(game.state, {indent=true})
  123. lfs.write("saved.txt", f, #f)
  124. end
  125. local function new_or_load_game(load)
  126. -- restore saved game or start new game
  127. if load and lfs.isFile("saved.txt") then
  128. local f, s = lfs.read("saved.txt")
  129. local tmp, err
  130. game.state, tmp, err = json.decode(f, 1, json.null, nil)
  131. if err then
  132. game.state = false
  133. else
  134. -- validate
  135. if #game.state.orbs > #main.orbs
  136. or #game.state.enemies > #main.enemies
  137. or game.state.counters.shields > 99
  138. then game.state = false end
  139. end
  140. else
  141. game.state = false
  142. end
  143. if not game.state then
  144. -- New game - copy initial state
  145. game.state = { enemies = main.deepcopy(main.enemies),
  146. ship = { angle = 0 },
  147. orbs = main.deepcopy(main.orbs),
  148. counters = { shields = 10, score = 0, respawn = 1, clock = 0 },
  149. }
  150. game.state.ship.x = main.respawn[game.state.counters.respawn].x
  151. game.state.ship.y = main.respawn[game.state.counters.respawn].y
  152. game.state.ship.oldx = game.state.ship.x -- initialize integrator with vel 0
  153. game.state.ship.oldy = game.state.ship.y
  154. -- Can't find a good place for this at the moment.
  155. game.state.counters.shields = game.state.counters.shields - 1
  156. end
  157. -- shortcuts
  158. enemies, ship, orb, orbs, counters =
  159. game.state.enemies, game.state.ship, game.state.orb, game.state.orbs,
  160. game.state.counters
  161. -- Clean up to start again
  162. orbbatch:clear()
  163. -- Add decoys
  164. for k, v in ipairs(decoys) do
  165. orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  166. end
  167. -- Add normal orbs
  168. for k, v in ipairs(orbs) do
  169. v.sprite = orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  170. end
  171. -- Update map with current progress
  172. tgt_index = #orbs + (orb and 1 or 0)
  173. for k, v in ipairs(targets) do
  174. if k <= tgt_index then
  175. map[v.y*128+v.x+1] = v.empty
  176. else
  177. map[v.y*128+v.x+1] = v.tile
  178. end
  179. end
  180. -- force refresh
  181. lastxtile = false
  182. -- assume no key pressed
  183. was_turning = false
  184. shooting = false
  185. -- initialize first frame
  186. game.update(0)
  187. end
  188. local function update_ship(dt)
  189. if dt == 0 then return end
  190. --[[ update angle ]]
  191. local angvel = 16
  192. local kleft, kright = lk.isDown(keys.left), lk.isDown(keys.right)
  193. if kleft and not kright then
  194. if was_turning then
  195. fineangle = (fineangle - angvel*dt) % 32
  196. ship.angle = math.ceil(fineangle) % 32
  197. else
  198. was_turning = true -- sharp reaction upon pressing
  199. fineangle = (ship.angle - 1) % 32
  200. ship.angle = fineangle
  201. end
  202. elseif kright and not kleft then
  203. if was_turning then
  204. fineangle = (fineangle + angvel*dt) % 32
  205. ship.angle = math.floor(fineangle)
  206. else
  207. was_turning = true
  208. fineangle = (ship.angle + 1) % 32
  209. ship.angle = fineangle
  210. end
  211. else
  212. was_turning = false
  213. fineangle = ship.angle
  214. end
  215. --[[ update ship (Verlet integrator) ]]
  216. -- flight parameters
  217. local thrust = 380
  218. local gravity = 45
  219. local dragx = 0.997
  220. local dragy = 0.998
  221. local shipmass = 1
  222. local dt2 = dt*dt
  223. local shipforcex, shipforcey = 0, gravity * shipmass
  224. thrusting = lk.isDown(keys.thrust)
  225. if thrusting then
  226. shipforcex = shipforcex + math.sin(fineangle*math.pi/16) * thrust
  227. shipforcey = shipforcey - math.cos(fineangle*math.pi/16) * thrust
  228. end
  229. local orbforcex = 0
  230. local orbforcey = 0
  231. local orbaccelx = 0
  232. local orbaccely = 0
  233. local neworbx
  234. local neworby
  235. local newshipx = ship.x
  236. local newshipy = ship.y
  237. local orbmass
  238. if orb then
  239. orbmass = orb.m * shipmass -- orb mass is how heavier than the ship
  240. neworbx = orb.x
  241. neworby = orb.y
  242. orbforcex = 0
  243. orbforcey = gravity * orbmass
  244. orbaccelx = orbforcex / orbmass
  245. orbaccely = orbforcey / orbmass
  246. neworbx = neworbx + (orb.x - orb.oldx) * dragx + orbaccelx * dt2
  247. neworby = neworby + (orb.y - orb.oldy) * dragy + orbaccely * dt2
  248. end
  249. local accelx = shipforcex / shipmass
  250. local accely = shipforcey / shipmass
  251. newshipx = newshipx + (newshipx - ship.oldx) * dragx + accelx * dt2
  252. newshipy = newshipy + (newshipy - ship.oldy) * dragy + accely * dt2
  253. if orb then
  254. -- Apply constraint
  255. local ropex = newshipx - neworbx
  256. local ropey = newshipy - neworby
  257. local actuallen = math.sqrt(ropex*ropex + ropey*ropey)
  258. if actuallen < 0.00001 then actuallen = 0.00001 end -- should never happen
  259. -- the distance to adjust for is tractor_length - actuallen
  260. -- ours is a rope - remove this condition if original behavior wanted
  261. if actuallen > tractor_length then
  262. -- Force of the ship over the orb
  263. local k = 30
  264. if #argv > 1 then k = tonumber(argv[2]) or 0 end
  265. shipforcex = (tractor_length - actuallen) * ropex / actuallen
  266. * k
  267. shipforcey = (tractor_length - actuallen) * ropey / actuallen
  268. * k
  269. orbforcex = -(tractor_length - actuallen) * ropex / actuallen
  270. * k
  271. orbforcey = -(tractor_length - actuallen) * ropey / actuallen
  272. * k
  273. accelx = shipforcex / shipmass
  274. accely = shipforcey / shipmass
  275. newshipx = newshipx + accelx * dt2
  276. newshipy = newshipy + accely * dt2
  277. orbaccelx = orbforcex / orbmass
  278. orbaccely = orbforcey / orbmass
  279. neworbx = neworbx + orbaccelx * dt2
  280. neworby = neworby + orbaccely * dt2
  281. end
  282. -- "Scroll" orb position
  283. orb.oldx = orb.x
  284. orb.oldy = orb.y
  285. orb.x = neworbx
  286. orb.y = neworby
  287. end
  288. -- "Scroll" ship position
  289. ship.oldx = ship.x
  290. ship.oldy = ship.y
  291. ship.x = newshipx
  292. ship.y = newshipy
  293. -- Normalize if reasonable
  294. if not orb or math.floor(ship.x / 4096) == math.floor(orb.x / 4096) then
  295. local k = math.floor(ship.x / 4096) * 4096
  296. ship.x = ship.x - k
  297. ship.oldx = ship.oldx - k
  298. if orb then orb.x = orb.x - k orb.oldx = orb.oldx - k end
  299. end
  300. end
  301. function game.newgame()
  302. new_or_load_game(main.restore)
  303. end
  304. local function collided()
  305. -- Collision test - paint the collision tiles/sprites to a canvas
  306. -- and multiply by ship's collision sprite to see if there are
  307. -- common pixels (all zeros = no)
  308. lg.setCanvas(collision_canvas)
  309. -- Clear collision canvas
  310. collision_canvas:clear()
  311. -- In order to only do getImageData only once, our canvas is
  312. -- divided into two halves vertically. The top part is for
  313. -- the ship, the bottom part is for the orb.
  314. for i = 0, (orb and 32 or 0), 32 do -- Draw twice if an orb is carried, else once.
  315. lg.setScissor(0, i, 32, 32) -- select which half to be drawn
  316. local topleftx = i == 0 and math.floor(ship.x-15.5)%4096 or math.floor(orb.x-16)%4096
  317. local toplefty = i == 0 and math.floor(ship.y-15.5) or math.floor(orb.y-16)
  318. -- Colision with tile
  319. local localx = -(topleftx%32)
  320. local localy = -(toplefty%32)
  321. local tilex = (topleftx+localx)/32
  322. local tiley = (toplefty+localy)/32
  323. if tiley > 62 then tiley = 62 end
  324. if tiley < 0 then tiley = 0 end
  325. lg.draw(tilecoll, tilequads[map[tilex%128 + tiley*128 + 1]], localx, localy+i)
  326. if localx + 32 < 32 then
  327. lg.draw(tilecoll, tilequads[map[(tilex+1)%128 + tiley*128 + 1]], localx+32, localy+i)
  328. end
  329. if localy + 32 < 32 then
  330. lg.draw(tilecoll, tilequads[map[tilex%128 + (tiley+1)*128 + 1]], localx, localy+32+i)
  331. end
  332. if localx + 32 < 32 and localy + 32 < 32 then
  333. lg.draw(tilecoll, tilequads[map[(tilex+1)%128 + (tiley+1)*128 + 1]], localx+32, localy+32+i)
  334. end
  335. -- Collision with decoys
  336. local vx, vy
  337. for k, v in ipairs(decoys) do
  338. vx = v.x-16 - topleftx - (topleftx < 32 and v.x >= 4064 and 4096 or 0)
  339. vy = v.y-16 - toplefty
  340. if vx > -32 and vx < 32 and vy > -32 and vy < 32 then -- only draw if in range
  341. lg.draw(spritecoll, spritequads[orb_sprite], vx, vy + i)
  342. end
  343. end
  344. -- Same for orbs
  345. for k, v in ipairs(orbs) do
  346. vx = v.x-16 - topleftx - (topleftx < 32 and v.x >= 4064 and 4096 or 0)
  347. vy = v.y-16 - toplefty
  348. if vx > -32 and vx < 32 and vy > -32 and vy < 32 then -- only draw if in range
  349. lg.draw(spritecoll, spritequads[orb_sprite], vx, vy + i)
  350. end
  351. end
  352. if i ~= 0 then
  353. -- only collide the orb with the ship, not with itself
  354. -- they are guaranteed to be in the same multiple of 4096
  355. vx = math.floor(ship.x-15.5) - topleftx
  356. vy = math.floor(ship.y-15.5) - toplefty
  357. lg.draw(spritecoll, spritequads[ship.angle + 1], vx, vy + i)
  358. end
  359. -- Collision with enemies
  360. local et
  361. for k, v in ipairs(enemies) do
  362. et = enemytypes[v.type]
  363. vx = math.floor(v.x/2)*2 - topleftx - (topleftx < et.width*32 and v.x >= 4096-et.width*32 and 4096 or 0)
  364. vy = math.floor(v.y/2)*2 - toplefty
  365. if vx > -(et.width*32) and vx < 32 and vy > -(et.height*32) and vy < 32 then
  366. -- visible
  367. lg.draw(spriteset, spritequads[v.f], vx, vy + i)
  368. end
  369. end
  370. -- Draw ship/orb in multiplicative mode
  371. lg.setBlendMode("multiplicative")
  372. lg.draw(spritecoll, spritequads[i == 0 and ship.angle+1 or orb_sprite], 0, i)
  373. lg.setBlendMode("alpha") -- return blend mode to normal
  374. end
  375. lg.setScissor()
  376. lg.setCanvas()
  377. return collision_canvas:getImageData():getString() ~= no_collision
  378. end
  379. function game.update(dt)
  380. garbagetimer = garbagetimer + dt
  381. if garbagetimer >= 5 then
  382. -- playing with Canvas:getImage tends to generate garbage that isn't
  383. -- collected, so we help Lua a bit here
  384. collectgarbage()
  385. garbagetimer = 0
  386. end
  387. lti.sleep(0.02) -- spare some CPU
  388. if dt > 0.05 then dt = 0.05 end -- smoothen the movement
  389. -- read keys
  390. local was_shooting = shooting
  391. shooting = lk.isDown(keys.fire)
  392. local pickup = lk.isDown(keys.pickup)
  393. if dt ~= 0 and collided() then
  394. -- Player died
  395. ship.x = respawn[counters.respawn].x
  396. ship.y = respawn[counters.respawn].y
  397. ship.oldx = ship.x
  398. ship.oldy = ship.y
  399. ship.angle = 0
  400. was_turning = false
  401. if orb then
  402. orb.x = ship.x
  403. orb.y = ship.y + tractor_length
  404. orb.oldx = orb.x
  405. orb.oldy = orb.y
  406. end
  407. if counters.shields == 0 then
  408. main.activate(gameover)
  409. end
  410. counters.shields = counters.shields - 1
  411. end
  412. -- Check if an orb was picked up
  413. tractor = pickup and tractor -- set to false if not picking up
  414. if tractor then
  415. if (orbs[tractor].x-ship.x)^2 + (orbs[tractor].y-ship.y)^2 >= tractor_length2 then
  416. -- Picked up orb
  417. orb = orbs[tractor]
  418. -- Remove the orb. The orbs table is unsorted, so for performance, to
  419. -- avoid scrolling (if removing a middle element) or creating a hole
  420. -- (if setting it to nil), we move the last element to this place.
  421. --table.remove(orbs, tractor) -- works, but this is presumably faster:
  422. orbs[tractor] = orbs[#orbs]
  423. table.remove(orbs)
  424. if #orbs % 4 == 1 then
  425. counters.shields = counters.shields + 1
  426. end
  427. game.state.orb = orb
  428. orb.oldx = orb.x
  429. orb.oldy = orb.y
  430. -- Sprites can't be deleted, so regenerate the batch.
  431. orbbatch:clear()
  432. -- Add decoys
  433. for k, v in ipairs(decoys) do
  434. orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  435. end
  436. -- Add normal orbs
  437. for k, v in ipairs(orbs) do
  438. v.sprite = orbbatch:add(spritequads[orb_sprite], v.x-16, v.y-16)
  439. end
  440. -- This is an alternative to regenerating the batch, but still draws
  441. -- a 0x0 sprite.
  442. --orbbatch:set(orb.sprite, 0,0,0,0,0)
  443. orb.sprite = nil
  444. tractor = false
  445. end
  446. end
  447. -- Update player
  448. update_ship(dt)
  449. -- Clamp vertically
  450. if ship.y < 64 then ship.y = 64 end
  451. if ship.y > 2038 then ship.y = 2038 end
  452. -- Assign respawn zone
  453. for k, v in ipairs(respawn) do
  454. if ship.x%4096 >= v.topleftx and ship.x%4096 < v.topleftx + v.w
  455. and ship.y >= v.toplefty and ship.y < v.toplefty + v.h
  456. then
  457. counters.respawn = k
  458. break
  459. end
  460. end
  461. -- Target animation timer
  462. tgt_timer = tgt_timer + dt
  463. if tgt_timer / tgt_fps >= 1 then
  464. tgt_timer = tgt_timer % tgt_fps
  465. tgt_tile_current = (tgt_tile_current - tgt_tile_start + 1) % tgt_frames + tgt_tile_start
  466. end
  467. local tgt = targets[tgt_index]
  468. if orb and tgt and dt > 0 then
  469. -- Check if orb is in target
  470. local orbvelx = (orb.x - orb.oldx) / dt
  471. local orbvely = (orb.y - orb.oldy) / dt
  472. if orbvelx*orbvelx + orbvely*orbvely < tgt_vel_threshold2 then
  473. if orb.x >= tgt.x*32 and orb.x < tgt.x*32+32
  474. and orb.y >= tgt.y*32 and orb.y < tgt.y*32+32
  475. then
  476. orb = nil
  477. game.state.orb = nil
  478. map[tgt.y*128+tgt.x+1] = tgt.tile
  479. tgt_index = tgt_index - 1
  480. -- Force refresh of map
  481. lastxtile = false
  482. end
  483. end
  484. end
  485. -- Agent timer
  486. if counters.agent then
  487. counters.agentinterval = counters.agentinterval + dt
  488. if counters.agentinterval / 0.625 >= 1 then
  489. counters.agentinterval = counters.agentinterval - 0.625
  490. counters.agenttime = counters.agenttime - 1
  491. if counters.agenttime <= 0 then
  492. counters.agenttime = nil
  493. counters.agent = nil
  494. counters.agentinterval = nil
  495. end
  496. end
  497. end
  498. -- Check if agent picked up.
  499. if pickup and (not counters.agenttime or counters.agenttime < 290) then
  500. local x, y = ship.x % 4096, ship.y + 64
  501. local t
  502. if y >= 0 and y < 2048 then
  503. t = agents[map[math.floor(x/32) + math.floor(y/32)*128 + 1]]
  504. if t then
  505. counters.agent = t
  506. counters.agenttime = 300
  507. counters.agentinterval = 0
  508. end
  509. end
  510. end
  511. -- Shoot timer
  512. if counters.shoot_timer then
  513. counters.shoot_timer = counters.shoot_timer - dt
  514. if counters.shoot_timer <= 0 then
  515. counters.shoot_timer = nil
  516. end
  517. end
  518. -- Check if shooting
  519. if shooting and not was_shooting and counters.agent and not counters.shoot_timer then
  520. counters.shoot_timer = 4
  521. counters.shoot_x = ship.x
  522. counters.shoot_y = ship.y
  523. local x1, y1, x2, y2
  524. local radius2 = shoot_radius*shoot_radius
  525. -- did it hit any enemies?
  526. local k, v, l
  527. k = 1
  528. l = #enemies
  529. while k <= l do -- iterate manually to safely delete elements
  530. v = enemies[k]
  531. if v.type == counters.agent then
  532. -- the bounding box must be completely enclosed within the radius
  533. -- (not too realistic, but a collision analysis for this sounds
  534. -- overkill)
  535. x1 = v.x - ship.x
  536. y1 = v.y - ship.y
  537. x2 = x1 + enemytypes[v.type].width*32
  538. y2 = y1 + enemytypes[v.type].height*32
  539. x1 = x1*x1
  540. x2 = x2*x2
  541. y1 = y1*y1
  542. y2 = y2*y2
  543. if x1 + y1 < radius2
  544. and x1 + y2 < radius2
  545. and x2 + y1 < radius2
  546. and x2 + y2 < radius2
  547. then
  548. -- Killed enemy
  549. enemies[k] = enemies[l]
  550. l = l - 1
  551. table.remove(enemies)
  552. else
  553. k = k + 1
  554. end
  555. else
  556. k = k + 1
  557. end
  558. end
  559. end
  560. -- Check if tractor reaches orb
  561. if pickup and not orb and not tractor then
  562. -- Window for consideration
  563. local sx1, sy1, sx2, sy2 = ship.x-tractor_length, ship.y-tractor_length,
  564. ship.x+tractor_length, ship.y+tractor_length
  565. for k, v in ipairs(orbs) do
  566. if v.x >= sx1 and v.x <= sx2 and v.y >= sy1 and v.y <= sy2 then
  567. -- *Might* be in range - do the more expensive Euclidean check
  568. if (v.x-ship.x)^2 + (v.y-ship.y)^2 <= tractor_length2 then
  569. -- In range - set tractor
  570. tractor = k
  571. break
  572. end
  573. end
  574. end
  575. end
  576. -- Update enemies' sprites and positions
  577. counters.clock = counters.clock + dt
  578. local t, tpos, et
  579. for k, v in ipairs(enemies) do
  580. -- Position
  581. tpos = (v.t + counters.clock) % v.period
  582. if tpos * 2 >= v.period then
  583. -- going back
  584. t = (v.period - tpos)*2 / v.period
  585. else
  586. t = tpos * 2 / v.period
  587. end
  588. -- Hack because our horizontal positions are broken
  589. if v.x0 == v.x1 then vx1 = v.x1 else vx1 = v.x1+2 end
  590. v.x = v.x0 * (1 - t) + vx1 * t
  591. v.y = v.y0 * (1 - t) + v.y1 * t
  592. -- Frame
  593. et = enemytypes[v.type]
  594. -- t = (v.frame + counters.clock * v.fps) % et.nframes
  595. if vx1 ~= v.x0 then
  596. t = (tpos/(v.period*16)*(vx1-v.x0)*v.fps) % et.nframes
  597. else
  598. t = (tpos/(v.period*16)*(v.y1-v.y0)*v.fps) % et.nframes
  599. end
  600. if et.pingpong then
  601. if tpos * 2 >= v.period then -- going backwards
  602. t = (et.nframes-t) % et.nframes
  603. end
  604. end
  605. v.f = et.initsprite + math.floor(t)
  606. end
  607. -- Debug string
  608. -- game.DEBUG=tostring(collided()).. " " .. 1/dt
  609. end
  610. function game.tiles_draw(x, y)
  611. -- Clamp coordinates to acceptable values
  612. --if y < 0 then y = 0 end
  613. --if y > 2048 - game.vh then y = 2048 - game.vh end
  614. x = x % 4096
  615. local xtile = math.floor(x/32)
  616. local ytile = math.floor(y/32)
  617. local xtiles = math.floor(game.vw+62)/32 -- max visible tiles
  618. local ytiles = math.floor(game.vh+62)/32
  619. if ytile + ytiles > 64 then
  620. -- clamp vertically
  621. ytiles = 64 - ytile
  622. end
  623. if xtile ~= lastxtile or ytile ~= lastytile then
  624. -- update required
  625. lastxtile, lastytile = xtile, ytile
  626. tilebatch:clear()
  627. for yt = ytile, ytile + ytiles - 1 do
  628. for xt = xtile, xtile + xtiles - 1 do
  629. tilebatch:add(tilequads[map[yt*128 + xt%128 + 1]],
  630. (xt-xtile)*32, (yt-ytile)*32)
  631. end
  632. end
  633. end
  634. lg.draw(tilebatch, -(x%32), -(y%32))
  635. lg.draw(orbbatch, -x, -y)
  636. -- Draw 1 screen to the left and/or 1 screen to the right if necessary
  637. if x < 32 then
  638. lg.draw(orbbatch, -x-4096, -y)
  639. end
  640. if x + game.vh >= 4064 then
  641. lg.draw(orbbatch, -x+4096, -y)
  642. end
  643. end
  644. function game.draw()
  645. local vpx = math.floor(ship.x+0.5-game.vw/2)
  646. local vpy = math.floor(ship.y+0.5-game.vh/2)
  647. if vpy < 0 then vpy = 0 end
  648. if vpy > 2048-game.vh then vpy = 2048-game.vh end
  649. lg.setScissor(game.vx, game.vy, game.vw, game.vh)
  650. game.tiles_draw(vpx, vpy)
  651. -- HACK: draw target in white first (we will colorize it later using multiplicative)
  652. local tgt = targets[tgt_index]
  653. if tgt then
  654. lg.draw(tileset, tilequads[75], tgt.x*32-vpx, tgt.y*32-vpy)
  655. end
  656. -- draw enemies
  657. local et
  658. for k, v in ipairs(enemies) do
  659. et = enemytypes[v.type]
  660. for x = v.x - 4096, vpx + game.vw, 4096 do
  661. if not (x >= vpx + game.vw
  662. or x+et.width*32 < vpx
  663. or v.y >= vpy + game.vh
  664. or v.y+et.height*32 < vpy)
  665. then
  666. -- visible
  667. lg.draw(spriteset, spritequads[v.f],
  668. (math.floor(x/2)*2 - vpx), (math.floor(v.y/2)*2 - vpy)
  669. )
  670. end
  671. end
  672. end
  673. -- draw orb and tractor line
  674. if orb or tractor then
  675. lg.setLineStyle("smooth")
  676. local torb = orb or orbs[tractor] -- tracted orb - either carried or still resting
  677. lg.line(torb.x-vpx, torb.y-vpy, ship.x-vpx, ship.y-vpy)
  678. lg.draw(spriteset, spritequads[orb_sprite], math.floor(torb.x-vpx-16), math.floor(torb.y-vpy-16))
  679. end
  680. -- draw ship
  681. lg.draw(spriteset, spritequads[ship.angle+1], math.floor(ship.x-vpx-15.5), math.floor(ship.y-vpy-15.5))
  682. -- HACK: draw target in multiplicative mode (colorizes other sprites)
  683. if tgt then
  684. lg.setBlendMode("multiplicative")
  685. lg.draw(tileset, tilequads[tgt_tile_current], tgt.x*32-vpx, tgt.y*32-vpy)
  686. lg.setBlendMode("alpha")
  687. end
  688. -- draw shooting explosion
  689. if counters.shoot_timer then
  690. lg.setColor(255, 255, 255, 255*(counters.shoot_timer/4))
  691. lg.circle("fill", (counters.shoot_x-vpx)%4096, counters.shoot_y-vpy, shoot_radius, 64)
  692. lg.setColor(255, 255, 255, 255)
  693. end
  694. -- FIXME: draw current agent
  695. if counters.agent then
  696. for k, v in pairs(agents) do
  697. if v == counters.agent then
  698. lg.draw(tileset, tilequads[k], 0, main.wh-32)
  699. lg.print(counters.agenttime, 35, main.wh-24)
  700. end
  701. end
  702. end
  703. -- FIXME: draw current shields
  704. lg.print((counters.shields < 10 and "0" or "") .. counters.shields, 96, main.wh-24)
  705. lg.setScissor()
  706. --[[ debug
  707. lg.print(game.DEBUG, 0, 0)
  708. -- draw collision canvas
  709. lg.setColor(100,100,100)
  710. lg.rectangle("fill", 100, 100, 32, 64)
  711. lg.setColor(255,255,255)
  712. lg.draw(collision_canvas, 100, 100)
  713. ]]
  714. end
  715. function game.keypressed(k, r)
  716. if r then return end
  717. if k == "pause" and (lk.isDown("lctrl") or lk.isDown("rctrl") or lk.isDown("ctrl")) then
  718. main.activate(menu)
  719. end
  720. if k == "f10" then game.savegame() end
  721. if k == "f3" then new_or_load_game(true) end
  722. end
  723. return game