Compare commits

...

1 Commits

Author SHA1 Message Date
tom 8f060bf3b0 Tagged 4.7.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/tags/renpy-4.7@46 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 21:06:20 +00:00
44 changed files with 5567 additions and 836 deletions
+207
View File
@@ -1,3 +1,210 @@
New in 4.7
----------
The demo was updated to show the new features in 4.7.
The big new feature in this release is text tags. Text tags is a
markup language that allows text properties to be changed inside a
single unit of text. It lets you emphasize individual words by making
them bold, bigger, or a different color. Rather than explaining it
fully here, let me point you to the new "Text Tags" section of the
documentation.
There were two changes that were made to Ren'Py to support text
tags. The first was that interpolation now quotes the interpolated
text, making it impossible to introduce a text tag via interpolation.
The second change is that renpy.input() and friends now take as
arguments strings giving allowed and excluded characters. The default
is to exclude the '{' and '}' characters, meaning that it's impossible
for a user to input a text tag.
Finally, this release features even more defensive programming
involving sound. Now, if the mixer does not initialize, no further
sound operations will be performed.
New in 4.6.2
------------
4.6.2 was never officially released, as it didn't cure all of the
sound woes, and didn't have much else in the way of new features.
Added a speed test, which tests the speed of the dissolve transition
on a user's system. This test can be accessed by typing 'S' during the
demo (that's shift+the 's' key).
Fixed a bug with sound on systems with no soundcard in them. (Also,
put in a note to remind myself not to re-introduce that bug.)
Fixed a bug in which imagemaps would not redraw properly.
New in 4.6.1
------------
Fixed a major memory leak in the rendering code. This leak was
rendering 4.6 and 4.5 unusable on smaller systems.
Executed 250,000 statements (well, 5 statements in a loop, while
skipping), and ensured that there was no memory leak during the
execution of these statements.
Added a memory profiler. You can see it by supplying the --leak option
to run_game.exe or .py, but don't expect to understand it.
Changed Dissolve so that it tries to use compositing when it can. This
means that it only draws to the screen once... which is a little bit
of a speedup on a really expensive operation.
Fixed a bug that was preventing predictive image loading from
working.
New in 4.6
----------
The demo was updated to show some of the new features listed below.
There's now a third state that hovered widgets can go into. The
activate state is enabled when a button is clicked or a menu choice is
selected. The activate state can only be seen when a transition occurs
immediately after a button is clicked or a menu choice is selected.
The margin and padding properties have been broken up, so it's now
possible to specify margin and padding for the left, right, top, and
bottom of a windows.
The biggest internal change in this version is the introduction of a
layer system in Ren'Py. Transitions can now work on an individual
layer (as well as the previous behavior of the whole screen), so it's
possible to do things like sliding in and out the dialogue box.
It's now possible to use transitions when entering and leaving the
game menu, by assigning a transition object to
library.enter_transition and library.exit_transition, respectively.
The preferences screen can now be entered from user code, and is
now part of the main menu.
Character objects now support an interact argument when called
directly. If it's False, then no interaction occurs. This lets a
character window become part of a larger screen.
Two new extras have been added:
[list]
[*] button_menu.rpy changes the way menus are displayed. Each menu
choice is displayed in its own button, roughly centered on the
screen. A single menu caption is displayed as narration, if such
a caption exists.
[*] overlay_menu.rpy changes the behavior of right click from
showing the game menu to showing a bank of buttons that, when
clicked, bring the user to the game menu.
[/list]
The overlay code was overhauled. It's now not possible for an overlay
function to return a list of widgets. Instead, the overlay functions
are expected to add widgets to the screen using the ui
functions. Calling the new function renpy.restart_interaction will,
among other things, cause the overlay functions to be called again, if
one wants to replace the overlay on the screen.
A new variable, config.overlay_during_wait, controls if overlays are
shown during the execution of wait statements.
Init blocks of the same priority are now assured to run in the order
that they appear in a script file.
The renpy.interact() function has been eliminated. Use ui.interact()
instead, as it performs more error checking.
Fixed a bug in which some redraws were ignored, and another in which
the input widget failed to eat characters. (So typing 'f' in an input
widget would be passed through, and eventually cause the game to go to
fullscreen mode. No longer.)
4.5's demo script had a bug that cause it to crash, after playing the
MPEG-1 movie. It was a bug in the demo script, and not in Ren'Py
proper. It's fixed.
New in 4.5
----------
We now ship an extras directory, with interesting sample code. It
includes:
[list]
[*] fullscreen.rpy, containing code to automatically switch the game
into fullscreen on the first run, while preserving the user's
preference on later runs.
[*] 640x480.rpy, which shows how to customize Ren'Py for a game that
runs at 640x480.
[*] kanamode.rpy, contains the code to emulate the interface of the
various Digital Object games, which display text one line at a
time, but keep a page of text on the screen at once.
([url=http://www.bishoujo.us/hosted/kanamode.jpg]screenshot[/url])
[/list]
Added a new CropMove transition. This single class is the root of not
one, not two, but fourteen transitions, in the forms of wipes, slides,
slideaways, and rectangular irises. You can see some of these
transitions in action in the demo, which now has a new section,
"What's new with Ren'Py?"
Added support for playing MPEG-1 movies. Rather than explain it all
here, we'l just mention that you can read the new "Movies" subsection
of the "Multimedia: Sound, Music, and Movies" section of the Ren'Py
manual. We support both hardware-accelerated full-window movies and
using a movie as a widget. This is also shown in the what's new
section of Ren'Py.
Added ui.grid(). This is a widget that places its children in a grid.
The children must have sizes that do not consider the amount of space
available for the grid to work.
Added ui.pausebehavior(). This separates pausing from the saybehavior
paving the way for uninterruptable pauses (a bad idea, IMO) and menus
that dismiss themseleves after a certain amount of time.
A new function, renpy.exists(), can check to see if a given file can
be found in the searchpath. This could be used, for example, to make a
game that only tries to play music if an add-on music package is
downloaded, and the files placed in the appropriate place.
Added the ability to bind mouse events to mouseup as well as
mousedown, and changed some of the default bindings to be on mouseup
rather than mousedown. If you make changes to config.keymaps, you'll
need to change mouse_1 to mousedown_1, and so on for various other
button numbers.
Moved the place we look for menu sounds from the style of the choices
in the menu to the style of the menu itself, as that seems more
rational. Also, gave menus their own style. (Can't believe I forgot
that.)
Made a few changes to the library screens. First of all, we
rationalized (to some extent, it's still pretty ugly) the names of the
styles for library widgets. So some of that may have changed. We
placed preferences and the yes/no dialogue inside windows, which can
be styled by the user. We then routed all button and label creation
through two functions, _button_factory and _label_factory. By
overriding these functions, the user can (for example) replace the
default textbuttons with imagebuttons.
Made 'f' toggle fullscreen at the main menu. This will help on virtual
windows, where the mouse doesn't work right in fullscreen mode.
Improved rendering speed. Now, we aggressively cache things to
minimize re-renders, and only draw to the screen the parts of the
screen that have changed. In common cases, such as the case where only
some text has changed, this can greately improve performance. In other
cases (transitions), we have to redraw the entire screen anyway, so
there's little improvement.
Fixed a bug in which hiding the UI was not working properly. (This
broke the center mouse button behavior.)
New in 4.4.2
------------
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2004-2005 Tom Rothamel <pytom@bishoujo.us>
Copyright 2004-2005 PyTom <pytom@bishoujo.us>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
+158 -64
View File
@@ -39,6 +39,7 @@ init -500:
library.main_menu = [
( "Start Game", "start" ),
( "Continue Game", "_continue" ),
( "Preferences", "_preferences" ),
( "Quit Game", "_quit" ),
]
@@ -63,6 +64,12 @@ init -500:
# button.
library.exit_sound = None
# Transition that occurs when entering the game menu.
library.enter_transition = None
# Transition that occurs when leaving the game menu.
library.exit_transition = None
# True if the skip indicator should be shown.
library.skip_indicator = True
@@ -76,6 +83,61 @@ init -500:
# True if we can save, false otherwise.
_can_save = True
def _button_factory(label,
type=None,
selected=None,
disabled=False,
clicked=None,
**properties):
"""
This function is called to create the various buttons used
in the game menu. By overriding this function, one can
(for example) replace the default textbuttons with image buttons.
When it is called, it's expected to add a button to the screen.
@param label: The label of this button, before translation.
@param type: The type of the button. One of "mm" (main menu),
"gm_nav" (game menu), "file_picker_nav", "yesno", or "prefs".
@param selected: True if the button is selected, False if not,
or None if it doesn't matter.
@param disabled: True if the button is disabled, False if not.
@param clicked: A function that should be executed when the
button is clicked.
@param properties: Addtional layout properties.
"""
style = type
if selected:
style += "_selected"
if disabled:
style += "_disabled"
style = style + "_button"
text_style = style + "_text"
ui.textbutton(_(label), style=style, text_style=text_style, clicked=clicked, **properties)
def _label_factory(label, type, **properties):
"""
This function is called to create a new label. It can be
overridden by the user to change how these labels are created.
@param label: The label of the box.
@param type: "prefs" or "yesno".
@param properties: This may contain position properties.
"""
ui.text(_(label), style=type + "_label", **properties)
# The function that's used to translate strings in the game menu.
def _(s):
"""
@@ -87,30 +149,16 @@ init -500:
else:
return s
# Called to make a screenshot happen.
def _screenshot():
renpy.screenshot("screenshot.bmp")
# Are the windows currently hidden?
_windows_hidden = False
# Hides the windows.
def _hide_windows():
global _windows_hidden
if _windows_hidden:
return
try:
_windows_hidden = True
renpy.interact(renpy.SayBehavior())
finally:
_windows_hidden = False
# Set up the default keymap.
python hide:
# Called to make a screenshot happen.
def screenshot():
renpy.screenshot("screenshot.bmp")
def invoke_game_menu():
renpy.play(library.enter_sound)
renpy.call_in_new_context('_game_menu')
@@ -121,12 +169,12 @@ init -500:
# The default keymap.
km = renpy.Keymap(
rollback = renpy.rollback,
screenshot = _screenshot,
screenshot = screenshot,
toggle_fullscreen = renpy.toggle_fullscreen,
toggle_music = renpy.toggle_music,
toggle_skip = toggle_skipping,
game_menu = invoke_game_menu,
hide_windows = _hide_windows,
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
)
config.underlay = [ km ]
@@ -142,11 +190,24 @@ init -500:
ui.conditional("config.skipping")
ui.text(_("Skip Mode"), style='skip_indicator')
return [ ]
config.overlay_functions.append(skip_indicator)
return
label _hide_windows:
if _windows_hidden:
return
python:
_windows_hidden = True
ui.saybehavior()
ui.interact(suppress_overlay=True)
_windows_hidden = False
return
# This is the true starting point of the program. Sssh... Don't
# tell anyone.
@@ -166,8 +227,11 @@ label _main_menu:
label _library_main_menu:
scene
python hide:
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
ui.keymousebehavior()
ui.window(style='mm_root_window')
@@ -177,13 +241,13 @@ label _library_main_menu:
ui.vbox()
for text, label in library.main_menu:
ui.textbutton(text, clicked=ui.returns(label))
_button_factory(text, "mm", clicked=ui.returns(label))
ui.close()
ui.close()
store._result = renpy.interact(suppress_overlay = True,
suppress_underlay = True)
store._result = ui.interact(suppress_overlay = True,
suppress_underlay = True)
# Computed jump to the appropriate label.
$ renpy.jump(_result)
@@ -195,7 +259,19 @@ label _continue:
$ _can_save = False
$ _at_main_menu = True
$ renpy.call_in_new_context("_load_menu")
$ renpy.call_in_new_context("_game_menu_load")
$ _can_save = True
$ _at_main_menu = False
jump _library_main_menu
# Used to call the game menu.
label _preferences:
$ _can_save = False
$ _at_main_menu = True
$ renpy.call_in_new_context("_game_menu_preferences")
$ _can_save = True
$ _at_main_menu = False
@@ -213,8 +289,7 @@ init -500:
# This is used to store scratch data that's used by the
# library, but shouldn't be saved out as part of the savegame.
_scratch = object()
# This returns a window containing the game menu navigation
# buttons, set up to jump to the appropriate screen sections.
def _game_nav(selected):
@@ -230,32 +305,24 @@ init -500:
ui.vbox()
for key, label, target, enabled in library.game_menu:
style="gm_nav_button"
text_style="gm_nav_button_text"
if key == selected:
style = 'gm_nav_selected_button'
text_style = 'gm_nav_selected_button_text'
clicked = ui.jumps(target)
disabled = False
if not eval(enabled):
style = 'gm_nav_disabled_button'
text_style = 'gm_nav_disabled_button_text'
clicked = lambda : None
disabled = True
clicked = ui.returns(None)
ui.textbutton(_(label), style=style, text_style=text_style,
clicked=clicked)
_button_factory(label, "gm_nav", selected=(key==selected),
disabled=disabled, clicked=clicked)
ui.close()
ui.close()
def _game_interact():
return renpy.interact(suppress_underlay=True,
suppress_overlay=True)
return ui.interact(suppress_underlay=True,
suppress_overlay=True)
def _render_new_slot(name, save):
@@ -332,32 +399,29 @@ init -500:
_game_nav(selected)
ui.window(style='file_picker_window')
ui.vbox() # whole thing.
# Draw the navigation.
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
def tb(cond, label, clicked):
if cond:
style = 'button'
text_style = 'button_text'
else:
style = 'disabled_button'
text_style = 'disabled_button_text'
ui.textbutton(label, style=style, text_style=text_style, clicked=clicked)
_button_factory(label, "file_picker_nav", disabled=not cond, clicked=clicked)
# Previous
tb(fpi > 0, _('Previous'), ui.returns(("fpidelta", -1)))
# Quick Access
for i in range(0, library.file_quick_access_pages):
target = i * library.file_page_length
tb(fpi != target, str(i + 1), ui.returns(("fpiset", target)))
# Next
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
ui.close() # nav buttons.
# Done with nav buttons.
ui.close()
# This draws a single slot.
def entry(offset):
i = fpi + offset
@@ -369,6 +433,7 @@ init -500:
_render_savefile(name, saves[name], newest)
# Actually draw a slot.
ui.hbox() # slots
ui.vbox()
@@ -402,9 +467,23 @@ init -500:
_game_nav(screen)
ui.text(message, style='yesno_prompt')
ui.textbutton(_("Yes"), style='yesno_yes', clicked=ui.returns(True))
ui.textbutton(_("No"), style='yesno_no', clicked=ui.returns(False))
ui.window(style='yesno_window')
ui.vbox(library.padding * 10, xpos=0.5, xanchor='center', ypos=0.5, yanchor='center')
_label_factory(message, "yesno", xpos=0.5, xanchor='center')
ui.grid(5, 1, xfill=True)
# The extra nulls are because we want equal whitespace surrounding
# the two buttons. It should work as long as we have xfill=True
ui.null()
_button_factory("Yes", 'yesno', clicked=ui.returns(True), xpos=0.5, xanchor='center')
ui.null()
_button_factory("No", 'yesno', clicked=ui.returns(False), xpos=0.5, xanchor='center')
ui.null()
ui.close()
ui.close()
return _game_interact()
@@ -427,21 +506,32 @@ init -500:
# Factored this all into one place, to make our lives a bit easier.
label _take_screenshot:
label _enter_game_menu:
scene
$ renpy.movie_stop()
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
if library.enter_transition:
$ renpy.transition(library.enter_transition)
return
# Entry points from the game into menu-space.
label _load_menu:
call _take_screenshot
jump _load_screen
label _game_menu:
call _take_screenshot
label _game_menu_save:
call _enter_game_menu
jump _save_screen
label _game_menu_load:
call _enter_game_menu
jump _load_screen
label _game_menu_preferences:
call _enter_game_menu
jump _prefs_screen
label _confirm_quit:
call _take_screenshot
call _enter_game_menu
jump _quit_screen
# Menu screens.
@@ -503,6 +593,10 @@ label _noisy_return:
# Return to the game.
label _return:
if library.exit_transition:
$ renpy.transition(library.exit_transition)
return
# Random nice things to have.
+13 -14
View File
@@ -46,28 +46,22 @@ init -450:
if not values:
return
ui.window(style='prefs_pref')
ui.vbox(style='prefs_pref')
ui.text(_(self.name), style='prefs_label')
_label_factory(self.name, "prefs")
cur = getattr(_preferences, self.field)
for name, value in values:
style = 'prefs_button'
text_style = 'prefs_button_text'
if cur == value:
style = 'prefs_selected_button'
text_style = 'prefs_selected_button_text'
def clicked(value=value):
setattr(_preferences, self.field, value)
return True
ui.textbutton(_(name),
style=style,
text_style=text_style,
clicked=clicked)
_button_factory(name, "prefs",
selected=cur==value,
clicked=clicked)
ui.close()
@@ -123,16 +117,21 @@ label _prefs_screen:
_game_nav("prefs")
ui.vbox(library.padding * 3, style='prefs_left')
ui.window(style='prefs_window')
ui.grid(2, 1, xfill=True)
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
for i in library.left_preferences:
i.render_preference()
ui.close()
ui.vbox(library.padding * 3, style='prefs_right')
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
for i in library.right_preferences:
i.render_preference()
ui.close()
ui.close()
_game_interact()
jump _prefs_screen
+57 -51
View File
@@ -14,7 +14,7 @@
# to your script. No need to mess around here, it will just make your
# life harder when a new version of Ren'Py is released.
init -250:
init -1000:
python hide:
style.create('default', None,
@@ -36,6 +36,9 @@ init -250:
style.default.antialias = True
style.default.size = 22
style.default.color = (255, 255, 255, 255)
style.default.bold = False
style.default.italic = False
style.default.underline = False
style.default.drop_shadow = (1, 1)
style.default.drop_shadow_color = (0, 0, 0, 128)
style.default.minwidth = 0
@@ -107,6 +110,8 @@ init -250:
the label of dialogue. The label is used to
indicate who is saying something.""")
style.say_label.bold = True
style.create('say_dialogue', 'default',
"""(text) The style that is used by default for
the text of dialogue.""")
@@ -122,14 +127,17 @@ init -250:
# Styles that are used for menus.
style.create('menu', 'default',
"(sound, position) The style that is used for menus themselves.")
style.create('menu_caption', 'default',
"""(text) The style that is used to render a menu
caption.""")
"(text) The style that is used to render a menu caption.")
style.create('menu_choice', 'default',
"""(text, hover, sound) The style that is used to render a menu choice.""")
style.menu_choice.hover_color = (255, 255, 0, 255) # yellow
style.menu_choice.activate_color = (255, 255, 0, 255) # yellow
style.menu_choice.idle_color = (0, 255, 255, 255) # cyan
style.create('menu_window', 'window',
@@ -197,6 +205,7 @@ init -250:
style.button_text.size = 24
style.button_text.color = dark_cyan
style.button_text.hover_color = bright_cyan
style.button_text.activate_color = bright_cyan
style.button_text.drop_shadow = (2, 2)
# Selected button.
@@ -208,6 +217,7 @@ init -250:
style.selected_button_text.color = dark_red
style.selected_button_text.hover_color = bright_red
style.selected_button_text.activate_color = bright_red
# Disabled button.
@@ -222,6 +232,7 @@ init -250:
style.disabled_button_text.color = (128, 128, 128, 255)
style.disabled_button_text.hover_color = (128, 128, 128, 255)
style.disabled_button_text.activate_color = (128, 128, 128, 255)
# Bar.
@@ -301,12 +312,17 @@ init -250:
'(position) The position of the naviation (next/previous) buttons in the file picker.')
style.file_picker_navbox.xmargin = 10
style.create('file_picker_image', 'default',
'(position) The position of the image in each file picker entry.')
style.file_picker_image.xminimum = 280
style.create('file_picker_nav_button', 'button',
'(window, hover) The style that is used for enabled file picker navigation buttons.')
style.create('file_picker_nav_button_text', 'button_text',
'(text) The style that is used for the label of enabled file picker navigation buttons.')
style.create('file_picker_nav_disabled_button', 'disabled_button',
'(window, hover) The style that is used for disabled file picker navigation buttons.')
style.create('file_picker_nav_disabled_button_text', 'disabled_button_text',
'(text) The style that is used for the label of disabled file picker navigation buttons.')
style.create('file_picker_entry', 'button',
'(window, hover) The style that is used for each of the slots in the file picker.')
@@ -318,6 +334,7 @@ init -250:
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
style.file_picker_entry.hover_background = Solid((255, 255, 192, 255))
style.file_picker_entry.activate_background = Solid((255, 255, 192, 255))
style.create('file_picker_text', 'default',
'(text) A base style for all text that is displayed in the file picker.')
@@ -333,6 +350,7 @@ init -250:
'(text) The style that is applied to the old indicator in the file pciker.')
style.file_picker_new.hover_color = bright_red
style.file_picker_new.activate_color = bright_red
style.file_picker_new.idle_color = dark_red
style.file_picker_new.minwidth = 30
style.file_picker_old.minwidth = 30
@@ -343,78 +361,66 @@ init -250:
style.create('file_picker_empty_slot', 'file_picker_text',
'(text) The style that is used for the empty slot indicator in the file picker.')
style.create('yesno_prompt', 'default',
style.create('yesno_label', 'default',
'(text, position) The style used for the prompt in a yes/no dialog.')
style.yesno_prompt.xpos = 0.5
style.yesno_prompt.xanchor = 'center'
style.yesno_label.color = green
style.yesno_prompt.ypos = 0.25
style.yesno_prompt.yanchor = 'center'
style.create('yesno_button', 'button',
'(window, hover) The style of yes/no buttons.')
style.yesno_prompt.color = green
style.create('yesno_yes', 'button',
'(position) The position of the yes button on the screen.')
style.yesno_yes.xpos = 0.33
style.yesno_yes.xanchor = 'center'
style.yesno_yes.ypos = 0.33
style.yesno_yes.yanchor = 'center'
style.create('yesno_no', 'button',
'(position) The position of the no button on the screen.')
style.yesno_no.xpos = 0.66
style.yesno_no.xanchor = 'center'
style.yesno_no.ypos = 0.33
style.yesno_no.yanchor = 'center'
style.create('yesno_button_text', 'button_text',
'(window, hover) The style of yes/no button text.')
style.create('yesno_window', 'default',
'(window) The style of a window containing a yes/no dialogue.')
style.yesno_window.xfill = True
style.yesno_window.yminimum = 0.5
# Preferences
style.create('prefs_pref', 'default',
'(window, position) The position of the box containing an individual preference.')
style.prefs_pref.xpos = 0.5
style.prefs_pref.xanchor = 'center'
style.create('prefs_label', 'default',
'(text, position) The style that is applied to the label of a block of preferences.')
style.prefs_label.xpos = 0.5
style.prefs_label.xanchor = "center"
style.prefs_label.color = green
style.create('prefs_pref', 'default',
'(position) The position of the box containing an individual preference.')
style.prefs_pref.xpos = 0.5
style.prefs_pref.xanchor = 'center'
style.create('prefs_left', 'default',
'(position) The position of the left column of preferences.')
style.prefs_left.xpos = 0.25
style.prefs_left.xanchor = "center"
style.prefs_left.ypos = 0.05
style.prefs_left.yalign = "top"
style.create('prefs_right', 'default',
'(position) The position of the right column of preferences.')
style.prefs_right.xpos = 0.75
style.prefs_right.xanchor = "center"
style.prefs_right.ypos = 0.05
style.prefs_right.yalign = "top"
style.create('prefs_button', 'button',
'(window, hover) The style of an unselected preferences button.')
style.prefs_button.xpos = 0.5
style.prefs_button.xanchor = 'center'
style.create('prefs_button_text', 'button_text',
'(text, hover) The style of the text of an unselected preferences button.')
style.create('prefs_selected_button', 'selected_button',
'(window, hover) The style of a selected preferences button.')
style.prefs_selected_button.xpos = 0.5
style.prefs_selected_button.xanchor = 'center'
style.create('prefs_selected_button_text', 'selected_button_text',
'(text, hover) The style of the text of a selected preferences button.')
style.create('prefs_window', 'default',
'(window, position) A window containing all preferences.')
style.prefs_window.xfill=True
style.prefs_window.ypadding = 0.05
style.create('skip_indicator', 'default',
'(text, position) The style of the text that is used to indicate that skipping is in progress.')
style.skip_indicator.xpos = 10
style.skip_indicator.ypos = 10
+56 -10
View File
@@ -2,7 +2,8 @@
import os.path
# Go psyco! (Compile where we can.)
# Enable psyco. Warning: Check for memory leaks!
try:
if not os.path.exists("nopsyco"):
import psyco
@@ -24,12 +25,7 @@ import encodings.zlib_codec
# Load up all of Ren'Py, in the right order.
import renpy
if __name__ == "__main__":
# Stdout should be a utf-8 stream, so print works nicely.
# utf8writer = codecs.getwriter("utf-8")
# sys.stdout = utf8writer(sys.stdout)
def main():
name = os.path.basename(sys.argv[0])
@@ -51,6 +47,9 @@ if __name__ == "__main__":
op.add_option('--python', dest='python', default=None,
help='Run the argument in the python interpreter.')
op.add_option('--leak', dest='leak', action='store_true', default=False,
help='When the game exits, dumps a profile of memory usage.')
options, args = op.parse_args()
if options.python:
@@ -72,6 +71,16 @@ if __name__ == "__main__":
type, value, tb = sys.exc_info()
print >>f, type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print >>f
print >>f, renpy.game.exception_info
print >>f
print >>f, "-- Full Traceback ------------------------------------------------------------"
print >>f
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
@@ -96,8 +105,45 @@ if __name__ == "__main__":
os.startfile('traceback.txt')
except:
pass
sys.exit(0)
if options.leak:
memory_profile()
sys.exit(0)
def memory_profile():
print "Memory Profile"
print
print "Showing all objects in memory at program termination."
print
import gc
gc.collect()
objs = gc.get_objects()
c = { } # count
dead_renders = 0
for i in objs:
t = type(i)
c[t] = c.get(t, 0) + 1
if isinstance(i, renpy.display.render.Render):
if i.dead:
dead_renders += 1
results = [ (count, ty) for ty, count in c.iteritems() ]
results.sort()
for count, ty in results:
print count, str(ty)
if dead_renders:
print
print "*** found", dead_renders, "dead Renders. ***"
if __name__ == "__main__":
main()
+822
View File
@@ -0,0 +1,822 @@
#!/usr/bin/python
#
# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# COVERAGE.PY -- COVERAGE TESTING
#
# Gareth Rees, Ravenbrook Limited, 2001-12-04
# Ned Batchelder, 2004-12-12
# http://nedbatchelder.com/code/modules/coverage.html
#
#
# 1. INTRODUCTION
#
# This module provides coverage testing for Python code.
#
# The intended readership is all Python developers.
#
# This document is not confidential.
#
# See [GDR 2001-12-04a] for the command-line interface, programmatic
# interface and limitations. See [GDR 2001-12-04b] for requirements and
# design.
"""Usage:
coverage.py -x MODULE.py [ARG1 ARG2 ...]
Execute module, passing the given command-line arguments, collecting
coverage data.
coverage.py -e
Erase collected coverage data.
coverage.py -r [-m] FILE1 FILE2 ...
Report on the statement coverage for the given files. With the -m
option, show line numbers of the statements that weren't executed.
coverage.py -a [-d dir] FILE1 FILE2 ...
Make annotated copies of the given files, marking statements that
are executed with > and statements that are missed with !. With
the -d option, make the copies in that directory. Without the -d
option, make each copy in the same directory as the original.
Coverage data is saved in the file .coverage by default. Set the
COVERAGE_FILE environment variable to save it somewhere else."""
__version__ = "2.2.20041231" # see detailed history at the end of this file.
import compiler
import compiler.visitor
import os
import re
import string
import sys
import types
# 2. IMPLEMENTATION
#
# This uses the "singleton" pattern.
#
# The word "morf" means a module object (from which the source file can
# be deduced by suitable manipulation of the __file__ attribute) or a
# filename.
#
# When we generate a coverage report we have to canonicalize every
# filename in the coverage dictionary just in case it refers to the
# module we are reporting on. It seems a shame to throw away this
# information so the data in the coverage dictionary is transferred to
# the 'cexecuted' dictionary under the canonical filenames.
#
# The coverage dictionary is called "c" and the trace function "t". The
# reason for these short names is that Python looks up variables by name
# at runtime and so execution time depends on the length of variables!
# In the bottleneck of this application it's appropriate to abbreviate
# names to increase speed.
# A dictionary with an entry for (Python source file name, line number
# in that file) if that line has been executed.
c = {}
# t(f, x, y). This method is passed to sys.settrace as a trace
# function. See [van Rossum 2001-07-20b, 9.2] for an explanation of
# sys.settrace and the arguments and return value of the trace function.
# See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
# objects.
def t(f, w, a):
if w == 'line':
c[(f.f_code.co_filename, f.f_lineno)] = 1
return t
class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
def __init__(self, statements, excluded, suite_spots):
compiler.visitor.ASTVisitor.__init__(self)
self.statements = statements
self.excluded = excluded
self.suite_spots = suite_spots
self.excluding_suite = 0
def doRecursive(self, node):
self.recordNodeLine(node)
for n in node.getChildNodes():
self.dispatch(n)
visitStmt = visitModule = doRecursive
def doCode(self, node):
if hasattr(node, 'decorators') and node.decorators:
self.dispatch(node.decorators)
self.doSuite(node, node.code)
visitFunction = visitClass = doCode
def getFirstLine(self, node):
# Find the first line in the tree node.
lineno = node.lineno
for n in node.getChildNodes():
f = self.getFirstLine(n)
if lineno and f:
lineno = min(lineno, f)
else:
lineno = lineno or f
return lineno
def getLastLine(self, node):
# Find the first line in the tree node.
lineno = node.lineno
for n in node.getChildNodes():
lineno = max(lineno, self.getLastLine(n))
return lineno
def doStatement(self, node):
self.recordLine(self.getFirstLine(node))
visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
visitPrintnl = visitRaise = visitSubscript = \
visitDecorators = \
doStatement
def recordNodeLine(self, node):
return self.recordLine(node.lineno)
def recordLine(self, lineno):
# Returns a bool, whether the line is included or excluded.
if lineno:
# Multi-line tests introducing suites have to get charged to their
# keyword.
if lineno in self.suite_spots:
lineno = self.suite_spots[lineno][0]
# If we're inside an exluded suite, record that this line was
# excluded.
if self.excluding_suite:
self.excluded[lineno] = 1
return 0
# If this line is excluded, or suite_spots maps this line to
# another line that is exlcuded, then we're excluded.
elif self.excluded.has_key(lineno) or \
self.suite_spots.has_key(lineno) and \
self.excluded.has_key(self.suite_spots[lineno][1]):
return 0
# Otherwise, this is an executable line.
else:
self.statements[lineno] = 1
return 1
return 0
default = recordNodeLine
def recordAndDispatch(self, node):
self.recordNodeLine(node)
self.dispatch(node)
def doSuite(self, intro, body, exclude=0):
exsuite = self.excluding_suite
if exclude or (intro and not self.recordNodeLine(intro)):
self.excluding_suite = 1
self.recordAndDispatch(body)
self.excluding_suite = exsuite
def doPlainWordSuite(self, prevsuite, suite):
# Finding the exclude lines for else's is tricky, because they aren't
# present in the compiler parse tree. Look at the previous suite,
# and find its last line. If any line between there and the else's
# first line are excluded, then we exclude the else.
lastprev = self.getLastLine(prevsuite)
firstelse = self.getFirstLine(suite)
for l in range(lastprev+1, firstelse):
if self.suite_spots.has_key(l):
self.doSuite(None, suite, exclude=self.excluded.has_key(l))
break
else:
self.doSuite(None, suite)
def doElse(self, prevsuite, node):
if node.else_:
self.doPlainWordSuite(prevsuite, node.else_)
def visitFor(self, node):
self.doSuite(node, node.body)
self.doElse(node.body, node)
def visitIf(self, node):
# The first test has to be handled separately from the rest.
# The first test is credited to the line with the "if", but the others
# are credited to the line with the test for the elif.
self.doSuite(node, node.tests[0][1])
for t, n in node.tests[1:]:
self.doSuite(t, n)
self.doElse(node.tests[-1][1], node)
def visitWhile(self, node):
self.doSuite(node, node.body)
self.doElse(node.body, node)
def visitTryExcept(self, node):
self.doSuite(node, node.body)
for i in range(len(node.handlers)):
a, b, h = node.handlers[i]
if not a:
# It's a plain "except:". Find the previous suite.
if i > 0:
prev = node.handlers[i-1][2]
else:
prev = node.body
self.doPlainWordSuite(prev, h)
else:
self.doSuite(a, h)
self.doElse(node.handlers[-1][2], node)
def visitTryFinally(self, node):
self.doSuite(node, node.body)
self.doPlainWordSuite(node.body, node.final)
def visitGlobal(self, node):
# "global" statements don't execute like others (they don't call the
# trace function), so don't record their line numbers.
pass
the_coverage = None
class coverage:
error = "coverage error"
# Name of the cache file (unless environment variable is set).
cache_default = ".coverage"
# Environment variable naming the cache file.
cache_env = "COVERAGE_FILE"
# A map from canonical Python source file name to a dictionary in
# which there's an entry for each line number that has been
# executed.
cexecuted = {}
# Cache of results of calling the analysis2() method, so that you can
# specify both -r and -a without doing double work.
analysis_cache = {}
# Cache of results of calling the canonical_filename() method, to
# avoid duplicating work.
canonical_filename_cache = {}
def __init__(self):
global the_coverage
if the_coverage:
raise self.error, "Only one coverage object allowed."
self.usecache = 1
self.cache = None
self.exclude_re = ''
def help(self, error=None):
if error:
print error
print
print __doc__
sys.exit(1)
def command_line(self):
import getopt
settings = {}
optmap = {
'-a': 'annotate',
'-d:': 'directory=',
'-e': 'erase',
'-h': 'help',
'-i': 'ignore-errors',
'-m': 'show-missing',
'-r': 'report',
'-x': 'execute',
}
short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
long_opts = optmap.values()
options, args = getopt.getopt(sys.argv[1:], short_opts,
long_opts)
for o, a in options:
if optmap.has_key(o):
settings[optmap[o]] = 1
elif optmap.has_key(o + ':'):
settings[optmap[o + ':']] = a
elif o[2:] in long_opts:
settings[o[2:]] = 1
elif o[2:] + '=' in long_opts:
settings[o[2:]] = a
else:
self.help("Unknown option: '%s'." % o)
if settings.get('help'):
self.help()
for i in ['erase', 'execute']:
for j in ['annotate', 'report']:
if settings.get(i) and settings.get(j):
self.help("You can't specify the '%s' and '%s' "
"options at the same time." % (i, j))
args_needed = (settings.get('execute')
or settings.get('annotate')
or settings.get('report'))
action = settings.get('erase') or args_needed
if not action:
self.help("You must specify at least one of -e, -x, -r, or -a.")
if not args_needed and args:
self.help("Unexpected arguments %s." % args)
self.get_ready()
self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
if settings.get('erase'):
self.erase()
if settings.get('execute'):
if not args:
self.help("Nothing to do.")
sys.argv = args
self.start()
import __main__
sys.path[0] = os.path.dirname(sys.argv[0])
execfile(sys.argv[0], __main__.__dict__)
if not args:
args = self.cexecuted.keys()
ignore_errors = settings.get('ignore-errors')
show_missing = settings.get('show-missing')
directory = settings.get('directory=')
if settings.get('report'):
self.report(args, show_missing, ignore_errors)
if settings.get('annotate'):
self.annotate(args, directory, ignore_errors)
def use_cache(self, usecache):
self.usecache = usecache
def get_ready(self):
if self.usecache and not self.cache:
self.cache = os.environ.get(self.cache_env, self.cache_default)
self.restore()
self.analysis_cache = {}
def start(self):
self.get_ready()
sys.settrace(t)
def stop(self):
sys.settrace(None)
def erase(self):
global c
c = {}
self.analysis_cache = {}
self.cexecuted = {}
if self.cache and os.path.exists(self.cache):
os.remove(self.cache)
self.exclude_re = ''
def exclude(self, re):
if self.exclude_re:
self.exclude_re += "|"
self.exclude_re += "(" + re + ")"
# save(). Save coverage data to the coverage cache.
def save(self):
if self.usecache and self.cache:
self.canonicalize_filenames()
cache = open(self.cache, 'wb')
import marshal
marshal.dump(self.cexecuted, cache)
cache.close()
# restore(). Restore coverage data from the coverage cache (if it
# exists).
def restore(self):
global c
c = {}
self.cexecuted = {}
assert self.usecache
if not os.path.exists(self.cache):
return
try:
cache = open(self.cache, 'rb')
import marshal
cexecuted = marshal.load(cache)
cache.close()
if isinstance(cexecuted, types.DictType):
self.cexecuted = cexecuted
except:
pass
# canonical_filename(filename). Return a canonical filename for the
# file (that is, an absolute path with no redundant components and
# normalized case). See [GDR 2001-12-04b, 3.3].
def canonical_filename(self, filename):
if not self.canonical_filename_cache.has_key(filename):
f = filename
if os.path.isabs(f) and not os.path.exists(f):
f = os.path.basename(f)
if not os.path.isabs(f):
for path in [os.curdir] + sys.path:
g = os.path.join(path, f)
if os.path.exists(g):
f = g
break
cf = os.path.normcase(os.path.abspath(f))
self.canonical_filename_cache[filename] = cf
return self.canonical_filename_cache[filename]
# canonicalize_filenames(). Copy results from "executed" to
# "cexecuted", canonicalizing filenames on the way. Clear the
# "executed" map.
def canonicalize_filenames(self):
global c
for filename, lineno in c.keys():
f = self.canonical_filename(filename)
if not self.cexecuted.has_key(f):
self.cexecuted[f] = {}
self.cexecuted[f][lineno] = 1
c = {}
# morf_filename(morf). Return the filename for a module or file.
def morf_filename(self, morf):
if isinstance(morf, types.ModuleType):
if not hasattr(morf, '__file__'):
raise self.error, "Module has no __file__ attribute."
file = morf.__file__
else:
file = morf
return self.canonical_filename(file)
# analyze_morf(morf). Analyze the module or filename passed as
# the argument. If the source code can't be found, raise an error.
# Otherwise, return a tuple of (1) the canonical filename of the
# source code for the module, (2) a list of lines of statements
# in the source code, and (3) a list of lines of excluded statements.
def analyze_morf(self, morf):
if self.analysis_cache.has_key(morf):
return self.analysis_cache[morf]
filename = self.morf_filename(morf)
ext = os.path.splitext(filename)[1]
if ext == '.pyc':
if not os.path.exists(filename[0:-1]):
raise self.error, ("No source for compiled code '%s'."
% filename)
filename = filename[0:-1]
elif ext != '.py':
raise self.error, "File '%s' not Python source." % filename
source = open(filename, 'r')
lines, excluded_lines = self.find_executable_statements(
source.read(), exclude=self.exclude_re
)
source.close()
result = filename, lines, excluded_lines
self.analysis_cache[morf] = result
return result
def get_suite_spots(self, tree, spots):
import symbol, token
for i in range(1, len(tree)):
if type(tree[i]) == type(()):
if tree[i][0] == symbol.suite:
# Found a suite, look back for the colon and keyword.
lineno_colon = lineno_word = None
for j in range(i-1, 0, -1):
if tree[j][0] == token.COLON:
lineno_colon = tree[j][2]
elif tree[j][0] == token.NAME:
if tree[j][1] == 'elif':
# Find the line number of the first non-terminal
# after the keyword.
t = tree[j+1]
while t and token.ISNONTERMINAL(t[0]):
t = t[1]
if t:
lineno_word = t[2]
else:
lineno_word = tree[j][2]
break
elif tree[j][0] == symbol.except_clause:
# "except" clauses look like:
# ('except_clause', ('NAME', 'except', lineno), ...)
if tree[j][1][0] == token.NAME:
lineno_word = tree[j][1][2]
break
if lineno_colon and lineno_word:
# Found colon and keyword, mark all the lines
# between the two with the two line numbers.
for l in range(lineno_word, lineno_colon+1):
spots[l] = (lineno_word, lineno_colon)
self.get_suite_spots(tree[i], spots)
def find_executable_statements(self, text, exclude=None):
# Find lines which match an exclusion pattern.
excluded = {}
suite_spots = {}
if exclude:
reExclude = re.compile(exclude)
lines = text.split('\n')
for i in range(len(lines)):
if reExclude.search(lines[i]):
excluded[i+1] = 1
import parser
tree = parser.suite(text+'\n\n').totuple(1)
self.get_suite_spots(tree, suite_spots)
# Use the compiler module to parse the text and find the executable
# statements. We add newlines to be impervious to final partial lines.
statements = {}
ast = compiler.parse(text+'\n\n')
visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
compiler.walk(ast, visitor, walker=visitor)
lines = statements.keys()
lines.sort()
excluded_lines = excluded.keys()
excluded_lines.sort()
return lines, excluded_lines
# format_lines(statements, lines). Format a list of line numbers
# for printing by coalescing groups of lines as long as the lines
# represent consecutive statements. This will coalesce even if
# there are gaps between statements, so if statements =
# [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
# format_lines will return "1-2, 5-11, 13-14".
def format_lines(self, statements, lines):
pairs = []
i = 0
j = 0
start = None
pairs = []
while i < len(statements) and j < len(lines):
if statements[i] == lines[j]:
if start == None:
start = lines[j]
end = lines[j]
j = j + 1
elif start:
pairs.append((start, end))
start = None
i = i + 1
if start:
pairs.append((start, end))
def stringify(pair):
start, end = pair
if start == end:
return "%d" % start
else:
return "%d-%d" % (start, end)
import string
return string.join(map(stringify, pairs), ", ")
# Backward compatibility with version 1.
def analysis(self, morf):
f, s, _, m, mf = self.analysis2(morf)
return f, s, m, mf
def analysis2(self, morf):
filename, statements, excluded = self.analyze_morf(morf)
self.canonicalize_filenames()
if not self.cexecuted.has_key(filename):
self.cexecuted[filename] = {}
missing = []
for line in statements:
if not self.cexecuted[filename].has_key(line):
missing.append(line)
return (filename, statements, excluded, missing,
self.format_lines(statements, missing))
def morf_name(self, morf):
if isinstance(morf, types.ModuleType):
return morf.__name__
else:
return os.path.splitext(os.path.basename(morf))[0]
def report(self, morfs, show_missing=1, ignore_errors=0):
if not isinstance(morfs, types.ListType):
morfs = [morfs]
max_name = max([5,] + map(len, map(self.morf_name, morfs)))
fmt_name = "%%- %ds " % max_name
fmt_err = fmt_name + "%s: %s"
header = fmt_name % "Name" + " Stmts Exec Cover"
fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
if show_missing:
header = header + " Missing"
fmt_coverage = fmt_coverage + " %s"
print header
print "-" * len(header)
total_statements = 0
total_executed = 0
for morf in morfs:
name = self.morf_name(morf)
try:
_, statements, _, missing, readable = self.analysis2(morf)
n = len(statements)
m = n - len(missing)
if n > 0:
pc = 100.0 * m / n
else:
pc = 100.0
args = (name, n, m, pc)
if show_missing:
args = args + (readable,)
print fmt_coverage % args
total_statements = total_statements + n
total_executed = total_executed + m
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
type, msg = sys.exc_info()[0:2]
print fmt_err % (name, type, msg)
if len(morfs) > 1:
print "-" * len(header)
if total_statements > 0:
pc = 100.0 * total_executed / total_statements
else:
pc = 100.0
args = ("TOTAL", total_statements, total_executed, pc)
if show_missing:
args = args + ("",)
print fmt_coverage % args
# annotate(morfs, ignore_errors).
blank_re = re.compile("\\s*(#|$)")
else_re = re.compile("\\s*else\\s*:\\s*(#|$)")
def annotate(self, morfs, directory=None, ignore_errors=0):
for morf in morfs:
try:
filename, statements, excluded, missing, _ = self.analysis2(morf)
self.annotate_file(filename, statements, excluded, missing, directory)
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
raise
def annotate_file(self, filename, statements, excluded, missing, directory=None):
source = open(filename, 'r')
if directory:
dest_file = os.path.join(directory,
os.path.basename(filename)
+ ',cover')
else:
dest_file = filename + ',cover'
dest = open(dest_file, 'w')
lineno = 0
i = 0
j = 0
covered = 1
while 1:
line = source.readline()
if line == '':
break
lineno = lineno + 1
while i < len(statements) and statements[i] < lineno:
i = i + 1
while j < len(missing) and missing[j] < lineno:
j = j + 1
if i < len(statements) and statements[i] == lineno:
covered = j >= len(missing) or missing[j] > lineno
if self.blank_re.match(line):
dest.write(' ')
elif self.else_re.match(line):
# Special logic for lines containing only
# 'else:'. See [GDR 2001-12-04b, 3.2].
if i >= len(statements) and j >= len(missing):
dest.write('! ')
elif i >= len(statements) or j >= len(missing):
dest.write('> ')
elif statements[i] == missing[j]:
dest.write('! ')
else:
dest.write('> ')
elif lineno in excluded:
dest.write('- ')
elif covered:
dest.write('> ')
else:
dest.write('! ')
dest.write(line)
source.close()
dest.close()
# Singleton object.
the_coverage = coverage()
# Module functions call methods in the singleton object.
def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
def start(*args, **kw): return the_coverage.start(*args, **kw)
def stop(*args, **kw): return the_coverage.stop(*args, **kw)
def erase(*args, **kw): return the_coverage.erase(*args, **kw)
def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
def report(*args, **kw): return the_coverage.report(*args, **kw)
def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
# Save coverage data when Python exits. (The atexit module wasn't
# introduced until Python 2.0, so use sys.exitfunc when it's not
# available.)
try:
import atexit
atexit.register(the_coverage.save)
except ImportError:
sys.exitfunc = the_coverage.save
# Command-line interface.
if __name__ == '__main__':
the_coverage.command_line()
# A. REFERENCES
#
# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
# Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/>.
#
# [GDR 2001-12-04b] "Statement coverage for Python: design and
# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/design.html>.
#
# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
# Guide van Rossum; 2001-07-20;
# <http://www.python.org/doc/2.1.1/ref/ref.html>.
#
# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-12-04 GDR Created.
#
# 2001-12-06 GDR Added command-line interface and source code
# annotation.
#
# 2001-12-09 GDR Moved design and interface to separate documents.
#
# 2001-12-10 GDR Open cache file as binary on Windows. Allow
# simultaneous -e and -x, or -a and -r.
#
# 2001-12-12 GDR Added command-line help. Cache analysis so that it
# only needs to be done once when you specify -a and -r.
#
# 2001-12-13 GDR Improved speed while recording. Portable between
# Python 1.5.2 and 2.1.1.
#
# 2002-01-03 GDR Module-level functions work correctly.
#
# 2002-01-07 GDR Update sys.path when running a file with the -x option,
# so that it matches the value the program would get if it were run on
# its own.
#
# 2004-12-12 NMB Significant code changes.
# - Finding executable statements has been rewritten so that docstrings and
# other quirks of Python execution aren't mistakenly identified as missing
# lines.
# - Lines can be excluded from consideration, even entire suites of lines.
# - The filesystem cache of covered lines can be disabled programmatically.
# - Modernized the code.
#
# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
# and add 'analysis2'. Add a global for 'annotate', and factor it, adding
# 'annotate_file'.
#
# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
#
# C. COPYRIGHT AND LICENCE
#
# Copyright 2001 Gareth Rees. All rights reserved.
# Copyright 2004 Ned Batchelder. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
# $Id: coverage.py 5 2004-12-14 12:08:23Z ned $
+278 -9
View File
@@ -40,6 +40,29 @@ init:
# clauses and statements.
$ fade = Fade(.5, 0, .5) # Fade to black and back.
$ dissolve = Dissolve(0.5)
$ wiperight = CropMove(1.0, "wiperight")
$ wipeleft = CropMove(1.0, "wipeleft")
$ wipeup = CropMove(1.0, "wipeup")
$ wipedown = CropMove(1.0, "wipedown")
$ slideright = CropMove(1.0, "slideright")
$ slideleft = CropMove(1.0, "slideleft")
$ slideup = CropMove(1.0, "slideup")
$ slidedown = CropMove(1.0, "slidedown")
$ slideawayright = CropMove(1.0, "slideawayright")
$ slideawayleft = CropMove(1.0, "slideawayleft")
$ slideawayup = CropMove(1.0, "slideawayup")
$ slideawaydown = CropMove(1.0, "slideawaydown")
$ irisout = CropMove(1.0, "irisout")
$ irisin = CropMove(1.0, "irisin")
# Select the transitions that are used when entering and exiting
# the game menu.
$ library.enter_transition = dissolve
$ library.exit_transition = dissolve
# Now, we declare the images that are used in the program.
@@ -101,9 +124,14 @@ label start:
scene washington with fade
show eileen vhappy with dissolve
# Store the current version of Ren'Py into a variable, so we can
# interpolate it into the next line.
$ version = renpy.version()
# Display a line of dialogue. In this case, we manually specify
# who's saying the line of dialoge.
"Girl" "Hi, and welcome to the Ren'Py 4 demo program."
# who's saying the line of dialogue. We also interpolate in the
# version of Ren'Py we're using.
"Girl" "Hi, and welcome to the %(version)s demo program."
# This instantly replaces the very happy picture of Eileen with
# one showing her merely happy. It demonstrates how the show
@@ -162,6 +190,10 @@ label choices:
call writing from _call_writing_1
jump choices
"What's new with Ren'Py?":
call whatsnew from _call_whatsnew_1
jump choices
# This choice has a condition associated with it. It is only
# displayed if the condition is true (in this case, if we have
# selected at least one other choice has been chosen.)
@@ -448,7 +480,7 @@ label find_out_more:
e "If you have questions, the best place to ask them is the Ren'Py
forum of the Lemmasoft forums."
e "Just go to http://www.lemmasoft.net/forums/, and click on
e "Just go to http://lemmasoft.renai.us/forums/, and click on
Ren'Py."
e "We thank Blue Lemma for hosting our forum."
@@ -604,6 +636,47 @@ label ending:
$ renpy.full_restart()
label speedtest:
with None
scene whitehouse
show eileen happy
with dissolve
e "Okay, I'm going to run the speedtest on your system."
e "I'll only be testing the performance of the dissolve
transition. It taxes your system the most, as it needs to
redraw the entire screen each frame."
$ frames = config.frames
with None
scene washington
show eileen happy
with Dissolve(5.0)
$ frames = config.frames - frames
$ fps = frames / 5.0
e "Well, your system displayed %(frames)d frames in five
seconds. That's %(fps).1f fps."
e "Remember, this is the worst-case speed, as usually we can just
draw the parts of the screen that have changed."
e "Thanks for viewing the secret speed test."
return
# Setup the secret key for the speedtest.
init:
python:
config.keymap['speedtest'] = [ 'S' ]
config.underlay.append(renpy.Keymap(speedtest=renpy.curried_call_in_new_context('speedtest')))
init:
# This is just some example code to show the ui functions in
@@ -736,10 +809,206 @@ init:
plan[editing] = value
editing = None
return plan
init:
image movie = Movie()
python:
style.create('odd_window', 'say_window')
style.odd_window.left_margin = 50
style.odd_window.right_margin = 150
style.odd_window.bottom_margin = 25
eodd = Character('Eileen', color=(200, 255, 200, 255), window_style='odd_window')
label whatsnew:
show washington
show eileen happy
e "I can give you a demonstration of some of the new features in
Ren'Py, but you'll have to tell me what version you want to
start with."
menu:
"I'd like to start with 4.5.":
jump whatsnew45
"I'd like to start with 4.6.":
jump whatsnew46
"I'd like to start with 4.7.":
jump whatsnew47
label whatsnew45:
show washington
show eileen happy
e "While most of the improvements in Ren'Py 4.5 were behind the scenes,
we can give you a demonstration of one of the new features."
e "There is now a new transition, CropMove, that can be used to
provide a whole range of transition effects."
hide eileen with dissolve
e "I'll stand offscreen, so you can see some of its modes. I'll read
out the mode name after each transiton."
scene whitehouse with wiperight
e "We first have wiperight..."
scene washington with wipeleft
e "...followed by wipeleft... "
scene whitehouse with wipeup
e "...wipeup..."
scene washington with wipedown
e "...and wipedown."
e "Next, the slides."
scene whitehouse with slideright
e "Slideright..."
scene washington with slideleft
e "...slideleft..."
scene whitehouse with slideup
e "...slideup..."
scene washington with slidedown
e "and slidedown."
e "We also have a couple of transitions that use a rectangular iris."
scene whitehouse with irisout
e "There's irisout..."
with None
scene washington
show eileen happy
with irisin
e "... and irisin."
e "There are other transitions, such as various forms of
slideaway. And if you can't find the transition for you, you
can write a custom one."
e "It's enough to make you feel a bit dizzy."
e "Ren'Py 4.5 also includes the ability to show MPEG-1 movies as
cutscenes or even backgrounds."
label ike:
if renpy.exists('Eisenhow1952.mpg'):
e "Since you downloaded the Eisenhower commercial, I can show
it to you as a cutscene."
e "You can click to continue if it gets on your nerves too
much."
$ renpy.movie_cutscene('Eisenhow1952.mpg', 63.0)
hide eileen
show movie at Position(xpos=420, ypos=25, xanchor='left', yanchor='top')
show eileen happy
$ renpy.movie_start_displayable('Eisenhow1952.mpg', (352, 240))
e "Ren'Py can even overlay rendered images on top of a movie,
although that's more taxing for your CPU."
e "It's like I'm some sort of newscaster or something."
$ renpy.movie_stop()
hide movie
else:
e "You haven't downloaded the Eisenhower commercial, so we
can't demonstrate it."
label whatsnew46:
eodd "As of 4.6, we now support separate padding and margin for the
left, right, top, and bottom sides of a window."
eodd "This means that a game can have oddly shaped windows without
having to go beyond the style system."
e "We also introduced a new layer system, and the ability to have
transitions affect only one layer."
e "Because of this we can do things like slide away a window..."
$ renpy.transition(slideawayup, 'transient')
$ renpy.pause(1.5)
$ renpy.transition(slidedown, 'transient')
e "... and slide it back in again."
e "Also new in this release is the ability to specify transitions
that occur when you enter and exit the game menu."
e "Right click to see them, if you want."
e "A few more obscure features involving things like overlays and
activated widgets round out the 4.6 release."
label whatsnew47:
e "Ren'Py 4.7 brought with it a total rewrite of the way text is
rendered to the screen."
e "It introduced text tags, which let a script writer control how
text is shown on the screen."
e "Text tags can make text {b}bold{/b}, {i}italic{/i}, or even
{u}underlined{/u}."
e "They can make the font size {size=+12}bigger{/size} or
{size=-8}smaller{/size}."
e "They can even change the
{color=#f00}color{/color}
{color=#ff0}of{/color}
{color=#0f0}the{/color}
{color=#0ff}text{/color}."
e "We also added bold, italic, and underline style properties, which can
be styled onto any text."
e "Used with care, text tags can enhance {b}your{/b} game."
e "{u}Used{/u} with {i}abandon,{/i} they {b}can{/b} make {b}your{/b}
game {color=#333}hard{/color} {color=#888}to{/color} {color=#ccc}read{/color}."
e "With great power comes great responsibility, after all."
e "And we want to give you all the power you need."
label whatsnewend:
e "Anyway, now that you've heard about some of the new features, is there anything
else I can help you with?"
return
+11 -2
View File
@@ -43,12 +43,18 @@ def copy_tree(source, dest, should_copy=lambda fn : True, license=""):
if "/CVS" in dirpath:
continue
if "/.svn" in dirpath:
continue
reldir = dirpath[len(source):]
dstrel = dest + "/" + reldir
for i in dirnames:
if i == "CVS":
continue
if i == ".svn":
continue
os.mkdir(dstrel + "/" + i)
@@ -99,13 +105,16 @@ def main():
copy_tree("doc", target + "/doc",
should_copy = lambda fn : fn in doc_files)
# Copy the game
# Copy the game
copy_tree(gamedir, target + "/game",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~") and not fn.endswith(".mpg"))
copy_tree("common", target + "/common",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
copy_tree("extras", target + "/extras",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
def cp(x, license=""):
copy_file(x, target + "/" + x)
+6
View File
@@ -84,6 +84,12 @@ def function(m):
renpy.store.renpy = renpy.exports
func = eval(name, store)
if isinstance(func, renpy.curry.Curry):
if func.callable == renpy.curry.Curry:
func = func.args[0]
else:
func = func.callable
doc = func.__doc__
+381 -127
View File
@@ -359,7 +359,8 @@ e "Starting today, I'll be living here."
<p>
Finally, the string in a dialogue is subject to interpolation of
variables. A string variable can be interpolated with %(name)s,
while a number requires %(name)d. For example:
while a number requires %(name)d. The interpolated value is quoted
so that a text tag cannot be interpolated in by mistake. For example:
</p>
<example>
@@ -540,12 +541,20 @@ menu what_to_do:
<p>
Without the ability to display images to the user, a visual
novel would be a text adventure. Ren'Py controls image display
by using a <def>scene list</def>, a list of things to be
displayed to the user. Every time an interaction starts (that
is, a line of dialogue or a menu is displayed), the things in
the scene list are drawn to the screen, with the first being in
by using a <def>layers</def>, each comprising a list of things to be
displayed to the user. By default, there are three layers. The
master layer is manipulated using the various image display
statement defined in this section. The transient layer is used to
display ui widgets, like windows containing dialogue and
menus. Finally, an overlay layer exists to allow things to be
overlaid on top of the screen.
</p>
<p>
Every time an interaction starts (for example, a line of dialogue or a menu is displayed), the things in
the each of the layers is drawn to the screen, with the first being in
the back and the last being in the front. A number of statements
manipulate the scene list. Before we can explain them, however,
manipulate the contents of the master layer. Before we can explain them, however,
we should first define a few terms.
</p>
@@ -607,16 +616,16 @@ init:
<p>
The next display statement is the show statement, which takes an
image specifier and displays it on the screen. If an image with
the same tag as the image given in the spec already exists on
the scene list, it is replaced with the newly displayed
image specifier and displays it on the master layer. If an image with
the same tag as the image given in the spec already exists in
the master layer, it is replaced with the newly displayed
Displayable. Otherwise, the new one is added to the end of the
scene list (that is, closest to the user).
master layer (that is, closest to the user).
</p><p>
If an at list is present, the image is transformed with the at
list before being added to the display list.
list before being added to the master layer.
</p><p>
@@ -630,7 +639,7 @@ init:
<rule>scene_statement -> "scene" ( image_spec )?</rule>
<p>
The scene statement first clears the scene_list. If the optional
The scene statement first clears the master layer. If the optional
image_spec is present, it is shown as if it was shown with the
show statement. The best use for the image_spec on a scene
command is show a background for the scene.
@@ -665,8 +674,8 @@ e "But sometimes, I can get upset for no good reason."
<rule>hide_statement -> "hide" image_spec</rule>
<p>
The hide statement is used to remove an image from the scene
list. The image spec is parsed for a tag, and any image matching
The hide statement is used to remove an image from the master
layer. The image spec is parsed for a tag, and any image matching
that tag is remove from the scene. The at list is ignored (but
should be valid or omitted), and transitions associated with the
image spec are run.
@@ -814,8 +823,8 @@ show eileen happy at left
<!-- func renpy.ParameterizedText -->
<p>
This is used to impelement the text image. The user may also want
to instatiate their own ParameterizedText object (in an init
This is used to implement the text image. The user may also want
to instantiate their own ParameterizedText object (in an init
block) if they want to have more than one bit of text on the
screen at once, or if they want to change the style (and therefore
the position) of that text.
@@ -914,22 +923,22 @@ show ball at Move((0.0, 0.0), (1.0, 1.0), 10.0,
<h3>Transitions</h3>
<p>
By default, Ren'Py displays each scene list by replacing the old
scene list with a new one. This is appropriate in general (such as
for emotion changes), but it may be boring for large scene
changes, such as a change in scene or a character entering or
By default, Ren'Py displays each scene by replacing the old
scene with a new one. This is appropriate in general (such as
for emotion changes), but it may be boring for large
changes, such as a change in location or a character entering or
leaving the scene. Ren'Py supports transitions that control how
changes to the scene lists are exposed to the user.
</p>
<p>
Transitions occur between the last scene list that was shown to
the user, and the current scene list that has been updated using
Transitions occur between the last scene that was shown to
the user, and the current scene that has been updated using
the scene, show, or hide statements. A transition takes both lists
as input, and is responsible for displaying the transition between
them to the user. Each transition runs for a given amount of time,
but may be dismissed early by the user. Once a transition is
shown, the scene list is considered shown for the purposes of
shown, the scene is considered shown for the purposes of
future transitions.
</p>
@@ -1068,57 +1077,23 @@ e "How are you doing?" with dissolve
</dl>
<p>
Functions that return things that are useful as arguments to with
statements or clauses are:
The following are functions that return things useful as
transitions. The user should not supply the new_widget or old_widget
parameters, as these are supplied by Ren'Py when a transition
begins.
</p>
<function name="Fade" sig="(in_time, hold_time, out_time, color=(0, 0, 0))">
<p>
This returns an object that can be used as an argument to a with
statement to fade the old scene into a solid color, waits for a
given amount of time, and then fades from the solid color into
the new scene.
</p>
<!-- func Fade -->
<p>
<param>in_time</param> - The amount of time that will be spent
fading from the old scene to the solid color. A float, given as
seconds.
</p>
<!-- func Dissolve -->
<p>
<param>hold_time</param> - The amount of time that will be spent
displaying the solid color. A float, given as seconds.
</p>
<!-- func CropMove -->
<p>
<param>out_time</param> - The amount of time that will be spent
fading from the solid color to the new scene. A float, given as
seconds.
</p>
<p>
<param>color</param> - The solid color that will be faded
to. This is an RGB triple, where each element is in the range 0
to 255. This defaults to black.
</p>
</function>
<function name="Dissolve" sig="(delay)">
<p>
This dissolves from the old scene to the new scene, by
overlaying the new scene on top of the old scene and varying its
alpha from 0 to 255.
</p>
<p>
<param>delay</param> - The amount of time the dissolve will
take.
</p>
</function>
<p>
Some transitions can also be applied to specific layers, using the
renpy.transition function (documented below). Only transitions that
are not completely opaque can be used in this way.
</p>
<h3>Control Statements</h3>
@@ -1740,6 +1715,8 @@ e "Pleased to meet you, %(name)s."
<!-- func renpy.transition -->
<!-- func renpy.restart_interaction -->
<!-- func renpy.full_restart -->
<!-- func renpy.quit -->
@@ -1748,6 +1725,8 @@ e "Pleased to meet you, %(name)s."
<!-- func renpy.windows -->
<!-- func renpy.exists -->
<!-- func renpy.clear_game_runtime -->
<!-- func renpy.get_game_runtime -->
@@ -1778,10 +1757,14 @@ e "Pleased to meet you, %(name)s."
<p>
Overlays are set up by adding to the config.overlay_functions list
a python function which, when called, returns a list of
Displayables that are added to the overlay. These functions are
a python function which, when called, uses the ui functions to add
widgets to the screen. By default, such widgets are added to the
'overlay' layer, but a call to ui.layer() can change the layer
to any of the layers listed in library.overlay_layers. These functions are
called for each interaction, which allows the overlay to change to
reflect the status of game variables.
reflect the status of game variables. If a variable affecting the
overlay changes during an interaction, renpy.restart_interaction()
should be called to regenerate the overlay.
</p>
<p>
@@ -1799,11 +1782,9 @@ init:
python hide:
def date_overlay():
if date:
return [ Image(date + ".png",
xpos=1.0, xanchor="right",
ypos=0.0, yanchor="top") ]
else:
return [ ]
ui.image(date + ".png",
xpos=1.0, xanchor="right",
ypos=0.0, yanchor="top") ]
config.overlay_functions.append(date_overlay)
</example>
@@ -1817,7 +1798,7 @@ init:
</p>
<h3>Sound and Music Functions</h3>
<h3>Multimedia: Sound, Music, and Movies</h3>
<h4>Sound</h4>
@@ -1895,6 +1876,79 @@ e "Just kidding."
of old music when a new track is started.
</p>
<h4>Movies</h4>
<p>
As of version 4.5, Ren'Py is capable of playing MPEG-1 movies. This
allows you to include video as part of your game. There are two ways
that this can be done. Displaying video in fullscreen mode allows
video to be scaled up to very large sizes, while displaying video in a
displayable allows it to be blended with images and text.
</p>
<p>
Displaying a video fullscreen allows it to take advantage of hardware
decode and scaling support. The downside of fullscreen mode is that
nothing else can be displayed while the movie is being displayed.
</p>
<p>
The easiest way to display a movie fullscreen is to display it using
the renpy.cutscene function. This function displays a movie for a
specified length of time. When that time has elapsed, or when the user
clicks to dismiss the movie, the movie ends and the function returns.
</p>
<!-- func renpy.movie_cutscene -->
<p>
Displaying a movie in a displayable is more difficult, but it allows
the movie to be mixed with rendered text and graphics. A downside of
this method is that the screen needs to be re-rendered with each
frame, a process that may be unacceptibly slow when the screen is
large. We don't recommend this when the game window is larger that
640x480.
</p>
<p>
To display a movie in a displayable, we must start the movie and show
a Movie displayable on the screen. The movie is then displayed on the
screen along with any other widget. When we no longer want to show it,
we need to stop the decoding of the movie and hide the displayable.
</p>
<!-- func Movie -->
<!-- func renpy.movie_start_displayable -->
<!-- func renpy.movie_stop -->
<example>
init:
image movie = Movie
$ renpy.movie_start_displayable("bg.mpg", (640, 480), -1)
scene movie
show eileen happy
e "I'm standing in front of a movie."
e "Isn't that keen?"
hide movie
$ renpy.movie_stop()
</example>
<p>
Whenever a movie is playing, it is expected to provide all sound that
the user hears. Ren'Py disables sound hardware for the duration of the
movie.
</p>
<example>
$ renpy.cutscene("On_Your_Mark.mpg", 394)
</example>
<h3>Configuration Variables</h3>
<p>
@@ -2072,6 +2126,31 @@ load scripts, as scripts will be loaded before it can be set.
screen.
</var>
<var name="config.layers" value="[ 'master', 'transient', 'overlay' ]">
This variable gives a list of all of the layers that Ren'Py knows
about, in the order that they will be displayed to the screen. (The
lowest layer is the first entry in the list.) Ren'Py uses the layers
"master", "transient", and "overlay" internally, so they should
always be in this list.
</var>
<var name="config.transient_layers" value="[ 'transient' ]" >
This variable gives a list of all of the transient layers. Transient
layers are layers that are cleared after each interaction. "transient"
should always be in this list.
</var>
<var name="config.overlay_layers" value="[ 'overlay' ]" >
This is a list of all of the overlay layers. Overlay layers are
cleared before the overlay functions are called. "overlay" should
always be in this list.
</var>
<var name="config.overlay_during_wait" value="True" >
True if we want overlays to be shown during wait statements, or False
if we'd prefer that they be hidden during the wait statements.
</var>
<var name="library.file_page_length" value="10">
This is the number of save slots that are shown in the picker
that's used by the load and save portions of the game menu. This
@@ -2100,14 +2179,14 @@ the file picker.
thumbnail is shown to the user.
</var>
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("Quit Game", "_quit") ]'>
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("Preferences", "_preferences"), ("Quit Game", "_quit") ]'>
This is used to give the main menu that is shown to the user
when the game first starts. It is a list of tuples, where the
first element of each tuple is the title of the menu button, and
the second element is a label that we jump to when that button
is selected. Two useful labels to use here are "_continue",
which brings up the game menu in load mode, and "_quit", which
terminates Ren'Py.
is selected. Some useful labels to use here are "_continue",
which brings up the game menu in load mode, "_preferences", which brings up the preferences screen,
and "_quit", which terminates Ren'Py.
</var>
<var name="library.has_music" value="True">
@@ -2145,6 +2224,16 @@ the file picker.
If True, the library will display a skip indicator when skipping
through the script.
</var>
<var name="library.enter_transition" value="None">
If not None, this variable should give a transition that will be
used when entering the game menu.
</var>
<var name="library.exit_transition" value="None">
If not None, this variable should give a transition that will be
performed when exiting the game menu.
</var>
</dl>
@@ -2214,12 +2303,25 @@ init:
<prop name="color">
The color in which the text will be displayed on the screen, as an
RGBA tuple. Example: (255, 255, 255, 255)
RGBA tuple. Due to a limitation of pygame, currently the alpha is
ignored. Example: (255, 255, 255, 255)
</prop>
<prop name="bold">
If True, then this text will be rendered in bold.
</prop>
<prop name="italic">
If True, then this text will be rendered in italics.
</prop>
<prop name="underline">
If true, then this text will be rendered with an underline.
</prop>
<prop name="antialias">
If True, the text will be antialiased. Otherwise, it will be left
jagged. Example: True
jagged.
</prop>
<prop name="drop_shadow">
@@ -2298,15 +2400,68 @@ indent to quoted dialogue.
128))
</prop>
<prop name="left_margin">
The amount of transparent space left to the left of this window. If a
floating point number, it is scaled to the available width.
</prop>
<prop name="right_margin">
The amount of transparent space left to the right of this window. If a
floating point number, it is scaled to the available width.
</prop>
<prop name="top_margin">
The amount of transparent space left to the top of this window. If a
floating point number, it is scaled to the available height.
</prop>
<prop name="bottom_margin">
The amount of transparent space left to the bottom of this window. If a
floating point number, it is scaled to the available height.
</prop>
<prop name="xmargin">
The amount of transparent space that is left around this window in
the x direction, in pixels.
This is a convenient (and backwards compatible) way of setting
left_margin and right_margin to the same value.
</prop>
<prop name="ymargin">
This is a convenient (and backwards compatible) way of setting
top_margin and bottom_margin to the same value.
</prop>
<prop name="left_padding">
The amount of space left between the edge of the border and the
left side of the contents of the window. If a float, it is scaled to the
width of the window.
</prop>
<prop name="right_padding">
The amount of space left between the edge of the border and the
right side of the contents of the window. If a float, it is scaled to the
width of the window.
</prop>
<prop name="top_padding">
The amount of space left between the edge of the border and the
top side of the contents of the window. If a float, it is scaled to the
height of the window.
</prop>
<prop name="bottom_padding">
The amount of space left between the edge of the border and the
bottom side of the contents of the window. If a float, it is scaled to the
height of the window.
</prop>
<prop name="xpadding">
The amount of space left between the edge of the background of the
window, and the edge of the content contained within this window,
in the x direction, in pixels.
A convenient (and backwards compatible) way of setting
left_padding and right_padding to the same value.
</prop>
<prop name="ypadding">
A convenient (and backwards compatible) way of setting
top_padding and bottom_padding to the same value.
</prop>
<prop name="xfill">
@@ -2314,32 +2469,23 @@ indent to quoted dialogue.
x direction. If False, it will shrink to fit its contents.
</prop>
<prop name="xminimum">
The minimum size of this window in the x direction, including
margins and padding. If the window would be smaller than this, it
is grown to be at least this size.
</prop>
<prop name="ymargin">
The amount of transparent space that is left around this window in
the y direction, in pixels.
</prop>
<prop name="ypadding">
The amount of space left between the edge of the background of the
window, and the edge of the content contained within this window,
in the y direction, in pixels.
</prop>
<prop name="yfill">
If True, the window will expand to fill all available space in the
y direction. If False, it will shrink to fit its contents.
</prop>
<prop name="xminimum">
The minimum size of this window in the x direction, including
margins and padding. If the window would be smaller than this, it
is grown to be at least this size. If a floating point number, it is
scaled to the available width.
</prop>
<prop name="yminimum">
The minimum size of this window in the y direction, including
margins and padding. If the window would be smaller than this, it
is grown to be at least this size.
is grown to be at least this size. If a floating point number, it is
scaled to the available height.
</prop>
@@ -2452,10 +2598,11 @@ The size of the left gutter of a clickable bar, in pixels.
<p>
In Ren'Py, buttons and their contents support the idea of hovering:
using different sets of properties when the mouse is over or not
over the widget. If the mouse is over the widget, then that widget
using different sets of properties for different states of the
widget. If the widget has been selected, it is considered to be
activated. Otherwise, if the mouse is over the widget, then that widget
is hovered, else it is idle. On these widgets, Ren'Py will look up
a property prefixed with "hover_" or "idle_" (as appropriate) in a
a property prefixed with "activate_", "hover_", "idle_", (as appropriate) in a
style, before looking up an unprefixed property. If neither form
is found on a style, the process is repeated again on the parent
style. (The search pattern for background on a hovered button
@@ -2463,6 +2610,11 @@ The size of the left gutter of a clickable bar, in pixels.
default.hover_background, default.background.)
</p>
<p>
The only time an activated widget will be seen by the user is if a
transition occurs immediately after the widget has been activated.
</p>
<p>
As an example, take assigning a background to buttons that changes
when the button is hovered over.
@@ -2564,6 +2716,95 @@ init:
</dl>
<h3>Text Tags</h3>
<p>
Styles are useful if you want to change the look of entire blocks
of text in the game. But what if you only want to change the look of a
small amount of text, or if you want to only change the look of entire
blocks of text. Text tags let you change the look of text within a
single text widget.
</p>
<p>
Text tags work wherever text can be placed. This means that they can
be used inside text widgets, text buttons, and menu and say
statements. But please note that say statement protect from
subsituting in a text tag, as that would most likely be an
error. Generally, though, if Ren'Py shows text to the user, that text
can be formatted through the use of text tags.
</p>
<p>
As an aside, let me point out that if you're using a text tag on every
line of a script, or even many lines, you're probably doing something
wrong. That's what styles are for.
</p>
<p>
Text tags start with a left brace, and continue to the matching right
brace. We chose to use braces because they do not require quoting in
HTML or BBCode. An open text tag may take a parameter, and then continues until
a closing tag, which is a tag that has a slash immediately after the
opening brace. Please note that text tags are sensitive to both case
and whitespace.
</p>
<p>
Some example tags are {b}, {i}, {color=#f88}, {size=+16}. The matching
closing tags are {/b}, {i}, {/color}, and {/size}. Tags must be
properly nested: "{b}{i}bold + italic{/i}{/b}" is okay,
but "{b}{i}this is wrong{/b}{/i}". Right now, improper nesting won't
cause an error but it probably won't do what you want. All open tags
must be closed by the end of the string.
</p>
<p>
If you, for some reason, want to include a left brace, just write two
left braces ({{) instead.
</p>
<h4>Supported Text Tags</h4>
<p>The following are supported text tags:</p>
<ul>
<li>{b}text{/b} makes the enclosed text bold.</li>
<li>{i}text{/i} makes the enclosed text italic.</li>
<li>{u}text{/u} makes the enclosed text underlined.</li>
<li>{plain}text{/plain} makes the enclosed text plain, which means that
it is not bold, italic, or underlined.</li>
<li>{size=<i>spec</i>}text{/size} changes the size of the text. The
supplied spec may be a number, in which case it is the number of
pixels high that the text will be. If it is a number preceded by a
plus (like "+10"), it means to increase the size by that number of
pixels, while if it is preceded by a minus it means to decrease.
</li>
<li>{color=<i>spec</i>}text{/color} changes the color of the text. The
color may be a hex triple or a hex sextuple, optionally preceded by a
hash mark. (This is the same format that color() accepts.)</li>
</ul>
<p>
Here's an example of a say statement that uses many of the text
tags. If you actually use this many text tags in a real game, you'll
probably drive your users mad.
</p>
<example>
"Using text tags, we can make text {size=+12}bigger{/size} or
{size=-8}smaller{/size}. We can make it {b}bold{/b}, {i}italic{/i},
or {u}underlined{/u}. We can even change its {color=#f88}color{/color}."
</example>
<h3>Obfuscating your Game</h3>
<p>
@@ -2677,10 +2918,10 @@ unilaterally making something different from everyone else.
<p>
Anyway, in Ren'Py keysyms are strings. The first kind of keysym is of
the form 'mouse_#', for a number between 1 and 5. These keysyms are
generated by mouse button presses or turns of the mouse wheel. For
example, "mouse_1" is generally the left mouse button, while "mouse_4"
is a turn of the the mouse wheel to the top.
the form 'mouseup_#' or 'mousedown_#', for a number between 1 and 5. These keysyms are
generated by mouse button presses, releases, or turns of the mouse wheel. For
example, "mousedown_1" is generally a press of the left mouse button,
"mouseup_1" is a release of that button, and "mousedown_4" is a turn of the the mouse wheel to the top.
</p>
<p>
@@ -2700,34 +2941,35 @@ This type of keysym looks like "K_BACKSPACE", "K_RETURN", and "K_TAB".
<p>
To change a binding, update the appropriate list in
config.keymap. The following code adds the 't' key to the list of
keys that dismiss a say statement.
keys that dismiss a say statement, and removes the space key from
that list.
</p>
<example>
init:
$ config.keymap['dismiss'].append('t')
$ config.keymap['dismiss'].remove('K_SPACE')
</example>
<p>
The default keymap is contained inside the python code implementing
Ren'Py, and as of version 4.3 is as follows:
Ren'Py, and as of version 4.5 is as follows:
</p>
<example>
keymap = dict(
# Bindings present almost everywhere, unless explicitly
# disabled.
rollback = [ 'K_PAGEUP', 'mouse_4' ],
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
screenshot = [ 's' ],
toggle_fullscreen = [ 'f' ],
toggle_music = [ 'm' ],
game_menu = [ 'K_ESCAPE', 'mouse_3' ],
hide_windows = [ 'mouse_2' ],
game_menu = [ 'K_ESCAPE', 'mousedown_3' ],
hide_windows = [ 'mouseup_2' ],
# Say.
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
# Keymouse.
keymouse_left = [ 'K_LEFT' ],
@@ -2736,23 +2978,23 @@ keymap = dict(
keymouse_down = [ 'K_DOWN' ],
# Menu.
menu_mouseselect = [ 'mouse_1' ],
menu_mouseselect = [ 'mouseup_1' ],
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
menu_keyup = [ 'K_UP' ],
menu_keydown = [ 'K_DOWN' ],
# Button.
button_select = [ 'mouse_1', 'K_RETURN', 'K_KP_ENTER' ],
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
# Input.
input_backspace = [ 'K_BACKSPACE' ],
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
# Imagemap.
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
# Bar.
bar_click = [ 'mouse_1' ],
bar_click = [ 'mouseup_1' ],
# These keys control skipping.
skip = [ 'K_LCTRL', 'K_RCTRL' ],
@@ -2790,7 +3032,7 @@ $ ui.textbutton("B", clicked=ui.returns("B"))
$ ui.textbutton("C", clicked=ui.returns("C"))
$ ui.close()
$ choice = renpy.interact(suppress_overlay=True)
$ choice = ui.interact(suppress_overlay=True)
</example>
<p>
@@ -2835,6 +3077,12 @@ suffer from this problem, and so should probably be used in preference
to lambda.
</p>
<p>
By default, widgets are added to the 'transient' layer. This can be
changed by a call to ui.layer(), followed by a matching call to
ui.close() when done adding widgets to the new layer.
</p>
<p>
The following are the functions available in the ui module, which is
automatically present in the game namespace.
@@ -2854,6 +3102,8 @@ automatically present in the game namespace.
<!-- func ui.hbox -->
<!-- func ui.grid -->
<!-- func ui.fixed -->
<h4>No-Child Widgets and Psuedo-Widgets</h4>
@@ -2874,12 +3124,16 @@ automatically present in the game namespace.
<!-- func ui.saybehavior -->
<!-- func ui.pausebehavior -->
<!-- func ui.keymousebehavior -->
<h4>Functions</h4>
<!-- func ui.interact -->
<!-- func ui.layer -->
<!-- func ui.close -->
<function name="ui.returns" sig="(value)">
+1
View File
@@ -0,0 +1 @@
../../extras/fullscreen.rpy
+1
View File
@@ -0,0 +1 @@
../../extras/kanamode.rpy
+1
View File
@@ -0,0 +1 @@
../script.rpy
+102
View File
@@ -0,0 +1,102 @@
init -10:
$ config.searchpath.append('moonlight')
# Set up the size of the screen, and the window title.
$ config.screen_width = 800
$ config.screen_height = 600
$ config.window_title = "A Ren'Py Extras Game"
# These are positions that can be used inside at clauses. We set
# them up here so that they can be used throughout the program.
$ left = Position(xpos=0.0, xanchor='left')
$ right = Position(xpos=1.0, xanchor='right')
$ center = Position()
# Likewise, we set up some transitions that we can use in with
# clauses and statements.
$ fade = Fade(.5, 0, .5) # Fade to black and back.
$ dissolve = Dissolve(0.5)
$ wiperight = CropMove(1.0, "wiperight")
$ wipeleft = CropMove(1.0, "wipeleft")
$ wipeup = CropMove(1.0, "wipeup")
$ wipedown = CropMove(1.0, "wipedown")
$ slideright = CropMove(1.0, "slideright")
$ slideleft = CropMove(1.0, "slideleft")
$ slideup = CropMove(1.0, "slideup")
$ slidedown = CropMove(1.0, "slidedown")
$ slideawayright = CropMove(1.0, "slideawayright")
$ slideawayleft = CropMove(1.0, "slideawayleft")
$ slideawayup = CropMove(1.0, "slideawayup")
$ slideawaydown = CropMove(1.0, "slideawaydown")
$ irisout = CropMove(1.0, "irisout")
$ irisin = CropMove(1.0, "irisin")
# Now, we declare the images that are used in the program.
# Images.
image black = Solid((0, 0, 0, 255))
image white = Solid((255, 255, 255, 255))
image yellow = Solid((255, 255, 200, 255))
# Opening Sequence.
image bigbeach1 = Image("bigbeach1.jpg")
image presents = Image("presents.png")
# Backgrounds.
image beach1 = Image("beach1b.jpg")
image beach1 mary = Image("beach1c.jpg")
image beach1 title = Image("beach1a.jpg")
image beach2 = Image("beach2.jpg")
image beach3 = Image("beach3.jpg")
image dawn1 = Image("dawn1.jpg")
image dawn2 = Image("dawn2.jpg")
image library = Image("library.jpg")
# Ending 1.
image transfer = Image("transfer.png")
image moonpic = Image("moonpic.jpg")
image nogirlpic = Image("nogirlpic.jpg")
# Ending 3.
image littlemary = Image("littlemary.jpg")
# Ending 4.
image hospital1 = Image("hospital1.jpg")
image hospital2 = Image("hospital2.jpg")
image hospital3 = Image("hospital3.jpg")
image heaven = Image("heaven.jpg")
# Endings Common
image good_ending = Image("ending.jpg")
image bad_ending = Image("badending.jpg")
# Mary.
image mary dark confused smiling = Image("mary_dark_confused_smiling.png")
image mary dark confused wistful = Image("mary_dark_confused_wistful.png")
image mary dark crying = Image("mary_dark_crying.png")
image mary dark laughing = Image("mary_dark_laughing.png")
image mary dark sad = Image("mary_dark_sad.png")
image mary dark smiling = Image("mary_dark_smiling.png")
image mary dark vhappy = Image("mary_dark_vhappy.png")
image mary dark wistful = Image("mary_dark_wistful.png")
image mary dawn confused smiling = Image("mary_dawn_confused_smiling.png")
image mary dawn confused wistful = Image("mary_dawn_confused_wistful.png")
image mary dawn crying = Image("mary_dawn_crying.png")
image mary dawn laughing = Image("mary_dawn_laughing.png")
image mary dawn sad = Image("mary_dawn_sad.png")
image mary dawn smiling = Image("mary_dawn_smiling.png")
image mary dawn vhappy = Image("mary_dawn_vhappy.png")
image mary dawn wistful = Image("mary_dawn_wistful.png")
label start:
jump example
+21
View File
@@ -0,0 +1,21 @@
# This file contains a minimal set of style changes needed to have
# Ren'Py work with a game that's 640x480 in size.
init 1:
# Change the screen width.
$ config.screen_width = 640
$ config.screen_height = 480
# Font sizes.
$ style.default.size = 20
$ style.button_text.size = 20
$ style.file_picker_text.size = 14
# Perhaps change the fudge factor on windows, if line spacing
# looks weird.
if renpy.windows():
$ style.default.line_height_fudge = -4
$ style.file_picker_entry.xminimum = 320
$ library.thumbnail_width = 60
$ library.thumbnail_height = 45
+63
View File
@@ -0,0 +1,63 @@
# In this file, we have an example of replacing the default menu with
# a custom one. This new menu consists of a series of centered
# buttons, with dialogue appearing in a narration window.
# You can try this one out by dropping it right into the game
# directory.
# The code that implements button menus.
init:
python:
style.create('menu_button', 'button')
style.create('menu_button_text', 'button_text')
def menu(menuitems):
narration = None
ui.keymousebehavior()
ui.window(style='menu_window')
ui.vbox(xanchor='center', xpos=0.5)
for label, value in menuitems:
if value is None:
narration = label
continue
ui.textbutton(label,
style="menu_button",
text_style="menu_button_text",
clicked=ui.returns(value))
ui.close()
if narration:
narrator(narration, interact=False)
rv = ui.interact()
renpy.checkpoint()
return rv
# Styles to make button menus look good.
init 1:
python hide:
style.menu_window.background = None
style.menu_window.yminimum = 0
style.menu_window.ypos = 0.40
style.menu_window.yanchor = 'center'
style.menu_window.xfill = True
style.menu_button.background = Solid((0, 0, 255, 128))
style.menu_button.xfill = True
style.menu_button.top_padding = 5
style.menu_button.bottom_margin = 5
style.menu_button_text.xpos = 0.5
style.menu_button_text.xanchor = 'center'
style.menu_button_text.hover_color = (255, 255, 0, 255)
style.menu_button_text.idle_color = (255, 255, 255, 255)
+23
View File
@@ -0,0 +1,23 @@
# This file contains code to automatically switch the game into
# fullscreen mode.
# Please note: Fullscreen mode is broken on some platforms (mostly
# virtual windows machines like vmware and virtual PC). So having
# this code automatically execute could make your game hard to use
# on those platforms.
#
# The way we work around this is to tell users of these platforms
# to press 'f' to toggle fullscreen mode. If the game's running in
# windowed mode, the problems don't seem to appear.
#
# You should probably put words to that effect in some sort of
# README file.
# The first time this code runs, the game switches into fullscreen.
# After that, it respects the user's preference.
init:
python:
if not persistent.set_fullscreen:
persistent.set_fullscreen = True
_preferences.fullscreen = True
+193
View File
@@ -0,0 +1,193 @@
# This is an implementation of Kana mode, a mode in which multiple
# lines of dialogue are shown at once, in a fullscreen window. A
# single line of dialogue is placed onto the screen for each mouse
# click. Calling the clear function clears the screen, ensuring that
# the next line will appear at the top of the sceen.
#
# An example of using Kana mode follows the code that implements it.
# This init block contains all of the code needed to implement
# Kana mode. You probably don't want to change this... unless you
# do. But all of the customization is found below.
init -100:
python:
# This is a list of KanaCharacter, line of dialogue tuples.
kana_display_list = [ ]
# Spacings used.
kana_vspacing = 0
kana_hspacing = 0
def clear(arg=None):
global kana_display_list
kana_display_list = [ ]
def kana_show():
ui.window(style='say_window')
ui.vbox(kana_vspacing)
for char, what in kana_display_list:
char.show(what)
ui.close()
ui.saybehavior()
ui.interact()
renpy.checkpoint()
# Characters and the narrator should be instances of this
# object.
class KanaCharacter(object):
def __init__(self, who,
what_prefix='"',
what_suffix='"',
who_style='say_label',
what_style='say_dialogue',
**properties):
self.who = who
self.what_prefix = what_prefix
self.what_suffix = what_suffix
self.who_style = who_style
self.what_style = what_style
self.properties = properties
def __call__(self, what):
kana_display_list.append((self, what))
kana_show()
def show(self, what):
ui.hbox(kana_hspacing)
ui.text(self.who, style=self.who_style,
**self.properties)
ui.text(self.what_prefix + what + self.what_suffix,
style=self.what_style)
ui.close()
# This section updates the styles that are used by the Kana mode stuff
# so that they work well with Kana mode. The user may want to change
# these so that they look more appropriate.
init:
# The space between lines of dialogue.
$ kana_vspacing = 10
$ kana_hspacing = 10
$ style.say_window.background = Solid((0, 0, 0, 96))
$ style.say_window.xfill = True
$ style.say_window.yfill = True
$ style.say_window.xmargin = 0
$ style.say_window.ymargin = 0
$ style.say_window.xpadding = 20
$ style.say_window.ypadding = 20
$ style.say_label.minwidth = 100
$ style.say_label.textalign = 1.0
# This just makes it look nicer.
$ style.say_dialogue.rest_indent = 9
# Adjust the menu to match.
$ style.menu_window.background = Solid((0, 0, 0, 96))
$ style.menu_window.xfill = True
$ style.menu_window.yfill = True
$ style.menu_window.xmargin = 0
$ style.menu_window.ymargin = 0
$ style.menu_window.xpadding = 20
$ style.menu_window.ypadding = 20
$ style.menu.xpos = 110
# And now, the example. This example can't be simply run, but instead
# should serve as a guide to how to get Kana mode working in your own
# game.
# In the init block, declare the characters as KanaCharacters. We also
# want to declare the narrator as a special KanaCharacter.
init:
$ p = KanaCharacter("")
$ g = KanaCharacter("Girl:", color=(255, 128, 128, 255))
$ narrator = KanaCharacter("",
what_prefix='',
what_suffix='',
what_style='say_thought')
# Now, the actual script of the example. (An excerpt from Moonlight
# Walks.) Notice how we place calls to clear in places where the
# we want the screen to be cleared.
# This text is here to serve as an example, and shouldn't be used
# in your game.
label example:
show beach2
show mary dark wistful
$ clear()
p "What can you tell me about your parents?"
g "Papa and Mama both came across the ocean as settlers when they
were just children."
g "Papa fought in the war. When it was over, he married Mama, and
they used his pension to move here from the mainland, and to
build us a house."
g "Together, they farmed the land, and eventually they had
children."
g "I had an older sister and a younger sister. I was the middle
child."
$ clear()
show mary dark sad
"She paused for a second to collect her thoughts before
continuing. This part was taking a strain on her."
g "When I was ten, an epidemic hit the island."
g "We came down with it, and so did all of the other families."
g "My family was too sick to move, but our neighbors, the Millers,
sent some of their boys to get help from the mainland."
"I remember thinking that Miller was the last name of my aunt and
uncle, and wondering if they could be related to me."
g "I don't know what happened to them, but I never heard from them
again."
show mary dark crying
g "It lasted a week, and then it was over. My sisters... my
parents... they all..."
g "Now I'm the only one left."
$ clear()
p "I'm sorry."
"I didn't know what else to say to a girl that had lost her
family, and was obviously broken up about it."
show mary dark sad
"She nodded in response, wiped her tears, and we once again
started walking in silence."
menu menu_1:
"Show me the example again.":
jump example
"I'm done. Let me go.":
return
+87
View File
@@ -0,0 +1,87 @@
# This file replaces most of the game menu navigation with overlay
# buttons that jump directly to various parts of the game menu.
# Right clicking hides and shows the buttons, rather than calling
# up the game menu directly.
init:
python hide:
# overlay_menu is an object storing information about the
# overlay menu state.
store.overlay_menu = object()
overlay_menu.shown = False
# overlay_menu is also a new layer, containing the overlay
# menu.
config.layers.append("overlay_menu")
config.overlay_layers.append("overlay_menu")
# This function actually draws the overlay menu.
def overlay_menu_func():
if overlay_menu.shown:
ui.layer("overlay_menu")
ui.vbox(xpos=1.0, xanchor="right", ypos=0.75, yanchor="bottom")
def button(label, target):
ui.textbutton(label, clicked=renpy.curried_call_in_new_context(target))
button("Load Game", "_game_menu_load")
button("Save Game", "_game_menu_save")
button("Preferences", "_game_menu_preferences")
ui.close()
ui.close()
config.overlay_functions.append(overlay_menu_func)
# This function toggles the visibility of the overlay menu.
def overlay_menu_toggle():
shown = not overlay_menu.shown
overlay_menu.shown = shown
# How long should the transitions take?
trans_delay = 0.5
# These transitions assume that the menu is placed on the
# right side of the screen. This indicator gives the
# fraction of the screen that participates in transitions.
trans_frac = 0.75
if shown:
trans = CropMove(trans_delay,
"custom",
startcrop=(trans_frac, 0.0, 0.0, 1.0),
startpos=(1.0, 0.0),
endcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
endpos=(trans_frac, 0.0),
topnew=True)
renpy.transition(trans, 'overlay_menu')
else:
trans = CropMove(trans_delay,
"custom",
endcrop=(trans_frac, 0.0, 0.0, 1.0),
endpos=(1.0, 0.0),
startcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
startpos=(trans_frac, 0.0),
topnew=False)
renpy.transition(trans, 'overlay_menu')
renpy.restart_interaction()
# Add a new underlay that handles the overlay menu toggle.
config.underlay.append(renpy.Keymap(game_menu = overlay_menu_toggle))
+7 -7
View File
@@ -2,9 +2,9 @@
# order.
# Some version numbers and things.
version = "Ren'Py 4.4.2"
script_version = 5
savegame_suffix = "-3.save"
version = "Ren'Py 4.7"
script_version = 7
savegame_suffix = "-7.save"
# Can be first, because has no dependencies, and may be imported
@@ -19,21 +19,21 @@ import renpy.curry
import renpy.execution
import renpy.loader
import renpy.loadsave
import renpy.music
import renpy.parser
import renpy.python # object
import renpy.script
import renpy.style
import renpy.sound
import renpy.display
import renpy.display
import renpy.display.render # Most display stuff depends on this.
import renpy.display.core # object
import renpy.display.surface
import renpy.display.audio
import renpy.display.text # core
import renpy.display.layout # core
import renpy.display.behavior # layout
import renpy.display.transition # core
import renpy.display.image # core, behavior
import renpy.display.video
import renpy.ui
+4 -12
View File
@@ -1,3 +1,4 @@
# This file contains the AST for the Ren'Py script language. Each class
# here corresponds to a statement in the script language.
@@ -117,7 +118,6 @@ def say_menu_with(expression):
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(what)
class Say(Node):
def __init__(self, loc, who, what, with):
@@ -136,7 +136,7 @@ class Say(Node):
who = None
say_menu_with(self.with)
renpy.exports.say(who, self.what % renpy.game.store)
renpy.exports.say(who, self.what)
return self.next
@@ -298,6 +298,7 @@ def predict_imspec(imspec, callback):
return
im = renpy.exports.images[imspec[0]]
im.predict(callback)
@@ -403,16 +404,7 @@ class With(Node):
def execute(self):
trans = renpy.python.py_eval(self.expr)
# Code copied into exports.with
if not trans:
renpy.game.interface.with_none()
else:
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(trans)
renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=True)
renpy.exports.with(trans)
return self.next
+26 -10
View File
@@ -98,21 +98,36 @@ fade_music = 0.0
# Should the at list be sticky?
sticky_positions = False
# A list of all of the layers that we know about.
layers = [ 'master', 'transient', 'overlay' ]
# A list of layers that should be cleared when we replace
# transients.
transient_layers = [ 'transient' ]
# A list of layers that should be cleared when we recompute
# overlays.
overlay_layers = [ 'overlay' ]
# True if we want to show overlays during wait statements, or
# false otherwise.
overlay_during_wait = True
# The keymap that is used to change keypresses and mouse events.
keymap = dict(
# Bindings present almost everywhere, unless explicitly
# disabled.
rollback = [ 'K_PAGEUP', 'mouse_4' ],
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
screenshot = [ 's' ],
toggle_fullscreen = [ 'f' ],
toggle_music = [ 'm' ],
game_menu = [ 'K_ESCAPE', 'mouse_3' ],
hide_windows = [ 'mouse_2' ],
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
hide_windows = [ 'mouseup_2' ],
# Say.
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
# Keymouse.
keymouse_left = [ 'K_LEFT' ],
@@ -121,23 +136,23 @@ keymap = dict(
keymouse_down = [ 'K_DOWN' ],
# Menu.
menu_mouseselect = [ 'mouse_1' ],
menu_mouseselect = [ 'mouseup_1' ],
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
menu_keyup = [ 'K_UP' ],
menu_keydown = [ 'K_DOWN' ],
# Button.
button_select = [ 'mouse_1', 'K_RETURN', 'K_KP_ENTER' ],
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
# Input.
input_backspace = [ 'K_BACKSPACE' ],
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
# Imagemap.
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
# Bar.
bar_click = [ 'mouse_1' ],
bar_click = [ 'mouseup_1' ],
# These keys control skipping.
@@ -145,7 +160,8 @@ keymap = dict(
toggle_skip = [ 'K_TAB' ],
)
# The number of frames that Ren'Py has shown.
frames = 0
def backup():
+2 -2
View File
@@ -4,8 +4,8 @@ class Curry(object):
callable with the stored arguments and the additional arguments
supplied to the call.
"""
__doc__ = property(fget=lambda self : self.callable.__doc__)
# __doc__ = property(fget=lambda self : self.callable.__doc__)
def __init__(self, callable, *args, **kwargs):
self.callable = callable
+386
View File
@@ -0,0 +1,386 @@
# This module contains code that handles the playing of sound and
# music files.
# NOTE TO SELF:
#
# Remember to code defensively against mikey's computer that
# doesn't have the sound card in it.
import pygame
import renpy
import sys # to detect windows.
# The Windows Volume Management Strategy (tm).
# We keep a master music volume, which is the volume we use directly
# when playing music as mp3, ogg, etc. When we start up, we compute
# a midi music scaling factor. This midi music scaling factor is
# computed from the current master music volume such that when we are not
# fading, if we pygame.mixer.music.set_volume() to the mmv * mmsv, we get
# read the same value from midiOutGetVolume() as we did before we tried
# doing that.
# True if the mixer works, False if it doesn't, None if we have no
# idea yet.
mixer_works = None
# Common stuff.
mixer_enabled = True
playing_midi = True
fading = False
master_music_volume = 1.0
# Windows stuff.
midi_msf = 0.0
last_raw_volume = -1
def init():
global mixer_works
global read_raw_volume
global compute_midi_msf
global set_music_volume
global playing_midi
if mixer_works is not None:
return
try:
pygame.mixer.get_volume()
except:
if renpy.config.debug_sound:
raise
else:
mixer_works = False
mixer_works = True
windows_magic = False
if hasattr(sys, 'winver') and mixer_works:
try:
from ctypes import windll, c_uint, byref
winmm = windll.winmm
def _read_raw_volume():
res = c_uint()
for i in range(0, winmm.midiOutGetNumDevs()):
rv = winmm.midiOutGetVolume(i, byref(res))
if not rv:
return res.value
else:
print "Couldn't read raw midi volume."
return -1
read_raw_volume = _read_raw_volume
def _compute_midi_msf():
"""
Computes the Midi MSF. Returns True if successful, False if otherwise.
"""
# Don't update the MSF when fading is going on, or when not
# playing a midi. (Except before playing any music whatsoever.)
if fading or not playing_midi:
return False
global last_raw_volume
raw_vol = read_raw_volume()
if raw_vol < 0:
return False
# The case in which the volume hasn't changed recently.
if raw_vol == last_raw_volume:
return True
last_raw_volume = raw_vol
# print "raw_vol", raw_vol
# The fraction that the midi mixer is at.
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
global midi_msf
midi_msf = mixfrac / master_music_volume
# print "Midi msf is now:", midi_msf
return True
compute_midi_msf = _compute_midi_msf
# This should get called after the music starts playing.
def _set_music_volume(vol):
global master_music_volume
master_music_volume = vol
if playing_midi:
vol *= midi_msf
if vol > 1.0:
vol = 1.0
try:
pygame.mixer.music.set_volume(vol)
except:
if renpy.config.debug_sound:
raise
global last_raw_volume
last_raw_volume = read_raw_volume()
set_music_volume = _set_music_volume
# Figure out the default msf, and set it up.
windows_magic = compute_midi_msf()
playing_midi = False
except Exception, e:
print "Exception when trying to init music:", str(e)
print "Falling back to Unix mode."
if not windows_magic:
def _compute_midi_msf():
return
compute_midi_msf = _compute_midi_msf
def _set_music_volume(vol):
if not mixer_works:
return
global master_music_volume
master_music_volume = vol
try:
pygame.mixer.music.set_volume(vol)
except:
if renpy.config.debug_sound:
raise
set_music_volume = _set_music_volume
playing_midi = False
# This detects if the filename is a midi, and sets playing_midi
# appropriately.
def detect_midi(fn):
fn = fn.lower()
global playing_midi
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
# Information about the currently playing track.
current_music = None
def music_delay(offset):
"""
Returns the time left until the current music has been playing for
offset seconds. If music is not playing, return None. May return
a negative time.
"""
if not mixer_works:
return None
mo = pygame.mixer.music.get_pos()
if mo < 0:
return None
mo /= 1000.0
return offset - mo
def music_start(filename, loops=-1, startpos=0.0):
"""
This starts music playing. If a music track is already playing,
stops that track in favor of this one.
@param filename: The file that the music will be played from. This
is relative to the game directory, and must be a real file (so it
cannot be stored in an archive.)
@param loops: The number of times the music will loop after it
finishes playing. If negative, the music will loop indefinitely.
Please note that even once the song has finished, rollback or load
may cause it to start playing again. So it may not be safe to have
this set to a non-negative value.
@param startpos: The number of seconds into the music to start playing.
"""
if not mixer_works:
return
music_stop()
renpy.game.context().scene_lists.music = (filename, loops, startpos)
restore_music()
def music_stop():
"""
Stops the currently playing music track.
"""
if not mixer_works:
return
renpy.game.context().scene_lists.music = None
restore_music()
def restore_music():
"""
This makes sure that the current music matches the music found in
the context.
"""
if not mixer_works:
return
global current_music
global fading
compute_midi_msf()
set_music_volume(1.0)
new_music = renpy.game.context().scene_lists.music
if not renpy.game.preferences.music:
new_music = None
if current_music == new_music:
return
if not mixer_enabled:
return
# Usually, ignore errors.
try:
if current_music != new_music and current_music:
current_music = None
pygame.mixer.music.fadeout(int(renpy.config.fade_music * 1000))
fading = True
else:
if not pygame.mixer.music.get_busy():
fn, loops, startpos = new_music
fading = False
detect_midi(fn)
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
pygame.mixer.music.play(loops, startpos)
set_music_volume(master_music_volume)
current_music = new_music
except pygame.error, e:
if renpy.config.debug_sound:
raise
else:
print "Error while trying to play music:", str(e)
# Plays sounds.
def play(fn, loops=0):
"""
This plays the given sound. The sound must be in a wav file,
and expected to have a sample rate 44100hz (changable with
config.sound_sample_rate), 16 bit, stereo. These expectations may
be violated, but that may lead to conversion delays.
Once a sound has been started, there's no way to stop it.
@param fn: The name of the file that the sound is read from. This
file may be contained in a game directory or an archive.
@param loops: The number of extra times the sound will be
played. (The default, 0, will play the sound once.)
"""
if not mixer_works:
return
if not fn:
return
if not renpy.game.preferences.sound:
return
if not mixer_enabled:
return
try:
sound = pygame.mixer.Sound(renpy.loader.load(fn))
sound.play()
except:
if renpy.config.debug_sound:
raise
def pre_init():
try:
bufsize = 4096
import os
if 'RENPY_SOUND_BUFSIZE' in os.environ:
bufsize = int(os.environ('RENPY_SOUND_BUFSIZE'))
pygame.mixer.pre_init(renpy.config.sound_sample_rate, -16, 2, bufsize)
except:
try:
pygame.mixer.pre_init()
except:
if renpy.config.debug_sound:
raise
def disable_mixer():
"""
This function is called by the video code to disable the
pygame mixer.
"""
if not mixer_works:
return
global mixer_enabled
if mixer_enabled:
try:
pygame.mixer.quit()
except:
if renpy.config.debug_sound:
raise
mixer_enabled = False
def enable_mixer():
"""
This function is called by the video code to enable the
pygame mixer.
"""
if not mixer_works:
return
global mixer_enabled
if not mixer_enabled:
try:
pygame.mixer.init()
except:
if renpy.config.debug_sound:
raise
mixer_enabled = True
+111 -62
View File
@@ -2,6 +2,7 @@
import renpy
from renpy.display.render import render
# import renpy.display.core as core
# import renpy.display.layout as layout
@@ -23,7 +24,13 @@ def map_event(ev, name):
keys = renpy.config.keymap[name]
if ev.type == MOUSEBUTTONDOWN:
if ( "mouse_" + str(ev.button) ) in keys:
if ( "mousedown_" + str(ev.button) ) in keys:
return True
else:
return False
if ev.type == MOUSEBUTTONUP:
if ( "mouseup_" + str(ev.button) ) in keys:
return True
else:
return False
@@ -79,7 +86,7 @@ def skipping(ev):
return
class Keymap(renpy.display.layout.Container):
class Keymap(renpy.display.layout.Null):
"""
This is a behavior that maps keys to functions that are called when
the key is pressed. The keys are specified by giving the appropriate
@@ -87,6 +94,7 @@ class Keymap(renpy.display.layout.Container):
"""
def __init__(self, **keymap):
super(Keymap, self).__init__(style='default')
self.keymap = keymap
def event(self, ev, x, y):
@@ -96,8 +104,8 @@ class Keymap(renpy.display.layout.Container):
action()
raise renpy.display.core.IgnoreEvent()
def render(self, width, height, st):
return None
# def render(self, width, height, st):
# return None
class KeymouseBehavior(renpy.display.layout.Null):
"""
@@ -128,6 +136,26 @@ class KeymouseBehavior(renpy.display.layout.Null):
return None
class PauseBehavior(renpy.display.layout.Null):
"""
This is a class implementing the Pause behavior, which is to
return a value after a certain amount of time has elapsed.
"""
def __init__(self, delay, result=False):
super(PauseBehavior, self).__init__()
self.delay = delay
self.result = result
def event(self, ev, x, y):
if ev.type == renpy.display.core.DISPLAYTIME and \
self.delay and ev.duration > self.delay:
return self.result
class SayBehavior(renpy.display.layout.Null):
"""
This is a class that implements the say behavior,
@@ -136,18 +164,12 @@ class SayBehavior(renpy.display.layout.Null):
mouse button.
"""
def __init__(self, delay=None):
def __init__(self):
super(SayBehavior, self).__init__()
self.delay = delay
def event(self, ev, x, y):
if ev.type == renpy.display.core.DISPLAYTIME and \
self.delay and ev.duration > self.delay:
return False
if ev.type == renpy.display.core.DISPLAYTIME and \
renpy.config.allow_skipping and renpy.config.skipping and \
ev.duration > renpy.config.skip_delay / 1000.0:
@@ -169,7 +191,7 @@ class SayBehavior(renpy.display.layout.Null):
class Menu(renpy.display.layout.VBox):
def __init__(self, menuitems, **properties):
def __init__(self, menuitems, style='menu', **properties):
"""
@param menuitems: A list of menuitem tuples. The first element
of each tuple is the string that should be displayed to the
@@ -178,21 +200,27 @@ class Menu(renpy.display.layout.VBox):
caption.
"""
super(Menu, self).__init__(**properties)
super(Menu, self).__init__(style=style, **properties)
self.selected = None
self.results = [ ]
self.caption_style = renpy.style.Style('menu_caption', { })
self.selected_style = renpy.style.Style('menu_choice', { })
self.unselected_style = renpy.style.Style('menu_choice', { })
# self.caption_style = renpy.style.Style('menu_caption', { })
# self.selected_style = renpy.style.Style('menu_choice', { })
# self.unselected_style = renpy.style.Style('menu_choice', { })
self.selected_style.set_prefix('hover_')
self.unselected_style.set_prefix('idle_')
# self.selected_style.set_prefix('hover_')
# self.unselected_style.set_prefix('idle_')
for i, (caption, result) in enumerate(menuitems):
self.add(renpy.display.text.Text(caption))
if result is not None:
style = 'menu_choice'
else:
style = 'menu_caption'
self.add(renpy.display.text.Text(caption, style=style))
if self.selected is None and result is not None:
self.selected = i
@@ -210,14 +238,13 @@ class Menu(renpy.display.layout.VBox):
# Captions should stay the default text color.
if result is None:
child.set_style(self.caption_style)
continue
# Actual choices change color if they are selected or not.
if i == self.selected:
child.set_style(self.selected_style)
child.set_style_prefix('hover_')
else:
child.set_style(self.unselected_style)
child.set_style_prefix('idle_')
def event(self, ev, x, y):
@@ -229,25 +256,17 @@ class Menu(renpy.display.layout.VBox):
# print x, y
old_selected = self.selected
mouse_select = False
# Change selection based on mouse position.
if ev.type == MOUSEMOTION:
if ev.type == MOUSEMOTION or map_event(ev, "menu_mouseselect"):
target = self.child_at_point(x, y)
if target is None:
return None
if self.results[target] is not None:
self.selected = target
# Make selection based on mouse click position.
if map_event(ev, "menu_mouseselect"):
target = self.child_at_point(x, y)
if target is None:
return None
if self.results[target] is not None:
renpy.sound.play(self.selected_style.activate_sound)
return self.results[target]
mouse_select = True
# Change selection based on keypress.
if map_event(ev, "menu_keydown"):
@@ -271,20 +290,23 @@ class Menu(renpy.display.layout.VBox):
self.selected = selected
break
# Make selection based on keypress.
if map_event(ev, "menu_keyselect"):
renpy.sound.play(self.selected_style.activate_sound)
return self.results[self.selected]
# If the selected item changed, update the display.
if self.selected != old_selected:
self.children[self.selected].set_style(self.selected_style)
self.children[old_selected].set_style(self.unselected_style)
self.children[self.selected].set_style_prefix("hover_")
self.children[old_selected].set_style_prefix("idle_")
renpy.sound.play(self.selected_style.hover_sound)
renpy.display.audio.play(self.style.hover_sound)
# renpy.display.render.redraw(self, 0)
renpy.game.interface.redraw(0)
# Make selection based on keypress or mouse click.
if map_event(ev, "menu_keyselect") or \
(mouse_select and map_event(ev, "menu_mouseselect")):
self.children[self.selected].set_style_prefix("activate_")
renpy.display.audio.play(self.style.activate_sound)
return self.results[self.selected]
return None
@@ -297,13 +319,17 @@ class Button(renpy.display.layout.Window):
super(Button, self).__init__(child, style=style, **properties)
self.style.set_prefix('idle_')
self.activated = False
self.old_hover = False
self.clicked = clicked
self.hovered = hovered
def render(self, width, height, st):
if self.old_hover:
if self.activated:
self.set_style_prefix('activate_')
elif self.old_hover:
self.set_style_prefix('hover_')
else:
self.set_style_prefix('idle_')
@@ -322,10 +348,13 @@ class Button(renpy.display.layout.Window):
# else:
# self.style.set_prefix('idle_')
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
def event(self, ev, x, y):
# We deactivate on an event.
self.activated = False
inside = False
width, height = self.window_size
@@ -343,12 +372,15 @@ class Button(renpy.display.layout.Window):
if self.hovered:
self.hovered()
renpy.sound.play(self.style.hover_sound)
renpy.display.audio.play(self.style.hover_sound)
if map_event(ev, "button_select"):
if inside and self.clicked:
renpy.sound.play(self.style.activate_sound)
renpy.display.audio.play(self.style.activate_sound)
self.activated = True
renpy.display.render.redraw(self, 0)
rv = self.clicked()
@@ -356,6 +388,7 @@ class Button(renpy.display.layout.Window):
return rv
else:
raise renpy.display.core.IgnoreEvent()
return super(Button, self).event(ev, x, y)
@@ -363,10 +396,10 @@ class Button(renpy.display.layout.Window):
# Reimplementation of the TextButton widget as a Button and a Text
# widget.
def TextButton(text, style='button', text_style='button_text',
clicked=None):
clicked=None, **properties):
text = renpy.display.text.Text(text, style=text_style)
return Button(text, style=style, clicked=clicked)
return Button(text, style=style, clicked=clicked, **properties)
@@ -398,19 +431,27 @@ class Input(renpy.display.text.Text):
"""
def __init__(self, default, length=None,
style='input_text', **properties):
super(Input, self).__init__(default + "_", style=style, **properties)
style='input_text',
allow=None,
exclude=None,
**properties):
super(Input, self).__init__(default.replace("{", "{{") + "_", style=style, **properties)
self.content = unicode(default)
self.length = length
self.allow = allow
self.exclude = exclude
def event(self, ev, x, y):
if map_event(ev, "input_backspace"):
if self.content:
self.content = self.content[:-1]
self.set_text(self.content + "_")
renpy.game.interface.redraw(0)
self.set_text(self.content.replace("{", "{{") + "_")
renpy.display.render.redraw(self, 0)
elif map_event(ev, "input_enter"):
@@ -421,12 +462,20 @@ class Input(renpy.display.text.Text):
return None
if self.length and len(self.content) >= self.length:
return None
raise renpy.display.core.IgnoreEvent()
if self.allow and ev.unicode not in self.allow:
raise renpy.display.core.IgnoreEvent()
if self.exclude and ev.unicode in self.exclude:
raise renpy.display.core.IgnoreEvent()
self.content += ev.unicode
self.set_text(self.content + "_")
renpy.game.interface.redraw(0)
self.set_text(self.content.replace("{", "{{") + "_")
renpy.display.render.redraw(self, 0)
raise renpy.display.core.IgnoreEvent()
class Bar(renpy.display.core.Displayable):
@@ -461,7 +510,7 @@ class Bar(renpy.display.core.Displayable):
if not (0 <= x < self.width and 0 <= y <= self.height):
return
print x, y
# print x, y
lgutter = self.style.left_gutter
rgutter = self.style.right_gutter
@@ -508,10 +557,10 @@ class Bar(renpy.display.core.Displayable):
left_width = barwidth * self.value // self.range
right_width = barwidth - left_width
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
lsurf = self.style.left_bar.render(left_width, height, st)
rsurf = self.style.right_bar.render(right_width, height, st)
lsurf = render(self.style.left_bar, left_width, height, st)
rsurf = render(self.style.right_bar, right_width, height, st)
rv.blit(lsurf, (lgutter, 0))
rv.blit(rsurf, (lgutter + left_width, 0))
@@ -538,16 +587,16 @@ class Conditional(renpy.display.layout.Container):
def render(self, width, height, st):
if self.state:
return self.child.render(width, height, st)
return render(self.child, width, height, st)
else:
return self.null.render(width, height, st)
return render(self.null, width, height, st)
def event(self, ev, x, y):
state = eval(self.condition, renpy.game.store)
if state != self.state:
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
self.state = state
+317 -213
View File
@@ -2,6 +2,7 @@
# window.
import renpy
from renpy.display.render import render
import pygame
from pygame.constants import *
@@ -34,6 +35,7 @@ class Displayable(renpy.object.Object):
def __init__(self):
self.style = None
self.style_prefix = None
def set_style_prefix(self, prefix):
"""
@@ -41,9 +43,15 @@ class Displayable(renpy.object.Object):
widgets, if any.
"""
if prefix == self.style_prefix:
return
if self.style:
self.style.set_prefix(prefix)
self.style_prefix = prefix
renpy.display.render.redraw(self, 0)
def parameterize(self, name, parameters):
"""
Called to parameterize this. By default, we don't take any
@@ -62,10 +70,7 @@ class Displayable(renpy.object.Object):
and height parameters, which give the largest width and height
that this drawable can be drawn to without overflowing some
bounding box. It's also given two times. It returns a Surface
that is the current image of this drawable. If the image will
be need to be redrawn at some time in the future, call
game.interface.redraw with the time with the delay until the
next redraw.
that is the current image of this drawable.
@param shown_time: The time since we first started showing this
drawable, a float in seconds.
@@ -171,8 +176,7 @@ class Displayable(renpy.object.Object):
dest.blit(surf, (xoff, yoff))
return xoff, yoff
class SceneLists(object):
"""
@@ -194,19 +198,26 @@ class SceneLists(object):
"""
def __init__(self, oldsl=None):
self.layers = { }
if oldsl:
self.master = oldsl.master[:]
for i in renpy.config.layers:
self.layers[i] = oldsl.layers[i][:]
self.replace_transient()
self.overlay = oldsl.overlay[:]
self.music = oldsl.music
self.sticky_positions = oldsl.sticky_positions.copy()
self.movie = oldsl.movie
else:
self.master = [ ]
self.transient = [ ]
self.overlay = [ ]
for i in renpy.config.layers:
self.layers[i] = [ ]
self.music = None
self.movie = None
self.sticky_positions = { }
def rollback_copy(self):
@@ -215,11 +226,16 @@ class SceneLists(object):
which is a 2-level copy.
"""
rv = SceneLists()
rv.master = self.master[:]
rv.transient = self.transient[:]
rv = SceneLists(self)
rv.music = self.music
for i in renpy.config.overlay_layers:
rv.layers[i] = [ ]
# rv.master = self.master[:]
# rv.transient = self.transient[:]
# rv.music = self.music
# rv.movie = self.movie
return rv
@@ -232,27 +248,28 @@ class SceneLists(object):
elements.
"""
self.transient = [ ]
for i in renpy.config.transient_layers:
self.layers[i] = [ ]
def add(self, listname, thing, key=None):
def add(self, layer, thing, key=None):
"""
This is called to add something to a display list. Listname is
the name of the displaylist that we need to add the thing to,
This is called to add something to a layer. Layer is
the name of the layer that we need to add the thing to,
one of 'master' or 'transient'. Key is an optional key.
If key is provided, and there exists something in the selected
displaylist with the given key, that entry from the displaylist
layer with the given key, that entry from the layer
is replaced with the supplied displayable, preserving the
time it was first displayed.
Otherwise, the displayable is added to the end of the list, and
Otherwise, the displayable is added to the end of the layer, and
the time it was first displayed is set to the current time.
"""
l = getattr(self, listname, None)
if layer not in self.layers:
raise Exception("Trying to add something to non-existent layer '%s'." % layer)
if l is None:
raise Exception("Trying to add something to non-existent display list '%s'." % listname)
l = self.layers[layer]
if key is not None:
for index, (k, t, d) in enumerate(l):
@@ -267,71 +284,33 @@ class SceneLists(object):
l.append((key, time.time(), thing))
def remove(self, listname, thing):
def remove(self, layer, thing):
"""
This is either a key or a displayable. This iterates through the
named display list, searching for entries matching the thing.
named layer, searching for entries matching the thing.
When they are found, they are removed from the displaylist.
It's not an error to remove something that isn't in the list in
It's not an error to remove something that isn't in the layer in
the first place.
"""
l = getattr(self, listname)
if layer not in self.layers:
raise Exception("Trying to remove something from non-existent layer '%s'." % layer)
l = self.layers[layer]
l = [ (k, t, d) for k, t, d in l if k != thing if d is not thing ]
setattr(self, listname, l)
self.layers[layer] = l
def clear(self, listname):
def clear(self, layer):
"""
Resets the given listname to either the empty list, or to the
contents of the empty scenelist if reset_to_empty is True.
@param: The name of the list to clear. One of the strings
'master' or 'transient'.
Clears the named layer, making it empty.
"""
l = getattr(self, listname, None)
if l is None:
raise Exception("Trying to clear non-existent display list '%s'." % listname)
if layer not in self.layers is None:
raise Exception("Trying to clear non-existent layer '%s'." % layer)
nl = [ ]
setattr(self, listname, nl)
def render_scene_list(sl, width, height):
"""
This renders the scene list sl, and returns a rendered
renpy Surface containing the rendered scene list.
@returns: The surface, and the offset of each member of the
list relative to the surface.
"""
t = time.time()
surftree = renpy.display.surface.Surface(width, height)
offsets = [ ]
if renpy.config.background:
surftree.fill(renpy.config.background)
for key, base_start_time, d in sl:
surf = d.render(width, height, t - base_start_time)
if not surf:
offsets.append((0, 0))
continue
# Place the surface.
offset = d.place(surftree, 0, 0, width, height, surf)
offsets.append(offset)
return surftree, offsets
self.layers[layer] = [ ]
class Display(object):
@@ -353,13 +332,20 @@ class Display(object):
@ivar mouse_location: The mouse location the last time it was
drawn, or None if it wasn't drawn the last time around.
@ivar full_redraw: Force a full redraw.
"""
def __init__(self):
renpy.sound.init()
# Ensure that we kill off the movie when changing screen res.
renpy.display.video.movie_stop(clear=False)
renpy.display.audio.pre_init()
pygame.init()
renpy.display.audio.init()
self.fullscreen = renpy.game.preferences.fullscreen
fsflag = 0
@@ -398,8 +384,11 @@ class Display(object):
pygame.mouse.set_visible(True)
self.mouse_location = None
self.suppress_mouse = False
def draw_mouse(self):
self.full_redraw = True
def draw_mouse(self, show_mouse=True):
"""
This draws the mouse to the screen, if necessary. It uses the
buffer to minimize the amount of the screen that needs to be
@@ -409,34 +398,35 @@ class Display(object):
if not self.mouse:
return
if self.suppress_mouse:
return
mw, mh = self.mouse.get_size()
pos = pygame.mouse.get_pos()
if not pygame.mouse.get_focused():
pos = None
flip = False
if not show_mouse:
pos = None
updates = [ ]
if self.mouse_location and self.mouse_location != pos:
ox, oy = self.mouse_location
self.window.blit(self.buffer, (ox, oy), (ox, oy, mw, mh))
flip = True
updates.append((ox, oy, mw, mh))
if pos and (pos != self.mouse_location):
self.window.blit(self.mouse, pos)
flip = True
updates.append(pos + (mw, mh))
self.mouse_location = pos
if flip:
pygame.display.flip()
pygame.display.update(updates)
def show(self, transient):
def show(self, root_widget, suppress_blit):
"""
Draws the current transient screen list to the screen.
@@ -444,23 +434,34 @@ class Display(object):
relative to the screen.
"""
surftree, rv = render_scene_list(transient,
renpy.config.screen_width,
renpy.config.screen_height)
surftree = renpy.display.render.render_screen(
root_widget,
renpy.config.screen_width,
renpy.config.screen_height,
0)
if not suppress_blit:
if self.mouse:
self.draw_mouse(False)
damage = renpy.display.render.screen_blit(surftree, self.full_redraw)
self.full_redraw = False
if self.mouse:
if damage:
self.buffer.blit(self.window, damage, damage)
self.draw_mouse(True)
if damage:
pygame.display.update(damage)
else:
self.full_redraw = True
self.suppress_mouse = suppress_blit
# self.window.set_clip((0, 0, 800, 300))
surftree.blit_to(self.window, 0, 0)
if self.mouse:
self.buffer.blit(self.window, (0, 0))
# We don't need to undraw the mouse.
self.mouse_location = None
pygame.display.flip()
return rv
def save_screenshot(self, filename):
"""
Saves a full-size screenshot in the given filename.
@@ -491,38 +492,44 @@ class Interface(object):
@ivar display: The display that we used to display the screen.
@ivar needs_redraw: True if we need a redraw now.
@ivar profile_time: The time of the last profiling.
@ivar screenshot: A screenshot, or None if no screenshot has been
taken.
@ivar old_scene: The last thing that was displayed to the screen, not
counting overlays and things like that.
counting underlays.
@ivar transition: If not None, the transition to be applied for the
next interaction.
@ivar transition: A map from layer name to the transition that will
be applied the next time interact restarts.
@ivar supress_transition: If True, then the next transition will not
@ivar suppress_transition: If True, then the next transition will not
happen.
@ivar quick_quit: If true, a click on the delete button will
cause an immediate quit.
@ivar force_redraw: If True, a redraw is forced.
@ivar restart_interaction: If True, the current interaction will
be restarted.
@ivar pushed_event: If not None, an event that was pushed back
onto the stack.
"""
def __init__(self):
self.display = Display()
self.needs_redraw = False
self.profile_time = time.time()
self.screenshot = None
self.redraw_time = 0.0
self.old_scene = [ ]
self.transition = None
self.supress_transition = False
self.old_scene = None
self.transition = { }
self.suppress_transition = False
self.quick_quit = False
self.force_redraw = False
self.restart_interaction = False
self.pushed_event = None
def take_screenshot(self, scale):
"""
@@ -549,37 +556,48 @@ class Interface(object):
self.screenshot = None
def redraw(self, delay=0.0):
"""
Called to indicate that the screen has changed in some way, and
needs to be redrawn.
@param delay: The time from now at which the redraw needs to
be performed, in fractional seconds.
"""
if delay == 0.0:
self.needs_redraw = True
else:
self.redraw_time = min(self.redraw_time, time.time() + delay)
def with_none(self):
"""
Implements the with None command, which sets the scene we will
be transitioning from.
"""
scene_lists = renpy.game.context().scene_lists
self.old_scene = scene_lists.master[:]
def set_transition(self, transition):
# We don't want the overlay to be clear here.
if not renpy.config.overlay_during_wait:
for i in renpy.config.overlay_layers:
scene_lists.clear(i)
self.old_scene = self.compute_scene(scene_lists)
# self.old_scene.append_scene_list(scene_lists.master)
def set_transition(self, transition, layer=None):
"""
Sets the transition that will be performed as part of the next
interaction.
"""
self.transition = transition
self.transition[layer] = transition
def event_peek(self):
"""
This peeks the next event. It returns None if no event exists.
"""
if self.pushed_event:
return self.pushed_event
ev = pygame.event.poll()
if ev.type == NOEVENT:
return None
self.pushed_event = ev
return ev
def event_wait(self):
"""
@@ -587,22 +605,78 @@ class Interface(object):
profiler how much time is spent in interact.
"""
if self.pushed_event:
rv = self.pushed_event
self.pushed_event = None
return rv
# We load at most one image per wait.
if renpy.display.image.cache.needs_preload():
ev = pygame.event.poll()
if ev.type != NOEVENT:
return ev
renpy.display.image.cache.preload()
return pygame.event.wait()
def interact(self, transient=None, show_mouse=True,
def compute_scene(self, scene_lists):
"""
This converts scene lists into a dictionary mapping layer
name to a Fixed containing that layer. None is mapped
to a fixed containing all of the layers.
"""
rv = { }
root = renpy.display.layout.Fixed()
for layer in renpy.config.layers:
f = renpy.display.layout.Fixed()
f.append_scene_list(scene_lists.layers[layer])
rv[layer] = f
root.add(f)
rv[None] = root
return rv
def interact(self, **kwargs):
"""
This handles an interaction, restarting it if necessary. All of the
keyword arguments are passed off to interact_core.
"""
# These things can be done once per interaction.
repeat = True
while repeat:
repeat, rv = self.interact_core(**kwargs)
# Clean out transient stuff at the end of an interaction.
scene_lists = renpy.game.context().scene_lists
scene_lists.replace_transient()
return rv
def interact_core(self,
show_mouse=True,
trans_pause=False,
suppress_overlay=False,
suppress_underlay=False,
):
"""
This handles one cycle of displaying an image to the user,
and then responding to user input.
@param transient: If given, a replacement list of transient
things to show.
@param show_mouse: Should the mouse be shown during this
interaction? Only advisory.
interaction? Only advisory, and usually doesn't work.
@param trans_pause: If given, we must have a transition. Should we
add a pause behavior during the transition?
@@ -611,22 +685,23 @@ class Interface(object):
@param suppress_underlay: This suppresses the display of the underlay.
"""
self.suppress_transition |= renpy.config.skipping
## Safety condition, prevents deadlocks.
if trans_pause:
if not self.transition:
return None
if self.supress_transition:
return None
return False, None
if self.suppress_transition:
return False, None
# We just restarted.
self.restart_interaction = False
## Expensive things we want to do before we pick the start_time.
# frames = 0
# Tick time forward.
renpy.display.image.cache.tick()
# Predict images.
renpy.game.context().predict(renpy.display.image.cache.preload_image)
# Set up key repeats.
# pygame.time.set_timer(KEYREPEATEVENT, renpy.config.skip_delay)
@@ -642,69 +717,85 @@ class Interface(object):
start_time = time.time()
scene_lists = renpy.game.context().scene_lists
# Compute the overlay, by calling the overlay functions.
overlay = [ ]
# Figure out what the overlay layer should look like.
for i in renpy.config.overlay_layers:
scene_lists.clear(i)
renpy.ui.layer("overlay")
if not suppress_overlay:
for i in renpy.config.overlay_functions:
i()
overlaid = i()
if overlaid:
for j in overlaid:
overlay.append((None, 0, j))
renpy.ui.close()
# Figure out the underlay layer.
if not suppress_underlay:
underlay = [ ( None, 0, i) for i in renpy.config.underlay ]
else:
underlay = [ ]
# Set up the transient scene list.
if transient:
transient = scene_lists.transient + [ (None, start_time, i) for i in transient ]
else:
transient = scene_lists.transient
# The root widget of everything that is displayed on the screen.
root_widget = renpy.display.layout.Fixed()
root_widget.append_scene_list(underlay)
# Figure out the display list.
current_scene = scene_lists.master + transient + overlay
# Figure out the scene. (All of the layers, and the root.)
scene = self.compute_scene(scene_lists)
if self.transition and not self.supress_transition:
trans = self.transition(self.old_scene, current_scene)
# The root widget of all of the layers.
layers_root = renpy.display.layout.Fixed()
transition_scene = [ (None, start_time, trans) ]
# Add layers (perhaps with transitions) to the layers root.
for layer in renpy.config.layers:
if layer in self.transition and self.old_scene and not self.suppress_transition:
trans = self.transition[layer](old_widget=self.old_scene[layer],
new_widget=scene[layer])
layers_root.add(trans, start_time)
else:
layers_root.add(scene[layer], start_time)
# Add layers_root to root_widget, perhaps through a transition.
if None in self.transition and self.old_scene and not self.suppress_transition:
trans = self.transition[None](old_widget=self.old_scene[None],
new_widget=layers_root)
root_widget.add(trans, start_time)
if trans_pause:
sb = renpy.display.behavior.SayBehavior(delay=trans.delay)
transition_scene.append((None, start_time, sb))
sb = renpy.display.behavior.SayBehavior()
root_widget.add(sb, start_time)
pb = renpy.display.behavior.PauseBehavior(trans.delay)
root_widget.add(pb, start_time)
else:
transition_scene = current_scene
root_widget.add(layers_root, start_time)
self.transition = None
self.supress_transition = False
# The list of things to be displayed.
display_list = underlay + transition_scene
# Now, update various things regarding scenes and transitions,
# so we are ready for a new interaction or a restart.
self.old_scene = scene
self.transition = { }
self.suppress_transition = False
# This list of things recieving events.
event_list = display_list[:]
# Okay, from here on we now have a single root widget (root_widget),
# which we will try to show to the user.
# Compute the reversed list, which is in the right order
# for handling events on.
event_list.reverse()
# Redraw the screen during every interaction.
self.needs_redraw = True
# This list of offsets will be filled in before we need
# it. It's kept in reverse order of the transient list
# (The same order as rev_transient.)
offsets = [ ]
# Redraw the screen.
renpy.display.render.process_redraws()
needs_redraw = True
# Post an event that moves us to the current mouse position.
pygame.event.post(pygame.event.Event(MOUSEMOTION,
pos=pygame.mouse.get_pos()))
# We only want to do prediction once, but we will defer it as
# long as possible.
did_prediction = False
rv = None
# This try block is used to force cleanup even on termination
@@ -713,42 +804,55 @@ class Interface(object):
while rv is None:
# Check for a change in fullscreen preference.
if self.display.fullscreen != renpy.game.preferences.fullscreen:
self.display = Display()
self.needs_redraw = True
needs_redraw = True
if self.needs_redraw:
self.needs_redraw = False
# Check for a forced redraw.
if self.force_redraw:
needs_redraw = True
self.force_redraw = False
# Redraw the screen.
if needs_redraw:
needs_redraw = False
# If we have a movie, start showing it.
suppress_blit = renpy.display.video.interact()
# Draw the screen.
draw_start = time.time()
self.redraw_time = draw_start + 365.25 * 86400.0
offsets = self.display.show(display_list)
offsets.reverse()
self.display.show(root_widget, suppress_blit)
renpy.config.frames += 1
# If profiling is enabled, report the profile time.
if renpy.config.profile:
new_time = time.time()
print "Profile: Redraw took %f seconds." % (new_time - draw_start)
print "Profile: %f seconds between event and display." % (new_time - self.profile_time)
print "Profile: %f seconds to complete event." % (new_time - self.profile_time)
# Draw the mouse, if it needs drawing.
if show_mouse:
self.display.draw_mouse()
# Determine if we need a redraw.
needs_redraw = renpy.display.render.process_redraws()
# If we need to redraw again, do it if we don't have an
# event going on.
if self.needs_redraw and not pygame.event.peek():
if needs_redraw and not self.event_peek():
continue
# While we have nothing to do, preload images.
while renpy.display.image.cache.needs_preload() and \
not pygame.event.peek():
renpy.display.image.cache.preload()
# Predict images, if we haven't done so already.
if not did_prediction and not self.event_peek():
renpy.game.context().predict(renpy.display.image.cache.preload_image)
root_widget.predict(renpy.display.image.cache.preload_image)
did_prediction = True
try:
# ev = pygame.event.wait()
ev = self.event_wait()
self.profile_time = time.time()
@@ -762,7 +866,10 @@ class Interface(object):
duration=(time.time() - start_time))
# Update the playing music, if necessary.
renpy.music.restore()
# This needs to be here so that we eventually start a
# new song at the end of a fadeout.
renpy.display.audio.restore_music()
# Handle skipping.
renpy.display.behavior.skipping(ev)
@@ -783,39 +890,36 @@ class Interface(object):
if len(evs):
ev = evs[-1]
# x, y = getattr(ev, 'pos', (0, 0))
x, y = pygame.mouse.get_pos()
for (k, t, d), (xo, yo) in zip(event_list, offsets):
rv = d.event(ev, x - xo, y - yo)
rv = root_widget.event(ev, x, y)
if rv is not None:
break
if rv is not None:
break
except IgnoreEvent:
pass
if time.time() > self.redraw_time:
self.needs_redraw = True
# Check again after handling the event.
needs_redraw |= renpy.display.render.process_redraws()
if self.restart_interaction:
return True, None
# But wait, there's more! The finally block runs some cleanup
# after this.
return rv
return False, rv
finally:
# pygame.time.set_timer(KEYREPEATEVENT, 0)
pygame.time.set_timer(DISPLAYTIME, 0)
scene_lists.replace_transient()
# Clear up the transitions.
self.old_scene = current_scene
self.transition = None
self.supress_transition = False
# Restart the old interaction, which also causes a
# redraw if needed.
self.restart_interaction = True
# Redraw the old scene, if any.
self.redraw(0)
# print "It took", frames, "frames."
+65 -22
View File
@@ -1,4 +1,6 @@
import renpy
from renpy.display.render import render
import pygame
from pygame.constants import *
@@ -47,11 +49,19 @@ class ImageCache(object):
layer = self.load_image(i)
rv.blit(layer, (0, 0))
renpy.display.render.mutated_surface(rv)
return rv
im = pygame.image.load(renpy.loader.load(fn), fn)
im = im.convert_alpha()
if im.get_flags() & SRCALPHA:
im = im.convert_alpha()
else:
im = im.convert()
renpy.display.render.mutated_surface(im)
return im
@@ -67,7 +77,7 @@ class ImageCache(object):
# iw, ih = im.get_size()
# surf = renpy.display.surface.Surface(iw, ih)
# surf = renpy.display.render.Render(iw, ih)
# surf.blit(im, (0, 0))
im = self.really_load_image(fn)
@@ -82,6 +92,7 @@ class ImageCache(object):
# Queues an image to be preloaded if not already loaded and there's
# room in the cache for it.
def preload_image(self, fn):
self.time_map[fn] = self.time
if fn in self.surface_map:
@@ -90,7 +101,6 @@ class ImageCache(object):
if fn not in self.preloads:
self.preloads.append(fn)
# This tries to ensure that there are n empty spaces in the image
# cache. Returns the number of empty spaces that are actually in
# the image cache. (A number that may be negative.)
@@ -176,6 +186,8 @@ class Image(renpy.display.core.Displayable):
other images will be aligned with the upper-left corner of the
image.
"""
super(Image, self).__init__()
self.filename = filename
self.style = renpy.style.Style(style, properties)
@@ -183,7 +195,7 @@ class Image(renpy.display.core.Displayable):
def render(self, w, h, st):
im = cache.load_image(self.filename)
w, h = im.get_size()
rv = renpy.display.surface.Surface(w, h)
rv = renpy.display.render.Render(w, h)
rv.blit(im, (0, 0))
return rv
@@ -200,11 +212,17 @@ class UncachedImage(renpy.display.core.Displayable):
def __init__(self, file, hint=None, scale=None, style='image_placement',
**properties):
super(UncachedImage, self).__init__()
self.surf = pygame.image.load(file, hint)
self.surf = self.surf.convert_alpha()
if scale:
self.surf = pygame.transform.scale(self.surf, scale)
renpy.display.render.mutated_surface(self.surf)
self.style = renpy.style.Style(style, properties)
def get_placement(self):
@@ -212,14 +230,14 @@ class UncachedImage(renpy.display.core.Displayable):
def render(self, w, h, st):
sw, sh = self.surf.get_size()
rv = renpy.display.surface.Surface(sw, sh)
rv = renpy.display.render.Render(sw, sh)
rv.blit(self.surf, (0, 0))
return rv
# Should never be called, but what the hey?
def predict(self, callback):
callback(self.filename)
return None
class ImageReference(renpy.display.core.Displayable):
"""
@@ -236,6 +254,8 @@ class ImageReference(renpy.display.core.Displayable):
nosave = [ 'target' ]
def __init__(self, name):
super(ImageReference, self).__init__()
self.name = name
def find_target(self):
@@ -276,17 +296,22 @@ class ImageReference(renpy.display.core.Displayable):
def render(self, width, height, st):
if not hasattr(self, 'target'):
self.find_target()
return self.target.render(width, height, st)
return render(self.target, width, height, st)
def get_placement(self):
if not hasattr(self, 'target'):
self.find_target()
return self.target.get_placement()
def predict(self, callback):
if not hasattr(self, 'target'):
self.find_target()
self.target.predict(callback)
class Solid(renpy.display.core.Displayable):
@@ -301,11 +326,12 @@ class Solid(renpy.display.core.Displayable):
@param color: An RGBA tuple, giving the color that the display will be filled with.
"""
super(Solid, self).__init__()
self.color = color
def render(self, width, height, st):
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
rv.fill(self.color)
return rv
@@ -339,19 +365,16 @@ class Frame(renpy.display.core.Displayable):
at. We detect this and avoid scaling if possible.
"""
super(Frame, self).__init__()
self.filename = filename
self.xborder = xborder
self.yborder = yborder
def render(self, width, height, st):
if hasattr(self, 'cache'):
if self.cache.get_size() == (width, height):
return self.cache
dest = renpy.display.surface.Surface(width, height)
dest = renpy.display.render.Render(width, height)
dw, dh = width, height
source = cache.load_image(self.filename)
@@ -432,8 +455,10 @@ class Frame(renpy.display.core.Displayable):
draw(-xb, 0, -yb, 0)
# And, finish up.
self.cache = dest
return dest
def predict(self, callback):
callback(self.filename)
class Animation(renpy.display.core.Displayable):
"""
@@ -451,6 +476,8 @@ class Animation(renpy.display.core.Displayable):
animation will restart after the final delay time.
"""
super(Animation, self).__init__()
self.images = [ ]
self.delays = [ ]
@@ -470,8 +497,15 @@ class Animation(renpy.display.core.Displayable):
for image, delay in zip(self.images, self.delays):
if t < delay:
renpy.game.interface.redraw(delay - t)
return cache.load_image(image)
renpy.display.render.redraw(self, delay - t)
im = cache.load_image(image)
width, height = im.get_size()
rv = renpy.display.render.Render(width, height)
rv.blit(im, (0, 0))
return rv
else:
t = t - delay
@@ -491,6 +525,8 @@ class ImageMap(renpy.display.core.Displayable):
def __init__(self, ground, selected, hotspots, unselected=None,
style='imagemap', **properties):
super(ImageMap, self).__init__()
self.ground = ground
self.selected = selected
self.hotspots = hotspots
@@ -501,6 +537,7 @@ class ImageMap(renpy.display.core.Displayable):
self.unselected = unselected
self.active = None
self.last_active = None
self.style = renpy.style.Style(style, properties)
@@ -520,7 +557,7 @@ class ImageMap(renpy.display.core.Displayable):
unselected = cache.load_image(self.unselected)
width, height = ground.get_size()
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
rv.blit(ground, (0, 0))
for i, hotspot in enumerate(self.hotspots):
@@ -533,6 +570,8 @@ class ImageMap(renpy.display.core.Displayable):
source = unselected
subsurface = source.subsurface((x0, y0, x1-x0, y1-y0))
renpy.display.render.mutated_surface(subsurface)
rv.blit(subsurface, (x0, y0))
return rv
@@ -551,17 +590,17 @@ class ImageMap(renpy.display.core.Displayable):
if old_active != active:
self.active = active
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
if active is not None:
renpy.sound.play(self.style.hover_sound)
renpy.display.audio.play(self.style.hover_sound)
if active is None:
return None
if renpy.display.behavior.map_event(ev, "imagemap_select"):
renpy.sound.play(self.style.activate_sound)
renpy.display.audio.play(self.style.activate_sound)
return result
return None
@@ -583,6 +622,10 @@ class ImageButton(renpy.display.behavior.Button):
hovered=hovered)
def predict(self, callback):
self.idle_image.predict(callback)
self.hover_image.predict(callback)
def set_hover(self, hover):
super(ImageButton, self).set_hover(hover)
+198 -61
View File
@@ -5,6 +5,19 @@ import pygame
from pygame.constants import *
import renpy
from renpy.display.render import render
import time
def scale(num, base):
"""
If num is a float, multiplies it by base and returns that. Otherwise,
returns num unchanged.
"""
if isinstance(num, float):
return num * base
else:
return num
class Null(renpy.display.core.Displayable):
"""
@@ -20,8 +33,11 @@ class Null(renpy.display.core.Displayable):
self.width = width
self.height = height
def get_placement(self):
return self.style
def render(self, width, height, st):
return renpy.display.surface.Surface(self.width, self.height)
return renpy.display.render.Render(self.width, self.height)
class Container(renpy.display.core.Displayable):
@@ -73,7 +89,7 @@ class Container(renpy.display.core.Displayable):
def render(self, width, height, st):
rv = self.child.render(width, height, st)
rv = render(self.child, width, height, st)
self.offsets = [ (0, 0) ]
self.sizes = [ rv.get_size() ]
@@ -83,7 +99,10 @@ class Container(renpy.display.core.Displayable):
return self.child.get_placement()
def event(self, ev, x, y):
for i, (xo, yo) in zip(self.children, self.offsets):
children_offsets = zip(self.children, self.offsets)
children_offsets.reverse()
for i, (xo, yo) in children_offsets:
rv = i.event(ev, x - xo, y - yo)
if rv is not None:
return rv
@@ -116,6 +135,67 @@ class Container(renpy.display.core.Displayable):
for i in self.children:
i.predict(callback)
class Fixed(Container):
"""
A container that lays out each of its children at fixed
coordinates determined by the position style of the child. Each
widget is given the whole area of this widget, and then placed
within that area based on its position style.
The result of this layout is the size of the entire area allocated
to it. So it's probably only viable for laying out a root window.
Fixed is used by the display core to render scene lists, and to
pass them off to transitions.
"""
def __init__(self, style='default', **properties):
super(Fixed, self).__init__()
self.style = renpy.style.Style(style, properties)
self.times = [ ]
def add(self, widget, time=None):
super(Fixed, self).add(widget)
self.times.append(time)
def append_scene_list(self, l):
for tag, time, d in l:
self.add(d, time)
def get_widget_time_list(self):
return zip(self.children, self.times)
def get_placement(self):
return self.style
def render(self, width, height, st):
self.offsets = [ ]
self.sizes = [ ]
rv = renpy.display.render.Render(width, height)
t = time.time()
for child, start in zip(self.children, self.times):
if start:
newst = t - start
else:
newst = st
surf = render(child, width, height, newst)
if surf:
self.sizes.append(surf.get_size())
offset = child.place(rv, 0, 0, width, height, surf)
self.offsets.append(offset)
else:
self.sizes.append((0, 0))
self.offsets.append((0, 0))
return rv
class Position(Container):
"""
Controls the placement of a displayable on the screen, using
@@ -141,7 +221,7 @@ class Position(Container):
def render(self, width, height, st):
surf = self.child.render(width, height, st)
surf = render(self.child, width, height, st)
cw, ch = surf.get_size()
self.offsets = [ (0, 0) ]
@@ -152,7 +232,76 @@ class Position(Container):
def get_placement(self):
return self.style
class Grid(Container):
"""
A grid is a widget that evenly allocates space to its children.
The child widgets should not be greedy, but should instead be
widgets that only use part of the space available to them.
"""
def __init__(self, cols, rows, padding=0,
style='default', **properties):
"""
@param cols: The number of columns in this widget.
@params rows: The number of rows in this widget.
"""
super(Grid, self).__init__()
self.style = renpy.style.Style(style, properties)
self.cols = cols
self.rows = rows
self.padding = padding
def render(self, width, height, st):
# For convenience and speed.
padding = self.padding
cols = self.cols
rows = self.rows
if len(self.children) != cols * rows:
raise Exception("Grid not completely full.")
renders = [ render(i, width, height, st) for i in self.children ]
self.sizes = [ i.get_size() for i in renders ]
cwidth = 0
cheight = 0
for w, h in self.sizes:
cwidth = max(cwidth, w)
cheight = max(cheight, h)
if self.style.xfill:
cwidth = (width - (cols - 1) * padding) / cols
if self.style.yfill:
cheight = (height - (rows - 1) * padding) / rows
width = cwidth * cols + padding * (cols - 1)
height = cheight * rows + padding * (rows - 1)
rv = renpy.display.render.Render(width, height)
self.offsets = [ ]
for y in range(0, rows):
for x in range(0, cols):
child = self.children[ x + y * cols ]
surf = renders[x + y * cols]
xpos = x * (cwidth + padding)
ypos = y * (cheight + padding)
offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
self.offsets.append(offset)
return rv
class HBox(Container):
"""
@@ -192,7 +341,7 @@ class HBox(Container):
for i in self.children:
xoffsets.append(xo)
surf = i.render(remwidth, height, st)
surf = render(i, remwidth, height, st)
sw, sh = surf.get_size()
@@ -209,7 +358,7 @@ class HBox(Container):
width = xo - self.padding
rv = renpy.display.surface.Surface(width, myheight)
rv = renpy.display.render.Render(width, myheight)
for surf, child, xo in zip(surfaces, self.children, xoffsets):
sw, sh = surf.get_size()
@@ -259,7 +408,7 @@ class VBox(Container):
yoffsets.append(yo)
surf = i.render(width, remheight, st)
surf = render(i, width, remheight, st)
sw, sh = surf.get_size()
@@ -276,7 +425,7 @@ class VBox(Container):
height = yo - self.padding
rv = renpy.display.surface.Surface(mywidth, height)
rv = renpy.display.render.Render(mywidth, height)
for surf, child, yo in zip(surfaces, self.children, yoffsets):
@@ -288,39 +437,6 @@ class VBox(Container):
return rv
class Fixed(Container):
"""
A container that lays out each of its children at fixed
coordinates determined by the position style of the child. Each
widget is given the whole area of this widget, and then placed
within that area based on its position style.
The result of this layout is the size of the entire area allocated
to it. So it's probably only viable for laying out a root window.
"""
def __init__(self, style='default', **properties):
super(Fixed, self).__init__()
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
def render(self, width, height, st):
self.offsets = [ ]
self.sizes = [ ]
rv = renpy.display.surface.Surface(width, height)
for child in self.children:
surf = child.render(width, height, st)
self.sizes.append(surf.get_size())
offset = child.place(rv, 0, 0, width, height, surf)
self.offsets.append(offset)
return rv
class Window(Container):
"""
@@ -353,43 +469,64 @@ class Window(Container):
# save typing and screen space.
style = self.style
xminimum = scale(style.xminimum, width)
yminimum = scale(style.yminimum, height)
left_margin = scale(style.left_margin, width)
left_padding = scale(style.left_padding, width)
right_margin = scale(style.right_margin, width)
right_padding = scale(style.right_padding, width)
top_margin = scale(style.top_margin, height)
top_padding = scale(style.top_padding, height)
bottom_margin = scale(style.bottom_margin, height)
bottom_padding = scale(style.bottom_padding, height)
# c for combined.
cxmargin = left_margin + right_margin
cymargin = top_margin + bottom_margin
cxpadding = left_padding + right_padding
cypadding = top_padding + bottom_padding
# Render the child.
surf = self.child.render(width - 2 * style.xmargin - 2 * style.xpadding,
height - 2 * style.ymargin - 2 * style.ypadding,
st)
surf = render(self.child,
width - cxmargin - cxpadding,
height - cymargin - cypadding,
st)
sw, sh = surf.get_size()
# If we don't fill, shrink our size to fit.
if not style.xfill:
width = max(2 * style.xmargin + 2 * style.xpadding + sw, style.xminimum)
width = max(cxmargin + cxpadding + sw, xminimum)
if not style.yfill:
height = max(2 * style.ymargin + 2 * style.ypadding + sh, style.yminimum)
height = max(cymargin + cypadding + sh, yminimum)
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
# Draw the background. The background should render at exactly the
# requested size. (That is, be a Frame or a Solid).
if style.background:
bw = width - 2 * style.xmargin
bh = height - 2 * style.ymargin
bw = width - cxmargin
bh = height - cymargin
back = style.background.render(bw, bh, st)
back = render(style.background, bw, bh, st)
rv.blit(back,
(style.xmargin, style.ymargin))
(left_margin, top_margin))
# (0, 0, bw, bh))
offsets = self.child.place(rv,
style.xmargin + style.xpadding,
style.ymargin + style.ypadding,
width - 2 * (style.xmargin + style.xpadding),
height - 2 * (style.ymargin + style.ypadding),
left_margin + left_padding,
top_margin + top_padding,
width - cxmargin - cxpadding,
height - cymargin - cypadding,
surf)
self.offsets = [ offsets ]
self.sizes = [ (sw, sh) ]
@@ -435,7 +572,7 @@ class Pan(Container):
def render(self, width, height, st):
surf = self.child.render(width, height, st)
surf = render(self.child, width, height, st)
self.sizes = [ surf.get_size() ]
x0, y0 = self.startpos
@@ -455,7 +592,7 @@ class Pan(Container):
self.offsets = [ (-xo, -yo) ]
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
# print surf
@@ -465,7 +602,7 @@ class Pan(Container):
# rv.blit(surf, (-xo, -yo))
if st < self.time:
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
return rv
@@ -520,13 +657,13 @@ class Move(Container):
def render(self, width, height, st):
self.st = st
rv = self.child.render(width, height, st)
rv = render(self.child, width, height, st)
self.sizes = [ rv.get_size() ]
self.offsets = [ (0, 0) ]
if st < self.time:
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
return rv
+538
View File
@@ -0,0 +1,538 @@
# Render lifespan.
# A render is alive when it is first created. It stays alive on subsequent
# styles if it is not killed and it is used. It can be killed either
# due to lack of use by the end of a cycle or because it was killed between
# cycles due to a timeout.
import sets
import time
import renpy
import pygame
from pygame.constants import *
# We only cache a single solid... but that should be enough to handle
# some important cases, like button and window backgrounds.
class SolidCache(object):
def __init__(self):
self.size = None
self.color = None
self.cached = None
def create(self, size, color):
if size == self.size and color == self.color:
return self.cached
self.size = size
self.color = color
if color[3] == 255:
surf = pygame.Surface(size, 0,
renpy.game.interface.display.window)
else:
surf = pygame.Surface(size, 0,
renpy.game.interface.display.sample_surface)
mutated_surface(surf)
surf.fill(color)
self.cached = surf
return surf
solid_cache = SolidCache()
## One thing to realize when considering the safety of this is that
## if any widget producing a render is redrawn, all instances of that
## render are killed, and so the entire thing is redrawn.
# Renders that have been used during the current rendering pass.
new_renders = { }
# Renders that were used on the old rendering pass.
old_renders = { }
# The set of surfaces that are mutated (that is, can change their
# contents.)
mutated_surfaces = { }
def render(widget, width, height, st):
"""
Renders a widget on the screen.
"""
if (widget, width, height) in old_renders:
rv = old_renders[widget, width, height]
# assert (widget, width, height) in rv.render_of
# assert not rv.dead
rv.keep_alive()
return rv
rv = widget.render(width, height, st)
rv.render_of.append((widget, width, height))
old_renders[widget, width, height] = rv
new_renders[widget, width, height] = rv
return rv
# A list of (when, widget) for redraws.
redraw_queue = [ ]
def process_redraws():
"""
Processes pending redraws. Returns True if a redraw is needed.
"""
global redraw_queue
redraw_queue.sort()
i = 0
dead_widgets = sets.Set()
now = time.time()
for when, widget in redraw_queue:
if when > now:
break
i += 1
dead_widgets.add(widget)
if not dead_widgets:
return False
redraw_queue = redraw_queue[i:]
for (widget, width, height), render in old_renders.items():
if widget in dead_widgets:
render.kill()
return True
def redraw(widget, when):
"""
Call this to queue the redraw of the supplied widget in the
supplied number of seconds.
"""
redraw_queue.append((when + time.time(), widget))
def render_screen(widget, width, height, st):
global redraw_queue
global old_renders
global new_renders
global mutated_surfaces
redraw_queue = [ ]
mutated_surfaces = { }
rv = render(widget, width, height, st)
# Renders that are in the old set but not the new one die here.
old_render_set = sets.Set(old_renders.itervalues())
new_render_set = sets.Set(new_renders.itervalues())
dead_render_set = old_render_set - new_render_set
for r in dead_render_set:
r.kill()
old_renders.update(new_renders)
new_renders.clear()
return rv
old_blits = [ ]
def compute_clip(source):
"""
This computes and returns the clipping rectangle of the source render.
"""
global old_blits
new_blits = [ ]
source.clip_to(pygame.display.get_surface(), 0, 0, new_blits)
bl0 = old_blits[:]
bl1 = new_blits[:]
# Backup blits.
old_blits = new_blits
# Changes between the two lists.
changes = [ ]
# Set of things in bl1.
bl1set = { }
for i in bl1:
bl1set[i] = True
# indices.
i0 = 0
i1 = 0
while True:
# If we're done with either of the lists, break.
if i0 >= len(bl0) or i1 >= len(bl1):
break
# blits
b0 = bl0[i0]
b1 = bl1[i1]
# If the two are the same.
if b0 == b1:
# Only add if the surface is mutated.
if b0[0] in mutated_surfaces:
changes.append(b0)
i0 += 1
i1 += 1
continue
# If the surface is only in bl0.
if b0 not in bl1set:
changes.append(b0)
i0 += 1
# The surface is only in bl1.
else:
changes.append(b1)
i1 += 1
changes.extend(bl0[i0:])
changes.extend(bl1[i1:])
if not changes:
return None
surf, x0, y0, w, h = changes[0]
x1 = x0 + w
y1 = y0 + h
for surf, x, y, w, h in changes:
x0 = min(x0, x)
y0 = min(y0, y)
x1 = max(x1, x + w)
y1 = max(y1, y + h)
return x0, y0, x1 - x0, y1 - y0
def screen_blit(source, full=False):
"""
Blits the given render to the screen. Computes the difference
between the current blit list and old_blits.
"""
screen = pygame.display.get_surface()
if full:
source.blit_to(screen, 0, 0)
return (0, 0) + screen.get_size()
cliprect = compute_clip(source)
if not cliprect:
return None
screen = pygame.display.get_surface()
screen.set_clip(cliprect)
source.blit_to(screen, 0, 0)
screen.set_clip()
return cliprect
def mutated_surface(surf):
"""
Called to indicate that a pygame surface has been mutated. This also
should be called each time a new pygame surface is created.
"""
mutated_surfaces[id(surf)] = True
class Render(object):
"""
A render represents a static picture of a single widget (perhaps
including the images of all of the children of that widget). It
is able to draw that widget to the screen, and contains
information about when it becomes invalid (and therefore the
widget can be withdrawn).
"""
def __init__(self, width, height):
"""
Creates a new render corresponding to the given widget with
the specified width and height.
@param widget: If this render corresponds directly to a
widget, then this is the widget it corresponds to.
"""
# Just for safety's sake.
self.dead = False
# A list of widget, width, height, corresponding to the
# entries in old_renders that this render is in.
self.render_of = [ ]
self.width = width
self.height = height
self.parents = [ ]
self.blittables = [ ]
self.children = [ ]
self.depends = [ ]
# A pygame surface holding this Render, if one exists.
self.surface = None
self.surface_alpha = False
self.subsurfaces = { }
# def __del__(self):
# Render.renders -= 1
# print "Render del", Render.renders, Render.liverenders, self
def keep_alive(self):
# assert not self.dead
for widget, width, height in self.render_of:
new_renders[widget, width, height] = self
for i in self.children:
# assert self in i.parents
# assert not i.dead
i.keep_alive()
def kill(self):
"""
Calling this marks the render and all of its parents dead. It also
unlinks it from the tree, readying it for reclamation.
"""
if self.dead:
return
self.dead = True
for widget, width, height in self.render_of:
del old_renders[widget, width, height]
if (widget, width, height) in new_renders:
del new_renders[widget, width, height]
parents = self.parents[:]
children = self.children[:]
depends = self.depends[:]
for p in parents:
p.kill()
for c in children:
while self in c.parents:
c.parents.remove(self)
for c in depends:
while self in c.parents:
c.parents.remove(self)
# assert not self.parents
# for p in parents:
# # assert p.dead
self.children = [ ]
self.depends = [ ]
# Removes cycles.
self.render_of = [ ]
def blit(self, source, (x, y)):
"""
Adds the source to the list of things that need to be blitted
to the screen. The source should be either a pygame.Surface,
or a Render.
"""
if isinstance(source, Render):
# assert not source.dead
source.parents.append(self)
self.children.append(source)
self.blittables.append((x, y, source))
def blit_to(self, dest, x, y):
"""
This blits the children of this Render to dest, which must be
a pygame.Surface. The x and y parameters are the location of
the upper-left hand corner of this surface, relative to the
destination surface.
"""
for xo, yo, source in self.blittables:
if isinstance(source, pygame.Surface):
dest.blit(source, (x + xo, y + yo))
else:
source.blit_to(dest, x + xo, y + yo)
def clip_to(self, dest, x, y, blits):
"""
This fills in blits with (id(surf), x, y, w, h) tuples.
"""
for xo, yo, source in self.blittables:
if isinstance(source, pygame.Surface):
blits.append((id(source), x + xo, y + yo) + source.get_size())
else:
source.clip_to(dest, x + xo, y + yo, blits)
def fill(self, color):
"""
Fake a pygame.Surface.fill()
"""
surf = solid_cache.create((self.width, self.height), color)
self.blit(surf, (0,0))
def get_size(self):
"""
Returns the size of this Render, a mostly ficticious value
that's taken from the inputs to the constructor. (As in, we
don't clip to this size.)
"""
return self.width, self.height
def pygame_surface(self, alpha=True):
"""
Returns a pygame surface constructed from this Render. This
may return a cached surface, if one already has been rendered
(so you probably shouldn't change the output of this much).
"""
if self.surface and self.surface_alpha == alpha:
return self.surface
if alpha:
sample = renpy.game.interface.display.sample_surface
else:
sample = renpy.game.interface.display.window
rv = pygame.Surface((self.width, self.height), 0, sample)
self.blit_to(rv, 0, 0)
self.surface = rv
self.surface_alpha = alpha
mutated_surface(rv)
return rv
def subsurface(self, pos):
"""
Returns a subsurface of this render.
"""
if pos in self.subsurfaces:
return self.subsurfaces[pos]
x, y, width, height = pos
if x > self.width or y > self.height:
return Render(0, 0)
width = min(self.width - x, width)
height = min(self.height - y, height)
rv = Render(width, height)
for xo, yo, source in self.blittables:
# ulx, uly -- the coordinates of the upper-left hand corner of
# the image, relative to the subsurface.
ulx = xo - x
uly = yo - y
# ox, oy -- the offsets that the source will be blitted at.
# sx, sy -- the offset within the subsurface at which we begin.
if ulx < 0:
ox = 0
sx = -ulx
else:
ox = ulx
sx = 0
if uly < 0:
oy = 0
sy = -uly
else:
oy = uly
sy = 0
sw, sh = source.get_size()
sw = min(sw - sx, width)
sh = min(sh - sy, height)
if sw <= 0 or sh <= 0:
continue
subsurf = source.subsurface((sx, sy, sw, sh))
if isinstance(subsurf, pygame.Surface):
mutated_surface(subsurf)
rv.blit(subsurf, (ox, oy))
self.subsurfaces[pos] = rv
rv.depends_on(self)
return rv
def depends_on(self, child):
"""
Used to indicate that this render depends on another
render. Useful, for example, if we use pygame_surface to make
a surface, and then blit that surface into another render.
"""
# assert not child.dead
self.depends.append(child)
child.parents.append(self)
+466 -9
View File
@@ -1,28 +1,483 @@
import pygame
from pygame.constants import *
import re
import renpy
import sys
_font_cache = { }
# TODO: Something sane if the font file can't be found.
def get_font(fn, size):
def get_font(fn, size, bold=False, italics=False, underline=False):
from renpy.loader import transfn
if (fn, size) in _font_cache:
return _font_cache[(fn, size)]
if (fn, size, bold, italics, underline) in _font_cache:
return _font_cache[(fn, size, bold, italics, underline)]
try:
rv = pygame.font.Font(transfn(fn), size)
rv.set_bold(bold)
rv.set_italic(italics)
except:
rv = pygame.font.SysFont(fn, size)
rv = pygame.font.SysFont(fn, size, bold, italics)
_font_cache[(fn, size)] = rv
rv.set_underline(underline)
_font_cache[(fn, size, bold, italics, underline)] = rv
return rv
def color(s):
"""
This function converts a hexcode into a color/alpha tuple. Leading
# marks are ignored. Colors can be rgb or rgba, with each element having
either one or two digits. (So the strings can be 3, 4, 6, or 8 digits long,
not including the optional #.) A missing alpha is interpreted as 255,
fully opaque.
For example, color('#123a') returns (17, 34, 51, 170), while
color('c0c0c0') returns (192, 192, 192, 255).
"""
if s[0] == '#':
s = s[1:]
if len(s) == 6:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = 255
elif len(s) == 8:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = int(s[6]+s[7], 16)
elif len(s) == 3:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = 255
elif len(s) == 4:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = int(s[3], 16) * 0x11
else:
raise Exception("Argument to color() must be 3, 4, 6, or 8 hex digits long.")
return (r, g, b, a)
class TextStyle(object):
"""
This is used to represent the style of text that will be displayed
on the screen.
"""
def __init__(self, source=None):
if source is not None:
vars(self).update(vars(source))
def get_font(self):
return get_font(self.font, self.size, self.bold, self.italic, self.underline)
def get_ascent(self):
return self.get_font().get_ascent()
def sizes(self, text):
font = self.get_font()
# print font.get_ascent() - font.get_descent(), font.get_height(), font.get_linesize()
return font.size(text)[0], font.get_linesize()
def render(self, text, antialias, color, use_colors):
if use_colors and self.color:
color = self.color
font = self.get_font()
rv = font.render(text, antialias, color)
renpy.display.render.mutated_surface(rv)
return rv
class Text(renpy.display.core.Displayable):
"""
A displayable that can format and display text on the screen.
"""
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_width', 'laidout_height', 'width' ]
def after_setstate(self):
self.laidout = None
def __init__(self, text, slow=False, style='default', **properties):
"""
@param text: The text that will be displayed on the screen.
@param slow: If True, the text will be slowly typed onto the screen.
@param style: A style that will be applied to the text.
@param properties: Additional properties that are applied to the text.
"""
super(Text, self).__init__()
self.text = text
self.style = renpy.style.Style(style, properties)
self.slow = slow
self.laidout = None
def get_placement(self):
return self.style
def set_text(self, new_text):
"""
Changes the text display by this object to new_text.
"""
self.text = new_text
self._update()
def set_style(self, style):
"""
Changes the style assocated with this object.
"""
self.style = style
self._update()
def _update(self):
"""
This is called after this widget has been updated by
set_text or set_style.
"""
self.laidout = None
renpy.display.render.redraw(self, 0)
def event(self, ev, x, y):
"""
Space, Enter, or Click ends slow, if it's enabled.
"""
if not self.slow:
return None
if renpy.display.behavior.map_event(ev, "dismiss"):
self.slow = False
raise renpy.display.core.IgnoreEvent()
def layout(self, width):
"""
This lays out the text of this widget. It sets self.laidout,
self.laidout_lineheights, self.laidout_width, and
self.laidout_height.
"""
if self.laidout and self.width == width:
return
# Set this, so caching works.
self.width = width
def indent():
if lines:
return self.style.rest_indent
else:
return self.style.first_indent
tsl = [ TextStyle() ]
tsl[-1].font = self.style.font
tsl[-1].size = self.style.size
tsl[-1].bold = self.style.bold
tsl[-1].italic = self.style.italic
tsl[-1].underline = self.style.underline
tsl[-1].color = None
lines = [ ]
line = [ ]
# The height of the current line, in pixels, not including
# line_height_fudge.
lineheight = 0
# A list of same.
lineheights = [ ]
# The width of the current line.
linewidth = 0
# The maximum linewidth.
maxwidth = 0
# The current text.
cur = ""
# The width, in pixels, of cur.
curwidth = 0
# The remaining width of the line, not including the text in
# cur.
remwidth = width - indent()
for i in re.split(r'( |\{[^{]+\}|\{\{|\n)', self.text):
# Newline.
if i == "\n":
if cur:
line.append((TextStyle(tsl[-1]), cur))
maxwidth = max(maxwidth, linewidth + curwidth)
cur = ""
lines.append(line)
lineheights.append(lineheight)
line = [ ]
linewidth = 0
curwidth, lineheight = tsl[-1].sizes(" ")
remwidth = width - indent()
continue
elif i == "{{":
i = "{"
# We want to render this like a word, so no continue.
elif i.startswith("{"):
# Are we closing a tag?
if i.startswith("{/"):
if cur:
line.append((TextStyle(tsl[-1]), cur))
cur = ""
remwidth -= curwidth
linewidth += curwidth
curwidth = 0
tsl.pop()
if not tsl:
raise Exception("Closing tag %s does not match an open tag." % i)
continue
# Otherwise, we're opening a new tag.
# Mark up any text that uses an old style.
if cur:
line.append((TextStyle(tsl[-1]), cur))
cur = ""
remwidth -= curwidth
linewidth += curwidth
curwidth = 0
tsl.append(TextStyle(tsl[-1]))
if i == "{b}":
tsl[-1].bold = True
elif i == "{i}":
tsl[-1].italic = True
elif i == "{u}":
tsl[-1].underline = True
elif i == "{plain}":
tsl[-1].bold = False
tsl[-1].italic = False
tsl[-1].underline = False
elif i.startswith("{size"):
m = re.match(r'\{size=(\+|-|)(\d+)\}', i)
if not m:
raise Exception('Size tag %s could not be parsed.' % i)
if m.group(1) == '+':
tsl[-1].size += int(m.group(2))
elif m.group(1) == '-':
tsl[-1].size -= int(m.group(2))
else:
tsl[-1].size = int(m.group(2))
elif i.startswith("{color"):
m = re.match(r'\{color=(\#?[a-fA-F0-9]+)\}', i)
if not m:
raise Exception('Color tag %s could not be parsed.' % i)
tsl[-1].color = color(m.group(1))
else:
raise Exception("Text tag %s was not recognized. Case and spacing matter here.")
continue
elif i == ' ':
# Spaces always get appended to the end of a line. So they
# will never show up at the start of a line, unless they're
# after a newline or at the start of a string.
cur += i
curwidth, lh = tsl[-1].sizes(cur)
lineheight = max(lh, lineheight)
continue
# If we made it here, then we have normal text.
# We must have at least one word or something else in the
# line before we care about wrapping.
if not cur and not line:
cur = i
curwidth, lineheight = tsl[-1].sizes(cur)
continue
# Should we wrap?
curwidth, lh = tsl[-1].sizes(cur + i)
if curwidth > remwidth:
line.append((TextStyle(tsl[-1]), cur))
lines.append(line)
maxwidth = max(maxwidth, linewidth)
line = [ ]
lineheights.append(lineheight)
cur = i
curwidth, lineheight = tsl[-1].sizes(cur)
remwidth = width - indent()
linewidth = 0
else:
cur = cur + i
lineheight = max(lh, lineheight)
# We're done. Let's close up.
if len(tsl) != 1:
exception("A tag was left open at the end of the text.")
if cur:
line.append((tsl[-1], cur))
maxwidth = max(maxwidth, curwidth)
if line:
lines.append(line)
lineheights.append(lineheight)
self.laidout = lines
self.laidout_lineheights = lineheights
self.laidout_width = max(maxwidth, self.style.minwidth)
self.laidout_height = sum(lineheights) + len(lineheights) * self.style.line_height_fudge
def render_pass(self, r, xo, yo, color, user_colors, length):
"""
Renders the text to r at xo, yo. Color is the base color,
and user_colors controls if the user can override those colors.
Returns True if all characters were rendered, or False if a
length restriction stopped some from being rendered.
"""
y = yo
indent = self.style.first_indent
rest_indent = self.style.rest_indent
antialias = self.style.antialias
line_height_fudge = self.style.line_height_fudge
for line, line_height in zip(self.laidout, self.laidout_lineheights):
x = xo + indent
indent = rest_indent
max_ascent = 0
for ts, text in line:
max_ascent = max(ts.get_ascent(), max_ascent)
for ts, text in line:
length -= len(text)
if length < 0:
text = text[:length]
surf = ts.render(text, antialias, color, user_colors)
sw, sh = surf.get_size()
r.blit(surf, (x, y + max_ascent - ts.get_ascent()))
x = x + sw
if length <= 0:
return False
y = y + line_height + line_height_fudge
return True
def render(self, width, height, st):
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
length = int(st * renpy.config.annoying_text_cps)
else:
length = sys.maxint
self.slow = False
if self.style.drop_shadow:
dsxo, dsyo = self.style.drop_shadow
absxo = abs(dsxo)
absyo = abs(dsyo)
width -= absxo
if dsxo < 0:
xo = -dsxo
dsxo = 0
else:
xo = 0
if dsyo < 0:
yo = -dsyo
dsyo = 0
else:
yo = 0
self.layout(width - absxo)
rv = renpy.display.render.Render(self.laidout_width + absxo, self.laidout_height + absxo)
if self.style.drop_shadow:
self.render_pass(rv, dsxo, dsyo, self.style.drop_shadow_color, False, length)
self.slow = not self.render_pass(rv, xo, yo, self.style.color, True, length)
if self.slow:
renpy.display.render.redraw(self, 0)
return rv
def event(self, ev, x, y):
"""
Space, Enter, or Click ends slow, if it's enabled.
"""
if not self.slow:
return None
if renpy.display.behavior.map_event(ev, "dismiss"):
self.slow = False
raise renpy.display.core.IgnoreEvent()
class OldText(renpy.display.core.Displayable):
"""
A Displayable that can display text on the screen.
"""
@@ -54,6 +509,7 @@ class Text(renpy.display.core.Displayable):
@param properties: Additional properties that are applied to the text.
"""
super(Text, self).__init__()
self.text = text
self.style = renpy.style.Style(style, properties)
@@ -96,8 +552,7 @@ class Text(renpy.display.core.Displayable):
Called to split the text into a string with newline characters
at line endings where wrapping has occured.
"""
font = get_font(self.style.font, self.style.size)
lines = [ ]
@@ -154,7 +609,7 @@ class Text(renpy.display.core.Displayable):
if not hasattr(self, "laidout"):
self.layout(width - dsxo)
surf = renpy.display.surface.Surface(self.width + dsxo, self.height + dsyo)
surf = renpy.display.render.Render(self.width + dsxo, self.height + dsyo)
font = get_font(self.style.font, self.style.size)
laidout = self.laidout
@@ -164,7 +619,7 @@ class Text(renpy.display.core.Displayable):
chars = int(st * renpy.config.annoying_text_cps)
if chars < len(laidout):
laidout = laidout[:chars]
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
else:
self.slow = False
else:
@@ -186,6 +641,7 @@ class Text(renpy.display.core.Displayable):
for l in lines:
ls = font.render(l, self.style.antialias, color)
renpy.display.render.mutated_surface(ls)
lw, lh = ls.get_size()
lw += indent
@@ -242,3 +698,4 @@ class ParameterizedText(object):
return Text(string, style=self.style, **self.properties)
+433 -43
View File
@@ -1,7 +1,57 @@
import renpy
from renpy.display.render import render
import pygame
from pygame.constants import *
# This is a utility function that attempts to refactor an old and a new
# Fixed into four Fixeds: below, old, new, and above. Since only the
# old and new need transitions, this can be a significant win.
def refactor_fixed(in_old, in_new):
Fixed = renpy.display.layout.Fixed
out_below = Fixed()
out_old = Fixed()
out_new = Fixed()
out_above = Fixed()
if (not isinstance(in_old, Fixed)) or (not isinstance(in_new, Fixed)):
return out_below, in_old, in_new, out_above
old_list = in_old.get_widget_time_list()
new_list = in_new.get_widget_time_list()
# Merge the beginnings of the lists.
while old_list and new_list:
if old_list[0] == new_list[0]:
out_below.add(new_list[0][0], new_list[0][1])
old_list.pop(0)
new_list.pop(0)
else:
break
# Merge the ends of the lists.
above_list = [ ]
while old_list and new_list:
if old_list[-1] == new_list[-1]:
above_list.insert(0, new_list[-1])
old_list.pop()
new_list.pop()
else:
break
for widget, time in above_list:
out_above.add(widget, time)
for widget, time in old_list:
out_old.add(widget, time)
for widget, time in new_list:
out_new.add(widget, time)
return out_below, out_old, out_new, out_above
class Transition(renpy.display.core.Displayable):
"""
@@ -12,38 +62,47 @@ class Transition(renpy.display.core.Displayable):
def __init__(self, delay):
self.delay = delay
self.offsets = [ ]
self.events = True
def event(self, ev, x, y):
event_list = self.new_scene_list[:]
event_list.reverse()
offsets = self.offsets[:]
offsets.reverse()
for (key, st, disp), (xo, yo) in zip(event_list, offsets):
rv = disp.event(ev, x - xo, y - yo)
if rv is not None:
return rv
return None
if self.events:
return self.new_widget.event(ev, x, y)
else:
return None
class Fade(Transition):
"""
This is a transition that involves fading to a certain color, then
holding that color for a certain amount of time, then fading in the
new scene.
This returns an object that can be used as an argument to a with
statement to fade the old scene into a solid color, waits for a
given amount of time, and then fades from the solid color into
the new scene.
@param in_time: The amount of time that will be spent
fading from the old scene to the solid color. A float, given as
seconds.
@param hold_time: The amount of time that will be spent
displaying the solid color. A float, given as seconds.
@param out_time: The amount of time that will be spent
fading from the solid color to the new scene. A float, given as
seconds.
@param color: The solid color that will be faded
to. This is an RGB triple, where each element is in the range 0
to 255. This defaults to black.
"""
def __init__(self, out_time, hold_time, in_time,
old_scene_list, new_scene_list, color=(0, 0, 0)):
old_widget=None, new_widget=None, color=(0, 0, 0)):
super(Fade, self).__init__(out_time + hold_time + in_time)
self.out_time = out_time
self.hold_time = hold_time
self.in_time = in_time
self.old_scene_list = old_scene_list
self.new_scene_list = new_scene_list
self.old_widget = old_widget
self.new_widget = new_widget
self.color = color
# self.frames = 0
@@ -55,31 +114,29 @@ class Fade(Transition):
# self.frames += 1
rv = renpy.display.surface.Surface(width, height)
rv = renpy.display.render.Render(width, height)
events = False
if st < self.out_time:
scene_list = self.old_scene_list
widget = self.old_widget
alpha = int(255 * (st / self.out_time))
elif st < self.out_time + self.hold_time:
scene_list = None
widget = None
alpha = 255
else:
scene_list = self.new_scene_list
widget = self.new_widget
alpha = 255 - int(255 * ((st - self.out_time - self.hold_time) / self.in_time))
events = True
if scene_list:
surf, offsets = renpy.display.core.render_scene_list(scene_list,
width,
height)
if widget:
surf = render(widget, width, height, st)
rv.blit(surf, (0, 0))
if events:
self.offsets = offsets
self.events = events
# Just to be sure.
if alpha < 0:
@@ -91,35 +148,368 @@ class Fade(Transition):
rv.fill(self.color[:3] + (alpha,))
if st < self.in_time + self.hold_time + self.out_time:
renpy.game.interface.redraw(0)
renpy.display.render.redraw(self, 0)
return rv
class Dissolve(Transition):
# This was a nifty idea that just didn't work out, since we can't vary
# the alpha on an image with an alpha channel. Too bad.
def __init__(self, time, old_scene_list, new_scene_list):
# class Dissolve(Transition):
# def __init__(self, time, old_widget, new_widget):
# super(Dissolve, self).__init__(time)
# self.time = time
# self.below, self.old, self.new, self.above = refactor_fixed(old_widget, new_widget)
# def event(self, ev, x, y):
# rv = self.above.event(ev, x, y)
# if rv is None:
# rv = self.new.event(ev, x, y)
# if rv is None:
# rv = self.below.event(ev, x, y)
# return rv
# def render(self, width, height, st):
# rv = renpy.display.render.Render(width, height)
# # Below.
# below = render(self.below, width, height, st)
# rv.blit(below, (0, 0))
# if st < self.time:
# # Old.
# old = render(self.old, width, height, st)
# rv.blit(old, (0, 0))
# # New.
# alpha = min(255, int(255 * st / self.time))
# new = render(self.new, width, height, st)
# if alpha < 255:
# surf = new.pygame_surface(False)
# renpy.display.render.mutable_surface(surf)
# surf.set_alpha(alpha, RLEACCEL)
# rv.blit(surf, (0, 0))
# rv.depends_on(new)
# else:
# rv.blit(new, (0, 0))
# # Above.
# above = render(self.above, width, height, st)
# rv.blit(above, (0, 0))
# if st < self.time:
# renpy.display.render.redraw(self, 0)
# return rv
class Dissolve(Transition):
"""
This dissolves from the old scene to the new scene, by
overlaying the new scene on top of the old scene and varying its
alpha from 0 to 255.
@param delay: The amount of time the dissolve will take.
"""
def __init__(self, time, old_widget=None, new_widget=None):
super(Dissolve, self).__init__(time)
self.time = time
self.old_scene_list = old_scene_list
self.new_scene_list = new_scene_list
self.old_widget = old_widget
self.new_widget = new_widget
self.events = False
self.old_bottom = None
self.old_top = None
self.old_alpha = 0
def render(self, width, height, st):
rsl = renpy.display.core.render_scene_list
if st >= self.time:
self.events = True
return render(self.new_widget, width, height, st)
rv, offsets = rsl(self.old_scene_list, width, height)
surftree, self.offsets = rsl(self.new_scene_list, width, height)
surf = surftree.pygame_surface(False)
if st < self.time:
renpy.display.render.redraw(self, 0)
alpha = min(255, int(255 * st / self.time))
surf.set_alpha(alpha)
rv.blit(surf, (0, 0))
rv = renpy.display.render.Render(width, height)
bottom = render(self.old_widget, width, height, st)
top = render(self.new_widget, width, height, st)
surf = top.pygame_surface(False)
renpy.display.render.mutated_surface(surf)
if id(top) == self.old_top and id(bottom) == self.old_bottom:
# Fast rendering path.
alpha = alpha / 255.0
change = ( alpha - self.old_alpha) / ( 1.0 - self.old_alpha)
change = int(change * 255.0)
surf.set_alpha(change, RLEACCEL)
rv.blit(surf, (0, 0))
change /= 255.0
self.old_alpha = self.old_alpha * ( 1 - change ) + change
else:
# Complete rendering path.
rv.blit(bottom, (0, 0))
surf.set_alpha(alpha, RLEACCEL)
rv.blit(surf, (0, 0))
self.old_alpha = alpha / 255.0
self.old_top = id(top)
self.old_bottom = id(bottom)
if st < self.time:
renpy.game.interface.redraw(0)
return rv
class CropMove(Transition):
"""
The CropMove transition works by placing the old and the new image
on two layers, called the top and the bottom. (Normally the new
image is on the top, but that can be changed in some modes.) The
bottom layer is always drawn in full. The top image is first
cropped to a rectangle, and then that rectangle drawn onto
the screen at a specified position. Start and end crop rectangles
and positions can be selected by the supplied mode, or
specified manually. The result is a surprisingly flexible
transition.
This transition has many modes, simplifying its use. We can group
these modes into three groups: wipes, slides, and other.
In a wipe, the image stays fixed, and more of it is revealed as
the transition progresses. For example, in "wiperight", a wipe from left to right, first the left edge of the image is
revealed at the left edge of the screen, then the center of the image,
and finally the right side of the image at the right of the screen.
Other supported wipes are "wipeleft", "wipedown", and "wipeup".
In a slide, the image moves. So in a "slideright", the right edge of the
image starts at the left edge of the screen, and moves to the right
as the transition progresses. Other slides are "slideleft", "slidedown",
and "slideup".
There are also slideaways, in which the old image moves on top of
the new image. Slideaways include "slideawayright", "slideawayleft",
"slideawayup", and "slideawaydown".
We also support a rectangular iris in with "irisin" and a
rectangular iris out with "irisout". Finally, "custom" lets the
user define new transitions, if these ones are not enough.
"""
def __init__(self, time,
mode="fromleft",
startcrop=(0.0, 0.0, 0.0, 1.0),
startpos=(0.0, 0.0),
endcrop=(0.0, 0.0, 1.0, 1.0),
endpos=(0.0, 0.0),
topnew=True,
old_widget=None,
new_widget=None):
"""
@param time: The time that this transition will last for, in seconds.
@param mode: One of the modes given above.
The following parameters are only respected if the mode is "custom".
@param startcrop: The starting rectangle that is cropped out of the
top image. A 4-element tuple containing x, y, width, and height.
@param startpos: The starting place that the top image is drawn
to the screen at, a 2-element tuple containing x and y.
@param startcrop: The starting rectangle that is cropped out of the
top image. A 4-element tuple containing x, y, width, and height.
@param startpos: The starting place that the top image is drawn
to the screen at, a 2-element tuple containing x and y.
@param topnew: If True, the top layer contains the new
image. Otherwise, the top layer contains the old image.
"""
self.time = time
if mode == "wiperight":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipeleft":
startpos = (1.0, 0.0)
startcrop = (1.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipedown":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipeup":
startpos = (0.0, 1.0)
startcrop = (0.0, 1.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideright":
startpos = (0.0, 0.0)
startcrop = (1.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideleft":
startpos = (1.0, 0.0)
startcrop = (0.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideup":
startpos = (0.0, 1.0)
startcrop = (0.0, 0.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slidedown":
startpos = (0.0, 0.0)
startcrop = (0.0, 1.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideawayleft":
endpos = (0.0, 0.0)
endcrop = (1.0, 0.0, 0.0, 1.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawayright":
endpos = (1.0, 0.0)
endcrop = (0.0, 0.0, 0.0, 1.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawaydown":
endpos = (0.0, 1.0)
endcrop = (0.0, 0.0, 1.0, 0.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawayup":
endpos = (0.0, 0.0)
endcrop = (0.0, 1.0, 1.0, 0.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "irisout":
startpos = (0.5, 0.5)
startcrop = (0.5, 0.5, 0.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "irisin":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
endpos = (0.5, 0.5)
endcrop = (0.5, 0.5, 0.0, 0.0)
topnew = False
elif mode == "custom":
pass
else:
raise Exception("Invalid mode %s passed into boxwipe." % mode)
self.delay = time
self.time = time
self.startpos = startpos
self.endpos = endpos
self.startcrop = startcrop
self.endcrop = endcrop
self.topnew = topnew
self.old_widget = old_widget
self.new_widget = new_widget
self.events = False
if topnew:
self.bottom = old_widget
self.top = new_widget
else:
self.bottom = new_widget
self.top = old_widget
def render(self, width, height, st):
time = 1.0 * st / self.time
# Done rendering.
if time >= 1.0:
self.events = True
return render(self.new_widget, width, height, st)
# How we scale each element of a tuple.
scales = (width, height, width, height)
def interpolate_tuple(t0, t1):
return tuple([ int(s * (a * (1.0 - time) + b * time))
for a, b, s in zip(t0, t1, scales) ])
crop = interpolate_tuple(self.startcrop, self.endcrop)
pos = interpolate_tuple(self.startpos, self.endpos)
rv = renpy.display.render.Render(width, height)
rv.blit(render(self.bottom, width, height, st), (0, 0))
top = render(self.top, width, height, st)
ss = top.subsurface(crop)
rv.blit(ss, pos)
renpy.display.render.redraw(self, 0)
return rv
+173
View File
@@ -0,0 +1,173 @@
import renpy
from renpy.display.render import render
import pygame
import sys # for maxint
class MovieInfo(object):
def __init__(self, filename, loops, fullscreen, size=None):
self.filename = filename
self.loops = loops + 1
self.fullscreen = fullscreen
self.size = size
# The movie that is currently playing, if any.
movie = None
# If the movie is running in a widget, this is the surface corresponding
# to that widget.
surface = None
# The current movie info.
current_info = None
# The number of loops the current movie has made.
loops = 0
def movie_stop(clear=True):
"""
This stops the currently playing movie.
"""
global movie
global loops
if movie:
movie.stop()
movie = None
surface = None
loops = 0
renpy.display.audio.enable_mixer()
if clear:
renpy.game.context().scene_lists.movie = None
def movie_start_fullscreen(filename, loops=0):
"""
This starts a MPEG-1 movie playing in fullscreen mode. While the movie is
playing (that is, until the next call to movie_stop), interactions will
not display anything on the screen.
@param filename: The filename of the MPEG-1 move that we're playing.
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
"""
movie_stop()
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, True)
def movie_start_displayable(filename, size, loops=0):
"""
This starts a MPEG-1 movie playing in displayable mode. One or more Movie()
widgets must be displayed if the movie is to be shown to the user.
@param filename: The filename of the MPEG-1 move that we're playing.
@param size: A tuple containing the size of the movie on the screen. For example, (640, 480).
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
"""
movie_stop()
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, False, size)
def interact():
"""
This is called at the start of an interaction. It starts the required
movie playing, if it's necessary. It returns True if the movie is fullscreen
and therefore nothing else should be drawn on the screen, or False
otherwise.
"""
try:
global movie
global surface
global current_info
global loops
info = renpy.game.context().scene_lists.movie
# Has the info changed? If so, stop the movie.
if info is not current_info:
movie_stop(False)
current_info = info
# No movie to play.
if not info:
return False
# Movie not playing, start it up.
if not movie:
# Needed so we get movie sound.
renpy.display.audio.disable_mixer()
m = pygame.movie.Movie(renpy.loader.transfn(info.filename))
if info.fullscreen:
s = None
m.set_display(pygame.display.get_surface(),
(0, 0,
renpy.config.screen_width,
renpy.config.screen_height))
else:
s = pygame.Surface(info.size)
m.set_display(s, (0, 0) + info.size)
movie = m
surface = s
if not movie.get_busy():
if not info.loops or loops < info.loops:
movie.rewind()
movie.play()
loops += 1
else:
movie_stop()
# Movie is playing (by now).
return info.fullscreen
except:
movie_stop()
if renpy.config.debug_sound:
raise
else:
renpy.display.audio.enable_mixer()
return False
class Movie(renpy.display.layout.Null):
"""
This is a displayable that displays the current movie. In general,
a movie should be playing whenever this is on the screen.
That movie should have been started using movie_start_displayable
before this is shown on the screen, and hidden before this is
removed.
"""
def __init__(self, style='image_placement', **properties):
super(Movie, self).__init__(style=style, **properties)
def render(self, width, height, st):
renpy.display.render.redraw(self, 0)
if surface:
renpy.display.render.mutated_surface(surface)
w, h = surface.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surface, (0, 0))
return rv
else:
return super(Movie, self).render(width, height, st)
+1 -2
View File
@@ -36,8 +36,7 @@ class Context(object):
oldsl = context.scene_lists
self.runtime = context.runtime
import renpy.display.core as dcore
self.scene_lists = dcore.SceneLists(oldsl)
self.scene_lists = renpy.display.core.SceneLists(oldsl)
def goto_label(self, node_name):
"""
+115 -23
View File
@@ -12,8 +12,9 @@ from renpy.display.behavior import *
from renpy.display.image import *
from renpy.curry import curry
from renpy.music import music_start, music_stop
from renpy.sound import play
from renpy.display.audio import music_start, music_stop
from renpy.display.audio import play
from renpy.display.video import movie_start_fullscreen, movie_start_displayable, movie_stop
from renpy.loadsave import *
from renpy.python import py_eval as eval
from renpy.python import rng as random
@@ -32,8 +33,8 @@ def checkpoint():
renpy.game.log.checkpoint()
def interact(*widgets, **kwargs):
return renpy.game.interface.interact(transient=widgets, **kwargs)
# def interact(**kwargs):
# return renpy.game.interface.interact(**kwargs)
def scene_lists(index=-1):
"""
@@ -111,7 +112,7 @@ def watch(expression, style='default', **properties):
renpy.config.overlay_functions.append(overlay_func)
def input(prompt, default='', length=None):
def input(prompt, default='', allow=None, exclude='{}', length=None):
"""
This pops up a window requesting that the user enter in some text.
It returns the entered text.
@@ -122,15 +123,23 @@ def input(prompt, default='', length=None):
@param length: If given, a limit to the amount of text that this
function will return.
@param allow: If not None, then if an input character is not in this
string, it is ignored.
@param exclude: If not None, then if an input character is in this
set, it is ignored.
"""
vbox = renpy.display.layout.VBox()
win = renpy.display.layout.Window(vbox, style='input_window')
renpy.ui.window(style='input_window')
renpy.ui.vbox()
vbox.add(renpy.display.text.Text(prompt, style='input_prompt'))
vbox.add(renpy.display.behavior.Input(default, length=length, style='input_text'))
renpy.ui.text(prompt, style='input_prompt')
renpy.ui.input(default, length=length, style='input_text', allow=allow, exclude=exclude)
return interact(win)
renpy.ui.close()
return renpy.ui.interact()
def menu(items, set_expr):
"""
@@ -186,11 +195,24 @@ def display_menu(items, window_style='menu_window'):
renpy.ui.window(style=window_style)
renpy.ui.menu(items)
rv = interact()
rv = renpy.ui.interact()
checkpoint()
return rv
class TagQuotingDict(object):
def __getitem__(self, key):
if key in renpy.game.store:
rv = renpy.game.store[key]
if isinstance(rv, (str, unicode)):
rv = rv.replace("{", "{{")
return rv
else:
raise Exception("During an interpolation, '%s' was not found as a variable." % key)
tag_quoting_dict = TagQuotingDict()
def say(who, what):
"""
@@ -200,7 +222,7 @@ def say(who, what):
"""
# Interpolate variables.
what = what % renpy.game.store
what = what % tag_quoting_dict
if who is None:
who = renpy.store.narrator
@@ -231,7 +253,7 @@ def display_say(who, what, who_style='say_label',
"""
# If we're going to do an interaction, then saybehavior needs
# to be here, right before ui.text.
# to be here.
if interact:
renpy.ui.saybehavior()
@@ -290,7 +312,7 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
renpy.ui.keymousebehavior()
rv = interact(suppress_overlay=(not overlays))
rv = renpy.ui.interact(suppress_overlay=(not overlays))
checkpoint()
return rv
@@ -298,7 +320,9 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
def pause(delay=None, music=None):
"""
When called, this pauses and waits for the user to click before
advancing the script.
advancing the script. If given a delay parameter, the Ren'Py will
wait for that amount of time before continuing, unless a user clicks to
interrupt the delay.
@param delay: The number of seconds to delay.
@@ -313,15 +337,45 @@ def pause(delay=None, music=None):
"""
if music is not None:
newdelay = renpy.music.music_delay(music)
newdelay = renpy.display.audio.music_delay(music)
if newdelay is not None:
delay = newdelay
sayb = renpy.display.behavior.SayBehavior(delay=delay)
scene_list_add('transient', sayb)
return interact()
renpy.ui.saybehavior()
if delay:
renpy.ui.pausebehavior(delay, False)
return renpy.ui.interact()
def movie_cutscene(filename, delay, loops=0):
"""
This displays an MPEG-1 cutscene for the specified number of
seconds. The user can click to interrupt the cutscene.
Overlays and Underlays are disabled for the duration of the cutscene.
@param filename: The name of a file containing an MPEG-1 movie.
@param delay: The number of seconds to wait before ending the cutscene. Normally the length of the movie, in seconds.
@param loops: The number of extra loops to show, -1 to loop forever.
Returns True if the movie was terminated by the user, or False if the
given delay elapsed uninterrupted.
"""
movie_start_fullscreen(filename, loops=loops)
renpy.ui.saybehavior()
renpy.ui.pausebehavior(delay, False)
rv = renpy.ui.interact(suppress_overlay=True, suppress_underlay=True, show_mouse=False)
movie_stop()
return rv
def with(trans):
"""
@@ -339,7 +393,7 @@ def with(trans):
renpy.game.interface.set_transition(trans)
return renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=True)
suppress_overlay=not renpy.config.overlay_during_wait)
else:
return False
@@ -423,17 +477,29 @@ def windows():
import sys
return hasattr(sys, 'winver')
def transition(trans):
def version():
"""
Returns a string containing the current version of Ren'Py, prefixed with the
string "Ren\'Py ".
"""
return renpy.version
def transition(trans, layer=None):
"""
Sets the transition that will be used for the next
interaction. This is useful when the next interaction doesn't take
a with clause, as is the case with pause, input, and imagemap.
@param layer: If the layer setting is not None, then the transition
will be applied only to the layer named. Please note that only some
transitions can be applied to specific layers.
"""
if trans is None:
renpy.game.interface.with_none()
else:
renpy.game.interface.set_transition(trans)
renpy.game.interface.set_transition(trans, layer)
def clear_game_runtime():
"""
@@ -458,6 +524,32 @@ def get_game_runtime():
return renpy.game.context().runtime / 1000.0
def exists(filename):
"""
Returns true if the given filename can be found in the
searchpath. This only works if a physical file exists on disk. It
won't find the file if it's inside of an archive.
"""
try:
renpy.loader.transfn(filename)
return True
except:
return False
def restart_interaction():
"""
Calling this restarts the current interaction. This will immediately end
any ongoing transition, and will call all of the overlay functions again.
This should be called whenever widgets are added or removed over the course
of an interaction, or when the information used to construct the overlay
changes.
"""
renpy.game.interface.restart_interaction = True
call_in_new_context = renpy.game.call_in_new_context
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
+1 -1
View File
@@ -147,4 +147,4 @@ def call_in_new_context(label):
context.run()
contexts.pop()
interface.redraw(0)
interface.force_redraw = True
+5 -1
View File
@@ -15,8 +15,12 @@ savegame_suffix = renpy.savegame_suffix
def debug_dump(prefix, o, seen):
if isinstance(o, (int, str, float, bool)):
print prefix, o
return
if id(o) in seen:
print prefix, id(o)
print prefix, "@%x" % id(o)
return
seen[id(o)] = True
+1 -1
View File
@@ -574,7 +574,7 @@ class RollbackLog(renpy.object.Object):
rb.rollback()
# Disable the next transition, as it's pointless.
renpy.game.interface.supress_transition = True
renpy.game.interface.suppress_transition = True
# If necessary, reset the RNG.
if force:
+9 -1
View File
@@ -91,7 +91,15 @@ class Script(object):
raise Exception("Could not load %s or %s." % (fn, alt))
self.initcode.sort()
# Make the sort stable.
initcode = [ (prio, index, code) for index, (prio, code) in
enumerate(self.initcode) ]
initcode.sort()
self.initcode = [ (prio, code) for prio, index, code in initcode ]
# Do some generic init here.
+14 -45
View File
@@ -22,12 +22,14 @@ Image = renpy.display.image.Image
Solid = renpy.display.image.Solid
Frame = renpy.display.image.Frame
Animation = renpy.display.image.Animation
Movie = renpy.display.video.Movie
Position = renpy.curry.curry(renpy.display.layout.Position)
Pan = renpy.curry.curry(renpy.display.layout.Pan)
Move = renpy.curry.curry(renpy.display.layout.Move)
Fade = renpy.curry.curry(renpy.display.transition.Fade)
Dissolve = renpy.curry.curry(renpy.display.transition.Dissolve)
CropMove = renpy.curry.curry(renpy.display.transition.CropMove)
def _return(v):
"""
@@ -98,11 +100,12 @@ class Character(object):
self.window_style = window_style
self.properties = properties
def __call__(self, what):
def __call__(self, what, interact=True):
renpy.display_say(self.name, what,
who_style=self.who_style,
what_style=self.what_style,
window_style=self.window_style,
interact=interact,
**self.properties)
class DynamicCharacter(object):
@@ -134,7 +137,7 @@ class DynamicCharacter(object):
self.window_style = window_style
self.properties = properties
def __call__(self, what):
def __call__(self, what, interact=True):
import renpy.python as python
renpy.display_say(python.py_eval(self.name_expr),
@@ -142,56 +145,22 @@ class DynamicCharacter(object):
who_style=self.who_style,
what_style=self.what_style,
window_style=self.window_style,
interact=interact,
**self.properties)
# The color function. (Moved, since text needs it, too.)
color = renpy.display.text.color
# Conveniently get rid of all the packages we had imported before.
import renpy.exports as renpy
def narrator(what):
renpy.display_say(None, what, what_style='say_thought')
# The default narrator.
def narrator(what, interact=True):
renpy.display_say(None, what, what_style='say_thought', interact=interact)
# The default menu function.
menu = renpy.display_menu
def color(s):
"""
This function converts a hexcode into a color/alpha tuple. Leading
# marks are ignored. Colors can be rgb or rgba, with each element having
either one or two digits. (So the strings can be 3, 4, 6, or 8 digits long,
not including the optional #.) A missing alpha is interpreted as 255,
fully opaque.
For example, color('#123a') returns (17, 34, 51, 170), while
color('c0c0c0') returns (192, 192, 192, 255).
"""
if s[0] == '#':
s = s[1:]
if len(s) == 6:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = 255
elif len(s) == 8:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = int(s[6]+s[7], 16)
elif len(s) == 3:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = 255
elif len(s) == 4:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = int(s[3], 16) * 0x11
else:
raise Exception("Argument to color() must be 3, 4, 6, or 8 hex digits long.")
return (r, g, b, a)
# The default transition.
default_transition = None
+17 -1
View File
@@ -1,7 +1,15 @@
import renpy
# A list of style prefixes we care about, including no prefix.
prefixes = [ 'hover_', 'idle_', '' ]
prefixes = [ 'hover_', 'idle_', 'activate_', '' ]
substitutes = dict(
xmargin = [ 'left_margin', 'right_margin' ],
ymargin = [ 'top_margin', 'bottom_margin' ],
xpadding = [ 'left_padding', 'right_padding' ],
ypadding = [ 'top_padding', 'bottom_padding' ],
)
class StyleManager(object):
"""
@@ -61,6 +69,7 @@ class Style(object):
"""
def __getstate__(self):
return dict(properties=self.properties,
prefix=self.prefix,
parent=self.parent)
@@ -76,6 +85,13 @@ class Style(object):
def __setattr__(self, key, value):
if key in substitutes:
for i in substitutes[key]:
self.__setattr__(i, value)
return
for prefix in prefixes:
prefkey = prefix + key
+110 -24
View File
@@ -16,11 +16,10 @@
import renpy
# The current widget (None to add widgets directly to the
# transient display list.)
current = None
# The current widget. (Should never become None.)
current = 'transient'
# A stack of current widgets.
# A stack of current widgets and/or layers.
current_stack = [ ]
# True if the current widget should be used at most once.
@@ -36,12 +35,12 @@ def interact(**kwargs):
@param show_mouse: Should the mouse be shown during this
interaction? Only advisory, as this doesn't work reliably.
@param suppress_overlay: This suppresses the display of the overlay during
this interaction.
@param suppress_overlay: This suppresses the display of the overlay
during this interaction.
"""
if current_stack:
raise Exception("ui.interact called with non-empty widget stack. Did you forget a ui.close()?")
raise Exception("ui.interact called with non-empty widget/layer stack. Did you forget a ui.close() somewhere?")
return renpy.game.interface.interact(**kwargs)
@@ -56,8 +55,8 @@ def add(w, make_current=False, once=False):
global current
global current_once
if current is None:
renpy.game.context(-1).scene_lists.add('transient', w)
if isinstance(current, str):
renpy.game.context(-1).scene_lists.add(current, w)
else:
current.add(w)
@@ -73,19 +72,38 @@ def add(w, make_current=False, once=False):
return w
def layer(name):
"""
This causes widgets to be added to the named layer, until a
matching call to ui.close().
"""
global current_once
global current
if not isinstance(current, str):
raise Exception("Opening a layer while a widget is open is not allowed.")
if name not in renpy.config.layers:
raise Exception("'%s' is not a known layer." % name)
current_stack.append(current)
current_once = False
current = name
def close():
"""
Closes the current widget. This means that we will no longer add
things to the current widget, but will instead start adding things
to the parent of that widget, or the screen if the widget has no
parent. Calling this when there is no current widget will lead to
an exception being thrown.
This closes the currently open widget or layer. If a widget is
closed, then we start adding to its parent, or the layer if no
parent is open. If a layer is closed, we return to the previously
open layer. An error is thrown if we close the last open layer.
"""
global current
if current is None:
raise Exception("ui.close() called when there is no open widget.")
if not current_stack:
raise Exception("ui.close() called to close the last open layer or widget.")
if current_once:
raise Exception("ui.close() called when expecting a widget.")
@@ -148,6 +166,36 @@ def vbox(padding=0, **properties):
return add(renpy.display.layout.VBox(padding, **properties), True)
def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
"""
This creates a layout that places widgets in an evenly spaced
grid. New widges are added to this vbox unil ui.close() is called.
Widgets are added by going from left to right within a single row,
and down to the start of the next row when a row is full. All cells
must be filled (that is, exactly col * rows widgets must be added to
the grid.)
The children of this widget should have a fixed size that does not
vary based on the space allocated to them. Failure to observe this
restriction could lead to really odd layouts, or things being
rendered off screen.
Each cell of the grid is exactly the same size. By default, the
grid is the smallest size that can accommodate all of its
children, but it can be expanded to consume all available space in
a given dimension by setting xfill or yfill to True, as appropriate.
@param cols: The number of columns in this grid.
@param rows: The number of rows in this grid.
@param padding: The amount of space to leave between rows and columns.
@param xfill: True if the grid should consume all available width.
@param yfill: True if the grid should consume all available height.
"""
return add(renpy.display.layout.Grid(cols, rows, padding, xfill=xfill, yfill=yfill, **properties), True)
def fixed(**properties):
"""
This creates a layout that places widgets at fixed locations
@@ -204,14 +252,34 @@ def saybehavior():
"""
This is a psuedo-widget that adds the say behavior to the
screen. The say behavior is to return True if the left mouse is
clicked or enter is pressed. It also returns true in various other
cases if the current statement has already been seen. This widget
clicked or enter is pressed. It also returns True in various other
cases, such as if the current statement has already been seen. This widget
should not be added to any other widget, but should instead be
only added to the screen itself.
"""
return add(renpy.display.behavior.SayBehavior())
def pausebehavior(delay, result=False):
"""
This is a psuedo-widget that adds the pause behavior to the
screen. The pause behavior is to return the supplied result when
the given number of seconds elapses. This widget should not be
added to any other widget, but should instead be only added to the
screen itself.
Please note that this widget will always pause for the given
amount of time. If you want a pause that can be interrupted by
the user, add in a saybehavior.
@param delay: The amount of time to pause, in seconds.
@param result: The result that will be retuned after the delay time
elapses.
"""
return add(renpy.display.behavior.PauseBehavior(delay, result))
def menu(menuitems, **properties):
"""
This creates a new menu widget. Unlike the menu statement or
@@ -221,12 +289,30 @@ def menu(menuitems, **properties):
@param menuitems: A list of tuples that are the items to be added
to this menu. The first element of a tuple is a string that is
used for this menuitem. The second element is the value to be
returned from renpy.interact() if this item is selected, or None
returned from ui.interact() if this item is selected, or None
if this item is a non-selectable caption.
"""
return add(renpy.display.behavior.Menu(menuitems, **properties))
def input(default, length=None, allow=None, exclude='{}', **properties):
"""
This creats a new input widget. This widget accepts textual input
until the user hits enter, and then returns that text.
@param default: The default text that fills the input.
@param length: If set, the maximum number of characters that will be
returned by this input.
@param allow: If not None, then if an input character is not in this
string, it is ignored.
@param exclude: If not None, then if an input character is in this
set, it is ignored.
"""
return add(renpy.display.behavior.Input(default, length=length, allow=allow, exclude=exclude, **properties))
def image(filename, **properties):
"""
@@ -241,7 +327,7 @@ def imagemap(ground, selected, hotspots, unselected=None,
"""
This is the widget that implements imagemaps. Parameters are
roughtly the same as renpy.imagemap. The value of the hotspot is
returned when renpy.interact() returns.
returned when ui.interact() returns.
"""
return add(renpy.display.image.ImageMap(ground, selected,
@@ -254,7 +340,7 @@ def button(clicked=None, **properties):
This creates a button that can be clicked by the user. When this
button is clicked or otherwise selected, the function supplied as
the clicked argument is called. If it returns a value, that value
is returned from renpy.interact().
is returned from ui.interact().
Buttons created with this function contain another widget,
specifically the next widget to be added. As a convenience, one
@@ -272,7 +358,7 @@ def textbutton(text, clicked=None, text_style='button_text', **properties):
This creates a button that is labelled with some text. When the
button is clicked or otherwise selected, the function supplied as
the clicked argument is called. If it returns a value, that value
is returned from renpy.interact().
is returned from ui.interact().
@param text: The text of this button.
@@ -295,7 +381,7 @@ def imagebutton(idle_image, hover_image, clicked=None,
while the second is the hover image, which is used when the mouse
is over the image. If the button is clicked or otherwise selected,
then the clicked argument is called. If it returns a value, that
value is returned from renpy.interact().
value is returned from ui.interact().
@param idle_image: The file name of the image used when this
button is idle.
@@ -337,7 +423,7 @@ def bar(width, height, range, value, clicked=None, **properties):
widget. It is called with a single argument, which is the value
corresponding to the location at which the mouse button was clicked.
If this function returns a value, that value is returned from
renpy.interact().
ui.interact().
For best results, if clicked is set then width should be at least
twice as big as range.
+56 -10
View File
@@ -2,7 +2,8 @@
import os.path
# Go psyco! (Compile where we can.)
# Enable psyco. Warning: Check for memory leaks!
try:
if not os.path.exists("nopsyco"):
import psyco
@@ -24,12 +25,7 @@ import encodings.zlib_codec
# Load up all of Ren'Py, in the right order.
import renpy
if __name__ == "__main__":
# Stdout should be a utf-8 stream, so print works nicely.
# utf8writer = codecs.getwriter("utf-8")
# sys.stdout = utf8writer(sys.stdout)
def main():
name = os.path.basename(sys.argv[0])
@@ -51,6 +47,9 @@ if __name__ == "__main__":
op.add_option('--python', dest='python', default=None,
help='Run the argument in the python interpreter.')
op.add_option('--leak', dest='leak', action='store_true', default=False,
help='When the game exits, dumps a profile of memory usage.')
options, args = op.parse_args()
if options.python:
@@ -72,6 +71,16 @@ if __name__ == "__main__":
type, value, tb = sys.exc_info()
print >>f, type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print >>f
print >>f, renpy.game.exception_info
print >>f
print >>f, "-- Full Traceback ------------------------------------------------------------"
print >>f
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
@@ -96,8 +105,45 @@ if __name__ == "__main__":
os.startfile('traceback.txt')
except:
pass
sys.exit(0)
if options.leak:
memory_profile()
sys.exit(0)
def memory_profile():
print "Memory Profile"
print
print "Showing all objects in memory at program termination."
print
import gc
gc.collect()
objs = gc.get_objects()
c = { } # count
dead_renders = 0
for i in objs:
t = type(i)
c[t] = c.get(t, 0) + 1
if isinstance(i, renpy.display.render.Render):
if i.dead:
dead_renders += 1
results = [ (count, ty) for ty, count in c.iteritems() ]
results.sort()
for count, ty in results:
print count, str(ty)
if dead_renders:
print
print "*** found", dead_renders, "dead Renders. ***"
if __name__ == "__main__":
main()
+30 -8
View File
@@ -1,6 +1,8 @@
init:
$ config.window_title = "Test Game"
# $ config.profile = True
$ config.profile = True
$ config.debug_sound = True
# Set up the size of the screen.
$ config.screen_width = 800
@@ -20,8 +22,8 @@ init:
# Styles.
$ style.window.background = Frame("frame.png", 120, 25)
$ style.menu_choice.hover_sound = "chev1.wav"
$ style.menu_choice.activate_sound = "chev2.wav"
$ style.menu.hover_sound = "chev1.wav"
$ style.menu.activate_sound = "chev2.wav"
$ style.imagemap.hover_sound = "chev3.wav"
$ style.imagemap.activate_sound = "chev4.wav"
@@ -45,6 +47,8 @@ init:
"9a_vhappy.png", 0.25,
"9a_concerned.png")
image movie = Movie()
# Character objects.
$ e = Character('Eileen', color=(200, 255, 200, 255))
$ pov = DynamicCharacter('pov_name', color=(255, 0, 0, 255))
@@ -78,6 +82,7 @@ label main_menu:
# Stuff that happens before the real main menu.
scene black
show text "American Bishoujo\nPresents" \
at Move((0.5, 0.0), (0.5, 0.5), 4.0,
@@ -111,14 +116,16 @@ label start:
ui.interact()
$ the_value = 10
label foo:
"Hey, let's make sure that the
multi-line text\ \ \ still
works right."
$ renpy.movie_cutscene("On_Your_Mark.mpg", 450.0)
$ the_value = 10
label foo:
python hide:
def bar_clicked(value):
@@ -130,7 +137,7 @@ label start:
ui.textbutton("Continue", clicked = lambda : False)
ui.close()
rv = renpy.interact()
rv = ui.interact()
if rv is not False:
store.the_value = rv
@@ -139,6 +146,21 @@ label start:
$ renpy.movie_start_displayable('On_Your_Mark.mpg', (400, 300))
show movie
p "I'm now showing a movie."
p "It's using alpha blending and all that."
p "Bully!"
$ renpy.movie_stop()
hide movie
p "This is a test."
$ overtext = 'This is overtext.'