Compare commits

..

22 Commits

Author SHA1 Message Date
tom 5ea9d436ce *** empty log message *** 2004-09-25 19:43:36 +00:00
tom c05e4e528e *** empty log message *** 2004-09-24 20:25:43 +00:00
tom e10488b77a *** empty log message *** 2004-09-24 02:31:54 +00:00
tom 86e0cb3331 *** empty log message *** 2004-09-23 22:20:34 +00:00
tom c6397c08af *** empty log message *** 2004-09-22 04:49:09 +00:00
tom f9db64231d *** empty log message *** 2004-09-22 04:39:38 +00:00
tom f3c3c67def *** empty log message *** 2004-09-22 00:15:05 +00:00
tom 6d316a9c78 *** empty log message *** 2004-09-21 23:54:02 +00:00
tom 6e7e68b31a Reorganizing to use common directory. 2004-09-13 18:16:40 +00:00
tom a24e6b2af1 *** empty log message *** 2004-09-12 13:51:22 +00:00
tom 597b01c26b Ready for 4.1 release? 2004-09-12 11:53:36 +00:00
tom a262eb0f60 *** empty log message *** 2004-09-11 18:30:37 +00:00
tom 92006d237c 4.0 Release 2004-09-06 20:33:05 +00:00
tom 1427336746 Feature complete. 2004-09-05 22:49:30 +00:00
tom c35ed8a7cd Before changing fill implementation. 2004-09-04 18:30:42 +00:00
tom 90f47eacf3 In the process of removing with. 2004-09-04 15:33:24 +00:00
tom ee093925f5 Before surface rewrite. 2004-08-31 14:33:13 +00:00
tom f686a810a7 *** empty log message *** 2004-08-31 03:43:43 +00:00
tom e2a12284d5 *** empty log message *** 2004-08-29 02:51:34 +00:00
tom 10b4064a53 *** empty log message *** 2004-08-25 02:18:07 +00:00
tom f1ea5289aa *** empty log message *** 2004-08-24 00:07:26 +00:00
tom a26ed9c328 *** empty log message *** 2004-08-23 01:21:57 +00:00
144 changed files with 2360 additions and 12494 deletions
-617
View File
@@ -1,617 +1,3 @@
New in 4.8
----------
A new system was implemented to manage widget focus. This new system
allows the focus to be moved from widget to widget using the keyboard,
without requiring the keymouse behavior be used. Because of this new
system, the keymouse behavior is now a no-op.
To simplify the code and make every widget compatible with the new
focus code, a number of widgets were reimplemented in terms of
buttons. This includes the ui.menu widget, which is the widget that is
used when presenting the user with choices. Menu buttons are of the
new style style.menu_choice_button, while their labels remain of style
style.menu_choice. Background images and padding can now be styled
onto these menu buttons.
A number of new image manipulators have been made available in the
new im package. These are objects that can load an image (im.Image),
crop an image (im.Crop), scale images (im.Scale), rotozoom images
(im.Rotozoom) and composite images (im.Composite). The latter is much
more flexible than the previous image composition system, as it now
supports placing images at arbitrary locations on an arbitrarily big
canvas. Image (by itself) is now a function that creates the
appropriate image manipulators, retaining compatibility with its
behavior in previous releases.
The image cache is now based on image size, rather than number of
images. This prevents a large number of small images from filling up
the cache.
We now load images before starting the timer for a transition. This
prevents us from popping into the middle of a transition because we
spent some time loading images. This behavior can be controlled by
config.load_before_transition.
The ui.imagemap widget has been rewritten in terms of ui.imagebuttons
and im.Crops. Some styles have changed names as a result.
Ren'Py now keeps track of the images that have been shown to the
user. This is the basis of this release's new extra, gallery.rpy,
which manages a gallery of unlockable CG.
The audio system has been rewritten, and a number of low-level audio
functions have been exposed in the new audio file. These functions
control audio directly, but do not save the state of audio in a save
file. The high-level music functions have been rewritten and placed in
the music.rpy file, allowing a sufficently adventurous game author to
change the way music is played.
Two changes have been made to the renpy.music_start() function in the
high-level sound api. The startpos argument was removed (as it was a
pain to support), and a new fadeout argument was added, which lets the
fadeout time be controlled for each new song that is played.
The low-level sound api now lets us do the following things:
[list][*] Play up to eight sound effects at once.
[*] Cancel a playing sound effect.
[*] Pause and unpause the playing music.
[/list]
There is now a new, state that a widget can be in. Along with hover,
idle, and activate, a widget can now be insensitive. This is the state
that all non-focused widgets are in. Having the new insensitive state
means that we can eliminate the various disabled_button styles, so we
did.
In general, in this release, many styles have changed names.
In prior versions of Ren'Py, the bar widget could respond to
clicks. This was never used, and won't work well with the
keyboard focus control system. So, it's been eliminated.
Now, if the label "enter_game_menu" exists, it is called when entering
the game menu. It's expected that the main purpose of this label will
be to play music when in the game menu. Using the default music
system, the code to do this would look like:
[code]
label enter_game_menu:
$ renpy.music_start("game_menu_music.mid")
return
[/code]
A number of bugs were fixed, including bugs with the style system, and
with animation refresh.
Tab characters are now expanded to tab stop at eight spaces, rather
than to a fixed spacing of eight spaces. Old code using tabs may need
to be reformatted.
The 'h' key now hides the windows on the screen. This means that all
Ren'Py functionality is now available through the keyboard, at least
for people with a 1-button mouse. (cough ... Mac ... cough)
Finally, setting the environment variable RENPY_DISABLE_FULLSCREEN to
a non-empty value will disable fullscreen support, for those people
(that person?) who had it crash their systems.
New in 4.7.2
------------
This release should be compatiable with the .rpyc and .save files
produced by 4.7.1.
The new readback extra caused save files to reach a size and/or
complexity that caused errors in cPickle on Windows, which in turn
lead to a repeatable crash of Ren'Py, and left a corrupt save file in
the saves directory that prevented further saving and loading of
games. This has been addressed in two ways. The first is the use of
pickle (as opposed to cPickle) throught Ren'Py, a change that may slow
down the engine a little but should ensure correctness. The second is
that we now catch the errors produced by corrupt files, and (if
config.debug is not set) proceed by ignoring that file.
There is now a new extra, readback.rpy. This implements readback, in
addition to, or instead of, rollback. Readback is limited in a number
of ways, insofar as it only shows text without changing pictures or
other things. As part of implementing readback, we added a new
function, say, that is called when the user gives dialogue consisting
of a pair of strings.
There are a number of changes that improve support of the Mac OS X
platform. The foremost among them is that we ship a new file,
run_game.pyw. This is identical to run_game.py, but is necessary for
the game to run in a graphical environment on the Mac. (It also will
run the game without a console window on Windows, if you have the
appropriate dependencies.) Ren'Py still requires that PyObjC and
pygame are installed on a OS X 10.3 box before it can run.
The way font line spacing is computed was changed in this release, in
the hope of making it consistent on all three platforms. We now ignore
font linesize hints, which seem to be computed incorrectly on some
platforms, and instead set the line spacing to be equal to the ascent
and descent of the font. The user can increase or decrease the line
spacing by setting the new line_spacing text property. The old
line_height_fudge property is now ignored. The style.rpy common file
was updated to take account of this change.
To aid the user in finding the source of error messages, we now report
the name of the file being parsed when an error occurs during the
parse phase.
The tutorial has now been renamed "The Ren'Py Reference Manual", as
it's really more of a poorly-organized comprehensive reference then a
tutorial. This is in the hope that the Ren'Py user community will one
day write a more reasonable tutorial. A number of errors in the
reference were corrected.
The functions used in keymaps can now return values, which are
returned to the ui.interact() that called them. This was needed
to support readback.
LICENSE.txt has been updated to include the names and URLs of software
that a windows build of Ren'Py depends on, the licenses of which you
will be required to comply with when releasing a Ren'Py game.
New in 4.7.1
------------
Added a new tool, dump_text.py/.exe, that dumps all of the text from a
Ren'Py script file. It uses a heuristic approach that is imperfect,
but is hopefully accurate enough to be useful for purposes like
spell-checking a script. The text is dumped into the file text.txt in
the current directory, and on windows that file is then displayed to
the user. By default all of the rpy files in the game directory are
displayed, but supplying a filename parameter can change that.
Fixed a bug that was preventing parser error messages from being
reported on Windows.
Fixed a bug in Render.subsurface that lead to weird effects when doing
an irisin or irisout on a sufficently complicated scene. See
http://lemmasoft.renai.us/forums/viewtopic.php?p=4612#4612 for the
discussion.
New in 4.7
----------
The demo was updated to show the new features in 4.7.
The big new feature in this release is text tags. Text tags is a
markup language that allows text properties to be changed inside a
single unit of text. It lets you emphasize individual words by making
them bold, bigger, or a different color. Rather than explaining it
fully here, let me point you to the new "Text Tags" section of the
documentation.
There were two changes that were made to Ren'Py to support text
tags. The first was that interpolation now quotes the interpolated
text, making it impossible to introduce a text tag via interpolation.
The second change is that renpy.input() and friends now take as
arguments strings giving allowed and excluded characters. The default
is to exclude the '{' and '}' characters, meaning that it's impossible
for a user to input a text tag.
Finally, this release features even more defensive programming
involving sound. Now, if the mixer does not initialize, no further
sound operations will be performed.
New in 4.6.2
------------
4.6.2 was never officially released, as it didn't cure all of the
sound woes, and didn't have much else in the way of new features.
Added a speed test, which tests the speed of the dissolve transition
on a user's system. This test can be accessed by typing 'S' during the
demo (that's shift+the 's' key).
Fixed a bug with sound on systems with no soundcard in them. (Also,
put in a note to remind myself not to re-introduce that bug.)
Fixed a bug in which imagemaps would not redraw properly.
New in 4.6.1
------------
Fixed a major memory leak in the rendering code. This leak was
rendering 4.6 and 4.5 unusable on smaller systems.
Executed 250,000 statements (well, 5 statements in a loop, while
skipping), and ensured that there was no memory leak during the
execution of these statements.
Added a memory profiler. You can see it by supplying the --leak option
to run_game.exe or .py, but don't expect to understand it.
Changed Dissolve so that it tries to use compositing when it can. This
means that it only draws to the screen once... which is a little bit
of a speedup on a really expensive operation.
Fixed a bug that was preventing predictive image loading from
working.
New in 4.6
----------
The demo was updated to show some of the new features listed below.
There's now a third state that hovered widgets can go into. The
activate state is enabled when a button is clicked or a menu choice is
selected. The activate state can only be seen when a transition occurs
immediately after a button is clicked or a menu choice is selected.
The margin and padding properties have been broken up, so it's now
possible to specify margin and padding for the left, right, top, and
bottom of a windows.
The biggest internal change in this version is the introduction of a
layer system in Ren'Py. Transitions can now work on an individual
layer (as well as the previous behavior of the whole screen), so it's
possible to do things like sliding in and out the dialogue box.
It's now possible to use transitions when entering and leaving the
game menu, by assigning a transition object to
library.enter_transition and library.exit_transition, respectively.
The preferences screen can now be entered from user code, and is
now part of the main menu.
Character objects now support an interact argument when called
directly. If it's False, then no interaction occurs. This lets a
character window become part of a larger screen.
Two new extras have been added:
[list]
[*] button_menu.rpy changes the way menus are displayed. Each menu
choice is displayed in its own button, roughly centered on the
screen. A single menu caption is displayed as narration, if such
a caption exists.
[*] overlay_menu.rpy changes the behavior of right click from
showing the game menu to showing a bank of buttons that, when
clicked, bring the user to the game menu.
[/list]
The overlay code was overhauled. It's now not possible for an overlay
function to return a list of widgets. Instead, the overlay functions
are expected to add widgets to the screen using the ui
functions. Calling the new function renpy.restart_interaction will,
among other things, cause the overlay functions to be called again, if
one wants to replace the overlay on the screen.
A new variable, config.overlay_during_wait, controls if overlays are
shown during the execution of wait statements.
Init blocks of the same priority are now assured to run in the order
that they appear in a script file.
The renpy.interact() function has been eliminated. Use ui.interact()
instead, as it performs more error checking.
Fixed a bug in which some redraws were ignored, and another in which
the input widget failed to eat characters. (So typing 'f' in an input
widget would be passed through, and eventually cause the game to go to
fullscreen mode. No longer.)
4.5's demo script had a bug that cause it to crash, after playing the
MPEG-1 movie. It was a bug in the demo script, and not in Ren'Py
proper. It's fixed.
New in 4.5
----------
We now ship an extras directory, with interesting sample code. It
includes:
[list]
[*] fullscreen.rpy, containing code to automatically switch the game
into fullscreen on the first run, while preserving the user's
preference on later runs.
[*] 640x480.rpy, which shows how to customize Ren'Py for a game that
runs at 640x480.
[*] kanamode.rpy, contains the code to emulate the interface of the
various Digital Object games, which display text one line at a
time, but keep a page of text on the screen at once.
([url=http://www.bishoujo.us/hosted/kanamode.jpg]screenshot[/url])
[/list]
Added a new CropMove transition. This single class is the root of not
one, not two, but fourteen transitions, in the forms of wipes, slides,
slideaways, and rectangular irises. You can see some of these
transitions in action in the demo, which now has a new section,
"What's new with Ren'Py?"
Added support for playing MPEG-1 movies. Rather than explain it all
here, we'l just mention that you can read the new "Movies" subsection
of the "Multimedia: Sound, Music, and Movies" section of the Ren'Py
manual. We support both hardware-accelerated full-window movies and
using a movie as a widget. This is also shown in the what's new
section of Ren'Py.
Added ui.grid(). This is a widget that places its children in a grid.
The children must have sizes that do not consider the amount of space
available for the grid to work.
Added ui.pausebehavior(). This separates pausing from the saybehavior
paving the way for uninterruptable pauses (a bad idea, IMO) and menus
that dismiss themseleves after a certain amount of time.
A new function, renpy.exists(), can check to see if a given file can
be found in the searchpath. This could be used, for example, to make a
game that only tries to play music if an add-on music package is
downloaded, and the files placed in the appropriate place.
Added the ability to bind mouse events to mouseup as well as
mousedown, and changed some of the default bindings to be on mouseup
rather than mousedown. If you make changes to config.keymaps, you'll
need to change mouse_1 to mousedown_1, and so on for various other
button numbers.
Moved the place we look for menu sounds from the style of the choices
in the menu to the style of the menu itself, as that seems more
rational. Also, gave menus their own style. (Can't believe I forgot
that.)
Made a few changes to the library screens. First of all, we
rationalized (to some extent, it's still pretty ugly) the names of the
styles for library widgets. So some of that may have changed. We
placed preferences and the yes/no dialogue inside windows, which can
be styled by the user. We then routed all button and label creation
through two functions, _button_factory and _label_factory. By
overriding these functions, the user can (for example) replace the
default textbuttons with imagebuttons.
Made 'f' toggle fullscreen at the main menu. This will help on virtual
windows, where the mouse doesn't work right in fullscreen mode.
Improved rendering speed. Now, we aggressively cache things to
minimize re-renders, and only draw to the screen the parts of the
screen that have changed. In common cases, such as the case where only
some text has changed, this can greately improve performance. In other
cases (transitions), we have to redraw the entire screen anyway, so
there's little improvement.
Fixed a bug in which hiding the UI was not working properly. (This
broke the center mouse button behavior.)
New in 4.4.2
------------
Improved the configurability of the say and menu statements, also
changing the way they work. Say statements without a speaker specified
are routed through the narrator character, while all menu statements
are routed through the menu function. Customizing these functions can
customize how Ren'Py interacts with the user, making it a much more
flexible engine.
As part of this, we changed the way character objects work. They are
now called directly to display dialogue, rather than having the say
method called on them.
If your game uses variables named "narrator" or "menu", you will need
to rename them.
Changed the way spaces are handled by the text widget. Specifically,
they are no longer merged, so it's possible to include spaces in
dialogue and thoughts. Please note that the parser still merges
adjacent spaces, unless you escape them with a backslash.
Added a new widget, ui.sizer, that can shrink the amount of space
allocated to its child.
Added two new text properties, first_indent and rest_indent. These
properties control how many pixels of indentation to put before each
line of text.
Added a function, color, that can translate a hex triple or quadruple
into a Ren'Py color.
New in 4.4.1
------------
4.4.1 is being released to test some sound fixes, and so it isn't
really isn't that complete or well-tested.
Fixed some longstanding bugs with midi music on Windows. First of all,
we now ship with the 1.2.6 version of SDL_mixer, which fixes a bug in
1.2.5. That bug caused panning to go to the hard left. We also try to
read the windows midi device volumes, and set our volume to match the
first one we can read. This should prevent really loud midi music from
happening.
Also, increased the size of the sound buffer to 4096 samples, to
prevent skipping.
Some new features:
Now, adding interact=False to a Character object will prevent an
interaction from occuring.
Implemented functions to get the amount of time elapsed during the
game, renpy.clear_game_runtime() and renpy.get_game_runtime().
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
----------
Now, when the library first starts up, it tries calling the
"splashscreen" label, if it exists. The splashscreen will not be
called in the case of a full reset (known to the user as a return to
the main menu).
Added a new text property, antialias, that controls the antialiasing
of text. Also made the font property optionally take a comma-separated
list of font names, which are searched for in the
Added a new class, DynamicCharacter, which is now the preferred way of
having a character with a changable and/or user-input name.
A new random number generator has been implemented as
renpy.random. This random number generator cooperates with rollback,
always producing the same random numbers when a rollback occurs.
Added a set of UI functions, that allow the user to build up a user
interface themselves. These could be useful when implementing more
complex games, such as those that require the user to schedule a day
in advance. Also new is a new imagebutton, a button consisting of two
images.
Sticky postions have been implemented. Enabling this (using the
variable config.sticky_positions) cause the at clause corresponding to
a given character to be remembered, allowing the character to change
emotion without changing places on the screen (or requiring a second
at clause).
Rewrote the implementation of preferences, to clean up the code. Moved
it into its own file, preferences.rpy. Also, made it possible for a
user to add his own preferences into the system, although that isn't
really recommended.
The various imagemap functions now have a new parameter, unselected,
which gives an image to use when a hotspot is present but not
hovered. This lets us distinguish between between hotspots that are
present and exist, and hotspots that are totally absent. This could be
used to implement something like an image gallery that only displays
unlocked images.
Implemented config.keymap, which allows the changing of keys and mouse
buttons that trigger various Ren'Py events.
There's now a new look for loading and saving games, that allows 10
saves to be presented at once. I strongly recommend removing the
library.file_page_length line at the start of your game scripts, if
it exists.
Fixed a bug in sound.init that lead to crashes in some cases.
Fixed a bug in ast.Node.predict that was causing an exception when
config.debug was turned on.
New in 4.2
----------
@@ -638,9 +24,6 @@ init:
$ style.window.background = Frame("frame.png", 125, 25)
[/code]
Fixed several bugs with the archiver. This version just might actually
work on Windows.
Added a new variable, config.hard_rollback_limit, which limits the
number of steps the user can rollback the game, interactively. This
limit now defaults to 10 steps. (suggested by Grey)
-47
View File
@@ -1,47 +0,0 @@
Copyright 2004-2005 PyTom <pytom@bishoujo.us>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Please note that the above license only applies to Ren'Py
proper. A binary distribution of Ren'Py contains a number of other
packages, each with their own licenses, which you may be bound to
comply with when distributing a Ren'Py game.
These packages may include:
Python (Python License)
http://www.python.org
Pygame (GNU LGPL)
http://www.pygame.org
SDL (GNU LGPL)
http://www.libsdl.org
SDL_mixer (GNU LGPL)
http://www.libsdl.org/projects/SDL_mixer
SDL_ttf (GNU LGPL)
http://www.libsdl.org/projects/SDL_ttf
ctypes (MIT)
http://starship.python.net/crew/theller/ctypes/
-42
View File
@@ -1,42 +0,0 @@
Greetings!
You've downloaded a game that was written using Ren'Py, a python based
engine for visual novel style games. If you're interested in making
your own games, you may want to consider downloading Ren'Py from:
http://www.bishoujo.us/renpy/
Ren'Py games can be played using the keyboard or the mouse.
When dialogue or transitions are displayed, they can be dismissed
by clicking the left mouse button, or pressing space or enter on the
keyboard. When the control key is held down, dialogue and transitions
are rapidly dismissed, provided that the user has seen them
already. Tab toggles skipping mode.
Choices on menus can be made by clicking on the appopriate choice with
the mouse, or by picking the choice using the up and down arrow and
hitting enter.
Hitting escape or clicking the right mouse button brings up the game
menu. This game menu lets you save and load games, quit the game, or
return to the main menu. It also lets you set preferences that control
the behavior of the game. These preferences are:
Display --- Controls if the game displays in a window or
fullscreen.
Music --- Controls if music is played or silenced.
TAB and CTRL Skip --- Chooses if CTRL skips all messages or only messages
that have ever been seen on this computer.
Transitions --- Controls the amount of transitions that are shown.
Finally, Ren'Py supports a rollback feature, which lets you, with some
limits, play the game backwards. For example, you can rollback to a
menu, and save or make a different choice. It also lets you see
dialogue that you missed. You can rollback by pressing the 'page up'
key, or by scrolling your mouse wheel up. Pressing 'page down' or
scrolling your mouse wheel down lets you skip dialogue that's been
seen in this session, which is almost the opposite of rollback.
Thank you for choosing to play a Ren'Py powered game.
-4
View File
@@ -1,4 +0,0 @@
Have a new form of imagemap that uses a color image to distinguish
hotspots.
Keyd compiles.
+6 -31
View File
@@ -6,7 +6,6 @@
import sys
import os
import re
import glob
def find_labels(fn, labels):
@@ -18,10 +17,6 @@ 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):
@@ -48,45 +43,25 @@ 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()
of.close()
try:
os.unlink(fn + ".bak")
except:
pass
os.rename(fn, fn + ".bak")
os.rename(fn + ".new", fn)
def main():
pattern = "*/*.rpy"
gamedir = "game"
if len(sys.argv) >= 2:
pattern = sys.argv[1]
gamedir = sys.argv[1]
files = glob.glob(pattern)
files = [ i for i in files if not i.startswith("common/") ]
print "Processing files in", gamedir
files = [ gamedir + "/" + i for i in os.listdir(gamedir)
if i.endswith(".rpy") ]
labels = { }
+1 -2
View File
@@ -1,3 +1,2 @@
cd game
cd images
..\archiver.exe images *.png *.jpg
pause
+1 -8
View File
@@ -8,7 +8,6 @@ import sys
import os
import encodings.zlib_codec
import random
import glob
from cPickle import loads, dumps, HIGHEST_PROTOCOL
@@ -48,13 +47,7 @@ def main():
offset = 0
# Needed because windows sucks. It doesn't do globbing on the
# command line.
files = [ ]
for i in sys.argv[2:]:
files.extend(glob.glob(i))
for fn in files:
for fn in sys.argv[2:]:
index[fn] = [ ]
print "Adding %s..." % fn
-1
View File
@@ -1 +0,0 @@
build_exe.py run_game
+1 -1
View File
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
setup(name="RenPy",
windows=programs,
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py" ],
console=[ "archiver.py", "add_from.py" ],
zipfile='lib/renpy.zip',
)
+226 -417
View File
@@ -21,124 +21,34 @@ init -500:
library = object()
# 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
library.file_page_length = 4
# A small amount of padding.
library.padding = 2
library.padding = 5
# The width of a thumbnail.
library.thumbnail_width = 66
library.thumbnail_width = 100
# The height of a thumbnail.
library.thumbnail_height = 50
library.thumbnail_height = 75
# The contents of the main menu.
library.main_menu = [
( "Start Game", "start" ),
( "Continue Game", "_continue" ),
( "Preferences", "_preferences" ),
( "Quit Game", "_quit" ),
]
# The contents of the game menu choices.
library.game_menu = [
( "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
# Transition that occurs when entering the game menu.
library.enter_transition = None
# Transition that occurs when leaving the game menu.
library.exit_transition = None
# True if the skip indicator should be shown.
library.skip_indicator = True
# 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
def _button_factory(label,
type=None,
selected=None,
disabled=False,
clicked=None,
**properties):
"""
This function is called to create the various buttons used
in the game menu. By overriding this function, one can
(for example) replace the default textbuttons with image buttons.
When it is called, it's expected to add a button to the screen.
@param label: The label of this button, before translation.
@param type: The type of the button. One of "mm" (main menu),
"gm_nav" (game menu), "file_picker_nav", "yesno", or "prefs".
@param selected: True if the button is selected, False if not,
or None if it doesn't matter.
@param disabled: True if the button is disabled, False if not.
@param clicked: A function that should be executed when the
button is clicked.
@param properties: Addtional layout properties.
"""
style = type
if selected and not disabled:
style += "_selected"
if disabled:
clicked = None
style = style + "_button"
text_style = style + "_text"
ui.textbutton(_(label), style=style, text_style=text_style, clicked=clicked, **properties)
def _label_factory(label, type, **properties):
"""
This function is called to create a new label. It can be
overridden by the user to change how these labels are created.
@param label: The label of the box.
@param type: "prefs" or "yesno".
@param properties: This may contain position properties.
"""
ui.text(_(label), style=type + "_label", **properties)
# The function that's used to translate strings in the game menu.
# The function that's used to translate strings in the game menu.
init:
python:
def _(s):
"""
Translates s into another language or something.
@@ -149,73 +59,56 @@ init -500:
else:
return s
##############################################################################
init:
python:
# Called to make a screenshot happen.
def _screenshot():
renpy.screenshot("screenshot.bmp")
# Are the windows currently hidden?
_windows_hidden = False
# Hides the windows.
def _hide_windows():
global _windows_hidden
if _windows_hidden:
return
try:
_windows_hidden = True
renpy.interact(renpy.SayBehavior())
finally:
_windows_hidden = False
# A keymouse object that we use on the mainmenu and gamemenu
# screens.
_keymouse = renpy.KeymouseBehavior()
# Set up the default keymap.
python hide:
# Called to make a screenshot happen.
def screenshot():
renpy.screenshot("screenshot.bmp")
def invoke_game_menu():
renpy.play(library.enter_sound)
renpy.call_in_new_context('_game_menu')
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,
toggle_skip = toggle_skipping,
game_menu = invoke_game_menu,
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
K_PAGEUP = renpy.rollback,
mouse_4 = renpy.rollback,
s = _screenshot,
f = renpy.toggle_fullscreen,
m = renpy.toggle_music,
K_ESCAPE = renpy.curried_call_in_new_context("_game_menu"),
mouse_3 = renpy.curried_call_in_new_context("_game_menu"),
mouse_2 = _hide_windows,
)
config.underlay = [ 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')
config.overlay_functions.append(skip_indicator)
return
label _hide_windows:
if _windows_hidden:
return
python:
_windows_hidden = True
ui.saybehavior()
ui.interact(suppress_overlay=True)
_windows_hidden = False
return
# This is the true starting point of the program. Sssh... Don't
# tell anyone.
label _start:
if renpy.has_label("splashscreen") and not _restart:
call splashscreen
jump _main_menu
# This shows the main menu to the user.
@@ -226,28 +119,25 @@ label _main_menu:
jump main_menu
label _library_main_menu:
scene
python hide:
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
ui.keymousebehavior()
ui.window(style='mm_root_window')
ui.fixed()
ui.window(style='mm_menu_window')
ui.vbox()
# Show the main menu screen.
vbox = renpy.VBox()
for text, label in library.main_menu:
_button_factory(text, "mm", clicked=ui.returns(label))
vbox.add(renpy.TextButton(text, clicked=_return(label)))
ui.close()
ui.close()
menu_window = renpy.Window(vbox, style='mm_menu_window')
fixed = renpy.Fixed()
fixed.add(menu_window)
store._result = ui.interact(suppress_overlay = True,
suppress_underlay = True)
root_window = renpy.Window(fixed, style='mm_root_window')
store._result = renpy.interact(_keymouse, root_window,
suppress_overlay=True,
suppress_underlay=True)
# Computed jump to the appropriate label.
$ renpy.jump(_result)
@@ -256,25 +146,7 @@ label _library_main_menu:
# Used to call the game menu.
label _continue:
$ _can_save = False
$ _at_main_menu = True
$ renpy.call_in_new_context("_game_menu_load")
$ _can_save = True
$ _at_main_menu = False
jump _library_main_menu
# Used to call the game menu.
label _preferences:
$ _can_save = False
$ _at_main_menu = True
$ renpy.call_in_new_context("_game_menu_preferences")
$ _can_save = True
$ _at_main_menu = False
$ renpy.call_in_new_context("_load_menu")
jump _library_main_menu
@@ -285,266 +157,191 @@ label _preferences:
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
# buttons, set up to jump to the appropriate screen sections.
def _game_nav(selected):
ui.keymousebehavior()
buttons = [
( "return", _("Return to Game"), "_return"),
( "load", _("Load Game"), "_load_screen" ),
( "save", _("Save Game"), "_save_screen" ),
( "prefs", _("Preferences"), "_prefs_screen" ),
( "mainmenu", _("Main Menu"), "_full_restart" ),
( "quit", _("Quit Game"), "_confirm_quit" ),
]
ui.add(renpy.Keymap(game_menu=ui.jumps("_noisy_return")))
vbox = renpy.VBox()
win = renpy.Window(vbox, style='gm_nav_window')
ui.window(style='gm_root_window')
ui.fixed()
ui.window(style='gm_nav_window')
ui.vbox(focus='gm_nav')
for key, label, target, enabled in library.game_menu:
clicked = ui.jumps(target)
disabled = False
if not eval(enabled):
disabled = True
clicked = None
_button_factory(label, "gm_nav", selected=(key==selected),
disabled=disabled, clicked=clicked)
ui.close()
ui.close()
for key, label, target in buttons:
style="button"
text_style="button_text"
def _game_interact():
return ui.interact(suppress_underlay=True,
suppress_overlay=True)
if key == selected:
style = 'selected_button'
text_style = 'selected_button_text'
def clicked(target=target):
renpy.jump(target)
tb = renpy.TextButton(label,
style=style,
text_style=text_style,
clicked=clicked)
vbox.add(tb)
def _render_new_slot(name, save):
if save:
clicked=ui.returns(("return", (name, False)))
enable_hover = True
return win
def _game_interact(selected, *widgets):
fixed = renpy.Fixed()
win = renpy.Window(fixed, style='gm_root_window')
fixed.add(_game_nav(selected))
for w in widgets:
fixed.add(w)
return renpy.interact(_keymouse, win,
suppress_underlay=True,
suppress_overlay=True
)
_file_picker_index = 0
def _render_filename(filename, newest_filename):
if filename is None:
return renpy.Text(_("Save in new slot."), style='file_picker_new_slot')
hbox = renpy.HBox(padding=library.padding)
if filename == newest_filename:
hbox.add(renpy.Text(_("New"), style='file_picker_new'))
else:
clicked = None
enable_hover = True
hbox.add(renpy.Text(_("Old"), style='file_picker_old'))
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):
hbox.add(renpy.load_screenshot(filename))
image, extra = info
hbox.add(renpy.Text(renpy.load_extra_info(filename), style='file_picker_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
return hbox
# This displays a file picker that can chose a save file from
# the list of save files.
def _file_picker(selected, save):
def _file_picker(selected, files):
saves, newest = renpy.saved_games()
# 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) - 1) // library.file_page_length * library.file_page_length
if fpi < 0:
fpi = 0
# The length of a half-page of files.
hfpl = library.file_page_length // 2
nsg = renpy.newest_save_game()
while True:
if fpi < 0:
fpi = 0
if _file_picker_index >= len(files):
store._file_picker_index -= library.file_page_length
_scratch.file_picker_index = fpi
if _file_picker_index < 0:
store._file_picker_index = 0
# Show Navigation
_game_nav(selected)
ui.window(style='file_picker_window')
ui.vbox() # whole thing.
# Draw the navigation.
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
fpi = _file_picker_index
cur_files = files[fpi:fpi + library.file_page_length]
vbox = renpy.VBox()
hbox = renpy.HBox(padding=library.padding * 3)
def tb(cond, label, clicked):
_button_factory(label, "file_picker_nav", disabled=not cond, clicked=clicked)
# Previous
tb(fpi > 0, _('Previous'), ui.returns(("fpidelta", -1)))
# Quick Access
for i in range(0, library.file_quick_access_pages):
target = i * library.file_page_length
tb(fpi != target, str(i + 1), ui.returns(("fpiset", target)))
# Next
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
# Done with nav buttons.
ui.close()
# This draws a single slot.
def entry(offset):
i = fpi + offset
name = str(i + 1)
if name not in saves:
_render_new_slot(name, save)
if cond:
style = 'button'
text_style = 'button_text'
else:
_render_savefile(name, saves[name], newest)
style = 'disabled_button'
text_style = 'disabled_button_text'
return renpy.TextButton(label, style=style, text_style=text_style, clicked=clicked)
hbox.add(tb(fpi > 0,
_('Previous Page'), _return(("fpidelta", -1))))
hbox.add(tb(fpi + library.file_page_length < len(files),
_('Next Page'), _return(("fpidelta", +1))))
vbox.add(hbox)
for i in cur_files:
child = _render_filename(i, nsg)
button = renpy.Button(child,
style='file_picker_entry',
clicked=_return(("return", i)))
# Actually draw a slot.
ui.hbox() # slots
vbox.add(button)
ui.vbox()
for i in range(0, hfpl):
entry(i)
ui.close()
win = renpy.Window(vbox, style='file_picker_window')
ui.vbox()
for i in range(hfpl, hfpl * 2):
entry(i)
ui.close()
ui.close() # slots
result = _game_interact(selected, win)
ui.close() # whole thing
result = _game_interact()
type, value = result
if type == "return":
return value
if type == "fpidelta":
fpi += value * library.file_page_length
if type == "fpiset":
fpi = value
store._file_picker_index += value * library.file_page_length
def _yesno_prompt(screen, message):
_game_nav(screen)
prompt = renpy.Text(message, style='yesno_prompt')
yes = renpy.TextButton(_("Yes"), style='yesno_yes',
clicked=_return(True))
no = renpy.TextButton(_("No"), style='yesno_no',
clicked=_return(False))
ui.window(style='yesno_window')
ui.vbox(library.padding * 10, xpos=0.5, xanchor='center', ypos=0.5, yanchor='center')
return _game_interact(screen, prompt, yes, no)
# Returns a button for a single preference and value.
def _prefbutton(label, var, value):
_label_factory(message, "yesno", xpos=0.5, xanchor='center')
def clicked():
setattr(_preferences, var, value)
return True
ui.grid(5, 1, xfill=True)
style = 'button'
text_style = 'button_text'
# The extra nulls are because we want equal whitespace surrounding
# the two buttons. It should work as long as we have xfill=True
ui.null()
_button_factory("Yes", 'yesno', clicked=ui.returns(True), xpos=0.5, xanchor='center')
ui.null()
_button_factory("No", 'yesno', clicked=ui.returns(False), xpos=0.5, xanchor='center')
ui.null()
if getattr(_preferences, var) == value:
style = 'selected_button'
text_style = 'selected_button_text'
ui.close()
ui.close()
return renpy.TextButton(_(label), style=style,
text_style=text_style, clicked=clicked)
return _game_interact()
# Returns a vbox for a single preference.
def _prefvbox(label, var, entries):
def _show_exception(title, message):
ui.add(Solid((0, 0, 0, 255)))
ui.vbox()
rv = renpy.VBox(style='prefs_pref')
rv.add(renpy.Text(_(label), style='prefs_label'))
ui.text(title, color=(255, 128, 128, 255))
ui.text("")
ui.text(message)
ui.text("")
ui.text("Please click to continue.")
for blabel, value in entries:
rv.add(_prefbutton(blabel, var, value))
ui.close()
return rv
ui.saybehavior()
ui.interact()
# Factored this all into one place, to make our lives a bit easier.
label _enter_game_menu:
scene
$ renpy.movie_stop()
label _load_menu:
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
if library.enter_transition:
$ renpy.transition(library.enter_transition)
if renpy.has_label("enter_game_menu"):
call enter_game_menu
return
# Entry points from the game into menu-space.
label _game_menu:
label _game_menu_save:
call _enter_game_menu
jump _save_screen
label _game_menu_load:
call _enter_game_menu
jump _load_screen
label _game_menu_preferences:
call _enter_game_menu
jump _prefs_screen
label _game_menu:
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
label _confirm_quit:
call _enter_game_menu
jump _quit_screen
jump _save_screen
# Menu screens.
label _load_screen:
python:
_fn, _exists = _file_picker("load", False )
_fn = _file_picker("load", renpy.saved_game_filenames() )
python:
renpy.load(_fn)
@@ -552,36 +349,55 @@ label _load_screen:
jump _load_screen
label _save_screen:
$ _fn, _exists = _file_picker("save", True)
$ _fn = _file_picker("save", renpy.saved_game_filenames() + [ None ] )
if not _exists or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
python hide:
if not _fn or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
$ renpy.save(_fn, renpy.time.strftime("%Y-%m-%d %H:%M:%S\n") + save_name)
if save_name:
full_save_name = "\n" + save_name
else:
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
# The preferences screen.
label _prefs_screen:
python hide:
prefs_left = [
( 'Display', 'fullscreen',
[ ('Window', False), ('Fullscreen', True) ] ),
( 'Music', 'music',
[ ('Enabled', True), ('Disabled', False) ] ),
( 'Sound Effects', 'sound',
[ ('Enabled', True), ('Disabled', False) ] ),
]
prefs_right = [
('CTRL Skips', 'skip_unseen',
[ ('Seen Messages', False), ('All Messages', True) ] ),
('Transitions', 'transitions',
[ ('All', 2), ('Some', 1), ('None', 0) ]),
]
if config.annoying_text_cps:
prefs_right.append(('Text Display', 'fast_text', [ ('Slow', False), ('Fast', True) ]))
vbox_left = renpy.VBox(padding=library.padding * 3, style='prefs_left')
for label, var, entries in prefs_left:
vbox_left.add(_prefvbox(label, var, entries))
vbox_right = renpy.VBox(padding=library.padding * 3, style='prefs_right')
for label, var, entries in prefs_right:
vbox_right.add(_prefvbox(label, var, entries))
_game_interact("prefs", vbox_left, vbox_right)
jump _prefs_screen
# Asks the user if he wants to quit.
label _quit_screen:
label _confirm_quit:
if _yesno_prompt("quit", _("Are you sure you want to end the game?")):
jump _quit
else:
@@ -593,16 +409,9 @@ label _quit:
label _full_restart:
$ renpy.full_restart()
# Make some noise, then return.
label _noisy_return:
$ renpy.play(library.exit_sound)
# Return to the game.
# Return to the game, after restoring the keymap.
label _return:
if library.exit_transition:
$ renpy.transition(library.exit_transition)
return
# Random nice things to have.
-121
View File
@@ -1,121 +0,0 @@
# This file contains code to manage the playing of music. It's here,
# in an rpy file, because we now optionally let the user override all
# of this to implement his or her own music system, using calls to
# various functions found in audio.
init -1000:
python hide:
config.debug_sound = True
# This becomes renpy.music_start.
def music_start(filename, loops=-1, fadeout=None):
"""
This starts music playing. If a music track is already
playing, this pauses that music track in favor of this
one.
@param filename: The file that the music will be played from.
This is relative to the game directory, and must be a real
file (so it cannot be stored in an archive).
@param loops: The number of times the music will loop
after it finishes playing for the first time. This is
made somewhat less accurate by rollback and loading. If
this number is less than zero, the song will loop
forever.
@param fadeout: If this parameter is not None, it is
interpreted as a time in seconds, which gives how long
the fade will take.
"""
if loops >= 0:
loops += 1
ctx = renpy.context()
ctx._music_name = filename
ctx._music_loops = loops
if fadeout and audio.music_enabled() and not config.skipping:
audio.music_fadeout(fadeout)
def music_stop():
"""
This stops the currently playing music track.
"""
ctx = renpy.context()
ctx._music_name = None
ctx._music_loops = None
renpy.music_start = music_start
renpy.music_stop = music_stop
# This is called once for each interaction, to ensure that the
# music is appropriate for that interaction.
def music_interact():
if not audio.music_enabled():
return
ctx = renpy.context()
if not hasattr(ctx, '_music_name'):
ctx._music_name = None
ctx._music_loops = None
playing, queued = audio.music_filenames()
# If music is disabled, ensure that it is stopped.
if not _preferences.music:
if playing:
audio.music_stop()
return
# If we do not match what is playing, stop what's currently
# playing and get ready to play something else.
if ctx._music_name != playing:
# If we're not playing anything, immediately start the
# new track by calling music_end_event.
if not playing:
config.music_end_event()
# Otherwise, we will start a fade to the new music if no
# such fade is already in progress, and we're not skipping.
elif not audio.music_fading() and not config.skipping:
audio.music_fadeout(config.fade_music)
# This is called whenever a track of music ends, or also from the
# above when we want to start a new track when nothing else is
# playing.
def music_end_event():
if not _preferences.music:
return
ctx = renpy.context()
playing, queued = audio.music_filenames()
if not hasattr(ctx, '_music_name'):
ctx._music_name = None
ctx._music_loops = None
if not ctx._music_name:
return
if not playing and ctx._music_loops:
ctx._music_loops -= 1
audio.music_play(ctx._music_name)
if not queued and ctx._music_loops:
ctx._music_loops -= 1
audio.music_queue(ctx._music_name)
config.music_interact = music_interact
config.music_end_event = music_end_event
-145
View File
@@ -1,145 +0,0 @@
# This file contains the code to implement the Ren'Py preferences
# screen.
init -450:
python:
# Used to collect the various preferences the system knows
# about.
library.left_preferences = [ ]
library.right_preferences = [ ]
class _Preference(object):
"""
This is a class that's used to represent a preference that
may be shown to the user.
"""
def __init__(self, name, field, values):
"""
@param name: The name of this preference. It will be
displayed to the user.
@param variable: The field on the _preferences object
that will be assigned the selected value. This field
must exist.
@param values: A list of value name, value, condition
triples. The value name is the name of this value that
will be shown to the user. The value is the literal
python value that will be assigned if this value is
selected. The condition is a condition that will be
evaluated to determine if this is a legal value. If no
conditions are true, this preference will not be
displayed to the user. A condition of None is always
considered to be True.
"""
self.name = name
self.field = field
self.values = values
def render_preference(self):
values = [ (name, val) for name, val, cond in self.values
if cond is None or renpy.eval(cond) ]
if not values:
return
ui.window(style='prefs_pref')
ui.vbox(style='prefs_pref')
_label_factory(self.name, "prefs")
cur = getattr(_preferences, self.field)
for name, value in values:
def clicked(value=value):
setattr(_preferences, self.field, value)
return True
_button_factory(name, "prefs",
selected=cur==value,
clicked=clicked)
ui.close()
python hide:
# Enablers for some preferences.
library.has_music = True
library.has_sound = True
library.has_transitions = True
p1 = _Preference('Display', 'fullscreen', [
('Window', False, None),
('Fullscreen', True, None),
])
p2 = _Preference('Music', 'music', [
('Enabled', True, 'library.has_music'),
('Disabled', False, 'library.has_music'),
])
p3 = _Preference('Sound Effects', 'sound', [
('Enabled', True, 'library.has_sound'),
('Disabled', False, 'library.has_sound'),
])
library.left_preferences = [ p1, p2, p3 ]
p4 = _Preference('TAB and CTRL Skip', 'skip_unseen', [
('Seen Messages', False, 'config.allow_skipping'),
('All Messages', True, 'config.allow_skipping'),
])
p5 = _Preference('Transitions', 'transitions', [
('All', 2, 'library.has_transitions'),
('Some', 1, 'library.has_transitions and default_transition'),
('None', 0, 'library.has_transitions'),
])
p6 = _Preference('Text Display', 'fast_text', [
('Fast', True, 'config.annoying_text_cps'),
('Slow', False, 'config.annoying_text_cps'),
])
library.right_preferences = [ p4, p5, p6 ]
label _prefs_screen:
python hide:
_game_nav("prefs")
ui.window(style='prefs_window')
ui.grid(2, 1, xfill=True)
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
for i in library.left_preferences:
i.render_preference()
ui.close()
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
for i in library.right_preferences:
i.render_preference()
ui.close()
ui.close()
_game_interact()
jump _prefs_screen
+86 -152
View File
@@ -14,40 +14,27 @@
# to your script. No need to mess around here, it will just make your
# life harder when a new version of Ren'Py is released.
init -1000:
init -250:
python hide:
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)
# Magic.
style.default.enable_hover = True
# Text properties.
style.default.font = "Vera.ttf"
style.default.antialias = True
style.default.size = 22
style.default.color = (255, 255, 255, 255)
style.default.bold = False
style.default.italic = False
style.default.underline = False
style.default.drop_shadow = (1, 1)
style.default.drop_shadow = (2, 2)
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.first_indent = 0
style.default.rest_indent = 0
style.default.line_spacing = 0
# Change this if you're not using Vera 22.
if renpy.windows():
style.default.line_height_fudge = -4
else:
style.default.line_height_fudge = 0
# Window properties.
style.default.background = None
style.default.xpadding = 0
@@ -66,7 +53,8 @@ init -1000:
style.default.yanchor = 'top'
# Sound properties.
style.default.sound = None
style.default.hover_sound = None
style.default.activate_sound = None
# The base style for the large windows.
style.create('window', 'default',
@@ -104,8 +92,6 @@ init -1000:
the label of dialogue. The label is used to
indicate who is saying something.""")
style.say_label.bold = True
style.create('say_dialogue', 'default',
"""(text) The style that is used by default for
the text of dialogue.""")
@@ -121,22 +107,14 @@ init -1000:
# Styles that are used for menus.
style.create('menu', 'default',
"(position) The style that is used for the vbox containing a menu.")
style.create('menu_caption', 'default',
"(text) The style that is used to render a menu caption.")
"""(text) The style that is used to render a menu
caption.""")
style.create('menu_choice', 'default',
"""(text, hover) The style that is used to render
the text of a menu choice.""")
style.create('menu_choice_button', 'default',
"""(window, hover, sound) The style that is used
to render the button containing a menu choice.""")
"""(text, hover, sound) The style that is used to render a menu choice.""")
style.menu_choice.hover_color = (255, 255, 0, 255) # yellow
style.menu_choice.activate_color = (255, 255, 0, 255) # yellow
style.menu_choice.idle_color = (0, 255, 255, 255) # cyan
style.create('menu_window', 'window',
@@ -179,20 +157,10 @@ init -1000:
# Styles that are used by imagemaps
style.create('imagemap', 'image_placement',
'(position) The style that is used for imagemaps.')
style.create('imagemap_button', 'default',
'(window, sound, hover) The style that is used for buttons inside imagemaps.')
# Styles that are used by imagebutttons.
style.create('image_button', 'default',
'(window, sound, hover) The default style used for image buttons.')
style.create('image_button_image', 'default',
'The default style used for images inside image buttons.')
'(sound, position) The style that is used for imagemaps.')
# Styles that are used by all other Buttons.
# Styles that are used by all Buttons.
style.create('button', 'default',
'(window, sound, hover) The default style used for buttons in the main and game menus.')
@@ -205,11 +173,8 @@ init -1000:
style.button_text.xpos = 0.5
style.button_text.xanchor = 'center'
style.button_text.size = 24
style.button_text.color = dark_cyan
style.button_text.hover_color = bright_cyan
style.button_text.activate_color = bright_cyan
style.button_text.insensitive_color = (192, 192, 192, 255)
style.button_text.drop_shadow = (2, 2)
style.button_text.color = (0, 255, 255, 255)
style.button_text.hover_color = (128, 255, 255, 255)
# Selected button.
style.create('selected_button', 'button',
@@ -218,16 +183,20 @@ init -1000:
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 = dark_red
style.selected_button_text.hover_color = bright_red
style.selected_button_text.activate_color = bright_red
style.selected_button_text.color = (255, 255, 0, 255)
# Bar.
style.create('bar', 'default',
'(bar) The style that is used by default for bars.')
# Disabled button.
style.bar.left_bar = Solid(bright_cyan)
style.bar.right_bar = Solid((0, 0, 0, 128))
style.create('disabled_button', 'button',
'(window, hover) The style that is used for a disabled button.')
style.disabled_button.hover_sound = None
style.disabled_button.activate_sound = None
style.create('disabled_button_text', 'button_text',
'(text, hover) The style that is used for the label of a disabled button.')
style.disabled_button_text.color = (128, 128, 128, 255)
# Styles that are used when laying out the main menu.
style.create('mm_root_window', 'default',
@@ -264,136 +233,101 @@ init -1000:
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('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 = 0
style.file_picker_window.xpos = 10
style.file_picker_window.xanchor = 'left'
style.file_picker_window.ypos = 0
style.file_picker_window.ypos = 10
style.file_picker_window.yanchor = 'top'
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_nav_button', 'button',
'(window, hover) The style that is used for enabled file picker navigation buttons.')
style.create('file_picker_nav_button_text', 'button_text',
'(text) The style that is used for the label of enabled file picker navigation buttons.')
style.create('file_picker_entry', 'button',
'(window, hover) The style that is used for each of the slots in the file picker.')
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.xpadding = 3
style.file_picker_entry.xminimum = 780
style.file_picker_entry.ymargin = 5
style.file_picker_entry.background = Solid((255, 255, 255, 255))
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
style.file_picker_entry.hover_background = Solid((255, 255, 192, 255))
style.file_picker_entry.activate_background = Solid((255, 255, 192, 255))
style.create('file_picker_text', 'default',
'(text) A base style for all text that is displayed in the file picker.')
style.file_picker_text.size = 18
style.file_picker_text.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.')
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.hover_color = bright_red
style.file_picker_new.activate_color = bright_red
style.file_picker_new.idle_color = dark_red
style.file_picker_new.minwidth = 30
style.file_picker_old.minwidth = 30
style.file_picker_new.color = (255, 192, 192, 255)
style.file_picker_old.color = (192, 192, 255, 255)
style.file_picker_new.minwidth = 50
style.file_picker_old.minwidth = 50
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.create('file_picker_empty_slot', 'file_picker_text',
'(text) The style that is used for the empty slot indicator in the file picker.')
style.file_picker_extra_info.color = (192, 192, 255, 255)
style.create('yesno_label', 'default',
style.create('file_picker_new_slot', 'file_picker_text',
'(text) The style that is used for the new slot indicator in the file picker.')
style.create('yesno_prompt', 'default',
'(text, position) The style used for the prompt in a yes/no dialog.')
style.yesno_label.color = green
style.yesno_prompt.xpos = 0.5
style.yesno_prompt.xanchor = 'center'
style.create('yesno_button', 'button',
'(window, hover) The style of yes/no buttons.')
style.yesno_prompt.ypos = 0.25
style.yesno_prompt.yanchor = 'center'
style.create('yesno_button_text', 'button_text',
'(window, hover) The style of yes/no button text.')
style.create('yesno_yes', 'button',
'(position) The position of the yes button on the screen.')
style.yesno_yes.xpos = 0.33
style.yesno_yes.xanchor = 'center'
style.yesno_yes.ypos = 0.33
style.yesno_yes.yanchor = 'center'
style.create('yesno_no', 'button',
'(position) The position of the no button on the screen.')
style.yesno_no.xpos = 0.66
style.yesno_no.xanchor = 'center'
style.yesno_no.ypos = 0.33
style.yesno_no.yanchor = 'center'
style.create('yesno_window', 'default',
'(window) The style of a window containing a yes/no dialogue.')
style.yesno_window.xfill = True
style.yesno_window.yminimum = 0.5
# Preferences
style.create('prefs_pref', 'default',
'(window, position) The position of the box containing an individual preference.')
style.prefs_pref.xpos = 0.5
style.prefs_pref.xanchor = 'center'
style.create('prefs_label', 'default',
'(text, position) The style that is applied to the label of a block of preferences.')
style.prefs_label.xpos = 0.5
style.prefs_label.xanchor = "center"
style.prefs_label.color = green
style.create('prefs_pref', 'default',
'(position) The position of the box containing an individual preference.')
style.prefs_pref.xpos = 0.5
style.prefs_pref.xanchor = 'center'
style.create('prefs_left', 'default',
'(position) The position of the left column of preferences.')
style.prefs_left.xpos = 0.25
style.prefs_left.xanchor = "center"
style.prefs_left.ypos = 0.05
style.prefs_left.yalign = "top"
style.create('prefs_right', 'default',
'(position) The position of the right column of preferences.')
style.prefs_right.xpos = 0.75
style.prefs_right.xanchor = "center"
style.prefs_right.ypos = 0.05
style.prefs_right.yalign = "top"
style.create('prefs_button', 'button',
'(window, hover) The style of an unselected preferences button.')
style.prefs_button.xpos = 0.5
style.prefs_button.xanchor = 'center'
style.create('prefs_button_text', 'button_text',
'(text, hover) The style of the text of an unselected preferences button.')
style.create('prefs_selected_button', 'selected_button',
'(window, hover) The style of a selected preferences button.')
style.prefs_selected_button.xpos = 0.5
style.prefs_selected_button.xanchor = 'center'
style.create('prefs_selected_button_text', 'selected_button_text',
'(text, hover) The style of the text of a selected preferences button.')
style.create('prefs_window', 'default',
'(window, position) A window containing all preferences.')
style.prefs_window.xfill=True
style.prefs_window.ypadding = 0.05
style.create('skip_indicator', 'default',
'(text, position) The style of the text that is used to indicate that skipping is in progress.')
style.skip_indicator.xpos = 10
style.skip_indicator.ypos = 10
-157
View File
@@ -1,157 +0,0 @@
#!/usr/bin/env python
import os.path
# Enable psyco. Warning: Check for memory leaks!
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.
import encodings.utf_8
import encodings.zlib_codec
import encodings.unicode_escape
import encodings.string_escape
import encodings.raw_unicode_escape
# Load up all of Ren'Py, in the right order.
import renpy
def main():
name = os.path.basename(sys.argv[0])
dirname = os.path.dirname(sys.argv[0])
if dirname:
os.chdir(dirname)
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,
help='The directory the game is in.')
op.add_option('--python', dest='python', default=None,
help='Run the argument in the python interpreter.')
op.add_option('--leak', dest='leak', action='store_true', default=False,
help='When the game exits, dumps a profile of memory usage.')
options, args = op.parse_args()
if options.python:
execfile(options.python)
sys.exit(0)
try:
renpy.main.main(options.game)
except Exception, e:
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
type, value, tb = sys.exc_info()
print >>f, type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print >>f
print >>f, renpy.game.exception_info
print >>f
print >>f, "-- Full Traceback ------------------------------------------------------------"
print >>f
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
print >>f, type.__name__ + ":",
print type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print unicode(e).encode('utf-8')
print
print >>f
print renpy.game.exception_info
print >>f, renpy.game.exception_info
print >>f
print >>f, "Ren'Py Version:", renpy.version
f.close()
try:
os.startfile('traceback.txt')
except:
pass
if options.leak:
memory_profile()
sys.exit(0)
def memory_profile():
print "Memory Profile"
print
print "Showing all objects in memory at program termination."
print
import gc
gc.collect()
objs = gc.get_objects()
c = { } # count
dead_renders = 0
for i in objs:
t = type(i)
c[t] = c.get(t, 0) + 1
if isinstance(i, renpy.display.render.Render):
if i.dead:
dead_renders += 1
results = [ (count, ty) for ty, count in c.iteritems() ]
results.sort()
for count, ty in results:
print count, str(ty)
if dead_renders:
print
print "*** found", dead_renders, "dead Renders. ***"
if __name__ == "__main__":
main()
-822
View File
@@ -1,822 +0,0 @@
#!/usr/bin/python
#
# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# COVERAGE.PY -- COVERAGE TESTING
#
# Gareth Rees, Ravenbrook Limited, 2001-12-04
# Ned Batchelder, 2004-12-12
# http://nedbatchelder.com/code/modules/coverage.html
#
#
# 1. INTRODUCTION
#
# This module provides coverage testing for Python code.
#
# The intended readership is all Python developers.
#
# This document is not confidential.
#
# See [GDR 2001-12-04a] for the command-line interface, programmatic
# interface and limitations. See [GDR 2001-12-04b] for requirements and
# design.
"""Usage:
coverage.py -x MODULE.py [ARG1 ARG2 ...]
Execute module, passing the given command-line arguments, collecting
coverage data.
coverage.py -e
Erase collected coverage data.
coverage.py -r [-m] FILE1 FILE2 ...
Report on the statement coverage for the given files. With the -m
option, show line numbers of the statements that weren't executed.
coverage.py -a [-d dir] FILE1 FILE2 ...
Make annotated copies of the given files, marking statements that
are executed with > and statements that are missed with !. With
the -d option, make the copies in that directory. Without the -d
option, make each copy in the same directory as the original.
Coverage data is saved in the file .coverage by default. Set the
COVERAGE_FILE environment variable to save it somewhere else."""
__version__ = "2.2.20041231" # see detailed history at the end of this file.
import compiler
import compiler.visitor
import os
import re
import string
import sys
import types
# 2. IMPLEMENTATION
#
# This uses the "singleton" pattern.
#
# The word "morf" means a module object (from which the source file can
# be deduced by suitable manipulation of the __file__ attribute) or a
# filename.
#
# When we generate a coverage report we have to canonicalize every
# filename in the coverage dictionary just in case it refers to the
# module we are reporting on. It seems a shame to throw away this
# information so the data in the coverage dictionary is transferred to
# the 'cexecuted' dictionary under the canonical filenames.
#
# The coverage dictionary is called "c" and the trace function "t". The
# reason for these short names is that Python looks up variables by name
# at runtime and so execution time depends on the length of variables!
# In the bottleneck of this application it's appropriate to abbreviate
# names to increase speed.
# A dictionary with an entry for (Python source file name, line number
# in that file) if that line has been executed.
c = {}
# t(f, x, y). This method is passed to sys.settrace as a trace
# function. See [van Rossum 2001-07-20b, 9.2] for an explanation of
# sys.settrace and the arguments and return value of the trace function.
# See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
# objects.
def t(f, w, a):
if w == 'line':
c[(f.f_code.co_filename, f.f_lineno)] = 1
return t
class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
def __init__(self, statements, excluded, suite_spots):
compiler.visitor.ASTVisitor.__init__(self)
self.statements = statements
self.excluded = excluded
self.suite_spots = suite_spots
self.excluding_suite = 0
def doRecursive(self, node):
self.recordNodeLine(node)
for n in node.getChildNodes():
self.dispatch(n)
visitStmt = visitModule = doRecursive
def doCode(self, node):
if hasattr(node, 'decorators') and node.decorators:
self.dispatch(node.decorators)
self.doSuite(node, node.code)
visitFunction = visitClass = doCode
def getFirstLine(self, node):
# Find the first line in the tree node.
lineno = node.lineno
for n in node.getChildNodes():
f = self.getFirstLine(n)
if lineno and f:
lineno = min(lineno, f)
else:
lineno = lineno or f
return lineno
def getLastLine(self, node):
# Find the first line in the tree node.
lineno = node.lineno
for n in node.getChildNodes():
lineno = max(lineno, self.getLastLine(n))
return lineno
def doStatement(self, node):
self.recordLine(self.getFirstLine(node))
visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
visitPrintnl = visitRaise = visitSubscript = \
visitDecorators = \
doStatement
def recordNodeLine(self, node):
return self.recordLine(node.lineno)
def recordLine(self, lineno):
# Returns a bool, whether the line is included or excluded.
if lineno:
# Multi-line tests introducing suites have to get charged to their
# keyword.
if lineno in self.suite_spots:
lineno = self.suite_spots[lineno][0]
# If we're inside an exluded suite, record that this line was
# excluded.
if self.excluding_suite:
self.excluded[lineno] = 1
return 0
# If this line is excluded, or suite_spots maps this line to
# another line that is exlcuded, then we're excluded.
elif self.excluded.has_key(lineno) or \
self.suite_spots.has_key(lineno) and \
self.excluded.has_key(self.suite_spots[lineno][1]):
return 0
# Otherwise, this is an executable line.
else:
self.statements[lineno] = 1
return 1
return 0
default = recordNodeLine
def recordAndDispatch(self, node):
self.recordNodeLine(node)
self.dispatch(node)
def doSuite(self, intro, body, exclude=0):
exsuite = self.excluding_suite
if exclude or (intro and not self.recordNodeLine(intro)):
self.excluding_suite = 1
self.recordAndDispatch(body)
self.excluding_suite = exsuite
def doPlainWordSuite(self, prevsuite, suite):
# Finding the exclude lines for else's is tricky, because they aren't
# present in the compiler parse tree. Look at the previous suite,
# and find its last line. If any line between there and the else's
# first line are excluded, then we exclude the else.
lastprev = self.getLastLine(prevsuite)
firstelse = self.getFirstLine(suite)
for l in range(lastprev+1, firstelse):
if self.suite_spots.has_key(l):
self.doSuite(None, suite, exclude=self.excluded.has_key(l))
break
else:
self.doSuite(None, suite)
def doElse(self, prevsuite, node):
if node.else_:
self.doPlainWordSuite(prevsuite, node.else_)
def visitFor(self, node):
self.doSuite(node, node.body)
self.doElse(node.body, node)
def visitIf(self, node):
# The first test has to be handled separately from the rest.
# The first test is credited to the line with the "if", but the others
# are credited to the line with the test for the elif.
self.doSuite(node, node.tests[0][1])
for t, n in node.tests[1:]:
self.doSuite(t, n)
self.doElse(node.tests[-1][1], node)
def visitWhile(self, node):
self.doSuite(node, node.body)
self.doElse(node.body, node)
def visitTryExcept(self, node):
self.doSuite(node, node.body)
for i in range(len(node.handlers)):
a, b, h = node.handlers[i]
if not a:
# It's a plain "except:". Find the previous suite.
if i > 0:
prev = node.handlers[i-1][2]
else:
prev = node.body
self.doPlainWordSuite(prev, h)
else:
self.doSuite(a, h)
self.doElse(node.handlers[-1][2], node)
def visitTryFinally(self, node):
self.doSuite(node, node.body)
self.doPlainWordSuite(node.body, node.final)
def visitGlobal(self, node):
# "global" statements don't execute like others (they don't call the
# trace function), so don't record their line numbers.
pass
the_coverage = None
class coverage:
error = "coverage error"
# Name of the cache file (unless environment variable is set).
cache_default = ".coverage"
# Environment variable naming the cache file.
cache_env = "COVERAGE_FILE"
# A map from canonical Python source file name to a dictionary in
# which there's an entry for each line number that has been
# executed.
cexecuted = {}
# Cache of results of calling the analysis2() method, so that you can
# specify both -r and -a without doing double work.
analysis_cache = {}
# Cache of results of calling the canonical_filename() method, to
# avoid duplicating work.
canonical_filename_cache = {}
def __init__(self):
global the_coverage
if the_coverage:
raise self.error, "Only one coverage object allowed."
self.usecache = 1
self.cache = None
self.exclude_re = ''
def help(self, error=None):
if error:
print error
print
print __doc__
sys.exit(1)
def command_line(self):
import getopt
settings = {}
optmap = {
'-a': 'annotate',
'-d:': 'directory=',
'-e': 'erase',
'-h': 'help',
'-i': 'ignore-errors',
'-m': 'show-missing',
'-r': 'report',
'-x': 'execute',
}
short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
long_opts = optmap.values()
options, args = getopt.getopt(sys.argv[1:], short_opts,
long_opts)
for o, a in options:
if optmap.has_key(o):
settings[optmap[o]] = 1
elif optmap.has_key(o + ':'):
settings[optmap[o + ':']] = a
elif o[2:] in long_opts:
settings[o[2:]] = 1
elif o[2:] + '=' in long_opts:
settings[o[2:]] = a
else:
self.help("Unknown option: '%s'." % o)
if settings.get('help'):
self.help()
for i in ['erase', 'execute']:
for j in ['annotate', 'report']:
if settings.get(i) and settings.get(j):
self.help("You can't specify the '%s' and '%s' "
"options at the same time." % (i, j))
args_needed = (settings.get('execute')
or settings.get('annotate')
or settings.get('report'))
action = settings.get('erase') or args_needed
if not action:
self.help("You must specify at least one of -e, -x, -r, or -a.")
if not args_needed and args:
self.help("Unexpected arguments %s." % args)
self.get_ready()
self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
if settings.get('erase'):
self.erase()
if settings.get('execute'):
if not args:
self.help("Nothing to do.")
sys.argv = args
self.start()
import __main__
sys.path[0] = os.path.dirname(sys.argv[0])
execfile(sys.argv[0], __main__.__dict__)
if not args:
args = self.cexecuted.keys()
ignore_errors = settings.get('ignore-errors')
show_missing = settings.get('show-missing')
directory = settings.get('directory=')
if settings.get('report'):
self.report(args, show_missing, ignore_errors)
if settings.get('annotate'):
self.annotate(args, directory, ignore_errors)
def use_cache(self, usecache):
self.usecache = usecache
def get_ready(self):
if self.usecache and not self.cache:
self.cache = os.environ.get(self.cache_env, self.cache_default)
self.restore()
self.analysis_cache = {}
def start(self):
self.get_ready()
sys.settrace(t)
def stop(self):
sys.settrace(None)
def erase(self):
global c
c = {}
self.analysis_cache = {}
self.cexecuted = {}
if self.cache and os.path.exists(self.cache):
os.remove(self.cache)
self.exclude_re = ''
def exclude(self, re):
if self.exclude_re:
self.exclude_re += "|"
self.exclude_re += "(" + re + ")"
# save(). Save coverage data to the coverage cache.
def save(self):
if self.usecache and self.cache:
self.canonicalize_filenames()
cache = open(self.cache, 'wb')
import marshal
marshal.dump(self.cexecuted, cache)
cache.close()
# restore(). Restore coverage data from the coverage cache (if it
# exists).
def restore(self):
global c
c = {}
self.cexecuted = {}
assert self.usecache
if not os.path.exists(self.cache):
return
try:
cache = open(self.cache, 'rb')
import marshal
cexecuted = marshal.load(cache)
cache.close()
if isinstance(cexecuted, types.DictType):
self.cexecuted = cexecuted
except:
pass
# canonical_filename(filename). Return a canonical filename for the
# file (that is, an absolute path with no redundant components and
# normalized case). See [GDR 2001-12-04b, 3.3].
def canonical_filename(self, filename):
if not self.canonical_filename_cache.has_key(filename):
f = filename
if os.path.isabs(f) and not os.path.exists(f):
f = os.path.basename(f)
if not os.path.isabs(f):
for path in [os.curdir] + sys.path:
g = os.path.join(path, f)
if os.path.exists(g):
f = g
break
cf = os.path.normcase(os.path.abspath(f))
self.canonical_filename_cache[filename] = cf
return self.canonical_filename_cache[filename]
# canonicalize_filenames(). Copy results from "executed" to
# "cexecuted", canonicalizing filenames on the way. Clear the
# "executed" map.
def canonicalize_filenames(self):
global c
for filename, lineno in c.keys():
f = self.canonical_filename(filename)
if not self.cexecuted.has_key(f):
self.cexecuted[f] = {}
self.cexecuted[f][lineno] = 1
c = {}
# morf_filename(morf). Return the filename for a module or file.
def morf_filename(self, morf):
if isinstance(morf, types.ModuleType):
if not hasattr(morf, '__file__'):
raise self.error, "Module has no __file__ attribute."
file = morf.__file__
else:
file = morf
return self.canonical_filename(file)
# analyze_morf(morf). Analyze the module or filename passed as
# the argument. If the source code can't be found, raise an error.
# Otherwise, return a tuple of (1) the canonical filename of the
# source code for the module, (2) a list of lines of statements
# in the source code, and (3) a list of lines of excluded statements.
def analyze_morf(self, morf):
if self.analysis_cache.has_key(morf):
return self.analysis_cache[morf]
filename = self.morf_filename(morf)
ext = os.path.splitext(filename)[1]
if ext == '.pyc':
if not os.path.exists(filename[0:-1]):
raise self.error, ("No source for compiled code '%s'."
% filename)
filename = filename[0:-1]
elif ext != '.py':
raise self.error, "File '%s' not Python source." % filename
source = open(filename, 'r')
lines, excluded_lines = self.find_executable_statements(
source.read(), exclude=self.exclude_re
)
source.close()
result = filename, lines, excluded_lines
self.analysis_cache[morf] = result
return result
def get_suite_spots(self, tree, spots):
import symbol, token
for i in range(1, len(tree)):
if type(tree[i]) == type(()):
if tree[i][0] == symbol.suite:
# Found a suite, look back for the colon and keyword.
lineno_colon = lineno_word = None
for j in range(i-1, 0, -1):
if tree[j][0] == token.COLON:
lineno_colon = tree[j][2]
elif tree[j][0] == token.NAME:
if tree[j][1] == 'elif':
# Find the line number of the first non-terminal
# after the keyword.
t = tree[j+1]
while t and token.ISNONTERMINAL(t[0]):
t = t[1]
if t:
lineno_word = t[2]
else:
lineno_word = tree[j][2]
break
elif tree[j][0] == symbol.except_clause:
# "except" clauses look like:
# ('except_clause', ('NAME', 'except', lineno), ...)
if tree[j][1][0] == token.NAME:
lineno_word = tree[j][1][2]
break
if lineno_colon and lineno_word:
# Found colon and keyword, mark all the lines
# between the two with the two line numbers.
for l in range(lineno_word, lineno_colon+1):
spots[l] = (lineno_word, lineno_colon)
self.get_suite_spots(tree[i], spots)
def find_executable_statements(self, text, exclude=None):
# Find lines which match an exclusion pattern.
excluded = {}
suite_spots = {}
if exclude:
reExclude = re.compile(exclude)
lines = text.split('\n')
for i in range(len(lines)):
if reExclude.search(lines[i]):
excluded[i+1] = 1
import parser
tree = parser.suite(text+'\n\n').totuple(1)
self.get_suite_spots(tree, suite_spots)
# Use the compiler module to parse the text and find the executable
# statements. We add newlines to be impervious to final partial lines.
statements = {}
ast = compiler.parse(text+'\n\n')
visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
compiler.walk(ast, visitor, walker=visitor)
lines = statements.keys()
lines.sort()
excluded_lines = excluded.keys()
excluded_lines.sort()
return lines, excluded_lines
# format_lines(statements, lines). Format a list of line numbers
# for printing by coalescing groups of lines as long as the lines
# represent consecutive statements. This will coalesce even if
# there are gaps between statements, so if statements =
# [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
# format_lines will return "1-2, 5-11, 13-14".
def format_lines(self, statements, lines):
pairs = []
i = 0
j = 0
start = None
pairs = []
while i < len(statements) and j < len(lines):
if statements[i] == lines[j]:
if start == None:
start = lines[j]
end = lines[j]
j = j + 1
elif start:
pairs.append((start, end))
start = None
i = i + 1
if start:
pairs.append((start, end))
def stringify(pair):
start, end = pair
if start == end:
return "%d" % start
else:
return "%d-%d" % (start, end)
import string
return string.join(map(stringify, pairs), ", ")
# Backward compatibility with version 1.
def analysis(self, morf):
f, s, _, m, mf = self.analysis2(morf)
return f, s, m, mf
def analysis2(self, morf):
filename, statements, excluded = self.analyze_morf(morf)
self.canonicalize_filenames()
if not self.cexecuted.has_key(filename):
self.cexecuted[filename] = {}
missing = []
for line in statements:
if not self.cexecuted[filename].has_key(line):
missing.append(line)
return (filename, statements, excluded, missing,
self.format_lines(statements, missing))
def morf_name(self, morf):
if isinstance(morf, types.ModuleType):
return morf.__name__
else:
return os.path.splitext(os.path.basename(morf))[0]
def report(self, morfs, show_missing=1, ignore_errors=0):
if not isinstance(morfs, types.ListType):
morfs = [morfs]
max_name = max([5,] + map(len, map(self.morf_name, morfs)))
fmt_name = "%%- %ds " % max_name
fmt_err = fmt_name + "%s: %s"
header = fmt_name % "Name" + " Stmts Exec Cover"
fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
if show_missing:
header = header + " Missing"
fmt_coverage = fmt_coverage + " %s"
print header
print "-" * len(header)
total_statements = 0
total_executed = 0
for morf in morfs:
name = self.morf_name(morf)
try:
_, statements, _, missing, readable = self.analysis2(morf)
n = len(statements)
m = n - len(missing)
if n > 0:
pc = 100.0 * m / n
else:
pc = 100.0
args = (name, n, m, pc)
if show_missing:
args = args + (readable,)
print fmt_coverage % args
total_statements = total_statements + n
total_executed = total_executed + m
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
type, msg = sys.exc_info()[0:2]
print fmt_err % (name, type, msg)
if len(morfs) > 1:
print "-" * len(header)
if total_statements > 0:
pc = 100.0 * total_executed / total_statements
else:
pc = 100.0
args = ("TOTAL", total_statements, total_executed, pc)
if show_missing:
args = args + ("",)
print fmt_coverage % args
# annotate(morfs, ignore_errors).
blank_re = re.compile("\\s*(#|$)")
else_re = re.compile("\\s*else\\s*:\\s*(#|$)")
def annotate(self, morfs, directory=None, ignore_errors=0):
for morf in morfs:
try:
filename, statements, excluded, missing, _ = self.analysis2(morf)
self.annotate_file(filename, statements, excluded, missing, directory)
except KeyboardInterrupt:
raise
except:
if not ignore_errors:
raise
def annotate_file(self, filename, statements, excluded, missing, directory=None):
source = open(filename, 'r')
if directory:
dest_file = os.path.join(directory,
os.path.basename(filename)
+ ',cover')
else:
dest_file = filename + ',cover'
dest = open(dest_file, 'w')
lineno = 0
i = 0
j = 0
covered = 1
while 1:
line = source.readline()
if line == '':
break
lineno = lineno + 1
while i < len(statements) and statements[i] < lineno:
i = i + 1
while j < len(missing) and missing[j] < lineno:
j = j + 1
if i < len(statements) and statements[i] == lineno:
covered = j >= len(missing) or missing[j] > lineno
if self.blank_re.match(line):
dest.write(' ')
elif self.else_re.match(line):
# Special logic for lines containing only
# 'else:'. See [GDR 2001-12-04b, 3.2].
if i >= len(statements) and j >= len(missing):
dest.write('! ')
elif i >= len(statements) or j >= len(missing):
dest.write('> ')
elif statements[i] == missing[j]:
dest.write('! ')
else:
dest.write('> ')
elif lineno in excluded:
dest.write('- ')
elif covered:
dest.write('> ')
else:
dest.write('! ')
dest.write(line)
source.close()
dest.close()
# Singleton object.
the_coverage = coverage()
# Module functions call methods in the singleton object.
def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
def start(*args, **kw): return the_coverage.start(*args, **kw)
def stop(*args, **kw): return the_coverage.stop(*args, **kw)
def erase(*args, **kw): return the_coverage.erase(*args, **kw)
def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
def report(*args, **kw): return the_coverage.report(*args, **kw)
def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
# Save coverage data when Python exits. (The atexit module wasn't
# introduced until Python 2.0, so use sys.exitfunc when it's not
# available.)
try:
import atexit
atexit.register(the_coverage.save)
except ImportError:
sys.exitfunc = the_coverage.save
# Command-line interface.
if __name__ == '__main__':
the_coverage.command_line()
# A. REFERENCES
#
# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
# Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/>.
#
# [GDR 2001-12-04b] "Statement coverage for Python: design and
# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
# <http://www.garethrees.org/2001/12/04/python-coverage/design.html>.
#
# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
# Guide van Rossum; 2001-07-20;
# <http://www.python.org/doc/2.1.1/ref/ref.html>.
#
# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-12-04 GDR Created.
#
# 2001-12-06 GDR Added command-line interface and source code
# annotation.
#
# 2001-12-09 GDR Moved design and interface to separate documents.
#
# 2001-12-10 GDR Open cache file as binary on Windows. Allow
# simultaneous -e and -x, or -a and -r.
#
# 2001-12-12 GDR Added command-line help. Cache analysis so that it
# only needs to be done once when you specify -a and -r.
#
# 2001-12-13 GDR Improved speed while recording. Portable between
# Python 1.5.2 and 2.1.1.
#
# 2002-01-03 GDR Module-level functions work correctly.
#
# 2002-01-07 GDR Update sys.path when running a file with the -x option,
# so that it matches the value the program would get if it were run on
# its own.
#
# 2004-12-12 NMB Significant code changes.
# - Finding executable statements has been rewritten so that docstrings and
# other quirks of Python execution aren't mistakenly identified as missing
# lines.
# - Lines can be excluded from consideration, even entire suites of lines.
# - The filesystem cache of covered lines can be disabled programmatically.
# - Modernized the code.
#
# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
# and add 'analysis2'. Add a global for 'annotate', and factor it, adding
# 'annotate_file'.
#
# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
#
# C. COPYRIGHT AND LICENCE
#
# Copyright 2001 Gareth Rees. All rights reserved.
# Copyright 2004 Ned Batchelder. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
# $Id: coverage.py 5 2004-12-14 12:08:23Z ned $
+207
View File
@@ -0,0 +1,207 @@
init:
# Set up the size of the screen.
$ config.screen_width = 800
$ config.screen_height = 600
# Positions of things on the screen.
$ left = Position(xpos=0.0, xanchor='left')
$ right = Position(xpos=1.0, xanchor='right')
$ center = Position()
# Backgrounds.
image whitehouse = Image("whitehouse.jpg")
# Character pictures.
image eileen happy = Image("9a_happy.png")
image eileen vhappy = Image("9a_vhappy.png")
image eileen concerned = Image("9a_concerned.png")
# Character objects.
$ e = Character('Eileen', color=(200, 255, 200, 255))
# The actual game starts here.
label start:
$ renpy.music_start('sun-flower-slow-drag.mid')
scene whitehouse
show eileen vhappy
"Girl" "Welcome to Ren'Py 4!"
show eileen happy
"Girl" "And welcome to American Bishoujo's former southern base,
just outside of Washington, D.C."
show eileen happy at left
"Girl" "This isn't the view from our former base, but it'll do for
this demo."
show eileen happy
"Girl" "My name is Eileen, and while I plan to star in my own game
one day, for now I'm helping to introduce you to Ren'Py."
e "Ren'Py is an engine that makes it easy to write visual novel
games, by taking care of much of the hard work of writing a
game."
e "For example, displaying a line of dialogue is a single
statement. So is displaying a thought or narration."
"I understand."
e "Dialogue is easy to write. Just put a string on a line by
itself, or to the right of an object name or label string."
e "Since dialogue makes up the bulk of these games, we thought it
should be easy to write."
e "Ren'Py can also display menus that let you alter the flow of
the story."
menu:
"Why don't you try a menu out by picking a number?"
"1":
show eileen concerned
e "You picked one. I don't like odd numbers."
"2":
show eileen vhappy
e "You picked two. Even numbers are lucky!"
show eileen happy
e "There are a number of statements that control what's displayed
on the screen. The image statement is used to introduce
images with names."
scene
e "The scene statement can clear the screen..."
scene whitehouse
e "... or it can clear the screen and then show a background."
show eileen happy
e "The show statement is used to show pictures."
e "When the show statement is used on an image with the same first
name (called a tag) as one already shown, it replaces that picture."
show eileen vhappy
e "This makes it easy for characters to change emotions."
hide eileen
e "The hide statement hides an image."
show eileen happy at center
e "A new feature in Ren'Py 4 is the at clause on images, which
lets you say where you want to show the image at. I can go
from the center of the screen..."
show eileen happy at left
e "... to the left of the screen ..."
show eileen happy at right
e "... to the right of the screen ..."
show eileen happy
e "... and back to the center."
e "Ren'Py supports a variety of control statements, such as jump,
call, return, if, and while statements."
e "Rather than bore you with the details, i'll just tell you to
check grab the Ren'Py tutorial from http://www.bishoujo.us/renpy/."
e "That's just about it for writing scripts. Let me show you some
of the new engine features."
e "The first feature I can show off is the ability to go
full-screen. Hit the 'f' key to try it out, and hit 'f'
again to go back to a window."
show eileen vhappy
e "The next feature, rollback, is really neat."
show eileen happy
menu:
"Would you like to see it?"
"Yes.":
pass
"No.":
jump after_rollback
e "Rollback lets you play the game backwards."
e "It lets you go back and reread a line of dialogue you missed,
or even to go back to a menu and make a different choice if you
made a mistake."
e "We do limit the number of steps someone can rollback."
e "Try it out now, by hitting page up until you get back to the
menu, and then choose 'No' instead of 'Yes'."
show eileen concerned
e "Well, try it."
e "You want to hit page up."
e "Well, whatever, your loss. Moving on."
label after_rollback:
show eileen happy
e "Another new feature works only on Windows. If a game crashes,
a notepad is brought up showing the crash message, to help
you debug what went wrong."
e "The biggest new feature, though, is reasonable
documentation, which you can read at http://www.bishoujo.us/renpy/."
show eileen concerned
e "Since this is just a preview release, there are still a few
things missing."
e "For example, there's no support for loading or saving."
e "We also left out music and animations."
show eileen happy
e "Don't worry, though. Those will be coming in the final release,
which is due out in a few weeks."
e "You can begin making your own game by editing game/script.rpy. That's
the script for the game you're playing now."
e "After you make a change, you have to re-run the game to see
the change take effect."
show eileen vhappy
e "Good luck making your own games!"
return
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

+7 -453
View File
@@ -2,9 +2,6 @@
# public domain. Feel free to use it as the basis for your own
# game.
# If you're trying to understand this script, I recommend skipping
# down to the line beginning with 'label start:', at least on your
# first read-through.
# This init block runs first, and sets up all sorts of things that
# are used by the rest of the game. Variables that are set in init
@@ -12,24 +9,20 @@
# program.
init:
# Set up the size of the screen, and the window title.
$ config.screen_width = 800
$ config.screen_height = 600
$ config.window_title = "The Ren'Py Demo Game"
# Set up the library.
$ library.file_page_length = 3
# Change some styles, to add images in the background of
# the menus and windows.
$ style.mm_root_window.background = Image("mainmenu.jpg")
$ style.gm_root_window.background = Image("gamemenu.jpg")
$ style.window.background = Frame("frame.png", 125, 25)
# 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.
$ left = Position(xpos=0.0, xanchor='left')
@@ -40,29 +33,6 @@ init:
# clauses and statements.
$ fade = Fade(.5, 0, .5) # Fade to black and back.
$ dissolve = Dissolve(0.5)
$ wiperight = CropMove(1.0, "wiperight")
$ wipeleft = CropMove(1.0, "wipeleft")
$ wipeup = CropMove(1.0, "wipeup")
$ wipedown = CropMove(1.0, "wipedown")
$ slideright = CropMove(1.0, "slideright")
$ slideleft = CropMove(1.0, "slideleft")
$ slideup = CropMove(1.0, "slideup")
$ slidedown = CropMove(1.0, "slidedown")
$ slideawayright = CropMove(1.0, "slideawayright")
$ slideawayleft = CropMove(1.0, "slideawayleft")
$ slideawayup = CropMove(1.0, "slideawayup")
$ slideawaydown = CropMove(1.0, "slideawaydown")
$ irisout = CropMove(1.0, "irisout")
$ irisin = CropMove(1.0, "irisin")
# Select the transitions that are used when entering and exiting
# the game menu.
$ library.enter_transition = dissolve
$ library.exit_transition = dissolve
# Now, we declare the images that are used in the program.
@@ -84,18 +54,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.
# We'll comment it out for now.
#
# label splashscreen:
# scene black
# show text "American Bishoujo Presents..." with fade
# $ renpy.pause(1.0)
# hide text with fade
#
# return
# The start label marks the place where the main menu jumps to to
# begin the actual game.
@@ -111,10 +69,6 @@ label start:
# that we won the date.
$ date = False
# Clear the game runtime timer, so it doesn't reflect time spent
# sitting at the main menu.
$ renpy.clear_game_runtime()
# Start some music playing in the background.
$ renpy.music_start('sun-flower-slow-drag.mid')
@@ -124,14 +78,9 @@ label start:
scene washington with fade
show eileen vhappy with dissolve
# Store the current version of Ren'Py into a variable, so we can
# interpolate it into the next line.
$ version = renpy.version()
# Display a line of dialogue. In this case, we manually specify
# who's saying the line of dialogue. We also interpolate in the
# version of Ren'Py we're using.
"Girl" "Hi, and welcome to the %(version)s demo program."
# who's saying the line of dialoge.
"Girl" "Hi, and welcome to the Ren'Py 4 demo program."
# This instantly replaces the very happy picture of Eileen with
# one showing her merely happy. It demonstrates how the show
@@ -190,10 +139,6 @@ label choices:
call writing from _call_writing_1
jump choices
"What's new with Ren'Py?":
call whatsnew from _call_whatsnew_1
jump choices
# This choice has a condition associated with it. It is only
# displayed if the condition is true (in this case, if we have
# selected at least one other choice has been chosen.)
@@ -340,14 +285,6 @@ 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."
@@ -441,10 +378,7 @@ 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."
e "Pressing Tab toggles the skipping of dialogue you've seen at
least once."
control quickly skips dialogue you've seen at least once, ever."
e "Pressing page down or scrolling the mouse wheel down will let
you skip dialogue you've seen this session. This is useful
@@ -480,7 +414,7 @@ label find_out_more:
e "If you have questions, the best place to ask them is the Ren'Py
forum of the Lemmasoft forums."
e "Just go to http://lemmasoft.renai.us/forums/, and click on
e "Just go to http://www.lemmasoft.net/forums/, and click on
Ren'Py."
e "We thank Blue Lemma for hosting our forum."
@@ -629,386 +563,6 @@ label ending:
who encouraged him.'
"We can't wait to see what you do with this. Good luck!"
$ minutes, seconds = divmod(int(renpy.get_game_runtime()), 60)
"It took you %(minutes)d minutes and %(seconds)d seconds to
finish this demo."
$ renpy.full_restart()
label speedtest:
with None
scene whitehouse
show eileen happy
with dissolve
e "Okay, I'm going to run the speedtest on your system."
e "I'll only be testing the performance of the dissolve
transition. It taxes your system the most, as it needs to
redraw the entire screen each frame."
$ frames = config.frames
with None
scene washington
show eileen happy
with Dissolve(5.0)
$ frames = config.frames - frames
$ fps = frames / 5.0
e "Well, your system displayed %(frames)d frames in five
seconds. That's %(fps).1f fps."
e "Remember, this is the worst-case speed, as usually we can just
draw the parts of the screen that have changed."
e "Thanks for viewing the secret speed test."
return
# Setup the secret key for the speedtest.
init:
python:
config.keymap['speedtest'] = [ 'S' ]
config.underlay.append(renpy.Keymap(speedtest=renpy.curried_call_in_new_context('speedtest')))
init:
# This is just some example code to show the ui functions in
# 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
init:
image movie = Movie()
python:
style.create('odd_window', 'say_window')
style.odd_window.left_margin = 50
style.odd_window.right_margin = 150
style.odd_window.bottom_margin = 25
eodd = Character('Eileen', color=(200, 255, 200, 255), window_style='odd_window')
label whatsnew:
show washington
show eileen happy
e "I can give you a demonstration of some of the new features in
Ren'Py, but you'll have to tell me what version you want to
start with."
menu:
"I'd like to start with 4.5.":
jump whatsnew45
"I'd like to start with 4.6.":
jump whatsnew46
"I'd like to start with 4.7.":
jump whatsnew47
label whatsnew45:
show washington
show eileen happy
e "While most of the improvements in Ren'Py 4.5 were behind the scenes,
we can give you a demonstration of one of the new features."
e "There is now a new transition, CropMove, that can be used to
provide a whole range of transition effects."
hide eileen with dissolve
e "I'll stand offscreen, so you can see some of its modes. I'll read
out the mode name after each transiton."
scene whitehouse with wiperight
e "We first have wiperight..."
scene washington with wipeleft
e "...followed by wipeleft... "
scene whitehouse with wipeup
e "...wipeup..."
scene washington with wipedown
e "...and wipedown."
e "Next, the slides."
scene whitehouse with slideright
e "Slideright..."
scene washington with slideleft
e "...slideleft..."
scene whitehouse with slideup
e "...slideup..."
scene washington with slidedown
e "and slidedown."
e "We also have a couple of transitions that use a rectangular iris."
scene whitehouse with irisout
e "There's irisout..."
with None
scene washington
show eileen happy
with irisin
e "... and irisin."
e "There are other transitions, such as various forms of
slideaway. And if you can't find the transition for you, you
can write a custom one."
e "It's enough to make you feel a bit dizzy."
e "Ren'Py 4.5 also includes the ability to show MPEG-1 movies as
cutscenes or even backgrounds."
label ike:
if renpy.exists('Eisenhow1952.mpg'):
e "Since you downloaded the Eisenhower commercial, I can show
it to you as a cutscene."
e "You can click to continue if it gets on your nerves too
much."
$ renpy.movie_cutscene('Eisenhow1952.mpg', 63.0)
hide eileen
show movie at Position(xpos=420, ypos=25, xanchor='left', yanchor='top')
show eileen happy
$ renpy.movie_start_displayable('Eisenhow1952.mpg', (352, 240))
e "Ren'Py can even overlay rendered images on top of a movie,
although that's more taxing for your CPU."
e "It's like I'm some sort of newscaster or something."
$ renpy.movie_stop()
hide movie
else:
e "You haven't downloaded the Eisenhower commercial, so we
can't demonstrate it."
label whatsnew46:
eodd "As of 4.6, we now support separate padding and margin for the
left, right, top, and bottom sides of a window."
eodd "This means that a game can have oddly shaped windows without
having to go beyond the style system."
e "We also introduced a new layer system, and the ability to have
transitions affect only one layer."
e "Because of this we can do things like slide away a window..."
$ renpy.transition(slideawayup, 'transient')
$ renpy.pause(1.5)
$ renpy.transition(slidedown, 'transient')
e "... and slide it back in again."
e "Also new in this release is the ability to specify transitions
that occur when you enter and exit the game menu."
e "Right click to see them, if you want."
e "A few more obscure features involving things like overlays and
activated widgets round out the 4.6 release."
label whatsnew47:
e "Ren'Py 4.7 brought with it a total rewrite of the way text is
rendered to the screen."
e "It introduced text tags, which let a script writer control how
text is shown on the screen."
e "Text tags can make text {b}bold{/b}, {i}italic{/i}, or even
{u}underlined{/u}."
e "They can make the font size {size=+12}bigger{/size} or
{size=-8}smaller{/size}."
e "They can even change the
{color=#f00}color{/color}
{color=#ff0}of{/color}
{color=#0f0}the{/color}
{color=#0ff}text{/color}."
e "We also added bold, italic, and underline style properties, which can
be styled onto any text."
e "Used with care, text tags can enhance {b}your{/b} game."
e "{u}Used{/u} with {i}abandon,{/i} they {b}can{/b} make {b}your{/b}
game {color=#333}hard{/color} {color=#888}to{/color} {color=#ccc}read{/color}."
e "With great power comes great responsibility, after all."
e "And we want to give you all the power you need."
label whatsnewend:
e "Anyway, now that you've heard about some of the new features, is there anything
else I can help you with?"
return
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

+5 -15
View File
@@ -9,6 +9,7 @@ def match_times(source, dest):
def dosify(s):
return s.replace("\n", "\r\n")
return s
def copy_file(source, dest, license=""):
@@ -43,18 +44,12 @@ def copy_tree(source, dest, should_copy=lambda fn : True, license=""):
if "/CVS" in dirpath:
continue
if "/.svn" in dirpath:
continue
reldir = dirpath[len(source):]
dstrel = dest + "/" + reldir
for i in dirnames:
if i == "CVS":
continue
if i == ".svn":
continue
os.mkdir(dstrel + "/" + i)
@@ -97,7 +92,7 @@ def main():
doc_files = [
'example.html',
'reference.html',
'tutorial.html',
'style.css',
]
@@ -105,14 +100,11 @@ def main():
copy_tree("doc", target + "/doc",
should_copy = lambda fn : fn in doc_files)
# Copy the game
# Copy the game
copy_tree(gamedir, target + "/game",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~") and not fn.endswith(".mpg"))
copy_tree("common", target + "/common",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
copy_tree("extras", target + "/extras",
copy_tree("common", target + "/common",
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
def cp(x, license=""):
@@ -123,11 +115,9 @@ def main():
cp("README_RENPY.txt")
cp("archive_images.bat")
cp("run_game.py", license=license)
copy_file("run_game.py", target + "/run_game.pyw", license=license)
cp("archiver.py", license=license)
# cp("build_exe.py", license=license)
cp("build_exe.py", license=license)
cp("add_from.py", license=license)
cp("dump_text.py", license=license)
cp("renpy-mode.el")
-13
View File
@@ -1,13 +0,0 @@
Steps one needs to take to make a Ren'Py distro.
0) Update the documentation. (This involves dump_styles, and perhaps
copying in a new config.keymap)
1) Change the version in run_game.py
2) In cygwin, run "release.sh <version>"
3) Test run_game.exe
4) Test console.exe
8) Release.
-14
View File
@@ -1,14 +0,0 @@
Some notes about making sure things are imported in the proper order:
1) Every module should import 'renpy'.
2) It's okay for modules to 'import renpy.game as game'.
3) Those are all the imports that normal modules should do.
4) renpy/__init__.py should list all the modules in the system, a
topologically sorted order, such that if a definition in a uses
a definition in b, b comes before a. Please note that this only
considers the top level of the file.
+6 -6
View File
@@ -1,12 +1,12 @@
all:: reference.html example.html
all:: tutorial.html example.html
reference.html: reference.xml preprocess.py stylesheet.xslt style.css styles.xml
python preprocess.py reference.xml > reference.hi.xml
xsltproc stylesheet.xslt reference.hi.xml > reference.html
cp reference.html style.css ~/ab/website/renpy/devel/doc
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css styles.xml
python preprocess.py tutorial.xml > tutorial.hi.xml
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml ../demo2/script.rpy
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml
python preprocess.py example.xml > example.hi.xml
xsltproc stylesheet.xslt example.hi.xml > example.html
cp example.html style.css ~/ab/website/renpy/devel/doc
-15
View File
@@ -1,15 +0,0 @@
# With pygame surfaces and fill.
Menu mousemove: .1336, .1244, .1437
"Save when I tell you to": .055, .066, .0476
# Without fill.
Menu mousemove: .115, .129, .134
"Save when I tell you to": .050, .057, .046
# With new surface code.
Menu mousemove: .02something
"Save when I tell you to": .038, .040, .038
-44
View File
@@ -1,44 +0,0 @@
import xml.dom.minidom
import sys
def escape(s):
return s.replace("&", "&amp;").replace("<", "&lt;").replace('"', "&quot;")
def transform_children(node):
rv = [ ]
for n in node.childNodes:
rv.append(transform(n))
return ''.join(rv)
def transformElement(node):
attributes = ' '.join(['%s="%s"' % (k, escape(v)) for k,v in node.attributes.items()])
tag = node.tagName
# Otherwise, the default.
return "<%s %s>%s</%s>" % (node.tagName, attributes,
transform_children(node), node.tagName)
def transform(node):
if node.nodeType == node.ELEMENT_NODE:
return transformElement(node)
else:
return escape(str(node))
def main():
dom = xml.dom.minidom.parse(sys.argv[1])
print transform(dom.documentElement)
if __name__ == "__main__":
main()
-163
View File
@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<renpydoc>
<!-- (define-key xml-mode-map '(control return) 'tompy-xml-ctrlret) -->
<section title="Say Statement">
<rule name="statement">
<alt>string</alt>
<alt>string string</alt>
<alt>identifier string</alt>
</rule>
<formal>
These three productions are the various forms of the say
statement. In order, they are the 1-string form, the 2-string
form, and the name-string form. We'll discuss individually
what each one does.
</formal><formal>
The 1-string form calls the <f>say</f> function (found in the
execution context) with the string.
</formal><formal>
The 2-string form also calls the <f>say</f> function, but this
time it passes it the two strings. The strings are passed in the
order in which they appear.
</formal><formal>
When a identifierstring say statement is executed, the first thing
that happens is that the identifier is looked up in the execution
context. It's an error if the name is not defined. If the name
is bound to a string, both strings are passed to the <f>say</f>
function, as if the 2-string form was used. Otherwise, the
<f>say</f> method is called on the object that the identifier was
bound to, with the string as the only argument.
</formal>
<user>
While the various say statements are very flexible, there are
some conventions that we suspect users will use when writing
games.
</user><user>
The 1-string form of the say statment is rendered to the user in
a text box without any name associated with it. It's used to
indicate the thoughts of the POV character, or to narrate
actions that occur in the game.
</user><user>
The 2-string form is displayed with the first string as a name,
and the second string as the line of dialogue said by the
character with that name. It's used to indicate spoken dialogue,
usually from characters that are minor enough to not have a
character object associated with them. The 2-string form is
rarely used directly, as normally the name-string or 1-string
forms are preferred for dialogue from main characters.
</user><user>
Generally, when the name-string form is used, the first name
refers to a character object. The name field of that character
object is looked up, and used to display dialogue as if the
2-string form was used. So the name-string form is what is used
when we want a main character to say something. This form allows
the direct use of the character's name before the dialogue,
without requiring the quoting needed by the 2-string form.
</user>
<example>
"I was walking down the street one day, when I came across the postman."
me "Hey, any mail for me today?"
"Postman" "Yeah, a package came for you. But there's no return address."
</example>
</section>
<subsection title="Image Display">
<formal>
In Ren'Py, image display is controlled by a pair of image
lists. The two image lists are the are called the master image
list and the temporary image list. These lists contain python
objects that are capable of drawing themselves to the
screen. When the time comes to show something to the user, we
iterate through the temporary image list, drawing things on
the screen in the order in which they appear in the list. So
the first thing in the list will be the background, and the
thing that's drawn closest to the user will be the last thing
in the list.
</formal><formal>
We can divide the execution of a Ren'Py program into periods
where we are showing a screen to the user (for example, during
the execution of a say statement) and periods where we are
not. After a period of showing the screen to the user, the master image
list is copied over the temporary image list.
</formal>
<user>
We have two image lists for two reasons. The first is the idea
that only the temporary image list will include transitions,
and once we're done performing the transition, we want it to
be remove from the list. The second reason is an idea that
(behind the scenes) the temporary list will also include
interface elements (like boxes containing character
dialog). We, in general, want these boxes to be shown to the
user once and then removed from the screen.
</user>
<rule name="image_name">
<alt>identifier+</alt>
</rule>
<formal>
An image name consists of one or more identifiers. The first
identifier is called the primary identifier of this image
name, while the second and later identifier are called
secondary identifers. An image name may not include a Ren'Py
keyword.
</formal><formal>
Ren'Py maintains an image dictionary, which maps between image
names and objects implementing images that can be show to the
user.
</formal>
<user>
The idea behind primary identifiers is that we will normally
want to display only one image of a character at a time. So we
use an image's primary identifier to find other images in the
master display list that have the same primary identifier.
</user>
<rule name="statement">
<alt>"image" image_name "=" python_expression</alt>
</rule>
<formal>
This adds a new entry to the image dictionary. Specifically, the
python_expression is evaluated to get something that's drawable,
and then that object is stored in a tuple in the image
dictionary.
</formal>
<user>
In general, the python expression will be a call to the image
constructor, which loads in a new image from disk (or
somewhere... read the documentation for the image constructor.)
It's also possible to use another constructor, like the one for
animation, but that will probably be more rare.
</user><user>
It's important to note that this doesn't actually load the image
into ram, until close to the time when the image is actually
needed.
</user><user>
In general, it makes sense to ensure that all image statements
execure during game startup, before the call to
startup_complete().
</user>
<example>
image woods = image("backgrounds/woods.jpg")
image eileen red upset = image("eileen/red_upset.png")
image eileen red happy = image("eileen/red_happy.png")
</example>
</subsection>
</renpydoc>
-373
View File
@@ -1,373 +0,0 @@
To write a game using Ren'Py, one must become familar with the Ren'Py
script language. This file gives some basic concepts and a list of
Ren'Py statements. A second file will give a list of python classes
and functions that are intended to be used from Ren'Py code.
A Ren'Py script can first be seen as a long string of unicode
characters. This string is first broken up into logical lines, then
the lines are organized into blocks, and finally as a tree of
statements. So we'll begin by defining some basic terms, and then
we'll go on to give a list of statements Ren'Py understands.
Logical Lines:
Each file consists of one or more logical lines. The first logical
line begins at the first character file, and subsequent logical lines
begin immediately following the end of the previous line. Logical
lines are normally ended by the first newline encountered. However,
there are several cases that will cause a logical line to extend past
a newline character:
<ul>
<li>
A logical line containing a string must extend at least until the
end of that string.
</li>
<li>
A logical line parenthesis, brackets, or braces cannot end until
each opening character ('(', ']', or '}') is matched with a closing
character (')', ']', or '}'). The expression enclosed within is called
a parenthetical expression.
</li>
<li>
A newline immediately preceded by a backslash will not end a logical
line. Instead, both characters will be treated as whitespace, and
ignored.
<li>
</ul>
Logical lines are numbered by giving the number of the physical line
in the file on which the logical line begins. (At most one logical
line can exist on each physical line.)
If a logical line contains a hash mark in it ('#'), all characters
from the hash mark to character before the next physical newline are
considered to be part of a comment, and are ignored as if they were
whitespace. If, after this step, a logical line consists entirely of
whitespace, it is ignored.
Each logical line has an indentaion level. This is computed by first
converting tabs to spaces (using 8-space tab stops), and then looking
at the number of spaces preceding the first non-whitespace character
on the line.
The following examples all consist of single logical lines with
indentation level 0.
<example>
me "How are you doing?"
</example>
<example>
"It's a question that I ask myself every day. Today, however, is
the first time I've asked it of someone else.
</example>
<example>
$ my_list = [ 1, 2, 3, 4,
5, 6, 7, 8 ]
</example>
<example>
$ a = 1 \
+ 2 \
+ 3
</example>
Blocks:
If a logical line is followed by a logical line with greater
indentation, the second logical line is considered to be part of the
block of the first logical line. This block contains all logical lines
with an indentation level equal to that of the second logical line,
provided there are no intervening lines with an indentaion level that
is less than that of the second logical line. If a line is encountered
with an indentation level that is greater than that of the first
logical line, but less than that of the second, the indentation is
mismatched and an error is reported.
An example of a block (as part of an if statement) is:
<example>
if happy:
e "I'm feeling really happy today."
e "It's like I'm the happiest person in the world."
</example>
An example of an indentation mismatch is:
<example>
else:
e "I'm having a really bad day."
e "I can't even get the indentation right."
</example>
One of the things we can parse is a string literal. String literals
begin with a quote character (' or "), and end with a matching
unescaped quote character. Strings use backslash (\) as an escape
character. The following are suppored escape sequences:
<ul>
<li>"\\" - Backslash</li>
<li>"\"" - Double Quote</li>
<li>"\'" - Single Quote</li>
<li>"\n" - Newline</li>
<li>"\ " - Space</li>
</ul>
As the string is being parsed, but before escape processing is done,
contiguous sequences of whitespace are converted to single space
characters. To include more than single spaces, explicitly escape the
desired whitespace.
<example>
"I hate having to \"escape\" whitespace."
</example>
Names and Numbers:
Names begin with a character in the set [a-zA-Z_], and may contain
additional characters in the set [a-zA-Z0-9_]. A keyword in Ren'Py may
not be used as a name.
Some example names are:
<example>
eileen
lucy
molly
crm_114
_underscore_
</example>
A dotted_name consists of one or more names, separated by dots '.'.
<example>
eileen
eileen.name
eileen.address.street
</example>
A number matches the characters [0-9.].
<example>
1
1234.
3.14159
.2
</example>
Python:
Ren'Py scripts may contain embedded python. There are three kinds of
python things that can be mixed into Ren'Py: simple_expressions,
python_expressions, and python_statements.
A simple_expression consists of either a dotted_name, string, or
number optionally followed by a parenthetical expression, or just a
single parenthetical expression.
Examples of simple_expressions are:
<example>
eileen
sample.image("foo.jpg", size=(100,200))
"test"
(1 + 2 + 3)
</example>
Python_expressions occur in statements such as if and while, and
extend from the current location on the logical line to just before
the next ':' that is not contained within a parenthetical expression
on the logical line.
Examples of expressions that are python_expressions but not
simple_expressions are:
<example>
1 + 2 + 3
foo.bar().baz()
</example>
Finally, python_statements simply assume that all remaining
information on the line should be interpreted as python code.
Statements:
The parsing of each logical line is syntax-directed. When parsing a
block, all of the logical lines in that block are parsed as
statements. The logical lines at the top level of a file are
considered to form a block. Here, we give the rules for parsing each
kind of statement, as well as an overview of what each statement
does. We'll first give some insight as to what a statement does when
used with the standard library, and then we'll write a little about
how it goes about accomplishing that task.
Say Statements:
<rule name="statement">
<alt>string</alt>
<alt>simple_expression string</alt>
</rule>
Probably the most commonly used statement in a Ren'Py script is the
say statement. It's so commonly used that we chose not to denote it
with a keyword, but instead to simply make all bare strings or
simple_expression string pairs into say statements.
Executing a say statement results in a line of dialogue or thought
being displayed to the user. Ren'Py then waits for the user to click
or otherwise dismiss the display before proceeding with execution of
the program.
In the single string version of the statement, the string is taken as
a thought or narration that should be displayed, unadorned, to the
user. If the string is preceded by a simple_expression, the expression
is first evaluated to yield a string, and that string is used to
indicate to the user who is speaking the line of dialogue.
Ren'Py executes say statements by first evaluating the
simple_expression, if present, and then calling the say function with
the values of the two strings, or None if the simple_expression is not
defined. Take a look at the documentation for the say function to see
what happens from there, as behind the scenes it's a little more
complicated that what we describe here.
Some example say statements are:
<example>
"I was walking down the street one day, when I came across the postman."
me "Hey, any mail for me today?"
"Postman" "Yeah, a package came for you. But there's no return address."
</example>
Here, we assume that 'me' is an expression that expands to the name of
the main character.
Menu Statement:
<rule name="statement">
<alt>"menu" name? ":" menu_block</alt>
</rule>
<rule name="menu_item">
<alt>string</alt>
<alt>string ( "if" python_expression )? ":" block</alt>
<alt>"set" simple_expression</alt>
</rule>
The menu statement displays a menu to the user, waits for the user to
provide a response, and then runs a block of code corresponding to the
choice that the user made. In a visual novel game, menu statement are
the main way in which the user can interact with the story.
Menu statements begin with the word "menu". This word can be followed
by an optional name. If the name is provided, it's as if the menu
statement was proceded by a label statement with that name. A menu
statement must have a block associated with it, and each of the
logical lines in this menu_block are parsed as menu_items.
There are three kinds of menu_items. The first is a logical line
containing only a string. These menu_items are used to provide blocks
of text in the menu that are not selectable. For example, they can be
use to provide a prompt to the user.
The second kind of menu_item is a string, followed by an optional if
clause, followed by a colon. This menu_item has a block of statements
associated with it. It's used to indicate a choice on the menu. If the
if clause is supplied, the choice is only presented to the user if the
python_expression is true. If a choice is selected by the user, the
statements in the block associated with the choice are executed before
control continues after the menu statement.
The final menu_item is the word "set" followed by a
simple_expression. The expression is evaluated to get a set of menu
items to supress. This set is used to filter the menu choices. If a
choice string is in the set, then the corresponding menu choice is not
displayed to the user. If a set is defined, then when a choice has
been made, the corresponding choice string is added to the set. This
provides an easy way of having a menu where the user can select each
item at most once.
Here's an example of a fairly complex menu, that demonstrates all
these features.
<example>
menu what_to_do:
set what_to_do_set
"What should we do today?"
"Go to the movies.":
"We went to the movies."
"Go shopping.":
"We went shopping, and the girls both bought swimsuits."
$ have_swimsuits = True
"Go to the beach." if have_swimsuits:
"We went to the beach together."
</example>
The actual task of displaying a menu is implemented internally by a
call to the menu python function.
Graphics Statements:
Before we can discuss the graphics statements, we must first discuss
the graphics model that Ren'Py uses. This will be covered in greater
detail in the library reference, but we want to cover the fundamentals
here to allow the graphics statements to be put in proper context.
First, let me define what it means to display a scene. The execution
of a Ren'Py script can be roughly divided into periods when the script
is executing code, and periods when the script is blocked wating for
user input (as is the case when a menu or say statement
executes). When Ren'Py is waiting for input, it is displaying a
scene. When it gets the input it's waiting for and execution
continues, we say it's finished displaying the scene.
The display in Ren'Py is controlled by three scene lists. These scene
lists are called the master scene list, the overlay scene list, and
the transient scene list.
The only scene list that is displayed to the user is the transient
scene list. This list is ordered such that items that are at the end
of the scene list are closest to the user. All of the items in the
scene lists must be displayable--- capable of drawing themselves to
the screen. See the library reference for the exact API displayable
objects must implement.
When a scene is displayed, the transient scene list is extended by
appending the overlay scene list to it. When display finishes, the
transient scene list is replaced with a copy of the master scene
list. The scene, show, and hide statements work by modifying both the
master and transient scene lists.
Having three scene lists may seem like needless complexity, but it's
useful in implementing transitions and other effects. When we want to
fade in an image, we add that image to the master scene list, and that
image wrapped in an object that handles the fade effect into the
transient scene list. When the scene is then displayed, the image will
fade it. The wrapped image on the transient list is then replaced
with the image from master list, removing any overhead the fade effect
can entail.
The transient list also helps when displaying UI elements, like
dialogue or menus. Finally, the overlay list is used to display things
that should always be presented to the user, like load and save
buttons.
-15
View File
@@ -1,6 +1,5 @@
#!/usr/bin/python
import os
import re
import sys
import time
@@ -85,12 +84,6 @@ def function(m):
renpy.store.renpy = renpy.exports
func = eval(name, store)
if isinstance(func, renpy.curry.Curry):
if func.callable == renpy.curry.Curry:
func = func.args[0]
else:
func = func.callable
doc = func.__doc__
@@ -133,14 +126,6 @@ def main():
s = f.read()
f.close()
os.chdir("..")
try:
renpy.main.main("dump_styles")
except "foo":
pass
os.chdir("doc")
s = re.sub(r"<!-- func (\S+) -->", function, s)
s = re.sub(r"<!-- include (\S+) -->", include, s)
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

-8
View File
@@ -1,8 +0,0 @@
init:
image black = Solid((0, 0, 0, 255))
label main_menu:
$ renpy.renpy.style.write_docs("doc/styles.xml")
$ raise "foo"
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env python
# This program dumps all the text found in the script to the file text.txt.
# If on windows, it also tries to show text.txt to the user.
import codecs
import sys
import os
import re
import glob
import renpy
def process_block(block, out):
for fn, ln, text, child in block:
if text.startswith("$") or text.startswith("python"):
continue
if text.startswith("init"):
continue
if text.startswith("if") or text.startswith("while"):
process_block(child, out)
continue
for m in re.finditer(r'"((?:[^\\"]+|\\.)+)"|' +
r"'((?:[^\\']+|\\.)+)'", text):
s = m.group(1) or m.group(2)
s = re.sub(r'\s+', ' ', s)
s = re.sub(r'\\.', ' ', s)
s = re.sub(r'\{.*?\}', '', s)
print >>out, s.encode('utf-8')
print >>out
process_block(child, out)
def process(fn, out):
block = renpy.parser.group_logical_lines(renpy.parser.list_logical_lines(fn))
process_block(block, out)
def main():
pattern = "game/*.rpy"
if len(sys.argv) >= 2:
pattern = sys.argv[1]
files = glob.glob(pattern)
files = [ i for i in files if not i.startswith("common/") ]
out = file("text.txt", "w")
out.write(codecs.BOM_UTF8)
for fn in files:
process(fn, out)
out.close()
try:
os.startfile('text.txt')
except:
pass
if __name__ == "__main__":
main()
-1
View File
@@ -1 +0,0 @@
../../extras/fullscreen.rpy
-1
View File
@@ -1 +0,0 @@
../../extras/kanamode.rpy
-1
View File
@@ -1 +0,0 @@
../script.rpy
-102
View File
@@ -1,102 +0,0 @@
init -10:
$ config.searchpath.append('moonlight')
# Set up the size of the screen, and the window title.
$ config.screen_width = 800
$ config.screen_height = 600
$ config.window_title = "A Ren'Py Extras Game"
# These are positions that can be used inside at clauses. We set
# them up here so that they can be used throughout the program.
$ left = Position(xpos=0.0, xanchor='left')
$ right = Position(xpos=1.0, xanchor='right')
$ center = Position()
# Likewise, we set up some transitions that we can use in with
# clauses and statements.
$ fade = Fade(.5, 0, .5) # Fade to black and back.
$ dissolve = Dissolve(0.5)
$ wiperight = CropMove(1.0, "wiperight")
$ wipeleft = CropMove(1.0, "wipeleft")
$ wipeup = CropMove(1.0, "wipeup")
$ wipedown = CropMove(1.0, "wipedown")
$ slideright = CropMove(1.0, "slideright")
$ slideleft = CropMove(1.0, "slideleft")
$ slideup = CropMove(1.0, "slideup")
$ slidedown = CropMove(1.0, "slidedown")
$ slideawayright = CropMove(1.0, "slideawayright")
$ slideawayleft = CropMove(1.0, "slideawayleft")
$ slideawayup = CropMove(1.0, "slideawayup")
$ slideawaydown = CropMove(1.0, "slideawaydown")
$ irisout = CropMove(1.0, "irisout")
$ irisin = CropMove(1.0, "irisin")
# Now, we declare the images that are used in the program.
# Images.
image black = Solid((0, 0, 0, 255))
image white = Solid((255, 255, 255, 255))
image yellow = Solid((255, 255, 200, 255))
# Opening Sequence.
image bigbeach1 = Image("bigbeach1.jpg")
image presents = Image("presents.png")
# Backgrounds.
image beach1 = Image("beach1b.jpg")
image beach1 mary = Image("beach1c.jpg")
image beach1 title = Image("beach1a.jpg")
image beach2 = Image("beach2.jpg")
image beach3 = Image("beach3.jpg")
image dawn1 = Image("dawn1.jpg")
image dawn2 = Image("dawn2.jpg")
image library = Image("library.jpg")
# Ending 1.
image transfer = Image("transfer.png")
image moonpic = Image("moonpic.jpg")
image nogirlpic = Image("nogirlpic.jpg")
# Ending 3.
image littlemary = Image("littlemary.jpg")
# Ending 4.
image hospital1 = Image("hospital1.jpg")
image hospital2 = Image("hospital2.jpg")
image hospital3 = Image("hospital3.jpg")
image heaven = Image("heaven.jpg")
# Endings Common
image good_ending = Image("ending.jpg")
image bad_ending = Image("badending.jpg")
# Mary.
image mary dark confused smiling = Image("mary_dark_confused_smiling.png")
image mary dark confused wistful = Image("mary_dark_confused_wistful.png")
image mary dark crying = Image("mary_dark_crying.png")
image mary dark laughing = Image("mary_dark_laughing.png")
image mary dark sad = Image("mary_dark_sad.png")
image mary dark smiling = Image("mary_dark_smiling.png")
image mary dark vhappy = Image("mary_dark_vhappy.png")
image mary dark wistful = Image("mary_dark_wistful.png")
image mary dawn confused smiling = Image("mary_dawn_confused_smiling.png")
image mary dawn confused wistful = Image("mary_dawn_confused_wistful.png")
image mary dawn crying = Image("mary_dawn_crying.png")
image mary dawn laughing = Image("mary_dawn_laughing.png")
image mary dawn sad = Image("mary_dawn_sad.png")
image mary dawn smiling = Image("mary_dawn_smiling.png")
image mary dawn vhappy = Image("mary_dawn_vhappy.png")
image mary dawn wistful = Image("mary_dawn_wistful.png")
label start:
jump example
-21
View File
@@ -1,21 +0,0 @@
# This file contains a minimal set of style changes needed to have
# Ren'Py work with a game that's 640x480 in size.
init 1:
# Change the screen width.
$ config.screen_width = 640
$ config.screen_height = 480
# Font sizes.
$ style.default.size = 20
$ style.button_text.size = 20
$ style.file_picker_text.size = 14
# Perhaps change the fudge factor on windows, if line spacing
# looks weird.
if renpy.windows():
$ style.default.line_height_fudge = -4
$ style.file_picker_entry.xminimum = 320
$ library.thumbnail_width = 60
$ library.thumbnail_height = 45
-35
View File
@@ -1,35 +0,0 @@
# This file adds a number of buttons to the lower-right hand corner of
# the screen. Three of these buttons jump to the game menu, which
# giving quick access to Load, Save, and Prefs. The fourth button
# toggles skipping, to make that more convenient.
init:
# Give us some space on the right side of the screen.
$ style.window.right_margin = 100
python:
def toggle_skipping():
config.skipping = not config.skipping
def button_game_menu():
# to save typing
ccinc = renpy.curried_call_in_new_context
ui.vbox(xpos=0.98, ypos=0.98, xanchor='right', yanchor='bottom')
ui.textbutton("Skip", clicked=toggle_skipping)
ui.textbutton("Save", clicked=ccinc("_game_menu_save"))
ui.textbutton("Load", clicked=ccinc("_game_menu_load"))
ui.textbutton("Prefs", clicked=ccinc("_game_menu_preferences"))
ui.close()
config.overlay_functions.append(button_game_menu)
-63
View File
@@ -1,63 +0,0 @@
# In this file, we have an example of replacing the default menu with
# a custom one. This new menu consists of a series of centered
# buttons, with dialogue appearing in a narration window.
# You can try this one out by dropping it right into the game
# directory.
# The code that implements button menus.
init:
python:
style.create('menu_button', 'button')
style.create('menu_button_text', 'button_text')
def menu(menuitems):
narration = None
ui.keymousebehavior()
ui.window(style='menu_window')
ui.vbox(xanchor='center', xpos=0.5)
for label, value in menuitems:
if value is None:
narration = label
continue
ui.textbutton(label,
style="menu_button",
text_style="menu_button_text",
clicked=ui.returns(value))
ui.close()
if narration:
narrator(narration, interact=False)
rv = ui.interact()
renpy.checkpoint()
return rv
# Styles to make button menus look good.
init 1:
python hide:
style.menu_window.background = None
style.menu_window.yminimum = 0
style.menu_window.ypos = 0.40
style.menu_window.yanchor = 'center'
style.menu_window.xfill = True
style.menu_button.background = Solid((0, 0, 255, 128))
style.menu_button.xfill = True
style.menu_button.top_padding = 5
style.menu_button.bottom_margin = 5
style.menu_button_text.xpos = 0.5
style.menu_button_text.xanchor = 'center'
style.menu_button_text.hover_color = (255, 255, 0, 255)
style.menu_button_text.idle_color = (255, 255, 255, 255)
-23
View File
@@ -1,23 +0,0 @@
# This file contains code to automatically switch the game into
# fullscreen mode.
# Please note: Fullscreen mode is broken on some platforms (mostly
# virtual windows machines like vmware and virtual PC). So having
# this code automatically execute could make your game hard to use
# on those platforms.
#
# The way we work around this is to tell users of these platforms
# to press 'f' to toggle fullscreen mode. If the game's running in
# windowed mode, the problems don't seem to appear.
#
# You should probably put words to that effect in some sort of
# README file.
# The first time this code runs, the game switches into fullscreen.
# After that, it respects the user's preference.
init:
python:
if not persistent.set_fullscreen:
persistent.set_fullscreen = True
_preferences.fullscreen = True
-262
View File
@@ -1,262 +0,0 @@
# This extra implements an image gallery, complete with automatic
# unlocking of the images that have been shown to the user. The images
# are divided into pages, with a fixed number of images on each page.
# Right now, this is configured to used the images found in the demo.
# The images are repeated quite a bit, so that we can get four pages
# of them. You probably wouldn't do that if you were using this in a
# real game.
# To see this in action, drop it into the game directory of the
# demo.
# Configuration.
init:
python hide:
# The number of columns and rows of images to show in the
# gallery.
store.gallery_cols = 3
store.gallery_rows = 4
# The size that each image should be scaled to, and that
# thumbnails should be.
store.gallery_width = 160
store.gallery_height = 120
# The contents of each of the page. Each of these is a list of
# tuples with the first element of the tuple being the image
# filename and the second element being the name of the image
# that will unlock the image with this filename. (That is,
# the name that is used in show or scene statements, as a
# string.)
#
# When displaying an image as a thumbnail, this code first
# looks for the file thumbnail_<filename>. If that file
# exists, it should be a gallery_width x gallery_height
# thumbnail. Otherwise, a thumbnail is automatically
# generated, but it may screw up the aspect ratio of the
# image.
#
# You probably want to create thumbnails for most images, to
# limit memory consumption.
page1 = [
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
]
page2 = [
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
( "carillon.jpg", "carillon" ),
]
page3 = [
( "whitehouse.jpg", "whitehouse" ),
( "washington.jpg", "washington" ),
]
page4 = [
( "9a_happy.png", "eileen happy" ),
( "9a_vhappy.png", "eileen vhappy" ),
( "9a_concerned.png", "eileen concerned" ),
]
# This is the actual list of gallery pages. It's a list
# of tuples, with the first element being the name of the
# page, the second being the contents of the page (one of
# the lists created above), and the third image being the
# image used as the background of the page.
store.gallery_pages = [
("Backgrounds 1", page1, "washington.jpg"),
("Backgrounds 2", page2, "whitehouse.jpg"),
("Backgrounds 3", page3, "carillon.jpg"),
("Character Art", page4, "washington.jpg"),
]
# A window containing the gallery page buttons.
style.create('gallery_pages', 'default')
style.gallery_pages.xpos = 0.99
style.gallery_pages.xanchor='right'
style.gallery_pages.ypos = 0.02
style.gallery_pages.yanchor = 'top'
# A button that links to a gallery page.
style.create('gallery_page_button', 'button')
style.create('gallery_page_button_text', 'button_text')
# A button that returns us to from whence we came.
style.create('gallery_return_button', 'button')
style.create('gallery_return_button_text', 'button_text')
style.gallery_return_button.xpos = 0.99
style.gallery_return_button.xanchor='right'
style.gallery_return_button.ypos = 0.98
style.gallery_return_button.yanchor = 'bottom'
# The grid containing the gallery image buttons.
style.create('gallery_grid', 'default')
# The style of the buttons in the gallery.
style.create('gallery_button', 'default')
# Right now, the backgrounds are all solids, but in a more
# professional version, the insensitive background would
# probably be a placeholder image that indicates that a
# picture has yet to be unlocked.
style.gallery_button.insensitive_background = Solid((192, 192, 192, 255))
style.gallery_button.idle_background = Solid((255, 255, 255, 255))
style.gallery_button.hover_background = Solid((255, 255, 192, 255))
style.gallery_button.left_margin = 5
style.gallery_button.right_margin = 5
style.gallery_button.top_margin = 5
style.gallery_button.bottom_margin = 5
style.gallery_button.left_padding = 5
style.gallery_button.right_padding = 5
style.gallery_button.top_padding = 5
style.gallery_button.bottom_padding = 5
# The style of the images in the buttons in the gallery.
style.create('gallery_button_image', 'default')
# The style of the images that are being shown to the user.
style.create('gallery_image', 'image_placement')
# The transition used when switching gallery pages.
store.gallery_transition = Dissolve(0.5)
python:
# The function that actually manages the display of the image
# gallery.
def gallery():
page = 0
while True:
images = gallery_pages[page][1]
ui.image(gallery_pages[page][2])
# Show the names of the various gallery pages.
ui.window(style='gallery_pages')
ui.vbox(focus="gallery_pages")
for i in range(0, len(gallery_pages)):
if i == page:
clicked = None
else:
clicked = ui.returns(("page", i))
ui.textbutton(gallery_pages[i][0],
style='gallery_page_button',
text_style='gallery_page_button_text',
clicked=clicked)
ui.close()
# Show the return button.
ui.textbutton('Return',
style='gallery_return_button',
text_style='gallery_return_button_text',
clicked=ui.returns(("return", None)))
# Show the grid for this page.
ui.grid(gallery_cols, gallery_rows, style='gallery_grid')
# For each grid cell.
for i in range(0, gallery_cols * gallery_rows):
# Fill empty space with nulls.
if i >= len(images):
ui.null()
continue
# Otherwise, get the filename and spec and see if
# we've unlocked it.
filename, spec = images[i]
if spec:
spec = spec.split()
if spec and tuple(spec) not in persistent._seen_images:
filename = None
clicked = None
else:
clicked = ui.returns(('show', filename))
# Create the button, containing the appropriate
# image or a null if we haven't unlocked it yet.
ui.button(style='gallery_button', clicked=clicked)
if not filename:
ui.null(width=gallery_width, height=gallery_height)
else:
if renpy.loadable("thumbnail_" + filename):
ui.image("thumbnail_" + filename,
style="gallery_button_image")
else:
ui.add(im.Scale(filename,
gallery_width,
gallery_height))
ui.close()
# Interact with the user.
renpy.transition(gallery_transition)
cmd, arg = ui.interact(suppress_overlay=True, suppress_underlay=True)
# Process the user's commands.
if cmd == "show":
ui.add(Solid((0, 0, 0, 255)))
ui.image(arg)
ui.saybehavior()
renpy.transition(gallery_transition)
ui.interact(suppress_overlay=True, suppress_underlay=True)
if cmd == "page":
page = arg
if cmd == "return":
renpy.transition(gallery_transition)
return
library.main_menu.insert(2, ( "CG Gallery", "gallery"))
label gallery:
$ gallery()
jump _main_menu
-193
View File
@@ -1,193 +0,0 @@
# This is an implementation of Kana mode, a mode in which multiple
# lines of dialogue are shown at once, in a fullscreen window. A
# single line of dialogue is placed onto the screen for each mouse
# click. Calling the clear function clears the screen, ensuring that
# the next line will appear at the top of the sceen.
#
# An example of using Kana mode follows the code that implements it.
# This init block contains all of the code needed to implement
# Kana mode. You probably don't want to change this... unless you
# do. But all of the customization is found below.
init -100:
python:
# This is a list of KanaCharacter, line of dialogue tuples.
kana_display_list = [ ]
# Spacings used.
kana_vspacing = 0
kana_hspacing = 0
def clear(arg=None):
global kana_display_list
kana_display_list = [ ]
def kana_show():
ui.window(style='say_window')
ui.vbox(kana_vspacing)
for char, what in kana_display_list:
char.show(what)
ui.close()
ui.saybehavior()
ui.interact()
renpy.checkpoint()
# Characters and the narrator should be instances of this
# object.
class KanaCharacter(object):
def __init__(self, who,
what_prefix='"',
what_suffix='"',
who_style='say_label',
what_style='say_dialogue',
**properties):
self.who = who
self.what_prefix = what_prefix
self.what_suffix = what_suffix
self.who_style = who_style
self.what_style = what_style
self.properties = properties
def __call__(self, what):
kana_display_list.append((self, what))
kana_show()
def show(self, what):
ui.hbox(kana_hspacing)
ui.text(self.who, style=self.who_style,
**self.properties)
ui.text(self.what_prefix + what + self.what_suffix,
style=self.what_style)
ui.close()
# This section updates the styles that are used by the Kana mode stuff
# so that they work well with Kana mode. The user may want to change
# these so that they look more appropriate.
init:
# The space between lines of dialogue.
$ kana_vspacing = 10
$ kana_hspacing = 10
$ style.say_window.background = Solid((0, 0, 0, 96))
$ style.say_window.xfill = True
$ style.say_window.yfill = True
$ style.say_window.xmargin = 0
$ style.say_window.ymargin = 0
$ style.say_window.xpadding = 20
$ style.say_window.ypadding = 20
$ style.say_label.minwidth = 100
$ style.say_label.textalign = 1.0
# This just makes it look nicer.
$ style.say_dialogue.rest_indent = 9
# Adjust the menu to match.
$ style.menu_window.background = Solid((0, 0, 0, 96))
$ style.menu_window.xfill = True
$ style.menu_window.yfill = True
$ style.menu_window.xmargin = 0
$ style.menu_window.ymargin = 0
$ style.menu_window.xpadding = 20
$ style.menu_window.ypadding = 20
$ style.menu.xpos = 110
# And now, the example. This example can't be simply run, but instead
# should serve as a guide to how to get Kana mode working in your own
# game.
# In the init block, declare the characters as KanaCharacters. We also
# want to declare the narrator as a special KanaCharacter.
init:
$ p = KanaCharacter("")
$ g = KanaCharacter("Girl:", color=(255, 128, 128, 255))
$ narrator = KanaCharacter("",
what_prefix='',
what_suffix='',
what_style='say_thought')
# Now, the actual script of the example. (An excerpt from Moonlight
# Walks.) Notice how we place calls to clear in places where the
# we want the screen to be cleared.
# This text is here to serve as an example, and shouldn't be used
# in your game.
label example:
show beach2
show mary dark wistful
$ clear()
p "What can you tell me about your parents?"
g "Papa and Mama both came across the ocean as settlers when they
were just children."
g "Papa fought in the war. When it was over, he married Mama, and
they used his pension to move here from the mainland, and to
build us a house."
g "Together, they farmed the land, and eventually they had
children."
g "I had an older sister and a younger sister. I was the middle
child."
$ clear()
show mary dark sad
"She paused for a second to collect her thoughts before
continuing. This part was taking a strain on her."
g "When I was ten, an epidemic hit the island."
g "We came down with it, and so did all of the other families."
g "My family was too sick to move, but our neighbors, the Millers,
sent some of their boys to get help from the mainland."
"I remember thinking that Miller was the last name of my aunt and
uncle, and wondering if they could be related to me."
g "I don't know what happened to them, but I never heard from them
again."
show mary dark crying
g "It lasted a week, and then it was over. My sisters... my
parents... they all..."
g "Now I'm the only one left."
$ clear()
p "I'm sorry."
"I didn't know what else to say to a girl that had lost her
family, and was obviously broken up about it."
show mary dark sad
"She nodded in response, wiped her tears, and we once again
started walking in silence."
menu menu_1:
"Show me the example again.":
jump example
"I'm done. Let me go.":
return
-87
View File
@@ -1,87 +0,0 @@
# This file replaces most of the game menu navigation with overlay
# buttons that jump directly to various parts of the game menu.
# Right clicking hides and shows the buttons, rather than calling
# up the game menu directly.
init:
python hide:
# overlay_menu is an object storing information about the
# overlay menu state.
store.overlay_menu = object()
overlay_menu.shown = False
# overlay_menu is also a new layer, containing the overlay
# menu.
config.layers.append("overlay_menu")
config.overlay_layers.append("overlay_menu")
# This function actually draws the overlay menu.
def overlay_menu_func():
if overlay_menu.shown:
ui.layer("overlay_menu")
ui.vbox(xpos=1.0, xanchor="right", ypos=0.75, yanchor="bottom")
def button(label, target):
ui.textbutton(label, clicked=renpy.curried_call_in_new_context(target))
button("Load Game", "_game_menu_load")
button("Save Game", "_game_menu_save")
button("Preferences", "_game_menu_preferences")
ui.close()
ui.close()
config.overlay_functions.append(overlay_menu_func)
# This function toggles the visibility of the overlay menu.
def overlay_menu_toggle():
shown = not overlay_menu.shown
overlay_menu.shown = shown
# How long should the transitions take?
trans_delay = 0.5
# These transitions assume that the menu is placed on the
# right side of the screen. This indicator gives the
# fraction of the screen that participates in transitions.
trans_frac = 0.75
if shown:
trans = CropMove(trans_delay,
"custom",
startcrop=(trans_frac, 0.0, 0.0, 1.0),
startpos=(1.0, 0.0),
endcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
endpos=(trans_frac, 0.0),
topnew=True)
renpy.transition(trans, 'overlay_menu')
else:
trans = CropMove(trans_delay,
"custom",
endcrop=(trans_frac, 0.0, 0.0, 1.0),
endpos=(1.0, 0.0),
startcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
startpos=(trans_frac, 0.0),
topnew=False)
renpy.transition(trans, 'overlay_menu')
renpy.restart_interaction()
# Add a new underlay that handles the overlay menu toggle.
config.underlay.append(renpy.Keymap(game_menu = overlay_menu_toggle))
-247
View File
@@ -1,247 +0,0 @@
# This is the annoying readback mode. I implemented this because
# people asked for it, not because I like it. I'd prefer you use
# rollback, which is better in every way.
# To use readback, drop this file into your game directory. Readback
# will take over when rollback no longer works, so you'll also need to
# either reduce config.hard_rollback_limit or set
# config.rollback_enabled to false.
# init:
# $ config.hard_rollback_limit = 1
# Readback works by replacing the default Character and Menu objects
# with ones that record what is said in a readback buffer. The user
# can then go back, and what has been said will be shown to them
# again. Images and the like will not change... if you want that, use
# rollback. Heck, use rollback anyway. :-P
# The number of lines of readback can be changed by assigning an
# integer to the readback_limit variable. But leaving it None will
# leave the entire game in the buffer. As this is actually fairly
# memory-efficent, you should probably leave it as None.
# Readback uses two styles, readback_dialogue and
# readback_thought. Change them to change the color and look of
# read-back text.
# If you want to add your own message, as narration, to the readback
# buffer, you can do it by calling the readback function or having
# the readback character say it:
# $ readback('A message for readbackers only.')
# readback "A similar message."
# Note: When using Readback, you can no longer use a string for a
# character's name. All dialogue must be routed through Character
# objects, if it is to show up in the readback buffer. This limitation
# will be fixed in a future version of Ren'Py.
init -100:
python:
# The limit of the number of readbacks to keep. None means no limit.
readback_limit = None
# Set this to true to print out the contents of the readback
# buffer when it is saved.
readback_debug = False
# Readback styles.
style.create('readback_dialogue', 'say_dialogue', '')
style.create('readback_thought', 'say_thought', '')
style.readback_dialogue.color = (255, 128, 128, 255)
style.readback_thought.color = (255, 128, 128, 255)
# No user-servicable parts below this line. ######################
# The readback buffer is a doubly-linked list of readback
# objects.
class Readback(object):
def __init__(self, obj, args):
self.older = None
self.newer = None
self.obj = obj
self.args = args
def show(self):
self.obj.readback(*self.args)
readback_oldest = None
readback_newest = None
readback_count = 0
# This saves a readback entry to the readback buffer.
def readback_save(obj, *args):
store.readback_count += 1
if readback_limit and readback_count > readback_limit:
store.readback_count -= 1
readback_oldest.newer.older = None
store.readback_oldest = readback_oldest.newer
rb = Readback(obj, args)
if readback_newest:
readback_newest.newer = rb
else:
store.readback_oldest = rb
rb.older = readback_newest
store.readback_newest = rb
if readback_debug:
print "---- Readback Buffer ----"
rb = readback_oldest
while rb:
print rb.obj, rb.args
rb = rb.newer
# The rest of this file is replacing the default objects and
# functions with versions that save things in the readback
# buffer.
# Save the old character object.
readback_OldCharacter = Character
readback_OldDynamicCharacter = DynamicCharacter
readback_oldmenu = menu
class Character(readback_OldCharacter):
def __init__(self, who,
readback_style='readback_dialogue',
**kwargs):
readback_OldCharacter.__init__(self, who, **kwargs)
self.readback_style = readback_style
def __call__(self, what, **kwargs):
readback_OldCharacter.__call__(self, what, **kwargs)
readback_save(self, what)
def readback(self, what):
renpy.display_say(self.name, what,
who_style=self.who_style,
what_style=self.readback_style,
window_style=self.window_style,
interact=False,
**self.properties)
class DynamicCharacter(readback_OldDynamicCharacter):
def __init__(self, who,
readback_style='readback_dialogue',
**kwargs):
readback_OldDynamicCharacter.__init__(self, who, **kwargs)
self.readback_style = readback_style
def __call__(self, what, **kwargs):
name = renpy.renpy.python.py_eval(self.name_expr)
readback_OldDynamicCharacter.__call__(self, what, **kwargs)
readback_save(self, name, what)
def readback(self, name, what):
renpy.display_say(self.name, what,
who_style=self.who_style,
what_style=self.readback_style,
window_style=self.window_style,
interact=False,
**self.properties)
class Sayer(object):
def __call__(self, who, what):
renpy.display_say(who, what)
readback_save(self, who, what)
def readback(self, who, what):
renpy.display_say(who, what,
what_style='readback_dialogue',
interact=False)
narrator = Character(None, what_style='say_thought')
say = Sayer()
def readback(what):
readback_save(narrator, what)
def menu(menuitems):
rv = readback_oldmenu(menuitems)
text = '\n'.join([ l for l, v in menuitems
if v is None or v == rv ])
readback(text)
return rv
# This stuff is involved in entering the readback mode.
def readback_mode():
# Try rollback, first.
renpy.rollback()
# If we made it here, we're into readback mode. So let's
# go there now.
renpy.call_in_new_context("readback")
# Add in the readback function.
config.underlay.append(renpy.Keymap(rollback=readback_mode))
# This label is called in a new context, when the user succesfully
# enters Readback mode.
label readback:
# If we have an empty readback buffer, go home.
if not readback_newest:
return
python hide:
rb = readback_newest
while True:
rb.show()
ui.add(renpy.Keymap(rollback=lambda : "older",
rollforward=lambda : "newer",
dismiss=lambda : "dismiss",
))
res = ui.interact()
if res == "newer":
rb = rb.newer
if not rb:
break
elif res == "older":
if rb.older:
rb = rb.older
elif res == "dismiss":
break
return
-1
View File
@@ -1 +0,0 @@
demo2/
BIN
View File
Binary file not shown.
-134
View File
@@ -1,134 +0,0 @@
;;; Comments beginning with ";;;" are commentary added by PyTom to
;;; try to help you understand this file.
;;;
;;; This installer expects that you will have supplied LICENSE.txt
;;; and README.txt files in the root directory of your game.
;;;
;;; To use this, place it in the root directory of your game,
;;; right-click on it, and choose "Compile NSIS script..."
;;; First up, change the settings below to match your game.
;;; The exe used to run your game.
!define EXE "moonlight.exe"
;;; The exe containing the installer. This will be created in the directory
;;; above the root directory containing your game.
!define INSTALLER_EXE "moonlight-1.0ni.exe"
;;; The name and version.
!define PRODUCT_NAME "Moonlight Walks"
!define PRODUCT_VERSION "1.0"
;;; The following settings are only shown to the user in Add/Remove programs
;;; but you'll still want to use them.
!define PRODUCT_WEB_SITE "http://www.bishoujo.us/moonlight/"
!define PRODUCT_PUBLISHER "American Bishoujo"
;;; Ignore this next block of stuff. It's mostly boilerplate.
!include "MUI.nsh"
!define MUI_ABORTWARNING
;;; Change this to change the compression scheme.
SetCompressor lzma
;;; You can change these to customize the bitmaps and icons used for
;;; your installer and uninstaller. Bitmaps should be 150x57.
; !define MUI_HEADERIMAGE
; !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
; !define MUI_HEADERIMAGE_UNBITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
;;; This is the sequencing of the pages that does the actual installation.
;;; you can comment pages out, if you want to.
; Welcome page
!insertmacro MUI_PAGE_WELCOME
; License page
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
; Directory page
!insertmacro MUI_PAGE_DIRECTORY
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
!insertmacro MUI_PAGE_FINISH
; Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES
;;; Okay, that's it for the commentary. You're on your own from here...
;;; but you probably won't need to touch anything below this point.
; Various other defines.
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
; Language files
!insertmacro MUI_LANGUAGE "English"
; Reserve files
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "..\${INSTALLER_EXE}"
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
Section "!${PRODUCT_NAME}" SEC01
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
File /r /x renpy /x *.py /x *.pyw /x installer.nsi /x persistent *.*
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
SectionEnd
Section -AdditionalIcons
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk" "$INSTDIR\README.txt"
SectionEnd
Section -Post
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\${EXE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd
Function un.onUninstSuccess
HideWindow
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
FunctionEnd
Function un.onInit
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
Abort
FunctionEnd
Section Uninstall
Delete "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
RMDir /r "$INSTDIR"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
SetAutoClose true
SectionEnd
-16
View File
@@ -1,16 +0,0 @@
# This is supposed to be run on Windows! In Cygwin!
if test "x$1" = "x"; then
echo Need release name.
exit -1
fi
rm -Rf build dist
cp run_game.py console.py
cmd /c build_exe.bat
python distribute.py ../$1 demo2
cd ..
zip -9 -r $1.zip $1
+5 -15
View File
@@ -1,12 +1,6 @@
# This file ensures that renpy packages will be imported in the right
# order.
# Some version numbers and things.
version = "Ren'Py 4.8"
script_version = 8000
savegame_suffix = "-8.save"
# Can be first, because has no dependencies, and may be imported
# directly.
import renpy.game
@@ -19,25 +13,21 @@ import renpy.curry
import renpy.execution
import renpy.loader
import renpy.loadsave
import renpy.music
import renpy.parser
import renpy.python # object
import renpy.script
import renpy.style
import renpy.sound
import renpy.display
import renpy.display.render # Most display stuff depends on this.
import renpy.display
import renpy.display.core # object
import renpy.display.audio
import renpy.display.surface
import renpy.display.image # core
import renpy.display.text # core
import renpy.display.layout # core
import renpy.display.behavior # layout
import renpy.display.transition # core
import renpy.display.im
import renpy.display.image # core, behavior, im
import renpy.display.video
import renpy.display.focus
import renpy.ui
import renpy.exports
import renpy.config # depends on lots.
+26 -63
View File
@@ -1,4 +1,3 @@
# This file contains the AST for the Ren'Py script language. Each class
# here corresponds to a statement in the script language.
@@ -94,10 +93,7 @@ class Node(object):
probably execute next.
"""
if self.next:
return [ self.next ]
else:
return [ ]
return [ self.next ]
def say_menu_with(expression):
@@ -118,6 +114,7 @@ def say_menu_with(expression):
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(what)
class Say(Node):
def __init__(self, loc, who, what, with):
@@ -136,7 +133,7 @@ class Say(Node):
who = None
say_menu_with(self.with)
renpy.exports.say(who, self.what)
renpy.exports.say(who, self.what % renpy.game.store)
return self.next
@@ -209,17 +206,12 @@ 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_bytecode(self.bytecode, self.hide)
renpy.python.py_exec(self.python_code, self.hide)
return self.next
@@ -254,31 +246,18 @@ def imspec_common(imspec, hide=False):
This is code that's common to the three statements that can
take imspecs (scene, show, and hide).
It parses the imspec into a key, and an image, perhaps applying
at clauses.
It parses the imspec into a key, an image, and a with_image, and
returns all three to the user.
@param hide: Reduces error checking, and changes the sticky position
logic.
@param hide: Reduces error checking, and makes the with_image None
if the list of with expressions is empty.
"""
import renpy.display.image
sls = renpy.game.context().scene_lists
name, at_list = imspec
key = name[0]
# Handle sticky positions.
if renpy.config.sticky_positions:
if hide:
if key in sls.sticky_positions:
del sls.sticky_positions[key]
else:
if not at_list and key in sls.sticky_positions:
at_list = sls.sticky_positions[key]
sls.sticky_positions[key] = at_list
# Get a reference to the base image.
img = renpy.display.image.ImageReference(name)
@@ -286,10 +265,6 @@ def imspec_common(imspec, hide=False):
for i in at_list:
img = renpy.python.py_eval(i)(img)
# Update the set of images that have ever been seen.
if not hide:
renpy.game.persistent._seen_images[tuple(name)] = True
return key, img
def predict_imspec(imspec, callback):
@@ -302,7 +277,6 @@ def predict_imspec(imspec, callback):
return
im = renpy.exports.images[imspec[0]]
im.predict(callback)
@@ -354,7 +328,6 @@ class Scene(Node):
sls = renpy.game.context().scene_lists
sls.clear('master')
sls.sticky_positions.clear()
if self.imspec:
key, img = imspec_common(self.imspec)
@@ -408,32 +381,32 @@ class With(Node):
def execute(self):
trans = renpy.python.py_eval(self.expr)
renpy.exports.with(trans)
# Code copied into exports.with
if not trans:
renpy.game.interface.with_none()
else:
if renpy.game.preferences.transitions:
renpy.game.interface.set_transition(trans)
renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=True)
return self.next
class Call(Node):
def __init__(self, loc, label, expression):
def __init__(self, loc, label):
super(Call, self).__init__(loc)
self.label = label
self.expression = expression
def execute(self):
label = self.label
if self.expression:
label = renpy.python.py_eval(label)
return renpy.game.context().call(label, return_site=self.next.name)
return renpy.game.context().call(self.label, return_site=self.next.name)
def predict(self, callback):
if self.expression:
return [ ]
else:
return [ renpy.game.script.lookup(self.label) ]
return [ renpy.game.script.lookup(self.label) ]
class Return(Node):
@@ -513,30 +486,20 @@ class Menu(Node):
# instead.
class Jump(Node):
def __init__(self, loc, target, expression):
def __init__(self, loc, target):
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):
target = self.target
if self.expression:
target = renpy.python.py_eval(target)
return renpy.game.script.lookup(target)
return renpy.game.script.lookup(self.target)
def predict(self, callback):
if self.expression:
return [ ]
else:
return [ renpy.game.script.lookup(self.target) ]
return [ renpy.game.script.lookup(self.target) ]
# GNDN
class Pass(Node):
+10 -102
View File
@@ -2,12 +2,11 @@
# 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
@@ -19,9 +18,6 @@ background = None # (0, 0, 0, 255)
# about and fix them.
debug = False
# Ditto, but for sound operations
debug_sound = False
# Is rollback enabled? (This only controls if the user-invoked
# rollback command does anything)
rollback_enabled = True
@@ -47,9 +43,9 @@ profile = False
# The directory save files will be saved to.
savedir = None
# The number of screens worth of images that are allowed to live in the image
# cache at once.
image_cache_size = 8
# The number of images that are allowed to live in the image cache
# at once.
image_cache_size = 10
# The number of statements we will analyze when doing predictive
# loading. Please note that this is a total number of statements in a
@@ -61,21 +57,12 @@ 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 = 75
skip_delay = 100
# 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
@@ -83,6 +70,9 @@ force_archives = False
# An image file containing the mouse cursor, if one is defined.
mouse = None
# The distance the keyboard moves the mouse, per 50 ms tick, in pixels.
keymouse_distance = 5
# The default sound playback sample rate.
sound_sample_rate = 44100
@@ -92,89 +82,7 @@ annoying_text_cps = None
# The amount of time music is faded out between tracks.
fade_music = 0.0
# Should the at list be sticky?
sticky_positions = False
# A list of all of the layers that we know about.
layers = [ 'master', 'transient', 'overlay' ]
# A list of layers that should be cleared when we replace
# transients.
transient_layers = [ 'transient' ]
# A list of layers that should be cleared when we recompute
# overlays.
overlay_layers = [ 'overlay' ]
# True if we want to show overlays during wait statements, or
# false otherwise.
overlay_during_wait = True
# When using the keyboard to navigate, how much we penalize
# distance out of the preferred direction.
focus_crossrange_penalty = 1024
# If True, then we force all loading to occur before transitions
# start.
load_before_transition = True
# The keymap that is used to change keypresses and mouse events.
keymap = dict(
# Bindings present almost everywhere, unless explicitly
# disabled.
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
screenshot = [ 's' ],
toggle_fullscreen = [ 'f' ],
toggle_music = [ 'm' ],
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
hide_windows = [ 'mouseup_2', 'h' ],
# Say.
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
# Focus.
focus_left = [ 'K_LEFT' ],
focus_right = [ 'K_RIGHT' ],
focus_up = [ 'K_UP' ],
focus_down = [ 'K_DOWN' ],
# Button.
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
# Input.
input_backspace = [ 'K_BACKSPACE' ],
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
# These keys control skipping.
skip = [ 'K_LCTRL', 'K_RCTRL' ],
toggle_skip = [ 'K_TAB' ],
)
# A function that is called before each interaction, to update the
# music that is currently playing.
music_interact = None
# A function that is called when a music track ends, perhaps to
# play another track.
music_end_event = None
# The number of frames that Ren'Py has shown.
frames = 0
def backup():
import copy
global _globals
_globals = globals().copy()
del _globals["backup"]
del _globals["reload"]
del _globals["__builtins__"]
_globals = copy.deepcopy(_globals)
_globals = globals().copy()
def reload():
globals().update(_globals)
+2 -2
View File
@@ -4,8 +4,8 @@ class Curry(object):
callable with the stored arguments and the additional arguments
supplied to the call.
"""
# __doc__ = property(fget=lambda self : self.callable.__doc__)
__doc__ = property(fget=lambda self : self.callable.__doc__)
def __init__(self, callable, *args, **kwargs):
self.callable = callable
-620
View File
@@ -1,620 +0,0 @@
# This module contains code that handles the playing of sound and
# music files.
# NOTE TO SELF:
#
# Remember to code defensively against mikey's computer that
# doesn't have the sound card in it.
import pygame
import renpy
import sys # to detect windows.
# The Windows Volume Management Strategy (tm).
# We keep a master music volume, which is the volume we use directly
# when playing music as mp3, ogg, etc. When we start up, we compute
# a midi music scaling factor. This midi music scaling factor is
# computed from the current master music volume such that when we are not
# fading, if we pygame.mixer.music.set_volume() to the mmv * mmsv, we get
# read the same value from midiOutGetVolume() as we did before we tried
# doing that.
# True if the mixer works, False if it doesn't, None if we have no
# idea yet.
mixer_works = None
# Common stuff.
mixer_enabled = True
playing_midi = True
fading = False
master_music_volume = 1.0
# Windows stuff.
midi_msf = 0.0
last_raw_volume = -1
def init():
global mixer_works
global read_raw_volume
global compute_midi_msf
global set_music_volume
global playing_midi
if mixer_works is not None:
return
try:
pygame.mixer.music.get_volume()
mixer_works = True
except:
if renpy.config.debug_sound:
raise
else:
mixer_works = False
windows_magic = False
if hasattr(sys, 'winver') and mixer_works:
try:
from ctypes import windll, c_uint, byref
winmm = windll.winmm
def _read_raw_volume():
res = c_uint()
for i in range(0, winmm.midiOutGetNumDevs()):
rv = winmm.midiOutGetVolume(i, byref(res))
if not rv:
return res.value
else:
print "Couldn't read raw midi volume."
return -1
read_raw_volume = _read_raw_volume
def _compute_midi_msf():
"""
Computes the Midi MSF. Returns True if successful, False if otherwise.
"""
# Don't update the MSF when fading is going on, or when not
# playing a midi. (Except before playing any music whatsoever.)
if fading or not playing_midi:
return False
global last_raw_volume
raw_vol = read_raw_volume()
if raw_vol < 0:
return False
# The case in which the volume hasn't changed recently.
if raw_vol == last_raw_volume:
return True
last_raw_volume = raw_vol
# print "raw_vol", raw_vol
# The fraction that the midi mixer is at.
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
global midi_msf
midi_msf = mixfrac / master_music_volume
# print "Midi msf is now:", midi_msf
return True
compute_midi_msf = _compute_midi_msf
# This should get called after the music starts playing.
def _set_music_volume(vol):
global master_music_volume
master_music_volume = vol
if playing_midi:
vol *= midi_msf
if vol > 1.0:
vol = 1.0
try:
pygame.mixer.music.set_volume(vol)
except:
if renpy.config.debug_sound:
raise
global last_raw_volume
last_raw_volume = read_raw_volume()
set_music_volume = _set_music_volume
# Figure out the default msf, and set it up.
windows_magic = compute_midi_msf()
playing_midi = False
except Exception, e:
print "Exception when trying to init music:", str(e)
print "Falling back to Unix mode."
if not windows_magic:
def _compute_midi_msf():
return
compute_midi_msf = _compute_midi_msf
def _set_music_volume(vol):
if not mixer_works:
return
global master_music_volume
master_music_volume = vol
try:
pygame.mixer.music.set_volume(vol)
except:
if renpy.config.debug_sound:
raise
set_music_volume = _set_music_volume
playing_midi = False
if mixer_works:
pygame.mixer.music.set_endevent(renpy.display.core.MUSICEND)
def pre_init():
try:
bufsize = 4096
import os
if 'RENPY_SOUND_BUFSIZE' in os.environ:
bufsize = int(os.environ('RENPY_SOUND_BUFSIZE'))
pygame.mixer.pre_init(renpy.config.sound_sample_rate, -16, 2, bufsize)
except:
try:
pygame.mixer.pre_init()
except:
if renpy.config.debug_sound:
raise
def disable_mixer():
"""
This function is called by the video code to disable the
pygame mixer.
"""
if not mixer_works:
return
global mixer_enabled
if mixer_enabled:
try:
music_stop()
pygame.mixer.quit()
except:
if renpy.config.debug_sound:
raise
mixer_enabled = False
def enable_mixer():
"""
This function is called by the video code to enable the
pygame mixer.
"""
if not mixer_works:
return
global mixer_enabled
if not mixer_enabled:
try:
pygame.mixer.init()
except:
if renpy.config.debug_sound:
raise
mixer_enabled = True
# This detects if the filename is a midi, and sets playing_midi
# appropriately.
def detect_midi(fn):
fn = fn.lower()
global playing_midi
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
def music_delay(offset):
"""
Returns the time left until the current music has been playing for
offset seconds. If music is not playing, return None. May return
a negative time.
"""
if not mixer_works:
return None
mo = pygame.mixer.music.get_pos()
if mo < 0:
return None
mo /= 1000.0
return offset - mo
# def music_start(filename, loops=-1, startpos=0.0):
# """
# This starts music playing. If a music track is already playing,
# stops that track in favor of this one.
# @param filename: The file that the music will be played from. This
# is relative to the game directory, and must be a real file (so it
# cannot be stored in an archive.)
# @param loops: The number of times the music will loop after it
# finishes playing. If negative, the music will loop indefinitely.
# Please note that even once the song has finished, rollback or load
# may cause it to start playing again. So it may not be safe to have
# this set to a non-negative value.
# @param startpos: The number of seconds into the music to start playing.
# """
# if not mixer_works:
# return
# music_stop()
# renpy.game.context().scene_lists.music = (filename, loops, startpos)
# restore_music()
# def music_stop():
# """
# Stops the currently playing music track.
# """
# if not mixer_works:
# return
# renpy.game.context().scene_lists.music = None
# restore_music()
# The filename of the currently playing piece of music.
playing_filename = None
# The filename of the currently queued piece of music.
queued_filename = None
# True if the music is in the process of fading out, or
# False otherwise.
fading = False
def music_update_volume():
"""
Sets the volume as appropriate for a midi.
"""
if not playing_filename:
return
detect_midi(playing_filename)
set_music_volume(master_music_volume)
def music_interact():
"""
This is called before each interaction, to update the playing music
(if necessary).
"""
# Call the appropriate config function.
if renpy.config.music_interact:
renpy.config.music_interact()
def music_end_event():
"""
This is called by renpy.display.core when a track of music has
endend.
"""
if not music_enabled():
return
try:
# shift the filenames.
global playing_filename
global queued_filename
global fading
playing_filename = queued_filename
queued_filename = None
fading = False
music_update_volume()
# Call the appropriate function.
if renpy.config.music_end_event:
renpy.config.music_end_event()
except:
if renpy.config.debug_sound:
raise
def music_enabled():
"""
This should be called to check to see if music is enabled. If this
returns False, then no music call should be made. Please note that
this does not check preferences.music_enabled, so user code should
also check that to see if music should be played.
This will return True if the mixer works and it has not been
pre-empted by the video player, and False otherwise.
If this does not return True, none of the other music functions should
be called.
"""
return mixer_works and mixer_enabled
def music_play(filename):
"""
This causes the named music filename to be loaded in and played.
Music loaded in this way immediately replaces the currently
playing music.
The track is played once, and then stops. It's up to the higher-level
music layer to ensure that a track that needs to be looped actually
is.
The filename must refer to a real file, and not a file hidden in an
archive.
"""
if not music_enabled():
return
try:
global playing_filename
global queued_filename
global fading
playing_filename = filename
queued_filename = None
fading = False
pygame.mixer.music.load(renpy.loader.transfn(filename))
if not pygame.mixer.music.get_busy():
pygame.mixer.music.play()
music_update_volume()
except:
if renpy.config.debug_sound:
raise
def music_queue(filename):
"""
This causes the given filename to be placed into the queue, to be
played immediately after the currently playing track finishes.
The filename must refer to a real file, and not a file hidden in an
archive.
"""
if not music_enabled():
return
try:
global queued_filename
queued_filename = filename
pygame.mixer.music.queue(renpy.loader.transfn(filename))
except:
if renpy.config.debug_sound:
raise
def music_stop():
"""
This causes the music to be stopped immediately.
"""
if not music_enabled():
return
try:
global playing_filename
global queued_filename
global fading
playing_filename = None
queued_filename = None
fading = False
pygame.mixer.music.stop()
except:
if renpy.config.debug_sound:
raise
def music_fadeout(seconds):
"""
This causes the music to be faded out over a period of
time.
"""
if not music_enabled():
return
try:
global queued_filename
global fading
queued_filename = None
fading = True
pygame.mixer.music.fadeout(int(1000 * seconds))
except:
if renpy.config.debug_sound:
raise
def music_filenames():
"""
Returns a tuple giving the currently playing music filename and
the filename of the track in the queue. It returns None if there
is no filename in either slot.
"""
if not music_enabled():
return None, None
try:
return playing_filename, queued_filename
except:
if renpy.config.debug_sound:
raise
else:
return None, None
def music_fading():
"""
Returns True if the music is in the process of fading out, or False
otherwise.
"""
return fading
def music_pause():
"""
Causes the currently playing music to be paused.
"""
if not music_enabled():
return
try:
pygame.mixer.music.pause()
except:
if renpy.config.debug_sound:
raise
def music_unpause():
"""
Causes the currently playing music to be unpaused, if it is
currently paused.
"""
if not music_enabled():
return
try:
pygame.mixer.music.unpause()
except:
if renpy.config.debug_sound:
raise
def sound_enabled():
"""
Returns True if it's possible to play sound, or False if sound
should not be played.
Please note that this does not check preferences.sound. It's up
to higher-level code (like renpy.play) to do that.
"""
return mixer_works and mixer_enabled
def sound_play(filename, loops=0, channel=0):
"""
This causes the sound contained in the given filename to be played.
@param loops: The number of extra times that the sound will be
played. If -1, the sound is played forever (until stopped).
@param channel: The channel that the sound will be played on, an
integer from 0 to 7. This allows us to support playing up to 8
sounds at once.
"""
if not sound_enabled():
return
try:
chan = pygame.mixer.Channel(channel)
chan.play(pygame.mixer.Sound(renpy.loader.load(filename)), loops)
except:
if renpy.config.debug_sound:
raise
def sound_stop(channel=0):
"""
This causes the sound currently playing in the specified channel
to be stopped.
"""
if not music_enabled():
return
try:
chan = pygame.mixer.Channel(channel)
chan.stop()
except:
if renpy.config.debug_sound:
raise
def play(fn, loops=0):
"""
This plays the given sound. The sound must be in a wav file,
and expected to have a sample rate 44100hz (changeable with
config.sound_sample_rate), 16 bit, stereo. These expectations may
be violated, but that may lead to conversion delays.
Once a sound has been started, there's no way to stop it.
@param fn: The name of the file that the sound is read from. This
file may be contained in a game directory or an archive.
@param loops: The number of extra times the sound will be
played. (The default, 0, will play the sound once.)
This plays the sound on channel 0.
"""
if not fn:
return
if not renpy.game.preferences.sound:
return
sound_play(fn, loops=loops)
+260 -278
View File
@@ -2,7 +2,6 @@
import renpy
from renpy.display.render import render
# import renpy.display.core as core
# import renpy.display.layout as layout
@@ -13,80 +12,7 @@ from renpy.display.render import render
import pygame
from pygame.constants import *
def map_event(ev, name):
"""
This looks up the name in the keymap, and uses it to determine if
the given event was caused by one of the keys or mouse buttons
mapped to the given name in config.keymap. If so, it returns
True, otherwise it returns False.
"""
keys = renpy.config.keymap[name]
if ev.type == MOUSEBUTTONDOWN:
if ( "mousedown_" + str(ev.button) ) in keys:
return True
else:
return False
if ev.type == MOUSEBUTTONUP:
if ( "mouseup_" + str(ev.button) ) in keys:
return True
else:
return False
if ev.type == KEYDOWN:
for key in keys:
if key == ev.unicode or ev.key == getattr(pygame.constants, key, None):
return True
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):
"""
This looks the given name up in the keymap. For each binding of the
form K_whatever, it checks to see if the given key is pressed, and if
so, returns the keycode of the pressed key. Otherwise, returns False.
"""
keys = renpy.config.keymap[name]
for key in keys:
code = getattr(pygame.constants, key)
if pressed[code]:
return code
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.Null):
class Keymap(renpy.display.layout.Container):
"""
This is a behavior that maps keys to functions that are called when
the key is pressed. The keys are specified by giving the appropriate
@@ -94,42 +20,57 @@ class Keymap(renpy.display.layout.Null):
"""
def __init__(self, **keymap):
super(Keymap, self).__init__(style='default')
self.keymap = keymap
def event(self, ev, x, y):
for name, action in self.keymap.iteritems():
if map_event(ev, name):
rv = action()
if rv is not None:
return rv
# Mouse events.
if ev.type == MOUSEBUTTONDOWN:
key = 'mouse_' + str(ev.button)
if key in self.keymap:
self.keymap[key]()
raise renpy.display.core.IgnoreEvent()
# def render(self, width, height, st):
# return None
class PauseBehavior(renpy.display.layout.Null):
# Keyboard events.
if ev.type != KEYDOWN:
return
for key, action in self.keymap.iteritems():
if key == ev.unicode or ev.key == getattr(pygame.constants, key, None):
action()
raise renpy.display.core.IgnoreEvent()
def render(self, width, height, st):
return None
class KeymouseBehavior(renpy.display.layout.Null):
"""
This is a class implementing the Pause behavior, which is to
return a value after a certain amount of time has elapsed.
This is a class that causes the keyboard to move the mouse. It's
useful on the game and key menus, as well as in imagemaps and the
like.
"""
def __init__(self, delay, result=False):
super(PauseBehavior, self).__init__()
self.delay = delay
self.result = result
def event(self, ev, x, y):
if ev.type == renpy.display.core.DISPLAYTIME and \
self.delay and ev.duration > self.delay:
return self.result
if ev.type == renpy.display.core.DISPLAYTIME:
pressed = pygame.key.get_pressed()
x, y = pygame.mouse.get_pos()
ox, oy = x, y
if pressed[K_LEFT]:
x -= renpy.config.keymouse_distance
if pressed[K_RIGHT]:
x += renpy.config.keymouse_distance
if pressed[K_UP]:
y -= renpy.config.keymouse_distance
if pressed[K_DOWN]:
y += renpy.config.keymouse_distance
if (x, y) != (ox, oy):
pygame.mouse.set_pos((x, y))
return None
class SayBehavior(renpy.display.layout.Null):
"""
@@ -139,102 +80,241 @@ class SayBehavior(renpy.display.layout.Null):
mouse button.
"""
focusable = True
def __init__(self, delay=None):
super(SayBehavior, self).__init__()
def __init__(self, default=True, **properties):
super(SayBehavior, self).__init__(default=default, **properties)
self.delay = delay
def event(self, ev, x, y):
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:
self.delay and \
ev.duration > self.delay:
return False
if renpy.game.preferences.skip_unseen:
return True
elif renpy.game.context().seen_current(True):
if ev.type == MOUSEBUTTONDOWN:
if ev.button == 1:
return True
if map_event(ev, "dismiss") and self.is_focused():
return True
if ev.button == 5:
if renpy.game.context().seen_current(False):
return True
if map_event(ev, "rollforward"):
if renpy.game.context().seen_current(False):
if ev.type == KEYDOWN:
if ev.key == K_PAGEDOWN:
if renpy.game.context().seen_current(False):
return True
if ev.key == K_RETURN:
return True
if ev.key == K_SPACE:
return True
if ev.key == K_LCTRL or ev.key == K_RCTRL:
if renpy.game.preferences.skip_unseen:
return True
elif renpy.game.context().seen_current(True):
return True
return None
class Button(renpy.display.layout.Window):
def __init__(self, child, style='button', clicked=None,
hovered=None, **properties):
class Menu(renpy.display.layout.VBox):
super(Button, self).__init__(child, style=style, **properties)
def __init__(self, menuitems):
"""
@param menuitems: A list of menuitem tuples. The first element
of each tuple is the string that should be displayed to the
user. The second item is the value that should be returned if
this item is selected, or None to indicate that this item is a
caption.
"""
self.activated = False
self.clicked = clicked
self.hovered = hovered
self.focusable = clicked is not None
super(Menu, self).__init__(full=False)
def render(self, width, height, st):
self.selected = None
self.results = [ ]
rv = super(Button, self).render(width, height, st)
self.caption_style = renpy.style.Style('menu_caption', { })
self.selected_style = renpy.style.Style('menu_choice', { })
self.unselected_style = renpy.style.Style('menu_choice', { })
if self.clicked:
rv.add_focus(self,
None,
self.style.left_margin,
self.style.top_margin,
rv.width - self.style.right_margin,
rv.height - self.style.bottom_margin)
return rv
self.selected_style.set_prefix('hover_')
self.unselected_style.set_prefix('idle_')
for i, (caption, result) in enumerate(menuitems):
self.add(renpy.display.text.Text(caption))
if self.selected is None and result is not None:
self.selected = i
self.results.append(result)
self.update_styles()
def update_styles(self):
"""
This updates the colors of our children to reflect the
one that has been selected by the user.
"""
for i, (child, result) in enumerate(zip(self.children, self.results)):
# Captions should stay the default text color.
if result is None:
child.set_style(self.caption_style)
continue
# Actual choices change color if they are selected or not.
if i == self.selected:
child.set_style(self.selected_style)
else:
child.set_style(self.unselected_style)
def event(self, ev, x, y):
"""
Processes events.
"""
# We deactivate on an event.
if self.activated:
self.activated = False
# print ev
# print x, y
if self.focusable:
if self.is_focused():
self.set_style_prefix('hover_')
else:
self.set_style_prefix('idle_')
else:
self.set_style_prefix('insensitive_')
old_selected = self.selected
# If not focused, ignore all events.
if not self.is_focused():
return None
# Change selection based on mouse position.
if ev.type == MOUSEMOTION:
target = self.child_at_point(x, y)
if target is None:
return None
# If clicked,
if map_event(ev, "button_select") and self.clicked:
if self.results[target] is not None:
self.selected = target
self.activated = True
# Make selection based on mouse click position.
if ev.type == MOUSEBUTTONDOWN and ev.button == 1:
target = self.child_at_point(x, y)
if target is None:
return None
self.set_style_prefix('activate_')
renpy.display.audio.play(self.style.sound)
if self.results[target] is not None:
renpy.sound.play(self.selected_style.activate_sound)
return self.results[target]
rv = self.clicked()
# Change selection based on keypress.
if ev.type == KEYDOWN and ev.key == K_DOWN:
selected = self.selected
while selected < len(self.results) - 1:
selected += 1
if self.results[selected] is not None:
self.selected = selected
break
# Change selection based on keypress.
if ev.type == KEYDOWN and ev.key == K_UP:
selected = self.selected
while selected > 0:
selected -= 1
if self.results[selected] is not None:
self.selected = selected
break
# Make selection based on keypress.
if ev.type == KEYDOWN and ev.key == K_RETURN:
renpy.sound.play(self.selected_style.activate_sound)
return self.results[self.selected]
# If the selected item changed, update the display.
if self.selected != old_selected:
self.children[self.selected].set_style(self.selected_style)
self.children[old_selected].set_style(self.unselected_style)
renpy.sound.play(self.selected_style.hover_sound)
renpy.game.interface.redraw(0)
return None
class Button(renpy.display.layout.Window):
def __init__(self, child, style='button', clicked=None, **properties):
super(Button, self).__init__(child, style=style, **properties)
self.style.set_prefix('idle_')
self.old_hover = False
self.clicked = clicked
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_')
renpy.game.interface.redraw(0)
def event(self, ev, x, y):
inside = False
width, height = self.window_size
if x >= 0 and x < width and y >= 0 and y < height:
inside = True
if ev.type == MOUSEMOTION:
if self.old_hover != inside:
self.old_hover = inside
self.set_hover(inside)
if inside:
renpy.sound.play(self.style.hover_sound)
if (ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
(ev.type == KEYDOWN and ev.key == K_RETURN):
if inside:
renpy.sound.play(self.style.activate_sound)
return self.clicked()
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
return None
# Reimplementation of the TextButton widget as a Button and a Text
# widget.
def TextButton(text, style='button', text_style='button_text',
clicked=None, **properties):
class TextButton(Button):
text = renpy.display.text.Text(text, style=text_style)
return Button(text, style=style, clicked=clicked, **properties)
def __init__(self, text, style='button', text_style='button_text',
clicked=None):
self.text_widget = renpy.display.text.Text(text, style=text_style)
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)
if hover:
self.text_widget.style.set_prefix("hover_")
else:
self.text_widget.style.set_prefix("idle_")
class Input(renpy.display.text.Text):
"""
@@ -242,128 +322,30 @@ class Input(renpy.display.text.Text):
"""
def __init__(self, default, length=None,
style='input_text',
allow=None,
exclude=None,
**properties):
super(Input, self).__init__(default.replace("{", "{{") + "_", style=style, **properties)
style='input_text', **properties):
super(Input, self).__init__(default + "_", style=style, **properties)
self.content = unicode(default)
self.length = length
self.allow = allow
self.exclude = exclude
def event(self, ev, x, y):
if map_event(ev, "input_backspace"):
if self.content:
self.content = self.content[:-1]
if ev.type == KEYDOWN:
if ev.key == K_BACKSPACE:
if self.content:
self.content = self.content[:-1]
self.set_text(self.content.replace("{", "{{") + "_")
renpy.display.render.redraw(self, 0)
elif ev.key == K_RETURN:
return self.content
elif map_event(ev, "input_enter"):
return self.content
elif ev.type == KEYDOWN and ev.unicode:
if ord(ev.unicode[0]) < 32:
return None
elif ev.unicode:
if ord(ev.unicode[0]) < 32:
return None
if self.length and len(self.content) >= self.length:
raise renpy.display.core.IgnoreEvent()
if self.length and len(self.content) >= self.length:
return None
if self.allow and ev.unicode not in self.allow:
raise renpy.display.core.IgnoreEvent()
self.content += ev.unicode
if self.exclude and ev.unicode in self.exclude:
raise renpy.display.core.IgnoreEvent()
self.content += ev.unicode
self.set_text(self.content.replace("{", "{{") + "_")
renpy.display.render.redraw(self, 0)
raise renpy.display.core.IgnoreEvent()
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,
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
def render(self, width, height, st):
width = self.width
height = self.height
lgutter = 0
rgutter = 0
barwidth = width - lgutter - rgutter
left_width = barwidth * self.value // self.range
right_width = barwidth - left_width
rv = renpy.display.render.Render(width, height)
lsurf = render(self.style.left_bar, left_width, height, st)
rsurf = render(self.style.right_bar, right_width, height, st)
rv.blit(lsurf, (lgutter, 0))
rv.blit(rsurf, (lgutter + left_width, 0))
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, vars(renpy.store))
def render(self, width, height, st):
if self.state:
return render(self.child, width, height, st)
else:
return render(self.null, width, height, st)
def event(self, ev, x, y):
state = eval(self.condition, vars(renpy.store))
if state != self.state:
renpy.display.render.redraw(self, 0)
self.state = state
if state:
return self.child.event(ev, x, y)
+271 -473
View File
File diff suppressed because it is too large Load Diff
-355
View File
@@ -1,355 +0,0 @@
# This file contains code to manage focus on the display.
import renpy
import pygame
from pygame.constants import *
class Focus(object):
def __init__(self, widget, arg, x, y, w, h):
self.widget = widget
self.arg = arg
self.x = x
self.y = y
self.w = w
self.h = h
def __iter__(self):
return iter((self.widget, self.arg, self.x, self.y, self.w, self.h))
# Sets the currently focused widget.
def set_focused(widget):
renpy.game.context().scene_lists.focused = widget
# Gets the currently focused widget.
def get_focused():
return renpy.game.context().scene_lists.focused
# The current list of focuses that we know about.
focus_list = [ ]
# This takes in a focus list from the rendering system.
def take_focuses(fl):
global focus_list
focus_list = fl
# This is called before each interaction. It's purpose is to choose
# the widget that is focused, and to mark it as focused and all of
# the other widgets as unfocused.
def before_interact(root):
# a list of focusable, name tuples.
fwn = [ ]
def callback(f, n):
fwn.append((f, n))
root.find_focusable(callback, None)
# Assign a full name to each focusable.
namecount = { }
for f, n in fwn:
serial = namecount.get(n, 0)
namecount[n] = serial + 1
f.full_focus_name = n, serial
# If there's something with the same full name as the current widget,
# it becomes the new current widget.
current = get_focused()
if current is not None:
current_name = current.full_focus_name
for f, n in fwn:
if f.full_focus_name == current.full_focus_name:
current = f
set_focused(f)
break
else:
current = None
# Otherwise, focus the default widget, or nothing.
if current is None:
for f, n in fwn:
if f.default:
current = f
set_focused(f)
break
else:
set_focused(None)
# Finally, mark the current widget as the focused widget, and
# all other widgets as unfocused.
for f, n in fwn:
if f is current:
f.focus(default=True)
else:
f.unfocus()
# This changes the focus to be the widget contained inside the new
# focus object.
def change_focus(newfocus):
if newfocus is None:
widget = None
else:
widget = newfocus.widget
current = get_focused()
# Nothing to do.
if current is widget:
return
if current is not None:
current.unfocus()
current = widget
if widget is not None:
widget.focus()
set_focused(current)
# This handles mouse events, to see if they change the focus.
def mouse_handler(ev):
x, y = ev.pos
newfocus = None
default = None
for f in focus_list:
if f.x is None:
default = f
continue
if f.x <= x <= f.x + f.w and f.y <= y <= f.y + f.h:
newfocus = f
break
else:
newfocus = default
change_focus(newfocus)
# This focuses an extreme widget, which is one of the widgets that's
# at an edge. To do this, we multiply the x, y, width, and height by
# the supplied multiplers, add them all up, and take the focus with
# the largest value.
def focus_extreme(xmul, ymul, wmul, hmul):
max_focus = None
max_score = -(65536**2)
for f in focus_list:
if not f.x:
continue
score = (f.x * xmul +
f.y * ymul +
f.w * wmul +
f.h * hmul)
if score > max_score:
max_score = score
max_focus = f
if max_focus:
change_focus(max_focus)
# This calculates the distance between two points, applying
# the given fudge factors. The distance is left squared.
def points_dist(x0, y0, x1, y1, xfudge, yfudge):
return (( x0 - x1 ) * xfudge ) ** 2 + \
(( y0 - y1 ) * yfudge ) ** 2
# This computes the distance between two horizontal lines. (So the
# distance is either vertical, or has a vertical component to it.)
#
# The distance is left squared.
def horiz_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
# The lines overlap in x.
if bx0 <= ax0 <= ax1 <= bx1 or \
ax0 <= bx0 <= bx1 <= ax1 or \
ax0 <= bx0 <= ax1 <= bx1 or \
bx0 <= ax0 <= bx1 <= ax1:
return (ay0 - by0) ** 2
# The right end of a is to the left of the left end of b.
if ax0 <= ax1 <= bx0 <= bx1:
return points_dist(ax1, ay1, bx0, by0, renpy.config.focus_crossrange_penalty, 1.0)
if bx0 <= bx1 <= ax0 <= ax1:
return points_dist(ax0, ay0, bx1, by1, renpy.config.focus_crossrange_penalty, 1.0)
assert False
# This computes the distance between two vertical lines. (So the
# distance is either hortizontal, or has a horizontal component to it.)
#
# The distance is left squared.
def verti_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
# The lines overlap in x.
if by0 <= ay0 <= ay1 <= by1 or \
ay0 <= by0 <= by1 <= ay1 or \
ay0 <= by0 <= ay1 <= by1 or \
by0 <= ay0 <= by1 <= ay1:
return (ax0 - bx0) ** 2
# The right end of a is to the left of the left end of b.
if ay0 <= ay1 <= by0 <= by1:
return points_dist(ax1, ay1, bx0, by0, 1.0, renpy.config.focus_crossrange_penalty)
if by0 <= by1 <= ay0 <= ay1:
return points_dist(ax0, ay0, bx1, by1, 1.0, renpy.config.focus_crossrange_penalty)
assert False
# This focuses the widget that is nearest to the current widget. To
# determine nearest, we compute points on the widgets using the
# {from,to}_{x,y}off values. We pick the nearest, applying a fudge
# multiplier to the distances in each direction, that satisfies
# the condition (which is given a Focus object to evaluate).
#
# If no focus can be found matching the above, we look for one
# with an x of None, and make that the focus. Otherwise, we do
# nothing.
#
# If no widget is focused, we pick one and focus it.
#
# If the current widget has an x of None, we pass things off to
# focus_extreme to deal with.
def focus_nearest(from_x0, from_y0, from_x1, from_y1,
to_x0, to_y0, to_x1, to_y1,
line_dist,
condition,
xmul, ymul, wmul, hmul):
if not focus_list:
return
# No widget focused.
current = get_focused()
if not current:
change_focus(focus_list[0])
return
# Find the current focus.
for f in focus_list:
if f.widget == current:
from_focus = f
break
else:
# If we can't pick something.
change_focus(focus_list[0])
return
# If placeless, focus_extreme.
if from_focus.x is None:
focus_extreme(xmul, ymul, wmul, hmul)
return
fx0 = from_focus.x + from_focus.w * from_x0
fy0 = from_focus.y + from_focus.h * from_y0
fx1 = from_focus.x + from_focus.w * from_x1
fy1 = from_focus.y + from_focus.h * from_y1
placeless = None
new_focus = None
# a really big number.
new_focus_dist = (65536.0 * renpy.config.focus_crossrange_penalty) ** 2
for f in focus_list:
if f is from_focus:
continue
if f.x is None:
placeless = f
continue
if not condition(from_focus, f):
continue
tx0 = f.x + f.w * to_x0
ty0 = f.y + f.h * to_y0
tx1 = f.x + f.w * to_x1
ty1 = f.y + f.h * to_y1
dist = line_dist(fx0, fy0, fx1, fy1,
tx0, ty0, tx1, ty1)
if dist < new_focus_dist:
new_focus = f
new_focus_dist = dist
# If we couldn't find anything, try the placeless focus.
new_focus = new_focus or placeless
# If we have something, switch to it.
if new_focus:
change_focus(new_focus)
# And, we're done.
def key_handler(ev):
if renpy.display.behavior.map_event(ev, 'focus_right'):
focus_nearest(0.9, 0.1, 0.9, 0.9,
0.1, 0.1, 0.1, 0.9,
verti_line_dist,
lambda old, new : old.x + old.w <= new.x,
-1, 0, 0, 0)
if renpy.display.behavior.map_event(ev, 'focus_left'):
focus_nearest(0.1, 0.1, 0.1, 0.9,
0.9, 0.1, 0.9, 0.9,
verti_line_dist,
lambda old, new : new.x + new.w <= old.x,
1, 0, 1, 0)
if renpy.display.behavior.map_event(ev, 'focus_up'):
focus_nearest(0.1, 0.1, 0.9, 0.1,
0.1, 0.9, 0.9, 0.9,
horiz_line_dist,
lambda old, new : new.y + new.h <= old.y,
0, 1, 0, 1)
if renpy.display.behavior.map_event(ev, 'focus_down'):
focus_nearest(0.1, 0.9, 0.9, 0.9,
0.1, 0.1, 0.9, 0.1,
horiz_line_dist,
lambda old, new : old.y + old.h <= new.y,
0, -1, 0, 0)
# This handles pygame events that may change focus.
def event_handler(ev):
if ev.type in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN):
mouse_handler(ev)
key_handler(ev)
-576
View File
@@ -1,576 +0,0 @@
# This file contains the new image code, which includes provisions for
# size-based caching and constructing images from operations (like
# cropping and scaling).
import renpy
import pygame
from pygame.constants import *
# This is an entry in the image cache.
class CacheEntry(object):
def __init__(self, what, surf):
# The object that is being cached (which needs to be
# hashable and comparable).
self.what = what
# The pygame surface corresponding to the cached object.
self.surf = surf
# The size of this image.
w, h = surf.get_size()
self.size = w * h
# The time when this cache entry was last used.
self.time = 0
# This is the singleton image cache.
class Cache(object):
def __init__(self):
# The current arbitrary time. (Increments by one for each
# interaction.)
self.time = 0
# A map from Image object to CacheEntry.
self.cache = { }
# A list of Image objects that we want to preload.
self.preloads = [ ]
# False if this is not the first preload in this tick.
self.first_preload_in_tick = True
# The total size of the current generation of images.
self.size_of_current_generation = 0
# The total size of everything in the cache.
self.total_cache_size = 0
# Returns the maximum size of the cache, after which we start
# tossing things out.
def cache_limit(self):
return renpy.config.image_cache_size * renpy.config.screen_width * renpy.config.screen_height
# Increments time, and clears the list of images to be
# preloaded.
def tick(self):
self.time += 1
self.preloads = [ ]
self.first_preload_in_tick = True
self.size_of_current_generation = 0
# Called to report that a given image would like to be preloaded.
def preload_image(self, image):
self.preloads.append(image)
# Do we need to preload an image?
def needs_preload(self):
return (self.preloads and True) or self.first_preload_in_tick
# This returns the pygame surface corresponding to the provided
# image. It also takes care of updating the age of images in the
# cache to be current, and maintaining the size of the current
# generation of images.
def get(self, image):
if not isinstance(image, ImageBase):
raise Exception("Expected an image of some sort, but got something else.")
if image in self.cache:
ce = self.cache[image]
if ce.time == self.time:
return ce.surf
else:
ce = CacheEntry(image, image.load())
self.total_cache_size += ce.size
self.cache[image] = ce
# Indicate that this surface had changed.
renpy.display.render.mutated_surface(ce.surf)
if renpy.config.debug_image_cache:
print "IC Added", ce.what
# Move it into the current generation.
ce.time = self.time
self.size_of_current_generation += ce.size
return ce.surf
# This kills off a given cache entry.
def kill(self, ce):
# Should never happen... but...
if ce.time == self.time:
self.size_of_current_generation -= ce.size
self.total_cache_size -= ce.size
del self.cache[ce.what]
if renpy.config.debug_image_cache:
print "IC Removed", ce.what
# Calling this cleans out the image cache if it has gotten too large.
def cleanout(self):
cache_limit = self.cache_limit()
# If we're within the limit, return.
if self.total_cache_size <= cache_limit:
return
# If we're outside the cache limit, we need to go and start
# killing off some of the entries until we're back inside it.
# A list of time, cache_entry pairs.
ace = [ (ce.time, ce) for ce in self.cache.itervalues() ]
ace.sort()
while ace and self.total_cache_size > cache_limit:
time, ce = ace.pop(0)
if time == self.time:
# If we're bigger than the limit, and there's nothing
# to remove, we should stop the preloading right away.
self.preloads = [ ]
break
# Otherwise, kill off the given cache entry.
self.kill(ce)
if renpy.config.debug_image_cache:
print "IC is:", self.cache.keys()
print "IC size:", self.total_cache_size, "/", cache_limit
# This actually performs preloading.
def preload(self):
if self.first_preload_in_tick:
self.first_preload_in_tick = False
# Triage into stuff that's already in the cache and should
# be kept there, and stuff that isn't there already.
new_preloads = [ ]
for i in self.preloads:
if i in self.cache:
self.get(i)
else:
new_preloads.append(i)
self.preloads = new_preloads
# Clean out the cache.
self.cleanout()
# Return after doing said triage.
return
# Otherwise, new_preloads contains things that aren't in the
# cache already. So load one of them into the cache, maybe.
cache_limit = self.cache_limit()
# If the size of the current generation is bigger than the
# total cache size, stop preloading.
if self.size_of_current_generation > cache_limit:
self.preloads = [ ]
return
# Otherwise, preload the next image.
image = self.preloads.pop(0)
self.get(image)
# And, we're done.
self.cleanout()
cache = Cache()
class ImageBase(renpy.display.core.Displayable):
"""
This is the base class for all of the various kinds of images that
we can possibly have.
"""
def __init__(self, *args, **properties):
if 'style' not in properties:
properties = properties.copy()
properties['style'] = 'image_placement'
super(ImageBase, self).__init__(**properties)
self.identity = (type(self).__name__, ) + args
def __hash__(self):
return hash(self.identity)
def __eq__(self, other):
if not isinstance(other, ImageBase):
return False
return self.identity == other.identity
def __repr__(self):
return "<" + " ".join([repr(i) for i in self.identity]) + ">"
def load(self):
"""
This function is called by the image cache code to cause this
image to be loaded. It's expected that children of this class
would override this.
"""
assert False
def render(self, w, h, st):
im = cache.get(self)
w, h = im.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(im, (0, 0))
return rv
def get_placement(self):
return self.style
def predict(self, callback):
callback(self)
class Image(ImageBase):
"""
This image manipulator loads an image from a file.
"""
def __init__(self, filename, **properties):
"""
@param filename: The filename that the image will be loaded from.
"""
super(Image, self).__init__(filename, **properties)
self.filename = filename
def load(self):
im = pygame.image.load(renpy.loader.load(self.filename), self.filename)
if im.get_flags() & SRCALPHA:
im = im.convert_alpha()
else:
im = im.convert()
return im
class Composite(ImageBase):
"""
This image manipulator composites one or more images together.
"""
def __init__(self, size, *args, **properties):
"""
The first argument that this takes is size, which is either the
desired size of the image (in pixels), or None to indicate that
the size should be
This takes an even number of position arguments. Odd numbered
(starting the count with 1) arguments are positions, which
give the position of the image, in pixels, with the origin in
the upper-left corner of the image. The even-numbered
arguments give the images (image manipulators) that will be
composited in those positions. The images are composited in
bottom-to-top order.
@param size: If given, this will be the size of the new
image. Otherwise, the size will be the same as that of the
first image.
"""
super(Composite, self).__init__(size, *args, **properties)
if len(args) % 2 != 0:
raise Exception("Composite requires an even number of arguments.")
self.size = size
self.positions = args[0::2]
self.images = [ image(i) for i in args[1::2] ]
def load(self):
if self.size:
size = self.size
else:
size = cache.get(self.images[0]).get_size()
rv = pygame.Surface(size, 0,
renpy.game.interface.display.sample_surface)
for pos, im in zip(self.positions, self.images):
rv.blit(cache.get(im), pos)
return rv
class FrameImage(ImageBase):
"""
This is an image that implements a frame with a given size. Instances
of this are used by the frame object to return a new frame when
such a new frame is needed.
"""
def __init__(self, im, xborder, yborder, width, height):
"""
@param image: The image that will be used as the base of this
frame.
@param xborder: The number of pixels in the x direction to use as
a border.
@param yborder: The number of pixels in the y direction to use as
a border.
@param width: The width we are being rendered at.
@param height: The height we are being rendered at.
"""
im = image(im)
super(FrameImage, self).__init__(im, xborder, yborder, width, height)
self.image = im
self.xborder = xborder
self.yborder = yborder
self.width = width
self.height = height
def load(self):
dw = self.width
dh = self.height
dest = pygame.Surface((dw, dh), 0,
renpy.game.interface.display.sample_surface)
source = cache.get(self.image)
sw, sh = source.get_size()
def draw(x0, x1, y0, y1):
# Quick exit.
if x0 == x1 or y0 == y1:
return
# Compute the coordinates of the left, right, top, and
# bottom sides of the region, for both the source and
# destination surfaces.
# left side.
if x0 >= 0:
dx0 = x0
sx0 = x0
else:
dx0 = dw + x0
sx0 = sw + x0
# right side.
if x1 > 0:
dx1 = x1
sx1 = x1
else:
dx1 = dw + x1
sx1 = sw + x1
# top side.
if y0 >= 0:
dy0 = y0
sy0 = y0
else:
dy0 = dh + y0
sy0 = sh + y0
# bottom side
if y1 > 0:
dy1 = y1
sy1 = y1
else:
dy1 = dh + y1
sy1 = sh + y1
# Compute sizes.
srcsize = (sx1 - sx0, sy1 - sy0)
dstsize = (dx1 - dx0, dy1 - dy0)
# Get a subsurface.
surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
# Scale if we have to.
if dstsize != srcsize:
surf = pygame.transform.scale(surf, dstsize)
# Blit.
dest.blit(surf, (dx0, dy0))
xb = self.xborder
yb = self.yborder
# Top row.
draw(0, xb, 0, yb)
draw(xb, -xb, 0, yb)
draw(-xb, 0, 0, yb)
# Middle row.
draw(0, xb, yb, -yb)
draw(xb, -xb, yb, -yb)
draw(-xb, 0, yb, -yb)
# Bottom row.
draw(0, xb, -yb, 0)
draw(xb, -xb, -yb, 0)
draw(-xb, 0, -yb, 0)
# And, finish up.
return dest
class SolidImage(ImageBase):
"""
This is an image that is a solid rectangle with a given size. It's
used to implement Solid.
"""
def __init__(self, color, width, height):
super(SolidImage, self).__init__(color, width, height)
self.color = color
self.width = width
self.height = height
def load(self):
rv = pygame.Surface((self.width, self.height), 0,
renpy.game.interface.display.sample_surface)
rv.fill(self.color)
return rv
class Scale(ImageBase):
"""
This is an image manipulator that scales another image manipulator
to the specified width and height. This scalling is unfiltered, so
you can expect your image to look a bit jagged.
"""
def __init__(self, im, width, height):
im = image(im)
super(Scale, self).__init__(im, width, height)
self.image = im
self.width = width
self.height = height
def load(self):
return pygame.transform.scale(cache.get(self.image),
(self.width, self.height))
class Rotozoom(ImageBase):
"""
This is an image manipulator that is a smooth rotation and zoom of another image manipulator.
"""
def __init__(self, im, angle, zoom):
"""
@param im: The image to be rotozoomed.
@param angle: The number of degrees counterclockwise the image is
to be rotated.
@param zoom: The zoom factor. Numbers that are greater than 1.0
lead to the image becoming larger.
"""
im = image(im)
super(Rotozoom, self).__init__(im, angle, zoom)
self.image = im
self.angle = angle
self.zoom = zoom
def load(self):
return pygame.transform.rotozoom(cache.get(self.image),
self.angle, self.zoom)
class Crop(ImageBase):
"""
This crops the image that is its child.
"""
def __init__(self, im, x, y, w, h):
im = image(im)
super(Crop, self).__init__(im, x, y, w, h)
self.image = im
self.x = x
self.y = y
self.w = w
self.h = h
def load(self):
return cache.get(self.image).subsurface((self.x, self.y,
self.w, self.h))
def image(arg, **properties):
"""
This takes as input one of a number of ways of specifying an
image, and returns the Displayable image object that has been so
specified. Specifically, this can take as input:
<ul>
<li> An image object. In that case, it's returned unchanged.</li>
<li> A string. If a string is given, then the string is interpreted
as a filename, and what is returned is an im.Image object, which
loads the image from disk.</li>
<li> A tuple. If this is the case, then what is returned is an
im.Composite object, which aligns the upper-left corner of all
of the images supplied as arguments. </li>
</ul>
"""
if isinstance(arg, ImageBase):
return arg
elif isinstance(arg, basestring):
return Image(arg, **properties)
elif isinstance(arg, tuple):
params = [ ]
for i in arg:
params.append((0, 0))
params.append(i)
return Composite(None, *params)
elif isinstance(arg, renpy.display.core.Displayable):
raise Exception("Expected an image, but got a general displayable.")
else:
raise Exception("Could not construct image from argument.")
def load_image(fn):
"""
This loads an image from the given filename, using the cache.
"""
return cache.get(image(fn))
+324 -85
View File
@@ -1,14 +1,159 @@
# This file contains some miscellanious displayables that involve images.
# Most of the guts of this file have been moved into im.py, with only some
# of the stuff thar uses images remaining.
import renpy
from renpy.display.render import render
import pygame
from pygame.constants import *
Image = renpy.display.im.image
class ImageCache(object):
def __init__(self):
# A monotonically increasing time.
self.time = 0
# A map from image filename to surface.
self.surface_map = { }
# A map from image filename to last access time.
self.time_map = { }
# The list of things we want to preload.
self.preloads = [ ]
def tick(self):
self.time += 1
self.preloads = [ ]
# Forces an image load, regardless of if the cache is full or not.
def load_image(self, fn):
self.time_map[fn] = self.time
if fn in self.surface_map:
return self.surface_map[fn]
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))
self.surface_map[fn] = im
if renpy.config.debug_image_cache:
print "Image cache:", self.surface_map.keys()
return im
# Queues an image to be preloaded if not already loaded and there's
# room in the cache for it.
def preload_image(self, fn):
self.time_map[fn] = self.time
if fn in self.surface_map:
return
if fn not in self.preloads:
self.preloads.append(fn)
# This tries to ensure that there are n empty spaces in the image
# cache. Returns the number of empty spaces that are actually in
# the image cache. (A number that may be negative.)
def clear_image_cache(self, n):
rv = renpy.config.image_cache_size - len(self.surface_map)
if rv >= n:
return rv
# The number of images to remove. (This is the amount we are over
# the cache limit + the number of images we have been requested to
# pull.)
num_to_remove = len(self.surface_map) - renpy.config.image_cache_size + n
time_files = [ (self.time_map[fn], fn) for fn in self.surface_map ]
time_files = [ (time, fn) for time, fn in time_files if time != self.time ]
time_files.sort()
time_files = time_files[:num_to_remove]
for time, fn in time_files:
del self.surface_map[fn]
del self.time_map[fn]
if renpy.config.debug_image_cache:
print "Image cache:", self.surface_map.keys()
rv = renpy.config.image_cache_size - len(self.surface_map)
return rv
def needs_preload(self):
"""
Returns True if calling preload would do anything.
"""
return self.preloads and True
def preload(self):
# If we have nothing to preload, bail early.
if not self.preloads:
return
# Try to clear up enough space for the preloads.
avail = self.clear_image_cache(len(self.preloads))
if avail < 0:
avail = 0
self.preloads = self.preloads[:avail]
# If no space is available, bail here.
if not self.preloads:
return
# Get the first thing to preload.
fn = self.preloads[0]
# Actually load the image.
try:
self.load_image(fn)
except:
if renpy.config.debug:
raise
cache = ImageCache()
class Image(renpy.display.core.Displayable):
"""
Returns a Displayable that is an image that is loaded from a file
on disk.
"""
def __init__(self, filename, style='image_placement', **properties):
"""
@param filename: The filename that the image is loaded from. Many common file formats are supported.
"""
self.filename = filename
self.style = renpy.style.Style(style, properties)
def render(self, w, h, st):
im = cache.load_image(self.filename)
w, h = im.get_size()
rv = renpy.display.surface.Surface(w, h)
rv.blit(im, (0, 0))
return rv
def get_placement(self):
return self.style
def predict(self, callback):
callback(self.filename)
class UncachedImage(renpy.display.core.Displayable):
"""
@@ -17,17 +162,11 @@ class UncachedImage(renpy.display.core.Displayable):
def __init__(self, file, hint=None, scale=None, style='image_placement',
**properties):
super(UncachedImage, self).__init__()
self.surf = pygame.image.load(file, hint)
self.surf = self.surf.convert_alpha()
if scale:
self.surf = pygame.transform.scale(self.surf, scale)
renpy.display.render.mutated_surface(self.surf)
self.style = renpy.style.Style(style, properties)
def get_placement(self):
@@ -35,14 +174,14 @@ class UncachedImage(renpy.display.core.Displayable):
def render(self, w, h, st):
sw, sh = self.surf.get_size()
rv = renpy.display.render.Render(sw, sh)
rv = renpy.display.surface.Surface(sw, sh)
rv.blit(self.surf, (0, 0))
return rv
# Should never be called, but what the hey?
def predict(self, callback):
return None
callback(self.filename)
class ImageReference(renpy.display.core.Displayable):
"""
@@ -59,8 +198,6 @@ class ImageReference(renpy.display.core.Displayable):
nosave = [ 'target' ]
def __init__(self, name):
super(ImageReference, self).__init__()
self.name = name
def find_target(self):
@@ -70,7 +207,8 @@ class ImageReference(renpy.display.core.Displayable):
parameters = [ ]
def error(msg):
self.target = renpy.display.text.Text(msg, color=(255, 0, 0, 255))
self.target = renpy.display.text.Text(msg,
color=(255, 0, 0, 255))
if renpy.config.debug:
raise Exception(msg)
@@ -100,22 +238,17 @@ class ImageReference(renpy.display.core.Displayable):
def render(self, width, height, st):
if not hasattr(self, 'target'):
self.find_target()
return render(self.target, width, height, st)
return self.target.render(width, height, st)
def get_placement(self):
if not hasattr(self, 'target'):
self.find_target()
return self.target.get_placement()
def predict(self, callback):
if not hasattr(self, 'target'):
self.find_target()
self.target.predict(callback)
class Solid(renpy.display.core.Displayable):
@@ -127,20 +260,17 @@ class Solid(renpy.display.core.Displayable):
def __init__(self, color):
"""
@param color: An RGBA tuple, giving the color that the display
will be filled with.
@param color: An RGBA tuple, giving the color that the display will be filled with.
"""
super(Solid, self).__init__()
self.color = color
def render(self, width, height, st):
si = renpy.display.im.SolidImage(self.color,
width,
height)
rv = renpy.display.surface.Surface(width, height)
rv.fill(self.color)
return render(si, width, height, st)
return rv
class Frame(renpy.display.core.Displayable):
"""
@@ -154,10 +284,11 @@ class Frame(renpy.display.core.Displayable):
the center of the image is scaled in both x and y directions.
"""
def __init__(self, image, xborder, yborder):
nosave = [ 'cache' ]
def __init__(self, filename, xborder, yborder):
"""
@param image: The image (which may be a filename or image
object) that will be scaled.
@param filename: The file that the original image will be read from.
@param xborder: The number of pixels in the x direction to use as
a border.
@@ -165,29 +296,106 @@ class Frame(renpy.display.core.Displayable):
@param yborder: The number of pixels in the y direction to use as
a border.
For better performance, have the image share a dimension
For better performance, have the image file share a dimension
length in common with the size the frame will be rendered
at. We detect this and avoid scaling if possible.
"""
super(Frame, self).__init__()
self.image = Image(image)
self.filename = filename
self.xborder = xborder
self.yborder = yborder
def render(self, width, height, st):
fi = renpy.display.im.FrameImage(self.image,
self.xborder,
self.yborder,
width,
height)
return render(fi, width, height, st)
if hasattr(self, 'cache'):
if self.cache.get_size() == (width, height):
return self.cache
dest = renpy.display.surface.Surface(width, height)
dw, dh = width, height
def predict(self, callback):
self.image.predict(callback)
source = cache.load_image(self.filename)
sw, sh = source.get_size()
def draw(x0, x1, y0, y1):
# Quick exit.
if x0 == x1 or y0 == y1:
return
# Compute the coordinates of the left, right, top, and
# bottom sides of the region, for both the source and
# destination surfaces.
# left side.
if x0 >= 0:
dx0 = x0
sx0 = x0
else:
dx0 = dw + x0
sx0 = sw + x0
# right side.
if x1 > 0:
dx1 = x1
sx1 = x1
else:
dx1 = dw + x1
sx1 = sw + x1
# top side.
if y0 >= 0:
dy0 = y0
sy0 = y0
else:
dy0 = dh + y0
sy0 = sh + y0
# bottom side
if y1 > 0:
dy1 = y1
sy1 = y1
else:
dy1 = dh + y1
sy1 = sh + y1
# Compute sizes.
srcsize = (sx1 - sx0, sy1 - sy0)
dstsize = (dx1 - dx0, dy1 - dy0)
# Get a subsurface.
surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
# Scale if we have to.
if dstsize != srcsize:
surf = pygame.transform.scale(surf, dstsize)
# Blit.
dest.blit(surf, (dx0, dy0))
xb = self.xborder
yb = self.yborder
# Top row.
draw(0, xb, 0, yb)
draw(xb, -xb, 0, yb)
draw(-xb, 0, 0, yb)
# Middle row.
draw(0, xb, yb, -yb)
draw(xb, -xb, yb, -yb)
draw(-xb, 0, yb, -yb)
# Bottom row.
draw(0, xb, -yb, 0)
draw(xb, -xb, -yb, 0)
draw(-xb, 0, -yb, 0)
# And, finish up.
self.cache = dest
return dest
class Animation(renpy.display.core.Displayable):
"""
@@ -205,15 +413,13 @@ class Animation(renpy.display.core.Displayable):
animation will restart after the final delay time.
"""
super(Animation, self).__init__(style='image_placement')
self.images = [ ]
self.delays = [ ]
for i, arg in enumerate(args):
if i % 2 == 0:
self.images.append(Image(arg))
self.images.append(arg)
else:
self.delays.append(arg)
@@ -226,56 +432,89 @@ class Animation(renpy.display.core.Displayable):
for image, delay in zip(self.images, self.delays):
if t < delay:
renpy.display.render.redraw(self, delay - t)
im = render(image, width, height, st)
width, height = im.get_size()
rv = renpy.display.render.Render(width, height)
rv.blit(im, (0, 0))
return rv
renpy.game.interface.redraw(delay - t)
return cache.load_image(image)
else:
t = t - delay
def predict(self, callback):
for i in self.images:
i.predict(callback)
callback(i)
def get_placement(self):
return renpy.game.style.image_placement
class ImageButton(renpy.display.behavior.Button):
class ImageMap(renpy.display.core.Displayable):
"""
Used to implement the guts of an image button.
The displayable that implements renpy.imagemap.
"""
def __init__(self, idle_image, hover_image,
style='image_button',
image_style='image_button_image',
clicked=None, hovered=None, **properties):
self.idle_image = Image(idle_image, style=image_style)
self.idle_image.style.set_prefix("idle_")
self.hover_image = Image(hover_image, style=image_style)
self.hover_image.style.set_prefix("hover_")
def __init__(self, ground, selected, hotspots,
style='imagemap', **properties):
self.ground = ground
self.selected = selected
self.hotspots = hotspots
self.active = None
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
super(ImageButton, self).__init__(self.idle_image,
style=style,
clicked=clicked,
hovered=hovered,
**properties)
def predict(self, callback):
self.idle_image.predict(callback)
self.hover_image.predict(callback)
callback(i.ground)
callback(i.selected)
def focus(self, default=False):
self.child = self.hover_image
super(ImageButton, self).focus(default=default)
def render(self, width, height, st):
def unfocus(self):
self.child = self.idle_image
super(ImageButton, self).unfocus()
ground = cache.load_image(self.ground)
selected = cache.load_image(self.selected)
width, height = ground.get_size()
rv = renpy.display.surface.Surface(width, height)
rv.blit(ground, (0, 0))
if self.active is not None:
x0, y0, x1, y1, result = self.hotspots[self.active]
subsurface = selected.subsurface((x0, y0, x1-x0, y1-y0))
rv.blit(subsurface, (x0, y0))
return rv
def event(self, ev, x, y):
old_active = self.active
active = None
for i, (x0, y0, x1, y1, result) in enumerate(self.hotspots):
if x >= x0 and x <= x1 and y >= y0 and y <= y1:
active = i
break
# result stays set.
if old_active != active:
self.active = active
renpy.game.interface.redraw(0)
if active is not None:
renpy.sound.play(self.style.hover_sound)
if active is None:
return None
if (ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
(ev.type == KEYDOWN and ev.key == K_RETURN):
renpy.sound.play(self.style.activate_sound)
return result
return None
+71 -262
View File
@@ -5,19 +5,6 @@ import pygame
from pygame.constants import *
import renpy
from renpy.display.render import render
import time
def scale(num, base):
"""
If num is a float, multiplies it by base and returns that. Otherwise,
returns num unchanged.
"""
if isinstance(num, float):
return num * base
else:
return num
class Null(renpy.display.core.Displayable):
"""
@@ -26,21 +13,8 @@ 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__(style=style, **properties)
self.width = width
self.height = height
def get_placement(self):
return self.style
def render(self, width, height, st):
rv = renpy.display.render.Render(self.width, self.height)
if self.focusable:
rv.add_focus(self, None, None, None, None, None)
return rv
return renpy.display.surface.Surface(1, 1)
class Container(renpy.display.core.Displayable):
@@ -63,44 +37,25 @@ class Container(renpy.display.core.Displayable):
"""
def __init__(self, *args, **properties):
def __init__(self, *args):
self.children = []
self.child = None
for i in args:
self.add(i)
super(Container, self).__init__(**properties)
def find_focusable(self, callback, focus_name):
super(Container, self).find_focusable(callback, focus_name)
for i in self.children:
i.find_focusable(callback, self.focus_name or focus_name)
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.
"""
if child is None:
return
self.children.append(child)
self.child = child
def render(self, width, height, st):
rv = render(self.child, width, height, st)
rv = self.child.render(width, height, st)
self.offsets = [ (0, 0) ]
self.sizes = [ rv.get_size() ]
@@ -110,10 +65,7 @@ class Container(renpy.display.core.Displayable):
return self.child.get_placement()
def event(self, ev, x, y):
children_offsets = zip(self.children, self.offsets)
children_offsets.reverse()
for i, (xo, yo) in children_offsets:
for i, (xo, yo) in zip(self.children, self.offsets):
rv = i.event(ev, x - xo, y - yo)
if rv is not None:
return rv
@@ -143,75 +95,13 @@ class Container(renpy.display.core.Displayable):
def predict(self, callback):
super(Container, self).predict(callback)
for i in self.children:
i.predict(callback)
class Fixed(Container):
"""
A container that lays out each of its children at fixed
coordinates determined by the position style of the child. Each
widget is given the whole area of this widget, and then placed
within that area based on its position style.
The result of this layout is the size of the entire area allocated
to it. So it's probably only viable for laying out a root window.
Fixed is used by the display core to render scene lists, and to
pass them off to transitions.
"""
def __init__(self, style='default', **properties):
super(Fixed, self).__init__(style=style, **properties)
self.times = [ ]
def add(self, widget, time=None):
super(Fixed, self).add(widget)
self.times.append(time)
def append_scene_list(self, l):
for tag, time, d in l:
self.add(d, time)
def get_widget_time_list(self):
return zip(self.children, self.times)
def get_placement(self):
return self.style
def render(self, width, height, st):
self.offsets = [ ]
self.sizes = [ ]
rv = renpy.display.render.Render(width, height)
t = time.time()
for child, start in zip(self.children, self.times):
if start:
newst = t - start
else:
newst = st
surf = render(child, width, height, newst)
if surf:
self.sizes.append(surf.get_size())
offset = child.place(rv, 0, 0, width, height, surf)
self.offsets.append(offset)
else:
self.sizes.append((0, 0))
self.offsets.append((0, 0))
return rv
class Position(Container):
"""
Controls the placement of a displayable on the screen, using
supplied position properties. This is the non-curried form of
supplied positon properties. This is the non-curried form of
Position, which should be used when the user has directly created
the displayable that will be shown on the screen.
"""
@@ -226,12 +116,14 @@ class Position(Container):
child of this widget is placed.
"""
super(Position, self).__init__(style=style, **properties)
super(Position, self).__init__()
self.style = renpy.style.Style(style, properties)
self.add(child)
def render(self, width, height, st):
surf = render(self.child, width, height, st)
surf = self.child.render(width, height, st)
cw, ch = surf.get_size()
self.offsets = [ (0, 0) ]
@@ -242,76 +134,7 @@ class Position(Container):
def get_placement(self):
return self.style
class Grid(Container):
"""
A grid is a widget that evenly allocates space to its children.
The child widgets should not be greedy, but should instead be
widgets that only use part of the space available to them.
"""
def __init__(self, cols, rows, padding=0,
style='default', **properties):
"""
@param cols: The number of columns in this widget.
@params rows: The number of rows in this widget.
"""
super(Grid, self).__init__()
self.style = renpy.style.Style(style, properties)
self.cols = cols
self.rows = rows
self.padding = padding
def render(self, width, height, st):
# For convenience and speed.
padding = self.padding
cols = self.cols
rows = self.rows
if len(self.children) != cols * rows:
raise Exception("Grid not completely full.")
renders = [ render(i, width, height, st) for i in self.children ]
self.sizes = [ i.get_size() for i in renders ]
cwidth = 0
cheight = 0
for w, h in self.sizes:
cwidth = max(cwidth, w)
cheight = max(cheight, h)
if self.style.xfill:
cwidth = (width - (cols - 1) * padding) / cols
if self.style.yfill:
cheight = (height - (rows - 1) * padding) / rows
width = cwidth * cols + padding * (cols - 1)
height = cheight * rows + padding * (rows - 1)
rv = renpy.display.render.Render(width, height)
self.offsets = [ ]
for y in range(0, rows):
for x in range(0, cols):
child = self.children[ x + y * cols ]
surf = renders[x + y * cols]
xpos = x * (cwidth + padding)
ypos = y * (cheight + padding)
offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
self.offsets.append(offset)
return rv
class HBox(Container):
"""
@@ -351,7 +174,7 @@ class HBox(Container):
for i in self.children:
xoffsets.append(xo)
surf = render(i, remwidth, height, st)
surf = i.render(remwidth, height, st)
sw, sh = surf.get_size()
@@ -368,7 +191,7 @@ class HBox(Container):
width = xo - self.padding
rv = renpy.display.render.Render(width, myheight)
rv = renpy.display.surface.Surface(width, myheight)
for surf, child, xo in zip(surfaces, self.children, xoffsets):
sw, sh = surf.get_size()
@@ -418,7 +241,7 @@ class VBox(Container):
yoffsets.append(yo)
surf = render(i, width, remheight, st)
surf = i.render(width, remheight, st)
sw, sh = surf.get_size()
@@ -435,7 +258,7 @@ class VBox(Container):
height = yo - self.padding
rv = renpy.display.render.Render(mywidth, height)
rv = renpy.display.surface.Surface(mywidth, height)
for surf, child, yo in zip(surfaces, self.children, yoffsets):
@@ -447,6 +270,39 @@ class VBox(Container):
return rv
class Fixed(Container):
"""
A container that lays out each of its children at fixed
coordinates determined by the position style of the child. Each
widget is given the whole area of this widget, and then placed
within that area based on its position style.
The result of this layout is the size of the entire area allocated
to it. So it's probably only viable for laying out a root window.
"""
def __init__(self, style='default', **properties):
super(Fixed, self).__init__()
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
def render(self, width, height, st):
self.offsets = [ ]
self.sizes = [ ]
rv = renpy.display.surface.Surface(width, height)
for child in self.children:
surf = child.render(width, height, st)
self.sizes.append(surf.get_size())
offset = child.place(rv, 0, 0, width, height, surf)
self.offsets.append(offset)
return rv
class Window(Container):
"""
@@ -466,75 +322,56 @@ class Window(Container):
def __init__(self, child, style='window', **properties):
super(Window, self).__init__(style=style, **properties)
super(Window, self).__init__()
self.add(child)
self.style = renpy.style.Style(style, properties)
def get_placement(self):
return self.style
def render(self, width, height, st):
# save some typing.
# save typing and screen space.
style = self.style
xminimum = scale(style.xminimum, width)
yminimum = scale(style.yminimum, height)
left_margin = scale(style.left_margin, width)
left_padding = scale(style.left_padding, width)
right_margin = scale(style.right_margin, width)
right_padding = scale(style.right_padding, width)
top_margin = scale(style.top_margin, height)
top_padding = scale(style.top_padding, height)
bottom_margin = scale(style.bottom_margin, height)
bottom_padding = scale(style.bottom_padding, height)
# c for combined.
cxmargin = left_margin + right_margin
cymargin = top_margin + bottom_margin
cxpadding = left_padding + right_padding
cypadding = top_padding + bottom_padding
# Render the child.
surf = render(self.child,
width - cxmargin - cxpadding,
height - cymargin - cypadding,
st)
surf = self.child.render(width - 2 * style.xmargin - 2 * style.xpadding,
height - 2 * style.ymargin - 2 * style.ypadding,
st)
sw, sh = surf.get_size()
# If we don't fill, shrink our size to fit.
if not style.xfill:
width = max(cxmargin + cxpadding + sw, xminimum)
width = max(2 * style.xmargin + 2 * style.xpadding + sw, style.xminimum)
if not style.yfill:
height = max(cymargin + cypadding + sh, yminimum)
height = max(2 * style.ymargin + 2 * style.ypadding + sh, style.yminimum)
rv = renpy.display.render.Render(width, height)
rv = renpy.display.surface.Surface(width, height)
# Draw the background. The background should render at exactly the
# requested size. (That is, be a Frame or a Solid).
if style.background:
bw = width - cxmargin
bh = height - cymargin
bw = width - 2 * style.xmargin
bh = height - 2 * style.ymargin
back = render(style.background, bw, bh, st)
back = style.background.render(bw, bh, st)
rv.blit(back,
(left_margin, top_margin))
(style.xmargin, style.ymargin))
# (0, 0, bw, bh))
offsets = self.child.place(rv,
left_margin + left_padding,
top_margin + top_padding,
width - cxmargin - cxpadding,
height - cymargin - cypadding,
style.xmargin + style.xpadding,
style.ymargin + style.ypadding,
width - 2 * (style.xmargin + style.xpadding),
height - 2 * (style.ymargin + style.ypadding),
surf)
self.offsets = [ offsets ]
self.sizes = [ (sw, sh) ]
@@ -580,7 +417,7 @@ class Pan(Container):
def render(self, width, height, st):
surf = render(self.child, width, height, st)
surf = self.child.render(width, height, st)
self.sizes = [ surf.get_size() ]
x0, y0 = self.startpos
@@ -600,7 +437,7 @@ class Pan(Container):
self.offsets = [ (-xo, -yo) ]
rv = renpy.display.render.Render(width, height)
rv = renpy.display.surface.Surface(width, height)
# print surf
@@ -610,7 +447,7 @@ class Pan(Container):
# rv.blit(surf, (-xo, -yo))
if st < self.time:
renpy.display.render.redraw(self, 0)
renpy.game.interface.redraw(0)
return rv
@@ -665,42 +502,14 @@ class Move(Container):
def render(self, width, height, st):
self.st = st
rv = render(self.child, width, height, st)
rv = self.child.render(width, height, st)
self.sizes = [ rv.get_size() ]
self.offsets = [ (0, 0) ]
if st < self.time:
renpy.display.render.redraw(self, 0)
renpy.game.interface.redraw(0)
return rv
class Sizer(Container):
"""
This is a widget that can change the size allocated to the widget that
it contains. Please note that it can only shrink the widget, and that
not all widgets respond well to having their areas shrunk. (For example,
this has no effect on an image.)
"""
def __init__(self, maxwidth, maxheight, child,
style='default', **properties):
super(Sizer, self).__init__()
self.add(child)
self.maxwidth = maxwidth
self.maxheight = maxheight
self.style = renpy.style.Style(style, properties)
def render(self, width, height, st):
if self.maxwidth:
width = min(width, self.maxwidth)
if self.maxheight:
height = min(height, self.maxheight)
return super(Sizer, self).render(width, height, st)
-605
View File
@@ -1,605 +0,0 @@
# Render lifespan.
# A render is alive when it is first created. It stays alive on subsequent
# styles if it is not killed and it is used. It can be killed either
# due to lack of use by the end of a cycle or because it was killed between
# cycles due to a timeout.
import sets
import time
import renpy
import pygame
from pygame.constants import *
# We only cache a single solid... but that should be enough to handle
# some important cases, like button and window backgrounds.
class SolidCache(object):
def __init__(self):
self.size = None
self.color = None
self.cached = None
def create(self, size, color):
if size == self.size and color == self.color:
return self.cached
self.size = size
self.color = color
if color[3] == 255:
surf = pygame.Surface(size, 0,
renpy.game.interface.display.window)
else:
surf = pygame.Surface(size, 0,
renpy.game.interface.display.sample_surface)
mutated_surface(surf)
surf.fill(color)
self.cached = surf
return surf
solid_cache = SolidCache()
## One thing to realize when considering the safety of this is that
## if any widget producing a render is redrawn, all instances of that
## render are killed, and so the entire thing is redrawn.
# Renders that have been used during the current rendering pass.
new_renders = { }
# Renders that were used on the old rendering pass.
old_renders = { }
# The set of surfaces that are mutated (that is, can change their
# contents.)
mutated_surfaces = { }
def render(widget, width, height, st):
"""
Renders a widget on the screen.
"""
if (widget, width, height) in old_renders:
rv = old_renders[widget, width, height]
# assert (widget, width, height) in rv.render_of
# assert not rv.dead
rv.keep_alive()
return rv
rv = widget.render(width, height, st)
rv.render_of.append((widget, width, height))
old_renders[widget, width, height] = rv
new_renders[widget, width, height] = rv
return rv
# A list of (when, widget) for redraws.
redraw_queue = [ ]
def process_redraws():
"""
Processes pending redraws. Returns True if a redraw is needed.
"""
global redraw_queue
redraw_queue.sort()
i = 0
dead_widgets = sets.Set()
now = time.time()
for when, widget in redraw_queue:
if when > now:
break
i += 1
dead_widgets.add(widget)
if not dead_widgets:
return False
redraw_queue = redraw_queue[i:]
for (widget, width, height), render in old_renders.items():
if widget in dead_widgets:
render.kill()
return True
def redraw(widget, when):
"""
Call this to queue the redraw of the supplied widget in the
supplied number of seconds.
"""
redraw_queue.append((when + time.time(), widget))
def render_screen(widget, width, height, st):
global redraw_queue
global old_renders
global new_renders
global mutated_surfaces
mutated_surfaces = { }
rv = render(widget, width, height, st)
# Renders that are in the old set but not the new one die here.
old_render_set = sets.Set(old_renders.itervalues())
new_render_set = sets.Set(new_renders.itervalues())
dead_render_set = old_render_set - new_render_set
for r in dead_render_set:
r.kill()
old_renders.update(new_renders)
new_renders.clear()
# Figure out which widgets are still alive.
live_widgets = sets.Set()
for widget, height, width in old_renders:
live_widgets.add(widget)
# Filter dead widgets from the redraw queue.
redraw_queue = [ (when, widget) for when, widget in redraw_queue if
widget in live_widgets ]
return rv
old_blits = [ ]
def compute_clip(source):
"""
This computes and returns the clipping rectangle of the source render.
"""
global old_blits
new_blits = [ ]
source.clip_to(pygame.display.get_surface(), 0, 0, new_blits)
bl0 = old_blits[:]
bl1 = new_blits[:]
# Backup blits.
old_blits = new_blits
# Changes between the two lists.
changes = [ ]
# Set of things in bl1.
bl1set = { }
for i in bl1:
bl1set[i] = True
# indices.
i0 = 0
i1 = 0
while True:
# If we're done with either of the lists, break.
if i0 >= len(bl0) or i1 >= len(bl1):
break
# blits
b0 = bl0[i0]
b1 = bl1[i1]
# If the two are the same.
if b0 == b1:
# Only add if the surface is mutated.
if b0[0] in mutated_surfaces:
changes.append(b0)
i0 += 1
i1 += 1
continue
# If the surface is only in bl0.
if b0 not in bl1set:
changes.append(b0)
i0 += 1
# The surface is only in bl1.
else:
changes.append(b1)
i1 += 1
changes.extend(bl0[i0:])
changes.extend(bl1[i1:])
if not changes:
return None
surf, x0, y0, w, h = changes[0]
x1 = x0 + w
y1 = y0 + h
for surf, x, y, w, h in changes:
x0 = min(x0, x)
y0 = min(y0, y)
x1 = max(x1, x + w)
y1 = max(y1, y + h)
return x0, y0, x1 - x0, y1 - y0
def screen_blit(source, full=False):
"""
Blits the given render to the screen. Computes the difference
between the current blit list and old_blits.
"""
screen = pygame.display.get_surface()
if full:
source.blit_to(screen, 0, 0)
return (0, 0) + screen.get_size()
cliprect = compute_clip(source)
if not cliprect:
return None
screen = pygame.display.get_surface()
screen.set_clip(cliprect)
source.blit_to(screen, 0, 0)
screen.set_clip()
return cliprect
def mutated_surface(surf):
"""
Called to indicate that a pygame surface has been mutated. This also
should be called each time a new pygame surface is created.
"""
mutated_surfaces[id(surf)] = True
class Render(object):
"""
A render represents a static picture of a single widget (perhaps
including the images of all of the children of that widget). It
is able to draw that widget to the screen, and contains
information about when it becomes invalid (and therefore the
widget can be withdrawn).
"""
def __init__(self, width, height):
"""
Creates a new render corresponding to the given widget with
the specified width and height.
@param widget: If this render corresponds directly to a
widget, then this is the widget it corresponds to.
"""
# Just for safety's sake.
self.dead = False
# A list of widget, width, height, corresponding to the
# entries in old_renders that this render is in.
self.render_of = [ ]
# The width and height of this render.
self.width = width
self.height = height
# The parents of this render.
self.parents = [ ]
self.blittables = [ ]
self.children = [ ]
self.depends = [ ]
# A pygame surface holding this Render, if one exists.
self.surface = None
self.surface_alpha = False
self.subsurfaces = { }
# The list of focusable widgets collected from this render
# and the children of this render. A list of (widget, arg, x, y, w, h,)
self.focuses = [ ]
# def __del__(self):
# Render.renders -= 1
# print "Render del", Render.renders, Render.liverenders, self
def keep_alive(self):
# assert not self.dead
for widget, width, height in self.render_of:
new_renders[widget, width, height] = self
for i in self.children:
# assert self in i.parents
# assert not i.dead
i.keep_alive()
def kill(self):
"""
Calling this marks the render and all of its parents dead. It also
unlinks it from the tree, readying it for reclamation.
"""
if self.dead:
return
self.dead = True
for widget, width, height in self.render_of:
del old_renders[widget, width, height]
if (widget, width, height) in new_renders:
del new_renders[widget, width, height]
parents = self.parents[:]
children = self.children[:]
depends = self.depends[:]
for p in parents:
p.kill()
for c in children:
while self in c.parents:
c.parents.remove(self)
for c in depends:
while self in c.parents:
c.parents.remove(self)
# assert not self.parents
# for p in parents:
# # assert p.dead
self.children = [ ]
self.depends = [ ]
# Removes cycles.
self.render_of = [ ]
self.focuses = [ ]
def blit(self, source, (xo, yo), focus=True):
"""
Adds the source to the list of things that need to be blitted
to the screen. The source should be either a pygame.Surface,
or a Render.
"""
if isinstance(source, Render):
# assert not source.dead
source.parents.append(self)
self.children.append(source)
if focus and xo == 0 and yo == 0:
self.focuses.extend(source.focuses)
elif focus:
for widget, arg, x, y, w, h in source.focuses:
if x is not None:
x += xo
y += yo
self.add_focus(widget, arg, x, y, w, h)
self.blittables.append((xo, yo, source))
def blit_to(self, dest, x, y):
"""
This blits the children of this Render to dest, which must be
a pygame.Surface. The x and y parameters are the location of
the upper-left hand corner of this surface, relative to the
destination surface.
"""
for xo, yo, source in self.blittables:
if isinstance(source, pygame.Surface):
dest.blit(source, (x + xo, y + yo))
else:
source.blit_to(dest, x + xo, y + yo)
def clip_to(self, dest, x, y, blits):
"""
This fills in blits with (id(surf), x, y, w, h) tuples.
"""
for xo, yo, source in self.blittables:
if isinstance(source, pygame.Surface):
blits.append((id(source), x + xo, y + yo) + source.get_size())
else:
source.clip_to(dest, x + xo, y + yo, blits)
def fill(self, color):
"""
Fake a pygame.Surface.fill()
"""
surf = solid_cache.create((self.width, self.height), color)
self.blit(surf, (0,0))
def get_size(self):
"""
Returns the size of this Render, a mostly ficticious value
that's taken from the inputs to the constructor. (As in, we
don't clip to this size.)
"""
return self.width, self.height
def pygame_surface(self, alpha=True):
"""
Returns a pygame surface constructed from this Render. This
may return a cached surface, if one already has been rendered
(so you probably shouldn't change the output of this much).
"""
if self.surface and self.surface_alpha == alpha:
return self.surface
if alpha:
sample = renpy.game.interface.display.sample_surface
else:
sample = renpy.game.interface.display.window
rv = pygame.Surface((self.width, self.height), 0, sample)
self.blit_to(rv, 0, 0)
self.surface = rv
self.surface_alpha = alpha
mutated_surface(rv)
return rv
def subsurface(self, pos, focus=False):
"""
Returns a subsurface of this render.
"""
if pos in self.subsurfaces:
return self.subsurfaces[pos]
x, y, width, height = pos
if x > self.width or y > self.height:
return Render(0, 0)
width = min(self.width - x, width)
height = min(self.height - y, height)
rv = Render(width, height)
if focus:
for fwidget, farg, fx, fy, fw, fh in self.focuses:
if fx is not None:
fx -= x
fx = max(fx, 0)
fy -= y
fy = max(fy, 0)
fw -= x
fw = min(fw, width)
fh -= y
fh = min(fh, height)
if fw <= 0 or fh <= 0:
continue
rv.add_focus(fwidget, farg, fx, fy, fw, fh)
for xo, yo, source in self.blittables:
# ulx, uly -- the coordinates of the upper-left hand corner of
# the image, relative to the subsurface.
ulx = xo - x
uly = yo - y
# ox, oy -- the offsets that the source will be blitted at.
# sx, sy -- the offset within the subsurface at which we begin.
if ulx < 0:
ox = 0
sx = -ulx
else:
ox = ulx
sx = 0
if uly < 0:
oy = 0
sy = -uly
else:
oy = uly
sy = 0
if ox > width or oy > height:
continue
sw, sh = source.get_size()
sw = min(sw - sx, width - ox)
sh = min(sh - sy, height - oy)
if sw <= 0 or sh <= 0:
continue
subsurf = source.subsurface((sx, sy, sw, sh))
if isinstance(subsurf, pygame.Surface):
mutated_surface(subsurf)
rv.blit(subsurf, (ox, oy))
self.subsurfaces[pos] = rv
rv.depends_on(self)
return rv
def depends_on(self, child):
"""
Used to indicate that this render depends on another
render. Useful, for example, if we use pygame_surface to make
a surface, and then blit that surface into another render.
"""
# assert not child.dead
self.depends.append(child)
child.parents.append(self)
def add_focus(self, widget, arg=None, x=0, y=0, w=None, h=None):
"""
This is called to indicate a region of the screen that can be
focused.
@param widget: The widget that will be focused.
@param arg: A focus argument, which can be checked by the widget.
The rest of the parameters are a rectangle giving the portion of
this region corresponding to the focus. If they are all None, than
this focus is assumed to be the singular full-screen focus.
"""
if x is not None:
if w is None:
w = self.width
if h is None:
h = self.height
self.focuses.append(renpy.display.focus.Focus(widget, arg, x, y, w, h))
+156
View File
@@ -0,0 +1,156 @@
# Function to allocate a surface.
import pygame
from pygame.constants import *
import renpy
# def Surface(width, height):
# """
# Allocate a surface. Flags and depth are ignored, for compatibility
# with pygame.Surface.
# """
# return pygame.Surface((width, height), 0,
# renpy.game.interface.display.sample_surface)
# class FilledSurface(object):
# def __init__(self, width, height, color):
# self.width = width
# self.height = height
# self.color = color
# def blit_to(self, dest, x, y):
# dest.fill(self.color, [ x, y, self.width, self.height ])
class Surface(object):
"""
This is our own surface object, which is a node in a tree in which
all of the leaves are PyGame surfaces. It ensures that things are
only blit to the screen once, hopefully giving a performance boost.
"""
def __init__(self, width, height):
self.width = width
self.height = height
self.blittables = [ ]
def blit(self, source, (x, y)):
"""
Adds the source surface to the list of things that need to be
blitted to the screen. The source surface is either a
pygame.Surface, or one of these Ren'Py Surfaces.
"""
self.blittables.append((x, y, source))
def blit_to(self, dest, x, y):
"""
This blits the children of this Surface to dest, which must be
a pygame.Surface. The x and y parameters are the location of
the upper-left hand corner of this surface, relative to the
destination surface.
"""
for xo, yo, source in self.blittables:
if isinstance(source, pygame.Surface):
dest.blit(source, (x + xo, y + yo))
else:
source.blit_to(dest, x + xo, y + yo)
def fill(self, color):
"""
Fake a pygame.Surface.fill()
"""
surf = pygame.Surface((self.width, self.height), 0,
renpy.game.interface.display.sample_surface)
surf.fill(color)
# surf = FilledSurface(self.width, self.height, color)
self.blittables.append((0, 0, surf))
def get_size(self):
"""
Returns the size of this surface, a mostly ficticious value
that's taken from the inputs to the constructor. (As in, we
don't clip to this size.)
"""
return self.width, self.height
def pygame_surface(self, alpha=True):
"""
Returns a pygame surface constructed from self.
"""
if alpha:
sample = renpy.game.interface.display.sample_surface
else:
sample = renpy.game.interface.display.window
rv = pygame.Surface((self.width, self.height), 0, sample)
self.blit_to(rv, 0, 0)
return rv
def subsurface(self, (x, y, width, height)):
"""
Returns the subsurface of this surface, similar to
pygame.Surface.subsurface
"""
if x > self.width or y > self.height:
return Surface(0, 0)
width = min(self.width - x, width)
height = min(self.height - y, height)
rv = Surface(width, height)
for xo, yo, source in self.blittables:
# ulx, uly -- the coordinates of the upper-left hand corner of
# the image, relative to the subsurface.
ulx = xo - x
uly = yo - y
# ox, oy -- the offsets that the source will be blitted at.
# sx, sy -- the offset within the subsurface at which we begin.
if ulx < 0:
ox = 0
sx = -ulx
else:
ox = ulx
sx = 0
if uly < 0:
oy = 0
sy = -uly
else:
oy = uly
sy = 0
sw, sh = source.get_size()
if sw - ox <= 0:
continue
if sh - oy <= 0:
continue
sw = min(sw - sx - ox, width)
sh = min(sh - sy - oy, height)
rv.blit(source.subsurface((sx, sy, sw, sh)),
(ox, oy))
return rv
+87 -368
View File
@@ -1,114 +1,43 @@
import pygame
from pygame.constants import *
import re
import renpy
import sys
_font_cache = { }
# TODO: Something sane if the font file can't be found.
def get_font(fn, size, bold=False, italics=False, underline=False):
def get_font(fn, size):
from renpy.loader import transfn
if (fn, size, bold, italics, underline) in _font_cache:
return _font_cache[(fn, size, bold, italics, underline)]
if (fn, size) in _font_cache:
return _font_cache[(fn, size)]
try:
rv = pygame.font.Font(transfn(fn), size)
rv.set_bold(bold)
rv.set_italic(italics)
except:
rv = pygame.font.SysFont(fn, size, bold, italics)
rv.set_underline(underline)
_font_cache[(fn, size, bold, italics, underline)] = rv
rv = pygame.font.Font(transfn(fn), size)
_font_cache[(fn, size)] = rv
return rv
def color(s):
"""
This function converts a hexcode into a color/alpha tuple. Leading
# marks are ignored. Colors can be rgb or rgba, with each element having
either one or two digits. (So the strings can be 3, 4, 6, or 8 digits long,
not including the optional #.) A missing alpha is interpreted as 255,
fully opaque.
For example, color('#123a') returns (17, 34, 51, 170), while
color('c0c0c0') returns (192, 192, 192, 255).
"""
if s[0] == '#':
s = s[1:]
if len(s) == 6:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = 255
elif len(s) == 8:
r = int(s[0]+s[1], 16)
g = int(s[2]+s[3], 16)
b = int(s[4]+s[5], 16)
a = int(s[6]+s[7], 16)
elif len(s) == 3:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = 255
elif len(s) == 4:
r = int(s[0], 16) * 0x11
g = int(s[1], 16) * 0x11
b = int(s[2], 16) * 0x11
a = int(s[3], 16) * 0x11
else:
raise Exception("Argument to color() must be 3, 4, 6, or 8 hex digits long.")
return (r, g, b, a)
class TextStyle(object):
"""
This is used to represent the style of text that will be displayed
on the screen.
"""
def __init__(self, source=None):
if source is not None:
vars(self).update(vars(source))
def get_font(self):
return get_font(self.font, self.size, self.bold, self.italic, self.underline)
def get_ascent(self):
return self.get_font().get_ascent()
def sizes(self, text):
font = self.get_font()
# print font.get_ascent() - font.get_descent(), font.get_height(), font.get_linesize()
return font.size(text)[0], font.get_ascent() - font.get_descent()
def render(self, text, antialias, color, use_colors):
if use_colors and self.color:
color = self.color
font = self.get_font()
rv = font.render(text, antialias, color)
renpy.display.render.mutated_surface(rv)
return rv
class Text(renpy.display.core.Displayable):
"""
A displayable that can format and display text on the screen.
A Displayable that can display text on the screen.
"""
"""
@ivar style: The style that is used to display the text.
@ivar text: The text that is being displayed.
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_width', 'laidout_height', 'width' ]
The following aren't serialized, but are reconstructed the first
time this is redrawn:
def after_setstate(self):
self.laidout = None
@ivar laidout: The text, split into a list of strings where each
string happens to be one line on the screen.
@ivar height: The height of the laid-out text.
@ivar width: The width of the laid-out text.
"""
def __init__(self, text, slow=False, style='default', **properties):
"""
@@ -121,14 +50,11 @@ class Text(renpy.display.core.Displayable):
@param properties: Additional properties that are applied to the text.
"""
super(Text, self).__init__()
self.text = text
self.style = renpy.style.Style(style, properties)
self.slow = slow
self.laidout = None
def get_placement(self):
return self.style
@@ -152,317 +78,111 @@ class Text(renpy.display.core.Displayable):
"""
This is called after this widget has been updated by
set_text or set_style.
"""
self.laidout = None
renpy.display.render.redraw(self, 0)
def event(self, ev, x, y):
"""
Space, Enter, or Click ends slow, if it's enabled.
"""
if not self.slow:
return None
if renpy.display.behavior.map_event(ev, "dismiss"):
self.slow = False
raise renpy.display.core.IgnoreEvent()
try:
del self.laidout
del self.width
del self.height
except AttributeError:
pass
def layout(self, width):
"""
This lays out the text of this widget. It sets self.laidout,
self.laidout_lineheights, self.laidout_width, and
self.laidout_height.
Called to split the text into a string with newline characters
at line endings where wrapping has occured.
"""
if self.laidout and self.width == width:
return
# Set this, so caching works.
self.width = width
def indent():
if lines:
return self.style.rest_indent
else:
return self.style.first_indent
tsl = [ TextStyle() ]
tsl[-1].font = self.style.font
tsl[-1].size = self.style.size
tsl[-1].bold = self.style.bold
tsl[-1].italic = self.style.italic
tsl[-1].underline = self.style.underline
tsl[-1].color = None
font = get_font(self.style.font, self.style.size)
lines = [ ]
line = [ ]
pars = self.text.split('\n')
# The height of the current line, in pixels, not including
# line_spacing.
lineheight = 0
lh = 0
# A list of same.
lineheights = [ ]
# The width of the current line.
linewidth = 0
# The maximum linewidth.
maxwidth = 0
# The current text.
cur = ""
for p in pars:
words = p.split()
line = ""
# The width, in pixels, of cur.
curwidth = 0
# The remaining width of the line, not including the text in
# cur.
remwidth = width - indent()
for i in re.split(r'( |\{[^{]+\}|\{\{|\n)', self.text):
# Newline.
if i == "\n":
if cur:
line.append((TextStyle(tsl[-1]), cur))
maxwidth = max(maxwidth, linewidth + curwidth)
cur = ""
lines.append(line)
lineheights.append(lineheight)
line = [ ]
linewidth = 0
curwidth, lineheight = tsl[-1].sizes(" ")
remwidth = width - indent()
continue
elif i == "{{":
i = "{"
# We want to render this like a word, so no continue.
elif i.startswith("{"):
# Are we closing a tag?
if i.startswith("{/"):
if cur:
line.append((TextStyle(tsl[-1]), cur))
cur = ""
remwidth -= curwidth
linewidth += curwidth
curwidth = 0
tsl.pop()
if not tsl:
raise Exception("Closing tag %s does not match an open tag." % i)
for w in words:
# Each line must have at least one word on it.
if not line:
line = w
lw, lh = font.size(line)
maxwidth = max(maxwidth, lw)
continue
# Otherwise, we're opening a new tag.
# Mark up any text that uses an old style.
if cur:
line.append((TextStyle(tsl[-1]), cur))
cur = ""
remwidth -= curwidth
linewidth += curwidth
curwidth = 0
tsl.append(TextStyle(tsl[-1]))
if i == "{b}":
tsl[-1].bold = True
elif i == "{i}":
tsl[-1].italic = True
elif i == "{u}":
tsl[-1].underline = True
elif i == "{plain}":
tsl[-1].bold = False
tsl[-1].italic = False
tsl[-1].underline = False
elif i.startswith("{size"):
m = re.match(r'\{size=(\+|-|)(\d+)\}', i)
if not m:
raise Exception('Size tag %s could not be parsed.' % i)
if m.group(1) == '+':
tsl[-1].size += int(m.group(2))
elif m.group(1) == '-':
tsl[-1].size -= int(m.group(2))
else:
tsl[-1].size = int(m.group(2))
elif i.startswith("{color"):
m = re.match(r'\{color=(\#?[a-fA-F0-9]+)\}', i)
if not m:
raise Exception('Color tag %s could not be parsed.' % i)
tsl[-1].color = color(m.group(1))
lw, lh = font.size(line + " " + w)
if lw < width:
line += " " + w
maxwidth = max(maxwidth, lw)
else:
raise Exception("Text tag %s was not recognized. Case and spacing matter here.")
lines.append(line)
line = w
continue
elif i == ' ':
# Spaces always get appended to the end of a line. So they
# will never show up at the start of a line, unless they're
# after a newline or at the start of a string.
cur += i
curwidth, lh = tsl[-1].sizes(cur)
lineheight = max(lh, lineheight)
continue
# If we made it here, then we have normal text.
# We must have at least one word or something else in the
# line before we care about wrapping.
if not cur and not line:
cur = i
curwidth, lineheight = tsl[-1].sizes(cur)
continue
# Should we wrap?
curwidth, lh = tsl[-1].sizes(cur + i)
if curwidth > remwidth:
line.append((TextStyle(tsl[-1]), cur))
lines.append(line)
maxwidth = max(maxwidth, linewidth)
line = [ ]
lineheights.append(lineheight)
cur = i
curwidth, lineheight = tsl[-1].sizes(cur)
remwidth = width - indent()
linewidth = 0
else:
cur = cur + i
lineheight = max(lh, lineheight)
# We're done. Let's close up.
if len(tsl) != 1:
exception("A tag was left open at the end of the text.")
if cur:
line.append((tsl[-1], cur))
maxwidth = max(maxwidth, curwidth)
if line:
lines.append(line)
lineheights.append(lineheight)
self.laidout = lines
self.laidout_lineheights = lineheights
self.laidout_width = max(maxwidth, self.style.minwidth)
self.laidout_height = sum(lineheights) + len(lineheights) * self.style.line_spacing
self.laidout = "\n".join(lines)
self.height = len(lines) * (font.get_linesize() + self.style.line_height_fudge)
self.width = max(maxwidth, self.style.minwidth)
def render_pass(self, r, xo, yo, color, user_colors, length):
"""
Renders the text to r at xo, yo. Color is the base color,
and user_colors controls if the user can override those colors.
Returns True if all characters were rendered, or False if a
length restriction stopped some from being rendered.
"""
y = yo
indent = self.style.first_indent
rest_indent = self.style.rest_indent
antialias = self.style.antialias
line_spacing = self.style.line_spacing
for line, line_height in zip(self.laidout, self.laidout_lineheights):
x = xo + indent
indent = rest_indent
max_ascent = 0
for ts, text in line:
max_ascent = max(ts.get_ascent(), max_ascent)
for ts, text in line:
length -= len(text)
if length < 0:
text = text[:length]
surf = ts.render(text, antialias, color, user_colors)
sw, sh = surf.get_size()
r.blit(surf, (x, y + max_ascent - ts.get_ascent()))
x = x + sw
if length <= 0:
return False
y = y + line_height + line_spacing
return True
def render(self, width, height, st):
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
length = int(st * renpy.config.annoying_text_cps)
else:
length = sys.maxint
self.slow = False
if self.style.drop_shadow:
dsxo, dsyo = self.style.drop_shadow
else:
dsxo, dsyo = 0, 0
absxo = abs(dsxo)
absyo = abs(dsyo)
if not hasattr(self, "laidout"):
self.layout(width - dsxo)
width -= absxo
surf = renpy.display.surface.Surface(self.width + dsxo, self.height + dsyo)
font = get_font(self.style.font, self.style.size)
if dsxo < 0:
xo = -dsxo
dsxo = 0
laidout = self.laidout
# Annoying text hack.
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
chars = int(st * renpy.config.annoying_text_cps)
if chars < len(laidout):
laidout = laidout[:chars]
renpy.game.interface.redraw(0)
else:
xo = 0
self.slow = False
else:
self.slow = False
if dsyo < 0:
yo = -dsyo
dsyo = 0
else:
yo = 0
self.layout(width - absxo)
rv = renpy.display.render.Render(self.laidout_width + absxo, self.laidout_height + absyo)
lines = laidout.split('\n')
# Common rendering code.
def render_lines(x, y, color):
for l in lines:
ls = font.render(l, True, color)
lw, lh = ls.get_size()
xo = int((self.width - lw) * self.style.textalign)
surf.blit(ls, (x + xo, y + font.get_descent()))
y += font.get_linesize() + self.style.line_height_fudge
fudge = 1
# Render drop-shadow.
if self.style.drop_shadow:
self.render_pass(rv, dsxo, dsyo, self.style.drop_shadow_color, False, length)
render_lines(dsxo, dsyo + fudge, self.style.drop_shadow_color)
self.slow = not self.render_pass(rv, xo, yo, self.style.color, True, length)
# Render foreground.
render_lines(0, 0 + fudge, self.style.color)
if self.slow:
renpy.display.render.redraw(self, 0)
return rv
return surf
def event(self, ev, x, y):
"""
@@ -472,12 +192,12 @@ class Text(renpy.display.core.Displayable):
if not self.slow:
return None
if renpy.display.behavior.map_event(ev, "dismiss"):
if ( ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
( ev.type == KEYDOWN and (ev.key == K_RETURN or ev.key == K_SPACE)):
self.slow = False
raise renpy.display.core.IgnoreEvent()
class ParameterizedText(object):
"""
This can be used as an image. When used, this image is expected to
@@ -499,4 +219,3 @@ class ParameterizedText(object):
return Text(string, style=self.style, **self.properties)
+42 -438
View File
@@ -1,57 +1,7 @@
import renpy
from renpy.display.render import render
import pygame
from pygame.constants import *
# This is a utility function that attempts to refactor an old and a new
# Fixed into four Fixeds: below, old, new, and above. Since only the
# old and new need transitions, this can be a significant win.
def refactor_fixed(in_old, in_new):
Fixed = renpy.display.layout.Fixed
out_below = Fixed()
out_old = Fixed()
out_new = Fixed()
out_above = Fixed()
if (not isinstance(in_old, Fixed)) or (not isinstance(in_new, Fixed)):
return out_below, in_old, in_new, out_above
old_list = in_old.get_widget_time_list()
new_list = in_new.get_widget_time_list()
# Merge the beginnings of the lists.
while old_list and new_list:
if old_list[0] == new_list[0]:
out_below.add(new_list[0][0], new_list[0][1])
old_list.pop(0)
new_list.pop(0)
else:
break
# Merge the ends of the lists.
above_list = [ ]
while old_list and new_list:
if old_list[-1] == new_list[-1]:
above_list.insert(0, new_list[-1])
old_list.pop()
new_list.pop()
else:
break
for widget, time in above_list:
out_above.add(widget, time)
for widget, time in old_list:
out_old.add(widget, time)
for widget, time in new_list:
out_new.add(widget, time)
return out_below, out_old, out_new, out_above
class Transition(renpy.display.core.Displayable):
"""
@@ -60,53 +10,40 @@ class Transition(renpy.display.core.Displayable):
"""
def __init__(self, delay):
super(Transition, self).__init__()
self.delay = delay
self.offsets = [ ]
self.events = True
def event(self, ev, x, y):
if self.events:
return self.new_widget.event(ev, x, y)
else:
return None
event_list = self.new_scene_list[:]
event_list.reverse()
def find_focusable(self, callback, focus_name):
self.new_widget.find_focusable(callback, focus_name)
offsets = self.offsets[:]
offsets.reverse()
for (key, st, disp), (xo, yo) in zip(event_list, offsets):
rv = disp.event(ev, x - xo, y - yo)
if rv is not None:
return rv
return None
class Fade(Transition):
"""
This returns an object that can be used as an argument to a with
statement to fade the old scene into a solid color, waits for a
given amount of time, and then fades from the solid color into
the new scene.
@param in_time: The amount of time that will be spent
fading from the old scene to the solid color. A float, given as
seconds.
@param hold_time: The amount of time that will be spent
displaying the solid color. A float, given as seconds.
@param out_time: The amount of time that will be spent
fading from the solid color to the new scene. A float, given as
seconds.
@param color: The solid color that will be faded
to. This is an RGB triple, where each element is in the range 0
to 255. This defaults to black.
This is a transition that involves fading to a certain color, then
holding that color for a certain amount of time, then fading in the
new scene.
"""
def __init__(self, out_time, hold_time, in_time,
old_widget=None, new_widget=None, color=(0, 0, 0)):
old_scene_list, new_scene_list, color=(0, 0, 0)):
super(Fade, self).__init__(out_time + hold_time + in_time)
self.out_time = out_time
self.hold_time = hold_time
self.in_time = in_time
self.old_widget = old_widget
self.new_widget = new_widget
self.old_scene_list = old_scene_list
self.new_scene_list = new_scene_list
self.color = color
# self.frames = 0
@@ -118,29 +55,31 @@ class Fade(Transition):
# self.frames += 1
rv = renpy.display.render.Render(width, height)
rv = renpy.display.surface.Surface(width, height)
events = False
if st < self.out_time:
widget = self.old_widget
scene_list = self.old_scene_list
alpha = int(255 * (st / self.out_time))
elif st < self.out_time + self.hold_time:
widget = None
scene_list = None
alpha = 255
else:
widget = self.new_widget
scene_list = self.new_scene_list
alpha = 255 - int(255 * ((st - self.out_time - self.hold_time) / self.in_time))
events = True
if widget:
surf = render(widget, width, height, st)
rv.blit(surf, (0, 0), focus=events)
if scene_list:
surf, offsets = renpy.display.core.render_scene_list(scene_list,
width,
height)
rv.blit(surf, (0, 0))
self.events = events
if events:
self.offsets = offsets
# Just to be sure.
if alpha < 0:
@@ -152,370 +91,35 @@ class Fade(Transition):
rv.fill(self.color[:3] + (alpha,))
if st < self.in_time + self.hold_time + self.out_time:
renpy.display.render.redraw(self, 0)
renpy.game.interface.redraw(0)
return rv
# This was a nifty idea that just didn't work out, since we can't vary
# the alpha on an image with an alpha channel. Too bad.
# class Dissolve(Transition):
# def __init__(self, time, old_widget, new_widget):
# super(Dissolve, self).__init__(time)
# self.time = time
# self.below, self.old, self.new, self.above = refactor_fixed(old_widget, new_widget)
# def event(self, ev, x, y):
# rv = self.above.event(ev, x, y)
# if rv is None:
# rv = self.new.event(ev, x, y)
# if rv is None:
# rv = self.below.event(ev, x, y)
# return rv
# def render(self, width, height, st):
# rv = renpy.display.render.Render(width, height)
# # Below.
# below = render(self.below, width, height, st)
# rv.blit(below, (0, 0))
# if st < self.time:
# # Old.
# old = render(self.old, width, height, st)
# rv.blit(old, (0, 0))
# # New.
# alpha = min(255, int(255 * st / self.time))
# new = render(self.new, width, height, st)
# if alpha < 255:
# surf = new.pygame_surface(False)
# renpy.display.render.mutable_surface(surf)
# surf.set_alpha(alpha, RLEACCEL)
# rv.blit(surf, (0, 0))
# rv.depends_on(new)
# else:
# rv.blit(new, (0, 0))
# # Above.
# above = render(self.above, width, height, st)
# rv.blit(above, (0, 0))
# if st < self.time:
# renpy.display.render.redraw(self, 0)
# return rv
class Dissolve(Transition):
"""
This dissolves from the old scene to the new scene, by
overlaying the new scene on top of the old scene and varying its
alpha from 0 to 255.
@param delay: The amount of time the dissolve will take.
"""
def __init__(self, time, old_widget=None, new_widget=None):
def __init__(self, time, old_scene_list, new_scene_list):
super(Dissolve, self).__init__(time)
self.time = time
self.old_widget = old_widget
self.new_widget = new_widget
self.events = False
self.old_bottom = None
self.old_top = None
self.old_alpha = 0
self.old_scene_list = old_scene_list
self.new_scene_list = new_scene_list
def render(self, width, height, st):
if st >= self.time:
self.events = True
return render(self.new_widget, width, height, st)
rsl = renpy.display.core.render_scene_list
if st < self.time:
renpy.display.render.redraw(self, 0)
rv, offsets = rsl(self.old_scene_list, width, height)
surftree, self.offsets = rsl(self.new_scene_list, width, height)
surf = surftree.pygame_surface(False)
alpha = min(255, int(255 * st / self.time))
rv = renpy.display.render.Render(width, height)
bottom = render(self.old_widget, width, height, st)
top = render(self.new_widget, width, height, st)
surf = top.pygame_surface(False)
renpy.display.render.mutated_surface(surf)
rv.focuses.extend(top.focuses)
if id(top) == self.old_top and id(bottom) == self.old_bottom:
# Fast rendering path.
alpha = alpha / 255.0
change = ( alpha - self.old_alpha) / ( 1.0 - self.old_alpha)
change = int(change * 255.0)
surf.set_alpha(change, RLEACCEL)
rv.blit(surf, (0, 0))
change /= 255.0
self.old_alpha = self.old_alpha * ( 1 - change ) + change
else:
# Complete rendering path.
rv.blit(bottom, (0, 0), focus=False)
surf.set_alpha(alpha, RLEACCEL)
rv.blit(surf, (0, 0))
self.old_alpha = alpha / 255.0
self.old_top = id(top)
self.old_bottom = id(bottom)
surf.set_alpha(alpha)
rv.blit(surf, (0, 0))
if st < self.time:
renpy.game.interface.redraw(0)
return rv
class CropMove(Transition):
"""
The CropMove transition works by placing the old and the new image
on two layers, called the top and the bottom. (Normally the new
image is on the top, but that can be changed in some modes.) The
bottom layer is always drawn in full. The top image is first
cropped to a rectangle, and then that rectangle drawn onto
the screen at a specified position. Start and end crop rectangles
and positions can be selected by the supplied mode, or
specified manually. The result is a surprisingly flexible
transition.
This transition has many modes, simplifying its use. We can group
these modes into three groups: wipes, slides, and other.
In a wipe, the image stays fixed, and more of it is revealed as
the transition progresses. For example, in "wiperight", a wipe from left to right, first the left edge of the image is
revealed at the left edge of the screen, then the center of the image,
and finally the right side of the image at the right of the screen.
Other supported wipes are "wipeleft", "wipedown", and "wipeup".
In a slide, the image moves. So in a "slideright", the right edge of the
image starts at the left edge of the screen, and moves to the right
as the transition progresses. Other slides are "slideleft", "slidedown",
and "slideup".
There are also slideaways, in which the old image moves on top of
the new image. Slideaways include "slideawayright", "slideawayleft",
"slideawayup", and "slideawaydown".
We also support a rectangular iris in with "irisin" and a
rectangular iris out with "irisout". Finally, "custom" lets the
user define new transitions, if these ones are not enough.
"""
def __init__(self, time,
mode="fromleft",
startcrop=(0.0, 0.0, 0.0, 1.0),
startpos=(0.0, 0.0),
endcrop=(0.0, 0.0, 1.0, 1.0),
endpos=(0.0, 0.0),
topnew=True,
old_widget=None,
new_widget=None):
"""
@param time: The time that this transition will last for, in seconds.
@param mode: One of the modes given above.
The following parameters are only respected if the mode is "custom".
@param startcrop: The starting rectangle that is cropped out of the
top image. A 4-element tuple containing x, y, width, and height.
@param startpos: The starting place that the top image is drawn
to the screen at, a 2-element tuple containing x and y.
@param startcrop: The starting rectangle that is cropped out of the
top image. A 4-element tuple containing x, y, width, and height.
@param startpos: The starting place that the top image is drawn
to the screen at, a 2-element tuple containing x and y.
@param topnew: If True, the top layer contains the new
image. Otherwise, the top layer contains the old image.
"""
super(CropMove, self).__init__(time)
self.time = time
if mode == "wiperight":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipeleft":
startpos = (1.0, 0.0)
startcrop = (1.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipedown":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "wipeup":
startpos = (0.0, 1.0)
startcrop = (0.0, 1.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideright":
startpos = (0.0, 0.0)
startcrop = (1.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideleft":
startpos = (1.0, 0.0)
startcrop = (0.0, 0.0, 0.0, 1.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideup":
startpos = (0.0, 1.0)
startcrop = (0.0, 0.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slidedown":
startpos = (0.0, 0.0)
startcrop = (0.0, 1.0, 1.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "slideawayleft":
endpos = (0.0, 0.0)
endcrop = (1.0, 0.0, 0.0, 1.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawayright":
endpos = (1.0, 0.0)
endcrop = (0.0, 0.0, 0.0, 1.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawaydown":
endpos = (0.0, 1.0)
endcrop = (0.0, 0.0, 1.0, 0.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "slideawayup":
endpos = (0.0, 0.0)
endcrop = (0.0, 1.0, 1.0, 0.0)
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
topnew = False
elif mode == "irisout":
startpos = (0.5, 0.5)
startcrop = (0.5, 0.5, 0.0, 0.0)
endpos = (0.0, 0.0)
endcrop = (0.0, 0.0, 1.0, 1.0)
topnew = True
elif mode == "irisin":
startpos = (0.0, 0.0)
startcrop = (0.0, 0.0, 1.0, 1.0)
endpos = (0.5, 0.5)
endcrop = (0.5, 0.5, 0.0, 0.0)
topnew = False
elif mode == "custom":
pass
else:
raise Exception("Invalid mode %s passed into boxwipe." % mode)
self.delay = time
self.time = time
self.startpos = startpos
self.endpos = endpos
self.startcrop = startcrop
self.endcrop = endcrop
self.topnew = topnew
self.old_widget = old_widget
self.new_widget = new_widget
self.events = False
if topnew:
self.bottom = old_widget
self.top = new_widget
else:
self.bottom = new_widget
self.top = old_widget
def render(self, width, height, st):
time = 1.0 * st / self.time
# Done rendering.
if time >= 1.0:
self.events = True
return render(self.new_widget, width, height, st)
# How we scale each element of a tuple.
scales = (width, height, width, height)
def interpolate_tuple(t0, t1):
return tuple([ int(s * (a * (1.0 - time) + b * time))
for a, b, s in zip(t0, t1, scales) ])
crop = interpolate_tuple(self.startcrop, self.endcrop)
pos = interpolate_tuple(self.startpos, self.endpos)
rv = renpy.display.render.Render(width, height)
rv.blit(render(self.bottom, width, height, st), (0, 0), focus=not self.topnew)
top = render(self.top, width, height, st)
ss = top.subsurface(crop, focus=self.topnew)
rv.blit(ss, pos, focus=self.topnew)
renpy.display.render.redraw(self, 0)
return rv
-172
View File
@@ -1,172 +0,0 @@
import renpy
from renpy.display.render import render
import pygame
import sys # for maxint
class MovieInfo(object):
def __init__(self, filename, loops, fullscreen, size=None):
self.filename = filename
self.loops = loops + 1
self.fullscreen = fullscreen
self.size = size
# The movie that is currently playing, if any.
movie = None
# If the movie is running in a widget, this is the surface corresponding
# to that widget.
surface = None
# The current movie info.
current_info = None
# The number of loops the current movie has made.
loops = 0
def movie_stop(clear=True):
"""
This stops the currently playing movie.
"""
global movie
global loops
if movie:
movie.stop()
movie = None
surface = None
loops = 0
renpy.display.audio.enable_mixer()
if clear:
renpy.game.context().scene_lists.movie = None
def movie_start_fullscreen(filename, loops=0):
"""
This starts a MPEG-1 movie playing in fullscreen mode. While the movie is
playing (that is, until the next call to movie_stop), interactions will
not display anything on the screen.
@param filename: The filename of the MPEG-1 move that we're playing.
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
"""
movie_stop()
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, True)
def movie_start_displayable(filename, size, loops=0):
"""
This starts a MPEG-1 movie playing in displayable mode. One or more Movie()
widgets must be displayed if the movie is to be shown to the user.
@param filename: The filename of the MPEG-1 move that we're playing.
@param size: A tuple containing the size of the movie on the screen. For example, (640, 480).
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
"""
movie_stop()
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, False, size)
def interact():
"""
This is called at the start of an interaction. It starts the required
movie playing, if it's necessary. It returns True if the movie is fullscreen
and therefore nothing else should be drawn on the screen, or False
otherwise.
"""
try:
global movie
global surface
global current_info
global loops
info = renpy.game.context().scene_lists.movie
# Has the info changed? If so, stop the movie.
if info is not current_info:
movie_stop(False)
current_info = info
# No movie to play.
if not info:
return False
# Movie not playing, start it up.
if not movie:
# Needed so we get movie sound.
renpy.display.audio.disable_mixer()
m = pygame.movie.Movie(renpy.loader.transfn(info.filename))
if info.fullscreen:
s = None
m.set_display(pygame.display.get_surface(),
(0, 0,
renpy.config.screen_width,
renpy.config.screen_height))
else:
s = pygame.Surface(info.size)
m.set_display(s, (0, 0) + info.size)
movie = m
surface = s
if not movie.get_busy():
if not info.loops or loops < info.loops:
movie.rewind()
movie.play()
loops += 1
else:
movie_stop()
# Movie is playing (by now).
return info.fullscreen
except:
movie_stop()
if renpy.config.debug_sound:
raise
else:
renpy.display.audio.enable_mixer()
return False
class Movie(renpy.display.layout.Null):
"""
This is a displayable that displays the current movie. In general,
a movie should be playing whenever this is on the screen.
That movie should have been started using movie_start_displayable
before this is shown on the screen, and hidden before this is
removed.
"""
def __init__(self, style='image_placement', **properties):
super(Movie, self).__init__(style=style, **properties)
def render(self, width, height, st):
renpy.display.render.redraw(self, 0)
if surface:
renpy.display.render.mutated_surface(surface)
w, h = surface.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surface, (0, 0))
return rv
else:
return super(Movie, self).render(width, height, st)
+3 -13
View File
@@ -20,10 +20,6 @@ class Context(object):
context.
@ivar rollback: True if this context participates in rollbacks.
@ivar runtime: The time spent in this context, in milliseconds.
@ivar info: A RevertableObject, which is made available to user code.
"""
def __init__(self, rollback, context=None):
@@ -31,17 +27,13 @@ class Context(object):
self.current = None
self.return_stack = [ ]
self.rollback = rollback
self.runtime = 0
self.info = renpy.python.RevertableObject()
oldsl = None
if context:
oldsl = context.scene_lists
self.runtime = context.runtime
vars(self.info).update(vars(context.info))
self.scene_lists = renpy.display.core.SceneLists(oldsl)
import renpy.display.core as dcore
self.scene_lists = dcore.SceneLists(oldsl)
def goto_label(self, node_name):
"""
@@ -119,8 +111,6 @@ class Context(object):
rv.return_stack = self.return_stack[:]
rv.current = self.current
rv.scene_lists = self.scene_lists.rollback_copy()
rv.runtime = self.runtime
rv.info = self.info
return rv
+64 -228
View File
@@ -5,21 +5,18 @@
import renpy
# Many of these shouldn't be used directly.
# from renpy.display.layout import *
from renpy.display.text import ParameterizedText
from renpy.display.behavior import Keymap
# from renpy.display.image import *
from renpy.display.layout import *
from renpy.display.text import *
from renpy.display.behavior import *
from renpy.display.image import *
from renpy.curry import curry
# from renpy.display.audio import music_start, music_stop
from renpy.display.audio import play
from renpy.display.video import movie_start_fullscreen, movie_start_displayable, movie_stop
from renpy.loadsave import load, save, saved_games
from renpy.python import py_eval as eval
from renpy.python import rng as random
from renpy.music import music_start, music_stop
from renpy.sound import play
from renpy.loadsave import *
import time
import random
# This is a map from image name to a Displayable object corresponding
# to that image name.
@@ -33,8 +30,8 @@ def checkpoint():
renpy.game.log.checkpoint()
# def interact(**kwargs):
# return renpy.game.interface.interact(**kwargs)
def interact(*widgets, **kwargs):
return renpy.game.interface.interact(transient=widgets, **kwargs)
def scene_lists(index=-1):
"""
@@ -112,7 +109,7 @@ def watch(expression, style='default', **properties):
renpy.config.overlay_functions.append(overlay_func)
def input(prompt, default='', allow=None, exclude='{}', length=None):
def input(prompt, default='', length=None):
"""
This pops up a window requesting that the user enter in some text.
It returns the entered text.
@@ -123,25 +120,17 @@ def input(prompt, default='', allow=None, exclude='{}', length=None):
@param length: If given, a limit to the amount of text that this
function will return.
@param allow: If not None, then if an input character is not in this
string, it is ignored.
@param exclude: If not None, then if an input character is in this
set, it is ignored.
"""
renpy.ui.window(style='input_window')
renpy.ui.vbox()
vbox = renpy.display.layout.VBox()
win = renpy.display.layout.Window(vbox, style='input_window')
renpy.ui.text(prompt, style='input_prompt')
renpy.ui.input(default, length=length, style='input_text', allow=allow, exclude=exclude)
vbox.add(renpy.display.text.Text(prompt, style='input_prompt'))
vbox.add(renpy.display.behavior.Input(default, length=length, style='input_text'))
renpy.ui.close()
return interact(win)
return renpy.ui.interact()
def menu(items, set_expr):
def menu(items, set_expr, window_style='menu_window'):
"""
Displays a menu, and returns to the user the value of the selected
choice. Also handles conditions and the menuset.
@@ -170,7 +159,7 @@ def menu(items, set_expr):
return None
# Show the menu.
rv = renpy.store.menu(items)
rv = display_menu(items, window_style=window_style)
# If we have a set, fill it in with the label of the chosen item.
if set is not None and rv is not None:
@@ -182,103 +171,70 @@ def menu(items, set_expr):
def display_menu(items, window_style='menu_window'):
"""
Displays a menu containing the given items, returning the value of
the item the user selects.
@param items: A list of tuples that are the items to be added to
this menu. The first element of a tuple is a string that is used
for this menuitem. The second element is the value to be returned
if this item is selected, or None if this item is a non-selectable
caption.
Displays a menu containing the given items.
"""
renpy.ui.window(style=window_style)
renpy.ui.menu(items)
menu = Menu(items)
win = Window(menu, style=window_style)
rv = renpy.ui.interact()
rv = interact(win)
checkpoint()
return rv
class TagQuotingDict(object):
def __getitem__(self, key):
store = vars(renpy.store)
if key in store:
rv = store[key]
if isinstance(rv, (str, unicode)):
rv = rv.replace("{", "{{")
return rv
else:
raise Exception("During an interpolation, '%s' was not found as a variable." % key)
tag_quoting_dict = TagQuotingDict()
def say(who, what):
"""
This is the core of the say command. If the who parameter is None
or a string, it is passed directly to display_say. Otherwise, the
say method is called on the who object with what as a parameter.
This is the core of the say command. If the who parameter is None or
a string, it is passed directly to do_say. Otherwise, the say method
is called on the who object with what as a parameter.
"""
# Interpolate variables.
what = what % tag_quoting_dict
what = what % renpy.game.store
if who is None:
who = renpy.store.narrator
if isinstance(who, (str, unicode)):
renpy.store.say(who, what)
display_say(who, what, what_style='say_thought')
elif isinstance(who, (str, unicode)):
display_say(who, what, what_style='say_dialogue')
else:
who(what)
who.say(what)
def display_say(who, what, who_style='say_label',
what_style='say_dialogue',
window_style='say_window',
who_prefix='',
who_suffix=': ',
what_prefix='',
what_suffix='',
interact=True,
slow=True,
**properties):
window_style='say_window', **properties):
"""
@param who: Who is saying the dialogue, or None if it's not being
said by anyone.
@param what: What is being said.
For documentation of the various prefixes, suffixes, and styles,
please read the documentation for Character.
"""
# If we're going to do an interaction, then saybehavior needs
# to be here.
if interact:
renpy.ui.saybehavior()
if who is not None:
who = who_prefix + who + who_suffix
what = what_prefix + what + what_suffix
import renpy.display.layout as layout
import renpy.display.text as text
import renpy.display.behavior as behavior
renpy.ui.window(style=window_style)
renpy.ui.vbox(padding=10)
if who is not None:
renpy.ui.text(who, style=who_style, **properties)
who = who + ": "
renpy.ui.text(what, style=what_style, slow=slow)
renpy.ui.close()
vbox = layout.VBox(padding=10)
if interact:
renpy.ui.interact()
checkpoint()
if who is not None:
label = text.Text(who, style=who_style, **properties)
vbox.add(label)
def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
line = text.Text(what, style=what_style, slow=True)
vbox.add(line)
window = layout.Window(vbox, style=window_style)
saybehavior = behavior.SayBehavior()
interact(saybehavior, window)
checkpoint()
def imagemap(ground, selected, hotspots, overlays=False,
style='imagemap', **properties):
"""
Displays an imagemap. An image map consists of two images and a
@@ -287,8 +243,8 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
returned.
@param ground: The name of the file containing the ground
image. The ground image is displayed for areas that are not part
of any hotspots.
image. The ground image is displayed in hotspots that the mouse is
not over, and for areas that are not part of any hotspots.
@param selected: The name of the file containing the selected
image. This image is displayed in hotspots when the mouse is over
@@ -301,19 +257,14 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
the value returned from this function if the mouse is clicked in
the hotspot.
@param unselected: If provided, then it is the name of a file
containing the image that's used to fill in hotspots that are not
selected as part of any image. If not provided, the ground image
is used instead.
@param overlays: If True, overlays are displayed when this imagemap
@param overlay: If True, overlays are displayed when this imagemap
is active. If False, the overlays are suppressed.
"""
renpy.ui.imagemap(ground, selected, hotspots, unselected=unselected,
style=style, **properties)
imagemap = ImageMap(ground, selected, hotspots, style=style, **properties)
keymouse = KeymouseBehavior()
rv = renpy.ui.interact(suppress_overlay=(not overlays))
rv = interact(keymouse, imagemap)
checkpoint()
return rv
@@ -321,9 +272,7 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
def pause(delay=None, music=None):
"""
When called, this pauses and waits for the user to click before
advancing the script. If given a delay parameter, the Ren'Py will
wait for that amount of time before continuing, unless a user clicks to
interrupt the delay.
advancing the script.
@param delay: The number of seconds to delay.
@@ -338,45 +287,15 @@ def pause(delay=None, music=None):
"""
if music is not None:
newdelay = renpy.display.audio.music_delay(music)
newdelay = renpy.music.music_delay(music)
if newdelay is not None:
delay = newdelay
renpy.ui.saybehavior()
if delay:
renpy.ui.pausebehavior(delay, False)
return renpy.ui.interact()
def movie_cutscene(filename, delay, loops=0):
"""
This displays an MPEG-1 cutscene for the specified number of
seconds. The user can click to interrupt the cutscene.
Overlays and Underlays are disabled for the duration of the cutscene.
@param filename: The name of a file containing an MPEG-1 movie.
@param delay: The number of seconds to wait before ending the cutscene. Normally the length of the movie, in seconds.
@param loops: The number of extra loops to show, -1 to loop forever.
Returns True if the movie was terminated by the user, or False if the
given delay elapsed uninterrupted.
"""
movie_start_fullscreen(filename, loops=loops)
renpy.ui.saybehavior()
renpy.ui.pausebehavior(delay, False)
rv = renpy.ui.interact(suppress_overlay=True, suppress_underlay=True, show_mouse=False)
movie_stop()
return rv
sayb = renpy.display.behavior.SayBehavior(delay=delay)
scene_list_add('transient', sayb)
return interact()
def with(trans):
"""
@@ -394,7 +313,7 @@ def with(trans):
renpy.game.interface.set_transition(trans)
return renpy.game.interface.interact(show_mouse=False,
trans_pause=True,
suppress_overlay=not renpy.config.overlay_during_wait)
suppress_overlay=True)
else:
return False
@@ -478,100 +397,17 @@ def windows():
import sys
return hasattr(sys, 'winver')
def version():
"""
Returns a string containing the current version of Ren'Py, prefixed with the
string "Ren\'Py ".
"""
return renpy.version
def transition(trans, layer=None):
def transition(trans):
"""
Sets the transition that will be used for the next
interaction. This is useful when the next interaction doesn't take
a with clause, as is the case with pause, input, and imagemap.
@param layer: If the layer setting is not None, then the transition
will be applied only to the layer named. Please note that only some
transitions can be applied to specific layers.
"""
if trans is None:
renpy.game.interface.with_none()
else:
renpy.game.interface.set_transition(trans, layer)
def clear_game_runtime():
"""
Resets the game runtime timer down to 0.
The game runtime counter counts the number of seconds that have
elapsed while waiting for user input in the current context. (So
it doesn't count time spent in the game menu.)
"""
renpy.game.context().runtime = 0
def get_game_runtime():
"""
Returns the number of seconds that have elapsed in gameplay since
the last call to clear_game_timer, as a float.
The game runtime counter counts the number of seconds that have
elapsed while waiting for user input in the current context. (So
it doesn't count time spent in the game menu.)
"""
return renpy.game.context().runtime / 1000.0
def loadable(filename):
"""
Returns True if the given filename is loadable, meaning that it
can be loaded from the disk or from inside an archive. Returns
False if this is not the case.
"""
try:
renpy.loader.load(filename)
return True
except:
return False
def exists(filename):
"""
Returns true if the given filename can be found in the
searchpath. This only works if a physical file exists on disk. It
won't find the file if it's inside of an archive.
"""
try:
renpy.loader.transfn(filename)
return True
except:
return False
def restart_interaction():
"""
Calling this restarts the current interaction. This will immediately end
any ongoing transition, and will call all of the overlay functions again.
This should be called whenever widgets are added or removed over the course
of an interaction, or when the information used to construct the overlay
changes.
"""
renpy.game.interface.restart_interaction = True
def context():
"""
Returns an object that is unique to the current context, that
participates in rollback and the like.
"""
return renpy.game.context().info
renpy.game.interface.set_transition(trans)
call_in_new_context = renpy.game.call_in_new_context
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
+6 -1
View File
@@ -21,6 +21,11 @@ searchpath = [ ]
# A Script object, giving the script of the currently executing game.
script = None
# The store is where Ren'Py python results are stored. We first need
# to import in the module, and then we use the module's dictionary
# directly.
store = None
# A shallow copy of the store made at the end of the init phase. If
# a key in here points to the same value here as it does in the store,
# it is not saved.
@@ -142,4 +147,4 @@ def call_in_new_context(label):
context.run()
contexts.pop()
interface.force_redraw = True
interface.redraw(0)
+2 -2
View File
@@ -1,6 +1,6 @@
import renpy
import os.path
from pickle import loads
from cPickle import loads
from cStringIO import StringIO
archives = [ ]
@@ -63,7 +63,7 @@ def transfn(name):
searched directories.
"""
for d in renpy.config.searchpath:
for d in renpy.game.searchpath:
if os.path.exists(d + "/" + name):
return d + "/" + name
+51 -126
View File
@@ -1,68 +1,12 @@
# This file contains functions that load and save the game state.
from pickle import dumps, loads, HIGHEST_PROTOCOL
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 isinstance(o, (int, str, float, bool)):
print prefix, o
return
if id(o) in seen:
print prefix, "@%x" % 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
@@ -71,98 +15,79 @@ 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.
"""
filename = filename + savegame_suffix
try:
os.unlink(renpy.config.savedir + "/" + filename)
except:
pass
if filename == None:
filename = str(time.time()) + ".save"
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()
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():
def saved_game_filenames():
"""
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.
Returns a list of savegame files.
"""
files = os.listdir(renpy.config.savedir)
files.sort()
files = [ i for i in files if i.endswith(savegame_suffix) ]
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") ]
if not files:
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)]
return None
saveinfo = { }
datefiles = [ (os.stat(renpy.config.savedir + "/" + i).st_mtime, i) for i in files ]
datefiles.sort()
for f in files:
return datefiles[-1][1]
def load_extra_info(filename):
"""
Returns the extra_info string that was saved in a savegame file.
"""
try:
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename, "r")
rv = zf.read("extra_info")
zf.close()
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
extra_info = zf.read("extra_info")
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
zf.close()
return rv
screenshot = renpy.display.image.UncachedImage(sio, "screenshot.tga", False)
f = f[:-len(savegame_suffix)]
saveinfo[f] = screenshot, extra_info
except:
if renpy.config.debug:
raise Exception
if newest not in saveinfo:
newest = None
return saveinfo, newest
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)
def load(filename):
"""
Loads the game from the given file. This function never returns.
"""
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename + savegame_suffix, "r")
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename, "r")
log = loads(zf.read("log"))
zf.close()
log.unfreeze()
+8 -30
View File
@@ -12,16 +12,14 @@
import renpy
import renpy.game as game
import os
from pickle import loads, dumps, HIGHEST_PROTOCOL
from cPickle import loads, dumps, HIGHEST_PROTOCOL
def run(restart=False):
def run():
"""
This is called during a single run of the script. Restarting the script
will cause this to change.
"""
renpy.game.exception_info = 'While beginning to run the game.'
# Initialize the log.
game.log = renpy.python.RollbackLog()
@@ -29,9 +27,6 @@ def run(restart=False):
renpy.store.reload()
renpy.config.reload()
# Note that this is a restart.
renpy.store._restart = restart
renpy.config.savedir = game.basepath + "/saves"
# Make the save directory.
@@ -42,7 +37,7 @@ def run(restart=False):
# Unserialize the persistent data.
try:
f = file(renpy.config.savedir + "/persistent", "rb")
f = file(renpy.config.savedir + "/persistent", "r")
s = f.read().decode("zlib")
f.close()
game.persistent = loads(s)
@@ -54,11 +49,6 @@ def run(restart=False):
game.persistent._seen_ever = { }
game.seen_ever = game.persistent._seen_ever
# Initialize the set of images seen ever.
if not game.persistent._seen_images:
game.persistent._seen_images = { }
# Clear the list of seen statements in this game.
game.seen_session = { }
@@ -71,11 +61,11 @@ def run(restart=False):
# Initialize the store.
renpy.store.store = renpy.store
game.store = vars(renpy.store)
renpy.store.persistent = game.persistent
renpy.store._preferences = game.preferences
# Set up styles.
renpy.style.reset()
game.style = renpy.style.StyleManager()
renpy.store.style = game.style
@@ -90,17 +80,12 @@ def run(restart=False):
game.init_phase = False
renpy.game.exception_info = 'After initialization, but before game start.'
# Rebuild the various style caches.
renpy.style.build_styles()
# 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()
# Make a clean copy of the store.
game.clean_store = vars(renpy.store).copy()
game.clean_store = game.store.copy()
# Re-Initialize the log.
game.log = renpy.python.RollbackLog()
@@ -143,24 +128,17 @@ def run(restart=False):
def main(basepath):
renpy.game.exception_info = 'While loading the script.'
game.basepath = basepath
renpy.config.searchpath = [ "common", basepath ]
renpy.config.backup()
game.searchpath = [ "common", basepath ]
# Load the script.
game.script = renpy.script.load_script()
game.script = renpy.script.load_script(game.basepath)
# Start things running.
restart = False
while True:
try:
run(restart)
run()
break
except game.FullRestartException, e:
restart = True
pass
+89
View File
@@ -0,0 +1,89 @@
# This module contains code that handles the playing of music.
import pygame
import renpy
# Information about the currently playing track.
current_music = None
def music_delay(offset):
"""
Returns the time left until the current music has been playing for
offset seconds. If music is not playing, return None. May return
a negative time.
"""
mo = pygame.mixer.music.get_pos()
if mo < 0:
return None
mo /= 1000.0
return offset - mo
def music_start(filename, loops=-1, startpos=0.0):
"""
This starts music playing. If a music track is already playing,
stops that track in favor of this one.
@param filename: The file that the music will be played from. This
is relative to the game directory, and must be a real file (so it
cannot be stored in an archive.)
@param loops: The number of times the music will loop after it
finishes playing. If negative, the music will loop indefinitely.
Please note that even once the song has finished, rollback or load
may cause it to start playing again. So it may not be safe to have
this set to a non-negative value.
@param startpos: The number of seconds into the music to start playing.
"""
music_stop()
renpy.game.context().scene_lists.music = (filename, loops, startpos)
restore()
def music_stop():
"""
Stops the currently playing music track.
"""
renpy.game.context().scene_lists.music = None
restore()
def restore():
"""
This makes sure that the current music matches the music found in
the context.
"""
global current_music
new_music = renpy.game.context().scene_lists.music
if not renpy.game.preferences.music:
new_music = None
if current_music == new_music:
return
# Usually, ignore errors.
try:
if current_music != new_music and current_music:
current_music = None
pygame.mixer.music.fadeout(int(renpy.config.fade_music * 1000))
else:
if not pygame.mixer.music.get_busy():
fn, loops, startpos = new_music
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
pygame.mixer.music.play(loops, startpos)
current_music = new_music
except pygame.error, e:
if renpy.config.debug:
raise
else:
print "Error while trying to play music:", str(e)
+22 -54
View File
@@ -5,13 +5,12 @@ import codecs
import re
import os
import renpy
import renpy.ast as ast
class ParseError(Exception):
def __init__(self, filename, number, msg, line=None, pos=None):
message = u"On line %d of %s: %s" % (number, filename, msg)
message = "On line %d of %s: %s" % (number, filename, msg)
if line is not None:
message += "\n\n" + line
@@ -19,12 +18,7 @@ class ParseError(Exception):
if pos is not None:
message += "\n" + " " * pos + "^"
self.message = message
Exception.__init__(self, message.encode('unicode_escape'))
def __unicode__(self):
return self.message
Exception.__init__(self, message)
def list_logical_lines(filename):
@@ -47,10 +41,6 @@ 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):
@@ -143,7 +133,7 @@ def list_logical_lines(filename):
if line != "":
raise ParseError(filename, start_number, "is not terminated with a newline (check quotes and parenthesis).")
raise ParseError(filename, number, "is not terminated with a newline.")
return rv
@@ -172,11 +162,13 @@ def group_logical_lines(lines):
if l[index] == '\t':
index += 1
depth = depth + 8 - (depth % 8)
depth = depth + 8 - (16 % 8)
continue
break
# TODO: Fix to handle tabs properly. Or else update the docs to
# forbid tabs entirely.
return depth, l[depth:]
# i, min_depth -> block, new_i
@@ -226,7 +218,6 @@ class Lexer(object):
keywords = [
'at',
'call',
'expression'
'hide',
'if',
'image',
@@ -303,9 +294,7 @@ class Lexer(object):
Advances the current position beyond any contiguous whitespace.
"""
# print self.text[self.pos].encode('unicode_escape')
self.match_regexp(ur"\s+")
self.match_regexp(r"\s+")
def match(self, regexp):
"""
@@ -333,7 +322,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):
@@ -465,17 +454,12 @@ class Lexer(object):
if c == 'u':
self.pos += 1
if self.pos == len(self.text):
self.pos -= 1
if self.eol():
return False
c = self.text[self.pos]
if c not in ('"', "'"):
self.pos -= 1
return False
elif c not in ('"', "'"):
if c not in ('"', "'"):
return False
delim = c
@@ -517,7 +501,7 @@ class Lexer(object):
while self.match(r'\.'):
n = self.name()
if not n:
self.error('expecting name.')
self.parse_error('expecting name.')
rv += "." + n
@@ -938,6 +922,15 @@ 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'):
@@ -967,35 +960,12 @@ 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)
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) ]
rv = [ ast.Call(loc, target) ]
if l.keyword('from'):
name = l.require(l.name)
@@ -1188,8 +1158,6 @@ def parse(fn):
statements that were found at the top level of the file.
"""
renpy.game.exception_info = 'While parsing ' + fn + '.'
version = os.stat(fn).st_mtime
lines = list_logical_lines(fn)
nested = group_logical_lines(lines)
+24 -111
View File
@@ -9,9 +9,6 @@ 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
@@ -62,7 +59,7 @@ def reached(obj, path, reachable):
def reached_vars(store, reachable):
"""
Marks everything reachable from the variables in the store
or from the context info objects as reachable.
as reachable.
@param store: A map from variable name to variable value.
@param reachable: A dictionary mapping reached object ids to
@@ -72,9 +69,7 @@ def reached_vars(store, reachable):
for k, v in store.iteritems():
reached(v, k, reachable)
for c in renpy.game.contexts:
reached(c.info, "#context", reachable)
##### Code that replaces literals will calls to magic constructors.
@@ -135,19 +130,9 @@ 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.
@@ -228,43 +213,6 @@ class RevertableObject(object):
self.__dict__.clear()
self.__dict__.update(old)
##### An object that handles deterministic randomness, or something.
class DetRandom(random.Random):
def __init__(self):
super(DetRandom, self).__init__()
self.stack = [ ]
def random(self):
if self.stack:
rv = self.stack.pop()
else:
rv = super(DetRandom, self).random()
renpy.game.log.current.random.append(rv)
return rv
def pushback(self, l):
"""
Pushes the random numbers in l onto the stack so they will be generated
in the order given.
"""
ll = l[:]
ll.reverse()
self.stack.extend(ll)
def reset(self):
"""
Resets the RNG, removing all of the pushbacked numbers.
"""
self.stack = [ ]
rng = DetRandom()
##### This is the code that actually handles the logging and managing
##### of the rollbacks.
@@ -293,10 +241,6 @@ class Rollback(renpy.object.Object):
@ivar purged: True if purge_unreachable has already been called on
this Rollback, False otherwise.
@ivar random: A list of random numbers that were generated during the
execution of this element.
"""
def __init__(self):
@@ -305,7 +249,6 @@ class Rollback(renpy.object.Object):
self.store = [ ]
self.checkpoint = False
self.purged = False
self.random = [ ]
def purge_unreachable(self, reachable):
"""
@@ -358,13 +301,12 @@ class Rollback(renpy.object.Object):
for t in self.store:
if len(t) == 2:
k, v = t
vars(renpy.store)[k] = v
renpy.game.store[k] = v
else:
k, = t
del vars(renpy.store)[k]
del renpy.game.store[k]
renpy.game.contexts = [ self.context ]
rng.pushback(self.random)
class RollbackLog(renpy.object.Object):
@@ -405,9 +347,6 @@ class RollbackLog(renpy.object.Object):
self.frozen_roots = None
self.rollback_limit = 0
# Reset the RNG on the creation of a new game.
rng.reset()
def after_setstate(self):
self.mutated = { }
@@ -430,7 +369,7 @@ class RollbackLog(renpy.object.Object):
self.log.append(self.current)
self.mutated = { }
self.old_store = vars(renpy.store).copy()
self.old_store = renpy.game.store.copy()
def complete(self):
"""
@@ -441,7 +380,7 @@ class RollbackLog(renpy.object.Object):
occurs.
"""
new_store = vars(renpy.store)
new_store = renpy.game.store
store = [ ]
@@ -474,8 +413,6 @@ 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
@@ -486,11 +423,9 @@ class RollbackLog(renpy.object.Object):
rv = { }
store = vars(renpy.store)
for k in self.ever_been_changed.keys():
if k in store:
rv[k] = store[k]
if k in renpy.game.store:
rv[k] = renpy.game.store[k]
return rv
@@ -578,11 +513,7 @@ class RollbackLog(renpy.object.Object):
rb.rollback()
# Disable the next transition, as it's pointless.
renpy.game.interface.suppress_transition = True
# If necessary, reset the RNG.
if force:
rng.reset()
renpy.game.interface.supress_transition = True
# Restart the game with the new state.
raise renpy.game.RestartException()
@@ -607,9 +538,6 @@ 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
@@ -621,53 +549,38 @@ class RollbackLog(renpy.object.Object):
renpy.game.log = self
# Restore the store.
store = vars(renpy.store)
store.clear()
store.update(renpy.game.clean_store)
renpy.game.store.clear()
renpy.game.store.update(renpy.game.clean_store)
for k in self.ever_been_changed:
if k in store:
del store[k]
if k in renpy.game.store:
del renpy.game.store[k]
store.update(self.frozen_roots)
renpy.game.store.update(self.frozen_roots)
self.frozen_roots = None
# Now, rollback to an acceptable point.
self.rollback(0, force=True)
# We never make it this far.
def py_exec_bytecode(bytecode, hide=False):
store = vars(renpy.store)
if hide:
locals = { }
else:
locals = store
exec marshal.loads(bytecode) in store, locals
def py_exec(source, hide=False):
store = vars(renpy.store)
if hide:
locals = { }
else:
locals = store
locals = renpy.game.store
exec py_compile(source, 'exec') in store, locals
def py_eval_bytecode(bytecode):
return eval(marshal.loads(bytecode), vars(renpy.store))
exec py_compile(source, 'exec') in renpy.game.store, locals
def py_eval(source):
source = source.strip()
return eval(py_compile(source, 'eval'),
vars(renpy.store))
renpy.game.store)
+11 -21
View File
@@ -6,10 +6,10 @@ import renpy
import os.path
import os
from pickle import loads, dumps
from cPickle import loads, dumps
# The version of the dumped script.
script_version = renpy.script_version
script_version = 2
class ScriptError(Exception):
"""
@@ -35,7 +35,7 @@ class Script(object):
"""
def __init__(self, node_callback=None):
def __init__(self, dir):
"""
Loads the script by parsing all of the given files, and then
walking the various ASTs to initialize this Script object.
@@ -48,7 +48,7 @@ class Script(object):
# A list of all files in the search directories.
dirlist = [ ]
for dirname in renpy.config.searchpath:
for dirname in renpy.game.searchpath:
for fn in os.listdir(dirname):
dirlist.append(dirname + "/" + fn)
@@ -80,30 +80,22 @@ class Script(object):
# print "Loading", fn
if self.load_file(fn, node_callback):
if self.load_file(fn):
continue
print "Couldn't load %s, trying %s instead." % (fn, alt)
if self.load_file(alt, node_callback):
if self.load_file(alt):
continue
raise Exception("Could not load %s or %s." % (fn, alt))
# Make the sort stable.
initcode = [ (prio, index, code) for index, (prio, code) in
enumerate(self.initcode) ]
initcode.sort()
self.initcode = [ (prio, code) for prio, index, code in initcode ]
self.initcode.sort()
# Do some generic init here.
def load_file(self, fn, node_callback):
def load_file(self, fn):
if fn.endswith(".rpy"):
stmts = renpy.parser.parse(fn)
@@ -143,9 +135,6 @@ class Script(object):
# Check each node individually.
for node in all_stmts:
if node_callback:
node_callback(node)
# Check to see if the name is defined twice. If it is,
# report the error.
name = node.name
@@ -185,8 +174,9 @@ class Script(object):
return label in self.namemap
def load_script():
rv = Script()
def load_script(dir):
rv = Script(dir)
return rv
+39
View File
@@ -0,0 +1,39 @@
# Plays sounds.
import renpy
import pygame
from pygame.constants import *
def init():
pygame.mixer.init(renpy.config.sound_sample_rate)
def play(fn, loops=0):
"""
This plays the given sound. The sound must be in a wav file,
and expected to have a sample rate 44100hz (changable with
config.sound_sample_rate), 16 bit, stereo. These expectations may
be violated, but that may lead to conversion delays.
Once a sound has been started, there's no way to stop it.
@param fn: The name of the file that the sound is read from. This
file may be contained in a game directory or an archive.
@param loops: The number of extra times the sound will be
played. (The default, 0, will play the sound once.)
"""
if not fn:
return
if not renpy.game.preferences.sound:
return
try:
sound = pygame.mixer.Sound(renpy.loader.load(fn))
sound.play()
except:
if renpy.config.debug:
raise
+2 -79
View File
@@ -5,10 +5,6 @@
# of user code, unless we re-import it.
import renpy
import renpy.ui as ui
import renpy.display.im as im
import renpy.display.audio as audio
from renpy.python import RevertableList as __renpy__list__
list = __renpy__list__
@@ -24,14 +20,12 @@ Image = renpy.display.image.Image
Solid = renpy.display.image.Solid
Frame = renpy.display.image.Frame
Animation = renpy.display.image.Animation
Movie = renpy.display.video.Movie
Position = renpy.curry.curry(renpy.display.layout.Position)
Pan = renpy.curry.curry(renpy.display.layout.Pan)
Move = renpy.curry.curry(renpy.display.layout.Move)
Fade = renpy.curry.curry(renpy.display.transition.Fade)
Dissolve = renpy.curry.curry(renpy.display.transition.Dissolve)
CropMove = renpy.curry.curry(renpy.display.transition.CropMove)
def _return(v):
"""
@@ -78,22 +72,6 @@ class Character(object):
@param properties: Additional style properties, that are
applied to the label containing the character's name.
In addition to the parameters given above, there are also a
few other keyword parameters:
@param who_prefix: A prefix that is prepended to the name.
@param who_suffix: A suffix that is appended to the name. (Defaults to ':')
@param what_prefix: A prefix that is prepended to the text body.
@param what_suffix: A suffix that is appended to the text body.
@param interact: If True (the default), then each line said
through this character causes an interaction. If False, then
the window is added to the screen, but control immediately
proceeds. You'll need to call ui.interact yourself to show it.
"""
self.name = name
@@ -102,71 +80,16 @@ class Character(object):
self.window_style = window_style
self.properties = properties
def __call__(self, what, interact=True):
def say(self, what):
renpy.display_say(self.name, what,
who_style=self.who_style,
what_style=self.what_style,
window_style=self.window_style,
interact=interact,
**self.properties)
class DynamicCharacter(object):
"""
A DynamicCharacter is similar to a Character, except that instead
of having a fixed name, it has an expression that is evaluated to
produce a name before each line of dialogue is displayed. This allows
one to have a character with a name that is read from the user, as
may be the case for the POV character.
"""
import renpy.config as config
def __init__(self, name_expr,
who_style='say_label',
what_style='say_dialogue',
window_style='say_window',
**properties):
"""
@param name_expr: An expression that, when evaluated, should yield
the name of the character, as a string.
All other parameters are as for Character.
"""
self.name_expr = name_expr
self.who_style = who_style
self.what_style = what_style
self.window_style = window_style
self.properties = properties
def __call__(self, what, interact=True):
import renpy.python as python
renpy.display_say(python.py_eval(self.name_expr),
what,
who_style=self.who_style,
what_style=self.what_style,
window_style=self.window_style,
interact=interact,
**self.properties)
# The color function. (Moved, since text needs it, too.)
color = renpy.display.text.color
# Conveniently get rid of all the packages we had imported before.
import renpy.exports as renpy
# The default narrator.
def narrator(what, interact=True):
renpy.display_say(None, what, what_style='say_thought', interact=interact)
# The default menu function.
menu = renpy.display_menu
# The function that is called when anonymous text is said.
def say(who, what):
renpy.display_say(who, what)
# The default transition.
default_transition = None
+66 -162
View File
@@ -1,192 +1,96 @@
import renpy
# A list of style prefixes we care about, including no prefix.
prefixes = [ 'hover_', 'idle_', 'activate_' , 'insensitive_' ]
def startswith_prefix(s):
for i in prefixes:
if s.startswith(i):
return True
else:
return False
substitutes = dict(
xmargin = [ 'left_margin', 'right_margin' ],
ymargin = [ 'top_margin', 'bottom_margin' ],
xpadding = [ 'left_padding', 'right_padding' ],
ypadding = [ 'top_padding', 'bottom_padding' ],
)
# Expand out substitutes:
for k in substitutes.keys():
for p in prefixes:
substitutes[p + k] = [ p + i for i in substitutes[k] ]
# A map from a style name to the style associated with that name.
style_map = { }
# True if we have expanded all of the style caches, False otherwise.
styles_built = False
# A list of styles that are pending expansion.
styles_pending = [ ]
# A list of created styles giving the style's name, parent, and description.
style_info = [ ]
def reset():
"""
This resets all of the data structures associated with style
management.
"""
global style_map
global styles_built
global styles_pending
global style_info
style_map = { }
styles_built = False
styles_pending = [ ]
style_info = [ ]
class StyleManager(object):
"""
This is the singleton object that is exported into the store
as style
This is the singleton object that is exported into the store as
'style', and to everyone as renpy.game.style. It's responsible for
mapping style names to styles.
"""
def __getattr__(self, name):
try:
return style_map[name]
except:
raise Exception('The style %s does not exist.' % name)
def __init__(self):
self._style_list = [ ]
def create(self, name, parent, description=None):
style_map[name] = Style(parent, { })
def create(self, name, parent='default', description=''):
"""
Creates a new style with the given parent and description, and
adds it to the StyleManager.
"""
if description:
style_info.append((name, parent, description))
# This expands out property names and adds them to style's property
# dictionary.
def compute_properties(style, properties):
props = { }
# Expand substitutions.
for k, v in properties.items():
if k in substitutes:
for j in substitutes[k]:
props[j] = v
else:
props[k] = v
# Expand prefixes, where necessary.
for k, v in props.items():
if startswith_prefix(k):
continue
del props[k]
for p in prefixes:
props[p + k] = v
style.properties.update(props)
style.cache.update(props)
# This builds the style. If recurse is True, this also builds the
# parent style.
def build_style(style, recurse=False):
style.cache.clear()
if style.parent:
try:
parent = style_map[style.parent]
except:
raise Exception('Style %s is not known.' % style.parent)
if recurse:
build_style(parent)
if parent and not hasattr(self, parent):
raise Exception("Style '%s' has non-existent parent '%s'." % (name, parent))
style.cache.update(parent.cache)
s = Style(parent)
s.name = name
s.parent = parent
s.description = description
style.cache.update(style.properties)
setattr(self, name, s)
# This builds all pending styles, recursing to ensure that they are built
# in the right order.
def build_styles():
self._style_list.append(s)
global styles_pending
global styles_built
def _write_docs(self, filename):
for s in styles_pending:
build_style(s, True)
f = file(filename, "w")
styles_pending = None
styles_built = True
import re
for s in self._style_list:
f.write(' <renpy_style name="%s">' % s.name)
if s.parent:
f.write('<renpy_style_inherits>%s</renpy_style_inherits>' % s.parent)
f.write(re.sub(r'\s+', ' ', s.description))
f.write("</renpy_style>\n\n")
f.close()
class Style(object):
"""
This is an individual style object, which can have properties
looked up on it or its parent. Call the constructor of this
to create an anonymous style.
"""
def __getstate__(self):
return dict(prefix = self.prefix,
parent = self.parent,
cache = { },
properties = self.properties)
return vars(self)
# Not sure why this is necessary, but it seems to be. :-(
def __setstate__(self, state):
vars(self).update(state)
build_style(self)
self.__dict__.update(state)
def __init__(self, parent, properties):
def __getattr__(self, key):
return self.lookup(key, self.prefix)
fields = dict(
prefix = 'insensitive_',
parent = parent,
cache = { },
properties = { },
)
def lookup(self, key, prefix):
vars(self).update(fields)
if prefix + key in vars(self):
return vars(self)[prefix + key]
if key in vars(self):
return vars(self)[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)
if styles_built:
build_style(self)
else:
styles_pending.append(self)
compute_properties(self, properties)
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):
if parent and not hasattr(renpy.game.style, parent):
raise Exception("Style '%s' is not known." % parent)
def __getattr__(self, name):
return self.cache[self.prefix + name]
self.parent = parent
self.prefix = ''
def __setattr__(self, name, value):
compute_properties(self, { name : value } )
if properties:
vars(self).update(properties)
def write_docs(filename):
f = file(filename, "w")
import re
for name, parent, description in style_info:
f.write(' <renpy_style name="%s">' % name)
if parent:
f.write('<renpy_style_inherits>%s</renpy_style_inherits>' % parent)
f.write(re.sub(r'\s+', ' ', description))
f.write("</renpy_style>\n\n")
f.close()
-507
View File
@@ -1,507 +0,0 @@
# This file contains functions that can be used to display a UI on the
# screen. The UI isn't implemented here (rather, in
# renpy.display). Instead, these functions provide a simple interface
# that allows a user to procedurally create a UI.
# The functions in this file work in terms of a current widget. By
# default, the current widget is the screen. Each call to a function
# creates a new widget, and adds it to the current widget. In
# addition, some calls will also update the current widget, pushing
# the old current widget onto a stack. (For example, boxes and buttons
# all can contain other widgets.) The close function pops things off
# the stack.
# The stack should always be empty when we go to interact with the
# user.
import renpy
# The current widget. (Should never become None.)
current = 'transient'
# A stack of current widgets and/or layers.
current_stack = [ ]
# True if the current widget should be used at most once.
current_once = False
def interact(**kwargs):
"""
Displays the current scene to the user, waits for a widget to indicate
a return value, and returns that value to the user.
Some useful keyword arguments are:
@param show_mouse: Should the mouse be shown during this
interaction? Only advisory, as this doesn't work reliably.
@param suppress_overlay: This suppresses the display of the overlay
during this interaction.
"""
if current_stack:
raise Exception("ui.interact called with non-empty widget/layer stack. Did you forget a ui.close() somewhere?")
return renpy.game.interface.interact(**kwargs)
def add(w, make_current=False, once=False):
"""
Adds a new widget to the current widget. If make_current is true,
then the widget is also made the current widget, with the old
widget being pushed onto a stack.
"""
global current
global current_once
if isinstance(current, str):
renpy.game.context(-1).scene_lists.add(current, w)
else:
current.add(w)
if current_once:
current_once = False
close()
current_once = once
if make_current:
current_stack.append(current)
current = w
return w
def layer(name):
"""
This causes widgets to be added to the named layer, until a
matching call to ui.close().
"""
global current_once
global current
if not isinstance(current, str):
raise Exception("Opening a layer while a widget is open is not allowed.")
if name not in renpy.config.layers:
raise Exception("'%s' is not a known layer." % name)
current_stack.append(current)
current_once = False
current = name
def close():
"""
This closes the currently open widget or layer. If a widget is
closed, then we start adding to its parent, or the layer if no
parent is open. If a layer is closed, we return to the previously
open layer. An error is thrown if we close the last open layer.
"""
global current
if not current_stack:
raise Exception("ui.close() called to close the last open layer or widget.")
if current_once:
raise Exception("ui.close() called when expecting a widget.")
current = current_stack.pop()
def reopen(w, clear):
"""
Reopens a widget, optionally clearing it. This scares me. Don't
document it.
"""
global current
current_stack.append(current)
current = w
if clear:
w.children[:] = [ ]
def null(**properties):
"""
This widget displays nothing on the screen. Why would one want to
do this? If a widget requires contents, but you don't have any
contents to provide it.
"""
return add(renpy.display.layout.Null(**properties))
def text(label, **properties):
"""
This creates a widget displaying a text label.
@param label: The text that will be displayed on the screen.
It uses font properties.
"""
return add(renpy.display.text.Text(label, **properties))
def hbox(padding=0, **properties):
"""
This creates a layout that places widgets next to each other, from
left to right. New widgets are added to this hbox until ui.close()
is called.
@param padding: The number of pixels to leave between widgets.
"""
return add(renpy.display.layout.HBox(padding, **properties), True)
def vbox(padding=0, **properties):
"""
This creates a layout that places widgets next to each other, from
top to bottom. New widgets are added to this vbox until ui.close()
is called.
@param padding: The number of pixels to leave between widgets.
"""
return add(renpy.display.layout.VBox(padding, **properties), True)
def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
"""
This creates a layout that places widgets in an evenly spaced
grid. New widgets are added to this grid unil ui.close() is called.
Widgets are added by going from left to right within a single row,
and down to the start of the next row when a row is full. All cells
must be filled (that is, exactly col * rows widgets must be added to
the grid.)
The children of this widget should have a fixed size that does not
vary based on the space allocated to them. Failure to observe this
restriction could lead to really odd layouts, or things being
rendered off screen.
Each cell of the grid is exactly the same size. By default, the
grid is the smallest size that can accommodate all of its
children, but it can be expanded to consume all available space in
a given dimension by setting xfill or yfill to True, as appropriate.
@param cols: The number of columns in this grid.
@param rows: The number of rows in this grid.
@param padding: The amount of space to leave between rows and columns.
@param xfill: True if the grid should consume all available width.
@param yfill: True if the grid should consume all available height.
"""
return add(renpy.display.layout.Grid(cols, rows, padding, xfill=xfill, yfill=yfill, **properties), True)
def fixed(**properties):
"""
This creates a layout that places widgets at fixed locations
relative to the origin of the enclosing widget. New widgets are
added to this widget.
"""
rv = renpy.display.layout.Fixed(**properties)
add(rv, True)
return rv
def sizer(maxwidth=None, maxheight=None, **properties):
"""
This is a widget that can shrink the size allocated to the next
widget added. If maxwidth or maxheight is not None, then the space
allocated to the child in the appropriate direction is limited to
the given amount.
Please note that this only works with child widgets that can have
a limited area allocated to them (like text), and not with ones
that use a fixed area (like images).
@param maxwidth: The maximum width of the child widget, or None to not affect width.
@param maxheight: The maximum height of the child widget, or None ot not affect height.
"""
return add(renpy.display.layout.Sizer(maxwidth, maxheight, None, **properties),
True, True)
def window(**properties):
"""
A window contains a single widget. It draws that window atop a
background and with appropriate amounts of margin and padding,
taken from the window properties supplied to this call. The next
widget created is added to this window.
"""
return add(renpy.display.layout.Window(None, **properties), True, True)
def keymousebehavior():
"""
This is a psuedo-widget that adds the keymouse behavior to the
screen. The keymouse behavior allows the mouse to be controlled
by the keyboard. This widget should not be added to any other
widget, but should instead be only added to the screen itself.
As of 4.8, this does nothing, but is retained for compatability.
"""
return
def saybehavior():
"""
This is a psuedo-widget that adds the say behavior to the
screen. The say behavior is to return True if the left mouse is
clicked or enter is pressed. It also returns True in various other
cases, such as if the current statement has already been seen. This widget
should not be added to any other widget, but should instead be
only added to the screen itself.
"""
return add(renpy.display.behavior.SayBehavior())
def pausebehavior(delay, result=False):
"""
This is a psuedo-widget that adds the pause behavior to the
screen. The pause behavior is to return the supplied result when
the given number of seconds elapses. This widget should not be
added to any other widget, but should instead be only added to the
screen itself.
Please note that this widget will always pause for the given
amount of time. If you want a pause that can be interrupted by
the user, add in a saybehavior.
@param delay: The amount of time to pause, in seconds.
@param result: The result that will be retuned after the delay time
elapses.
"""
return add(renpy.display.behavior.PauseBehavior(delay, result))
def menu(menuitems,
style = 'menu',
caption_style='menu_caption',
choice_style='menu_choice',
choice_button_style='menu_choice_button',
**properties):
"""
This creates a new menu widget. Unlike the menu statement or
renpy.menu function, this menu widget is not enclosed in any sort
of window. You'd have to do that yourself, if it is desired.
@param menuitems: A list of tuples that are the items to be added
to this menu. The first element of a tuple is a string that is
used for this menuitem. The second element is the value to be
returned from ui.interact() if this item is selected, or None
if this item is a non-selectable caption.
"""
# menu is now a conglomeration of other widgets. And bully for it.
renpy.ui.vbox(style=style, **properties)
for label, val in menuitems:
if val is None:
renpy.ui.text(label, style=caption_style)
else:
renpy.ui.textbutton(label,
style=choice_button_style,
text_style=choice_style,
clicked=renpy.ui.returns(val))
renpy.ui.close()
# return add(renpy.display.behavior.Menu(menuitems, **properties))
def input(default, length=None, allow=None, exclude='{}', **properties):
"""
This creats a new input widget. This widget accepts textual input
until the user hits enter, and then returns that text.
@param default: The default text that fills the input.
@param length: If set, the maximum number of characters that will be
returned by this input.
@param allow: If not None, then if an input character is not in this
string, it is ignored.
@param exclude: If not None, then if an input character is in this
set, it is ignored.
"""
return add(renpy.display.behavior.Input(default, length=length, allow=allow, exclude=exclude, **properties))
def image(filename, **properties):
"""
This loads an image from the given file, and displays it as a
widget.
"""
return add(renpy.display.image.Image(filename, **properties))
def imagemap(ground, selected, hotspots, unselected=None,
style='imagemap', button_style='imagemap_button',
**properties):
"""
This is called to create imagemaps. Parameters are
roughtly the same as renpy.imagemap. The value of the hotspot is
returned when ui.interact() returns.
"""
rv = fixed(style=style, **properties)
if not unselected:
unselected = ground
image(ground)
for x0, y0, x1, y1, result in hotspots:
imagebutton(renpy.display.im.Crop(unselected, x0, y0, x1 - x0, y1 - y0),
renpy.display.im.Crop(selected, x0, y0, x1 - x0, y1 - y0),
clicked=returns(result),
style=button_style,
xpos=x0, xanchor='left',
ypos=y0, yanchor='top',
)
close()
return rv
def button(clicked=None, **properties):
"""
This creates a button that can be clicked by the user. When this
button is clicked or otherwise selected, the function supplied as
the clicked argument is called. If it returns a value, that value
is returned from ui.interact().
Buttons created with this function contain another widget,
specifically the next widget to be added. As a convenience, one
can use ui.textbutton to create a button with a text label.
@param clicked: A function that is called when this button is
clicked.
"""
return add(renpy.display.behavior.Button(None, clicked=clicked,
**properties), True, True)
def textbutton(text, clicked=None, text_style='button_text', **properties):
"""
This creates a button that is labelled with some text. When the
button is clicked or otherwise selected, the function supplied as
the clicked argument is called. If it returns a value, that value
is returned from ui.interact().
@param text: The text of this button.
@param clicked: A function that is called when this button is
clicked.
@param text_style: The style that is used for button text.
"""
return add(renpy.display.behavior.TextButton(text, clicked=clicked,
text_style=text_style,
**properties))
def imagebutton(idle_image, hover_image, clicked=None,
image_style='image_button_image', **properties):
"""
This creates a button that contains two images. The first is the
idle image, which is used when the mouse is not over the image,
while the second is the hover image, which is used when the mouse
is over the image. If the button is clicked or otherwise selected,
then the clicked argument is called. If it returns a value, that
value is returned from ui.interact().
@param idle_image: The file name of the image used when this
button is idle.
@param hover_image: The file name of the image used when this
button is hovered.
@param clicked: The function that is called when this button is
clicked.
@param image_style: The style that is applied to the images that
are used as part of the imagebutton.
"""
return add(renpy.display.image.ImageButton(idle_image,
hover_image,
clicked=clicked,
image_style=image_style,
**properties))
def bar(width, height, range, value, clicked=None, **properties):
"""
This creates a bar widget. The bar widget can be used to display data
in a bar graph format, and optionally to report when the user clicks on
a location in that bar.
@param width: The width of the bar. If clicked is set, this includes
the gutters on either side of the bar.
@param height: The height of the bar.
@param range: The range of values this bar can undertake. The bar
is completely full when its value is this number.
@param value: The value of this bar. It must be between 0 and range,
inclusive.
@clicked clicked: This is called when the mouse is clicked in this
widget. It is called with a single argument, which is the value
corresponding to the location at which the mouse button was clicked.
If this function returns a value, that value is returned from
ui.interact().
For best results, if clicked is set then width should be at least
twice as big as range.
"""
return add(renpy.display.behavior.Bar(width, height, range, value,
clicked=clicked, **properties))
def conditional(condition):
"""
This contains a conditional widget, a one-child widget that only
displays its child if a condition is true.
The condition MUST NOT change the game state in any way, as it is
not protected against rollback.
"""
return add(renpy.display.behavior.Conditional(condition), True, True)
def _returns(v):
"""
This function returns a function that returns the supplied
value. It's best used as the clicked argument of the button
functions.
"""
return v
returns = renpy.curry.curry(_returns)
def _jumps(label):
"""
This function returns a function that, when called, causes the
game to jump to the supplied label. It's best used as the clicked
argument of the button functions.
"""
raise renpy.game.JumpException(label)
jumps = renpy.curry.curry(_jumps)
-1
View File
@@ -1 +0,0 @@
run_game.py --game demo2
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Executable → Regular
+9 -106
View File
@@ -1,69 +1,29 @@
#!/usr/bin/env python
import os.path
# Enable psyco. Warning: Check for memory leaks!
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.
import encodings.utf_8
import encodings.zlib_codec
import encodings.unicode_escape
import encodings.string_escape
import encodings.raw_unicode_escape
# Load up all of Ren'Py, in the right order.
import renpy
def main():
name = os.path.basename(sys.argv[0])
dirname = os.path.dirname(sys.argv[0])
# The version of Ren'Py in use.
version = 'Renpy 4.2'
if dirname:
os.chdir(dirname)
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"
if __name__ == "__main__":
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,
help='Run the argument in the python interpreter.')
op.add_option('--leak', dest='leak', action='store_true', default=False,
help='When the game exits, dumps a profile of memory usage.')
options, args = op.parse_args()
if options.python:
execfile(options.python)
sys.exit(0)
try:
renpy.main.main(options.game)
@@ -71,32 +31,12 @@ def 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
type, value, tb = sys.exc_info()
print >>f, type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print >>f
print >>f, renpy.game.exception_info
print >>f
print >>f, "-- Full Traceback ------------------------------------------------------------"
print >>f
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
print >>f, type.__name__ + ":",
print type.__name__ + ":",
print >>f, unicode(e).encode('utf-8')
print unicode(e).encode('utf-8')
traceback.print_exc(None, sys.stdout)
traceback.print_exc(None, f)
print
print >>f
@@ -105,7 +45,7 @@ def main():
print >>f, renpy.game.exception_info
print >>f
print >>f, "Ren'Py Version:", renpy.version
print >>f, "Ren'Py Version:", version
f.close()
@@ -113,45 +53,8 @@ def main():
os.startfile('traceback.txt')
except:
pass
if options.leak:
memory_profile()
sys.exit(0)
def memory_profile():
print "Memory Profile"
print
print "Showing all objects in memory at program termination."
print
import gc
gc.collect()
objs = gc.get_objects()
c = { } # count
dead_renders = 0
for i in objs:
t = type(i)
c[t] = c.get(t, 0) + 1
if isinstance(i, renpy.display.render.Render):
if i.dead:
dead_renders += 1
results = [ (count, ty) for ty, count in c.iteritems() ]
results.sort()
for count, ty in results:
print count, str(ty)
if dead_renders:
print
print "*** found", dead_renders, "dead Renders. ***"
if __name__ == "__main__":
main()
-1
View File
@@ -1 +0,0 @@
run_game.py
-98
View File
@@ -1,98 +0,0 @@
#!/usr/bin/env python
# Go psyco! (Compile where we can.)
# try:
# import psyco
# psyco.full()
#except ImportError:
# pass
import codecs
import optparse
import traceback
import os
import os.path
import re
import sys
# Extra things used for distribution.
import encodings.utf_8
import encodings.zlib_codec
# Load up all of Ren'Py, in the right order.
import renpy
if __name__ == "__main__":
# Stdout should be a utf-8 stream, so print works nicely.
# utf8writer = codecs.getwriter("utf-8")
# sys.stdout = utf8writer(sys.stdout)
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,
help='The directory the game is in.')
op.add_option('--python', dest='python', default=None,
help='Run the argument in the python interpreter.')
options, args = op.parse_args()
if options.python:
execfile(options.python)
sys.exit(0)
try:
renpy.main.main(options.game)
except Exception, e:
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
type, value, tb = sys.exc_info()
traceback.print_tb(tb, None, sys.stdout)
traceback.print_tb(tb, None, f)
print >>f, unicode(e).encode('utf-8')
print unicode(e).encode('utf-8')
print
print >>f
print renpy.game.exception_info
print >>f, renpy.game.exception_info
print >>f
print >>f, "Ren'Py Version:", renpy.version
f.close()
try:
os.startfile('traceback.txt')
except:
pass
sys.exit(0)
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/python
import hotshot, hotshot.stats
import sys
import renpy
prof = hotshot.Profile("renpy.prof")
prof.runcall(renpy.main.main, sys.argv[1])
# prof.run(file("run_game.py").read())
prof.close()
print "Profiling in progress..."
stats = hotshot.stats.load("renpy.prof")
stats.strip_dirs()
stats.sort_stats('time', 'calls')
stats.print_stats(40)
-1
View File
@@ -1 +0,0 @@
run_game.py
-17
View File
@@ -1,17 +0,0 @@
import renpy
nodes = [ ]
def got_node(n):
nodes.append(n)
if isinstance(n, renpy.ast.Say):
print n.what
renpy.config.searchpath = [ 'game' ]
renpy.script.Script(got_node)
print "Script consists of %d nodes." % len(nodes)
print "Script consists of %d say nodes." % len([ n for n in nodes if isinstance(n, renpy.ast.Say)])
print "Script consists of %d menu nodes." % len([ n for n in nodes if isinstance(n, renpy.ast.Menu)])
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Some files were not shown because too many files have changed in this diff Show More