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