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# The Character object (and friends). 23 24from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 25from renpy.compat import * 26 27import renpy.display 28 29import re 30import os 31import collections 32import renpy.six as six 33 34# This matches the dialogue-relevant text tags. 35TAG_RE = re.compile(r'(\{\{)|(\{(p|w|nw|fast|done)(?:\=([^}]*))?\})', re.S) 36 37less_pauses = ("RENPY_LESS_PAUSES" in os.environ) 38 39 40class DialogueTextTags(object): 41 """ 42 This object parses the text tags that only make sense in dialogue, 43 like {fast}, {p}, {w}, and {nw}. 44 """ 45 46 def __init__(self, s): 47 48 # The text that we've accumulated, not including any tags. 49 self.text = "" 50 51 # The index in the produced string where each pause starts. 52 self.pause_start = [ 0 ] 53 54 # The index in the produced string where each pause ends. 55 self.pause_end = [ ] 56 57 # The time to delay for each pause. None to delay forever. 58 self.pause_delay = [ ] 59 60 # True if we've encountered the no-wait tag. 61 self.no_wait = False 62 63 # Does this statement have a done tag? 64 self.has_done = False 65 66 # Does this statement have a fast tag? 67 self.fast = False 68 69 i = iter(TAG_RE.split(s)) 70 71 while True: 72 73 try: 74 self.text += next(i) 75 76 quoted = next(i) 77 full_tag = next(i) 78 tag = next(i) 79 value = next(i) 80 81 if value is not None: 82 value = float(value) 83 84 if quoted is not None: 85 self.text += quoted 86 continue 87 88 if tag == "p" or tag == "w": 89 if not less_pauses: 90 self.pause_start.append(len(self.text)) 91 self.pause_end.append(len(self.text)) 92 self.pause_delay.append(value) 93 94 elif tag == "nw": 95 self.no_wait = True 96 97 elif tag == "fast": 98 self.pause_start = [ len(self.text) ] 99 self.pause_end = [ ] 100 self.pause_delay = [ ] 101 self.no_wait = False 102 self.fast = True 103 104 elif tag == "done": 105 self.has_done = True 106 self.text += full_tag 107 break 108 109 self.text += full_tag 110 111 except StopIteration: 112 break 113 114 self.pause_end.append(len(self.text)) 115 116 while True: 117 118 try: 119 self.text += next(i) 120 121 quoted = next(i) 122 full_tag = next(i) 123 tag = next(i) 124 value = next(i) 125 126 if quoted is not None: 127 self.text += quoted 128 continue 129 130 self.text += full_tag 131 132 except StopIteration: 133 break 134 135 if self.no_wait: 136 self.pause_delay.append(0) 137 else: 138 self.pause_delay.append(None) 139 140 141def predict_show_display_say(who, what, who_args, what_args, window_args, image=False, two_window=False, side_image=None, screen=None, properties=None, **kwargs): 142 """ 143 This is the default function used by Character to predict images that 144 will be used by show_display_say. It's called with more-or-less the 145 same parameters as show_display_say, and it's expected to return a 146 list of images used by show_display_say. 147 """ 148 149 if side_image: 150 renpy.easy.predict(side_image) 151 152 if renpy.store._side_image_attributes: 153 renpy.easy.predict(renpy.display.image.ImageReference(("side",) + renpy.store._side_image_attributes)) 154 155 if image: 156 if image != "<Dynamic>": 157 renpy.easy.predict(who) 158 159 kwargs["image"] = image 160 161 if screen: 162 props = compute_widget_properties(who_args, what_args, window_args, properties) 163 164 renpy.display.predict.screen( 165 screen, 166 _widget_properties=props, 167 who=who, 168 what=what, 169 two_window=two_window, 170 side_image=side_image, 171 **kwargs) 172 173 return 174 175 176def compute_widget_properties(who_args, what_args, window_args, properties, variant=None, multiple=None): 177 """ 178 Computes and returns the widget properties. 179 """ 180 181 def style_args(d, name): 182 183 style = d.get("style", None) 184 185 if style is None: 186 if multiple is None: 187 return d 188 else: 189 style = name 190 191 in_rollback = renpy.exports.in_rollback() 192 193 if (not in_rollback) and (not variant) and (not multiple): 194 return d 195 196 d = d.copy() 197 198 if isinstance(style, basestring): 199 200 if multiple is not None: 201 style = "block{}_multiple{}_{}".format(multiple[0], multiple[1], style) 202 203 style = getattr(renpy.store.style, style) 204 205 if variant is not None: 206 style = style[variant] 207 208 if in_rollback: 209 style = style["rollback"] 210 211 d["style"] = style 212 213 return d 214 215 who_args = style_args(who_args, "who") 216 what_args = style_args(what_args, "what") 217 window_args = style_args(window_args, "window") 218 219 rv = dict(properties) 220 221 for prefix in renpy.config.character_id_prefixes: 222 rv[prefix] = style_args(properties.get(prefix, {}), prefix) 223 224 rv["window"] = window_args 225 rv["what"] = what_args 226 rv["who"] = who_args 227 228 return rv 229 230 231def show_display_say(who, what, who_args={}, what_args={}, window_args={}, 232 image=False, side_image=None, two_window=False, 233 two_window_vbox_properties={}, 234 who_window_properties={}, 235 say_vbox_properties={}, 236 transform=None, 237 variant=None, 238 screen=None, 239 layer=None, 240 properties={}, 241 multiple=None, 242 **kwargs): 243 """ 244 This is called (by default) by renpy.display_say to add the 245 widgets corresponding to a screen of dialogue to the user. It is 246 not expected to be called by the user, but instead to be called by 247 display_say, or by a function passed as the show_function argument 248 to Character or display_say. 249 250 @param who: The name of the character that is speaking, or None to 251 not show this name to the user. 252 253 @param what: What that character is saying. Please not that this 254 may not be a string, as it can also be a list containing both text 255 and displayables, suitable for use as the first argument of ui.text(). 256 257 @param who_args: Additional keyword arguments intended to be 258 supplied to the ui.text that creates the who widget of this dialogue. 259 260 @param what_args: Additional keyword arguments intended to be 261 supplied to the ui.text that creates the what widget of this dialogue. 262 263 @param window_args: Additional keyword arguments intended to be 264 supplied to the ui.window that creates the who widget of this 265 dialogue. 266 267 @param image: If True, then who should be interpreted as an image 268 or displayable rather than a text string. 269 270 @param kwargs: Additional keyword arguments should be ignored. 271 272 This function is required to return the ui.text() widget 273 displaying the what text. 274 """ 275 276 props = compute_widget_properties(who_args, what_args, window_args, properties, variant=variant, multiple=multiple) 277 278 def handle_who(): 279 if who: 280 if image: 281 renpy.ui.add(renpy.display.im.image(who, loose=True, **props["who"])) 282 else: 283 renpy.ui.text(who, **who_args) 284 285 def merge_style(style, properties): 286 287 if isinstance(style, basestring): 288 style = getattr(renpy.store.style, style) 289 290 if variant is not None: 291 style = style[variant] 292 293 if renpy.exports.in_rollback(): 294 style = style["rollback"] 295 296 rv = dict(style=style) 297 rv.update(properties) 298 return rv 299 300 if screen and renpy.display.screen.has_screen(screen): 301 302 if layer is None: 303 layer = renpy.config.say_layer 304 305 tag = screen 306 index = 0 307 308 if multiple: 309 310 if renpy.display.screen.has_screen("multiple_" + screen): 311 screen = "multiple_" + screen 312 kwargs["multiple"] = multiple 313 314 tag = "block{}_multiple{}_{}".format(multiple[0], multiple[1], tag) 315 316 if image: 317 kwargs["image"] = image 318 319 if (side_image is not None) or renpy.config.old_say_args: 320 kwargs["side_image"] = side_image 321 322 if two_window or renpy.config.old_say_args: 323 kwargs["two_window"] = two_window 324 325 renpy.display.screen.show_screen( 326 screen, 327 _widget_properties=props, 328 _transient=True, 329 _tag=tag, 330 who=who, 331 what=what, 332 _layer=layer, 333 **kwargs) 334 335 renpy.exports.shown_window() 336 337 return (tag, "what", layer) 338 339 # Apply the transform. 340 if transform: 341 renpy.ui.at(transform) 342 343 if two_window: 344 345 # Opens say_two_window_vbox. 346 renpy.ui.vbox(**merge_style('say_two_window_vbox', two_window_vbox_properties)) 347 348 renpy.ui.window(**merge_style('say_who_window', who_window_properties)) 349 handle_who() 350 351 renpy.ui.window(**props["window"]) 352 # Opens the say_vbox. 353 renpy.ui.vbox(**merge_style('say_vbox', say_vbox_properties)) 354 355 if not two_window: 356 handle_who() 357 358 rv = renpy.ui.text(what, **props["what"]) 359 360 # Closes the say_vbox. 361 renpy.ui.close() 362 363 if two_window: 364 # Closes the say_two_window_vbox. 365 renpy.ui.close() 366 367 if side_image: 368 renpy.ui.image(side_image) 369 370 renpy.exports.shown_window() 371 372 return rv 373 374 375class SlowDone(object): 376 delay = None 377 ctc_kwargs = { } 378 last_pause = True 379 380 def __init__(self, ctc, ctc_position, callback, interact, type, cb_args, delay, ctc_kwargs, last_pause): # @ReservedAssignment 381 self.ctc = ctc 382 self.ctc_position = ctc_position 383 self.callback = callback 384 self.interact = interact 385 self.type = type 386 self.cb_args = cb_args 387 self.delay = delay 388 self.ctc_kwargs = ctc_kwargs 389 self.last_pause = last_pause 390 391 def __call__(self): 392 393 if self.interact and self.delay != 0: 394 395 if renpy.display.screen.has_screen("ctc"): 396 397 if self.ctc: 398 args = [ self.ctc ] 399 else: 400 args = [ ] 401 402 renpy.display.screen.show_screen("ctc", *args, _transient=True, _ignore_extra_kwargs=True, **self.ctc_kwargs) 403 renpy.exports.restart_interaction() 404 405 elif self.ctc and self.ctc_position == "fixed": 406 renpy.display.screen.show_screen("_ctc", _transient=True, ctc=self.ctc) 407 renpy.exports.restart_interaction() 408 409 if self.delay is not None: 410 renpy.ui.pausebehavior(self.delay, True, voice=self.last_pause) 411 renpy.exports.restart_interaction() 412 413 for c in self.callback: 414 c("slow_done", interact=self.interact, type=self.type, **self.cb_args) 415 416# This function takes care of repeatably showing the screen as part of 417# an interaction. 418 419 420def display_say( 421 who, 422 what, 423 show_function, 424 interact, 425 slow, 426 afm, 427 ctc, 428 ctc_pause, 429 ctc_position, 430 all_at_once, 431 cb_args, 432 with_none, 433 callback, 434 type, # @ReservedAssignment 435 checkpoint=True, 436 ctc_timedpause=None, 437 ctc_force=False, 438 advance=True, 439 multiple=None, 440 dtt=None): 441 442 # Final is true if this statement should perform an interaction. 443 444 if multiple is None: 445 final = interact 446 else: 447 step, total = multiple 448 449 if step == total: 450 final = interact 451 else: 452 final = False 453 454 if not final: 455 advance = False 456 457 if final and (not renpy.game.preferences.skip_unseen) and (not renpy.game.context().seen_current(True)) and renpy.config.skipping == "fast": 458 renpy.config.skipping = None 459 460 # If we're in fast skipping mode, don't bother with say 461 # statements at all. 462 if advance and renpy.config.skipping == "fast": 463 464 for i in renpy.config.fast_skipping_callbacks: 465 i() 466 467 # Clears out transients. 468 renpy.exports.with_statement(None) 469 return 470 471 # Figure out the callback(s) we want to use. 472 if callback is None: 473 if renpy.config.character_callback: 474 callback = [ renpy.config.character_callback ] 475 else: 476 callback = [ ] 477 478 if not isinstance(callback, list): 479 callback = [ callback ] 480 481 callback = renpy.config.all_character_callbacks + callback 482 483 # Call the begin callback. 484 for c in callback: 485 c("begin", interact=interact, type=type, **cb_args) 486 487 roll_forward = renpy.exports.roll_forward_info() 488 489 if roll_forward is True: 490 roll_forward = False 491 492 # If we're just after a rollback or roll_forward, disable slow. 493 after_rollback = renpy.game.after_rollback 494 if after_rollback: 495 slow = False 496 all_at_once = True 497 498 # If we're committed to skipping this statement, disable slow. 499 elif (renpy.config.skipping and 500 advance and 501 (renpy.game.preferences.skip_unseen or 502 renpy.game.context().seen_current(True))): 503 slow = False 504 all_at_once = True 505 506 # Figure out which pause we're on. (Or set the pause to None in 507 # order to put us in all-at-once mode.) 508 if not interact or renpy.game.preferences.self_voicing: 509 all_at_once = True 510 511 if dtt is None: 512 dtt = DialogueTextTags(what) 513 514 if all_at_once: 515 pause_start = [ dtt.pause_start[0] ] 516 pause_end = [ dtt.pause_end[-1] ] 517 pause_delay = [ dtt.pause_delay[-1] ] 518 else: 519 pause_start = dtt.pause_start 520 pause_end = dtt.pause_end 521 pause_delay = dtt.pause_delay 522 523 exception = None 524 525 if dtt.fast: 526 for i in renpy.config.say_sustain_callbacks: 527 i() 528 529 try: 530 531 for i, (start, end, delay) in enumerate(zip(pause_start, pause_end, pause_delay)): 532 533 # True if the is the last pause in a line of dialogue. 534 last_pause = (i == len(pause_start) - 1) 535 536 if dtt.no_wait: 537 last_pause = False 538 539 # If we're going to do an interaction, then saybehavior needs 540 # to be here. 541 if advance: 542 behavior = renpy.ui.saybehavior(allow_dismiss=renpy.config.say_allow_dismiss) 543 else: 544 behavior = None 545 546 # The string to show. 547 what_string = dtt.text 548 549 # Figure out the CTC to use, if any. 550 if last_pause: 551 what_ctc = ctc 552 ctc_kind = "last" 553 else: 554 if delay is not None: 555 what_ctc = ctc_timedpause or ctc_pause 556 ctc_kind = "timedpause" 557 else: 558 what_ctc = ctc_pause 559 ctc_kind = "pause" 560 561 ctc_kwargs = { 562 "ctc_kind" : ctc_kind, 563 "ctc_last" : ctc, 564 "ctc_pause" : ctc_pause, 565 "ctc_timedpause" : ctc_timedpause, 566 } 567 568 if not (interact or ctc_force): 569 what_ctc = None 570 571 what_ctc = renpy.easy.displayable_or_none(what_ctc) 572 573 if (what_ctc is not None) and what_ctc._duplicatable: 574 what_ctc = what_ctc._duplicate(None) 575 what_ctc._unique() 576 577 if ctc is not what_ctc: 578 if (ctc is not None) and ctc._duplicatable: 579 ctc = ctc._duplicate(None) 580 ctc._unique() 581 582 if delay == 0: 583 what_ctc = None 584 ctc = None 585 586 # Run the show callback. 587 for c in callback: 588 c("show", interact=interact, type=type, **cb_args) 589 590 # Create the callback that is called when the slow text is done. 591 slow_done = SlowDone(what_ctc, ctc_position, callback, interact, type, cb_args, delay, ctc_kwargs, last_pause) 592 593 # Show the text. 594 if multiple: 595 what_text = show_function(who, what_string, multiple=multiple) 596 else: 597 what_text = show_function(who, what_string) 598 599 if interact or what_string or (what_ctc is not None) or (behavior and afm): 600 601 if isinstance(what_text, tuple): 602 what_text = renpy.display.screen.get_widget(what_text[0], what_text[1], what_text[2]) 603 604 if not isinstance(what_text, renpy.text.text.Text): # @UndefinedVariable 605 raise Exception("The say screen (or show_function) must return a Text object.") 606 607 if what_ctc: 608 609 if ctc_position == "nestled": 610 what_text.set_ctc(what_ctc) 611 elif ctc_position == "nestled-close": 612 what_text.set_ctc([ u"\ufeff", what_ctc, ]) 613 614 if (not last_pause) and ctc: 615 if ctc_position == "nestled": 616 what_text.set_last_ctc(ctc) 617 elif ctc_position == "nestled-close": 618 what_text.set_last_ctc([ u"\ufeff", ctc, ]) 619 620 if what_text.text[0] == what_string: 621 622 # Update the properties of the what_text widget. 623 what_text.start = start 624 what_text.end = end 625 what_text.slow = slow 626 what_text.slow_done = slow_done 627 628 what_text.update() 629 630 elif renpy.config.developer: 631 raise Exception("The displayable with id 'what' was not given the exact contents of the what variable given to the say screen.") 632 633 if behavior and afm: 634 behavior.set_text(what_text) 635 636 else: 637 638 slow = False 639 640 for c in callback: 641 c("show_done", interact=interact, type=type, **cb_args) 642 643 if not slow: 644 slow_done() 645 646 if final: 647 rv = renpy.ui.interact(mouse='say', type=type, roll_forward=roll_forward) 648 649 # This is only the case if the user has rolled forward, {nw} happens, or 650 # maybe in some other obscure cases. 651 if rv is False: 652 break 653 654 if isinstance(rv, (renpy.game.JumpException, renpy.game.CallException)): 655 raise rv 656 657 if not last_pause: 658 for i in renpy.config.say_sustain_callbacks: 659 i() 660 661 except (renpy.game.JumpException, renpy.game.CallException) as e: 662 663 exception = e 664 665 # Do the checkpoint and with None. 666 if final: 667 668 if not dtt.no_wait: 669 if checkpoint: 670 if exception is None: 671 renpy.exports.checkpoint(True) 672 else: 673 renpy.exports.checkpoint(exception) 674 675 else: 676 renpy.game.after_rollback = after_rollback 677 678 if with_none is None: 679 with_none = renpy.config.implicit_with_none 680 681 renpy.plog(1, "before with none") 682 683 if with_none: 684 renpy.game.interface.do_with(None, None) 685 686 renpy.plog(1, "after with none") 687 688 for c in callback: 689 c("end", interact=interact, type=type, **cb_args) 690 691 if exception is not None: 692 raise 693 694 695class HistoryEntry(renpy.object.Object): 696 """ 697 Instances of this object are used to represent history entries in 698 _history_list. 699 """ 700 701 # See ADVCharacter.add_history for the fields. 702 703 multiple = None 704 705 def __repr__(self): 706 return "<History {!r} {!r}>".format(self.who, self.what) 707 708 709# This is used to flag values that haven't been set by the user. 710NotSet = renpy.object.Sentinel("NotSet") 711 712# The number of multiple characters we've seen during the current 713# interaction. 714multiple_count = 0 715 716 717class ADVCharacter(object): 718 """ 719 The character object contains information about a character. When 720 passed as the first argument to a say statement, it can control 721 the name that is displayed to the user, and the style of the label 722 showing the name, the text of the dialogue, and the window 723 containing both the label and the dialogue. 724 """ 725 726 # Properties beginning with what or window that are treated 727 # specially. 728 special_properties = [ 729 'what_prefix', 730 'what_suffix', 731 'who_prefix', 732 'who_suffix', 733 'show_function', 734 ] 735 736 voice_tag = None 737 properties = { } 738 739 _statement_name = None 740 741 # When adding a new argument here, remember to add it to copy below. 742 def __init__( 743 self, 744 name=NotSet, 745 kind=None, 746 **properties): 747 748 if kind is None: 749 kind = renpy.store.adv 750 751 if name is not NotSet: 752 properties["name"] = name 753 754 # This grabs a value out of properties, and then grabs it out of 755 # kind if it's not set. 756 def v(n): 757 if n in properties: 758 return properties.pop(n) 759 else: 760 return getattr(kind, n) 761 762 # Similar, but it grabs the value out of kind.display_args instead. 763 def d(n): 764 if n in properties: 765 return properties.pop(n) 766 else: 767 return kind.display_args[n] 768 769 self.name = v('name') 770 self.who_prefix = v('who_prefix') 771 self.who_suffix = v('who_suffix') 772 self.what_prefix = v('what_prefix') 773 self.what_suffix = v('what_suffix') 774 775 self.show_function = v('show_function') 776 self.predict_function = v('predict_function') 777 778 self.condition = v('condition') 779 self.dynamic = v('dynamic') 780 self.screen = v('screen') 781 self.mode = v('mode') 782 783 self.voice_tag = v('voice_tag') 784 785 if renpy.config.new_character_image_argument: 786 if "image" in properties: 787 self.image_tag = properties.pop("image") 788 else: 789 self.image_tag = kind.image_tag 790 else: 791 self.image_tag = None 792 793 self.display_args = dict( 794 interact=d('interact'), 795 slow=d('slow'), 796 afm=d('afm'), 797 ctc=renpy.easy.displayable_or_none(d('ctc')), 798 ctc_pause=renpy.easy.displayable_or_none(d('ctc_pause')), 799 ctc_timedpause=renpy.easy.displayable_or_none(d('ctc_timedpause')), 800 ctc_position=d('ctc_position'), 801 all_at_once=d('all_at_once'), 802 with_none=d('with_none'), 803 callback=d('callback'), 804 type=d('type'), 805 advance=d('advance'), 806 ) 807 808 self._statement_name = properties.pop("statement_name", None) 809 810 self.properties = collections.defaultdict(dict) 811 812 if kind: 813 self.who_args = kind.who_args.copy() 814 self.what_args = kind.what_args.copy() 815 self.window_args = kind.window_args.copy() 816 self.show_args = kind.show_args.copy() 817 self.cb_args = kind.cb_args.copy() 818 819 for k, v in kind.properties.items(): 820 self.properties[k] = dict(v) 821 822 else: 823 self.who_args = { "substitute" : False } 824 self.what_args = { "substitute" : False } 825 self.window_args = { } 826 self.show_args = { } 827 self.cb_args = { } 828 829 if not renpy.config.new_character_image_argument: 830 if "image" in properties: 831 self.show_args["image"] = properties.pop("image") 832 833 if "slow_abortable" in properties: 834 self.what_args["slow_abortable"] = properties.pop("slow_abortable") 835 836 prefixes = [ "show", "cb", "what", "window", "who"] + renpy.config.character_id_prefixes 837 split_args = [ i + "_" for i in prefixes ] + [ "" ] 838 839 split = renpy.easy.split_properties(properties, *split_args) 840 841 for prefix, d in zip(prefixes, split): 842 self.properties[prefix].update(d) 843 844 self.properties["who"].update(split[-1]) 845 846 self.show_args.update(self.properties.pop("show")) 847 self.cb_args.update(self.properties.pop("cb")) 848 self.what_args.update(self.properties.pop("what")) 849 self.window_args.update(self.properties.pop("window")) 850 self.who_args.update(self.properties.pop("who")) 851 852 def copy(self, name=NotSet, **properties): 853 return type(self)(name, kind=self, **properties) 854 855 # This is called before the interaction. 856 def do_add(self, who, what, multiple=None): 857 return 858 859 # This is what shows the screen for a given interaction. 860 def do_show(self, who, what, multiple=None): 861 862 if multiple is not None: 863 864 return self.show_function( 865 who, 866 what, 867 who_args=self.who_args, 868 what_args=self.what_args, 869 window_args=self.window_args, 870 screen=self.screen, 871 properties=self.properties, 872 multiple=multiple, 873 **self.show_args) 874 875 else: 876 877 return self.show_function( 878 who, 879 what, 880 who_args=self.who_args, 881 what_args=self.what_args, 882 window_args=self.window_args, 883 screen=self.screen, 884 properties=self.properties, 885 **self.show_args) 886 887 # This is called after the last interaction is done. 888 def do_done(self, who, what, multiple=None): 889 self.add_history("adv", who, what, multiple=multiple) 890 891 # This is called when an extend occurs, before the usual add/show 892 # cycel. 893 def do_extend(self): 894 self.pop_history() 895 896 # This is called to actually do the displaying. 897 def do_display(self, who, what, **display_args): 898 display_say(who, 899 what, 900 self.do_show, 901 **display_args) 902 903 # This is called to predict images that will be used by this 904 # statement. 905 def do_predict(self, who, what): 906 return self.predict_function( 907 who, 908 what, 909 who_args=self.who_args, 910 what_args=self.what_args, 911 window_args=self.window_args, 912 screen=self.screen, 913 properties=self.properties, 914 **self.show_args) 915 916 def resolve_say_attributes(self, predict, attrs, wanted=[], remove=[]): 917 """ 918 Deals with image attributes associated with the current say 919 statement. Returns True if an image is shown, None otherwise. 920 """ 921 922 if not (attrs or wanted or remove): 923 return 924 925 if not self.image_tag: 926 if attrs and not predict: 927 raise Exception("Say has image attributes %r, but there's no image tag associated with the speaking character." % (attrs,)) 928 else: 929 return 930 931 if attrs is None: 932 attrs = () 933 else: 934 attrs = tuple(attrs) 935 936 tagged_attrs = (self.image_tag,) + attrs 937 images = renpy.game.context().images 938 939 layer = renpy.config.tag_layer.get(self.image_tag, "master") 940 941 # If image is showing already, resolve it, then show or predict it. 942 if images.showing(layer, (self.image_tag,)): 943 944 new_image = images.apply_attributes(layer, self.image_tag, tagged_attrs, wanted, remove) 945 946 if new_image is None: 947 new_image = tagged_attrs 948 949 if images.showing(layer, new_image, exact=True): 950 return 951 952 show_image = (self.image_tag,) + attrs + tuple(wanted) + tuple("-" + i for i in remove) 953 954 if predict: 955 renpy.exports.predict_show(new_image) 956 else: 957 renpy.exports.show(show_image) 958 return True 959 960 else: 961 962 if renpy.config.say_attributes_use_side_image: 963 964 tagged_attrs = (renpy.config.side_image_prefix_tag,) + tagged_attrs 965 966 new_image = images.apply_attributes(layer, self.image_tag, tagged_attrs, wanted, remove) 967 968 if new_image is None: 969 new_image = tagged_attrs 970 971 images.predict_show(layer, new_image[1:], show=False) 972 973 else: 974 975 # Otherwise, just record the attributes of the image. 976 images.predict_show(layer, tagged_attrs, show=False) 977 978 def handle_say_attributes(self, predicting, interact): 979 980 attrs = renpy.game.context().say_attributes 981 renpy.game.context().say_attributes = None 982 983 temporary_attrs = renpy.game.context().temporary_attributes 984 renpy.game.context().say_attributes = None 985 986 if interact: 987 if temporary_attrs: 988 temporary_attrs = list(temporary_attrs) 989 else: 990 temporary_attrs = [ ] 991 992 # Prepend speaking_attribute, if present. This allows it to 993 # be suppressed by a negative temporary_attr, if desired. 994 if renpy.config.speaking_attribute is not None: 995 temporary_attrs.insert(0, renpy.config.speaking_attribute) 996 997 images = renpy.game.context().images 998 before = images.get_attributes(None, self.image_tag) 999 mode = None 1000 1001 if self.resolve_say_attributes(predicting, attrs): 1002 mode = 'permanent' 1003 1004 # This is so late to give resolve_say_attributes time to do some 1005 # error handling. 1006 if not self.image_tag: 1007 return None 1008 1009 if temporary_attrs: 1010 attrs = images.get_attributes(None, self.image_tag) 1011 1012 if self.resolve_say_attributes(predicting, temporary_attrs): 1013 mode = 'both' if mode else 'temporary' 1014 1015 if mode: 1016 after = images.get_attributes(None, self.image_tag) 1017 self.handle_say_transition(mode, before, after) 1018 1019 if temporary_attrs: 1020 return (attrs, images) 1021 1022 def handle_say_transition(self, mode, before, after): 1023 1024 before = set(before) 1025 after = set(after) 1026 1027 if before == after: 1028 return 1029 1030 if renpy.config.say_attribute_transition_callback_attrs: 1031 delta = (before, after) 1032 else: 1033 delta = () 1034 1035 trans, layer = renpy.config.say_attribute_transition_callback( 1036 self.image_tag, mode, *delta) 1037 if trans is not None: 1038 if layer is None: 1039 renpy.exports.with_statement(trans) 1040 else: 1041 renpy.exports.transition(trans, layer=layer) 1042 1043 def restore_say_attributes(self, predicting, state, interact): 1044 1045 if state is None: 1046 return 1047 1048 attrs, images = state 1049 1050 if not self.image_tag: 1051 return 1052 1053 # This is False when the context changes. 1054 if images is not renpy.game.context().images: 1055 return 1056 1057 current_attrs = images.get_attributes(None, self.image_tag) 1058 1059 if attrs == current_attrs: 1060 return 1061 1062 image_with_attrs = (self.image_tag,) + attrs + tuple("-" + i for i in current_attrs if i not in attrs) 1063 1064 if images.showing(None, (self.image_tag,)): 1065 1066 if not predicting: 1067 renpy.exports.show(image_with_attrs) 1068 return True 1069 else: 1070 renpy.exports.predict_show(image_with_attrs) 1071 1072 else: 1073 images.predict_show(None, image_with_attrs, show=False) 1074 1075 def __unicode__(self): 1076 1077 who = self.name 1078 1079 if self.dynamic: 1080 if callable(who): 1081 who = who() 1082 else: 1083 who = renpy.python.py_eval(who) 1084 1085 return renpy.substitutions.substitute(who)[0] 1086 1087 def __str__(self): 1088 1089 who = self.name 1090 1091 if self.dynamic: 1092 if callable(who): 1093 who = who() 1094 else: 1095 who = renpy.python.py_eval(who) 1096 1097 rv = renpy.substitutions.substitute(who)[0] 1098 1099 if PY2: 1100 rv = rv.encode("utf-8") 1101 1102 return rv 1103 1104 def __format__(self, spec): 1105 return format(str(self), spec) 1106 1107 def __repr__(self): 1108 return "<Character: {!r}>".format(self.name) 1109 1110 def empty_window(self): 1111 if renpy.config.fast_empty_window and (self.name is None) and not (self.what_prefix or self.what_suffix): 1112 self.do_show(None, "") 1113 return 1114 1115 self("", interact=False, _call_done=False) 1116 1117 def has_character_arguments(self, **kwargs): 1118 """ 1119 Returns True if `kwargs` contains any keyword arguments that will 1120 cause the creation of a new Character object and the proxying of a 1121 call to that Character object, and False otherwise. 1122 """ 1123 1124 safe_kwargs_keys = { "interact", "_mode", "_call_done", "multiple", "_with_none" } 1125 1126 for i in kwargs: 1127 if i not in safe_kwargs_keys: 1128 return False 1129 1130 return True 1131 1132 def prefix_suffix(self, thing, prefix, body, suffix): 1133 1134 def sub(s, scope=None, force=False, translate=True): 1135 return renpy.substitutions.substitute(s, scope=scope, force=force, translate=translate)[0] 1136 1137 thingvar_quoted = "[[" + thing + "]" 1138 thingvar = "[" + thing + "]" 1139 1140 if not renpy.config.new_substitutions: 1141 return prefix + body + suffix 1142 1143 # Used before Ren'Py 7.4. 1144 elif renpy.config.who_what_sub_compat == 0: 1145 pattern = sub(prefix + thingvar_quoted + suffix) 1146 return pattern.replace(thingvar, sub(body)) 1147 1148 # Used from Ren'Py 7.4 to Ren'Py 7.4.4 1149 elif renpy.config.who_what_sub_compat == 1: 1150 pattern = sub(sub(prefix) + thingvar_quoted + sub(suffix)) 1151 return pattern.replace(thingvar, sub(body)) 1152 1153 # 7.4.5 on. 1154 else: 1155 return (sub(prefix) + sub(body) + sub(suffix)) 1156 1157 def __call__(self, what, interact=True, _call_done=True, multiple=None, **kwargs): 1158 1159 _mode = kwargs.pop("_mode", None) 1160 _with_none = kwargs.pop("_with_none", None) 1161 1162 if kwargs: 1163 return Character(kind=self, **kwargs)(what, interact=interact, _call_done=_call_done, multiple=multiple, _mode=_mode, _with_none=_with_none) 1164 1165 # Check self.condition to see if we should show this line at all. 1166 if not (self.condition is None or renpy.python.py_eval(self.condition)): 1167 return True 1168 1169 if not isinstance(what, basestring): 1170 raise Exception("Character expects its what argument to be a string, got %r." % (what,)) 1171 1172 # Figure out multiple and final. Multiple is None if this is not a multiple 1173 # dialogue, or a step and the total number of steps in a multiple interaction. 1174 1175 global multiple_count 1176 1177 if multiple is None: 1178 multiple_count = 0 1179 1180 else: 1181 multiple_count += 1 1182 multiple = (multiple_count, multiple) 1183 1184 if multiple_count == multiple[1]: 1185 multiple_count = 0 1186 1187 if multiple is None: 1188 1189 old_attr_state = self.handle_say_attributes(False, interact) 1190 1191 old_side_image_attributes = renpy.store._side_image_attributes 1192 1193 if self.image_tag: 1194 attrs = (self.image_tag,) + renpy.game.context().images.get_attributes(None, self.image_tag) 1195 else: 1196 attrs = None 1197 1198 renpy.store._side_image_attributes = attrs 1199 1200 if not interact: 1201 renpy.store._side_image_attributes_reset = True 1202 1203 if renpy.config.voice_tag_callback is not None: 1204 renpy.config.voice_tag_callback(self.voice_tag) 1205 1206 try: 1207 1208 if interact: 1209 mode = _mode or self.mode 1210 renpy.exports.mode(mode) 1211 else: 1212 renpy.game.context().deferred_translate_identifier = renpy.game.context().translate_identifier 1213 1214 # Figure out the arguments to display. 1215 display_args = self.display_args.copy() 1216 display_args["interact"] = display_args["interact"] and interact 1217 1218 if multiple is not None: 1219 display_args["multiple"] = multiple 1220 1221 if _with_none is not None: 1222 display_args["with_none"] = _with_none 1223 1224 who = self.name 1225 1226 # If dynamic is set, evaluate the name expression. 1227 if self.dynamic: 1228 if callable(who): 1229 who = who() 1230 else: 1231 who = renpy.python.py_eval(who) 1232 1233 if who is not None: 1234 who = self.prefix_suffix("who", self.who_prefix, who, self.who_suffix) 1235 1236 what = self.prefix_suffix("what", self.what_prefix, what, self.what_suffix) 1237 1238 # Run the add_function, to add this character to the 1239 # things like NVL-mode. 1240 1241 if multiple is not None: 1242 self.do_add(who, what, multiple=multiple) 1243 else: 1244 self.do_add(who, what) 1245 1246 dtt = DialogueTextTags(what) 1247 1248 # Now, display the damned thing. 1249 self.do_display(who, what, cb_args=self.cb_args, dtt=dtt, **display_args) 1250 1251 # Indicate that we're done. 1252 if _call_done and not dtt.has_done: 1253 1254 if multiple is not None: 1255 self.do_done(who, what, multiple=multiple) 1256 else: 1257 self.do_done(who, what) 1258 1259 # Finally, log this line of dialogue. 1260 if who and isinstance(who, basestring): 1261 renpy.exports.log(who) 1262 1263 renpy.exports.log(what) 1264 renpy.exports.log("") 1265 1266 finally: 1267 1268 if (multiple is None) and interact: 1269 renpy.store._side_image_attributes = old_side_image_attributes 1270 1271 if old_attr_state is not None: 1272 _, images = old_attr_state 1273 before = images.get_attributes(None, self.image_tag) 1274 1275 if self.restore_say_attributes(False, old_attr_state, interact): 1276 after = images.get_attributes(None, self.image_tag) 1277 self.handle_say_transition('restore', before, after) 1278 1279 def statement_name(self): 1280 if self._statement_name is not None: 1281 return self._statement_name 1282 elif not (self.condition is None or renpy.python.py_eval(self.condition)): 1283 return "say-condition-false" 1284 else: 1285 return "say" 1286 1287 def predict(self, what): 1288 1289 old_attr_state = self.handle_say_attributes(True, True) 1290 1291 old_side_image_attributes = renpy.store._side_image_attributes 1292 1293 if self.image_tag: 1294 attrs = (self.image_tag,) + renpy.game.context().images.get_attributes("master", self.image_tag) 1295 else: 1296 attrs = None 1297 1298 renpy.store._side_image_attributes = attrs 1299 1300 try: 1301 1302 if self.dynamic: 1303 who = "<Dynamic>" 1304 else: 1305 who = self.name 1306 1307 return self.do_predict(who, what) 1308 1309 finally: 1310 renpy.store._side_image_attributes = old_side_image_attributes 1311 self.restore_say_attributes(True, old_attr_state, True) 1312 1313 def will_interact(self): 1314 1315 if not (self.condition is None or renpy.python.py_eval(self.condition)): 1316 return False 1317 1318 return self.display_args['interact'] 1319 1320 def add_history(self, kind, who, what, multiple=None, **kwargs): 1321 """ 1322 This is intended to be called by subclasses of ADVCharacter to add 1323 History entries to _history_list. 1324 """ 1325 1326 history_length = renpy.config.history_length 1327 1328 if history_length is None: 1329 return 1330 1331 if not renpy.store._history: # @UndefinedVariable 1332 return 1333 1334 history = renpy.store._history_list # @UndefinedVariable 1335 1336 h = HistoryEntry() 1337 1338 h.kind = kind 1339 1340 h.who = who 1341 h.what = what 1342 1343 h.who_args = self.who_args 1344 h.what_args = self.what_args 1345 h.window_args = self.window_args 1346 h.show_args = self.show_args 1347 1348 h.image_tag = self.image_tag 1349 1350 h.multiple = multiple 1351 1352 if renpy.game.context().rollback: 1353 h.rollback_identifier = renpy.game.log.current.identifier 1354 else: 1355 h.rollback_identifier = None 1356 1357 for k, v in kwargs.items(): 1358 setattr(h, k, v) 1359 1360 for i in renpy.config.history_callbacks: 1361 i(h) 1362 1363 history.append(h) 1364 1365 while len(history) > history_length: 1366 history.pop(0) 1367 1368 def pop_history(self): 1369 """ 1370 This is intended to be called by do_extend to remove entries from 1371 _history_list. 1372 """ 1373 1374 history_length = renpy.config.history_length 1375 1376 if history_length is None: 1377 return 1378 1379 if not renpy.store._history: # @UndefinedVariable 1380 return 1381 1382 # The history can be reset at any time, so check that we have some. 1383 if renpy.store._history_list: 1384 renpy.store._history_list.pop() # @UndefinedVariable 1385 1386 1387def Character(name=NotSet, kind=None, **properties): 1388 """ 1389 :doc: character 1390 :args: (name=..., kind=adv, **args) 1391 :name: Character 1392 1393 Creates and returns a Character object, which controls the look 1394 and feel of dialogue and narration. 1395 1396 `name` 1397 If a string, the name of the character for dialogue. When 1398 `name` is None, display of the name is omitted, as for 1399 narration. If no name is given, the name is taken from 1400 `kind`, and otherwise defaults to None. 1401 1402 `kind` 1403 The Character to base this Character off of. When used, the 1404 default value of any argument not supplied to this Character 1405 is the value of that argument supplied to ``kind``. This can 1406 be used to define a template character, and then copy that 1407 character with changes. 1408 1409 **Linked Image.** 1410 An image tag may be associated with a Character. This allows a 1411 say statement involving this character to display an image with 1412 the tag, and also allows Ren'Py to automatically select a side 1413 image to show when this character speaks. 1414 1415 `image` 1416 A string giving the image tag that is linked with this 1417 character. 1418 1419 **Voice Tag.** 1420 If a voice tag is assign to a Character, the voice files that are 1421 associated with it, can be muted or played in the preference 1422 screen. 1423 1424 `voice_tag` 1425 A String that enables the voice file associated with the 1426 Character to be muted or played in the 'voice' channel. 1427 1428 **Prefixes and Suffixes.** 1429 These allow a prefix and suffix to be applied to the name of the 1430 character, and to the text being shown. This can be used, for 1431 example, to add quotes before and after each line of dialogue. 1432 1433 `what_prefix` 1434 A string that is prepended to the dialogue being spoken before 1435 it is shown. 1436 1437 `what_suffix` 1438 A string that is appended to the dialogue being spoken before 1439 it is shown. 1440 1441 `who_prefix` 1442 A string that is prepended to the name of the character before 1443 it is shown. 1444 1445 `who_suffix` 1446 A string that is appended to the name of the character before 1447 it is shown. 1448 1449 **Changing Name Display.** 1450 These options help to control the display of the name. 1451 1452 `dynamic` 1453 If true, then `name` should either be a string containing a Python 1454 expression, a function, or a callable object. If it's a string, 1455 That string will be evaluated before each line of dialogue, and 1456 the result used as the name of the character. Otherwise, the 1457 function or callable object will be called with no arguments 1458 before each line of dialogue, and the return value of the call will 1459 be used as the name of the character. 1460 1461 **Controlling Interactions.** 1462 These options control if the dialogue is displayed, if an 1463 interaction occurs, and the mode that is entered upon display. 1464 1465 `condition` 1466 If given, this should be a string containing a Python 1467 expression. If the expression is false, the dialogue 1468 does not occur, as if the say statement did not happen. 1469 1470 `interact` 1471 If true, the default, an interaction occurs whenever the 1472 dialogue is shown. If false, an interaction will not occur, 1473 and additional elements can be added to the screen. 1474 1475 `advance` 1476 If true, the default, the player can click to advance through 1477 the statement, and other means of advancing (such as skip and 1478 auto-forward mode) will also work. If false, the player will be 1479 unable to move past the say statement unless an alternate means 1480 (such as a jump hyperlink or screen) is provided. 1481 1482 `mode` 1483 A string giving the mode to enter when this character 1484 speaks. See the section on :ref:`modes <modes>` for more details. 1485 1486 `callback` 1487 A function that is called when events occur while the 1488 character is speaking. See the section on 1489 :ref:`character-callbacks` fore more information. 1490 1491 **Click-to-continue.** 1492 A click-to-continue indicator is displayed once all the text has 1493 finished displaying, to prompt the user to advance. 1494 1495 `ctc` 1496 A displayable to use as the click-to-continue indicator, unless 1497 a more specific indicator is used. 1498 1499 `ctc_pause` 1500 A displayable to use a the click-to-continue indicator when the 1501 display of text is paused by the {p} or {w} text tags. 1502 1503 `ctc_timedpause` 1504 A displayable to use a the click-to-continue indicator when the 1505 display of text is paused by the {p=} or {w=} text tags. When 1506 None, this takes its default from `ctc_pause`, use ``Null()`` 1507 when you want a `ctc_pause` but no `ctc_timedpause`. 1508 1509 `ctc_position` 1510 Controls the location of the click-to-continue indicator. If 1511 ``"nestled"``, the indicator is displayed as part of the text 1512 being shown, immediately after the last character. ``"nestled-close"`` is 1513 similar, except a break is not allowed between the text and the CTC 1514 indicator. If ``"fixed"``, 1515 the indicator is added to the screen, and its position is 1516 controlled by the position style properties. 1517 1518 1519 **Screens.** 1520 The display of dialogue uses a :ref:`screen <screens>`. These arguments 1521 allow you to select that screen, and to provide arguments to it. 1522 1523 `screen` 1524 The name of the screen that is used to display the dialogue. 1525 1526 Keyword arguments beginning with ``show_`` have the prefix 1527 stripped off, and are passed to the screen as arguments. For 1528 example, the value of ``show_myflag`` will become the value of 1529 the ``myflag`` variable in the screen. (The ``myflag`` variable isn't 1530 used by default, but can be used by a custom say screen.) 1531 1532 One show variable is, for historical reasons, handled by Ren'Py itself: 1533 1534 `show_layer` 1535 If given, this should be a string giving the name of the layer 1536 to show the say screen on. 1537 1538 **Styling Text and Windows.** 1539 Keyword arguments beginning with ``who_``, ``what_``, and 1540 ``window_`` have their prefix stripped, and are used to :ref:`style 1541 <styles>` the character name, the spoken text, and the window 1542 containing both, respectively. 1543 1544 For example, if a character is given the keyword argument 1545 ``who_color="#c8ffc8"``, the color of the character's name is 1546 changed, in this case to green. ``window_background="frame.png"`` 1547 sets the background of the window containing this character's 1548 dialogue. 1549 1550 The style applied to the character name, spoken text, and window 1551 can also be set this way, using the ``who_style``, ``what_style``, and 1552 ``window_style`` arguments, respectively. 1553 1554 Setting :var:`config.character_id_prefixes` makes it possible to style 1555 other displayables as well. For example, when the default GUI is used, 1556 styles prefixed with ``namebox_`` are used to style the name of the 1557 speaking character. 1558 """ 1559 1560 if kind is None: 1561 kind = renpy.store.adv 1562 1563 return type(kind)(name, kind=kind, **properties) 1564 1565 1566def DynamicCharacter(name_expr, **properties): 1567 return Character(name_expr, dynamic=True, **properties) 1568