Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ea9d436ce | |||
| c05e4e528e | |||
| e10488b77a | |||
| 86e0cb3331 | |||
| c6397c08af | |||
| f9db64231d | |||
| f3c3c67def | |||
| 6d316a9c78 | |||
| 6e7e68b31a | |||
| a24e6b2af1 | |||
| 597b01c26b | |||
| a262eb0f60 | |||
| 92006d237c | |||
| 1427336746 | |||
| c35ed8a7cd | |||
| 90f47eacf3 | |||
| ee093925f5 | |||
| f686a810a7 | |||
| e2a12284d5 | |||
| 10b4064a53 | |||
| f1ea5289aa | |||
| a26ed9c328 |
@@ -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)
|
||||
|
||||
@@ -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/
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Have a new form of imagemap that uses a color image to distinguish
|
||||
hotspots.
|
||||
|
||||
Keyd compiles.
|
||||
@@ -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,3 +1,2 @@
|
||||
cd game
|
||||
cd images
|
||||
..\archiver.exe images *.png *.jpg
|
||||
pause
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
build_exe.py run_game
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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 $
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -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
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 94 KiB |
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,44 +0,0 @@
|
||||
import xml.dom.minidom
|
||||
import sys
|
||||
|
||||
def escape(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace('"', """)
|
||||
|
||||
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()
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -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"
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
../../extras/fullscreen.rpy
|
||||
@@ -1 +0,0 @@
|
||||
../../extras/kanamode.rpy
|
||||
@@ -1 +0,0 @@
|
||||
../script.rpy
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
run_game.py --game demo2
|
||||
|
Before Width: | Height: | Size: 7.2 KiB |
@@ -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 +0,0 @@
|
||||
run_game.py
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
run_game.py
|
||||
@@ -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)])
|
||||
|
Before Width: | Height: | Size: 72 KiB |