File: ingame.lua

package info (click to toggle)
mrrescue 1.02e-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 13,228 kB
  • sloc: makefile: 9; sh: 1
file content (527 lines) | stat: -rw-r--r-- 14,590 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
ingame = {}

local lg = love.graphics
INGAME_ACTIVE, INGAME_FADE_IN, INGAME_NEXTLEVEL_OUT, INGAME_FALL_OUT, INGAME_PRESCREEN,
INGAME_GAMEOVER_OUT, INGAME_GAMEOVER, INGAME_WON, INGAME_COUNTDOWN, INGAME_COUNTDOWN_IN = 0,1,2,3,4,5,6,7,8,9

COMBO_TIME = 4

function ingame.enter()
	state = STATE_INGAME
	translate_x, translate_y = 0,0
	
	stopMusic()
	ingame.newGame()
end

function ingame.newGame()
	ingame_state = INGAME_COUNTDOWN_IN
	max_casualties = 6-level
	ingame.shake = 0

	casualties = 0
	score = 0
	saved = 0
	section = 1
	last_missed = 0
	last_rescue = 0
	combo = 0
	max_combo = 0
	time = 0

	transition_time = 0
	warning_frame = 0

	map = Map.create(section, level)
	local startx, starty = map:getStart()
	player = Player.create(startx,starty,level)
end

function ingame.nextLevel()
	last_missed = #map.humans
	casualties = casualties + last_missed
	stats[6] = stats[6] + 3

	if casualties >= max_casualties then
		ingame_state = INGAME_GAMEOVER
	else
		ingame_state = INGAME_PRESCREEN
		score = score + 1000
		section = section + 1
		map = Map.create(section, level)
		player:warp(map:getStart())
	end
end

function ingame.update(dt)
	if love.keyboard.isDown("e") then
		dt = dt/4
	end

	updateKeys()

	-- INGAME STATE
	if ingame_state == INGAME_ACTIVE then
		time = time + dt

		-- Check combo counter
		last_rescue = last_rescue + dt
		if last_rescue > COMBO_TIME then
			max_combo = math.max(max_combo, combo)
			combo = 0
		end

		-- Update map entities
		map:update(dt)

		-- Update entities
		player:update(dt)

		-- Calculate translation offest
		translate_x = cap(player.x-WIDTH/2, 0, MAPW-WIDTH)
		translate_y = cap(player.y-11-HEIGHT/2, 0, MAPH-HEIGHT+30)
		if ingame.shake > 0 then
			ingame.shake = ingame.shake - dt
		end
		map:setDrawRange(translate_x, translate_y, WIDTH, HEIGHT)

		-- Set next level transition state if player has climbed out of the screen
		if player.y < 0 then
			ingame_state = INGAME_NEXTLEVEL_OUT
			transition_time = 0
		-- Set out of screen transition if player has fallen out of screen
		elseif player.y > MAPH+32 then
			ingame_state = INGAME_FALL_OUT
			transition_time = 0
		end

		-- Update warning icon frame
		warning_frame = (warning_frame + dt*2) % 2

		-- Kill player if too many casualties
		if casualties >= max_casualties then
			player.temperature = player.max_temperature
		end

	-- Transition TO or FROM next level
	elseif ingame_state == INGAME_NEXTLEVEL_OUT or ingame_state == INGAME_FALL_OUT
	or ingame_state == INGAME_FADE_IN or ingame_state == INGAME_GAMEOVER_OUT
	or ingame_state == INGAME_COUNTDOWN_IN then
		transition_time = transition_time + dt*15

		-- Calculate translation offest
		translate_x = cap(player.x-WIDTH/2, 0, MAPW-WIDTH)
		translate_y = cap(player.y-11-HEIGHT/2, 0, MAPH-HEIGHT+30)

		map:setDrawRange(translate_x, translate_y, WIDTH, HEIGHT)
		map:update(dt)

		if transition_time > 20 then
			if ingame_state == INGAME_NEXTLEVEL_OUT then
				ingame.nextLevel()
				setPrescreenMessage()
			elseif ingame_state == INGAME_FALL_OUT then
				player:warp(map:getStart())
				ingame_state = INGAME_FADE_IN
			elseif ingame_state == INGAME_FADE_IN then
				ingame_state = INGAME_ACTIVE
			elseif ingame_state == INGAME_COUNTDOWN_IN then
				ingame_state = INGAME_COUNTDOWN
				playSound("countdown")
			elseif ingame_state == INGAME_GAMEOVER_OUT then
				setPrescreenMessage()
				ingame_state = INGAME_GAMEOVER
			end
			transition_time = 0
		end
	elseif ingame_state == INGAME_PRESCREEN or ingame_state == INGAME_GAMEOVER then
		transition_time = transition_time + dt*3
	elseif ingame_state == INGAME_WON then
		if translate_y > -HEIGHT then
			translate_y = translate_y - dt*20
		end
		map:setDrawRange(translate_x, translate_y, WIDTH, HEIGHT)
		map:update(dt)
	elseif ingame_state == INGAME_COUNTDOWN then
		-- Calculate translation offest
		translate_x = cap(player.x-WIDTH/2, 0, MAPW-WIDTH)
		translate_y = cap(player.y-11-HEIGHT/2, 0, MAPH-HEIGHT+30)

		map:setDrawRange(translate_x, translate_y, WIDTH, HEIGHT)
		map:update(dt)

		transition_time = transition_time + dt
		if transition_time >= 3.5 then
			ingame_state = INGAME_ACTIVE
			playMusic(table.random({"rockerronni","bundesliga","scooterfest"}))
		end
	end
