Compare commits

...

11 Commits

Author SHA1 Message Date
tom 06ea834b53 *** empty log message *** 2004-12-24 21:28:59 +00:00
tom a2b564214c *** empty log message *** 2004-12-24 16:41:52 +00:00
tom b3017fbdfe *** empty log message *** 2004-12-24 03:47:54 +00:00
tom 4070cc4b3d Implemented Bar, changed styles/hover. 2004-12-23 06:24:17 +00:00
tom ba3808c9a2 UTF-8 Fixes. 2004-12-21 21:18:28 +00:00
tom b30e291668 UTF-8 Fixes. 2004-12-21 20:58:00 +00:00
tom 209a7628d5 *** empty log message *** 2004-12-18 23:29:03 +00:00
tom 3cc214985d *** empty log message *** 2004-12-17 21:15:23 +00:00
tom 338809dcee *** empty log message *** 2004-12-14 19:40:09 +00:00
tom eee94002ba *** empty log message *** 2004-12-14 19:32:45 +00:00
tom 20949241f3 Added styles for the various preference and game menu nav buttons. 2004-12-11 21:11:12 +00:00
25 changed files with 1559 additions and 409 deletions
+118
View File
@@ -1,3 +1,121 @@
New in 4.4
----------
Added a number of features to allow Ren'Py to support statistics-based
dating simulations. The first is a new ui.bar() widget, which allows
the display of a bar graph. The demo has been enhanced to include a
stats and schedule screen, to show how this could be used. You can get
to it by asking how to write your own games in the demo, or you can
follow the below link to get a screenshot.
[url]http://www.bishoujo.us/hosted/screenshot.jpg[/url]
Another feature that has been added were 'jump expression' and 'call
expression' statements, allowing computed jumps and calls. Together
with an imporoved scheduler and yet-to-be written event dispatcher,
this provides the foundation for SBDSes.
Now, setting a property on a style also sets the hover_ and idle_
variants of that property. This makes changing a property of an
inherited style a bit saner. In addition, hover now propagates to all
children of a button. This means that hover will now work with the
styles in the file chooser.
Added support for layering images. This support (invoked by passing a
tuple as an image filename) allows a single image to be constructed
from multiple image files, saving disk space while still keeping
performance.
Improved skipping. Now, along with control causing skipping of seen
dialogue, TAB toggles skip mode. An indicator displays to let you know
that skip mode is enabled. Finally, if a game author wants to disable
skipping, he can set config.allow_skipping to false.
(The following were also in 4.3.2, which was a private release to
test the fixes for UTF-8 support.)
There were two bugs with UTF-8 support. The first was that we didn't
understand the byte-order mark, and choked on the syntax error. The
second was that the error-reporting code wasn't passing unicode errors
through properly, so one couldn't even see the real error. Both are
now fixed.
Improved the reporting of errors that occur during script loading and
interpreter initialization by no longer giving a line number when none
is appropriate.
Ren'Py now uses the name of the executable to choose the directory to
read the script from. It does this by looking at the name of the
executable that was used to run Ren'Py. It strips off the extension,
and anything preceding the first underscore in the name, if such a
thing exists. It then looks to see if that directory exists, and if it
does, uses it. For example, if the program is named "run_en.exe", the
"en" directory is used, while if the program is named "homestay.exe",
the directory "homestay" is used.
The config variable config.searchpath is a list of directory that are
searched for image files and other media (but not scripts, since all
scripts are loaded before the variable can be set). This allows
multiple game directories to share images, music, and other data files.
Once again, I redid the file chooser and default styles. The new file
chooser displays 10 entries in two columns, with each entry being
shown with an image. The files are now orginized into numbered slots,
and it's possible to save in a slot without saving in all previous
slots. There were also some changes to the non-user-visible parts of
loading and saving.
Speed Enhancements:
We now precompile python blocks in Ren'Py scripts, and store the
compiled code in the .rpyc files. This makes loading an unmodified
script significantly faster. On my system, this more than halved the
time it took for the demo script to load. On the dowside, when a
script is changed, it now can take somewhat longer for it to begin
running, as all the changed code needs to be recompiled. Overall,
it's a win, provided you ship the .rpyc files to your users.
Did another round of profiling, and found that styles were
significanly slowing the system down. So I rewrote them to be much
faster. This change shouldn't be user-visible, except that your game
will feel a bit peppier.
Added a 1-entry cache for solid fill surfaces. If your game uses them,
this might make it go faster.
New in 4.3.1
------------
New, but undocumented, in 4.3 was the console.exe windows binary. This
is a windows executable that opens a windows console. This could be
useful if one wants to have a script that prints debugging information
to standard output, as in:
[code]
$ a = 1
$ print "a =", a
[/code]
There is now a new style property, enable_hover. This must be set to
True for a widget to respond to hovering by changing its
style. However, if it's set to False, no style change/redraw occurs,
making Ren'Py snappier. It defaults to True.
Ren'Py is now built with psyco, the python specializing compiler. This
makes program startup a bit slower, but in return the running time
once it's loaded should be faster.
A new config variable, config.window_icon, allows the user to specify
an image file to use for the window icon. Counterintuitively, this
should probably be a PNG, as the ICO format is not supported by the
SDL_image library.
Game menu navigations and preference buttons now have their own
styles, allowing their look to be customized.
A bug that prevented the reloading of persistent data on windows has
been fixed.
New in 4.3
----------
+25 -6
View File
@@ -6,6 +6,7 @@
import sys
import os
import re
import glob
def find_labels(fn, labels):
@@ -17,6 +18,10 @@ def find_labels(fn, labels):
if m:
labels[m.group(1)] = True
m = re.match(r'^\s*call\s+expression.*from\s+(\w+)\s*$', l)
if m:
labels[m.group(1)] = True
f.close()
def replace_labels(fn, labels):
@@ -43,6 +48,22 @@ def replace_labels(fn, labels):
for l in f:
l = re.sub(r'^(\s*)call\s+(\w+)(\s*$)', replaceit, l)
if re.search(r'call\s+expression', l) and not re.search('from', l):
num = 0
while True:
num += 1
label = "_call_expression_%d" % num
if label not in labels:
break
labels[label] = label
l = l[:-1] + " from " + label + "\n"
of.write(l)
f.close()
@@ -59,15 +80,13 @@ def replace_labels(fn, labels):
def main():
gamedir = "game"
pattern = "*/*.rpy"
if len(sys.argv) >= 2:
gamedir = sys.argv[1]
pattern = sys.argv[1]
print "Processing files in", gamedir
files = [ gamedir + "/" + i for i in os.listdir(gamedir)
if i.endswith(".rpy") ]
files = glob.glob(pattern)
files = [ i for i in files if not i.startswith("common/") ]
labels = { }
+212 -81
View File
@@ -23,14 +23,17 @@ init -500:
# The number of files to show at once.
library.file_page_length = 10
# The number of pages to add quick access buttons for.
library.file_quick_access_pages = 5
# A small amount of padding.
library.padding = 2
# The width of a thumbnail.
library.thumbnail_width = 250
library.thumbnail_width = 66
# The height of a thumbnail.
library.thumbnail_height = 188
library.thumbnail_height = 50
# The contents of the main menu.
library.main_menu = [
@@ -41,22 +44,38 @@ init -500:
# The contents of the game menu choices.
library.game_menu = [
( "return", "Return to Game", "_return"),
( "load", "Load Game", "_load_screen" ),
( "save", "Save Game", "_save_screen" ),
( "prefs", "Preferences", "_prefs_screen" ),
( "mainmenu", "Main Menu", "_full_restart" ),
( "quit", "Quit Game", "_quit_screen" ),
( "return", "Return", "_return", 'True'),
( "prefs", "Preferences", "_prefs_screen", 'True' ),
( "save", "Save Game", "_save_screen", '_can_save' ),
( "load", "Load Game", "_load_screen", 'True'),
( "mainmenu", "Main Menu", "_full_restart", 'not _at_main_menu' ),
( "quit", "Quit", "_quit_screen", 'True' ),
]
# Used to translate strings in the library.
library.translations = { }
# Sound played when entering the library without clicking a
# button.
library.enter_sound = None
# Sound played when leaving the library without clicking a
# button.
library.exit_sound = None
# True if the skip indicator should be shown.
library.skip_indicator = True
# This is updated to give the user an idea of where a save is
# taking place.
save_name = ''
# True if we're at the main menu, false otherwise.
_at_main_menu = False
# True if we can save, false otherwise.
_can_save = True
# The function that's used to translate strings in the game menu.
def _(s):
"""
@@ -91,18 +110,42 @@ init -500:
# Set up the default keymap.
python hide:
def invoke_game_menu():
renpy.play(library.enter_sound)
renpy.call_in_new_context('_game_menu')
def toggle_skipping():
config.skipping = not config.skipping
# The default keymap.
km = renpy.Keymap(
rollback = renpy.rollback,
screenshot = _screenshot,
toggle_fullscreen = renpy.toggle_fullscreen,
toggle_music = renpy.toggle_music,
game_menu = renpy.curried_call_in_new_context("_game_menu"),
toggle_skip = toggle_skipping,
game_menu = invoke_game_menu,
hide_windows = _hide_windows,
)
config.underlay = [ km ]
# The skip indicator.
python hide:
def skip_indicator():
if config.allow_skipping and library.skip_indicator:
ui.conditional("config.skipping")
ui.text(_("Skip Mode"), style='skip_indicator')
return [ ]
config.overlay_functions.append(skip_indicator)
return
# This is the true starting point of the program. Sssh... Don't
@@ -122,7 +165,7 @@ label _main_menu:
jump main_menu
label _library_main_menu:
python hide:
ui.keymousebehavior()
@@ -141,7 +184,7 @@ label _library_main_menu:
store._result = renpy.interact(suppress_overlay = True,
suppress_underlay = True)
# Computed jump to the appropriate label.
$ renpy.jump(_result)
@@ -149,8 +192,14 @@ label _library_main_menu:
# Used to call the game menu.
label _continue:
$ _can_save = False
$ _at_main_menu = True
$ renpy.call_in_new_context("_load_menu")
$ _can_save = True
$ _at_main_menu = False
jump _library_main_menu
@@ -160,29 +209,45 @@ label _continue:
init -500:
python:
# 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
# This returns a window containing the game menu navigation
# buttons, set up to jump to the appropriate screen sections.
def _game_nav(selected):
ui.keymousebehavior()
ui.add(renpy.Keymap(game_menu=ui.jumps("_noisy_return")))
ui.window(style='gm_root_window')
ui.fixed()
ui.window(style='gm_nav_window')
ui.vbox()
for key, label, target in library.game_menu:
style="button"
text_style="button_text"
for key, label, target, enabled in library.game_menu:
style="gm_nav_button"
text_style="gm_nav_button_text"
if key == selected:
style = 'selected_button'
text_style = 'selected_button_text'
style = 'gm_nav_selected_button'
text_style = 'gm_nav_selected_button_text'
clicked = ui.jumps(target)
if not eval(enabled):
style = 'gm_nav_disabled_button'
text_style = 'gm_nav_disabled_button_text'
clicked = lambda : None
ui.textbutton(_(label), style=style, text_style=text_style,
clicked=ui.jumps(target))
clicked=clicked)
ui.close()
ui.close()
@@ -192,60 +257,85 @@ init -500:
return renpy.interact(suppress_underlay=True,
suppress_overlay=True)
_file_picker_index = 0
# This is used to render a single filename to the screen.
def _render_filename(index, filename, newest_filename):
if filename is None:
ui.text(_("Save in new slot."), style='file_picker_new_slot')
return
ui.hbox(padding=library.padding)
num = "% 2d." % index
def _render_new_slot(name, save):
if filename == newest_filename:
ui.text(num, style='file_picker_new')
if save:
clicked=ui.returns(("return", (name, False)))
enable_hover = True
else:
ui.text(num, style='file_picker_old')
clicked=None
enable_hover = False
# Do something better about this.
# ui.add(renpy.load_screenshot(filename))
ui.button(style='file_picker_entry',
clicked=clicked,
enable_hover=enable_hover)
ui.hbox(padding=library.padding)
ui.null(width=library.thumbnail_width,
height=library.thumbnail_height)
ui.text(name + ". ", style='file_picker_old')
ui.text(_("Empty Slot."), style='file_picker_empty_slot')
ui.close()
def _render_savefile(name, info, newest):
ui.text(renpy.load_extra_info(filename), style='file_picker_extra_info')
image, extra = info
ui.button(style='file_picker_entry',
clicked=ui.returns(("return", (name, True))))
ui.hbox(padding=library.padding)
ui.add(image)
if name == newest:
ui.text(name + ". ", style='file_picker_new')
else:
ui.text(name + ". ", style='file_picker_old')
ui.text(extra, style='file_picker_extra_info')
ui.close()
_scratch.file_picker_index = None
# This displays a file picker that can chose a save file from
# the list of save files.
def _file_picker(selected, files):
def _file_picker(selected, save):
saves, newest = renpy.saved_games()
nsg = renpy.newest_save_game()
# The index of the first entry in the page.
fpi = _scratch.file_picker_index
if fpi is None:
fpi = 0
if newest:
fpi = int(newest) // library.file_page_length * library.file_page_length
# The length of a half-page of files.
hfpl = library.file_page_length // 2
while True:
if _file_picker_index >= len(files):
store._file_picker_index -= library.file_page_length
if fpi < 0:
fpi = 0
if _file_picker_index < 0:
store._file_picker_index = 0
_scratch.file_picker_index = fpi
fpi = _file_picker_index
cur_files = files[fpi:fpi + library.file_page_length]
# Navigation
# Show Navigation
_game_nav(selected)
ui.window(style='file_picker_window')
ui.hbox()
ui.vbox()
ui.vbox() # whole thing.
ui.hbox(padding=library.padding * 10)
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
def tb(cond, label, clicked):
if cond:
@@ -258,38 +348,42 @@ init -500:
ui.textbutton(label, style=style, text_style=text_style, clicked=clicked)
tb(fpi > 0, _('Previous Page'), ui.returns(("fpidelta", -1)))
tb(fpi + library.file_page_length < len(files),
_('Next Page'), ui.returns(("fpidelta", +1)))
tb(fpi > 0, _('Previous'), ui.returns(("fpidelta", -1)))
ui.close() # hbox
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)))
for index, i in enumerate(cur_files):
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
def hover(i=i):
ui.reopen(image_win, True)
ui.close() # nav buttons.
if i:
ui.add(renpy.load_screenshot(i))
else:
ui.null()
ui.close()
def entry(offset):
i = fpi + offset
ui.button(style='file_picker_entry',
clicked=ui.returns(("return", i)),
hovered=hover)
name = str(i + 1)
_render_filename(index + fpi + 1, i, nsg)
if name not in saves:
_render_new_slot(name, save)
else:
_render_savefile(name, saves[name], newest)
ui.close() # vbox
ui.hbox() # slots
image_win = ui.window(style='file_picker_image')
ui.null()
ui.vbox()
for i in range(0, hfpl):
entry(i)
ui.close()
ui.close() # hbox
ui.vbox()
for i in range(hfpl, hfpl * 2):
entry(i)
ui.close()
ui.close() # slots
ui.close() # whole thing
result = _game_interact()
type, value = result
@@ -298,7 +392,11 @@ init -500:
return value
if type == "fpidelta":
store._file_picker_index += value * library.file_page_length
fpi += value * library.file_page_length
if type == "fpiset":
fpi = value
def _yesno_prompt(screen, message):
@@ -310,6 +408,22 @@ init -500:
return _game_interact()
def _show_exception(title, message):
ui.add(Solid((0, 0, 0, 255)))
ui.vbox()
ui.text(title, color=(255, 128, 128, 255))
ui.text("")
ui.text(message)
ui.text("")
ui.text("Please click to continue.")
ui.close()
ui.saybehavior()
ui.interact()
# Factored this all into one place, to make our lives a bit easier.
@@ -334,7 +448,7 @@ label _confirm_quit:
label _load_screen:
python:
_fn = _file_picker("load", renpy.saved_game_filenames() )
_fn, _exists = _file_picker("load", False )
python:
renpy.load(_fn)
@@ -342,19 +456,32 @@ label _load_screen:
jump _load_screen
label _save_screen:
$ _fn = _file_picker("save", renpy.saved_game_filenames() + [ None ] )
$ _fn, _exists = _file_picker("save", True)
if not _fn or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
if not _exists or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
python hide:
if save_name:
full_save_name = " - " + save_name
full_save_name = "\n" + save_name
else:
full_save_name = ""
renpy.save(_fn, renpy.time.strftime("%b %d, %H:%M") +
full_save_name)
try:
renpy.save(_fn, renpy.time.strftime("%b %d, %H:%M") +
full_save_name)
except Exception, e:
if config.debug:
raise
message = ( "The error message was:\n\n" +
e.__class__.__name__ + ": " + unicode(e) + "\n\n" +
"You may want to try saving in a different slot, or playing for a while and trying again later.")
_show_exception(_("Save Failed."), message)
jump _save_screen
# Asks the user if he wants to quit.
@@ -370,7 +497,11 @@ label _quit:
label _full_restart:
$ renpy.full_restart()
# Return to the game, after restoring the keymap.
# Make some noise, then return.
label _noisy_return:
$ renpy.play(library.exit_sound)
# Return to the game.
label _return:
return
+7 -7
View File
@@ -53,12 +53,12 @@ init -450:
for name, value in values:
style = 'button'
text_style = 'button_text'
style = 'prefs_button'
text_style = 'prefs_button_text'
if cur == value:
style = 'selected_button'
text_style = 'selected_button_text'
style = 'prefs_selected_button'
text_style = 'prefs_selected_button_text'
def clicked(value=value):
setattr(_preferences, self.field, value)
@@ -98,9 +98,9 @@ init -450:
library.left_preferences = [ p1, p2, p3 ]
p4 = _Preference('CTRL Skips', 'skip_unseen', [
('Seen Messages', False, None),
('All Messages', True, None),
p4 = _Preference('TAB and CTRL Skip', 'skip_unseen', [
('Seen Messages', False, 'config.allow_skipping'),
('All Messages', True, 'config.allow_skipping'),
])
p5 = _Preference('Transitions', 'transitions', [
+84 -16
View File
@@ -20,16 +20,25 @@ init -250:
style.create('default', None,
'The default style that all styles inherit from.')
dark_cyan = (0, 192, 255, 255)
bright_cyan = (0, 255, 255, 255)
dark_red = (255, 128, 128, 255)
bright_red = (255, 64, 64, 255)
green = (0, 128, 0, 255)
# Text properties.
style.default.font = "Vera.ttf"
style.default.antialias = True
style.default.size = 22
style.default.color = (255, 255, 255, 255)
style.default.drop_shadow = (2, 2)
style.default.drop_shadow = (1, 1)
style.default.drop_shadow_color = (0, 0, 0, 128)
style.default.minwidth = 0
style.default.textalign = 0
style.default.text_y_fudge = 0
style.default.enable_hover = True
# Change this if you're not using Vera 22.
if renpy.windows():
@@ -182,8 +191,9 @@ init -250:
style.button_text.xpos = 0.5
style.button_text.xanchor = 'center'
style.button_text.size = 24
style.button_text.color = (0, 255, 255, 255)
style.button_text.hover_color = (128, 255, 255, 255)
style.button_text.color = dark_cyan
style.button_text.hover_color = bright_cyan
style.button_text.drop_shadow = (2, 2)
# Selected button.
style.create('selected_button', 'button',
@@ -192,7 +202,8 @@ init -250:
style.create('selected_button_text', 'button_text',
'(text, hover) The style that is used for the label of a selected button.')
style.selected_button_text.color = (255, 255, 0, 255)
style.selected_button_text.color = dark_red
style.selected_button_text.hover_color = bright_red
# Disabled button.
@@ -206,6 +217,17 @@ init -250:
'(text, hover) The style that is used for the label of a disabled button.')
style.disabled_button_text.color = (128, 128, 128, 255)
style.disabled_button_text.hover_color = (128, 128, 128, 255)
# Bar.
style.create('bar', 'default',
'(bar) The style that is used by default for bars.')
style.bar.left_bar = Solid(bright_cyan)
style.bar.right_bar = Solid((0, 0, 0, 128))
style.bar.left_gutter = 0
style.bar.right_gutter = 0
# Styles that are used when laying out the main menu.
style.create('mm_root_window', 'default',
@@ -242,25 +264,52 @@ init -250:
style.gm_nav_window.ypos = 0.95
style.gm_nav_window.yanchor = 'bottom'
style.create('gm_nav_button', 'button',
'(window, hover) The style of an unselected game menu navigation button.')
style.create('gm_nav_button_text', 'button_text',
'(text, hover) The style of the text of an unselected game menu navigation button.')
style.create('gm_nav_selected_button', 'selected_button',
'(window, hover) The style of a selected game menu navigation button.')
style.create('gm_nav_selected_button_text', 'selected_button_text',
'(text, hover) The style of the text of a selected game menu navigation button.')
style.create('gm_nav_disabled_button', 'disabled_button',
'(window, hover) The style of a disabled game menu navigation button.')
style.create('gm_nav_disabled_button_text', 'disabled_button_text',
'(text, hover) The style of the text of a disabled game menu navigation button.')
style.create('file_picker_window', 'default',
'(window, position) A window containing the file picker that is used to choose slots for loading and saving.')
style.file_picker_window.xpos = 10
style.file_picker_window.xpos = 0
style.file_picker_window.xanchor = 'left'
style.file_picker_window.ypos = 10
style.file_picker_window.ypos = 0
style.file_picker_window.yanchor = 'top'
style.create('file_picker_image', 'default')
style.create('file_picker_navbox', 'default',
'(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_entry', 'button',
'(window, hover) The style that is used for each of the slots in the file picker.')
style.file_picker_entry.xpadding = 3
style.file_picker_entry.xminimum = 500
style.file_picker_entry.xpadding = 5
style.file_picker_entry.ypadding = 2
style.file_picker_entry.xmargin = 10
style.file_picker_entry.xminimum = 400
style.file_picker_entry.ymargin = 2
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
@@ -270,6 +319,8 @@ init -250:
'(text) A base style for all text that is displayed in the file picker.')
style.file_picker_text.size = 18
style.file_picker_text.idle_color = dark_cyan
style.file_picker_text.hover_color = bright_cyan
style.create('file_picker_new', 'file_picker_text',
'(text) The style that is applied to the new indicator in the file picker.')
@@ -277,19 +328,16 @@ init -250:
style.create('file_picker_old', 'file_picker_text',
'(text) The style that is applied to the old indicator in the file pciker.')
style.file_picker_new.color = (255, 192, 192, 255)
style.file_picker_old.color = (192, 192, 255, 255)
style.file_picker_new.hover_color = bright_red
style.file_picker_new.idle_color = dark_red
style.file_picker_new.minwidth = 30
style.file_picker_old.minwidth = 30
style.create('file_picker_extra_info', 'file_picker_text',
'(text) The style that is applied to extra info in the file picker. The extra info is the save time, and the save_name if one exists.')
style.file_picker_extra_info.color = (192, 192, 255, 255)
style.create('file_picker_new_slot', 'file_picker_text',
'(text) The style that is used for the new slot indicator in the file picker.')
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',
'(text, position) The style used for the prompt in a yes/no dialog.')
@@ -300,6 +348,8 @@ init -250:
style.yesno_prompt.ypos = 0.25
style.yesno_prompt.yanchor = 'center'
style.yesno_prompt.color = green
style.create('yesno_yes', 'button',
'(position) The position of the yes button on the screen.')
@@ -323,6 +373,7 @@ init -250:
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.')
@@ -346,3 +397,20 @@ init -250:
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.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.create('prefs_selected_button_text', 'selected_button_text',
'(text, hover) The style of the text of a selected preferences button.')
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
+158 -31
View File
@@ -12,6 +12,7 @@
# program.
init:
# Set up the size of the screen, and the window title.
$ config.screen_width = 800
$ config.screen_height = 600
@@ -23,34 +24,11 @@ init:
$ style.gm_root_window.background = Image("gamemenu.jpg")
$ style.window.background = Frame("frame.png", 125, 25)
# Change button styles.
$ style.button.background = Frame("button.png", 25, 10)
$ style.selected_button.background = Frame("button_checked.png", 25, 10)
$ del style.selected_button_text.color
$ style.button_text.drop_shadow = None
$ style.button_text.xpos = 28
$ style.button_text.xanchor = 'left'
$ style.button_text.ypos = 0.6
$ style.button_text.yanchor = 'center'
$ style.button_text.color = (255, 255, 255, 255)
$ style.button_text.hover_color = (255, 255, 0, 255)
$ style.button.xminimum = 250
# Change other styles.
$ style.prefs_label.color = (64, 64, 255, 255)
$ style.yesno_prompt.color = (64, 64, 255, 255)
$ style.file_picker_entry.xpos = 0
$ style.file_picker_entry.xanchor = 'left'
$ style.file_picker_entry.xminimum = 520
$ style.file_picker_text.xpos = 0
$ style.file_picker_text.ypos = 3
$ style.file_picker_text.drop_shadow = None
$ style.file_picker_entry.idle_background = Solid((0, 0, 192, 255))
$ style.file_picker_entry.hover_background = Solid((64, 64, 255, 255))
$ style.file_picker_old.color = (255, 255, 255, 255)
$ style.file_picker_extra_info.color = (255, 255, 255, 255)
$ style.file_picker_new_slot.color = (255, 255, 255, 255)
# Interface sounds, just for the heck of it.
$ style.button.activate_sound = 'click.wav'
$ style.imagemap.activate_sound = 'click.wav'
$ library.enter_sound = 'click.wav'
$ library.exit_sound = 'click.wav'
# These are positions that can be used inside at clauses. We set
# them up here so that they can be used throughout the program.
@@ -83,7 +61,6 @@ init:
# Character objects.
$ e = Character('Eileen', color=(200, 255, 200, 255))
# The splashscreen is called, if it exists, before the main menu is
# shown the first time. It is not called if the game has restarted.
@@ -97,7 +74,6 @@ init:
#
# return
# The start label marks the place where the main menu jumps to to
# begin the actual game.
@@ -112,6 +88,7 @@ label start:
# that we won the date.
$ date = False
# Start some music playing in the background.
$ renpy.music_start('sun-flower-slow-drag.mid')
@@ -328,6 +305,14 @@ label writing:
$ renpy.play("18005551212.wav")
e "... and sound effects, like the one that just played."
e "We now provide a series of user-interface functions, that allow
the programmer to create fairly complex interfaces."
e "For example, try the following scheduling and stats screen,
which could be used by a stat-based dating simulation."
$ day_planner()
e "Ren'Py also includes a number of control statements, and even
lets you include python code."
@@ -421,7 +406,10 @@ label after_rollback:
show eileen happy
e "Ren'Py gives you a few ways of skipping dialogue. Pressing
control quickly skips dialogue you've seen at least once, ever."
control quickly skips dialogue you've seen at least once."
e "Pressing Tab toggles the skipping of dialogue you've seen at
least once."
e "Pressing page down or scrolling the mouse wheel down will let
you skip dialogue you've seen this session. This is useful
@@ -609,3 +597,142 @@ label ending:
$ renpy.full_restart()
init:
# This is just some example code to show the ui functions in
# action. You probably want to delete this (and the call to
# day_planner above) from your game. This code isn't really all
# that useful except as an example.
python:
def day_planner():
periods = [ 'Morning', 'Afternoon', 'Evening' ]
choices = [ 'Study', 'Exercise',
'Eat', 'Drink', 'Be Merry' ]
plan = { 'Morning' : 'Eat',
'Afternoon' : 'Drink',
'Evening' : 'Be Merry' }
day = 'March 25th'
stats = [
('Strength', 100, 10),
('Intelligence', 100, 25),
('Moxie', 100, 100),
('Chutzpah', 100, 75),
]
editing = None
def button(text, selected, returns, **properties):
style = 'button'
style_text = 'button_text'
if selected:
style='selected_button'
style_text='selected_button_text'
ui.button(clicked=ui.returns(returns),
style=style, **properties)
ui.text(text, style=style_text)
while True:
# Stats Window
ui.window(xpos=0,
ypos=0,
xanchor='left',
yanchor='top',
xfill=True,
yminimum=200,
)
ui.vbox()
ui.text('Statistics')
ui.null(height=20)
for name, range, value in stats:
ui.hbox()
ui.text(name, minwidth=150)
ui.bar(600, 20, range, value, ypos=0.5, yanchor=center)
ui.close()
ui.close()
# Period Selection Window.
ui.window(xpos=0,
ypos=200,
xanchor='left',
yanchor='top',
xfill=False,
xminimum=300
)
ui.vbox(xpos=0.5, xanchor='center')
ui.text(day, xpos=0.5, xanchor='center', textalign=0.5)
ui.null(height=20)
for i in periods:
face = i + ": " + plan[i]
button(face, editing == i, ("edit", i))
ui.null(height=20)
ui.textbutton("Continue", clicked=ui.returns(("done", True)))
ui.null(height=20)
ui.close()
# Choice window.
if editing:
ui.window(xpos=300,
ypos=200,
xanchor='left',
yanchor='top',
xfill=False,
xminimum=500
)
ui.vbox()
ui.text("What will you do in the %s?" % editing.lower())
ui.null(height=20)
for i in choices:
button(i, plan[editing] == i, ("set", i),
xpos=0, xanchor='left')
ui.close()
# Window at the bottom.
ui.window()
ui.vbox()
ui.text("To get to the next screen, click the 'Continue' button.")
ui.close()
type, value = ui.interact()
if type == "done":
break
if type == "edit":
editing = value
if type == "set":
plan[editing] = value
editing = None
return plan
-1
View File
@@ -9,7 +9,6 @@ def match_times(source, dest):
def dosify(s):
return s.replace("\n", "\r\n")
return s
def copy_file(source, dest, license=""):
+137 -17
View File
@@ -1118,6 +1118,7 @@ e "How are you doing?" with dissolve
</p>
<rule>jump statement -> "jump" name</rule>
<rule>jump statement -> "jump" "expression" simple_expression</rule>
<p>
The jump statement unconditionally transfers control to the
@@ -1133,7 +1134,15 @@ e "Oh no! It looks like we're trapped in an infinite loop."
jump loop_start
</example>
<p>
A second form of the jump statement is invoked if the expression
keyword is in the statement. In this form, the supplied simple
expression is evaluated to get a string, and control is transferred to
the label given by that string.
</p>
<rule>call_statement -> "call" name ( "from" name )?</rule>
<rule>call_statement -> "call" "expression" simple_expression ( "from" name )?</rule>
<p>
The call statement transfers control to the location given. It
@@ -1150,13 +1159,21 @@ jump loop_start
return to the proper place when loaded on a changed script. On
the other hand, from clauses may be distracting when a game is
still under development. We provide with Ren'Py a program, called
"add_from", that adds from clauses to all bare calls in the game
"add_from", that adds from clauses to all bare calls in any game
directory. This program should be run before a final release of
your game is made. <b>Be sure to make a backup of your game
directory before running add_from.</b>
directories before running add_from.</b>
</p>
<p>
A second form of the call statement is invoked if the expression
keyword is in the statement. In this form, the supplied simple
expression is evaluated to get a string, and control is transferred to
the label given by that string, in a manner similar to a normal call.
</p>
<rule>return_statement -> "return"</rule>
<p>
@@ -1350,12 +1367,23 @@ python:
<h3>Starting a Game</h3>
<p>
When Ren'Py is first invoked, it first tries to parse all the
.rpy files in the game directory. If at least one .rpy file
exists, it is loaded, and the script is then written out in a
serialized form. If no .rpy files exist, but the serialized
script does exist, the serialized script is read back in from
disk.
When Ren'Py is first invoked, it first determines the name of the
game directory. It does this by looking at the name of the
executable file that is being run. It strips off the extension and
any prefix up to and including the first underscore. It looks to
see if what's left corresponds to a directory, and if so, that is
the game directory. Otherwise, the default game directory "game"
is used.
</p><p>
It then tries tries to parse all the .rpy files in the game
directory and common directory. If a .rpy file exists and is newer
than the corresponding .rpyc file, it is loaded, and the script is
then written out in a serialized form. If no .rpy files exist, but
the serialized script (.rpyc file) does exist, or if the
serialized script is newere than the corresponding .rpy file, the
serialized script is read back in from disk.
</p><p>
@@ -1938,15 +1966,29 @@ init:
the image cache change.
</var>
<var name="config.skip_delay" value="100">
<var name="config.allow_skipping" value="True">
If set to False, the user is not able to skip over the text of the
game.
</var>
<var name="config.skip_delay" value="75">
The amount of time that dialogue will be shown for, when
skipping statements using ctrl, in milliseconds.
skipping statements using ctrl, in milliseconds. (Although it's
nowhere near that precise in practice.)
</var>
<var name="config.archives" value="[ ]">
A list of archive files that will be searched for images.
</var>
<var name="config.searchpath" value="[ 'common', 'game' ]">
A list of directories that are searched for images, music, archives,
and other media, but not scripts. This is initialized to a list
containing "common" and the name of the game directory, which changes
depending on the name of the exe file. This variable is not used to
load scripts, as scripts will be loaded before it can be set.
</var>
<var name="config.mouse" value="None">
If this variable contains a string, that string is taken as an
image file that a color mouse cursor is loaded from. It's
@@ -2004,7 +2046,14 @@ init:
<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.
that's used by the load and save portions of the game menu. This
should probably be an even number.
</var>
<var name="library.file_quick_access_pages" value="5">
The number of pages of the file picker to provide quick access
to. Quick access is provided by number buttons which are at the top of
the file picker.
</var>
<var name="library.thumbnail_width" value="100">
@@ -2052,6 +2101,24 @@ init:
statement or clause).
</var>
<var name="library.enter_sound" value="None">
If not None, this is a sound file that is played when entering
the game menu without clicking a button. (For example, when
right-clicking during the game.)
</var>
<var name="library.exit_sound" value="None">
If not None, this is a sound file that is played when exiting
the game menu without clicking a button. (For example, when
right-clicking inside the game menu.)
</var>
<var name="library.skip_indicator" value="True">
If True, the library will display a skip indicator when skipping
through the script.
</var>
</dl>
<h3>Properties and Styles</h3>
@@ -2305,10 +2372,44 @@ init:
otherwise selecting it.
</prop>
<h4>Bar Properties</h4>
<p>
The ui.bar() widget has a few properties that are specific to
it. These properties control the look of the bars, and the size of
the gutters on either side of a clickable widget. The gutters are
empty space on the left and right of a clickable bar, and serve to
make it easy to set the maximum and minimum value of the bar
widget. Clicking on the left gutter sets the minimum value, while the
right gutter sets the maximum value.
</p>
<prop name="left_bar">
A Displayable that is used to draw the left side of the bar. This
Displayable needs to be one that can change its size, such as a Solid
or a Frame.
</prop>
<prop name="right_bar">
A Displayable that is used to draw the right side of the bar. This
Displayable needs to be one that can change its size, such as a Solid
or a Frame.
</prop>
<prop name="left_gutter">
The size of the left gutter of a clickable bar, in pixels.
</prop>
<prop name="right_gutter">
The size of the left gutter of a clickable bar, in pixels.
</prop>
<h4>Hovering</h4>
<p>
In Ren'Py, buttons and their labels support the idea of hovering:
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
is hovered, else it is idle. On these widgets, Ren'Py will look up
@@ -2331,7 +2432,13 @@ init:
style.button.idle_background = Solid((0, 0, 0, 255))
</example>
<prop name="enable_hover">
This property must be set to True (the default) on the style of a
hoverable widget (like a button) for hovering to be
enabled. Setting it to False speeds up Ren'Py, at the cost of
making the widget not respond to hovering.
</prop>
<h4>Using Properties and Styles</h4>
<p>
@@ -2579,7 +2686,6 @@ keymap = dict(
# Say.
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
skip = [ 'K_LCTRL', 'K_RCTRL' ],
# Keymouse.
keymouse_left = [ 'K_LEFT' ],
@@ -2603,9 +2709,12 @@ keymap = dict(
# Imagemap.
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
# This isn't a binding, but instead a list of keys that should
# repeat when held down.
repeating = [ 'K_LCTRL', 'K_RCTRL' ],
# Bar.
bar_click = [ 'mouse_1' ],
# These keys control skipping.
skip = [ 'K_LCTRL', 'K_RCTRL' ],
toggle_skip = [ 'K_TAB' ],
)
</example>
@@ -2675,6 +2784,15 @@ statement. The ui functions must be called again if we want to show
the same user interface to the user again.
</p>
<p>
It's important to ensure that, if a lambda or nested function is used
as an argument to one of these ui functions, ui.interact() is called
in the same python block. Failure to do this will prevent your game
from saving, and that's probably a bad thing. ui.returns doesn't
suffer from this problem, and so should probably be used in preference
to lambda.
</p>
<p>
The following are the functions available in the ui module, which is
automatically present in the game namespace.
@@ -2708,6 +2826,8 @@ automatically present in the game namespace.
<!-- func ui.menu -->
<!-- func ui.bar -->
<!-- func ui.saybehavior -->
<!-- func ui.keymousebehavior -->
+6
View File
@@ -1,6 +1,12 @@
# This file ensures that renpy packages will be imported in the right
# order.
# Some version numbers and things.
version = "Ren'Py 4.4 \"Christmas Bonus\""
script_version = 5
savegame_suffix = "-2.save"
# Can be first, because has no dependencies, and may be imported
# directly.
import renpy.game
+33 -9
View File
@@ -209,12 +209,17 @@ class Python(Node):
super(Python, self).__init__(loc)
self.python_code = python_code
self.hide = hide
old_ei = renpy.game.exception_info
renpy.game.exception_info = "While compiling python block starting at line %d of %s." % (self.linenumber, self.filename)
self.bytecode = renpy.python.py_compile_exec_bytecode(python_code)
renpy.game.exception_info = old_ei
def execute(self):
renpy.python.py_exec(self.python_code, self.hide)
renpy.python.py_exec_bytecode(self.bytecode, self.hide)
return self.next
@@ -414,16 +419,25 @@ class With(Node):
class Call(Node):
def __init__(self, loc, label):
def __init__(self, loc, label, expression):
super(Call, self).__init__(loc)
self.label = label
self.expression = expression
def execute(self):
return renpy.game.context().call(self.label, return_site=self.next.name)
label = self.label
if self.expression:
label = renpy.python.py_eval(label)
return renpy.game.context().call(label, return_site=self.next.name)
def predict(self, callback):
return [ renpy.game.script.lookup(self.label) ]
if self.expression:
return [ ]
else:
return [ renpy.game.script.lookup(self.label) ]
class Return(Node):
@@ -503,20 +517,30 @@ class Menu(Node):
# instead.
class Jump(Node):
def __init__(self, loc, target):
def __init__(self, loc, target, expression):
super(Jump, self).__init__(loc)
self.target = target
self.expression = expression
# We don't care what our next node is.
def chain(self, next):
return
def execute(self):
return renpy.game.script.lookup(self.target)
target = self.target
if self.expression:
target = renpy.python.py_eval(target)
return renpy.game.script.lookup(target)
def predict(self, callback):
return [ renpy.game.script.lookup(self.target) ]
if self.expression:
return [ ]
else:
return [ renpy.game.script.lookup(self.target) ]
# GNDN
class Pass(Node):
+34 -8
View File
@@ -2,11 +2,12 @@
# This includes both simple settings (like the screen dimensions) and
# methods that perform standard tasks, like the say and menu methods.
import renpy.display
# The title of the game window.
window_title = "A Ren'Py Game"
# An image file containing the window icon image.
window_icon = None
# The width and height of the drawable area of the screen.
screen_width = 800
screen_height = 600
@@ -60,12 +61,21 @@ predict_statements = 10
# it changes.
debug_image_cache = False
# Should we allow skipping at all?
allow_skipping = True
# Are we currently skipping?
skipping = False
# The delay while we are skipping say statements.
skip_delay = 100
skip_delay = 75
# Archive files that are searched for images.
archives = [ ]
# Searchpath.
searchpath = [ ]
# If True, we will only try loading from archives.
# Only useful for debugging Ren'Py, don't document.
force_archives = False
@@ -103,7 +113,6 @@ keymap = dict(
# Say.
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
skip = [ 'K_LCTRL', 'K_RCTRL' ],
# Keymouse.
keymouse_left = [ 'K_LEFT' ],
@@ -127,12 +136,29 @@ keymap = dict(
# Imagemap.
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
# This isn't a binding, but instead a list of keys that should
# repeat when held down.
repeating = [ 'K_LCTRL', 'K_RCTRL' ],
# Bar.
bar_click = [ 'mouse_1' ],
# These keys control skipping.
skip = [ 'K_LCTRL', 'K_RCTRL' ],
toggle_skip = [ 'K_TAB' ],
)
_globals = globals().copy()
def backup():
import copy
global _globals
_globals = globals().copy()
del _globals["backup"]
del _globals["reload"]
del _globals["__builtins__"]
_globals = copy.deepcopy(_globals)
def reload():
globals().update(_globals)
+198 -29
View File
@@ -36,6 +36,18 @@ def map_event(ev, name):
return False
return False
def map_keyup(ev, name):
keys = renpy.config.keymap[name]
if ev.type == KEYUP:
for key in keys:
if ev.key == getattr(pygame.constants, key, None):
return True
return False
def is_pressed(pressed, name):
"""
@@ -53,6 +65,19 @@ def is_pressed(pressed, name):
return False
def skipping(ev):
"""
This handles setting skipping in response to the press of one of the
CONTROL keys. The library handles skipping in response to TAB.
"""
if map_event(ev, "skip"):
renpy.config.skipping = True
if map_keyup(ev, "skip"):
renpy.config.skipping = False
return
class Keymap(renpy.display.layout.Container):
"""
@@ -120,10 +145,19 @@ class SayBehavior(renpy.display.layout.Null):
def event(self, ev, x, y):
if ev.type == renpy.display.core.DISPLAYTIME and \
self.delay and \
ev.duration > self.delay:
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:
if renpy.game.preferences.skip_unseen:
return True
elif renpy.game.context().seen_current(True):
return True
if map_event(ev, "dismiss"):
return True
@@ -131,12 +165,6 @@ class SayBehavior(renpy.display.layout.Null):
if renpy.game.context().seen_current(False):
return True
if map_event(ev, "skip"):
if renpy.game.preferences.skip_unseen:
return True
elif renpy.game.context().seen_current(True):
return True
return None
class Menu(renpy.display.layout.VBox):
@@ -273,16 +301,26 @@ class Button(renpy.display.layout.Window):
self.clicked = clicked
self.hovered = hovered
def render(self, width, height, st):
if self.old_hover:
self.set_style_prefix('hover_')
else:
self.set_style_prefix('idle_')
return super(Button, self).render(width, height, st)
def set_hover(self, hover):
"""
Called when we change from hovered to un-hovered, or
vice-versa.
"""
if hover:
self.style.set_prefix('hover_')
else:
self.style.set_prefix('idle_')
# if hover:
# self.style.set_prefix('hover_')
# else:
# self.style.set_prefix('idle_')
renpy.game.interface.redraw(0)
@@ -295,7 +333,7 @@ class Button(renpy.display.layout.Window):
if x >= 0 and x < width and y >= 0 and y < height:
inside = True
if ev.type == MOUSEMOTION:
if self.style.enable_hover and ev.type == MOUSEMOTION:
if self.old_hover != inside:
self.old_hover = inside
@@ -309,33 +347,42 @@ class Button(renpy.display.layout.Window):
if map_event(ev, "button_select"):
if inside:
if inside and self.clicked:
renpy.sound.play(self.style.activate_sound)
return self.clicked()
return None
return super(Button, self).event(ev, x, y)
# Reimplementation of the TextButton widget as a Button and a Text
# widget.
def TextButton(text, style='button', text_style='button_text',
clicked=None):
text = renpy.display.text.Text(text, style=text_style)
return Button(text, style=style, clicked=clicked)
class TextButton(Button):
def __init__(self, text, style='button', text_style='button_text',
clicked=None):
# class TextButton(Button):
self.text_widget = renpy.display.text.Text(text, style=text_style)
# def __init__(self, text, style='button', text_style='button_text',
# clicked=None):
super(TextButton, self).__init__(self.text_widget,
style=style,
clicked=clicked)
# self.text_widget = renpy.display.text.Text(text, style=text_style)
self.text_widget.style.set_prefix('idle_')
# super(TextButton, self).__init__(self.text_widget,
# style=style,
# clicked=clicked)
# self.text_widget.style.set_prefix('idle_')
def set_hover(self, hover):
super(TextButton, self).set_hover(hover)
# def set_hover(self, hover):
# super(TextButton, self).set_hover(hover)
if hover:
self.text_widget.style.set_prefix("hover_")
else:
self.text_widget.style.set_prefix("idle_")
# if hover:
# self.text_widget.style.set_prefix("hover_")
# else:
# self.text_widget.style.set_prefix("idle_")
class Input(renpy.display.text.Text):
@@ -374,3 +421,125 @@ class Input(renpy.display.text.Text):
self.set_text(self.content + "_")
renpy.game.interface.redraw(0)
class Bar(renpy.display.core.Displayable):
"""
Implements a bar that can display an integer value, and respond
to clicks on that value.
"""
def __init__(self, width, height, range, value, clicked=None,
style='bar', **properties):
super(Bar, self).__init__()
self.style = renpy.style.Style(style, properties)
self.width = width
self.height = height
self.range = range
self.value = value
self.clicked = clicked
def event(self, ev, x, y):
if not self.clicked:
return
if not map_event(ev, 'bar_click'):
return
if not (0 <= x < self.width and 0 <= y <= self.height):
return
print x, y
lgutter = self.style.left_gutter
rgutter = self.style.right_gutter
if x < lgutter:
value = 0
elif x > self.width - rgutter:
value = self.range
else:
barwidth = self.width - lgutter - rgutter
# This makes it easier to select 100%.
x = x - lgutter
x = x + (barwidth / self.range // 2)
value = x * self.range / barwidth
value = max(value, 0)
return self.clicked(value)
def render(self, width, height, st):
width = self.width
height = self.height
# The amount of space taken up by the bars.
if self.clicked:
lgutter = self.style.left_gutter
rgutter = self.style.right_gutter
else:
lgutter = 0
rgutter = 0
barwidth = width - lgutter - rgutter
left_width = barwidth * self.value // self.range
right_width = barwidth - left_width
rv = renpy.display.surface.Surface(width, height)
lsurf = self.style.left_bar.render(left_width, height, st)
rsurf = self.style.right_bar.render(right_width, height, st)
rv.blit(lsurf, (lgutter, 0))
rv.blit(rsurf, (lgutter + left_width, 0))
return rv
class Conditional(renpy.display.layout.Container):
"""
This class renders its child if and only if the condition is
true. Otherwise, it renders nothing. (Well, a Null).
Warning: the condition MUST NOT update the game state in any
way, as that would break rollback.
"""
def __init__(self, condition, *args):
super(Conditional, self).__init__(*args)
self.condition = condition
self.null = renpy.display.layout.Null()
self.state = eval(self.condition, renpy.game.store)
def render(self, width, height, st):
if self.state:
return self.child.render(width, height, st)
else:
return self.null.render(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)
self.state = state
if state:
return self.child.event(ev, x, y)
+126 -86
View File
@@ -8,13 +8,9 @@ from pygame.constants import *
import time
import cStringIO
KEYREPEATEVENT = USEREVENT + 1
# KEYREPEATEVENT = USEREVENT + 1
DISPLAYTIME = USEREVENT + 2
# A list of keys that we allow to repeat.
repeating_keys = [ K_LCTRL, K_RCTRL ]
class IgnoreEvent(Exception):
"""
Exception that is raised when we want to ignore an event, but
@@ -33,6 +29,18 @@ class Displayable(renpy.object.Object):
their fields.
"""
def __init__(self):
self.style = None
def set_style_prefix(self, prefix):
"""
Called to set the style prefix of this widget and its child
widgets, if any.
"""
if self.style:
self.style.set_prefix(prefix)
def parameterize(self, name, parameters):
"""
Called to parameterize this. By default, we don't take any
@@ -372,9 +380,13 @@ class Display(object):
pygame.event.set_grab(False)
# Window title.
# Window title and icon.
pygame.display.set_caption(renpy.config.window_title)
if renpy.config.window_icon:
pygame.display.set_icon(renpy.display.image.cache.load_image(renpy.config.window_icon))
# Load the mouse image, if any.
if renpy.config.mouse:
self.mouse = renpy.display.image.cache.load_image(renpy.config.mouse)
@@ -434,6 +446,8 @@ class Display(object):
renpy.config.screen_width,
renpy.config.screen_height)
# self.window.set_clip((0, 0, 800, 300))
surftree.blit_to(self.window, 0, 0)
if self.mouse:
@@ -442,8 +456,6 @@ class Display(object):
self.mouse_location = None
pygame.display.flip()
return rv
@@ -567,6 +579,14 @@ class Interface(object):
self.transition = transition
def event_wait(self):
"""
This is in its own function so that we can track in the
profiler how much time is spent in interact.
"""
return pygame.event.wait()
def interact(self, transient=None, show_mouse=True,
trans_pause=False,
suppress_overlay=False,
@@ -606,10 +626,15 @@ class Interface(object):
renpy.game.context().predict(renpy.display.image.cache.preload_image)
# Set up key repeats.
pygame.time.set_timer(KEYREPEATEVENT, renpy.config.skip_delay)
# pygame.time.set_timer(KEYREPEATEVENT, renpy.config.skip_delay)
# Set up display time event.
pygame.time.set_timer(DISPLAYTIME, 50)
# Clear some events.
pygame.event.clear((MOUSEMOTION, DISPLAYTIME,
MOUSEBUTTONUP, MOUSEBUTTONDOWN))
# Figure out the scene list we want to show.
start_time = time.time()
@@ -680,106 +705,121 @@ class Interface(object):
rv = None
while rv is None:
# This try block is used to force cleanup even on termination
# caused by an exception propigating through this function.
try:
if self.display.fullscreen != renpy.game.preferences.fullscreen:
self.display = Display()
self.needs_redraw = True
while rv is None:
if self.needs_redraw:
self.needs_redraw = False
if self.display.fullscreen != renpy.game.preferences.fullscreen:
self.display = Display()
self.needs_redraw = True
draw_start = time.time()
self.redraw_time = draw_start + 365.25 * 86400.0
if self.needs_redraw:
self.needs_redraw = False
offsets = self.display.show(display_list)
offsets.reverse()
draw_start = time.time()
self.redraw_time = draw_start + 365.25 * 86400.0
# 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)
offsets = self.display.show(display_list)
offsets.reverse()
# Draw the mouse, if it needs drawing.
if show_mouse:
self.display.draw_mouse()
# 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)
# Update the playing music, if necessary.
renpy.music.restore()
# Draw the mouse, if it needs drawing.
if show_mouse:
self.display.draw_mouse()
# 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():
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()
# Update the playing music, if necessary.
renpy.music.restore()
try:
ev = pygame.event.wait()
self.profile_time = time.time()
# 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():
continue
if ev.type == DISPLAYTIME:
pygame.event.clear([DISPLAYTIME])
ev = pygame.event.Event(DISPLAYTIME, {},
duration=(time.time() - start_time))
# 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()
if ev.type == KEYREPEATEVENT:
pygame.time.set_timer(KEYREPEATEVENT, 0)
try:
# ev = pygame.event.wait()
ev = self.event_wait()
self.profile_time = time.time()
i = renpy.display.behavior.is_pressed(pygame.key.get_pressed(),
"repeating")
if ev.type == DISPLAYTIME:
pygame.event.clear([DISPLAYTIME])
ev = pygame.event.Event(DISPLAYTIME, {},
duration=(time.time() - start_time))
if i:
ev = pygame.event.Event(KEYDOWN, key=i, unicode=u'')
# Handle quit specially for now.
if ev.type == QUIT:
if renpy.game.script.has_label("_confirm_quit") and not self.quick_quit:
self.quick_quit = True
renpy.game.call_in_new_context("_confirm_quit")
self.quick_quit = False
else:
raise renpy.game.QuitException()
# if ev.type == KEYREPEATEVENT:
# pygame.time.set_timer(KEYREPEATEVENT, 0)
# Merge mousemotion events.
if ev.type == MOUSEMOTION:
evs = pygame.event.get([MOUSEMOTION])
if len(evs):
ev = evs[-1]
# i = renpy.display.behavior.is_pressed(pygame.key.get_pressed(),
# "repeating")
# x, y = getattr(ev, 'pos', (0, 0))
# if i:
# ev = pygame.event.Event(KEYDOWN, key=i, unicode=u'')
x, y = pygame.mouse.get_pos()
# Handle skipping.
renpy.display.behavior.skipping(ev)
for (k, t, d), (xo, yo) in zip(event_list, offsets):
rv = d.event(ev, x - xo, y - yo)
# Handle quit specially for now.
if ev.type == QUIT:
if renpy.game.script.has_label("_confirm_quit") and not self.quick_quit:
self.quick_quit = True
renpy.game.call_in_new_context("_confirm_quit")
self.quick_quit = False
else:
raise renpy.game.QuitException()
if rv is not None:
break
# Merge mousemotion events.
if ev.type == MOUSEMOTION:
evs = pygame.event.get([MOUSEMOTION])
except IgnoreEvent:
pass
if len(evs):
ev = evs[-1]
if time.time() > self.redraw_time:
self.needs_redraw = True
pygame.time.set_timer(KEYREPEATEVENT, 0)
pygame.event.clear()
scene_lists.replace_transient()
# x, y = getattr(ev, 'pos', (0, 0))
# Clear up the transitions.
self.old_scene = current_scene
self.transition = None
self.supress_transition = False
x, y = pygame.mouse.get_pos()
# Redraw the old scene, if any.
self.redraw(0)
for (k, t, d), (xo, yo) in zip(event_list, offsets):
rv = d.event(ev, x - xo, y - yo)
if rv is not None:
break
except IgnoreEvent:
pass
if time.time() > self.redraw_time:
self.needs_redraw = True
# But wait, there's more! The finally block runs some cleanup
# after this.
return 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
# Redraw the old scene, if any.
self.redraw(0)
return rv
+42 -4
View File
@@ -22,6 +22,38 @@ class ImageCache(object):
def tick(self):
self.time += 1
self.preloads = [ ]
def really_load_image(self, fn):
"""
This is called by load_image, and does the actual loading of
images. This may be a load of an image, or perhaps the
compositing of images if fn is a tuple.
"""
# If fn is not a single filename but a tuple, we composite the
# elements of the tuple.
if isinstance(fn, tuple):
if not tuple:
raise Exception("Trying to create a composite image from an empty tuple.")
base = self.load_image(fn[0])
rv = pygame.Surface(base.get_size(), 0,
renpy.game.interface.display.sample_surface)
rv.blit(base, (0, 0))
for i in fn[1:]:
layer = self.load_image(i)
rv.blit(layer, (0, 0))
return rv
im = pygame.image.load(renpy.loader.load(fn), fn)
im = im.convert_alpha()
return im
# Forces an image load, regardless of if the cache is full or not.
def load_image(self, fn):
@@ -33,14 +65,13 @@ class ImageCache(object):
if fn in self.preloads:
self.preloads.remove(fn)
im = pygame.image.load(renpy.loader.load(fn), fn)
im = im.convert_alpha()
# iw, ih = im.get_size()
# surf = renpy.display.surface.Surface(iw, ih)
# surf.blit(im, (0, 0))
im = self.really_load_image(fn)
self.surface_map[fn] = im
if renpy.config.debug_image_cache:
@@ -136,7 +167,14 @@ class Image(renpy.display.core.Displayable):
def __init__(self, filename, style='image_placement', **properties):
"""
@param filename: The filename that the image is loaded from. Many common file formats are supported.
@param filename: The filename that the image is loaded
from. Many common file formats are supported.
If the filename is not a single string but instead a tuple of
strings, the image is considered to be"layered". In this case,
the image will be the size of the first image in the tuple, and
other images will be aligned with the upper-left corner of the
image.
"""
self.filename = filename
+16 -1
View File
@@ -13,8 +13,15 @@ class Null(renpy.display.core.Displayable):
but don't want to actually have anything there.
"""
def __init__(self, width=0, height=0, style='default', **properties):
super(Null, self).__init__()
self.style = renpy.style.Style(style, properties)
self.width = width
self.height = height
def render(self, width, height, st):
return renpy.display.surface.Surface(1, 1)
return renpy.display.surface.Surface(self.width, self.height)
class Container(renpy.display.core.Displayable):
@@ -38,6 +45,8 @@ class Container(renpy.display.core.Displayable):
"""
def __init__(self, *args):
super(Container, self).__init__()
self.children = []
self.child = None
@@ -45,6 +54,12 @@ class Container(renpy.display.core.Displayable):
for i in args:
self.add(i)
def set_style_prefix(self, prefix):
super(Container, self).set_style_prefix(prefix)
for i in self.children:
i.set_style_prefix(prefix)
def add(self, child):
"""
Adds a child to this container.
+34 -4
View File
@@ -21,8 +21,36 @@ import renpy
# self.color = color
# def blit_to(self, dest, x, y):
# dest.set_alpha(0)
# dest.fill(self.color, [ x, y, self.width, self.height ])
# 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
surf = pygame.Surface(size, 0,
renpy.game.interface.display.sample_surface)
surf.fill(color)
self.cached = surf
return surf
solid_cache = SolidCache()
class Surface(object):
"""
This is our own surface object, which is a node in a tree in which
@@ -66,11 +94,13 @@ class Surface(object):
"""
Fake a pygame.Surface.fill()
"""
surf = pygame.Surface((self.width, self.height), 0,
renpy.game.interface.display.sample_surface)
surf.fill(color)
surf = solid_cache.create((self.width, self.height), color)
# surf = pygame.Surface((self.width, self.height), 0,
# renpy.game.interface.display.sample_surface)
# surf.fill(color)
# surf = FilledSurface(self.width, self.height, color)
+1 -1
View File
@@ -63,7 +63,7 @@ def transfn(name):
searched directories.
"""
for d in renpy.game.searchpath:
for d in renpy.config.searchpath:
if os.path.exists(d + "/" + name):
return d + "/" + name
+106 -49
View File
@@ -2,11 +2,63 @@
from cPickle import dumps, loads, HIGHEST_PROTOCOL
import cStringIO
import renpy
import zipfile
import time
import os
import renpy
# This is used as a quick and dirty way of versioning savegame
# files.
savegame_suffix = renpy.savegame_suffix
def debug_dump(prefix, o, seen):
if id(o) in seen:
print prefix, id(o)
return
seen[id(o)] = True
if isinstance(o, tuple):
print prefix, "("
for i in o:
debug_dump(prefix + " ", i, seen)
print prefix, ")"
elif isinstance(o, list):
print prefix, "["
for i in o:
debug_dump(prefix + " ", i, seen)
print prefix, "]"
elif isinstance(o, dict):
print prefix, "{"
for k, v in o.iteritems():
print prefix, repr(k), "="
debug_dump(prefix + " ", v, seen)
print prefix, "}"
elif hasattr(o, "__dict__"):
ignored = getattr(o, "nosave", [ ])
print prefix, repr(o), "{{"
for k, v in vars(o).iteritems():
if k in ignored:
continue
print prefix, repr(k), "="
debug_dump(prefix + " ", v, seen)
print prefix, "}}"
else:
print prefix, repr(o)
def save(filename, extra_info=''):
"""
Saves the game in the given filename. This will save the game
@@ -15,84 +67,89 @@ def save(filename, extra_info=''):
It's expected that a screenshot will be taken (with
renpy.take_screenshot) before this is called.
If the filename is None, one is automatically generated based
on the current time.
"""
if filename == None:
filename = str(time.time()) + ".save"
filename = filename + savegame_suffix
try:
os.unlink(renpy.config.savedir + "/" + filename)
except:
pass
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename,
"w", zipfile.ZIP_DEFLATED)
# Screenshot.
zf.writestr("screenshot.tga", renpy.game.interface.get_screenshot())
# Extra info.
zf.writestr("extra_info", extra_info)
# The actual game.
renpy.game.log.freeze()
zf.writestr("log", dumps(renpy.game.log, HIGHEST_PROTOCOL))
renpy.game.log.discard_freeze()
zf.close()
def saved_game_filenames():
try:
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename,
"w", zipfile.ZIP_DEFLATED)
# Screenshot.
zf.writestr("screenshot.tga", renpy.game.interface.get_screenshot())
# Extra info.
zf.writestr("extra_info", extra_info)
# print
# print "Debug Dump!"
# debug_dump("", renpy.game.log, { })
# The actual game.
zf.writestr("log", dumps(renpy.game.log, HIGHEST_PROTOCOL))
zf.close()
finally:
renpy.game.log.discard_freeze()
def saved_games():
"""
Returns a list of savegame files.
This scans the savegames that we know about and returns
information about them. Specifically, it returns tuple containing
a savelist and the filename of the newest save file (or None if no
save file exists).
The savelist, in turn, is a list of tuples, with each tuple containing
the filename of the saved game, a Displayable containing a screenshot,
and a string giving the extra data of that save.
"""
files = os.listdir(renpy.config.savedir)
files.sort()
return [ i for i in files if i.endswith(".save") ]
def newest_save_game():
"""
Returns the name of the newest savegame file.
"""
files = os.listdir(renpy.config.savedir)
files = [ i for i in files if i.endswith(".save") ]
files = [ i for i in files if i.endswith(savegame_suffix) ]
if not files:
return None
newest = None
else:
datefiles = [ (os.stat(renpy.config.savedir + "/" + i).st_mtime, i) for i in files ]
datefiles.sort()
newest = datefiles[-1][1]
newest = newest[:-len(savegame_suffix)]
datefiles = [ (os.stat(renpy.config.savedir + "/" + i).st_mtime, i) for i in files ]
datefiles.sort()
saveinfo = { }
return datefiles[-1][1]
for f in files:
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
extra_info = zf.read("extra_info")
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
zf.close()
def load_extra_info(filename):
"""
Returns the extra_info string that was saved in a savegame file.
"""
screenshot = renpy.display.image.UncachedImage(sio, "screenshot.tga", False)
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename, "r")
rv = zf.read("extra_info")
zf.close()
f = f[:-len(savegame_suffix)]
return rv
saveinfo[f] = screenshot, extra_info
def load_screenshot(filename, scale=None):
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename, "r")
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
zf.close()
return renpy.display.image.UncachedImage(sio, "screenshot.tga", scale)
return saveinfo, newest
def load(filename):
"""
Loads the game from the given file. This function never returns.
"""
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename, "r")
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename + savegame_suffix, "r")
log = loads(zf.read("log"))
zf.close()
log.unfreeze()
+13 -2
View File
@@ -20,6 +20,8 @@ def run(restart=False):
will cause this to change.
"""
renpy.game.exception_info = 'While beginning to run the game.'
# Initialize the log.
game.log = renpy.python.RollbackLog()
@@ -40,7 +42,7 @@ def run(restart=False):
# Unserialize the persistent data.
try:
f = file(renpy.config.savedir + "/persistent", "r")
f = file(renpy.config.savedir + "/persistent", "rb")
s = f.read().decode("zlib")
f.close()
game.persistent = loads(s)
@@ -83,6 +85,11 @@ def run(restart=False):
game.init_phase = False
renpy.game.exception_info = 'After initialization, but before game start.'
# Rebuild the various style caches.
game.style._build_style_caches()
# Index the archive files. We should not have loaded an image
# before this point. (As pygame will not have been initialized.)
renpy.loader.index_archives()
@@ -131,8 +138,12 @@ def run(restart=False):
def main(basepath):
renpy.game.exception_info = 'While loading the script.'
game.basepath = basepath
game.searchpath = [ "common", basepath ]
renpy.config.searchpath = [ "common", basepath ]
renpy.config.backup()
# Load the script.
game.script = renpy.script.load_script(game.basepath)
+41 -15
View File
@@ -10,7 +10,7 @@ import renpy.ast as ast
class ParseError(Exception):
def __init__(self, filename, number, msg, line=None, pos=None):
message = "On line %d of %s: %s" % (number, filename, msg)
message = u"On line %d of %s: %s" % (number, filename, msg)
if line is not None:
message += "\n\n" + line
@@ -18,7 +18,12 @@ class ParseError(Exception):
if pos is not None:
message += "\n" + " " * pos + "^"
Exception.__init__(self, message)
self.message = message
Exception.__init__(self, message.encode('unicode_escape'))
def __unicode__(self):
return self.message
def list_logical_lines(filename):
@@ -41,6 +46,10 @@ def list_logical_lines(filename):
# The current position we're looking at in the buffer.
pos = 0
# Skip the BOM, if any.
if len(data) and data[0] == u'\ufeff':
pos += 1
# Looping over the lines in the file.
while pos < len(data):
@@ -218,6 +227,7 @@ class Lexer(object):
keywords = [
'at',
'call',
'expression'
'hide',
'if',
'image',
@@ -294,7 +304,9 @@ class Lexer(object):
Advances the current position beyond any contiguous whitespace.
"""
self.match_regexp(r"\s+")
# print self.text[self.pos].encode('unicode_escape')
self.match_regexp(ur"\s+")
def match(self, regexp):
"""
@@ -322,7 +334,7 @@ class Lexer(object):
Convenience function for reporting a parse error at the current
location.
"""
raise ParseError(self.filename, self.number, msg, self.text, self.pos)
def eol(self):
@@ -922,15 +934,6 @@ def parse_statement(l):
return ast.Pass(loc)
### Jump statement
if l.keyword('jump'):
l.expect_noblock('jump statement')
target = l.require(l.name)
l.expect_eol()
l.advance()
return ast.Jump(loc, target)
### Menu statement.
if l.keyword('menu'):
@@ -960,12 +963,35 @@ def parse_statement(l):
return ast.Return(loc)
### Jump statement
if l.keyword('jump'):
l.expect_noblock('jump statement')
if l.keyword('expression'):
expression = True
target = l.require(l.simple_expression)
else:
expression = False
target = l.require(l.name)
l.expect_eol()
l.advance()
return ast.Jump(loc, target, expression)
### Call/From statement.
if l.keyword('call'):
l.expect_noblock('call statment')
target = l.require(l.name)
rv = [ ast.Call(loc, target) ]
if l.keyword('expression'):
expression = True
target = l.require(l.simple_expression)
else:
expression = False
target = l.require(l.name)
rv = [ ast.Call(loc, target, expression) ]
if l.keyword('from'):
name = l.require(l.name)
+34 -7
View File
@@ -9,7 +9,9 @@ from compiler.pycodegen import ModuleCodeGenerator, ExpressionCodeGenerator
from compiler.misc import set_filename
import compiler.ast as ast
import marshal
import random
import types
import weakref
import renpy
@@ -131,9 +133,19 @@ def py_compile(source, mode):
else:
set_filename("<none>", tree)
cg = ExpressionCodeGenerator(tree)
return cg.getCode()
def py_compile_exec_bytecode(source):
code = py_compile(source, 'exec')
return marshal.dumps(code)
def py_compile_eval_bytecode(source):
source = source.strip()
code = py_compile(source, 'exec')
return marshal.dumps(code)
##### Classes that are exported in place of the normal list, dict, and
##### object.
@@ -460,6 +472,8 @@ class RollbackLog(renpy.object.Object):
self.current.objects.append((obj, roll))
def get_roots(self):
"""
Return a map giving the current roots of the store. This is a
@@ -589,6 +603,9 @@ class RollbackLog(renpy.object.Object):
self.frozen_roots = None
# We need to do this to counteract the effects of self.purge_unreachable
self.current.purged = False
def unfreeze(self):
"""
Used to unfreeze the game state after a load of this log
@@ -614,12 +631,17 @@ class RollbackLog(renpy.object.Object):
self.rollback(0, force=True)
# We never make it this far.
def py_exec_bytecode(bytecode, hide=False):
if hide:
locals = { }
else:
locals = renpy.game.store
exec marshal.loads(bytecode) in renpy.game.store, locals
def py_exec(source, hide=False):
if hide:
@@ -630,8 +652,13 @@ def py_exec(source, hide=False):
exec py_compile(source, 'exec') in renpy.game.store, locals
def py_eval_bytecode(bytecode):
return eval(marshal.loads(bytecode), renpy.game.store)
def py_eval(source):
source = source.strip()
return eval(py_compile(source, 'eval'),
renpy.game.store)
+2 -2
View File
@@ -9,7 +9,7 @@ import os
from cPickle import loads, dumps
# The version of the dumped script.
script_version = 3
script_version = renpy.script_version
class ScriptError(Exception):
"""
@@ -48,7 +48,7 @@ class Script(object):
# A list of all files in the search directories.
dirlist = [ ]
for dirname in renpy.game.searchpath:
for dirname in renpy.config.searchpath:
for fn in os.listdir(dirname):
dirlist.append(dirname + "/" + fn)
+70 -24
View File
@@ -1,5 +1,8 @@
import renpy
# A list of style prefixes we care about, including no prefix.
prefixes = [ 'hover_', 'idle_', '' ]
class StyleManager(object):
"""
This is the singleton object that is exported into the store as
@@ -19,7 +22,7 @@ class StyleManager(object):
if parent and not hasattr(self, parent):
raise Exception("Style '%s' has non-existent parent '%s'." % (name, parent))
s = Style(parent)
s = Style(parent, defer=True)
s.name = name
s.parent = parent
s.description = description
@@ -28,6 +31,11 @@ class StyleManager(object):
self._style_list.append(s)
def _build_style_caches(self):
for i in self._style_list:
i.build_cache()
def _write_docs(self, filename):
f = file(filename, "w")
@@ -53,44 +61,82 @@ class Style(object):
"""
def __getstate__(self):
return vars(self)
return dict(properties=self.properties,
prefix=self.prefix,
parent=self.parent)
# Not sure why this is necessary, but it seems to be. :-(
def __setstate__(self, state):
self.__dict__.update(state)
# This should always work, as only one layer of these styles will
# be serialized.
self.build_cache()
def __setattr__(self, key, value):
for prefix in prefixes:
prefkey = prefix + key
self.properties[prefkey] = value
self.cache[prefkey] = value
def __getattr__(self, key):
return self.lookup(key, self.prefix)
cache = self.cache
try:
return cache[self.prefix + key]
except KeyError:
raise AttributeError("Style property '%s' not found." % key)
def __delattr__(self, key):
del self.properties[key]
del self.cache[key]
def lookup(self, key, prefix):
if prefix + key in vars(self):
return vars(self)[prefix + key]
cache = self.cache
if key in vars(self):
return vars(self)[key]
try:
return cache.get(prefix + key, cache[key])
except KeyError:
raise AttributeError("Style property '%s' not found." % key)
if self.parent:
# This must always work, since we check for this in
# create_style.
ps = getattr(renpy.game.style, self.parent)
return ps.lookup(key, prefix)
else:
raise Exception("Style property '%s' not found." % key)
def set_prefix(self, prefix):
vars(self)["prefix"] = prefix
self.prefix = prefix
def __init__(self, parent, properties=None):
def build_cache(self):
vars(self)["cache"] = { }
self.cache = { }
if self.parent:
self.cache.update(getattr(renpy.game.style, self.parent).cache)
self.cache.update(self.properties)
def __init__(self, parent, properties=None, defer=False):
if parent and not hasattr(renpy.game.style, parent):
raise Exception("Style '%s' is not known." % parent)
if not properties:
properties = { }
for k in properties.keys():
for p in prefixes:
if p + k not in properties:
properties[p + k] = properties[k]
vars(self)["parent"] = parent
vars(self)["prefix"] = ''
vars(self)["properties"] = properties
vars(self)["cache"] = { }
self.parent = parent
self.prefix = ''
if properties:
vars(self).update(properties)
if not defer:
self.build_cache()
+44 -8
View File
@@ -1,8 +1,20 @@
#!/usr/bin/env python
import os.path
# Go psyco! (Compile where we can.)
try:
if not os.path.exists("nopsyco"):
import psyco
psyco.full()
except ImportError:
pass
import codecs
import optparse
import traceback
import os
import re
import sys
# Extra things used for distribution.
@@ -12,14 +24,28 @@ import encodings.zlib_codec
# Load up all of Ren'Py, in the right order.
import renpy
# The version of Ren'Py in use.
version = 'Renpy 4.3 "Birthday Present"'
if __name__ == "__main__":
# Stdout should be a utf-8 stream, so print works nicely.
# utf8writer = codecs.getwriter("utf-8")
# sys.stdout = utf8writer(sys.stdout)
name = os.path.basename(sys.argv[0])
if name.find(".") != -1:
name = name[:name.find(".")]
if name.find("_") != -1:
name = name[name.find("_") + 1:]
if os.path.isdir(name):
game = name
else:
game = "game"
op = optparse.OptionParser()
op.add_option('--game', dest='game', default='game',
op.add_option('--game', dest='game', default=game,
help='The directory the game is in.')
op.add_option('--python', dest='python', default=None,
@@ -38,12 +64,22 @@ if __name__ == "__main__":
f = file("traceback.txt", "wU")
f.write(codecs.BOM_UTF8)
print >>f, "I'm sorry, but an exception occured while executing your Ren'Py"
print >>f, "script."
print >>f
traceback.print_exc(None, sys.stdout)
traceback.print_exc(None, f)
type, value, tb = sys.exc_info()
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
print >>f, type.__name__ + ":",
print type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print unicode(e).encode('utf-8')
print
print >>f
@@ -52,7 +88,7 @@ if __name__ == "__main__":
print >>f, renpy.game.exception_info
print >>f
print >>f, "Ren'Py Version:", version
print >>f, "Ren'Py Version:", renpy.version
f.close()
+18 -1
View File
@@ -37,6 +37,23 @@ label start:
e "Welcome to the Ren'Py testcases."
call expression "sub1" from _call_expression_1
jump expression ("jump" + "1")
e "You shouldn't see this."
label sub1:
e "1"
return
label jump1:
e "2"
# say
"Single-argument say. (Expect no label)"
@@ -87,7 +104,7 @@ label start:
$ n = 0
call test_sub
call test_sub from _call_test_sub_1
python:
n += 1