Compare commits

...

12 Commits

Author SHA1 Message Date
tom 5ea9d436ce *** empty log message *** 2004-09-25 19:43:36 +00:00
tom c05e4e528e *** empty log message *** 2004-09-24 20:25:43 +00:00
tom e10488b77a *** empty log message *** 2004-09-24 02:31:54 +00:00
tom 86e0cb3331 *** empty log message *** 2004-09-23 22:20:34 +00:00
tom c6397c08af *** empty log message *** 2004-09-22 04:49:09 +00:00
tom f9db64231d *** empty log message *** 2004-09-22 04:39:38 +00:00
tom f3c3c67def *** empty log message *** 2004-09-22 00:15:05 +00:00
tom 6d316a9c78 *** empty log message *** 2004-09-21 23:54:02 +00:00
tom 6e7e68b31a Reorganizing to use common directory. 2004-09-13 18:16:40 +00:00
tom a24e6b2af1 *** empty log message *** 2004-09-12 13:51:22 +00:00
tom 597b01c26b Ready for 4.1 release? 2004-09-12 11:53:36 +00:00
tom a262eb0f60 *** empty log message *** 2004-09-11 18:30:37 +00:00
39 changed files with 2888 additions and 568 deletions
+186
View File
@@ -0,0 +1,186 @@
New in 4.2
----------
Ren'Py now ships with a common directory that is used to store the
library files, as well as the files needed by a minimal game. (Such as
a default font.) The idea here is to allow one to copy a game
directory from one version of Ren'Py to another, and have it just
work.
One can upgrade from 4.1 to 4.2 by deleting script.rpy, script.rpyc,
library.rpy, and library.rpyc, and then copying in the game
directory.
We've also eliminated the default background images. To go back to the
images used with Ren'Py 4.1, add the following lines to your script,
and copy the images out of the 4.1 distribution, or your 4.1 based
game.
[code]
init:
$ style.mm_root_window.background = Image("mainmenu.jpg")
$ style.gm_root_window.background = Image("gamemenu.jpg")
$ style.window.background = Frame("frame.png", 125, 25)
[/code]
Added a new variable, config.hard_rollback_limit, which limits the
number of steps the user can rollback the game, interactively. This
limit now defaults to 10 steps. (suggested by Grey)
Added a second parameter to renpy.pause, that allows us to pause until
a particular offset in the background music is reached. (suggested by
Alessio)
Added keyboard mouse controls that can be used to move the mouse
around on the game and main menus. This allows us to manipulate the
game and main menus without having to touch the mouse. This is mostly
for completeness. (suggested by Alessio)
Changed the way in which overlays work. We now have the variable
config.overlay_functions, which contains a list of functions, each of
which returns a list of displayables that will be added to the
screen. (sorta-kinda suggested by Alessio)
Removed renpy.set_overlay, since config.overlay_functions is a more
flexible way of doing the same thing.
Now, defining the label main_menu allows you to totally replace the
main menu with something of your own devising. Changed the
documentation to reflect this, as well as the changes in startup that
happened in 4.1.
Added a program called "add_from", which adds from clauses to all bare
call statements found in the program.
Replaced the menu_selected and menu_unselected styles with a single
menu_choice style which participates in the hover protocol. This may
change user code, if you change the menu colors. To fix this, replace
style.menu_selected.color with style.menu_choice.hover_color, and
style.menu_unselected.color with style.menu_choice.idle_color.
Added the ability to play sound effects, by calling the renpy.play
function. Added a preference that allows the user to control if
sound effects are played or not.
Added sound styles, which are used to define the sounds that are
played when buttons, imagemaps, and menu choices are hovered and
activated. (By default, no such sounds are played.)
Implemented that annoying thing where text is typed on the screen one
character at a time. Also added a preference that allows the user to
disable it. Used all the restraint I could muster to avoid defaulting
that preference to off.
Added the ability to fade out music when changing music
tracks. This is controlled by the config.fade_music variable.
(suggested by Alessio)
Added parameterized images, and the ability to show text as image
using a command like:
[code]
show text "American Bishoujo Presents..."
[/code]
Added a Move function, which can be used in at to allow an image to be
moved on the screen. This is best used when the image is smaller than
the screen. (suggested by Alessio)
Changed the format of archive files, to make them somewhat harder to
reverse-engineer. This requires the user to regenerate the archive
files. To make this easier, we now ship a batch file (called
archive_images.bat) that automatically archives the images found in
the game directory.
Added a new function renpy.wait that is equivalent to the wait
statement, and made it and renpy.pause return True if they are
interrupted, or False if the run to their natural completions. This
now makes it possible to jump somewhere else when the user click,
rather than blindly going to the next label. For example:
[code]
# Actually, this runs before the library main menu.
label main_menu:
scene black
show text "American Bishoujo\nPresents" \
at Move((0.5, 0.0), (0.5, 0.5), 3.0,
xanchor='center', yanchor='bottom')
if renpy.pause(6):
jump _library_main_menu
show text "A PyTom Game" at Position(xpos=0.5, ypos=0.5,
xanchor="center", yanchor="bottom")
if renpy.with(fade) or renpy.pause(4):
jump _library_main_menu
$ renpy.transition(fade)
jump _library_main_menu
[code]
Updated renpy-mode.el to add imenu support, so (X)Emacs users can use
the speedbar to jump around in a script. If you don't know what this
means, you probably don't care.
Improved the comments in the demo script, to make it a better
example. Added a syntax-highlighted version of the demo script to the
tutorial, to make it easier on peoples eyes.
New in 4.1
----------
Added the ability to customize the main menu by giving a list of
buttons on the main menu and the labels that they jump the user to.
Added the ability to center-click to hide any displayed text or menus.
Added the ability to have a color mouse that we can move around the
screen.
Added a persistent object, which has fields that are persistent across
games. Made the preferences part of this object, so that they are
persisted across sessions. For simplicity's sake, made seen_ever part
of that persistent object.
Added a special character called "centered", which causes the text it
displays to be centered in the screen without any window. Added two
new styles (centered_window and centered_text), which are used for
this, and a new property, textalign, which controls the horizontal
alignment of text within the Text object itself.
Keywords are now special in all contexts. So you can no longer include
keywords in image names. Sorry.
Improved parse error messages. They now include the text of the
line and a caret indication the location in the line where the parse
error occurred.
Added a with clause to say statements and menus. This lets one display
dialogue, thoughts, or menus using a transition. In conjunction with
this, changed a bit the semantics of with statements, and with clauses
on show, hide, and scene statements. Rewrote the section on
transitions in the documentation to reflect the new reality.
Added the ability to translate the text of the game menu. Together
with the ability to change the main menu, this makes Ren'Py fully
localizable, except for error messages. Check out the Localization
section in the tutorial for more details.
Added confirmations for quitting and overwriting a save game. This
also includes quitting by trying to close the window.
Added a preferences screen, which lets users change the Ren'Py
preferences. Right now, this includes Windowed/Fullscreen, Music
Enabled/Disabled, CTRL Skips Unread/All messages, and Transitions
controls which transitions are performed.
Added a Pan function which can be used in an at clause. This allows us
to pan over a background image.
Executable
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python
# This program adds froms to every unqualified call found in the game
# directory. It's not perfect, but it's better than nothing.
import sys
import os
import re
def find_labels(fn, labels):
f = file(fn, "r")
for l in f:
m = re.match(r'^\s*call\s+\w+\s+from\s+(\w+)\s*$', l)
if m:
labels[m.group(1)] = True
f.close()
def replace_labels(fn, labels):
f = file(fn, "r")
of = file(fn + ".new", "w")
def replaceit(m):
target = m.group(2)
num = 0
while True:
num += 1
label = "_call_%s_%d" % (target, num)
if label not in labels:
break
labels[label] = True
return m.group(1) + "call " + target + " from " + label + m.group(3)
for l in f:
l = re.sub(r'^(\s*)call\s+(\w+)(\s*$)', replaceit, l)
of.write(l)
f.close()
of.close()
os.rename(fn, fn + ".bak")
os.rename(fn + ".new", fn)
def main():
gamedir = "game"
if len(sys.argv) >= 2:
gamedir = sys.argv[1]
print "Processing files in", gamedir
files = [ gamedir + "/" + i for i in os.listdir(gamedir)
if i.endswith(".rpy") ]
labels = { }
for fn in files:
print "Finding labels in", fn
find_labels(fn, labels)
for fn in files:
print "Replacing labels in", fn
replace_labels(fn, labels)
if __name__ == "__main__":
main()
+2
View File
@@ -0,0 +1,2 @@
cd images
..\archiver.exe images *.png *.jpg
+45 -5
View File
@@ -7,9 +7,27 @@
import sys
import os
import encodings.zlib_codec
import random
from cPickle import loads, dumps, HIGHEST_PROTOCOL
# The most we will go without inserting some padding. 10k.
padding_every = 10240
# The amount of padding we will add.
padding_max = 32
def randpadding():
plen = random.randint(1, padding_max)
rv = ""
for i in range(0, plen):
rv += chr(random.randint(1, 255))
return rv
def main():
if len(sys.argv) < 3:
print "Usage: %s <file-prefix> <files ...>" % sys.argv[0]
@@ -25,18 +43,40 @@ def main():
index = { }
random.seed()
offset = 0
for fn in sys.argv[2:]:
index[fn] = [ ]
print "Adding %s..." % fn
data = file(fn, "rd").read()
dlen = len(data)
datafile = file(fn, "rb")
archivef.write(data)
while True:
index[fn] = (offset, dlen)
offset += dlen
# Pad with junk.
padding = randpadding()
archivef.write(padding)
offset += len(padding)
# Pick a random block size.
block = random.randint(1, padding_every)
data = datafile.read(block)
if not data:
break
dlen = len(data)
archivef.write(data)
index[fn].append((offset, dlen))
offset += dlen
datafile.close()
archivef.close()
+23 -3
View File
@@ -2,10 +2,30 @@
from distutils.core import setup
import py2exe
import sys
if len(sys.argv) != 2:
print "Usage: build_exe.py <prefix>"
print ""
print "Builds <prefix>.exe."
print "Expects icons in <prefix>.ico."
sys.exit(-1)
def program(name):
return dict(script="run_game.py",
icon_resources=[ (0, name + ".ico"),
],
dest_base=name)
programs = [
program(sys.argv[1]),
]
sys.argv[1:] = [ 'py2exe' ]
setup(name="RenPy",
windows=[ dict(script="run_game.py",
icon_resources=[(1, "icon.ico")]) ],
console=[ "archiver.py" ],
windows=programs,
console=[ "archiver.py", "add_from.py" ],
zipfile='lib/renpy.zip',
)
BIN
View File
Binary file not shown.
+55
View File
@@ -0,0 +1,55 @@
This package was debianized by Michael Fedrowitz <michaelf@debian.org>
on Sun, 23 Feb 2003.
It was downloaded from
'http://ftp.gnome.org/pub/GNOME/sources/ttf-bitstream-vera/'.
Upstream Author: Bitstream, Inc.
Copyright:
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute
the Font Software, including without limitation the rights to use,
copy, merge, publish, distribute, and/or sell copies of the Font
Software, and to permit persons to whom the Font Software is furnished
to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font
Software without prior written authorization from the Gnome Foundation
or Bitstream Inc., respectively. For further information, contact:
fonts at gnome dot org.
+183 -59
View File
@@ -32,10 +32,32 @@ init -500:
# The height of a thumbnail.
library.thumbnail_height = 75
# The contents of the main menu.
library.main_menu = [
( "Start Game", "start" ),
( "Continue Game", "_continue" ),
( "Quit Game", "_quit" ),
]
# Used to translate strings in the library.
library.translations = { }
# This is updated to give the user an idea of where a save is
# taking place.
save_name = ''
# The function that's used to translate strings in the game menu.
init:
python:
def _(s):
"""
Translates s into another language or something.
"""
if s in library.translations:
return library.translations[s]
else:
return s
##############################################################################
@@ -46,17 +68,28 @@ init:
def _screenshot():
renpy.screenshot("screenshot.bmp")
# Are the windows currently hidden?
_windows_hidden = False
# Hides the windows.
def _hide_windows():
global _windows_hidden
# Installs the keymap that lets the user do things like invoke the
# game menu.
label _install_keymap:
if _windows_hidden:
return
try:
_windows_hidden = True
renpy.interact(renpy.SayBehavior())
finally:
_windows_hidden = False
# A keymouse object that we use on the mainmenu and gamemenu
# screens.
_keymouse = renpy.KeymouseBehavior()
# Set up the default keymap.
python hide:
config.underlay = [ ]
config.overlay = [ ]
# The default keymap.
km = renpy.Keymap(
K_PAGEUP = renpy.rollback,
@@ -66,37 +99,34 @@ label _install_keymap:
m = renpy.toggle_music,
K_ESCAPE = renpy.curried_call_in_new_context("_game_menu"),
mouse_3 = renpy.curried_call_in_new_context("_game_menu"),
mouse_2 = _hide_windows,
)
config.underlay.append(km)
config.underlay = [ km ]
return
# This is the true starting point of the program. Sssh... Don't
# tell anyone.
label _start:
$ _started = False
call _main_menu
$ _started = True
call _install_keymap
jump start
jump _main_menu
# This shows the main menu to the user.
label _main_menu:
label _main_menu:
# jump start
# Let the user completely override the main menu.
if renpy.has_label("main_menu"):
jump main_menu
label _library_main_menu:
python hide:
# Show the main menu screen.
vbox = renpy.VBox()
vbox.add(renpy.TextButton('New Game', clicked=_return("start")))
vbox.add(renpy.TextButton('Continue Game', clicked=_return("continue")))
vbox.add(renpy.TextButton('Quit', clicked=_return("quit")))
for text, label in library.main_menu:
vbox.add(renpy.TextButton(text, clicked=_return(label)))
menu_window = renpy.Window(vbox, style='mm_menu_window')
@@ -105,17 +135,22 @@ label _main_menu:
root_window = renpy.Window(fixed, style='mm_root_window')
store._result = renpy.interact(root_window)
store._result = renpy.interact(_keymouse, root_window,
suppress_overlay=True,
suppress_underlay=True)
if _result == "start":
pass
elif _result == "continue":
$ renpy.call_in_new_context("_game_menu")
elif _result == "quit":
$ renpy.quit()
# Computed jump to the appropriate label.
$ renpy.jump(_result)
return
# Used to call the game menu.
label _continue:
$ renpy.call_in_new_context("_load_menu")
jump _library_main_menu
##############################################################################
#
# Code for the game menu.
@@ -128,12 +163,12 @@ init -500:
def _game_nav(selected):
buttons = [
( "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" ),
( "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"), "_confirm_quit" ),
]
vbox = renpy.VBox()
@@ -167,21 +202,24 @@ init -500:
for w in widgets:
fixed.add(w)
return renpy.interact(win)
return renpy.interact(_keymouse, win,
suppress_underlay=True,
suppress_overlay=True
)
_file_picker_index = 0
def _render_filename(filename, newest_filename):
if filename is None:
return renpy.Text("Save in new slot.", style='file_picker_new_slot')
return renpy.Text(_("Save in new slot."), style='file_picker_new_slot')
hbox = renpy.HBox(padding=library.padding)
if filename == newest_filename:
hbox.add(renpy.Text("New", style='file_picker_new'))
hbox.add(renpy.Text(_("New"), style='file_picker_new'))
else:
hbox.add(renpy.Text("Old", style='file_picker_old'))
hbox.add(renpy.Text(_("Old"), style='file_picker_old'))
hbox.add(renpy.load_screenshot(filename))
@@ -223,9 +261,9 @@ init -500:
hbox.add(tb(fpi > 0,
'Previous Page', _return(("fpidelta", -1))))
_('Previous Page'), _return(("fpidelta", -1))))
hbox.add(tb(fpi + library.file_page_length < len(files),
'Next Page', _return(("fpidelta", +1))))
_('Next Page'), _return(("fpidelta", +1))))
vbox.add(hbox)
for i in cur_files:
@@ -249,41 +287,122 @@ init -500:
if type == "fpidelta":
store._file_picker_index += value * library.file_page_length
def _yesno_prompt(screen, message):
prompt = renpy.Text(message, style='yesno_prompt')
yes = renpy.TextButton(_("Yes"), style='yesno_yes',
clicked=_return(True))
no = renpy.TextButton(_("No"), style='yesno_no',
clicked=_return(False))
return _game_interact(screen, prompt, yes, no)
# Returns a button for a single preference and value.
def _prefbutton(label, var, value):
def clicked():
setattr(_preferences, var, value)
return True
style = 'button'
text_style = 'button_text'
if getattr(_preferences, var) == value:
style = 'selected_button'
text_style = 'selected_button_text'
return renpy.TextButton(_(label), style=style,
text_style=text_style, clicked=clicked)
# Returns a vbox for a single preference.
def _prefvbox(label, var, entries):
rv = renpy.VBox(style='prefs_pref')
rv.add(renpy.Text(_(label), style='prefs_label'))
for blabel, value in entries:
rv.add(_prefbutton(blabel, var, value))
return rv
label _game_menu:
# disable keystrokes.
$ config.underlay = [ ]
$ config.overlay = [ ]
label _load_menu:
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
$ renpy.set_overlay([])
if _started:
jump _save_screen
else:
jump _load_screen
jump _load_screen
label _game_menu:
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
jump _save_screen
label _load_screen:
python:
_fn = _file_picker("load", renpy.saved_game_filenames() )
call _install_keymap
python:
renpy.load(_fn)
jump _game_menu
jump _load_screen
label _save_screen:
$ _fn = _file_picker("save", renpy.saved_game_filenames() + [ None ] )
python hide:
fn = _file_picker("save", renpy.saved_game_filenames() + [ None ] )
renpy.save(fn, renpy.time.strftime("%Y-%m-%d %H:%M:%S\n") + save_name)
if not _fn or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
$ renpy.save(_fn, renpy.time.strftime("%Y-%m-%d %H:%M:%S\n") + save_name)
jump _save_screen
# The preferences screen.
label _prefs_screen:
python hide:
prefs_left = [
( 'Display', 'fullscreen',
[ ('Window', False), ('Fullscreen', True) ] ),
( 'Music', 'music',
[ ('Enabled', True), ('Disabled', False) ] ),
( 'Sound Effects', 'sound',
[ ('Enabled', True), ('Disabled', False) ] ),
]
prefs_right = [
('CTRL Skips', 'skip_unseen',
[ ('Seen Messages', False), ('All Messages', True) ] ),
('Transitions', 'transitions',
[ ('All', 2), ('Some', 1), ('None', 0) ]),
]
if config.annoying_text_cps:
prefs_right.append(('Text Display', 'fast_text', [ ('Slow', False), ('Fast', True) ]))
vbox_left = renpy.VBox(padding=library.padding * 3, style='prefs_left')
for label, var, entries in prefs_left:
vbox_left.add(_prefvbox(label, var, entries))
vbox_right = renpy.VBox(padding=library.padding * 3, style='prefs_right')
for label, var, entries in prefs_right:
vbox_right.add(_prefvbox(label, var, entries))
_game_interact("prefs", vbox_left, vbox_right)
jump _prefs_screen
# Asks the user if he wants to quit.
label _confirm_quit:
if _yesno_prompt("quit", _("Are you sure you want to end the game?")):
jump _quit
else:
jump _return
label _quit:
$ renpy.quit()
@@ -293,6 +412,11 @@ label _full_restart:
# Return to the game, after restoring the keymap.
label _return:
call _install_keymap
return
# Random nice things to have.
init:
$ centered = Character(None, what_style="centered_text", window_style="centered_window")
image text = renpy.ParameterizedText(style="centered_text")
+110 -23
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 -100:
init -250:
python hide:
style.create('default', None,
@@ -27,6 +27,7 @@ init -100:
style.default.drop_shadow = (2, 2)
style.default.drop_shadow_color = (0, 0, 0, 128)
style.default.minwidth = 0
style.default.textalign = 0
# Change this if you're not using Vera 22.
if renpy.windows():
@@ -51,11 +52,15 @@ init -100:
style.default.xanchor = 'left'
style.default.yanchor = 'top'
# Sound properties.
style.default.hover_sound = None
style.default.activate_sound = None
# The base style for the large windows.
style.create('window', 'default',
'(window, placement) The base style for the windows that contain dialogue, thoughts, and menus.')
style.window.background = Frame("frame.png", 125, 25)
style.window.background = Solid((0, 0, 128, 128))
style.window.xpadding = 10
style.window.ypadding = 5
style.window.xmargin = 10
@@ -72,7 +77,7 @@ init -100:
# This style controls the default placement of images on the screen.
style.create('image_placement', None,
style.create('image_placement', 'default',
'This style is used to control the default placement of images on the screen.')
style.image_placement.xpos = 0.5
@@ -106,17 +111,11 @@ init -100:
"""(text) The style that is used to render a menu
caption.""")
style.create('menu_selected', 'default',
"""(text) The style that is used to render a
selected menu choice.""")
style.create('menu_choice', 'default',
"""(text, hover, sound) The style that is used to render a menu choice.""")
style.menu_selected.color = (255, 255, 0, 255) # yellow
style.create('menu_unselected', 'default',
"""(text) The style that is used to render an
unselected menu choice.""")
style.menu_unselected.color = (0, 255, 255, 255) # cyan
style.menu_choice.hover_color = (255, 255, 0, 255) # yellow
style.menu_choice.idle_color = (0, 255, 255, 255) # cyan
style.create('menu_window', 'window',
'(window, position) The default style for windows containing a menu.')
@@ -133,18 +132,49 @@ init -100:
style.create('input_window', 'window',
'(window, position) The style used for the window of an input box.')
# Styles used by centered.
style.create('centered_window', 'default',
'(window) The style that is used for a "window" containing centered text.')
style.create('centered_text', 'default',
'(text) The style used for centered text.')
style.centered_window.xpos = 0.5
style.centered_window.xanchor = 'center'
style.centered_window.xfill = False
style.centered_window.ypos = 0.5
style.centered_window.yanchor = 'center'
style.centered_window.yfill = False
style.centered_window.xpadding = 10
style.centered_text.textalign = 0.5
style.centered_text.xpos = 0.5
style.centered_text.ypos = 0.5
style.centered_text.xanchor = 'center'
style.centered_text.yanchor = 'center'
# Styles that are used by imagemaps
style.create('imagemap', 'image_placement',
'(sound, position) The style that is used for imagemaps.')
# Styles that are used by all Buttons.
style.create('button', 'default',
'(window, hover) The default style used for buttons in the main and game menus.')
'(window, sound, hover) The default style used for buttons in the main and game menus.')
style.button.xpos = 0.5
style.button.xanchor = 'center'
style.create('button_text', 'default',
'(text, hover) The default style used for the label of a button.')
style.button_text.xpos = 0.0
style.button_text.xanchor = 'left'
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)
# Selected button.
style.create('selected_button', 'button',
@@ -160,18 +190,19 @@ init -100:
style.create('disabled_button', 'button',
'(window, hover) The style that is used for a disabled button.')
style.disabled_button.hover_sound = None
style.disabled_button.activate_sound = None
style.create('disabled_button_text', 'button_text',
'(text, hover) The style that is used for the label of a disabled button.')
style.disabled_button_text.color = (128, 128, 128, 255)
# Styles that are used when laying out the main menu.
style.create('mm_root_window', 'default',
'(window) The style used for the root window of the main menu. This is primarily used to set a background for the main menu.')
style.mm_root_window.background = renpy.Image("mainmenu.jpg")
style.mm_root_window.background = Solid((0, 0, 0, 255))
style.create('mm_menu_window', 'default',
'(window, position) A window that contains the choices in the main menu. Change this to change the placement of these choices on the main menu screen.')
@@ -192,19 +223,19 @@ init -100:
style.create('gm_root_window', 'default',
'(window) The style used for the root window of the game menu. This is primarily used to change the background of the game menu.')
style.gm_root_window.background = renpy.Image("gamemenu.jpg")
style.gm_root_window.background = Solid((0, 0, 0, 255))
style.create('gm_nav_window', 'default',
'(window, placement) The style used by a window containing buttons that allow the user to navigate through the different screens of the game menu.')
'(window, position) The style used by a window containing buttons that allow the user to navigate through the different screens of the game menu.')
style.gm_nav_window.xpos = 0.95
style.gm_nav_window.xpos = 0.9
style.gm_nav_window.xanchor = 'right'
style.gm_nav_window.ypos = 0.95
style.gm_nav_window.yanchor = 'bottom'
style.create('file_picker_window', 'default',
'(window, placement) A window containing the file picker that is used to choose slots for loading and saving.')
'(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.xanchor = 'left'
@@ -244,3 +275,59 @@ init -100:
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('yesno_prompt', '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_prompt.ypos = 0.25
style.yesno_prompt.yanchor = 'center'
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'
# Preferences
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.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"
+229 -100
View File
@@ -2,8 +2,14 @@
# public domain. Feel free to use it as the basis for your own
# game.
# This init block runs first, and sets up all sorts of things that
# are used by the rest of the game. Variables that are set in init
# blocks are _not_ saved, unless they are changed later on in the
# program.
init:
# Set up the size of the screen.
# Set up the size of the screen, and the window title.
$ config.screen_width = 800
$ config.screen_height = 600
$ config.window_title = "The Ren'Py Demo Game"
@@ -11,14 +17,24 @@ init:
# Set up the library.
$ library.file_page_length = 3
# Positions of things on the screen.
# Change some styles, to add images in the background of
# the menus and windows.
$ style.mm_root_window.background = Image("mainmenu.jpg")
$ style.gm_root_window.background = Image("gamemenu.jpg")
$ style.window.background = Frame("frame.png", 125, 25)
# 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()
# Transitions between scenes.
# 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) # Dissolve.
$ dissolve = Dissolve(0.5)
# Now, we declare the images that are used in the program.
# Backgrounds.
image carillon = Image("carillon.jpg")
@@ -31,30 +47,53 @@ init:
image eileen vhappy = Image("9a_vhappy.png")
image eileen concerned = Image("9a_concerned.png")
# Finally, the character object. This object lets us have the
# character say dialogue without us having to repeatedly type
# her name. It also lets us change the color of her name.
# Character objects.
$ e = Character('Eileen', color=(200, 255, 200, 255))
# The actual game starts here.
# The start label marks the place where the main menu jumps to to
# begin the actual game.
label start:
# The save_name variable sets the name of the save game. Like all
# variables declared outside of init blocks, this variable is
# saved and restored with a save file.
$ save_name = "Introduction"
# Did we win the date?
# This variable is only used by our game. If it's true, it means
# that we won the date.
$ date = False
# Start some music playing in the background.
$ renpy.music_start('sun-flower-slow-drag.mid')
# Now, set up the first scene. We first fade in our washington
# background, and then we dissolve in the image of Eileen on top
# of it.
scene washington with fade
show eileen vhappy with dissolve
"Girl" "Hi, and welcome to the Ren'Py 4.0 demo program."
# 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."
# This instantly replaces the very happy picture of Eileen with
# one showing her merely happy. It demonstrates how the show
# statement lets characters change emotions.
show eileen happy
# Another line of dialogue.
"Girl" "My name is Eileen, and while I plan to one day star in a
real game, for now I'm here to tell you about Ren'Py."
# This line used the e character object, which displays Eileen's
# name in green. The use of a short name for a character object
# lets us save typing when writing the bulk of the dialogue.
e "Ren'Py is a language and engine for writing and playing visual
novel games."
@@ -65,34 +104,207 @@ label start:
e "I can tell you about the features of Ren'Py games, or how to write
your own game. What do you want to know about?"
# This variable is used to save the choices that have been made in
# the main menu.
$ seen_set = [ ]
label choices:
label choices:
# We change the save name here.
$ save_name = "Question Menu"
# This is the main menu, that lets the user decide what he wants
# to hear about.
menu:
# The set menu clause ensures that each menu choice can only
# be chosen once.
set seen_set
# This is a menu choice. When chosen, the statements in its
# block are executed.
"What are some features of Ren'Py games?":
call features from call_features
# We call the features label. The from clause needs to be
# here to ensure that save games work, even after we
# change the script. It was added automatically.
call features from _call_features_1
# When we're done talking about features, jump back up
# to choices.
jump choices
# Another choice.
"How do I write my own games with it?":
call writing from call_writing
call writing from _call_writing_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.)
"Where can I find out more?" if seen_set:
call find_out_more from call_find_out_more
call find_out_more from _call_find_out_more_1
jump choices
"Why are we in Washington, DC?":
call washington from call_washington
call washington from _call_washington_1
jump choices
"I think I've heard enough." if seen_set:
jump ending
# This is the section on writing games.
label writing:
# Change the title of the save games.
$ save_name = "Writing Games"
# We start off with a bunch of dialogue.
e "If you want to write a game, I recommend that you read the
Ren'Py tutorial, which you can get from our web page,
http://www.bishoujo.us/renpy/."
e "But here, we'll go over some of the basics of writing Ren'Py
scripts. It might make sense if you open the source for this
game."
e "The source for this game can be found in the file
game/script.rpy."
e "The goal of Ren'Py is to make writing the game similar to
typing up the script on the computer."
e "For example, a line of dialogue is expressed by putting the
character's name next to the dialogue string."
# A string by itself like this displays without a name associated
# with it. So it's useful for dialogue and narration.
"I somehow remember that strings by themselves are displayed as
thoughts or narration."
e "The menu statement makes it easy to create menus."
e "A number of statements let you control what is shown on the
screen."
# This scene statement has a with clause associated with it. In
# this case (based on what is defined in the init clause at the
# top of this script), it causes a fade to black, and then back
# to the new scene.
scene whitehouse with fade
e "The scene statement clears the scene list, which is the list of
things that are shown on the screen."
# This shows an image, and dissolves it in.
show eileen happy with dissolve
e "The show statement shows another image on the screen."
# The at clause here, displays the character on the left side of
# the screen.
show eileen happy at left with dissolve
e "Images can take at clauses that specify where on the screen
they are shown."
show eileen vhappy at left
e "Showing a new image with the same first part of the name
replaces the image in the scene list."
hide eileen with dissolve
e "Finally, the hide statement hides an image, which is useful
when a character leaves the scene."
show eileen happy with dissolve
e "Don't worry, I'm not going anywhere."
e "The with statement is used to cause transitions to
happen. Transitions like fade..."
# This statement hides the transient stuff from being included
# in the next fade.
with None
# This with statement causes things to fade without changing the
# scene.
with fade
e "... or dissolve ..."
# In this block, the scene statement clears the scene list. So we
# have to reshow the eileen happy image, so that it appears that
# just the background is dissolving. Sneaky.
with None
scene washington
show eileen happy
with dissolve
e "... are easily invoked."
e "As of version 4.2, Ren'Py supports image maps, which are like
another form of menu. Let's try one."
# This is an imagemap. It consists of two images, and a list of
# hotspots. For each hotspot we give the coordinates of the left,
# top, right, and bottom sides, and the value to return if it is
# picked.
$ result = renpy.imagemap("ground.png", "selected.png", [
(100, 100, 300, 400, "eileen"),
(500, 100, 700, 400, "lucy")
])
# We've assigned the chosen result from the imagemap to the
# result variable. We can use an if statement to vary what
# happens based on the user's choice.
if result == "eileen":
show eileen vhappy
e "You picked me!"
elif result == "lucy":
show eileen concerned
e "It looks like you picked Lucy."
# Eileen is being a bit possesive here. :-P
if date:
e "You can forget about Saturday."
$ date = False
show eileen happy
e "Ren'Py supports music, such as what's playing in the
background..."
# This plays a sound effect.
$ renpy.play("18005551212.wav")
e "... and sound effects, like the one that just played."
e "Ren'Py also includes a number of control statements, and even
lets you include python code."
e "Rather than go into this here, you can read all about it in the
tutorial."
e "If you want to make changes, you can edit the script for this
game by editing game/script.rpy"
e "When you've made a change, just re-run the game to see your
change in action."
e "Would you like to know about something else?"
# We return back up to the menu that lets the user pick a topic.
return
# This ends the well-commented portion of this script.
label features:
$ save_name = "Features"
@@ -120,6 +332,9 @@ label features:
e "The game menu also lets you restart or quit the game. But you
wouldn't want to do that, would you?"
e "Finally, the game menu lets you set up the game
preferences. These preferences are saved between games."
show eileen vhappy
e "The next feature is really neat."
@@ -182,92 +397,6 @@ label after_rollback:
return
label writing:
$ save_name = "Writing Games"
e "If you want to write a game, I recommend that you read the
Ren'Py tutorial, which you can get from our web page,
http://www.bishoujo.us/renpy/."
e "But here, we'll go over some of the basics of writing Ren'Py
scripts. It might make sense if you open the source for this
game."
e "The source for this game can be found in the file
game/script.rpy."
e "The goal of Ren'Py is to make writing the game similar to
typing up the script on the computer."
e "For example, a line of dialogue is expressed by putting the
character's name next to the dialogue string."
"I somehow remember that strings by themselves are displayed as
thoughts or narration."
e "The menu statement makes it easy to create menus."
e "A number of statements let you control what is shown on the
screen."
scene whitehouse with fade
e "The scene statement clears the scene list, which is the list of
things that are shown on the screen."
show eileen happy with dissolve
e "The show statement shows another image on the screen."
show eileen happy at left with dissolve
e "Images can take at clauses that specify where on the screen
they are shown."
show eileen vhappy at left
e "Showing a new image with the same first part of the name
replaces the image in the scene list."
hide eileen with dissolve
e "Finally, the hide statement hides an image, which is useful
when a character leaves the scene."
show eileen happy with dissolve
e "Don't worry, I'm not going anywhere."
e "The with statement is used to cause transitions to
happen. Transitions like fade..."
with fade
e "... or dissolve ..."
scene washington
show eileen happy
with dissolve
e "... are easily invoked."
e "Ren'Py also includes a number of control statements, and even
lets you include python code."
e "Rather than go into this here, you can read all about it in the
tutorial."
e "If you want to make changes, you can edit the script for this
game by editing game/script.rpy"
e "When you've made a change, just re-run the game to see your
change in action."
e "Would you like to know about something else?"
return
label find_out_more:
@@ -405,7 +534,6 @@ label post_netherlands:
return
label ending:
$ save_name = "Ending"
@@ -437,3 +565,4 @@ label ending:
"We can't wait to see what you do with this. Good luck!"
$ renpy.full_restart()
+10 -3
View File
@@ -21,8 +21,7 @@ def copy_file(source, dest, license=""):
df.write(license)
data = sf.read()
if dest.endswith(".txt") or dest.endswith(".py") or dest.endswith(".rpy"):
print "DOSIFY", dest
if dest.endswith(".txt") or dest.endswith(".py") or dest.endswith(".rpy") or dest.endswith(".bat"):
data = dosify(data)
df.write(data)
@@ -92,6 +91,7 @@ def main():
license=license)
doc_files = [
'example.html',
'tutorial.html',
'style.css',
]
@@ -104,14 +104,21 @@ def main():
copy_tree(gamedir, target + "/game",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
copy_tree("common", target + "/common",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
def cp(x, license=""):
copy_file(x, target + "/" + x)
cp("CHANGELOG.txt")
cp("LICENSE.txt")
cp("README_RENPY.txt")
cp("archive_images.bat")
cp("run_game.py", license=license)
cp("archiver.py", license=license)
cp("build_exe.py", license=license)
cp("add_from.py", license=license)
cp("renpy-mode.el")
+10 -2
View File
@@ -1,4 +1,12 @@
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css
all:: tutorial.html example.html
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css styles.xml
python preprocess.py tutorial.xml > tutorial.hi.xml
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml
python preprocess.py example.xml > example.hi.xml
xsltproc stylesheet.xslt example.hi.xml > example.html
cp example.html style.css ~/ab/website/renpy/devel/doc
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<doc>
<title>Demo Script Example</title>
<p><a href="tutorial.html">Return to the tutorial.</a></p>
<example>
<!-- include ../demo2/script.rpy -->
</example>
<p><a href="tutorial.html">Return to the tutorial.</a></p>
</doc>
+15 -2
View File
@@ -1,7 +1,8 @@
#!/usr/bin/python
import sys
import re
import sys
import time
import inspect
@@ -31,6 +32,7 @@ keywords = [
r'\bpass\b',
r'\bwith\b',
r'\bat\b',
r'\bpython\b',
]
kwre = '|'.join(keywords)
@@ -109,15 +111,26 @@ def function(m):
return '<function name="%(name)s" sig="%(args)s">%(doc)s</function>' % locals()
def include(m):
f = file(m.group(1))
rv = f.read()
f.close()
return rv
def main():
f = file(sys.argv[1])
s = f.read()
f.close()
s = re.sub(r"(?s)<example>(.*?)</example>", example, s)
s = re.sub(r"<!-- func (\S+) -->", function, s)
s = re.sub(r"<!-- include (\S+) -->", include, s)
s = re.sub(r"<!-- date -->", time.strftime("%04Y-%02m-%02d %02H:%02M"), s)
s = re.sub(r"(?s)<example>(.*?)</example>", example, s)
print s
+571 -91
View File
@@ -2,6 +2,10 @@
<doc>
<title>Writing Visual Novels with Ren'Py</title>
<subtitle>The Ren'Py Tutorial</subtitle>
<p>
<b>Last Updated:</b> <!-- date -->
</p>
<toc/>
@@ -202,13 +206,34 @@ line 2
<def>Keyword</def>s are words that appear in the source
code. They're used to introduce a statement, or to delimit parts
of a statement. You'll see keywords throughout the descriptions
of statements. In grammar rules, keywords are in quotes.
of statements. In grammar rules, keywords are in quotes. The
keywords are:
</p><p>
</p>
<pre>
at
call
hide
if
image
init
jump
menu
python
return
scene
set
show
with
while
</pre>
<p>
A <def>name</def>s consist of an alphabetic character or number,
followed by zero or more alphabetic characters or underscores,
so long as the string isn't a keyword.
so long as the string isn't a keyword.
</p><p>
@@ -283,7 +308,7 @@ line 2
a simple_expression followed by a string.
</p>
<rule>say_statement -> ( simple_expression )? string</rule>
<rule>say_statement -> ( simple_expression )? string ( "with" simple_expression )?</rule>
<p>
We can distinguish two forms of the say statement, depending on
@@ -344,6 +369,27 @@ e "I know that you're %(player_age)d years old, and your zodiac
sign is %(player_sign)s."
</example>
<p>
When the first object is a character object, that character object
is given given complete control over how text is displated. As an
example of this, the standard library includes a character object
called "centered", which displays the text centered on the screen,
without any background window.
</p>
<example>
centered "American Bishoujo presents..."
centered "The Ren'Py Demo Game"
</example>
<p>
The say statement also takes a with clause that is used to control
the transition that is used to introduce the dialogue or
thought. Please see the section on transitions for details on how
to use the with clause.
</p>
<h3>Menus</h3>
<p>
@@ -364,7 +410,7 @@ e "I know that you're %(player_age)d years old, and your zodiac
</p><p>
The menu statement must have a block associated with it. This
block must contain one or more menuitems in it. There are three
block must contain one or more menuitems in it. There are four
kinds of menuitems that can be contained in a menu block.
</p>
@@ -402,6 +448,16 @@ e "I know that you're %(player_age)d years old, and your zodiac
present, it's used to filter the list of choices shown to the
user.
</p>
<rule>with_menuitem -> "with" simple_expression</rule>
<p>
The final kind of menuitem is a with clause. This is used to
specify the transition that introduces this menu. Please see the
section on transitions for a discussion of this.
</p><p>
When a menu is to be shown to the user, the first thing that
@@ -618,6 +674,51 @@ hide eileen
most scenes needed in a visual novel type game.
</p>
<h4>Parameterized Images</h4>
<p>
Show and scene statements may also take parameterized images. If a
name of an image is a prefix of the name given in a scene or show
statement, the rest of the name is considered to be
parameters. Parameters may be names, but they may also be
simple_expressions, which includes strings. Parameters are handed
off to the object that the prefix is defined to, and that object
is responsible for returning a displayable that can be shown to
the user.
</p>
<p>
An example of this is the pre-defined text displayable, which
displays text as if it was an image. For example, the following
code:
</p>
<example>
show text "American Bishoujo Presents..."
</example>
<p>
Will display the given text as if it was an image. The game will
not pause while the text is being displayed. Such an image can
also be processed through an at clause, as in the following code
which moves the text down from the top of the screen, while
waiting 10 seconds or until the user makes some input.
</p>
<example>
show text "A PyTom Game" \
at Move((0.5, 0.0), (0.5, 0.5), 4.0,
xanchor='center', yanchor='bottom')
$ renpy.pause(10)
</example>
<p>
Please note that parameterized images are replaced in the same way
that normal images are. (So you can only have one image created
with text on the screen at a time, without creating a second
ParameterizedText object.)
</p>
<h3>Image and Scene Functions</h3>
@@ -668,10 +769,10 @@ init:
<p>
Position, when given position properties as arguments, returns a
callable that can be passed to the "at" clause of a show, hide,
or scene statement to display the image at the given
location. See the section below on position properties to get
a full explanation of how they are used to lay things out, but
callable that can be passed to the "at" clause of a show or
scene statement to display the image at the given location. See
the section below on position properties to get a full
explanation of how they are used to lay things out, but
hopefully this example will show how Position can be used:
</p>
</function>
@@ -688,6 +789,105 @@ init:
show eileen happy at left
</example>
<!-- 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
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.
</p>
<example>
init:
image text1 = renpy.ParameterizedText(ypos=0.25)
show text "centered."
show text1 "1/4 of the way down the screen."
</example>
<function name="Pan" sig="(startpos, endpos, time)">
<p>
Pan, when given the appropriate arguments, gives an object that
can be passed to the at clause of that image to cause the image to
be panned on the screen. The parameters startpos and endpos are
tuples, containing the x and y coordinates of the upper-left hand
corner of the screen relative to the image. Time is the time it
will take this position to move from startpos to endpos.
</p><p>
As the current implementation of Ren'Py is quite limited, there
are quite a few restrictions that we put on pan. The big one is
that there always must be a screen's worth of pixels to the
right and below the start and end positions. Failure to ensure
this may lead to inconsistent rendering.
</p><p>
Hopefully, an example will demonstrate how Pan is used. For this
example, assume that the screen is 800 x 600, and that the
image marspan is 2400 x 600 pixels in size. We want to take 10
seconds to pan from left to right on the image.
</p>
<example>
scene marspan at Pan((0, 0), (1600, 0), 10.0)
</example>
<p>
Please note that the pan will be immediately displayed, and that
Ren'Py will not wait for it to complete before moving on to
the next statement. This may lead to the pan being overlayed
with text or dialogue. You may want to use a call to renpy.pause
to delay for the time it will take to complete the pan.
</p>
<p>
Finally, also note that when a pan is completed, the image locks
into the ending position.
</p>
</function>
<function name="Move" sig="(startpos, endpos, time, **properties)">
<p>
Move is similar to Pan, insofar as it involves moving
things. But where Pan moves the screen through an image, Move
moves an image on the screen. Specifially, move changes the
position style of an image with time.
</p>
<p>
Move takes as parameters a starting position, an ending
position, the amount of time it takes to move from the starting
position to the ending position, and extra position
properties. The positions are given as tuples containing xpos
and ypos properties. The positions may be integer or floating
point, but it's not permissable to mix the two.
</p>
<p>
The following example moves a ball from the upper-left to the
lower-right of the screen, taking 10 seconds to do so.
</p>
<example>
show ball at Move((0.0, 0.0), (1.0, 1.0), 10.0,
xanchor="center", yanchor="center")
</example>
</function>
<p>
In general, one wants to use Pan when an image is bigger than the
screen, and Move when it is smaller.
</p>
<h3>Transitions</h3>
@@ -770,13 +970,21 @@ with dissolve
will be instantly shown, and then the character image will be
dissolved in over the background.
</p>
<p>
Another use of the "with None" statement is to remove transient
elements before a transition begins. By default, the scene list
includes transient elements like dialogue, thoughts, and
menus. "with None" always executes without these elements, and so
gets rid of them.
</p>
<p>
Finally, the "show", "hide", and "scene" statements all take a
with clause. A statement with a with clause associated with it is
actually converted into three statements: A "with None" statement,
the original statement sans the with clause, and the with clause as
a with statement. For example:
The "show", "hide", and "scene" statements all take a with
clause. One of these statement with a with clause associated with
it is actually converted into three statements: A "with None"
statement, the original statement sans the with clause, and the
with clause as a with statement. For example:
</p>
<example>
@@ -807,6 +1015,36 @@ with dissolve
other.
</p>
<p>
We also allow with clauses to be supplied for say and menu
statements. When a with clause is supplied on one of these
statements, the transition is used to introduce the say or menu
element. For example,
</p>
<example>
e "How are you doing?" with dissolve
</example>
<p>
Will dissolve in a line of dialogue. The line of dialogue will be
dismissed immediately, unless it is followed by a with statement
or clause that causes it to transition to something else.
</p>
<p>
There is one variable that controls transitions:
</p>
<dl>
<var name="default_transition" value="None">
If not none, specifies a default transition that is applied to
all say and menu statements that are not provided a with
clause. This is only considered if the transitions preference is
set to "All".
</var>
</dl>
<p>
Functions that return things that are useful as arguments to with
statements or clauses are:
@@ -911,8 +1149,11 @@ jump loop_start
required here to ensure that saved games with return stacks can
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. A script will be provided to add from
clauses to call statements right before a game is released.
still under development. We provide with Ren'Py a program, called
"add_from", that adds from clauses to all bare calls in the 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>
</p>
@@ -1129,11 +1370,16 @@ python:
</p><p>
After the last init block has finished running, the display is
initialized, and the actual game can begin. It's expected that
each game will have a label named "start". The game is begun by
jumping to this start label. Execution proceeds from there,
terminating when the end of a file or a return statement is
reached.
initialized, and the actual game can begin. If the library is
present it looks for a label named "main_menu". If it exists, then
that label is jumped to as the main menu. Otherwise, the library
main menu is displayed. While library.main_menu allows this menu
to be customized, the default main menu calles the label "start"
when the user chooses "Start Game". The library main menu can
also be manually accessed by jumping to _library_main_menu, this
is useful if you want to display an intro before the menu is
shown.
</p>
<h3>Saving, Loading, and Rollback</h3>
@@ -1184,6 +1430,7 @@ python:
<li>The mappings of image names to Displayable created by the image
statement.</li>
<li>Configuration variables (config.varname).</li>
<li>Library variables (library.varname).</li>
<li>Styles (style.stylename).</li>
</ul>
@@ -1344,6 +1591,47 @@ $ state.love_love_points = a + 1
such a variable, will be saved, loaded, and rolled-back properly.
</p>
<h3>Persistent Data</h3>
<p>
Persistent data is data that is saved that is not associated with a
single game. One possible use of it is to store information about
things that have been unlocked, such as an image gallery that is
only unlocked when an ending has been reached.
</p>
<p>
Persistent data is stored as fields on the "persistent"
object. This object is special, as uninitialized fields are forced
to take the value None. A change to this object is visible in
every game that the player undertakes.
</p>
<p>
Take as an example an unlockable image gallery. The code to
display the gallery look like:
</p>
<example>
label gallery:
if not persistent.gallery_unlocked:
show background
centered "You haven't unlocked this gallery yet."
$ renpy.full_restart()
# Actually show the gallery here.
</example>
<p>
Then, to unlock the gallery, run the following code somewhere in
your program.
</p>
<example>
$ persistent.gallery_unlocked = True
</example>
<h3>Interaction Functions</h3>
<p>
@@ -1371,12 +1659,32 @@ e "My name is shown in full, and in green."
<!-- func renpy.input -->
<!-- func renpy.input -->
<example>
$ name = renpy.input("What is your name?", "Joe User", length=20)
e "Pleased to meet you, %(name)s."
</example>
<!-- func renpy.imagemap -->
<example>
$ result = renpy.imagemap("ground.png", "selected.png", [
(100, 100, 300, 400, "eileen"),
(500, 100, 700, 400, "lucy")
])
if result == "eileen":
e "You picked me!"
elif result == "lucy":
e "It looks like you picked Lucy."
</example>
<!-- func renpy.transition -->
<!-- func renpy.full_restart -->
<!-- func renpy.quit -->
@@ -1385,7 +1693,82 @@ e "Pleased to meet you, %(name)s."
<!-- func renpy.windows -->
<h3>Music Functions</h3>
<h3>Overlays</h3>
<p>
Overlays are used to display information above the scene currently
displayed. The overlay is regenerated each time an interaction
with the user begins, making it suitable for displaying to the
user things like statistics or dates. The overlay is generally
displayed whenever transient things (like dialogue, thoughts and
menus) are.
</p>
<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
called for each interaction, which allows the overlay to change to
reflect the status of game variables.
</p>
<p>
As an example, take the following code fragement. When added to a
program, this displays a date image in the upper-right corner of
the screen (as is done in Kanon). The image shown is based on the
variable date. If date is None, then no date is shown. Otherwise,
a png file beginning with the value of date is shown.
</p>
<example>
init:
$ date = "mar25"
python hide:
def date_overlay():
if date:
return [ Image(date + ".png",
xpos=1.0, xanchor="right",
ypos=0.0, yanchor="top") ]
else:
return [ ]
config.overlay_functions.append(date_overlay)
</example>
<p>
Like all config variables, config.overlay_functions should only be
changed in an init block. If you need to toggle an overlay on and
off, then the overlay function should be conditioned on some
normal variable, returning an empty list if it is false. This is
done in the example above when date is None.
</p>
<h3>Sound and Music Functions</h3>
<h4>Sound</h4>
<p>
Ren'py supports playing sounds in the background, using the
renpy.play function. These sounds must be in wav files, but
may live in an archive file.
</p>
<!-- func renpy.play -->
<example>
$ renpy.play("quindar.wav")
e "Ground control to Major Tom."
</example>
<p>
Sounds can also be associated with buttons, menu choices, and
imagemaps becoming hovered and activated. See the section on
sound properties in styles for how to used this.
</p>
<h4>Music</h4>
<p>
Ren'Py supports playing music in the background of your
@@ -1435,6 +1818,11 @@ show eileen wink
e "Just kidding."
</example>
<p>
The config.fade_music variable is used to control the fading out
of old music when a new track is started.
</p>
<h3>Configuration Variables</h3>
<p>
@@ -1452,7 +1840,6 @@ init:
$ config.screen_width = 480
</example>
<dl>
<var name="config.screen_width" value="800">
This sets the width of the screen.
@@ -1483,11 +1870,18 @@ init:
for loading savegames when the script changes.)
</var>
<var name="config.rollback_length" value="512">
<var name="config.rollback_length" value="128">
When there are more than this many statements in the rollback log,
Ren'Py will consider trimming the log.
</var>
<var name="config.hard_rollback_limit" value="10">
This is the number of steps that Ren'Py will let the user
interactively rollback. Set this to 0 to disable rollback
entirely, although we don't recommend that, as rollback is
useful to let the user see text he skipped by mistake.
</var>
<var name="config.profile" value="False">
If set to True, some profiling information will be output to
stdout (wherever that may go to).
@@ -1528,7 +1922,78 @@ init:
<var name="config.archives" value="[ ]">
A list of archive files that will be searched for images.
</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
probably best given as a PNG file, in which case the mouse can
have transparency and translucency. If this is None, a
black-and-white hardware mouse is used, and the speed of Ren'Py
is slightly increased.
</var>
<var name="config.keymouse_distance" value="5">
This gives the number of pixels that the mouse is moved every
1/20th of a second when the keymouse is being used.
</var>
<var name="config.sound_sample_rate" value="44100">
The sample rate that the sound card will be run at. If all of
your wav files are of a lower rate, changing this to that rate
may make things more efficent.
</var>
<var name="config.overlay_functions" value="[ ]">
A list of functions. When called, each function is expected to
return a list of displayables, which are added to the overlay
list. See the section on overlays for more.
</var>
<var name="config.annoying_text_cps" value="None">
If not None, dialogue and thoughts will be "typed" onto the
screen a few characters at a time. The value of this is the
number of characters that will be typed onto the screen in a
second. I strongly reccomend keeping this None. Not only does
this interact poorly with the rest of Ren'Py, it's annoying.
</var>
<var name="config.fade_music" value="0.0">
This is the amount of time in seconds to spend fading the old track out
before a new music track starts. This should probably be fairly
short, so the wrong music doesn't play for too long.
</var>
<var name="library.file_page_length" value="4">
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.
</var>
<var name="library.thumbnail_width" value="100">
The width of the thumbnails that are taken when the game is
saved. These thumbnails are shown when the game is
loaded. Please note that the thumbnail is shown at the size it
was taken at, rather than the value of this setting when the
thumbnail is shown to the user.
</var>
<var name="library.thumbnail_height" value="75">
The width of the thumbnails that are taken when the game is
saved. These thumbnails are shown when the game is
loaded. Please note that the thumbnail is shown at the size it
was taken at, rather than the value of this setting when the
thumbnail is shown to the user.
</var>
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("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.
</var>
</dl>
<h3>Properties and Styles</h3>
@@ -1625,6 +2090,15 @@ init:
condition it on the result of renpy.windows().
</prop>
<prop name="textalign">
This is used to control the horizontal alignment of the lines of
text in the area allocated to the Text widget containing that
text. It only really has any effect if the text is more than one
line long. It's a number between 0 and 1, which gives the fraction
of empty space that should be to the left of each line of
text. (To center text, it should be 0.5.)
</prop>
<h4>Window Properties</h4>
<p>
@@ -1739,6 +2213,27 @@ init:
dimension. This can be one of 'top', 'center', or 'bottom'.
</prop>
<h4>Sound Properties</h4>
<p>
Some widgets can take sound style properties. These widgets can cause
a sound to play when they become hovered (see below), and also
when they are selected. Currently, the styles that can take sound
properties belong to buttons, imagemaps, and menu choices. Sound
style properties are either the name of a wav file which is played
with renpy.play when appropriate event occurs, or None to disable
sound playback.
</p>
<prop name="hover_sound">
The sound to play when this widget becomes hovered.
</prop>
<prop name="activate_sound">
The sound to play when the widget is activated, by clicking on or
otherwise selecting it.
</prop>
<h4>Hovering</h4>
<p>
@@ -1762,7 +2257,7 @@ init:
<example>
init:
style.button.hover_background = Solid((255, 255, 255, 255))
style.button.idle_background = Solid(0, 0, 0, 255))
style.button.idle_background = Solid((0, 0, 0, 255))
</example>
@@ -1845,72 +2340,7 @@ init:
<dl>
<renpy_style name="default">The default style that all styles inherit from.</renpy_style>
<renpy_style name="window"><renpy_style_inherits>default</renpy_style_inherits>(window, placement) The base style for the windows that contain dialogue, thoughts, and menus.</renpy_style>
<renpy_style name="image_placement">This style is used to control the default placement of images on the screen.</renpy_style>
<renpy_style name="say_label"><renpy_style_inherits>default</renpy_style_inherits>(text) The style that is used by default for the label of dialogue. The label is used to indicate who is saying something.</renpy_style>
<renpy_style name="say_dialogue"><renpy_style_inherits>default</renpy_style_inherits>(text) The style that is used by default for the text of dialogue.</renpy_style>
<renpy_style name="say_thought"><renpy_style_inherits>default</renpy_style_inherits>(text) The label that is used by default for the text of thoughts or narration, when no speaker is given.</renpy_style>
<renpy_style name="say_window"><renpy_style_inherits>window</renpy_style_inherits>(window, position) The default style for windows containing dialogue and thoughts.</renpy_style>
<renpy_style name="menu_caption"><renpy_style_inherits>default</renpy_style_inherits>(text) The style that is used to render a menu caption.</renpy_style>
<renpy_style name="menu_selected"><renpy_style_inherits>default</renpy_style_inherits>(text) The style that is used to render a selected menu choice.</renpy_style>
<renpy_style name="menu_unselected"><renpy_style_inherits>default</renpy_style_inherits>(text) The style that is used to render an unselected menu choice.</renpy_style>
<renpy_style name="menu_window"><renpy_style_inherits>window</renpy_style_inherits>(window, position) The default style for windows containing a menu.</renpy_style>
<renpy_style name="input_text"><renpy_style_inherits>default</renpy_style_inherits>(text) The style used for the text of an input box.</renpy_style>
<renpy_style name="input_prompt"><renpy_style_inherits>default</renpy_style_inherits>(text) The style used for the prompt of an input box.</renpy_style>
<renpy_style name="input_window"><renpy_style_inherits>window</renpy_style_inherits>(window, position) The style used for the window of an input box.</renpy_style>
<renpy_style name="button"><renpy_style_inherits>default</renpy_style_inherits>(window, hover) The default style used for buttons in the main and game menus.</renpy_style>
<renpy_style name="button_text"><renpy_style_inherits>default</renpy_style_inherits>(text, hover) The default style used for the label of a button.</renpy_style>
<renpy_style name="selected_button"><renpy_style_inherits>button</renpy_style_inherits>(window, hover) The style that is used for a selected button (for example, the active screen or a chosen preference).</renpy_style>
<renpy_style name="selected_button_text"><renpy_style_inherits>button_text</renpy_style_inherits>(text, hover) The style that is used for the label of a selected button.</renpy_style>
<renpy_style name="disabled_button"><renpy_style_inherits>button</renpy_style_inherits>(window, hover) The style that is used for a disabled button.</renpy_style>
<renpy_style name="disabled_button_text"><renpy_style_inherits>button_text</renpy_style_inherits>(text, hover) The style that is used for the label of a disabled button.</renpy_style>
<renpy_style name="mm_root_window"><renpy_style_inherits>default</renpy_style_inherits>(window) The style used for the root window of the main menu. This is primarily used to set a background for the main menu.</renpy_style>
<renpy_style name="mm_menu_window"><renpy_style_inherits>default</renpy_style_inherits>(window, position) A window that contains the choices in the main menu. Change this to change the placement of these choices on the main menu screen.</renpy_style>
<renpy_style name="mm_button"><renpy_style_inherits>button</renpy_style_inherits>(window, hover) The style that is used on buttons that are part of the main menu.</renpy_style>
<renpy_style name="mm_button_text"><renpy_style_inherits>button_text</renpy_style_inherits>(text, hover) The style that is used for the labels of buttons that are part of the main menu.</renpy_style>
<renpy_style name="gm_root_window"><renpy_style_inherits>default</renpy_style_inherits>(window) The style used for the root window of the game menu. This is primarily used to change the background of the game menu.</renpy_style>
<renpy_style name="gm_nav_window"><renpy_style_inherits>default</renpy_style_inherits>(window, placement) The style used by a window containing buttons that allow the user to navigate through the different screens of the game menu.</renpy_style>
<renpy_style name="file_picker_window"><renpy_style_inherits>default</renpy_style_inherits>(window, placement) A window containing the file picker that is used to choose slots for loading and saving.</renpy_style>
<renpy_style name="file_picker_entry"><renpy_style_inherits>button</renpy_style_inherits>(window, hover) The style that is used for each of the slots in the file picker.</renpy_style>
<renpy_style name="file_picker_text"><renpy_style_inherits>default</renpy_style_inherits>(text) A base style for all text that is displayed in the file picker.</renpy_style>
<renpy_style name="file_picker_new"><renpy_style_inherits>file_picker_text</renpy_style_inherits>(text) The style that is applied to the new indicator in the file picker.</renpy_style>
<renpy_style name="file_picker_old"><renpy_style_inherits>file_picker_text</renpy_style_inherits>(text) The style that is applied to the old indicator in the file picker.</renpy_style>
<renpy_style name="file_picker_extra_info"><renpy_style_inherits>file_picker_text</renpy_style_inherits>(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.</renpy_style>
<renpy_style name="file_picker_new_slot"><renpy_style_inherits>file_picker_text</renpy_style_inherits>(text) The style that is used for the new slot indicator in the file picker.</renpy_style>
<!-- include styles.xml -->
</dl>
@@ -1965,6 +2395,47 @@ init:
that need to be loaded.
</p>
<h3>Localization</h3>
<p>
While Ren'Py is by default set up to operate in an English
speaking environment, it is not limited to such settings. Assuming
a proper font is loaded, Ren'Py scripts can contain any language
expressible in Unicode.
</p>
<p>
There are two things in the Ren'Py library that may need to be
translated into a user's language. The first is the main
menu. There is no explicit support for doing this, but as the
library.main_menu variable supports changing the text of the main
menu, it also supports translating said text.
</p>
<p>
The second thing that needs to be translated is the game menu. The
library.translations dictionary is used to translate text in the
game menu into your language. If a key in this map corresponds to
the English text that would be displayed, the value corresponding
to that key is displayed again. For example:
</p>
<example>
init:
$ library.translations = {
"Yes" : u"HIja'",
"No" : u"ghobe'",
# etc.
}
</example>
<p>
The u characters prefixed to the strings on the right, while not
strictly necessary in this case, are used to tell Python that the
string is in Unicode rather than ASCII. This is useful if your
language uses non-ascii characters.
</p>
<h3>Function Index</h3>
<funcindex/>
@@ -1981,6 +2452,15 @@ init:
<styleindex/>
<h3>Example Script</h3>
<p>
For your browsing pleasure, we present a syntax-highlighted
version of the <a href="example.html">demo that ships with
Ren'Py</a>. The start of this script has been commented to make
it easier to understand.
</p>
</doc>
<!-- (define-key xml-mode-map [(control return)] 'tompy-xml-ctrlret) -->
+184 -5
View File
@@ -1,9 +1,188 @@
; (X)Emacs mode. Requires python-mode to be installed to work.
;
; To use, M-x load-file renpy-mode.el RET M-x renpy-mode RET
(require 'speedbar)
(require 'python-mode)
; How we highlight a single keyword.
(defun renpy-keyword (kw)
(list
(concat "\\b" kw "\\b")
'( 0 font-lock-keyword-face)
)
)
; The big list of things we highlight.
(setq renpy-font-lock-keywords
(list
(renpy-keyword "at")
(renpy-keyword "call")
(renpy-keyword "hide")
(renpy-keyword "if")
(renpy-keyword "image")
(renpy-keyword "init")
(renpy-keyword "jump")
(renpy-keyword "menu")
(renpy-keyword "python")
(renpy-keyword "return")
(renpy-keyword "scene")
(renpy-keyword "set")
(renpy-keyword "show")
(renpy-keyword "with")
(renpy-keyword "while")
; Python keywords we want to keep highlighting.
(renpy-keyword "and")
(renpy-keyword "assert")
(renpy-keyword "break")
(renpy-keyword "class")
(renpy-keyword "continue")
(renpy-keyword "def")
(renpy-keyword "del")
(renpy-keyword "elif")
(renpy-keyword "else")
(renpy-keyword "except")
(renpy-keyword "exec")
(renpy-keyword "finally")
(renpy-keyword "for")
(renpy-keyword "from")
(renpy-keyword "global")
(renpy-keyword "import")
(renpy-keyword "in")
(renpy-keyword "is")
(renpy-keyword "lambda")
(renpy-keyword "not")
(renpy-keyword "or")
(renpy-keyword "pass")
(renpy-keyword "print")
(renpy-keyword "raise")
(renpy-keyword "try")
(renpy-keyword "yield")
'("\\$" (0 font-lock-keyword-face) )
'("\\b\\(label\\|menu\\)\\s-+\\(\\w+\\):" (1 font-lock-keyword-face) (2 font-lock-function-name-face))
'("\\b\\(from\\)\\s-+\\(\\w+\\)" (1 font-lock-keyword-face) (2 font-lock-function-name-face))
'("\\b\\(def\\|class\\)\\s-+\\(\\w+\\)" (1 font-lock-keyword-face) (2 font-lock-function-name-face))
))
(setq renpy-generic-imenu
'( ( "labels" "\\b\\(label\\|menu\\)\\s-+\\(\\w+\\):" 2)
( "labels" "\\bcall\\s-+\\w+\\s-+from\\s-+\\(\\w+\\)" 1)
( "python" "\\b\\(def\\|class\\)\\s-+\\(\\w+\\)" 2)
))
(defun renpy-mode ()
(interactive)
(python-mode)
(setq font-lock-keywords
(append '( ("\\b\\(menu\\|call\\|\\$\\|python\\|image\\|scene\\|show\\|hide\\|init\\|set\\|jump\\|at\\|with\\)\\b" (0 font-lock-keyword-face))
("\\b\\(label\\|menu\\)\\s-+\\(\\w+\\):" (1 font-lock-keyword-face) (2 font-lock-function-name-face))
) python-font-lock-keywords))
; (setq font-lock-keywords
; (append '( ("\\b\\(menu\\|call\\|\\$\\|python\\|image\\|scene\\|show\\|hide\\|init\\|set\\|jump\\|at\\|with\\)\\b" (0 font-lock-keyword-face))
; ("\\b\\(label\\|menu\\)\\s-+\\(\\w+\\):" (1 font-lock-keyword-face) (2 font-lock-function-name-face))
; ) python-font-lock-keywords))
(setq mode-name "RenPy")
(setq major-mode 'renpy-mode)
(setq imenu-create-index-function 'imenu-default-create-index-function)
(setq imenu-generic-expression renpy-generic-imenu)
(setq font-lock-keywords renpy-font-lock-keywords)
(setq semantic-toplevel-bovine-table nil)
(font-lock-mode 1)
(font-lock-fontify-buffer))
(font-lock-fontify-buffer)
(auto-fill-mode 1)
(setq indent-line-function 'renpy-indent-line)
(setq fill-paragraph-function 'renpy-fill-paragraph)
)
(speedbar-add-supported-extension ".rpy")
(setq auto-mode-alist (cons '("\\.rpy\\'" . renpy-mode) auto-mode-alist))
; Computes the start of the current string.
(defun renpy-string-start ()
(nth 8 (parse-partial-sexp (point-min) (point)))
)
; Computes the amount of indentation needed to put the current string
; in the right spot.
(defun renpy-string-indentation ()
(+ 1
(save-excursion
(- (goto-char (renpy-string-start))
(progn (beginning-of-line) (point)))
)
)
)
; Figures out the prefix, without the line indentation, required to
; get strings to line up right after a fill.
(defun renpy-string-fill-prefix ()
(make-string
(- (renpy-string-indentation)
0
; (save-excursion
; (goto-char (renpy-string-start))
; (current-indentation)
; )
) ?\ )
)
; Indents a paragraph. We also handle strings properly.
(defun renpy-fill-paragraph (&optional justify)
(interactive)
(if (eq (py-in-literal) 'string)
(let* ((string-indentation (renpy-string-indentation))
(fill-prefix (renpy-string-fill-prefix))
(fill-column (- fill-column string-indentation))
(fill-paragraph-function nil)
(indent-line-function nil)
)
; Fixup the fill.
;(save-excursion
; (goto-char (+ 1 (nth 8 (parse-partial-sexp (point-min) (point)))))
; (insert (make-string (renpy-string-indentation) ?\ ))
; )
(message "fill prefix: %S" fill-prefix)
; (py-fill-paragraph justify)
(py-fill-string (renpy-string-start))
;(save-excursion
; (goto-char (+ 1 (nth 8 (parse-partial-sexp (point-min) (point)))))
; (delete-char (renpy-string-indentation))
; )
t
)
(py-fill-paragraph justify)
)
)
; Indents the current line.
(defun renpy-indent-line (&optional arg)
(interactive)
; Let python-mode indent. (Always needed to keep python-mode sane.)
(py-indent-line)
; Reindent strings if appropriate.
(save-excursion
(beginning-of-line)
(if (eq (py-in-literal) 'string)
(progn
(delete-horizontal-space)
(indent-to (renpy-string-indentation))
)
))
(if ( < (current-column) (current-indentation) )
(back-to-indentation) )
)
+1
View File
@@ -18,6 +18,7 @@ import renpy.parser
import renpy.python # object
import renpy.script
import renpy.style
import renpy.sound
import renpy.display
import renpy.display.core # object
+36 -11
View File
@@ -96,25 +96,44 @@ class Node(object):
return [ self.next ]
def say_menu_with(expression):
"""
This handles the with clause of a say or menu statement.
"""
if expression is not None:
what = renpy.python.py_eval(expression)
elif renpy.store.default_transition and renpy.game.preferences.transitions == 2:
what = renpy.store.default_transition
else:
return
if not what:
return
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(what)
class Say(Node):
def __init__(self, loc, who, what):
def __init__(self, loc, who, what, with):
super(Say, self).__init__(loc)
self.who = who
self.what = what
self.with = with
def execute(self):
import renpy.exports as exports
if self.who is not None:
who = renpy.python.py_eval(self.who)
else:
who = None
exports.say(who, self.what % renpy.game.store)
say_menu_with(self.with)
renpy.exports.say(who, self.what % renpy.game.store)
return self.next
@@ -358,14 +377,20 @@ class With(Node):
super(With, self).__init__(loc)
self.expr = expr
def execute(self):
trans = renpy.python.py_eval(self.expr)
# Code copied into exports.with
if not trans:
sls = renpy.game.context().scene_lists
sls.replace_old_master()
renpy.game.interface.with_none()
else:
renpy.game.interface.interact(trans)
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(trans)
renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=True)
return self.next
@@ -403,11 +428,12 @@ class Return(Node):
class Menu(Node):
def __init__(self, loc, items, set):
def __init__(self, loc, items, set, with):
super(Menu, self).__init__(loc)
self.items = items
self.set = set
self.with = with
def get_children(self):
rv = [ ]
@@ -429,8 +455,6 @@ class Menu(Node):
def execute(self):
import renpy.exports as exports
choices = [ ]
for i, (label, condition, block) in enumerate(self.items):
@@ -439,7 +463,8 @@ class Menu(Node):
else:
choices.append((label, condition, i))
choice = exports.menu(choices, self.set)
say_menu_with(self.with)
choice = renpy.exports.menu(choices, self.set)
if choice is None:
return self.next
+27 -4
View File
@@ -23,11 +23,15 @@ debug = False
rollback_enabled = True
# If the rollback is longer than this, we may trim it.
rollback_length = 512
rollback_length = 128
# A list of Displayables that should always be added to the end
# of the scene list.
overlay = [ ]
# The maximum number of steps the user can rollback the game,
# interactively.
hard_rollback_limit = 10
# A list of functions returning lists of displayables that will be
# added to the end of the display list.
overlay_functions = [ ]
# A list of Displayables that should always be added to the start
# of the scene list. (Mostly used for keymaps and the like.)
@@ -59,6 +63,25 @@ skip_delay = 100
# Archive files that are searched for images.
archives = [ ]
# If True, we will only try loading from archives.
# Only useful for debugging Ren'Py, don't document.
force_archives = False
# An image file containing the mouse cursor, if one is defined.
mouse = None
# The distance the keyboard moves the mouse, per 50 ms tick, in pixels.
keymouse_distance = 5
# The default sound playback sample rate.
sound_sample_rate = 44100
# How fast text is displayed on the screen, by default.
annoying_text_cps = None
# The amount of time music is faded out between tracks.
fade_music = 0.0
_globals = globals().copy()
def reload():
+53 -7
View File
@@ -43,6 +43,35 @@ class Keymap(renpy.display.layout.Container):
def render(self, width, height, st):
return None
class KeymouseBehavior(renpy.display.layout.Null):
"""
This is a class that causes the keyboard to move the mouse. It's
useful on the game and key menus, as well as in imagemaps and the
like.
"""
def event(self, ev, x, y):
if ev.type == renpy.display.core.DISPLAYTIME:
pressed = pygame.key.get_pressed()
x, y = pygame.mouse.get_pos()
ox, oy = x, y
if pressed[K_LEFT]:
x -= renpy.config.keymouse_distance
if pressed[K_RIGHT]:
x += renpy.config.keymouse_distance
if pressed[K_UP]:
y -= renpy.config.keymouse_distance
if pressed[K_DOWN]:
y += renpy.config.keymouse_distance
if (x, y) != (ox, oy):
pygame.mouse.set_pos((x, y))
return None
class SayBehavior(renpy.display.layout.Null):
"""
This is a class that implements the say behavior,
@@ -62,7 +91,7 @@ class SayBehavior(renpy.display.layout.Null):
if ev.type == renpy.display.core.DISPLAYTIME and \
self.delay and \
ev.duration > self.delay:
return True
return False
if ev.type == MOUSEBUTTONDOWN:
if ev.button == 1:
@@ -108,6 +137,13 @@ class Menu(renpy.display.layout.VBox):
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.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))
@@ -128,14 +164,14 @@ class Menu(renpy.display.layout.VBox):
# Captions should stay the default text color.
if result is None:
child.set_style('menu_caption')
child.set_style(self.caption_style)
continue
# Actual choices change color if they are selected or not.
if i == self.selected:
child.set_style('menu_selected')
child.set_style(self.selected_style)
else:
child.set_style('menu_unselected')
child.set_style(self.unselected_style)
def event(self, ev, x, y):
@@ -164,6 +200,7 @@ class Menu(renpy.display.layout.VBox):
return None
if self.results[target] is not None:
renpy.sound.play(self.selected_style.activate_sound)
return self.results[target]
# Change selection based on keypress.
@@ -190,13 +227,16 @@ class Menu(renpy.display.layout.VBox):
# Make selection based on keypress.
if ev.type == KEYDOWN and ev.key == K_RETURN:
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('menu_selected')
self.children[old_selected].set_style('menu_unselected')
self.children[self.selected].set_style(self.selected_style)
self.children[old_selected].set_style(self.unselected_style)
renpy.sound.play(self.selected_style.hover_sound)
renpy.game.interface.redraw(0)
@@ -241,8 +281,14 @@ class Button(renpy.display.layout.Window):
self.old_hover = inside
self.set_hover(inside)
if ev.type == MOUSEBUTTONDOWN and ev.button == 1:
if inside:
renpy.sound.play(self.style.hover_sound)
if (ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
(ev.type == KEYDOWN and ev.key == K_RETURN):
if inside:
renpy.sound.play(self.style.activate_sound)
return self.clicked()
return None
+221 -48
View File
@@ -33,6 +33,18 @@ class Displayable(renpy.object.Object):
their fields.
"""
def parameterize(self, name, parameters):
"""
Called to parameterize this. By default, we don't take any
parameters.
"""
if parameters:
raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" %
(' '.join(name), ' '.join(parameters)))
return self
def render(self, width, height, shown_time):
"""
Called to display this displayable. This is called with width
@@ -163,7 +175,6 @@ class SceneLists(object):
drawn.
@ivar master: The current master display list.
@ivar old_master: The MDL that was last shown to the user.
@ivar transient: The current transient display list.
@ivar overlay: The current overlay display list.
@@ -174,7 +185,6 @@ class SceneLists(object):
def __init__(self, oldsl=None):
if oldsl:
self.old_master = oldsl.old_master[:]
self.master = oldsl.master[:]
self.replace_transient()
@@ -183,7 +193,6 @@ class SceneLists(object):
self.music = oldsl.music
else:
self.old_master = [ ]
self.master = [ ]
self.transient = [ ]
self.overlay = [ ]
@@ -196,10 +205,8 @@ class SceneLists(object):
"""
rv = SceneLists()
rv.old_master = self.old_master[:]
rv.master = self.master[:]
rv.transient = self.transient[:]
rv.overlay = self.overlay[:]
rv.music = self.music
@@ -216,14 +223,6 @@ class SceneLists(object):
self.transient = [ ]
def replace_old_master(self):
"""
Replaces the contents of the old master display list with
a copy of the current master display list.
"""
self.old_master = self.master[:]
def add(self, listname, thing, key=None):
"""
This is called to add something to a display list. Listname is
@@ -291,14 +290,6 @@ class SceneLists(object):
setattr(self, listname, nl)
def set_overlay(self, new_overlay):
"""
This replaces the overlay scene list with the provided overlay
scene list.
"""
self.overlay = [ (None, time.time(), i) for i in new_overlay ]
def render_scene_list(sl, width, height):
"""
This renders the scene list sl, and returns a rendered
@@ -338,11 +329,19 @@ class Display(object):
@ivar window: The window that is being presented to the user.
@ivar sample_surface: A sample surface that is optimized for
fast blitting to thew window. Used to create other surfaces from.
@ivar buffer: A surface that buffers the window.
@ivar sample_surface: A sample surface that is optimized for fast
blitting to the window, with alpha. Used to create other surfaces
from.
@ivar fullscreen: Is the window in fullscreen mode?
@ivar mouse: The mouse image, if we have one, or None if
we do not have one.
@ivar mouse_location: The mouse location the last time it was
drawn, or None if it wasn't drawn the last time around.
"""
@@ -350,6 +349,7 @@ class Display(object):
# It shouldn't matter if pygame is already initialized.
pygame.init()
renpy.sound.init()
self.fullscreen = renpy.game.preferences.fullscreen
fsflag = 0
@@ -357,18 +357,71 @@ class Display(object):
if self.fullscreen:
fsflag = FULLSCREEN
# The window we display things in.
self.window = pygame.display.set_mode((renpy.config.screen_width,
renpy.config.screen_height),
fsflag)
pygame.event.set_grab(False)
pygame.mouse.set_visible(True)
pygame.display.set_caption(renpy.config.window_title)
# The mouse buffer.
if renpy.config.mouse:
self.buffer = pygame.Surface((renpy.config.screen_width,
renpy.config.screen_height))
# Sample surface that all surfaces are created based on.
self.sample_surface = self.window.convert_alpha()
pygame.event.set_grab(False)
# Window title.
pygame.display.set_caption(renpy.config.window_title)
# Load the mouse image, if any.
if renpy.config.mouse:
self.mouse = renpy.display.image.cache.load_image(renpy.config.mouse)
pygame.mouse.set_visible(False)
else:
self.mouse = None
pygame.mouse.set_visible(True)
self.mouse_location = None
def draw_mouse(self):
"""
This draws the mouse to the screen, if necessary. It uses the
buffer to minimize the amount of the screen that needs to be
drawn, and only redraws if the mouse has actually been moved.
"""
if not self.mouse:
return
mw, mh = self.mouse.get_size()
pos = pygame.mouse.get_pos()
if not pygame.mouse.get_focused():
pos = None
flip = False
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
if pos and (pos != self.mouse_location):
self.window.blit(self.mouse, pos)
flip = True
self.mouse_location = pos
if flip:
pygame.display.flip()
def show(self, transient):
"""
Draws the current transient screen list to the screen.
@@ -383,7 +436,15 @@ class Display(object):
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):
@@ -416,12 +477,26 @@ class Interface(object):
@ivar display: The display that we used to display the screen.
@ivar needs_reraw: True if we need a redraw now.
@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.
@ivar transition: If not None, the transition to be applied for the
next interaction.
@ivar supress_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.
"""
def __init__(self):
@@ -430,6 +505,10 @@ class Interface(object):
self.profile_time = time.time()
self.screenshot = None
self.redraw_time = 0.0
self.old_scene = [ ]
self.transition = None
self.supress_transition = False
self.quick_quit = False
def take_screenshot(self, scale):
"""
@@ -470,13 +549,54 @@ class Interface(object):
self.needs_redraw = True
else:
self.redraw_time = min(self.redraw_time, time.time() + delay)
def interact(self, transition=None):
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):
"""
Sets the transition that will be performed as part of the next
interaction.
"""
self.transition = transition
def interact(self, transient=None, 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.
@param trans_pause: If given, we must have a transition. Should we
add a pause behavior during the transition?
@param suppress_overlay: This suppresses the display of the overlay.
@param suppress_underlay: This suppresses the display of the underlay.
"""
## Safety condition, prevents deadlocks.
if trans_pause:
if not self.transition:
return None
if self.supress_transition:
return None
## Expensive things we want to do before we pick the start_time.
# Tick time forward.
@@ -495,22 +615,52 @@ class Interface(object):
start_time = time.time()
scene_lists = renpy.game.context().scene_lists
underlay = [ ( None, 0, i) for i in renpy.config.underlay ]
overlay = [ ( None, 0, i ) for i in renpy.config.overlay ]
# Compute the overlay, by calling the overlay functions.
overlay = [ ]
if not suppress_overlay:
for i in renpy.config.overlay_functions:
for j in i():
overlay.append((None, 0, j))
# Perhaps apply transition.
master = scene_lists.master
if transition:
master = [ ( None, start_time,
transition(scene_lists.old_master,
scene_lists.master) ) ]
if not suppress_underlay:
underlay = [ ( None, 0, i) for i in renpy.config.underlay ]
else:
underlay = [ ]
transient = underlay + master + scene_lists.transient + scene_lists.overlay + overlay
# Set up the transient scene list.
if transient:
transient = [ (None, start_time, i) for i in transient ]
else:
transient = scene_lists.transient
# Figure out the display list.
current_scene = scene_lists.master + transient + overlay
if self.transition and not self.supress_transition:
trans = self.transition(self.old_scene, current_scene)
transition_scene = [ (None, start_time, trans) ]
if trans_pause:
sb = renpy.display.behavior.SayBehavior(delay=trans.delay)
transition_scene.append((None, start_time, sb))
else:
transition_scene = current_scene
self.transition = None
self.supress_transition = False
# The list of things to be displayed.
display_list = underlay + transition_scene
# This list of things recieving events.
event_list = display_list[:]
# Compute the reversed list, which is in the right order
# for handling events on.
rev_transient = transient[:]
rev_transient.reverse()
event_list.reverse()
# Redraw the screen during every interaction.
self.needs_redraw = True
@@ -520,6 +670,10 @@ class Interface(object):
# (The same order as rev_transient.)
offsets = [ ]
# Post an event that moves us to the current mouse position.
pygame.event.post(pygame.event.Event(MOUSEMOTION,
pos=pygame.mouse.get_pos()))
rv = None
while rv is None:
@@ -534,7 +688,7 @@ class Interface(object):
draw_start = time.time()
self.redraw_time = draw_start + 365.25 * 86400.0
offsets = self.display.show(transient)
offsets = self.display.show(display_list)
offsets.reverse()
# If profiling is enabled, report the profile time.
@@ -543,6 +697,10 @@ class Interface(object):
print "Profile: Redraw took %f seconds." % (new_time - draw_start)
print "Profile: %f seconds between event and display." % (new_time - self.profile_time)
# Draw the mouse, if it needs drawing.
if show_mouse:
self.display.draw_mouse()
# Update the playing music, if necessary.
renpy.music.restore()
@@ -575,7 +733,12 @@ class Interface(object):
# Handle quit specially for now.
if ev.type == QUIT:
raise renpy.game.QuitException()
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()
# Merge mousemotion events.
if ev.type == MOUSEMOTION:
@@ -584,9 +747,11 @@ class Interface(object):
if len(evs):
ev = evs[-1]
x, y = getattr(ev, 'pos', (0, 0))
# x, y = getattr(ev, 'pos', (0, 0))
for (k, t, d), (xo, yo) in zip(rev_transient, offsets):
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)
if rv is not None:
@@ -598,9 +763,17 @@ class Interface(object):
if time.time() > self.redraw_time:
self.needs_redraw = True
pygame.event.clear()
pygame.time.set_timer(KEYREPEATEVENT, 0)
pygame.event.clear()
scene_lists.replace_transient()
scene_lists.replace_old_master()
# 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
+125 -10
View File
@@ -1,5 +1,6 @@
import renpy
import pygame
from pygame.constants import *
class ImageCache(object):
@@ -35,7 +36,7 @@ class ImageCache(object):
im = pygame.image.load(renpy.loader.load(fn), fn)
im = im.convert_alpha()
iw, ih = im.get_size()
# iw, ih = im.get_size()
# surf = renpy.display.surface.Surface(iw, ih)
# surf.blit(im, (0, 0))
@@ -133,18 +134,23 @@ class Image(renpy.display.core.Displayable):
on disk.
"""
def __init__(self, filename):
def __init__(self, filename, style='image_placement', **properties):
"""
@param filename: The filename that the image is loaded from. Many common file formats are supported.
"""
self.filename = filename
self.style = renpy.style.Style(style, properties)
def render(self, w, h, st):
return cache.load_image(self.filename)
im = cache.load_image(self.filename)
w, h = im.get_size()
rv = renpy.display.surface.Surface(w, h)
rv.blit(im, (0, 0))
return rv
def get_placement(self):
return renpy.game.style.image_placement
return self.style
def predict(self, callback):
callback(self.filename)
@@ -154,14 +160,17 @@ class UncachedImage(renpy.display.core.Displayable):
An image that is loaded immediately and not cached.
"""
def __init__(self, file, hint=None, scale=None):
def __init__(self, file, hint=None, scale=None, style='image_placement',
**properties):
self.surf = pygame.image.load(file, hint)
if scale:
self.surf = pygame.transform.scale(self.surf, scale)
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return renpy.game.style.image_placement
return self.style
def render(self, w, h, st):
sw, sh = self.surf.get_size()
@@ -194,10 +203,38 @@ class ImageReference(renpy.display.core.Displayable):
def find_target(self):
import renpy.exports as exports
if self.name in exports.images:
self.target = exports.images[self.name]
else:
self.target = renpy.display.text.Text("Image %s not found." % repr(self.name), color=(255, 0, 0, 255))
name = self.name
parameters = [ ]
def error(msg):
self.target = renpy.display.text.Text(msg,
color=(255, 0, 0, 255))
if renpy.config.debug:
raise Exception(msg)
# Scan through, searching for an image (defined with an
# input statement) that is a prefix of the given name.
while name:
if name in exports.images:
target = exports.images[name]
try:
self.target = target.parameterize(name, parameters)
except Exception, e:
if renpy.config.debug:
raise
error(str(e))
return
else:
parameters.insert(0, name[-1])
name = name[:-1]
error("Image '%s' not found." % ' '.join(self.name))
def render(self, width, height, st):
@@ -208,8 +245,12 @@ class ImageReference(renpy.display.core.Displayable):
return self.target.render(width, height, st)
def get_placement(self):
if not hasattr(self, 'target'):
self.find_target()
return self.target.get_placement()
class Solid(renpy.display.core.Displayable):
"""
Returns a Displayable that is solid, and filled with a single
@@ -403,3 +444,77 @@ class Animation(renpy.display.core.Displayable):
def get_placement(self):
return renpy.game.style.image_placement
class ImageMap(renpy.display.core.Displayable):
"""
The displayable that implements renpy.imagemap.
"""
def __init__(self, ground, selected, hotspots,
style='imagemap', **properties):
self.ground = ground
self.selected = selected
self.hotspots = hotspots
self.active = None
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
def predict(self, callback):
callback(i.ground)
callback(i.selected)
def render(self, width, height, st):
ground = cache.load_image(self.ground)
selected = cache.load_image(self.selected)
width, height = ground.get_size()
rv = renpy.display.surface.Surface(width, height)
rv.blit(ground, (0, 0))
if self.active is not None:
x0, y0, x1, y1, result = self.hotspots[self.active]
subsurface = selected.subsurface((x0, y0, x1-x0, y1-y0))
rv.blit(subsurface, (x0, y0))
return rv
def event(self, ev, x, y):
old_active = self.active
active = None
for i, (x0, y0, x1, y1, result) in enumerate(self.hotspots):
if x >= x0 and x <= x1 and y >= y0 and y <= y1:
active = i
break
# result stays set.
if old_active != active:
self.active = active
renpy.game.interface.redraw(0)
if active is not None:
renpy.sound.play(self.style.hover_sound)
if active is None:
return None
if (ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
(ev.type == KEYDOWN and ev.key == K_RETURN):
renpy.sound.play(self.style.activate_sound)
return result
return None
+134
View File
@@ -379,3 +379,137 @@ class Window(Container):
self.window_size = width, height
return rv
class Pan(Container):
"""
This is used to pan over a child displayable, which is almost
always an image. It works by interpolating the placement of the
upper-left corner of the image, over time. It's only really
suitable for use with images that are larger than the screen, as
we don't do any cropping on the image.
"""
def __init__(self, startpos, endpos, time, child,
style='image_placement', **properties):
"""
@param child: The child displayable.
@param startpos: The initial coordinates of the upper-left
corner of the screen, relative to the image.
@param endpos: The coordinates of the upper-left corner of the
screen, relative to the image, after time has elapsed.
@param time: The time it takes to pan from startpos to endpos.
"""
super(Pan, self).__init__()
self.add(child)
self.startpos = startpos
self.endpos = endpos
self.time = time
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
def render(self, width, height, st):
surf = self.child.render(width, height, st)
self.sizes = [ surf.get_size() ]
x0, y0 = self.startpos
x1, y1 = self.endpos
if self.time > 0:
tfrac = (st / self.time)
else:
tfrac = 1.0
if tfrac > 1.0:
tfrac = 1.0
xo = int(x0 * (1.0 - tfrac) + x1 * tfrac)
yo = int(y0 * (1.0 - tfrac) + y1 * tfrac)
self.offsets = [ (-xo, -yo) ]
rv = renpy.display.surface.Surface(width, height)
# print surf
subsurf = surf.subsurface((xo, yo, width, height))
rv.blit(subsurf, (0, 0))
# rv.blit(surf, (-xo, -yo))
if st < self.time:
renpy.game.interface.redraw(0)
return rv
class Move(Container):
"""
This moves a child relative to the thing containing it. This
motion is done by manipulating the xpos and ypos properties in a
placement style.
"""
def __init__(self, startpos, endpos, time, child,
style='default', **properties):
super(Move, self).__init__()
self.add(child)
self.startpos = startpos
self.endpos = endpos
self.time = time
self.st = 0.0
self.style = renpy.style.Style(style, properties)
def get_placement(self):
st = self.st
x0, y0 = self.startpos
x1, y1 = self.endpos
if self.time > 0:
tfrac = (st / self.time)
else:
tfrac = 1.0
if tfrac > 1.0:
tfrac = 1.0
xo = x0 * (1.0 - tfrac) + x1 * tfrac
yo = y0 * (1.0 - tfrac) + y1 * tfrac
if isinstance(x1, int):
xo = int(xo)
if isinstance(y1, int):
yo = int(yo)
self.style.xpos = xo
self.style.ypos = yo
return self.style
def render(self, width, height, st):
self.st = st
rv = self.child.render(width, height, st)
self.sizes = [ rv.get_size() ]
self.offsets = [ (0, 0) ]
if st < self.time:
renpy.game.interface.redraw(0)
return rv
+54
View File
@@ -100,3 +100,57 @@ class Surface(object):
self.blit_to(rv, 0, 0)
return rv
def subsurface(self, (x, y, width, height)):
"""
Returns the subsurface of this surface, similar to
pygame.Surface.subsurface
"""
if x > self.width or y > self.height:
return Surface(0, 0)
width = min(self.width - x, width)
height = min(self.height - y, height)
rv = Surface(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()
if sw - ox <= 0:
continue
if sh - oy <= 0:
continue
sw = min(sw - sx - ox, width)
sh = min(sh - sy - oy, height)
rv.blit(source.subsurface((sx, sy, sw, sh)),
(ox, oy))
return rv
+52 -29
View File
@@ -1,4 +1,6 @@
import pygame
from pygame.constants import *
import renpy
_font_cache = { }
@@ -37,10 +39,12 @@ class Text(renpy.display.core.Displayable):
"""
def __init__(self, text, style='default', **properties):
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.
@@ -49,6 +53,7 @@ class Text(renpy.display.core.Displayable):
self.text = text
self.style = renpy.style.Style(style, properties)
self.slow = slow
def get_placement(self):
return self.style
@@ -61,12 +66,12 @@ class Text(renpy.display.core.Displayable):
self.text = new_text
self._update()
def set_style(self, style, **properties):
def set_style(self, style):
"""
Changes the style assocated with this object.
"""
self.style = renpy.style.Style(style, properties)
self.style = style
self._update()
def _update(self):
@@ -143,13 +148,29 @@ class Text(renpy.display.core.Displayable):
surf = renpy.display.surface.Surface(self.width + dsxo, self.height + dsyo)
font = get_font(self.style.font, self.style.size)
lines = self.laidout.split('\n')
laidout = self.laidout
# Annoying text hack.
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
chars = int(st * renpy.config.annoying_text_cps)
if chars < len(laidout):
laidout = laidout[:chars]
renpy.game.interface.redraw(0)
else:
self.slow = False
else:
self.slow = False
lines = laidout.split('\n')
# Common rendering code.
def render_lines(x, y, color):
for l in lines:
ls = font.render(l, True, color)
surf.blit(ls, (x, y + font.get_descent()))
lw, lh = ls.get_size()
xo = int((self.width - lw) * self.style.textalign)
surf.blit(ls, (x + xo, y + font.get_descent()))
y += font.get_linesize() + self.style.line_height_fudge
fudge = 1
@@ -162,37 +183,39 @@ class Text(renpy.display.core.Displayable):
render_lines(0, 0 + fudge, self.style.color)
return surf
class ExpressionText(Text):
"""
This is a Displayable that displays the result of an expression on
the screen, as text.
"""
def __init__(self, expression, style='default', **properties):
def event(self, ev, x, y):
"""
@param expression: An expression that is evaluated to get the
text that will be shown on the screen.
@param style: A style that will be applied to the text.
@param properties: Additional properties that are applied to
the text.
Space, Enter, or Click ends slow, if it's enabled.
"""
super(ExpressionText, self).__init__('', **properties)
if not self.slow:
return None
self.old_value = ''
self.expression = expression
if ( ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
( ev.type == KEYDOWN and (ev.key == K_RETURN or ev.key == K_SPACE)):
def render(self, width, height, st):
self.slow = False
raise renpy.display.core.IgnoreEvent()
value = renpy.python.py_eval(self.expression)
value = str(value)
class ParameterizedText(object):
"""
This can be used as an image. When used, this image is expected to
have a single parameter, a string which is rendered as the image.
"""
if value != self.old_value:
self.old_value = value
self.set_text(value)
def __init__(self, style='default', **properties):
self.style = style
self.properties = properties
return super(ExpressionText, self).render(width, height, st)
def parameterize(self, name, parameters):
if len(parameters) != 1:
raise Exception("'%s' takes a single string parameter." %
' '.join(name))
param = parameters[0]
string = renpy.python.py_eval(param)
return Text(string, style=self.style, **self.properties)
+26 -7
View File
@@ -10,11 +10,22 @@ class Transition(renpy.display.core.Displayable):
"""
def __init__(self, delay):
self.saybehavior = renpy.display.behavior.SayBehavior(delay=delay)
self.delay = delay
self.offsets = [ ]
def event(self, ev, x, y):
return self.saybehavior.event(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
class Fade(Transition):
"""
@@ -46,6 +57,8 @@ class Fade(Transition):
rv = renpy.display.surface.Surface(width, height)
events = False
if st < self.out_time:
scene_list = self.old_scene_list
alpha = int(255 * (st / self.out_time))
@@ -57,6 +70,7 @@ class Fade(Transition):
else:
scene_list = self.new_scene_list
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,
@@ -64,6 +78,9 @@ class Fade(Transition):
height)
rv.blit(surf, (0, 0))
if events:
self.offsets = offsets
# Just to be sure.
if alpha < 0:
alpha = 0
@@ -73,7 +90,8 @@ class Fade(Transition):
rv.fill(self.color[:3] + (alpha,))
renpy.game.interface.redraw(0)
if st < self.in_time + self.hold_time + self.out_time:
renpy.game.interface.redraw(0)
return rv
@@ -91,7 +109,7 @@ class Dissolve(Transition):
rsl = renpy.display.core.render_scene_list
rv, offsets = rsl(self.old_scene_list, width, height)
surftree, offsets = rsl(self.new_scene_list, width, height)
surftree, self.offsets = rsl(self.new_scene_list, width, height)
surf = surftree.pygame_surface(False)
alpha = min(255, int(255 * st / self.time))
@@ -99,7 +117,8 @@ class Dissolve(Transition):
surf.set_alpha(alpha)
rv.blit(surf, (0, 0))
renpy.game.interface.redraw(0)
if st < self.time:
renpy.game.interface.redraw(0)
return rv
+104 -42
View File
@@ -12,6 +12,7 @@ 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.loadsave import *
import time
@@ -29,12 +30,8 @@ def checkpoint():
renpy.game.log.checkpoint()
def interact(*widgets):
for i in widgets:
scene_list_add('transient', i)
return renpy.game.interface.interact()
def interact(*widgets, **kwargs):
return renpy.game.interface.interact(transient=widgets, **kwargs)
def scene_lists(index=-1):
"""
@@ -96,33 +93,21 @@ def scene():
sls.clear('master')
sls.clear('transient')
def set_overlay(overlay_list):
"""
Sets the overlay list that will be used to display things at the
top of the screen. Clearing out the scene lists will not change
what's displayed in the overlay.
@param overlay_list: A list of displayables that will be displayed
above the current scene.
"""
scene_lists().set_overlay(overlay_list)
def watch(expression):
def watch(expression, style='default', **properties):
"""
This watches the given python expression, by displaying it in the
upper-right corner of the screen. The expression should always be
upper-right corner of the screen (although position properties
can change that). The expression should always be
defined, never throwing an exception.
This will replace any overlay defined by the program.
A watch will not persist through a save or restart.
"""
text = renpy.display.text.ExpressionText(expression)
pos = renpy.display.layout.Position(text,
xpos=1.0, xanchor='right',
ypos=0.0, yanchor='top')
def overlay_func():
return [ renpy.display.text.Text(renpy.python.py_eval(expression),
style=style, **properties) ]
set_overlay([ pos ])
renpy.config.overlay_functions.append(overlay_func)
def input(prompt, default='', length=None):
"""
@@ -192,9 +177,7 @@ def display_menu(items, window_style='menu_window'):
menu = Menu(items)
win = Window(menu, style=window_style)
scene_list_add('transient', win)
rv = interact()
rv = interact(win)
checkpoint()
return rv
@@ -232,39 +215,107 @@ def display_say(who, what, who_style='say_label',
import renpy.display.text as text
import renpy.display.behavior as behavior
if who is None:
who = ""
else:
if who is not None:
who = who + ": "
vbox = layout.VBox(padding=10)
label = text.Text(who, style=who_style, **properties)
vbox.add(label)
if who is not None:
label = text.Text(who, style=who_style, **properties)
vbox.add(label)
line = text.Text(what, style=what_style)
line = text.Text(what, style=what_style, slow=True)
vbox.add(line)
window = layout.Window(vbox, style=window_style)
saybehavior = behavior.SayBehavior()
scene_list_add('transient', window)
scene_list_add('transient', saybehavior)
interact()
interact(saybehavior, window)
checkpoint()
def pause(delay=None):
def imagemap(ground, selected, hotspots, overlays=False,
style='imagemap', **properties):
"""
Displays an imagemap. An image map consists of two images and a
list of hotspots that are defined on that image. When the user
clicks on a hotspot, the value associated with that hotspot is
returned.
@param ground: The name of the file containing the ground
image. The ground image is displayed in hotspots that the mouse is
not over, and for areas that are not part of any hotspots.
@param selected: The name of the file containing the selected
image. This image is displayed in hotspots when the mouse is over
them.
@param hotspots: A list of tuples defining the hotspots in this
image map. Each tuple has the format (x0, y0, x1, y1, result).
(x0, y0) gives the coordinates of the upper-left corner of the
hotspot, (x1, y1) gives the lower-right corner, and result gives
the value returned from this function if the mouse is clicked in
the hotspot.
@param overlay: If True, overlays are displayed when this imagemap
is active. If False, the overlays are suppressed.
"""
imagemap = ImageMap(ground, selected, hotspots, style=style, **properties)
keymouse = KeymouseBehavior()
rv = interact(keymouse, imagemap)
checkpoint()
return rv
def pause(delay=None, music=None):
"""
When called, this pauses and waits for the user to click before
advancing the script.
@param delay: The number of seconds to delay.
@param music: If supplied, this gives the number of seconds into
the background music that we will delay until. If music is
playing, this takes precedence, otherwise the delay parameter
take precedence.
Returns True if the pause was interrupted by the user hitting a key
or clicking a mouse, or False if the pause was ended by the appointed
time being reached.
"""
if music is not None:
newdelay = renpy.music.music_delay(music)
if newdelay is not None:
delay = newdelay
sayb = renpy.display.behavior.SayBehavior(delay=delay)
scene_list_add('transient', sayb)
interact()
return interact()
def with(trans):
"""
Behaves identically to a with statement. The only reason to use this
over a Ren'Py with statement is to get at the return code, which is
True if the transition was interrupted, or False otherwise.
"""
# Code basically copied from ast.With.execute.
if not trans:
renpy.game.interface.with_none()
return False
else:
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(trans)
return renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=True)
else:
return False
def rollback():
"""
@@ -346,6 +397,17 @@ def windows():
import sys
return hasattr(sys, 'winver')
def transition(trans):
"""
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.
"""
if trans is None:
renpy.game.interface.with_none()
else:
renpy.game.interface.set_transition(trans)
call_in_new_context = renpy.game.call_in_new_context
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
+38 -5
View File
@@ -11,10 +11,13 @@
import renpy
# The basepath. Everything that is loaded is relative to this, at least
# until we get around to implementing archive files.
# The basepath.
basepath = None
# A list of paths that we search to load things. This is searched for
# everything that can be loaded, before archives are used.
searchpath = [ ]
# A Script object, giving the script of the currently executing game.
script = None
@@ -54,17 +57,47 @@ seen_session = { }
# The set of statements we've ever seen.
seen_ever = { }
# The class that's used to hold the persistent data.
class Persistent(object):
def __setstate__(self, data):
vars(self).update(data)
def __getstate__(self):
return vars(self)
# Undefined attributes return None.
def __getattr__(self, attr):
return None
# The persistent data that's kept from session to session
persistent = None
class Preferences(object):
"""
Stores preferences that will one day be persisted.
"""
def __init__(self):
def reinit(self):
self.fullscreen = False
self.sound = True
self.music = True
self.skip_unseen = False
self.fast_text = False
preferences = Preferences()
# 2 - All transitions.
# 1 - Only non-default transitions.
# 0 - No transitions.
self.transitions = 2
def __setstate__(self, state):
self.reinit()
vars(self).update(state)
def __init__(self):
self.reinit()
# The current preferences.
preferences = None
class RestartException(Exception):
"""
+32 -13
View File
@@ -14,12 +14,14 @@ def index_archives():
archives = [ ]
for prefix in renpy.config.archives:
fn = transfn(prefix + ".rpi")
index = loads(file(fn, "rb").read().decode("zlib"))
print index.keys()
archives.append((prefix, index))
try:
fn = transfn(prefix + ".rpi")
index = loads(file(fn, "rb").read().decode("zlib"))
archives.append((prefix, index))
except:
if renpy.config.debug:
raise
def load(name):
"""
@@ -27,20 +29,28 @@ def load(name):
"""
# Look for the file directly.
fn = transfn(name)
if os.path.exists(fn):
return file(fn, "rb")
if not renpy.config.force_archives:
try:
fn = transfn(name)
return file(fn, "rb")
except:
pass
# Look for it in archive files.
for prefix, index in archives:
if not name in index:
continue
offset, dlen = index[name]
f = file(transfn(prefix + ".rpa"), "rb")
f.seek(offset)
rv = StringIO(f.read(dlen))
data = [ ]
for offset, dlen in index[name]:
f.seek(offset)
data.append(f.read(dlen))
rv = StringIO(''.join(data))
f.close()
return rv
@@ -48,4 +58,13 @@ def load(name):
raise Exception("Couldn't find file '%s'." % name)
def transfn(name):
return renpy.game.basepath + "/" + name
"""
Tries to translate the name to a file that exists in one of the
searched directories.
"""
for d in renpy.game.searchpath:
if os.path.exists(d + "/" + name):
return d + "/" + name
raise Exception("Couldn't find file '%s'." % name)
+28 -15
View File
@@ -12,7 +12,7 @@
import renpy
import renpy.game as game
import os
from cPickle import load, dumps, HIGHEST_PROTOCOL
from cPickle import loads, dumps, HIGHEST_PROTOCOL
def run():
"""
@@ -20,6 +20,9 @@ def run():
will cause this to change.
"""
# Initialize the log.
game.log = renpy.python.RollbackLog()
# Reload some things, in case this is a restart.
renpy.store.reload()
renpy.config.reload()
@@ -32,31 +35,40 @@ def run():
except:
pass
# Unserialize the set of seen statements.
# Unserialize the persistent data.
try:
game.seen_ever = load(file(renpy.config.savedir + "/seen"))
f = file(renpy.config.savedir + "/persistent", "r")
s = f.read().decode("zlib")
f.close()
game.persistent = loads(s)
except:
print "Couldn't load seen statements."
game.seen_ever = { }
game.persistent = game.Persistent()
# Initialize the set of statements seen ever.
if not game.persistent._seen_ever:
game.persistent._seen_ever = { }
game.seen_ever = game.persistent._seen_ever
# Clear the list of seen statements in this game.
game.seen_session = { }
# Initialize the preferences.
if not game.persistent._preferences:
game.persistent._preferences = game.Preferences()
game.preferences = game.persistent._preferences
# Initialize the store.
renpy.store.store = renpy.store
game.store = vars(renpy.store)
renpy.store.preferences = game.preferences
renpy.store.persistent = game.persistent
renpy.store._preferences = game.preferences
# Set up styles.
game.style = renpy.style.StyleManager()
renpy.store.style = game.style
# Initialize the log.
game.log = renpy.python.RollbackLog()
# Run init code in its own context. (Don't log.)
game.contexts = [ renpy.execution.Context(False) ]
@@ -107,16 +119,17 @@ def run():
except game.QuitException, e:
break
finally:
f = file(renpy.config.savedir + "/seen", "wb")
f.write(dumps(game.seen_ever))
f = file(renpy.config.savedir + "/persistent", "wb")
f.write(dumps(game.persistent).encode("zlib"))
f.close()
# And, we're done.
def main(basepath_):
def main(basepath):
game.basepath = basepath_
game.basepath = basepath
game.searchpath = [ "common", basepath ]
# Load the script.
game.script = renpy.script.load_script(game.basepath)
+26 -9
View File
@@ -6,6 +6,23 @@ import renpy
# 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.
"""
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,
@@ -53,17 +70,17 @@ def restore():
if current_music == new_music:
return
current_music = new_music
# Usually, ignore errors.
try:
if not new_music:
pygame.mixer.music.stop()
try:
if current_music != new_music and current_music:
current_music = None
pygame.mixer.music.fadeout(int(renpy.config.fade_music * 1000))
else:
fn, loops, startpos = new_music
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
pygame.mixer.music.play(loops, startpos)
if not pygame.mixer.music.get_busy():
fn, loops, startpos = new_music
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
pygame.mixer.music.play(loops, startpos)
current_music = new_music
except pygame.error, e:
if renpy.config.debug:
+64 -12
View File
@@ -9,8 +9,16 @@ import renpy.ast as ast
class ParseError(Exception):
def __init__(self, filename, number, msg):
Exception.__init__(self, "On line %d of %s: %s" % (number, filename, msg))
def __init__(self, filename, number, msg, line=None, pos=None):
message = "On line %d of %s: %s" % (number, filename, msg)
if line is not None:
message += "\n\n" + line
if pos is not None:
message += "\n" + " " * pos + "^"
Exception.__init__(self, message)
def list_logical_lines(filename):
@@ -209,7 +217,20 @@ class Lexer(object):
# there is a huge chance of confusion.
keywords = [
'at',
'call',
'hide',
'if',
'image',
'init',
'jump',
'menu',
'python',
'return',
'scene',
'set',
'show',
'with',
'while',
]
@@ -302,7 +323,7 @@ class Lexer(object):
location.
"""
raise ParseError(self.filename, self.number, msg)
raise ParseError(self.filename, self.number, msg, self.text, self.pos)
def eol(self):
"""
@@ -429,6 +450,15 @@ class Lexer(object):
c = self.text[self.pos]
# Allow unicode strings.
if c == 'u':
self.pos += 1
if self.eol():
return False
c = self.text[self.pos]
if c not in ('"', "'"):
return False
@@ -555,16 +585,20 @@ class Lexer(object):
def simple_expression(self):
"""
Tries to parse a simple_expression. Returns true if it can, or
false if it cannot.
Tries to parse a simple_expression. Returns the text if it can, or
None if it cannot.
"""
self.skip_whitespace()
if self.eol():
return None
start = self.pos
# We start with either a name, a python_string, or parenthesized
# python
if (not self.name() and
not self.python_string() and
if (not self.python_string() and
not self.name() and
not self.parenthesised_python()):
return None
@@ -677,11 +711,11 @@ def parse_image_name(l):
rv = [ l.require(l.name) ]
while True:
n = l.name()
n = l.simple_expression()
if not n:
break
rv.append(n)
rv.append(n.strip())
return tuple(rv)
@@ -752,6 +786,7 @@ def parse_with(l, node):
def parse_menu(l, loc):
with = None
set = None
# Tuples of (label, condition, block)
@@ -761,6 +796,12 @@ def parse_menu(l, loc):
while not l.eob:
if l.keyword('with'):
with = l.require(l.simple_expression)
l.expect_eol()
l.expect_noblock('with clause')
l.advance()
if l.keyword('set'):
set = l.require(l.simple_expression)
l.expect_eol()
@@ -798,7 +839,8 @@ def parse_menu(l, loc):
items.append((label, condition, block))
l.advance()
return ast.Menu(loc, items, set)
return ast.Menu(loc, items, set, with)
def parse_statement(l):
"""
@@ -1059,11 +1101,16 @@ def parse_statement(l):
state = l.checkpoint()
what = l.string()
if l.keyword('with'):
with = l.require(l.simple_expression)
else:
with = None
if what and l.eol():
# We have a one-argument say statement.
l.expect_noblock('say statement')
l.advance()
return ast.Say(loc, None, what)
return ast.Say(loc, None, what, with)
l.revert(state)
@@ -1071,11 +1118,16 @@ def parse_statement(l):
who = l.simple_expression()
what = l.string()
if l.keyword('with'):
with = l.require(l.simple_expression)
else:
with = None
if who and what is not None:
l.expect_eol()
l.expect_noblock('say statement')
l.advance()
return ast.Say(loc, who, what)
return ast.Say(loc, who, what, with)
l.error('expected statement.')
+24 -8
View File
@@ -318,14 +318,6 @@ class RollbackLog(renpy.object.Object):
@ivar current: The current rollback object. (Equivalent to
log[-1])
Not serialized:
@ivar old_store: A copy of the store as it was when begin was
last called.
@ivar mutated: A dictionary that maps object ids to a tuple of
(weakref to object, information needed to rollback that object)
@ivar ever_been_changed: A dictionary containing a key for each
variable in the store that has ever been changed. (These variables
become the roots of what is changed or rolled-back.)
@@ -333,6 +325,16 @@ class RollbackLog(renpy.object.Object):
@ivar frozen_roots: A frozen copy of the roots. When freeze is called,
this holds a copy of roots. It's None at other times.
@ivar rollback_limit: The number of steps left that we can
interactively rollback.
Not serialized:
@ivar old_store: A copy of the store as it was when begin was
last called.
@ivar mutated: A dictionary that maps object ids to a tuple of
(weakref to object, information needed to rollback that object)
"""
nosave = [ 'old_store', 'mutated' ]
@@ -343,6 +345,7 @@ class RollbackLog(renpy.object.Object):
self.mutated = { }
self.ever_been_changed = { }
self.frozen_roots = None
self.rollback_limit = 0
def after_setstate(self):
self.mutated = { }
@@ -453,6 +456,9 @@ class RollbackLog(renpy.object.Object):
that the user may want to rollback to just before this
node.
"""
if self.rollback_limit < renpy.config.hard_rollback_limit:
self.rollback_limit += 1
self.current.checkpoint = True
@@ -469,6 +475,13 @@ class RollbackLog(renpy.object.Object):
effect.
"""
# If we have exceeded the rollback limit, and don't have force,
# give up.
if not self.rollback_limit > 0 and not force:
return
self.rollback_limit -= 1
self.purge_unreachable(self.get_roots())
revlog = [ ]
@@ -499,6 +512,9 @@ class RollbackLog(renpy.object.Object):
for rb in revlog:
rb.rollback()
# Disable the next transition, as it's pointless.
renpy.game.interface.supress_transition = True
# Restart the game with the new state.
raise renpy.game.RestartException()
+32 -8
View File
@@ -8,6 +8,9 @@ import os
from cPickle import loads, dumps
# The version of the dumped script.
script_version = 2
class ScriptError(Exception):
"""
Exception that is raised if the script is somehow inconsistent,
@@ -41,8 +44,13 @@ class Script(object):
self.namemap = { }
self.initcode = [ ]
# Find the script files to load.
dirlist = list(os.listdir(dir))
# A list of all files in the search directories.
dirlist = [ ]
for dirname in renpy.game.searchpath:
for fn in os.listdir(dirname):
dirlist.append(dirname + "/" + fn)
# Files to ensure (because they are the alts of files that
# have been processed.)
@@ -53,8 +61,6 @@ class Script(object):
if not (fn.endswith('.rpyc') or fn.endswith('.rpy')):
continue
fn = dir + '/' + fn
if fn in ignore:
continue
@@ -74,7 +80,15 @@ class Script(object):
# print "Loading", fn
self.load_file(fn)
if self.load_file(fn):
continue
print "Couldn't load %s, trying %s instead." % (fn, alt)
if self.load_file(alt):
continue
raise Exception("Could not load %s or %s." % (fn, alt))
self.initcode.sort()
@@ -86,14 +100,22 @@ class Script(object):
if fn.endswith(".rpy"):
stmts = renpy.parser.parse(fn)
f = file(fn + "c", "wb")
f.write(dumps(stmts).encode('zlib'))
f.write(dumps((script_version, stmts)).encode('zlib'))
f.close()
elif fn.endswith(".rpyc"):
f = file(fn, "rb")
stmts = loads(f.read().decode('zlib'))
try:
version, stmts = loads(f.read().decode('zlib'))
except ValueError:
return False
if version != script_version:
return False
f.close()
else:
assert False
return False
# All of the statements found in file, regardless of nesting
# depth.
@@ -132,6 +154,8 @@ class Script(object):
if init:
self.initcode.append(init)
return True
def lookup(self, label):
"""
Looks up the given label in the game. If the label is not found,
+39
View File
@@ -0,0 +1,39 @@
# Plays sounds.
import renpy
import pygame
from pygame.constants import *
def init():
pygame.mixer.init(renpy.config.sound_sample_rate)
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 fn:
return
if not renpy.game.preferences.sound:
return
try:
sound = pygame.mixer.Sound(renpy.loader.load(fn))
sound.play()
except:
if renpy.config.debug:
raise
+7 -3
View File
@@ -21,7 +21,8 @@ Solid = renpy.display.image.Solid
Frame = renpy.display.image.Frame
Animation = renpy.display.image.Animation
Position = renpy.curry.curry(renpy.display.layout.Position)
# Resize = renpy.curry.curry(renpy.display.layout.Resize)
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)
@@ -54,7 +55,7 @@ class Character(object):
def __init__(self, name,
who_style='say_label',
what_style='say_dialogue',
window_style='window_say',
window_style='say_window',
**properties):
"""
@param name: The name of the character, as shown to the user.
@@ -83,12 +84,15 @@ class Character(object):
renpy.display_say(self.name, what,
who_style=self.who_style,
what_style=self.what_style,
window_style=self.window_style,
**self.properties)
# Conveniently get rid of all the packages we had imported before.
import renpy.exports as renpy
# The default transition.
default_transition = None
_globals = globals().copy()
def reload():
+1 -1
View File
@@ -14,7 +14,7 @@ import renpy
# The version of Ren'Py in use.
version = 'Renpy 4.0'
version = 'Renpy 4.2'
if __name__ == "__main__":
-43
View File
@@ -1,43 +0,0 @@
# This file consists of renpy functions that aren't expected to be
# touched by the user too much. We reserve the _ prefix for names
# defined in the library.
# It's strongly reccomended that you don't edit this file, as future
# releases of Ren'Py will probably change this file to include more
# functionality.
# It's also strongly recommended that you leave this file in the
# game directory, so its functionality is included in your game.
label _enable_overlay:
# Set up the default keymap.
python hide:
config.underlay = [ ]
config.overlay = [ ]
# The default keymap.
km = renpy.Keymap(
K_PAGEUP = renpy.rollback,
f = renpy.toggle_fullscreen,
)
config.underlay.append(km)
# If the user gives an enable_overlay function, call that.
if renpy.has_label('enable_overlay'):
call enable_overlay
return
# This is the true starting point of the program. Sssh... Don't
# tell anyone.
label _start:
call _enable_overlay
jump start
+49
View File
@@ -2,14 +2,24 @@ init:
# Set up the size of the screen.
$ config.screen_width = 800
$ config.screen_height = 600
$ config.mouse = "mouse.png"
$ config.background = (0, 0, 0, 255)
# Positions of things on the screen.
$ left = Position(xpos=0.0, xanchor='left')
$ right = Position(xpos=1.0, xanchor='right')
$ center = Position()
# Styles
$ style.window.background = Solid((0, 0, 128, 128))
# Transition
$ fade = Fade(0.5, 0, 0.5)
$ dissolve = Dissolve(0.5)
# Backgrounds.
image whitehouse = Image("whitehouse.jpg")
image marspan = Image("marspan.jpg")
# Character pictures.
image eileen happy = Image("9a_happy.png")
@@ -140,6 +150,45 @@ label after_sub:
show eileen happy at center
e "Testing transitions. If transitions are None, then nothing will
happen."
e "First, normal with statements. This box will fade out, and the
next will pop in."
with fade
e "This will pop out, a fade will happen, and the next will pop
in."
with None
with fade
e "Testing default transitions. The next three things will
dissolve in. If transitions is Some, nothing will
happen."
$ default_transition = dissolve
e "One"
e "Two"
e "Three"
$ default_transition = None
scene marspan at Pan((0,0), (1600,0), 15.0)
show eileen happy at center
e "Testing pan."
e "Still testing pan."
e "Wait for it to stop, and make sure it does."
scene marspan at Pan((0,0), (1600,0), 0)
show eileen happy at center
e "This should be a static pan."
init:
python: