@@ -54,6 +54,8 @@ def __init__(self, scale=3, fullscreen=False, crt=True, sound=True):
5454 self .difficulty = 1
5555 self .players = 1
5656 self .toast = None
57+ self .paused = False
58+ self .pause_menu = None
5759 self .enter_title ()
5860 self .audio .music (0 , 0 )
5961
@@ -127,6 +129,75 @@ def enter_title(self):
127129 "Leave the void alone." ],
128130 [data .CYAN , data .VIOLET , data .ORANGE , data .GREY ])
129131
132+ # Pause is a flag rather than a state.
133+ #
134+ # Every other screen is a state, but pause is the one thing that has to
135+ # happen *on top of* whatever is already there: a state would throw away
136+ # the combat, the map or the shop underneath, and the whole point of
137+ # pausing mid-fight is that the fight is still there when you come back.
138+ def toggle_pause (self ):
139+ if self .state in (TITLE , GAMEOVER ):
140+ return # nothing to come back to
141+ self .paused = not self .paused
142+ if self .paused :
143+ self .build_pause_menu ()
144+ self .audio .play ("menu_ok" , 0.6 )
145+ else :
146+ self .audio .play ("menu_move" )
147+
148+ def build_pause_menu (self ):
149+ crt = "ON" if self .crt_on else "OFF"
150+ self .pause_menu = ui .Menu (
151+ ["RESUME" , "SOUND " + self .audio .mode_name , "CRT " + crt ,
152+ "ABANDON RUN" , "QUIT" ],
153+ ["Back to it." ,
154+ "M does this too, mid-fight." ,
155+ "Scanlines. F1 does this too." ,
156+ "Give up this run and go back to the title." ,
157+ "Close the game." ],
158+ [data .CYAN , data .WHITE , data .WHITE , data .ORANGE , data .GREY ])
159+
160+ def pause_key (self , e ):
161+ before = self .pause_menu .index
162+ choice = self .pause_menu .key (e )
163+ if self .pause_menu .index != before :
164+ self .audio .play ("menu_move" )
165+ if choice is None :
166+ return
167+ if choice == 0 :
168+ self .toggle_pause ()
169+ elif choice == 1 :
170+ self .toast = [self .audio .cycle_mute (), 1.6 ]
171+ if not self .audio .muted :
172+ self .audio .music (self .run .sector if self .run else 0 ,
173+ self .run .seed if self .run else 0 )
174+ self .build_pause_menu ()
175+ elif choice == 2 :
176+ self .crt_on = not self .crt_on
177+ self .build_pause_menu ()
178+ elif choice == 3 :
179+ self .paused = False
180+ self .run = None
181+ self .combat = None
182+ self .audio .play ("menu_no" )
183+ self .enter_title ()
184+ self .audio .music (0 , 0 )
185+ elif choice == 4 :
186+ self .running = False
187+
188+ def draw_pause (self , c ):
189+ """Dim what is underneath, then the panel. The game stays visible on
190+ purpose: you paused to look at it."""
191+ w , h = c .get_size ()
192+ veil = pygame .Surface ((w , h ), pygame .SRCALPHA )
193+ veil .fill ((0 , 0 , 0 , 170 ))
194+ c .blit (veil , (0 , 0 ))
195+ ui .text (c , self .art , "PAUSED" , w // 2 , data .TOP + 18 , data .CYAN ,
196+ centre = True , big = True )
197+ self .pause_menu .draw (c , self .art , data .TOP + 54 , self .t )
198+ ui .text (c , self .art , "ESC or START to resume" , w // 2 , h - 16 ,
199+ data .DARK , centre = True )
200+
130201 def start_run (self , players ):
131202 self .players = players
132203 self .run = Run (players , self .difficulty )
@@ -339,6 +410,14 @@ def handle_event(self, e):
339410 return
340411 if e .type != pygame .KEYDOWN :
341412 return
413+ if e .key == pygame .K_ESCAPE :
414+ self .toggle_pause ()
415+ return
416+ if self .paused :
417+ # Nothing else gets a look in: F11 and the rest would otherwise
418+ # fire straight through the panel.
419+ self .pause_key (e )
420+ return
342421 if e .key == pygame .K_F11 or (e .key == pygame .K_RETURN and
343422 (e .mod & pygame .KMOD_ALT )):
344423 self .toggle_fullscreen ()
@@ -440,12 +519,28 @@ def pad_menu(self):
440519 """Turn pad presses into the key events the menus already understand,
441520 so there is exactly one place that knows what a menu does."""
442521 for edge in self .pads .menu_edges ():
443- key = {"up" : pygame .K_UP , "down" : pygame .K_DOWN ,
444- "confirm" : pygame .K_RETURN , "back" : pygame .K_BACKSPACE }[edge ]
522+ if edge == "start" :
523+ # Pause, except on the title where there is nothing to pause;
524+ # there it is the confirm button people expect it to be.
525+ key = (pygame .K_RETURN if self .state in (TITLE , GAMEOVER )
526+ else pygame .K_ESCAPE )
527+ else :
528+ key = {"up" : pygame .K_UP , "down" : pygame .K_DOWN ,
529+ "confirm" : pygame .K_RETURN ,
530+ "back" : pygame .K_BACKSPACE }[edge ]
445531 self .handle_event (pygame .event .Event (pygame .KEYDOWN , key = key , mod = 0 ))
446532
447533 def update (self , dt ):
448534 self .t += dt
535+ if self .paused :
536+ # The cursor still pulses and the toast still fades: a frozen menu
537+ # looks like a crash. Nothing else advances.
538+ if self .toast is not None :
539+ self .toast [1 ] -= dt
540+ if self .toast [1 ] <= 0 :
541+ self .toast = None
542+ self .pad_menu ()
543+ return
449544 self .flash .update (dt )
450545 if self .toast is not None :
451546 self .toast [1 ] -= dt
@@ -486,6 +581,9 @@ def draw(self):
486581 elif self .state == GAMEOVER :
487582 self .draw_gameover (c )
488583
584+ if self .paused :
585+ self .draw_pause (c )
586+ ox = oy = 0
489587 if self .toast is not None :
490588 self .draw_toast (c )
491589
0 commit comments