1 /** @file pause.cpp  Pausing the game.
2  *
3  * @authors Copyright © 1999-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2014 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 1993-1996 id Software, Inc.
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "common.h"
23 #include "pause.h"
24 
25 #include "d_net.h"
26 #include "d_netcl.h"
27 #include "d_netsv.h"
28 #include "g_common.h"
29 #include "hu_menu.h"
30 #include "hu_msg.h"
31 
32 using namespace de;
33 using namespace common;
34 
35 #define PAUSEF_PAUSED           0x1
36 #define PAUSEF_FORCED_PERIOD    0x2
37 
38 int paused;
39 
40 static int gamePauseWhenFocusLost; // cvar
41 static int gameUnpauseWhenFocusGained; // cvar
42 
43 #ifdef __JDOOM__
44 /// How long to pause the game after a map has been loaded (cvar).
45 /// - -1: matches the engine's busy transition tics.
46 static int gamePauseAfterMapStartTics = -1; // cvar
47 #else
48 // Crossfade doesn't require a very long pause.
49 static int gamePauseAfterMapStartTics = 7; // cvar
50 #endif
51 
52 static int forcedPeriodTicsRemaining;
53 
beginPause(int flags)54 static void beginPause(int flags)
55 {
56     if(!paused)
57     {
58         paused = PAUSEF_PAUSED | flags;
59 
60         // This will stop all sounds from all origins.
61         /// @todo Would be nice if the engine supported actually pausing the sounds. -jk
62         S_StopSound(0, 0);
63 
64         // Servers are responsible for informing clients about
65         // pauses in the game.
66         NetSv_Paused(paused);
67     }
68 }
69 
endPause()70 static void endPause()
71 {
72     if(paused)
73     {
74         LOG_VERBOSE("Pause ends (state:%i)") << paused;
75 
76         forcedPeriodTicsRemaining = 0;
77 
78         if(!(paused & PAUSEF_FORCED_PERIOD))
79         {
80             // Any impulses or accumulated relative offsets that occured
81             // during the pause should be ignored.
82             DD_Execute(true, "resetctlaccum");
83         }
84 
85         NetSv_Paused(0);
86     }
87     paused = 0;
88 }
89 
checkForcedPeriod()90 static void checkForcedPeriod()
91 {
92     if((paused != 0) && (paused & PAUSEF_FORCED_PERIOD))
93     {
94         if(forcedPeriodTicsRemaining-- <= 0)
95         {
96             endPause();
97         }
98     }
99 }
100 
Pause_IsPaused()101 dd_bool Pause_IsPaused()
102 {
103     return (paused != 0) || (!IS_NETGAME && (Hu_MenuIsActive() || Hu_IsMessageActive()));
104 }
105 
Pause_IsUserPaused()106 dd_bool Pause_IsUserPaused()
107 {
108     return (paused != 0) && !(paused & PAUSEF_FORCED_PERIOD);
109 }
110 
D_CMD(Pause)111 D_CMD(Pause)
112 {
113     DENG2_UNUSED3(src, argc, argv);
114 
115     if(G_QuitInProgress())
116         return false;
117 
118     // Toggle pause.
119     Pause_Set(!(paused & PAUSEF_PAUSED));
120     return true;
121 }
122 
Pause_Set(dd_bool yes)123 void Pause_Set(dd_bool yes)
124 {
125     // Can we start a pause?
126     if(Hu_MenuIsActive() || Hu_IsMessageActive() || IS_CLIENT)
127         return; // Nope.
128 
129     if(yes)
130         beginPause(0);
131     else
132         endPause();
133 }
134 
Pause_End()135 void Pause_End()
136 {
137     endPause();
138 }
139 
Pause_SetForcedPeriod(int tics)140 void Pause_SetForcedPeriod(int tics)
141 {
142     if(tics <= 0) return;
143 
144     LOG_MSG("Forced pause for %i tics") << tics;
145 
146     forcedPeriodTicsRemaining = tics;
147     beginPause(PAUSEF_FORCED_PERIOD);
148 }
149 
Pause_Ticker()150 void Pause_Ticker()
151 {
152     checkForcedPeriod();
153 }
154 
Pause_Responder(event_t * ev)155 dd_bool Pause_Responder(event_t *ev)
156 {
157     if(ev->type == EV_FOCUS)
158     {
159         if(gamePauseWhenFocusLost && !ev->data1)
160         {
161             Pause_Set(true);
162             return true;
163         }
164         else if(gameUnpauseWhenFocusGained && ev->data1)
165         {
166             Pause_Set(false);
167             return true;
168         }
169     }
170     return false;
171 }
172 
Pause_MapStarted()173 void Pause_MapStarted()
174 {
175     if(!IS_CLIENT)
176     {
177         if(gamePauseAfterMapStartTics < 0)
178         {
179             // Use the engine's transition visualization duration.
180             Pause_SetForcedPeriod(Con_GetInteger("con-transition-tics"));
181         }
182         else
183         {
184             // Use the configured time.
185             Pause_SetForcedPeriod(gamePauseAfterMapStartTics);
186         }
187     }
188 }
189 
Pause_Register()190 void Pause_Register()
191 {
192     forcedPeriodTicsRemaining = 0;
193 
194     // Default values (overridden by values from .cfg files).
195     gamePauseWhenFocusLost     = true;
196     gameUnpauseWhenFocusGained = false;
197 
198     C_CMD("pause", "", Pause);
199 
200 #define READONLYCVAR    (CVF_READ_ONLY|CVF_NO_MAX|CVF_NO_MIN|CVF_NO_ARCHIVE)
201 
202     C_VAR_INT("game-paused",              &paused,                     READONLYCVAR, 0,  0);
203     C_VAR_INT("game-pause-focuslost",     &gamePauseWhenFocusLost,     0,            0,  1);
204     C_VAR_INT("game-unpause-focusgained", &gameUnpauseWhenFocusGained, 0,            0,  1);
205     C_VAR_INT("game-pause-mapstart-tics", &gamePauseAfterMapStartTics, 0,            -1, 70);
206 
207 #undef READONLYCVAR
208 }
209 
NetSv_Paused(int pauseState)210 void NetSv_Paused(int pauseState)
211 {
212     if(!IS_SERVER || !IS_NETGAME)
213         return;
214 
215     writer_s *writer = D_NetWrite();
216     Writer_WriteByte(writer,
217             (pauseState & PAUSEF_PAUSED?        1 : 0) |
218             (pauseState & PAUSEF_FORCED_PERIOD? 2 : 0));
219     Net_SendPacket(DDSP_ALL_PLAYERS, GPT_PAUSE, Writer_Data(writer), Writer_Size(writer));
220 }
221 
NetCl_Paused(reader_s * msg)222 void NetCl_Paused(reader_s *msg)
223 {
224     byte flags = Reader_ReadByte(msg);
225     paused = 0;
226     if(flags & 1)
227     {
228         paused |= PAUSEF_PAUSED;
229     }
230     if(flags & 2)
231     {
232         paused |= PAUSEF_FORCED_PERIOD;
233     }
234     DD_SetInteger(DD_CLIENT_PAUSED, paused != 0);
235 }
236