game.lua 36 KB

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