1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us> 2# 3# Permission is hereby granted, free of charge, to any person 4# obtaining a copy of this software and associated documentation files 5# (the "Software"), to deal in the Software without restriction, 6# including without limitation the rights to use, copy, modify, merge, 7# publish, distribute, sublicense, and/or sell copies of the Software, 8# and to permit persons to whom the Software is furnished to do so, 9# subject to the following conditions: 10# 11# The above copyright notice and this permission notice shall be 12# included in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22# This module is intended to be used as a singleton object. 23# It's purpose is to store in one global all of the data that would 24# be to annoying to lug around otherwise. 25 26from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 27from renpy.compat import * 28 29import renpy.display 30 31# The basepath. 32basepath = None 33 34# A list of paths that we search to load things. This is searched for 35# everything that can be loaded, before archives are used. 36searchpath = [ ] 37 38# The options that were read off the command line. 39args = None 40 41# The game's script. 42script = None 43 44# A stack of execution contexts. 45contexts = [ ] 46 47# The interface that the game uses to interact with the user. 48interface = None 49 50# Are we inside lint? 51lint = False 52 53# The RollbackLog that keeps track of changes to the game state 54# and to the store. 55log = None 56 57# Some useful additional information about program execution that 58# can be added to the exception. 59exception_info = '' 60 61# Used to store style information. 62style = None 63 64# The set of statements we've seen in this session. 65seen_session = { } 66 67# The number of entries in persistent._seen_translates that are also in 68# the current game. 69seen_translates_count = 0 70 71# The number of new translates we've seen today. 72new_translates_count = 0 73 74# True if we're in the first interaction after a rollback or rollforward. 75after_rollback = False 76 77# Code that's run after the init code. 78post_init = [ ] 79 80# Should we attempt to run in a mode that uses less memory? 81less_memory = False 82 83# Should we attempt to run in a mode that minimizes the number 84# of screen updates? 85less_updates = False 86 87# Should we never show the mouse? 88less_mouse = False 89 90# Should we not imagedissiolve? 91less_imagedissolve = False 92 93# The persistent data that's kept from session to session 94persistent = None 95 96# The current preferences. 97preferences = None 98 99 100class ExceptionInfo(object): 101 """ 102 Context manager that sets exception_info iff an exception occurs. 103 104 `s` 105 A percent-format string to use. 106 `args` 107 The arguments that are percent-formatted with `s`. 108 """ 109 110 def __init__(self, s, args): 111 self.s = s 112 self.args = args 113 114 def __enter__(self): 115 return 116 117 def __exit__(self, exc_type, exc_val, exc_tb): 118 if exc_type: 119 renpy.game.exception_info = self.s % self.args 120 121 return False 122 123 124class RestartContext(Exception): 125 """ 126 Restarts the current context. If `label` is given, calls that label 127 in the restarted context. 128 """ 129 130 131class RestartTopContext(Exception): 132 """ 133 Restarts the top context. If `label` is given, calls that label 134 in the restarted context. 135 """ 136 137 138class FullRestartException(Exception): 139 """ 140 An exception of this type forces a hard restart, completely 141 destroying the store and config and so on. 142 """ 143 144 def __init__(self, reason="end_game"): # W0231 145 self.reason = reason 146 147 148class UtterRestartException(Exception): 149 """ 150 An exception of this type forces an even harder restart, causing 151 Ren'Py and the script to be reloaded. 152 """ 153 154 155class QuitException(Exception): 156 """ 157 An exception of this class will let us force a safe quit, from 158 anywhere in the program. 159 160 `relaunch` 161 If given, the program will run another copy of itself, with the 162 same arguments. 163 164 `status` 165 The status code Ren'Py will return to the operating system. 166 """ 167 168 def __init__(self, relaunch=False, status=0): 169 Exception.__init__(self) 170 self.relaunch = relaunch 171 self.status = status 172 173 174class JumpException(Exception): 175 """ 176 This should be raised with a label as the only argument. This causes 177 the current statement to terminate, and execution to be transferred 178 to the named label. 179 """ 180 181 182class JumpOutException(Exception): 183 """ 184 This should be raised with a label as the only argument. This exits 185 the current context, and then raises a JumpException. 186 """ 187 188 189class CallException(Exception): 190 """ 191 Raise this exception to cause the current statement to terminate, 192 and control to be transferred to the named label. 193 """ 194 195 from_current = False 196 197 def __init__(self, label, args, kwargs, from_current=False): 198 Exception.__init__(self) 199 200 self.label = label 201 self.args = args 202 self.kwargs = kwargs 203 self.from_current = from_current 204 205 def __reduce__(self): 206 return (CallException, (self.label, self.args, self.kwargs, self.from_current)) 207 208 209class EndReplay(Exception): 210 """ 211 Raise this exception to end the current replay (the current call to 212 call_replay). 213 """ 214 215 216class ParseErrorException(Exception): 217 """ 218 This is raised when a parse error occurs, after it has been 219 reported to the user. 220 """ 221 222 223# A tuple of exceptions that should not be caught by the 224# exception reporting mechanism. 225CONTROL_EXCEPTIONS = ( 226 RestartContext, 227 RestartTopContext, 228 FullRestartException, 229 UtterRestartException, 230 QuitException, 231 JumpException, 232 JumpOutException, 233 CallException, 234 EndReplay, 235 ParseErrorException, 236 KeyboardInterrupt, 237 ) 238 239 240def context(index=-1): 241 """ 242 Return the current execution context, or the context at the 243 given index if one is specified. 244 """ 245 246 return contexts[index] 247 248 249def invoke_in_new_context(callable, *args, **kwargs): # @ReservedAssignment 250 """ 251 :doc: label 252 253 This function creates a new context, and invokes the given Python 254 callable (function) in that context. When the function returns 255 or raises an exception, control returns to the the original context. 256 It's generally used to call a Python function that needs to display 257 information to the player (like a confirmation prompt) from inside 258 an event handler. 259 260 A context maintains the state of the display (including what screens 261 and images are being shown) and the audio system. Both are restored 262 when the context returns. 263 264 Additional arguments and keyword arguments are passed to the 265 callable. 266 267 A context created with this function cannot execute Ren'Py script. 268 Functions that would change the flow of Ren'Py script, like 269 :func:`renpy.jump`, are handled by the outer context. If you want 270 to call Ren'Py script rather than a Python function, use 271 :func:`renpy.call_in_new_context` instead. 272 """ 273 274 restart_context = False 275 276 context = renpy.execution.Context(False, contexts[-1], clear=True) 277 contexts.append(context) 278 279 if renpy.display.interface is not None: 280 renpy.display.interface.enter_context() 281 282 try: 283 284 return callable(*args, **kwargs) 285 286 except renpy.game.RestartContext: 287 restart_context = True 288 raise 289 290 except renpy.game.RestartTopContext: 291 restart_context = True 292 raise 293 294 except renpy.game.JumpOutException as e: 295 296 contexts[-2].force_checkpoint = True 297 contexts[-2].abnormal = True 298 raise renpy.game.JumpException(e.args[0]) 299 300 finally: 301 302 if not restart_context: 303 context.pop_all_dynamic() 304 305 contexts.pop() 306 contexts[-1].do_deferred_rollback() 307 308 if interface and interface.restart_interaction and contexts: 309 contexts[-1].scene_lists.focused = None 310 311 312def call_in_new_context(label, *args, **kwargs): 313 """ 314 :doc: label 315 316 This creates a new context, and then starts executing Ren'Py script 317 from the given label in that context. Rollback is disabled in the 318 new context, and saving/loading will occur in the top level 319 context. 320 321 Use this to begin a second interaction with the user while 322 inside an interaction. 323 """ 324 325 context = renpy.execution.Context(False, contexts[-1], clear=True) 326 contexts.append(context) 327 328 if renpy.display.interface is not None: 329 renpy.display.interface.enter_context() 330 331 if args: 332 renpy.store._args = args 333 else: 334 renpy.store._args = None 335 336 if kwargs: 337 renpy.store._kwargs = renpy.python.RevertableDict(kwargs) 338 else: 339 renpy.store._kwargs = None 340 341 try: 342 343 context.goto_label(label) 344 return renpy.execution.run_context(False) 345 346 except renpy.game.JumpOutException as e: 347 contexts[-2].force_checkpoint = True 348 contexts[-2].abnormal = True 349 raise renpy.game.JumpException(e.args[0]) 350 351 finally: 352 353 contexts.pop() 354 contexts[-1].do_deferred_rollback() 355 356 if interface and interface.restart_interaction and contexts: 357 contexts[-1].scene_lists.focused = None 358 359 360def call_replay(label, scope={}): 361 """ 362 :doc: replay 363 364 Calls a label as a memory. 365 366 Keyword arguments are used to set the initial values of variables in the 367 memory context. 368 """ 369 370 renpy.game.log.complete() 371 372 old_log = renpy.game.log 373 renpy.game.log = renpy.python.RollbackLog() 374 375 sb = renpy.python.StoreBackup() 376 renpy.python.clean_stores() 377 378 context = renpy.execution.Context(True) 379 contexts.append(context) 380 381 if renpy.display.interface is not None: 382 renpy.display.interface.enter_context() 383 384 # This has to be here, to ensure the scope stuff works. 385 renpy.exports.execute_default_statement() 386 387 for k, v in renpy.config.replay_scope.items(): 388 setattr(renpy.store, k, v) 389 390 for k, v in scope.items(): 391 setattr(renpy.store, k, v) 392 393 renpy.store._in_replay = label 394 395 try: 396 397 context.goto_label("_start_replay") 398 renpy.execution.run_context(False) 399 400 except EndReplay: 401 pass 402 403 finally: 404 405 context.pop_all_dynamic() 406 407 contexts.pop() 408 renpy.game.log = old_log 409 sb.restore() 410 411 if interface and interface.restart_interaction and contexts: 412 contexts[-1].scene_lists.focused = None 413 414 renpy.config.skipping = None 415 416 if renpy.config.after_replay_callback: 417 renpy.config.after_replay_callback() 418 419 420# Type information. 421if False: 422 script = renpy.script.Script() 423 interface = renpy.display.core.Interface() 424 log = renpy.python.RollbackLog() 425 preferences = renpy.preferences.Preferences() 426