end

function ingame.draw()
	-- Scale screen
	lg.push()
	lg.scale(config.scale)

	if ingame_state == INGAME_ACTIVE or ingame_state == INGAME_FADE_IN
	or ingame_state == INGAME_NEXTLEVEL_OUT or ingame_state == INGAME_FALL_OUT
	or ingame_state == INGAME_GAMEOVER_OUT or ingame_state == INGAME_WON
	or ingame_state == INGAME_COUNTDOWN_IN or ingame_state == INGAME_COUNTDOWN then
		lg.push()
		-- Translate to center player
		if ingame.shake > 0 then
			lg.translate(-math.floor(translate_x+math.random()*4-2), -math.floor(translate_y+math.random()*2.5))
		else
			lg.translate(-math.floor(translate_x), -math.floor(translate_y))
		end

		-- Draw back
		map:drawBack()
		-- Draw player
		player:draw()
		-- Draw front
		map:drawFront()

		if player.state == PS_DEAD then
			player:draw()
		end

		lg.pop()

		if map.type == MT_NORMAL then
			lg.setBlendMode("multiply", "premultiplied")
			lg.draw(canvas, 0,0)
			lg.setBlendMode("alpha")
		end

		-- Draw red screen if hit
		if player.heat > 0 then
			lg.setColor(1,1,1,cap(player.heat*255, 16, 255)/255)
			lg.draw(img.red_screen, quad.red_screen, 0,0)
			lg.setColor(1,1,1,1)
		end

		-- Draw hud
		drawHUD()

		-- Draw transition if eligible
		if ingame_state == INGAME_NEXTLEVEL_OUT or ingame_state == INGAME_FALL_OUT or ingame_state == INGAME_GAMEOVER_OUT then
			local frame = math.floor(transition_time)
			lg.pop()
			--lg.pop()
			lg.push()
			lg.scale(config.scale)

			for ix = 0,7 do
				for iy = 0,6 do
					lg.draw(img.circles, quad.circles[math.max(0,math.min(frame-13+ix+iy,6))], ix*32, iy*32)
				end
			end
		end
		if ingame_state == INGAME_FADE_IN or ingame_state == INGAME_COUNTDOWN_IN then
			local frame = math.floor(transition_time)

			for ix = 0,7 do
				for iy = 0,6 do
					lg.draw(img.circles, quad.circles[6-math.max(0,math.min(frame-13+ix+iy,6))], ix*32, iy*32)
				end
			end
		end
		if ingame_state == INGAME_WON then
			if translate_y < 0 then
				drawWonMessage()
			end
		end
		if ingame_state == INGAME_COUNTDOWN then
			local frame = math.floor(transition_time)
			lg.draw(img.countdown, quad.countdown[frame], 96, 87)
		end

	elseif ingame_state == INGAME_PRESCREEN then
		drawPrescreen()
	
	elseif ingame_state == INGAME_GAMEOVER then
		drawGameover()
	end

	lg.pop()
end
 
