1'''The main game module. One big event loop in the run function plus a few helper functions.'''
2
3import pygame
4from pygame.locals import *
5import os
6import random
7
8from locals import *
9
10from player import Player
11from spider import Spider
12from particle import Particle
13from level import Level
14from sound import play_sound
15from edit_utils import Edit_utils
16from util import *
17from variables import Variables
18from log import error_message
19from trigger import Trigger
20from visibleobject import flip_direction_from_position
21
22import data
23
24keys_released = {}
25
26def render_edit_utilities(screen):
27  return
28
29#This function renders the in-game GUI on the screen.
30def render_gui(screen, life, score, topleft):
31  score_image = render_text("Score: " + str(score) )
32  life_image = render_text("Life:")
33  #life_bar_bg_image = Util.cached_images["health_bar_empty"]
34  #life_bar_image = Util.cached_images["health_bar_fill"]
35  version_image = render_text("0.7.9")
36
37  rect = score_image.get_rect()
38  rect.left = topleft[0]
39  rect.top = topleft[1]
40  screen.blit(score_image, rect)
41
42  rect.left = topleft[0] + 26
43  rect.top = topleft[1] + 26
44  rect.width = 38
45  rect.height = 8
46  pygame.draw.rect(screen, COLOR_GUI_BG, rect)
47  if life > 0:
48    rect.left = topleft[0] + 27
49    rect.top = topleft[1] + 27
50    rect.width = life
51    rect.height = 6
52    pygame.draw.rect(screen, COLOR_BLOOD, rect)
53
54  rect = life_image.get_rect()
55  rect.left = topleft[0]
56  rect.top = topleft[1] + 20
57  screen.blit(life_image, rect)
58
59  rect = version_image.get_rect()
60  rect.right = SCREEN_WIDTH - 2
61  rect.bottom = SCREEN_HEIGHT - 2
62  screen.blit(version_image, rect)
63  return
64
65#This function parses inputs from the keyboard and returns them as an array
66def parse_inputs(joystick = None):
67  keys = pygame.key.get_pressed()
68  inputs = {}
69
70  if keys[K_LEFT]:
71    inputs["LEFT"] = True
72    if keys_released["K_LEFT"]:
73      inputs["EDIT_LEFT"] = True
74    keys_released["K_LEFT"] = False
75  else:
76    keys_released["K_LEFT"] = True
77
78  if keys[K_RIGHT]:
79    inputs["RIGHT"] = True
80    if keys_released["K_RIGHT"]:
81      inputs["EDIT_RIGHT"] = True
82    keys_released["K_RIGHT"] = False
83  else:
84    keys_released["K_RIGHT"] = True
85
86  if keys[K_DOWN]:
87    if keys_released["K_DOWN"]:
88      inputs["DOWN"] = True
89      inputs["EDIT_DOWN"] = True
90    keys_released["K_DOWN"] = False
91  else:
92    keys_released["K_DOWN"] = True
93
94  if keys[K_z]:
95    inputs["UP"] = True
96    if keys_released["K_z"]:
97      inputs["JUMP"] = True
98    keys_released["K_z"] = False
99  else:
100    keys_released["K_z"] = True
101
102  if keys[K_p]:
103    if keys_released["K_p"]:
104      inputs["PAUSE"] = True
105    keys_released["K_p"] = False
106  else:
107    keys_released["K_p"] = True
108
109  if keys[K_w]:
110    inputs["ADD_TILE_WALL"] = True
111
112  if keys[K_s]:
113    if keys_released["K_s"]:
114      inputs["ADD_TILE_SPIKES"] = True
115      if (keys[K_RCTRL] or keys[K_LCTRL]):
116        inputs["SAVE_TILES"] = True
117        del inputs["ADD_TILE_SPIKES"]
118    keys_released["K_s"] = False
119  else:
120    keys_released["K_s"] = True
121
122  if keys[K_b]:
123    inputs["ADD_TILE_BARS"] = True
124
125  if keys[K_d]:
126    inputs["REMOVE_TILE"] = True
127
128
129  if keys[K_UP]:
130    inputs["UP"] = True
131    if keys_released["K_UP"]:
132      inputs["JUMP"] = True
133      inputs["EDIT_UP"] = True
134    keys_released["K_UP"] = False
135  else:
136    keys_released["K_UP"] = True
137
138  if keys[K_F10]:
139    inputs["SPECIAL"] = True
140
141  if joystick != None:   # Parse joystick input
142
143#    axis0 = joystick.get_axis(0)
144
145#    if axis0 < -0.1:
146#      inputs["LEFT"] = True
147#      inputs["ANALOG"] = -axis0
148
149#    if axis0 > 0.1:
150#      inputs["RIGHT"] = True
151#      inputs["ANALOG"] = axis0
152
153    if joystick.get_numbuttons() > 1:
154      if joystick.get_button(0):
155        inputs["UP"] = True
156        if keys_released["J_B0"]:
157          inputs["JUMP"] = True
158        keys_released["J_B0"] = False
159      else:
160        keys_released["J_B0"] = True
161
162      if joystick.get_button(1):
163        if keys_released["J_B1"]:
164          inputs["DOWN"] = True
165        keys_released["J_B1"] = False
166      else:
167        keys_released["J_B1"] = True
168#    else:
169#      axis1 = joystick.get_axis(1)
170
171#      if axis1 < -0.1:
172#        inputs["UP"] = True
173#        if keys_released["J_A1U"]:
174#          inputs["JUMP"] = True
175#        keys_released["J_A1U"] = False
176#      else:
177#        keys_released["J_A1U"] = True
178
179      if axis1 > 0.1:
180        if keys_released["J_A1D"]:
181          inputs["DOWN"] = True
182        keys_released["J_A1D"] = False
183      else:
184        keys_released["J_A1D"] = True
185
186  return inputs
187
188
189def run(screen, level_name = "w0-l0", score_mod = 0, score = None, joystick = None):
190
191  if (Variables.vdict["devmode"]):
192    edit_utils = Edit_utils()
193
194  done = False
195  objects = []
196  particles = []
197
198  if score == None:
199    score = Score(0)
200
201  #try:
202  level = Level(screen, level_name)
203  #except:
204  #  error_message("Couldn't open level '" + level_name + "'")
205  #  return END_QUIT
206
207  objects = level.get_objects()
208  player = level.get_player()
209  objects.append(player)
210
211  player.life = score.life
212
213  clock = pygame.time.Clock()
214
215  end_trigger = END_NONE
216  scripted_event_on = False
217
218  #There's no music at the moment:
219  #pygame.mixer.music.load( data.filepath(os.path.join("music", "music.ogg")) )
220  #pygame.mixer.music.play(-1)
221
222  scripted_events = level.get_scripted_events()
223  current_scripted_event = None
224
225  scripted_event_trigger = TRIGGER_LEVEL_BEGIN
226
227  flip_wait = -1
228
229  keys_released["K_z"] = True
230  keys_released["K_p"] = True
231  keys_released["K_s"] = True
232  keys_released["K_DOWN"] = True
233  keys_released["K_LEFT"] = True
234  keys_released["K_RIGHT"] = True
235  keys_released["K_UP"] = True
236  keys_released["J_B0"] = True
237  keys_released["J_B1"] = True
238
239  fading = True
240  fade_target = FADE_STATE_NONE
241  Util.fade_state = FADE_STATE_BLACK
242
243  flip_trigger_position = (0, 0)
244
245  changing_level = False
246
247  paused = False
248
249  #Main game loop
250
251  while (end_trigger == END_NONE or fading):
252
253    # Pygame event and keyboard input processing
254    for event in pygame.event.get():
255      if event.type == QUIT:
256        end_trigger = END_HARD_QUIT
257      if (event.type == KEYDOWN and event.key == K_ESCAPE):
258        end_trigger = END_QUIT
259        if fading == False:
260          fading = True
261        fade_target = FADE_STATE_HALF
262
263    inputs = parse_inputs(joystick)
264
265    trigger = None
266
267    if scripted_event_on:
268      if inputs.has_key("JUMP") or inputs.has_key("DOWN"):
269        cleared = True
270
271    moved = False
272
273    add_time = False #The ingame time counter toggle - this is False when the player can't control the character
274
275    if not scripted_event_on and not level.flipping and not fading and not paused \
276    and player.current_animation != "dying" and player.current_animation != "exit":
277      #There isn't anything special going on: player can control the character
278      #Translates input to commands to the player object
279      add_time = True
280      if inputs.has_key("LEFT"):
281        player.move((-PLAYER_MAX_ACC, 0))
282        moved = True
283
284      if inputs.has_key("RIGHT"):
285        player.move((PLAYER_MAX_ACC, 0))
286        moved = True
287
288      if inputs.has_key("JUMP"):
289        if (player.on_ground):
290          count = 0
291          while (count < 5):
292            count += 1
293            particles.append(Particle(screen, 10, player.rect.centerx - player.dx / 4 + random.uniform(-3, 3), player.rect.bottom, -player.dx * 0.1, -0.5, 0.3, level.dust_color, 4))
294          player.jump()
295
296          #The blobs always try to jump when the player jumps
297
298          for o in objects:
299            if o.itemclass == "blob":
300              o.jump()
301
302      if inputs.has_key("UP") and not player.on_ground:
303        player.jump()
304
305      if inputs.has_key("DOWN"):
306        pick_up_item = level.pick_up(player.x, player.y)
307        if pick_up_item != None:
308          play_sound("coins")
309          player.inventory.append(pick_up_item)
310          scripted_event_trigger = pick_up_item.itemclass
311
312        #If the level is not flipping at the moment, the player can trigger stuff in the level
313        if flip_wait == -1:
314          trigger = level.trigger(player.x, player.y)
315
316      #Debug command for flipping:
317      if inputs.has_key("SPECIAL"):
318        trigger = Trigger(TRIGGER_FLIP, player.x, player.y)
319
320    if inputs.has_key("PAUSE") and player.current_animation != "dying":
321      paused = not paused
322
323    #Decelerates the player, if he doesn't press any movement keys or when he is dead and on the ground
324    if ((player.current_animation != "dying" and not moved) or (player.current_animation == "dying" and player.on_ground)) and not paused:
325      player.dec((PLAYER_MAX_ACC, 0))
326
327    if trigger != None and trigger.trigger_type == TRIGGER_FLIP:
328      if flip_wait == -1:
329        flip_wait = 0
330        flip_trigger_position = (trigger.x, trigger.y)
331        play_sound("woosh")
332
333    if flip_wait != -1 and not paused:
334      flip_wait += 1
335      if flip_wait > FLIP_DELAY:
336        flip_direction = flip_direction_from_position(flip_trigger_position)
337        flip_wait = -1
338        level.flip(flip_direction)
339        for o in objects:
340          o.flip(flip_direction)
341        for p in particles:
342          p.flip()
343
344    #Dust effect rising from the character's feet:
345
346    if (player.current_animation == "walking"):
347      particles.append(Particle(screen, 10, player.rect.centerx - player.dx / 2 + random.uniform(-2, 2), player.rect.bottom, -player.dx * 0.1, 0.1, 0.3, level.dust_color))
348
349    #Updating level and objects:
350
351    if scripted_event_trigger == None:
352      scripted_event_trigger = level.update()
353    else:
354      level.update()
355
356    #Objects are only updated when there's not a scripted event going on
357
358    normal_updating = not scripted_event_on and not fading and not paused
359
360    if changing_level:
361      player.update(level)
362    elif normal_updating:
363      for o in objects:
364        if o.dead and o.itemclass != "player":
365          objects.remove(o)
366          continue
367        new_particles = o.update(level)
368        if o.itemclass == "projectile":
369          if player.rect.collidepoint(o.x, o.y) and o.current_animation == "default":
370            new_particles = player.take_damage(o.damage)
371            o.die()
372        if type(new_particles) == list: #Sometimes the type of the return value is int (hackity hack)
373          if new_particles != None:
374            for p in new_particles:
375              particles.append(p)
376
377    if normal_updating or changing_level:
378      for p in particles:
379        p.update()
380        if p.dead:
381          particles.remove(p)
382
383    #Rendering level - background and tiles
384    level.render()
385
386    #Rendering objects and particles
387    for o in objects:
388      if o.itemclass == "player":
389        o.render(None, None, (fading or paused) )
390      else:
391        o.render(None, None, (scripted_event_on or fading or paused) )
392      #On special conditions the animations aren't updated. The player is updated on a scripted event, others are not.
393
394    for p in particles:
395      p.render()
396
397    #Rendering GUI on top of game graphics:
398    if (not paused) or (not Variables.vdict["devmode"]):
399      render_gui(screen, player.life, score.score, (5, 5))
400
401    # Scripted event triggering:
402
403    if scripted_event_trigger != None:
404      if player.on_ground:
405        for ev in scripted_events:
406          if ev.trigger_type == scripted_event_trigger:
407            scripted_event_on = True
408            current_scripted_event = ev
409            current_scripted_event_element = None
410            text = None
411            phase = 0
412            cleared = False        # Clearing dialog boxes
413            player.dy = 0
414            player.dx = 0
415            player.update()
416            scripted_event_trigger = None
417
418    # Scripted event processing:
419
420    if scripted_event_on and not fading and not paused:
421      if (current_scripted_event_element == None) or (current_scripted_event_element.finished):
422
423        current_scripted_event_element = current_scripted_event.next_element()
424
425        if current_scripted_event_element.event_type == "end":
426          scripted_event_on = False
427          current_scripted_event_element = None
428
429      else:
430
431        if not Variables.vdict["dialogue"]:  #Dialogue skipping
432          while (current_scripted_event_element.event_type == "dialogue" or current_scripted_event_element.event_type == "player"):
433            current_scripted_event_element.finished = True
434            current_scripted_event_element = current_scripted_event.next_element()
435            if current_scripted_event_element.event_type == "end":
436              current_scripted_event_element.finished = True
437
438        if current_scripted_event_element.event_type == "wait":
439          current_scripted_event_element.finished = True
440        elif current_scripted_event_element.event_type == "dialogue":
441          if text == None:
442            text = current_scripted_event_element.text
443            phase = 0
444          phase = render_text_dialogue(screen, text, phase)
445          if (phase == -1) and cleared:
446            current_scripted_event_element.finished = True
447            phase = 0
448            cleared = False
449            text = None
450          if cleared:
451            phase = -1
452            cleared = False
453        elif current_scripted_event_element.event_type == "player":
454          if current_scripted_event_element.text == "orientation":
455            player.orientation = current_scripted_event_element.orientation
456          current_scripted_event_element.finished = True
457        elif current_scripted_event_element.event_type == "change_level":
458          score.score += (5 + score_mod) * ((player.life + 4) / 5 + 12)
459          score.levels += 1
460          current_scripted_event_element.finished = True
461          if player.current_animation != "gone":
462            player.exit()
463
464    if player.current_animation == "exit":
465      changing_level = True
466    elif changing_level:
467      end_trigger = END_NEXT_LEVEL
468      fading = True
469      fade_target = FADE_STATE_BLACK
470
471
472    if player.dead:
473      end_trigger = END_LOSE
474      fading = True
475      fade_target = FADE_STATE_HALF
476
477    #And finally, rendering the pause button:
478
479    if paused:
480      if (Variables.vdict["devmode"]):
481        change = edit_utils.update(inputs)
482        level.change(change)
483        edit_utils.render(screen)
484      else:
485        render_text_dialogue(screen, "Game paused. Press P to continue.", -1, "p")
486
487    #Render fading on top of everything else:
488
489    if (fading or Util.fade_state != FADE_STATE_NONE):
490      if fade_to_black(screen, fade_target):
491        #Fading finished
492        fading = False
493
494    if(add_time):
495      score.time += 1
496
497    #Display, clock
498
499    pygame.display.flip()
500
501    clock.tick(FPS)
502
503  #Main game loop finished
504
505  score.life = player.life #To make the player's health stay the same to the next level
506
507  return end_trigger
508