function drawWonMessage()
	local alpha = cap((-translate_y)/100, 0, 1)
	lg.setColor(0,0,0,alpha*255)
	lg.rectangle("fill", 0, 40, WIDTH, #WON_MESSAGES[level]*10+12)
	lg.setColor(255,255,255,alpha*255)
	for i,v in ipairs(WON_MESSAGES[level]) do
		lg.printf(v, 0, 48+(i-1)*10, WIDTH, "center")
	end
	lg.setColor(255,255,255,255)
end

function drawHUD()
	lg.draw(img.hud, 0, HEIGHT-32)

	-- Draw water tank bar
	local water_ratio = player.water / player.water_capacity
	quad.water_bar:setViewport(0, 0, math.floor(water_ratio*55+0.5), 11)
	if player.overloaded == false then
		if player.hasReserve == true then
			lg.draw(img.reserve_bar, quad.water_bar, 10, HEIGHT-22)
		else
			lg.draw(img.water_bar, quad.water_bar, 10, HEIGHT-22)
		end
	else
		lg.draw(img.overloaded_bar, quad.water_bar, 10, HEIGHT-22)
	end

	-- Draw temperature bar
	local temp_length = math.floor((player.temperature/player.max_temperature)*82+0.5)
	quad.temperature_bar:setViewport(0,0, temp_length, 6)
	lg.draw(img.temperature_bar, quad.temperature_bar, 75, HEIGHT-25)
	lg.draw(img.temperature_bar, quad.temperature_bar_end, 75+temp_length, HEIGHT-25)

	-- Draw casualty count
	for i=1,max_casualties do
		if i<= casualties then
			lg.draw(img.hud_people, quad.hud_people_red, 168+(i-1)*5, HEIGHT-25)
		else
			lg.draw(img.hud_people, quad.hud_people_green, 168+(i-1)*5, HEIGHT-25)
		end
	end

	-- Draw second HUD layer
	lg.draw(img.hud2, 0, HEIGHT-32)

	-- Blink temperature bar if needed
	if player:isDying() then
		local color = 0
		if warning_frame <= 1 then
			color = 30 + warning_frame * 225
		else
			color = 255 - (warning_frame % 1)*225
		end
		lg.setColor(255,color,color)
		lg.draw(img.temperature_bar_blink, 74, HEIGHT-26)
		lg.setColor(255,255,255)
	end

	-- Draw item slots
	for i=1,3 do
		if i <= player.num_regens then
			lg.draw(img.item_slots, quad.item_slot_regen, 78+(i-1)*6, HEIGHT-14)
		end
		if i <= player.num_tanks then
			lg.draw(img.item_slots, quad.item_slot_tank, 100+(i-1)*6, HEIGHT-14)
		end
		if i <= player.num_suits then
			lg.draw(img.item_slots, quad.item_slot_suit, 122+(i-1)*6, HEIGHT-14)
		end
	end

	-- Draw score
	lg.setFont(font.bold)
	lg.setColor(16,12,9)
	lg.print("SCORE: "..score,150,187)
	lg.setColor(246,247,221)
	lg.print("SCORE: "..score,150,186)
	lg.setColor(255,255,255)

	-- Draw boss health bar
	if map.type == MT_BOSS and ingame_state ~= INGAME_WON then
		lg.draw(img.boss_health, quad.boss_health, 0, 11)
		local boss_length = math.floor((map.boss.health/map.boss.MAX_HEALTH)*178+0.5)
		lg.draw(img.boss_health, quad.boss_bar, 64,22, 0, boss_length, 1)
		lg.draw(img.boss_health, quad.boss_bar_end, 64+boss_length,22, 0)

		local bossframe = 0
		if map.boss.angry == true then bossframe = bossframe + 2 end
		if map.boss.hit == true or map.boss.state == BS_DEAD then bossframe = bossframe + 1 end
		lg.draw(map.boss:getPortraitImage(), quad.boss_portrait[bossframe], 15,15)
	end

	-- Draw panic/burning human icons
	drawIcons()
end

function drawPrescreen()
	local floor = section*3-2
	lg.setFont(font.bold)
	if map.type == MT_NORMAL then
		lg.printf("FLOOR ".. floor .. "-" .. floor+2, 0, 40, WIDTH, "center")
		lg.draw(img.captain_dialog, quad.prescreen_music, 7, 183)
	else
		lg.printf("ROOF", 0, 40, WIDTH, "center")
	end

	local fr = math.floor(transition_time) % 2
	lg.draw(img.captain_dialog, quad.captain_dialog[fr], 28, 72)

	lg.printf(prescreen_message, 74, 80, 150, "left")

	lg.printf("PRESS RETURN TO CONTINUE", 0, 150, WIDTH, "center")
end

function drawGameover()
	local fr = math.floor(transition_time) % 2
	lg.draw(img.captain_dialog_sad, quad.captain_dialog[fr], 28, 72)

	if casualties >= max_casualties then
		lg.printf(prescreen_message, 74, 80, 140, "left")
	elseif player.state == PS_DEAD then
		lg.printf(prescreen_message, 74, 80, 140, "left")
	end
end

function setPrescreenMessage()
	if casualties >= max_casualties then
		prescreen_message = "TOO MANY CIVILIANS HAVE DIED!\n\nYOU ARE FIRED!"
	elseif player.state == PS_DEAD then
		prescreen_message = "YOUR SUIT OVERHEATED!\n\nGAME OVER"
	else
		if section == 1 then
			prescreen_message = table.random(GOODLUCK_MESSAGES)
		elseif map.type == MT_BOSS then
			prescreen_message = BOSS_MESSAGE[level]
			playMusic("roof")
		elseif last_missed > 0 then
			if last_missed == 1 then
				prescreen_message = "HEY THERE, BUDDY!\nYOU MISSED 1 PERSON.\nTRY A LITTLE HARDER."
			else
				prescreen_message = "HEY THERE, BUDDY!\nYOU LET "..last_missed.." PEOPLE BURN TO DEATH.\nTRY A LITTLE HARDER."
			end
		else
			prescreen_message = table.random(NO_CASUALTIES_MESSAGES)
		end
	end
end

--- Draws warning icons for panicing/burning/dead enemies
function drawIcons()
	-- Draw for panicing/burning humans
	for i,v in ipairs(map.humans) do
		if (v.state == HS_BURN or v.state == HS_PANIC) and
		(v.x < translate_x or v.x > translate_x+WIDTH or v.y < translate_y or v.y > translate_y+174) then
			if v.state == HS_BURN then
				drawIcon(v.x, v.y-12, 0)
			else
				drawIcon(v.x, v.y-12, 2)
			end
		end
	end
	-- Draw for ashes particles (dead humans)
	for i,v in ipairs(map.particles) do
		if v.isAshes == true and
		(v.x < translate_x or v.x > translate_x+WIDTH or v.y < translate_y or v.y > translate_y+174) then
			drawIcon(v.x, v.y-12, 4)
		end
	end
end

--- Draws a warning icon for an entity.
--  Does not check if entity is actually outside screen.
--  @param x X position of entity
--  @param y Y position of entity
--  @param frame_offset Offset into warning icon quad array
function drawIcon(x,y,frame_offset)
	local frame = cap(frame_offset + math.floor(warning_frame), 0, 4)
	local deltax = x - (translate_x+WIDTH/2)
	local deltay = y - (translate_y+84)

	local xt,yt
	if deltax > 0 then xt = 114/deltax
	else xt = -114/deltax end
	if deltay > 0 then yt = 72/deltay
	else yt = -72/deltay end

	local t
	if xt > yt then t = yt
	else t = xt end
	
	lg.draw(img.warning_icons, quad.warning_icons[frame], WIDTH/2+t*deltax, 84+t*deltay, 0,1,1,11,10)
end

--- Updates the light map canvas
--  Assumes the view matrix is translated but not scaled
function updateLightmap()
	if ingame_state == INGAME_ACTIVE or ingame_state == INGAME_FADE_IN
	or ingame_state == INGAME_NEXTLEVEL_OUT or ingame_state == INGAME_FALL_OUT
	or ingame_state == INGAME_GAMEOVER_OUT or ingame_state == INGAME_WON
	or ingame_state == INGAME_COUNTDOWN_IN or ingame_state == INGAME_COUNTDOWN then
		lg.push()
		lg.translate(-math.floor(translate_x), -math.floor(translate_y))
		-- canvas:clear(0,0,0,255)

		lg.setCanvas(canvas)
		lg.clear(0,0,0,255)
		lg.setBlendMode("add")

		lg.draw(img.light_player, player.flx-128, player.fly-138)
		map:drawFireLight()
		for i,v in ipairs(map.enemies) do
			v:drawLight()
		end
		
		-- Light up outside building
		lg.rectangle("fill", 0,0, 32, MAPH)
		lg.rectangle("fill", MAPW-32, 0, 32, MAPH)
		lg.setBlendMode("alpha")
		lg.setCanvas()
		lg.pop()
	end
end

function ingame.keypressed(k,uni)
	if ingame_state == INGAME_ACTIVE then
		if k == "escape" then
			ingame_menu.enter()
		elseif k == "p" then
			map:addParticle(PopupText.create("megacombo"))
		else
			for a, key in pairs(config.keys) do
				if k == key then
					player:action(a)
				end
			end
		end
	elseif ingame_state == INGAME_PRESCREEN then
		if k == "return" or k == " " then
			ingame_state = INGAME_FADE_IN
			transition_time = 0
		elseif k == "left" then
			prevSong()
		elseif k == "right" then
			nextSong()
		end
	elseif ingame_state == INGAME_GAMEOVER then
		if k == "return" or k == " " then
			summary.enter()
		end
	elseif ingame_state == INGAME_WON and translate_y < 0 then
		if k == "return" or k == " " then
			summary.enter()
		end
	end
end

function ingame.action(k)
	if ingame_state == INGAME_ACTIVE then
		if k == "pause" then
			ingame_menu.enter()
		else
			player:action(k)
		end
	elseif ingame_state == INGAME_PRESCREEN then
		if k == "left" then
			prevSong()
		elseif k == "right" then
			nextSong()
		else
			ingame_state = INGAME_FADE_IN
			transition_time = 0
		end
	elseif ingame_state == INGAME_GAMEOVER then
		summary.enter()
	elseif ingame_state == INGAME_WON and translate_y < 0 then
		summary.enter()
	end
end