Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b51312b21d | |||
| ab5d5c0e40 | |||
| c28639ef0b | |||
| a3b3d26816 | |||
| ee6bbf5d9c | |||
| 6e406cdae0 | |||
| 8ef2d97247 | |||
| e9be405aaf | |||
| 23dbab98d2 | |||
| 2865599e34 | |||
| fb5957186a | |||
| 8d2e592589 | |||
| c7714a69fd | |||
| 6d2dce1704 | |||
| 8ab32e8dba | |||
| 00ee770512 | |||
| c5761728b3 | |||
| d33aa31101 | |||
| 34cfc4fa40 | |||
| d603d62680 | |||
| 4991c5761c | |||
| cf3f8afd9f | |||
| 31fc8b213e | |||
| 74be98faba | |||
| b4e6b24a86 | |||
| 7c1980775e | |||
| 90d586968e | |||
| af0778b8bc | |||
| 23bccf06cb | |||
| 7558b20b1e | |||
| 8fbb785674 | |||
| d2210cf08f | |||
| 8d8ba4fb00 | |||
| 915df21775 | |||
| 59c7baf1ff | |||
| dc0d2f8039 | |||
| bcf752d3a6 | |||
| d95cd1f6a0 | |||
| c9f025dab2 | |||
| d484cc2906 | |||
| 18cf867432 | |||
| 149a2904a5 | |||
| ef2e78616f | |||
| 2711f79080 | |||
| fa31e03d06 | |||
| 6a27954677 | |||
| eab1edd5b4 | |||
| b9624fe922 | |||
| 69727f3baf | |||
| 0551eb6135 | |||
| c0b8ca8777 | |||
| 7af2e3d066 | |||
| 0a857f8879 | |||
| 839073ad8c |
+742
@@ -1,3 +1,745 @@
|
||||
New in Ren'Py 4.8.7
|
||||
-------------------
|
||||
|
||||
The new DSE dating-sim engine code is now present in the dse/
|
||||
directory. It's not documented, apart from the comments in the source
|
||||
code, but it can be run by running the new run_dse program.
|
||||
|
||||
The image statement now passes its argument to the Image function,
|
||||
in loose mode. This means that strings and tuples are turned into
|
||||
single and composited images, respectively. So it's now possible to
|
||||
write:
|
||||
|
||||
init:
|
||||
image control room = "control_room.jpg"
|
||||
|
||||
instead of having to write:
|
||||
|
||||
init:
|
||||
image control room = Image("control_room.jpg")
|
||||
|
||||
Of course, the old way still works.
|
||||
|
||||
There is now a new transition type, ImageDissolve. It lets us creat
|
||||
dissolve transitions in which pixels are dissolved at different times,
|
||||
which can be used for a range of effects. Two such effects are
|
||||
'squares' and 'blinds'. A number of other types are defined in the
|
||||
source of the demo game, in their own section.
|
||||
|
||||
A new im.Tile image operator has been introduced. This operator tiles
|
||||
a source image until it reaches a specified size, or the size of the
|
||||
screen if no size is specified.
|
||||
|
||||
Ren'Py now supports a presplash screen. If the file presplash.png is
|
||||
present in the game directory, it is shown to the user on
|
||||
startup. This occurs before any loading or initialization occurs, so
|
||||
the size of the window it is shown in is determined by the size of the
|
||||
presplash.png image itself.
|
||||
|
||||
As per user request, we have made programmatic equivalents of the
|
||||
image, scene, show, and hide statements available. These functions are
|
||||
named renpy.image, renpy.scene, renpy.show, and renpy.hide,
|
||||
respectively.
|
||||
|
||||
Ren'Py now supports a new classification of layer, top layers. Top
|
||||
layers are layers that are displayed above any transition. This is
|
||||
useful if you want to have an overlay that is shown even when a
|
||||
transition is in progress. To have the overlays be displayed as a
|
||||
top_layer, remove the string 'overlay' from config.layers and append
|
||||
it to config.top_layers.
|
||||
|
||||
A new config variable, config.enable_fast_dissolve, was
|
||||
introduced. Setting this to False can fix a potential bug in the
|
||||
dissolve transition when used with an overlay layer that has an alpha
|
||||
channel that is not fully transparent or opaque, at the cost of 25% of
|
||||
the performance of dissolve. It usually can be kept at True with no
|
||||
ill effects.
|
||||
|
||||
Fade has been reimplemented in terms of Dissolve. This means that
|
||||
fades will be as fast as dissolves. Fade now also can take a widget,
|
||||
instead of a color, as an argument. If this widget is an Image, it
|
||||
means that we will dissolve from the old screen to the image, hold at
|
||||
the image for a given amount of time, and then dissolve from the image
|
||||
to the new screen.
|
||||
|
||||
The ImageReference class is now documented and present in the game's
|
||||
namespace. This lets us refer to an image by name from user code.
|
||||
|
||||
Added a RENPY_DISABLE_SOUND environment variable, which if set
|
||||
disables support for sound in Ren'Py. (For use on platforms where
|
||||
sound doesn't quite work.)
|
||||
|
||||
Fixed a bug in lint.
|
||||
|
||||
Fixed bugs that caused overlays to jump in and out during transitions,
|
||||
restoring the pre-4.8 behavior.
|
||||
|
||||
The definitions found in common/definitions.rpy are now documented in
|
||||
the reference. This gives a default set of transitions and positions,
|
||||
as well as an image (black).
|
||||
|
||||
|
||||
New in 4.8.6
|
||||
------------
|
||||
|
||||
Ren'Py now takes a new option, --lint. (Or for those of you under
|
||||
Windows, run lint.bat, editing it if necessary.) This option, when
|
||||
supplied, causes Ren'Py to run checks for dozens of errors in the
|
||||
script. Many of these checks test for problems that will only occur
|
||||
at runtime, on platforms other than Windows, or when loading a
|
||||
save-game after a change in script. This is the subtle stuff that my
|
||||
script-review service was for... now it's automated, so you don't need
|
||||
me anymore.
|
||||
|
||||
Lint should be run before you release any game with Ren'Py, from now
|
||||
on. In general, you should not release a game if lint gives you any
|
||||
errors, but instead you should fix those errors first.
|
||||
|
||||
Lint also produces a count of the number of say statments and menus in
|
||||
a program.
|
||||
|
||||
DynamicCharacter and Character have been unified into a single class,
|
||||
such that now calling DynamicCharacter is equivalent to calling
|
||||
Character with dynamic=True.
|
||||
|
||||
Character (and DynamicCharacter, and renpy.display_say) now take a new
|
||||
argument, image. If this argument is set to True, then the name of the
|
||||
character is interpreted as an image filename. This image is used
|
||||
where the character's name would go, in place of a textual name.
|
||||
|
||||
Fixed a bug that prevented newlines from being recognized in text
|
||||
widgets. (Introduced when I rewrote tokenizing in the previous
|
||||
release.)
|
||||
|
||||
Fixed a bug in the _renpy module, in which we accidentally used the
|
||||
height, rather than the width, of an image.
|
||||
|
||||
New in 4.8.5
|
||||
------------
|
||||
|
||||
Added a new state-machine based animation function, anim.SMAnimation.
|
||||
This function creates animations by applying images and transitions
|
||||
specified by the states and edges of a nondeterminstic state
|
||||
machine. The result is that it's possible to specify complicated
|
||||
random patterns of behavior. This can allow a character to blink
|
||||
randomly, allow random backgrounds to be shown, and even allow things
|
||||
to randomly move around the screen.
|
||||
|
||||
The SMAnimation framework can also be used to describe very
|
||||
complicated, even nondeterministic, motions. This is done by declaring
|
||||
a SMAnimation with None for the images, and then passing it in to the
|
||||
at clause.
|
||||
|
||||
I should point out that despite the complexity of the interface, the S
|
||||
and M in SMAnimation stand for state machine, and not anything
|
||||
else. Honest.
|
||||
|
||||
All motions (Motion, Pan, Move, and anim.SMAnimation when used as a
|
||||
motion) can now be used as transitions, in which case the motion is
|
||||
applied to the screen (or layer, as appropriate). This let us put
|
||||
together two new transitions, hpunch and vpunch, which shake the
|
||||
screen horizontally and vertically, as appropriate. Useful for when
|
||||
the POV character gets punched by a particularly feisty girl.
|
||||
|
||||
There are now two new image operators. im.Map can map the pixels
|
||||
values in an image to different values. Using this, one can adjust the
|
||||
colors of an image. The im.Alpha function is built on top of this, and
|
||||
allows one to alter the alpha of an image. (It even adjusts a
|
||||
pre-existing alpha channel.)
|
||||
|
||||
The definitions of transitions and placements, which were once in
|
||||
script.rpy, have been moved into common/definitions.rpy.
|
||||
|
||||
There is now a new config variable, config.text_tokenizer. This
|
||||
variable is used to give a function that breaks a line of text up into
|
||||
words, spaces, and other things. Providing a custom function here
|
||||
allows for control of where line breaking is allowed, useful for
|
||||
languages that have different rules about that.
|
||||
|
||||
Fixed the gallery.rpy extra, so that it now works properly with the
|
||||
main menu being in a menu context.
|
||||
|
||||
The demo game was updated to demonstrate the new feature, as well as
|
||||
to have a new, drop-shadowed window frame.
|
||||
|
||||
New in 4.8.4
|
||||
------------
|
||||
|
||||
The format of the compiled script (.rpyc) files has changed. The new
|
||||
format is more efficent, both in terms of space and memory.
|
||||
Unfortunately, the format change is not backwards compatible,
|
||||
and the automatic upgrade function probably won't work. So you'll need
|
||||
to delete the rpyc files from the game directory when you copy it
|
||||
over.
|
||||
|
||||
Removed some unnecessary conversion from pixellate, improving its
|
||||
performance when running on a computer with 32 bits per pixel
|
||||
graphics.
|
||||
|
||||
The xanchor and yanchor arguments can now take numbers between 0 and 1
|
||||
as well as names. Functions returned by Motion now can either return
|
||||
an xpos, ypos tuple or a xpos, ypos, xanchor, yanchor tuple. Move has
|
||||
been upgraded so that it can take similar tuples as the start and
|
||||
end arguments, letting us interpolate the location of the anchors
|
||||
while moving an image around.
|
||||
|
||||
The above modification allowed us to implement a new move
|
||||
transition. This transition attempts to find images that are present
|
||||
in both the old and new scenes, but have changed location between the
|
||||
two scenes. Such images are moved from the old location to the new
|
||||
location over the duration of the move transition. This makes it easy
|
||||
to smoothly move characters from one location to another on the
|
||||
screen.
|
||||
|
||||
The demo script was updated and reorganized, to demonstrate more
|
||||
features. Now, all transitions are demonstrated, as well as things
|
||||
like renpy.input, DynamicCharacter, Move, and Pan.
|
||||
|
||||
A slightly updated version of the script for Moonlight Walks is now
|
||||
in the scripts/ directory. It's there as an example for people to
|
||||
learn from. This script has been updated to work with Ren'Py 4.8, and
|
||||
to fix a tense error.
|
||||
|
||||
Fixed another segfault in windows native midi support.
|
||||
|
||||
|
||||
New in 4.8.3
|
||||
------------
|
||||
|
||||
Ren'Py no longer uses the psyco specializing compiler. The relatively
|
||||
small (about 2%) speed increase it gave wasn't worth the hassle
|
||||
involved in dealing with memory leaks and other semantic changes. This
|
||||
change significantly lowers Ren'Py's memory usage, and fixes a major
|
||||
memory leak that was introduced in the 4.8 series.
|
||||
|
||||
A number of race conditions were fixed in the windows native midi
|
||||
code. The upshot of this is that we can now play midi on windows
|
||||
without there being a small chance of crashing each time we start a
|
||||
new track.
|
||||
|
||||
The config.music_interact variable is gone. It's been replaced by
|
||||
config.interact_callbacks, which is a list of functions to call before
|
||||
each interaction. (This will help us support voice.)
|
||||
|
||||
A new Motion function allows the user to give a function that controls
|
||||
how a displayable moves on the screen, allowing for customizable
|
||||
motions. Pan and Move have been reimplemented in terms of Motion. All
|
||||
three functions now take repeat and bounce parameters. Repeat causes
|
||||
the motion to repeat after a single period is over, while bounce
|
||||
causes the motion to bounce from extreme to extreme.
|
||||
|
||||
Finally, a new voice extra has been added as extras/voice.rpy. This is
|
||||
a simplified version of what will eventually be Ren'Py's voice
|
||||
system. Right now, the only feature missing is that voices for
|
||||
characters cannot be enabled or disabled individually, but may
|
||||
instead only be manipulated in the aggregate.
|
||||
|
||||
|
||||
New in 4.8.2a
|
||||
-------------
|
||||
|
||||
Clicking preferences on the main menu jumpled to an undefined
|
||||
label. This is now fixed.
|
||||
|
||||
A new extra has been added. The commentary.rpy extra adds an optional
|
||||
commentary window, which is shown only when the commentary preference
|
||||
it adds is enabled.
|
||||
|
||||
To support the above, statements are only marked as seen if they have
|
||||
actually been shown to the user (by ui.interact being called during
|
||||
the statement.)
|
||||
|
||||
|
||||
New in 4.8.2
|
||||
------------
|
||||
|
||||
Unicode support in python blocks now works. This allows unicode
|
||||
strings to be used with Ren'Py. This is especially useful when naming
|
||||
characters and translating the various menus into your native
|
||||
language. Please note that the only unicode allowed is inside unicode
|
||||
strings, which begin with a u before the first quote. Unicode outside
|
||||
of unicode strings is an error.
|
||||
|
||||
We apologize to the non-english-speaking world for not noticing this
|
||||
one sooner.
|
||||
|
||||
The main menu was moved out of the context that the main body of the
|
||||
game code executes in, into its own context. The only effect this
|
||||
should have on user code is that some of the menu options changed from
|
||||
simple label names into more complicated jumps. But it does allow us
|
||||
to share the main menu with the game menu, which is something I've
|
||||
had requests for.
|
||||
|
||||
A number of minor changes were made to the menu at this time.
|
||||
|
||||
The README_RENPY.txt file was updated, to make it more current and
|
||||
useful.
|
||||
|
||||
The Character and DynamicCharacter objects were updated to take a
|
||||
condition argument. If this argument does not evaluate to true when a
|
||||
line of dialogue is executed, the line is not displayed to the user.
|
||||
|
||||
A new extra was added (in two_window_say.rpy) that places the name of
|
||||
a speaking character, and what they are saying, in two windows
|
||||
that can be styled separately.
|
||||
|
||||
The biggest change in Ren'Py was the addition of the _renpy
|
||||
module. This is a C module that enhances the functionality of pygame,
|
||||
allowing us to provide transitions and apply effects that simply
|
||||
weren't possible before. A compiled version ships with the windows
|
||||
binaries, installers are available for windows and mac from the Ren'PY
|
||||
website, and source is included in the module/ directory to compile it
|
||||
under Linux and Unix.
|
||||
|
||||
The presence of the _renpy module allows for two new features. The
|
||||
first is that the thumbnails of save images are now smoothly scaled
|
||||
down, which improves their look quite a bit.
|
||||
|
||||
The second new feature is the new pixellate transition, which
|
||||
pixellates the old screen, swaps pixellated screens, and then
|
||||
depixellates the new screen. One can see this feature in action by
|
||||
right clicking to access the game menu... for this release, we've set
|
||||
the game menu transition to be pixellate.
|
||||
|
||||
|
||||
New in 4.8.1
|
||||
------------
|
||||
|
||||
Character objects are no longer limited to displaying through
|
||||
renpy.display_say, but can now be supplied a function argument, which
|
||||
is a function that is called instead. This will make it easier to
|
||||
radically change how dialogue is displayed.
|
||||
|
||||
The ui.grid object now has a new argument, transpose, which allows
|
||||
widgets to be added down the columns first. It also is smarter about
|
||||
allocating space to subwidgets when xfill or yfill are set.
|
||||
|
||||
The file entries in the load and save screens are now arraigned in a
|
||||
grid, which has the style file_picker_grid. This means that we should
|
||||
now be smarter in the presence of games of varying size, and we should
|
||||
wrap text if it gets too long. As part of this,
|
||||
library.file_page_length has now been broken out into two variables,
|
||||
library.file_page_rows and library.file_page_cols.
|
||||
|
||||
renpy.music_stop now supports a fadeout parameter, for orthogonality
|
||||
with the new renpy.music_start.
|
||||
|
||||
Now, python blocks have the correct filename and line numbers
|
||||
annotated onto them. This means that tracebacks generated in python
|
||||
blocks will show a .rpy file and the appropriate line, rather than
|
||||
showing "<none>" and a random line as they did before.
|
||||
|
||||
There are two new style properties, xmaximum and ymaximum. These two
|
||||
properties limit the size of the widgets that they are applied to, as
|
||||
if that widget was placed inside a ui.sizer(). (In fact, ui.sizer() is
|
||||
now implemented in terms of these properties.)
|
||||
|
||||
Now, we only restore or rollback to places where all of the transient
|
||||
layers are empty. This prevents a bug in which additional dialogue
|
||||
will be lost when a restore or rollback occurs.
|
||||
|
||||
The way in which preferences are displayed has been made more
|
||||
configurable. Now, there is a new variable, library.preferences, which
|
||||
is a map from a style to a list of preferences to be displayed in a
|
||||
vbox with that style. There are now two new styles (prefs_left and
|
||||
prefs_right) which control the placement of the default preference
|
||||
columns.
|
||||
|
||||
There is a new spinner preference type, which allows a value to be
|
||||
incremented or decremented. This preference is now used to adjust the
|
||||
speed at which text is shown to the user. So now this is controlled by
|
||||
the user, rather than the config.annoying_text_cps parameter. If you
|
||||
don't want to give the user this control, you can set library.has_cps
|
||||
to False. A number of new styles define the spinner.
|
||||
|
||||
An empty string is now rendered as if it was a string consisting of a
|
||||
single space. This fixes a bug in which an empty string was rendered
|
||||
with zero width and height.
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
|
||||
+31
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2004-2005 Tom Rothamel <pytom@bishoujo.us>
|
||||
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
|
||||
@@ -20,3 +20,33 @@ 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/
|
||||
|
||||
|
||||
The character images in the demo game (game/9a_*.png) are under their
|
||||
own copyright. I allow them to be used as part of the demo game, but
|
||||
not as part of other games.
|
||||
|
||||
+45
-32
@@ -1,42 +1,55 @@
|
||||
Greetings!
|
||||
This file contains boilerplate information about running Ren'Py that
|
||||
can be included into the README for your game. It may be necessary to
|
||||
change some of the filenames in this document, to make it specific to
|
||||
your game.
|
||||
|
||||
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:
|
||||
Feel free to use this in your projects.
|
||||
|
||||
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.
|
||||
Running the Game
|
||||
================
|
||||
|
||||
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.
|
||||
(Windows)
|
||||
|
||||
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:
|
||||
If this game was installed on Windows using an installer, then you can
|
||||
run it by choosing the shortcut left by the installer. If this game
|
||||
was installed on Windows using the cross-platform zip file, then it
|
||||
can be run by executing the run_game.exe file.
|
||||
|
||||
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.
|
||||
(Macintosh)
|
||||
|
||||
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.
|
||||
For information about running Ren'Py games on Mac OS 10.3 or higher,
|
||||
please go to http://www.bishoujo.us/renpy/mac.html .
|
||||
|
||||
Thank you for choosing to play a Ren'Py powered game.
|
||||
(Linux/Unix)
|
||||
|
||||
For information about running Ren'Py games under Linux and Unix,
|
||||
please go to http://www.bishoujo.us/renpy/linux.html . At the very
|
||||
least, you will need to compile the _renpy module. Read
|
||||
module/README.txt for details.
|
||||
|
||||
|
||||
Playing the Game
|
||||
================
|
||||
|
||||
By default, the game starts running in full screen mode. On some
|
||||
computers, especially some virtual machines, this can lead to mouse
|
||||
problems. To fix this, press 'f'.
|
||||
|
||||
Some of the more interesting game actions can be performed as follows:
|
||||
|
||||
- The left mouse button is used to advance to the next line of
|
||||
dialogue, or to pick menu options.
|
||||
|
||||
- The right mouse button brings you into a menu screen where you can
|
||||
save the game, load the game, change preferences, return to the main
|
||||
menu, or quit entirely.
|
||||
|
||||
- Scrolling the mouse wheel up or pushing page up returns you to the
|
||||
previous screen.
|
||||
|
||||
- Holding down the CTRL key skips dialogue. By default, it only
|
||||
skips read dialogue, but this can be changed by a preference. Tab
|
||||
toggles skip mode.
|
||||
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
Have a new form of imagemap that uses a color image to distinguish hotspots.
|
||||
Have a new form of imagemap that uses a color image to distinguish
|
||||
hotspots.
|
||||
|
||||
Keyd compiles.
|
||||
+1
-1
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=programs,
|
||||
console=[ "archiver.py", "add_from.py", "console.py" ],
|
||||
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py", "run_dse.py" ],
|
||||
zipfile='lib/renpy.zip',
|
||||
)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# This file contains a number of definitions of standard
|
||||
# locations and transitions. We've moved them into the common
|
||||
# directory so that it's easy for an updated version of all of these
|
||||
# definitions.
|
||||
|
||||
init -1:
|
||||
|
||||
# Positions ##############################################################
|
||||
|
||||
# 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')
|
||||
$ center = Position()
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
|
||||
# Offscreen positions for use with the move transition. Images at
|
||||
# these positions are still shown (and consume
|
||||
# resources)... remember to hide the image after the transition.
|
||||
$ offscreenleft = Position(xpos=0.0, xanchor='right')
|
||||
$ offscreenright = Position(xpos=1.0, xanchor='left')
|
||||
|
||||
# Transitions ############################################################
|
||||
|
||||
# Simple transitions.
|
||||
$ fade = Fade(.5, 0, .5) # Fade to black and back.
|
||||
$ dissolve = Dissolve(0.5)
|
||||
$ pixellate = Pixellate(1.0, 5)
|
||||
|
||||
# Various uses of CropMove.
|
||||
$ 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")
|
||||
|
||||
# This moves changed images to their new locations
|
||||
$ move = MoveTransition(0.5)
|
||||
|
||||
# These shake the screen up and down for a quarter second.
|
||||
$ vpunch = Move((0, 10), (0, -10), .10, bounce=True, repeat=True, delay=.25)
|
||||
$ hpunch = Move((15, 0), (-15, 0), .10, bounce=True, repeat=True, delay=.25)
|
||||
|
||||
# These use the ImageDissolve to do some nifty effects.
|
||||
$ blinds = ImageDissolve(im.Tile("blindstile.png"), 1.0, 8)
|
||||
$ squares = ImageDissolve(im.Tile("squarestile.png"), 1.0, 256)
|
||||
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
# This file contains code for the game menu, and associated
|
||||
# functionality that looks an awful lot like it.
|
||||
|
||||
init -499:
|
||||
python:
|
||||
|
||||
######################################################################
|
||||
# First up, we define a bunch of configuration variable, which the
|
||||
# user can change.
|
||||
|
||||
# The contents of the game menu choices.
|
||||
library.game_menu = [
|
||||
( "return", "Return", ui.jumps("_return"), 'True'),
|
||||
( "prefs", "Preferences", ui.jumps("_prefs_screen"), 'True' ),
|
||||
( "save", "Save Game", ui.jumps("_save_screen"), 'not renpy.context().main_menu' ),
|
||||
( "load", "Load Game", ui.jumps("_load_screen"), 'True'),
|
||||
( "mainmenu", "Main Menu", lambda : _mainmenu_prompt(), 'not renpy.context().main_menu' ),
|
||||
( "quit", "Quit", lambda : _quit_prompt("quit"), 'True' ),
|
||||
]
|
||||
|
||||
# The number of columns of files to show at once.
|
||||
library.file_page_cols = 2
|
||||
|
||||
# The number of rows of files to show at once.
|
||||
library.file_page_rows = 5
|
||||
|
||||
# The number of pages to add quick access buttons for.
|
||||
library.file_quick_access_pages = 5
|
||||
|
||||
# A small amount of padding.
|
||||
library.padding = 2
|
||||
|
||||
# The width of a thumbnail.
|
||||
library.thumbnail_width = 66
|
||||
|
||||
# The height of a thumbnail.
|
||||
library.thumbnail_height = 50
|
||||
|
||||
# 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
|
||||
|
||||
# This lets us disable the file pager. (So we only have one
|
||||
# page of files.)
|
||||
library.disable_file_pager = False
|
||||
|
||||
# This lets us disable the thumbnails in the file pager.
|
||||
library.disable_thumbnails = False
|
||||
|
||||
# If True, we will be prompted before loading a game. (This can
|
||||
# be changed from inside the game code, so that one can load from
|
||||
# the first few screens but not after that.)
|
||||
_load_prompt = True
|
||||
|
||||
######################################################################
|
||||
# Next, support code.
|
||||
|
||||
# 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.
|
||||
#
|
||||
# This can be overridden by user code. It's called with the
|
||||
# name of the selected screen... one of "mainmenu", "prefs",
|
||||
# "save", "load", or "quit", at least for the default game
|
||||
# menu. If None, then it's an indication that none of the
|
||||
# nav buttons should be shown.
|
||||
def _game_nav(screen, buttons=True):
|
||||
|
||||
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
|
||||
ui.add(renpy.Keymap(game_menu=ui.jumps("_noisy_return")))
|
||||
|
||||
ui.window(style='gm_root_window')
|
||||
ui.null()
|
||||
|
||||
if not screen:
|
||||
return
|
||||
|
||||
ui.window(style='gm_nav_window')
|
||||
ui.vbox(focus='gm_nav')
|
||||
|
||||
for key, label, clicked, enabled in library.game_menu:
|
||||
|
||||
disabled = False
|
||||
|
||||
if not eval(enabled):
|
||||
disabled = True
|
||||
clicked = None
|
||||
|
||||
_button_factory(label, "gm_nav",
|
||||
selected=(key==screen),
|
||||
disabled=disabled,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
# This is called from the game menu to interact with the
|
||||
# user. It suppresses all of the underlays and overlays.
|
||||
def _game_interact():
|
||||
|
||||
return ui.interact(suppress_underlay=True,
|
||||
suppress_overlay=True)
|
||||
|
||||
# This renders an empty slot in the file picker.
|
||||
def _render_new_slot(name, save):
|
||||
|
||||
if save:
|
||||
clicked=ui.returns(("return", (name, False)))
|
||||
enable_hover = True
|
||||
else:
|
||||
clicked = None
|
||||
enable_hover = True
|
||||
|
||||
ui.button(style='file_picker_entry',
|
||||
clicked=clicked,
|
||||
enable_hover=enable_hover)
|
||||
|
||||
ui.hbox(padding=library.padding)
|
||||
|
||||
if not library.disable_thumbnails:
|
||||
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()
|
||||
|
||||
# This renders a slot with a file in it, in the file picker.
|
||||
def _render_savefile(name, info, newest):
|
||||
|
||||
image, extra = info
|
||||
|
||||
ui.button(style='file_picker_entry',
|
||||
clicked=ui.returns(("return", (name, True))))
|
||||
|
||||
ui.hbox(padding=library.padding)
|
||||
|
||||
if not library.disable_thumbnails:
|
||||
ui.add(image)
|
||||
|
||||
if name == newest:
|
||||
ui.text(name + ". ", style='file_picker_new')
|
||||
else:
|
||||
ui.text(name + ". ", style='file_picker_old')
|
||||
|
||||
|
||||
ui.text(extra, style='file_picker_extra_info')
|
||||
|
||||
ui.close()
|
||||
|
||||
_scratch.file_picker_index = None
|
||||
|
||||
# This displays a file picker that can chose a save file from
|
||||
# the list of save files.
|
||||
def _file_picker(selected, save):
|
||||
|
||||
# The number of slots in a page.
|
||||
file_page_length = library.file_page_cols * library.file_page_rows
|
||||
|
||||
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) // file_page_length * file_page_length
|
||||
|
||||
if fpi < 0:
|
||||
fpi = 0
|
||||
|
||||
|
||||
while True:
|
||||
|
||||
if fpi < 0:
|
||||
fpi = 0
|
||||
|
||||
_scratch.file_picker_index = fpi
|
||||
|
||||
# Show Navigation
|
||||
_game_nav(selected)
|
||||
|
||||
ui.window(style='file_picker_window')
|
||||
ui.vbox() # whole thing.
|
||||
|
||||
if not library.disable_file_pager:
|
||||
|
||||
# Draw the navigation.
|
||||
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
|
||||
|
||||
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 * 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)
|
||||
else:
|
||||
_render_savefile(name, saves[name], newest)
|
||||
|
||||
# Actually draw a slot.
|
||||
ui.grid(library.file_page_cols,
|
||||
library.file_page_rows,
|
||||
style='file_picker_grid',
|
||||
transpose=True) # slots
|
||||
|
||||
for i in range(0, file_page_length):
|
||||
entry(i)
|
||||
|
||||
ui.close() # slots
|
||||
|
||||
ui.close() # whole thing
|
||||
|
||||
result = _game_interact()
|
||||
type, value = result
|
||||
|
||||
if type == "return":
|
||||
return value
|
||||
|
||||
if type == "fpidelta":
|
||||
fpi += value * file_page_length
|
||||
|
||||
if type == "fpiset":
|
||||
fpi = value
|
||||
|
||||
|
||||
# This renders a yes/no prompt, as part of the game menu. If
|
||||
# screen is None, then it omits the game menu navigation.
|
||||
def _yesno_prompt(screen, message, nav=True):
|
||||
|
||||
_game_nav(screen)
|
||||
|
||||
ui.window(style='yesno_window')
|
||||
ui.vbox(library.padding * 10, xpos=0.5, xanchor='center', ypos=0.5, yanchor='center')
|
||||
|
||||
_label_factory(message, "yesno", xpos=0.5, xanchor='center')
|
||||
|
||||
ui.grid(5, 1, xfill=True)
|
||||
|
||||
# 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()
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
return _game_interact()
|
||||
|
||||
def _show_exception(title, message):
|
||||
ui.window(style='error_window')
|
||||
ui.vbox()
|
||||
|
||||
ui.text(title, style='error_title')
|
||||
ui.text("")
|
||||
ui.text(message, style='error_body')
|
||||
ui.text("")
|
||||
ui.text(_("Please click to continue."), style='error_body')
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.saybehavior()
|
||||
|
||||
ui.interact()
|
||||
|
||||
def _quit_prompt(screen="quit"):
|
||||
|
||||
def prompt():
|
||||
return _yesno_prompt(screen, "Are you sure you want to quit the game?")
|
||||
|
||||
if renpy.invoke_in_new_context(prompt):
|
||||
renpy.quit()
|
||||
else:
|
||||
return
|
||||
|
||||
def _mainmenu_prompt(screen="mainmenu"):
|
||||
|
||||
def prompt():
|
||||
return _yesno_prompt(screen, "Are you sure you want to return to the main menu?\nThis will end your game.")
|
||||
|
||||
if renpy.invoke_in_new_context(prompt):
|
||||
renpy.full_restart()
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# The actual menus begin around here.
|
||||
|
||||
# First up, we have the code that is common to all of the entry points into
|
||||
# the game.
|
||||
|
||||
# This is the code that executes when entering a menu context.
|
||||
label _enter_menu:
|
||||
scene
|
||||
$ renpy.movie_stop()
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
|
||||
# This may be changed, if we are already in the main menu.
|
||||
$ renpy.context().main_menu = False
|
||||
|
||||
return
|
||||
|
||||
# Factored this all into one place, to make our lives a bit easier.
|
||||
label _enter_game_menu:
|
||||
call _enter_menu from _call__enter_menu_2
|
||||
|
||||
if library.enter_transition:
|
||||
$ renpy.transition(library.enter_transition)
|
||||
|
||||
if renpy.has_label("enter_game_menu"):
|
||||
call expression "enter_game_menu" from _call_enter_game_menu_1
|
||||
|
||||
return
|
||||
|
||||
##############################################################################
|
||||
# Now, we have the actual entry points into the various menu screens.
|
||||
|
||||
# Entry points from the game into menu-space.
|
||||
label _game_menu:
|
||||
label _game_menu_save:
|
||||
call _enter_game_menu from _call__enter_game_menu_1
|
||||
jump _save_screen
|
||||
|
||||
label _game_menu_load:
|
||||
call _enter_game_menu from _call__enter_game_menu_2
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu_preferences:
|
||||
call _enter_game_menu from _call__enter_game_menu_3
|
||||
jump _prefs_screen
|
||||
|
||||
label _confirm_quit:
|
||||
call _enter_menu from _call__enter_menu_3
|
||||
$ _quit_prompt(None)
|
||||
return
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Finally, we have the actual menu screens that are shown to the user.
|
||||
|
||||
label _load_screen:
|
||||
|
||||
python hide:
|
||||
|
||||
fn, exists = _file_picker("load", False )
|
||||
|
||||
if not renpy.context().main_menu and _load_prompt:
|
||||
if _yesno_prompt("load", "Loading a new game will end your current game.\nAre you sure you want to do this?"):
|
||||
renpy.load(fn)
|
||||
else:
|
||||
renpy.load(fn)
|
||||
|
||||
jump _load_screen
|
||||
|
||||
label _save_screen:
|
||||
$ _fn, _exists = _file_picker("save", True)
|
||||
|
||||
if not _exists or _yesno_prompt("save", "Are you sure you want to overwrite your save?"):
|
||||
python hide:
|
||||
|
||||
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
|
||||
|
||||
label _quit:
|
||||
$ renpy.quit()
|
||||
|
||||
# Make some noise, then return.
|
||||
label _noisy_return:
|
||||
$ renpy.play(library.exit_sound)
|
||||
|
||||
# Return to the game.
|
||||
label _return:
|
||||
|
||||
if renpy.context().main_menu:
|
||||
jump _main_menu
|
||||
|
||||
if library.exit_transition:
|
||||
$ renpy.transition(library.exit_transition)
|
||||
|
||||
return
|
||||
+88
-401
@@ -20,49 +20,17 @@ init -500:
|
||||
# Used to store library settings.
|
||||
library = object()
|
||||
|
||||
# The number of files to show at once.
|
||||
library.file_page_length = 10
|
||||
# The minimum version of the module we work with. Don't change
|
||||
# this unless you know what you're doing.
|
||||
library.module_version = 4008002
|
||||
|
||||
# The number of pages to add quick access buttons for.
|
||||
library.file_quick_access_pages = 5
|
||||
|
||||
# A small amount of padding.
|
||||
library.padding = 2
|
||||
|
||||
# The width of a thumbnail.
|
||||
library.thumbnail_width = 66
|
||||
|
||||
# The height of a thumbnail.
|
||||
library.thumbnail_height = 50
|
||||
|
||||
# The contents of the main menu.
|
||||
library.main_menu = [
|
||||
( "Start Game", "start" ),
|
||||
( "Continue Game", "_continue" ),
|
||||
( "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' ),
|
||||
]
|
||||
# Should we warn the user if the module is missing or has a bad
|
||||
# version?
|
||||
library.module_warning = False
|
||||
|
||||
# Used to translate strings in the library.
|
||||
library.translations = { }
|
||||
|
||||
# Sound played when entering the library without clicking a
|
||||
# button.
|
||||
library.enter_sound = None
|
||||
|
||||
# Sound played when leaving the library without clicking a
|
||||
# button.
|
||||
library.exit_sound = None
|
||||
|
||||
# True if the skip indicator should be shown.
|
||||
library.skip_indicator = True
|
||||
|
||||
@@ -70,11 +38,60 @@ init -500:
|
||||
# taking place.
|
||||
save_name = ''
|
||||
|
||||
# True if we're at the main menu, false otherwise.
|
||||
_at_main_menu = False
|
||||
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.
|
||||
|
||||
# True if we can save, false otherwise.
|
||||
_can_save = True
|
||||
@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.
|
||||
def _(s):
|
||||
@@ -87,30 +104,16 @@ init -500:
|
||||
else:
|
||||
return s
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# 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')
|
||||
@@ -121,12 +124,12 @@ init -500:
|
||||
# The default keymap.
|
||||
km = renpy.Keymap(
|
||||
rollback = renpy.rollback,
|
||||
screenshot = _screenshot,
|
||||
screenshot = screenshot,
|
||||
toggle_fullscreen = renpy.toggle_fullscreen,
|
||||
toggle_music = renpy.toggle_music,
|
||||
toggle_skip = toggle_skipping,
|
||||
game_menu = invoke_game_menu,
|
||||
hide_windows = _hide_windows,
|
||||
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
|
||||
)
|
||||
|
||||
config.underlay = [ km ]
|
||||
@@ -142,368 +145,52 @@ init -500:
|
||||
ui.conditional("config.skipping")
|
||||
ui.text(_("Skip Mode"), style='skip_indicator')
|
||||
|
||||
return [ ]
|
||||
|
||||
config.overlay_functions.append(skip_indicator)
|
||||
|
||||
return
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
label _start:
|
||||
|
||||
if renpy.has_label("splashscreen") and not _restart:
|
||||
call splashscreen
|
||||
label _hide_windows:
|
||||
|
||||
jump _main_menu
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
# This shows the main menu to the user.
|
||||
label _main_menu:
|
||||
|
||||
# Let the user completely override the main menu.
|
||||
if renpy.has_label("main_menu"):
|
||||
jump main_menu
|
||||
|
||||
label _library_main_menu:
|
||||
|
||||
python hide:
|
||||
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='mm_root_window')
|
||||
ui.fixed()
|
||||
|
||||
ui.window(style='mm_menu_window')
|
||||
ui.vbox()
|
||||
|
||||
for text, label in library.main_menu:
|
||||
ui.textbutton(text, clicked=ui.returns(label))
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
store._result = renpy.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
$ renpy.jump(_result)
|
||||
python:
|
||||
_windows_hidden = True
|
||||
ui.saybehavior()
|
||||
ui.interact(suppress_overlay=True)
|
||||
_windows_hidden = False
|
||||
|
||||
return
|
||||
|
||||
# Used to call the game menu.
|
||||
label _continue:
|
||||
$ _can_save = False
|
||||
$ _at_main_menu = True
|
||||
|
||||
|
||||
|
||||
$ renpy.call_in_new_context("_load_menu")
|
||||
|
||||
$ _can_save = True
|
||||
$ _at_main_menu = False
|
||||
|
||||
jump _library_main_menu
|
||||
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Code for the game menu.
|
||||
|
||||
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()
|
||||
|
||||
ui.add(renpy.Keymap(game_menu=ui.jumps("_noisy_return")))
|
||||
|
||||
ui.window(style='gm_root_window')
|
||||
ui.fixed()
|
||||
|
||||
ui.window(style='gm_nav_window')
|
||||
ui.vbox()
|
||||
|
||||
for key, label, target, enabled in library.game_menu:
|
||||
style="gm_nav_button"
|
||||
text_style="gm_nav_button_text"
|
||||
|
||||
if key == selected:
|
||||
style = 'gm_nav_selected_button'
|
||||
text_style = 'gm_nav_selected_button_text'
|
||||
|
||||
clicked = ui.jumps(target)
|
||||
|
||||
if not eval(enabled):
|
||||
style = 'gm_nav_disabled_button'
|
||||
text_style = 'gm_nav_disabled_button_text'
|
||||
|
||||
clicked = lambda : None
|
||||
|
||||
|
||||
ui.textbutton(_(label), style=style, text_style=text_style,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
def _game_interact():
|
||||
|
||||
return renpy.interact(suppress_underlay=True,
|
||||
suppress_overlay=True)
|
||||
|
||||
|
||||
def _render_new_slot(name, save):
|
||||
|
||||
if save:
|
||||
clicked=ui.returns(("return", (name, False)))
|
||||
enable_hover = True
|
||||
else:
|
||||
clicked=None
|
||||
enable_hover = False
|
||||
# This code here handles check for the correct version of the Ren'Py module.
|
||||
|
||||
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):
|
||||
label _check_module:
|
||||
|
||||
image, extra = info
|
||||
if not library.module_warning:
|
||||
return
|
||||
|
||||
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')
|
||||
python hide:
|
||||
module_info = _("While Ren'Py games are playable without the _renpy module, some features may be disabled. For more information, read the module/README.txt file or go to http://www.bishoujo.us/renpy/.")
|
||||
|
||||
|
||||
ui.text(extra, style='file_picker_extra_info')
|
||||
if renpy.module_version() == 0:
|
||||
_show_exception(_("_renpy module not found."),
|
||||
_("The _renpy module could not be loaded on your system.") + "\n\n" + module_info)
|
||||
elif renpy.module_version() < library.module_version:
|
||||
_show_exception(_("Old _renpy module found."),
|
||||
_("An old version (%d) of the Ren'Py module was found on your system, while this game requires version %d.") % (renpy.module_version(), library.module_version) + "\n\n" + module_info)
|
||||
|
||||
ui.close()
|
||||
|
||||
|
||||
_scratch.file_picker_index = None
|
||||
|
||||
# This displays a file picker that can chose a save file from
|
||||
# the list of save files.
|
||||
def _file_picker(selected, save):
|
||||
|
||||
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) // library.file_page_length * library.file_page_length
|
||||
|
||||
|
||||
# The length of a half-page of files.
|
||||
hfpl = library.file_page_length // 2
|
||||
|
||||
while True:
|
||||
|
||||
if fpi < 0:
|
||||
fpi = 0
|
||||
|
||||
_scratch.file_picker_index = fpi
|
||||
|
||||
# Show Navigation
|
||||
_game_nav(selected)
|
||||
|
||||
ui.window(style='file_picker_window')
|
||||
|
||||
ui.vbox() # whole thing.
|
||||
|
||||
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
|
||||
|
||||
def tb(cond, label, clicked):
|
||||
if cond:
|
||||
style = 'button'
|
||||
text_style = 'button_text'
|
||||
else:
|
||||
style = 'disabled_button'
|
||||
text_style = 'disabled_button_text'
|
||||
|
||||
ui.textbutton(label, style=style, text_style=text_style, clicked=clicked)
|
||||
|
||||
|
||||
tb(fpi > 0, _('Previous'), ui.returns(("fpidelta", -1)))
|
||||
|
||||
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)))
|
||||
|
||||
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
|
||||
|
||||
ui.close() # nav buttons.
|
||||
|
||||
def entry(offset):
|
||||
i = fpi + offset
|
||||
|
||||
name = str(i + 1)
|
||||
|
||||
if name not in saves:
|
||||
_render_new_slot(name, save)
|
||||
else:
|
||||
_render_savefile(name, saves[name], newest)
|
||||
|
||||
|
||||
ui.hbox() # slots
|
||||
|
||||
ui.vbox()
|
||||
for i in range(0, hfpl):
|
||||
entry(i)
|
||||
ui.close()
|
||||
|
||||
ui.vbox()
|
||||
for i in range(hfpl, hfpl * 2):
|
||||
entry(i)
|
||||
ui.close()
|
||||
|
||||
ui.close() # slots
|
||||
|
||||
ui.close() # whole thing
|
||||
|
||||
result = _game_interact()
|
||||
type, value = result
|
||||
|
||||
if type == "return":
|
||||
return value
|
||||
|
||||
if type == "fpidelta":
|
||||
fpi += value * library.file_page_length
|
||||
|
||||
if type == "fpiset":
|
||||
fpi = value
|
||||
|
||||
|
||||
def _yesno_prompt(screen, message):
|
||||
|
||||
_game_nav(screen)
|
||||
|
||||
ui.text(message, style='yesno_prompt')
|
||||
ui.textbutton(_("Yes"), style='yesno_yes', clicked=ui.returns(True))
|
||||
ui.textbutton(_("No"), style='yesno_no', clicked=ui.returns(False))
|
||||
|
||||
return _game_interact()
|
||||
|
||||
def _show_exception(title, message):
|
||||
ui.add(Solid((0, 0, 0, 255)))
|
||||
ui.vbox()
|
||||
|
||||
ui.text(title, color=(255, 128, 128, 255))
|
||||
ui.text("")
|
||||
ui.text(message)
|
||||
ui.text("")
|
||||
ui.text("Please click to continue.")
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.saybehavior()
|
||||
|
||||
ui.interact()
|
||||
|
||||
|
||||
|
||||
# Factored this all into one place, to make our lives a bit easier.
|
||||
label _take_screenshot:
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
return
|
||||
|
||||
|
||||
# Entry points from the game into menu-space.
|
||||
label _load_menu:
|
||||
call _take_screenshot
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu:
|
||||
call _take_screenshot
|
||||
jump _save_screen
|
||||
|
||||
label _confirm_quit:
|
||||
call _take_screenshot
|
||||
jump _quit_screen
|
||||
|
||||
# Menu screens.
|
||||
label _load_screen:
|
||||
|
||||
python:
|
||||
_fn, _exists = _file_picker("load", False )
|
||||
|
||||
python:
|
||||
renpy.load(_fn)
|
||||
|
||||
jump _load_screen
|
||||
|
||||
label _save_screen:
|
||||
$ _fn, _exists = _file_picker("save", True)
|
||||
|
||||
if not _exists or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
|
||||
python hide:
|
||||
|
||||
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
|
||||
|
||||
# Asks the user if he wants to quit.
|
||||
label _quit_screen:
|
||||
if _yesno_prompt("quit", _("Are you sure you want to end the game?")):
|
||||
jump _quit
|
||||
else:
|
||||
jump _return
|
||||
|
||||
label _quit:
|
||||
$ renpy.quit()
|
||||
|
||||
label _full_restart:
|
||||
$ renpy.full_restart()
|
||||
|
||||
# Make some noise, then return.
|
||||
label _noisy_return:
|
||||
$ renpy.play(library.exit_sound)
|
||||
|
||||
# Return to the game.
|
||||
label _return:
|
||||
return
|
||||
|
||||
# Random nice things to have.
|
||||
init:
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# This file contains code fo the main menu, and anything else that
|
||||
# happens upon initial execution of a Ren'Py program.
|
||||
|
||||
init -498:
|
||||
python hide:
|
||||
|
||||
# The contents of the main menu.
|
||||
library.main_menu = [
|
||||
( "Start Game", "start" ),
|
||||
( "Continue Game", ui.jumps("_load_screen") ),
|
||||
( "Preferences", ui.jumps("_prefs_screen") ),
|
||||
( "Quit Game", ui.jumps("_quit") ),
|
||||
]
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
label _start:
|
||||
|
||||
call _check_module from _call__check_module_1
|
||||
|
||||
if renpy.has_label("splashscreen") and not _restart:
|
||||
call expression "splashscreen" from _call_splashscreen_1
|
||||
|
||||
# Clean out any residual scene from the splashscreen.
|
||||
scene
|
||||
|
||||
$ renpy.call_in_new_context("_enter_main_menu")
|
||||
|
||||
# Should never happen... but might as well do something
|
||||
jump start
|
||||
|
||||
# At this point, we've been switched into a new context. So we
|
||||
# initialize it.
|
||||
label _enter_main_menu:
|
||||
|
||||
call _enter_menu from _call__enter_menu_1
|
||||
|
||||
$ renpy.context().main_menu = True
|
||||
|
||||
# This is called to show the main menu to the user.
|
||||
label _main_menu:
|
||||
|
||||
# Let the user completely override the main menu. (But please note
|
||||
# it still lives in the menu context, rather than the game context.)
|
||||
if renpy.has_label("main_menu"):
|
||||
jump expression "main_menu"
|
||||
|
||||
# This is the default main menu, which we get if the user hasn't
|
||||
# defined his own, or if that function calls this explicitly.
|
||||
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()
|
||||
|
||||
for text, clicked in library.main_menu:
|
||||
|
||||
if isinstance(clicked, basestring):
|
||||
clicked = ui.jumpsoutofcontext(clicked)
|
||||
|
||||
_button_factory(text, "mm", clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
store._result = ui.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
jump _main_menu
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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
|
||||
it will take to fade out the currently playing music.
|
||||
"""
|
||||
|
||||
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(fadeout=None):
|
||||
"""
|
||||
This stops the currently playing music track.
|
||||
|
||||
@param fadeout: If this parameter is not None, it is
|
||||
interpreted as a time in seconds, which gives how long
|
||||
it will take to fade out the currently playing music.
|
||||
"""
|
||||
|
||||
ctx = renpy.context()
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
if fadeout and audio.music_enabled() and not config.skipping:
|
||||
audio.music_fadeout(fadeout)
|
||||
|
||||
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.interact_callbacks.append(music_interact)
|
||||
config.music_end_event = music_end_event
|
||||
|
||||
|
||||
|
||||
+131
-36
@@ -4,10 +4,10 @@
|
||||
init -450:
|
||||
python:
|
||||
|
||||
# Used to collect the various preferences the system knows
|
||||
# about.
|
||||
library.left_preferences = [ ]
|
||||
library.right_preferences = [ ]
|
||||
# This is a map from the name of the style that is applied to
|
||||
# a list of preferences that should be placed into a vbox
|
||||
# with that style.
|
||||
library.preferences = { }
|
||||
|
||||
class _Preference(object):
|
||||
"""
|
||||
@@ -15,12 +15,12 @@ init -450:
|
||||
may be shown to the user.
|
||||
"""
|
||||
|
||||
def __init__(self, name, field, values):
|
||||
def __init__(self, name, field, values, base=_preferences):
|
||||
"""
|
||||
@param name: The name of this preference. It will be
|
||||
displayed to the user.
|
||||
|
||||
@param variable: The field on the _preferences object
|
||||
@param variable: The field on the base object
|
||||
that will be assigned the selected value. This field
|
||||
must exist.
|
||||
|
||||
@@ -33,11 +33,16 @@ init -450:
|
||||
conditions are true, this preference will not be
|
||||
displayed to the user. A condition of None is always
|
||||
considered to be True.
|
||||
|
||||
@param base: The base object on which the variable is
|
||||
read from and set. This defaults to _preferences,
|
||||
the user preferences object.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.values = values
|
||||
self.base = base
|
||||
|
||||
def render_preference(self):
|
||||
values = [ (name, val) for name, val, cond in self.values
|
||||
@@ -46,30 +51,106 @@ init -450:
|
||||
if not values:
|
||||
return
|
||||
|
||||
ui.vbox(style='prefs_pref')
|
||||
ui.text(_(self.name), style='prefs_label')
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
cur = getattr(_preferences, self.field)
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
for name, value in values:
|
||||
|
||||
style = 'prefs_button'
|
||||
text_style = 'prefs_button_text'
|
||||
|
||||
if cur == value:
|
||||
style = 'prefs_selected_button'
|
||||
text_style = 'prefs_selected_button_text'
|
||||
|
||||
def clicked(value=value):
|
||||
setattr(_preferences, self.field, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
ui.textbutton(_(name),
|
||||
style=style,
|
||||
text_style=text_style,
|
||||
clicked=clicked)
|
||||
_button_factory(name, "prefs",
|
||||
selected=cur==value,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
class _PreferenceSpinner(object):
|
||||
"""
|
||||
This is a class that's used to represent a preference
|
||||
spinner, which is a preference that can be incremented
|
||||
and decremented, when shown to the user.
|
||||
"""
|
||||
|
||||
def __init__(self, name, field, minimum, maximum, delta,
|
||||
cond = "True", render = lambda x : str(x),
|
||||
base=_preferences):
|
||||
"""
|
||||
@param name: The name of this preference, that is presented
|
||||
to the user.
|
||||
|
||||
@param field: The name of the field on the base object
|
||||
that is updated by this spinner.
|
||||
|
||||
@param minimum: The minimum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param maximum: The maximum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param delta: The delta by which this spinner is
|
||||
incremented or decremented.
|
||||
|
||||
@param cond: If this condition is not true, this spinner is
|
||||
not shown.
|
||||
|
||||
@param render: This function is called with the value of
|
||||
the field, and is expected to render that value to a
|
||||
string.
|
||||
|
||||
@param base: The base object that this spinner updates
|
||||
the field on. It defaults to _preferences, the preferences
|
||||
object.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
self.delta = delta
|
||||
self.cond = cond
|
||||
self.render = render
|
||||
self.base = base
|
||||
|
||||
def render_preference(self):
|
||||
|
||||
if not renpy.eval(self.cond):
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
|
||||
def minus_clicked():
|
||||
value = cur - self.delta
|
||||
value = max(self.minimum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
def plus_clicked():
|
||||
value = cur + self.delta
|
||||
value = min(self.maximum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
ui.hbox(style='prefs_spinner')
|
||||
_button_factory("-", "prefs_spinner", clicked=minus_clicked)
|
||||
_label_factory(self.render(cur), "prefs_spinner")
|
||||
_button_factory("+", "prefs_spinner", clicked=plus_clicked)
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
|
||||
|
||||
|
||||
python hide:
|
||||
@@ -78,6 +159,7 @@ init -450:
|
||||
library.has_music = True
|
||||
library.has_sound = True
|
||||
library.has_transitions = True
|
||||
library.has_cps = True
|
||||
|
||||
|
||||
p1 = _Preference('Display', 'fullscreen', [
|
||||
@@ -96,7 +178,7 @@ init -450:
|
||||
])
|
||||
|
||||
|
||||
library.left_preferences = [ p1, p2, p3 ]
|
||||
library.preferences['prefs_left'] = [ p1, p2, p3 ]
|
||||
|
||||
p4 = _Preference('TAB and CTRL Skip', 'skip_unseen', [
|
||||
('Seen Messages', False, 'config.allow_skipping'),
|
||||
@@ -109,13 +191,23 @@ init -450:
|
||||
('None', 0, 'library.has_transitions'),
|
||||
])
|
||||
|
||||
p6 = _Preference('Text Display', 'fast_text', [
|
||||
('Fast', True, 'config.annoying_text_cps'),
|
||||
('Slow', False, 'config.annoying_text_cps'),
|
||||
])
|
||||
|
||||
# p6 = _Preference('Text Display', 'fast_text', [
|
||||
# ('Fast', True, 'config.annoying_text_cps'),
|
||||
# ('Slow', False, 'config.annoying_text_cps'),
|
||||
# ])
|
||||
|
||||
library.right_preferences = [ p4, p5, p6 ]
|
||||
def cps_render(n):
|
||||
if n == 0:
|
||||
return "Infinite"
|
||||
else:
|
||||
return str(n)
|
||||
|
||||
p6 = _PreferenceSpinner('Text Speed (CPS)', 'text_cps',
|
||||
0, 500, 10, 'library.has_cps',
|
||||
render=cps_render)
|
||||
|
||||
|
||||
library.preferences['prefs_right'] = [ p4, p5, p6 ]
|
||||
|
||||
label _prefs_screen:
|
||||
|
||||
@@ -123,14 +215,17 @@ label _prefs_screen:
|
||||
|
||||
_game_nav("prefs")
|
||||
|
||||
ui.vbox(library.padding * 3, style='prefs_left')
|
||||
for i in library.left_preferences:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.vbox(library.padding * 3, style='prefs_right')
|
||||
for i in library.right_preferences:
|
||||
i.render_preference()
|
||||
ui.window(style='prefs_window')
|
||||
ui.fixed()
|
||||
|
||||
|
||||
for style, prefs in library.preferences.iteritems():
|
||||
|
||||
ui.vbox(library.padding * 3, style=style)
|
||||
for i in prefs:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
_game_interact()
|
||||
|
||||
+144
-90
@@ -14,7 +14,7 @@
|
||||
# to your script. No need to mess around here, it will just make your
|
||||
# life harder when a new version of Ren'Py is released.
|
||||
|
||||
init -250:
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
style.create('default', None,
|
||||
@@ -36,6 +36,9 @@ init -250:
|
||||
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_color = (0, 0, 0, 128)
|
||||
style.default.minwidth = 0
|
||||
@@ -43,13 +46,8 @@ init -250:
|
||||
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,10 +64,11 @@ init -250:
|
||||
style.default.ypos = 0
|
||||
style.default.xanchor = 'left'
|
||||
style.default.yanchor = 'top'
|
||||
style.default.xmaximum = None
|
||||
style.default.ymaximum = None
|
||||
|
||||
# Sound properties.
|
||||
style.default.hover_sound = None
|
||||
style.default.activate_sound = None
|
||||
style.default.sound = None
|
||||
|
||||
# The base style for the large windows.
|
||||
style.create('window', 'default',
|
||||
@@ -107,6 +106,8 @@ init -250:
|
||||
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.""")
|
||||
@@ -122,14 +123,22 @@ init -250:
|
||||
|
||||
# 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, sound) The style that is used to render a menu choice.""")
|
||||
"""(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.""")
|
||||
|
||||
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',
|
||||
@@ -172,9 +181,12 @@ init -250:
|
||||
|
||||
# Styles that are used by imagemaps
|
||||
style.create('imagemap', 'image_placement',
|
||||
'(sound, position) The style that is used for imagemaps.')
|
||||
'(position) The style that is used for imagemaps.')
|
||||
|
||||
# Style that is used by imagebutttons.
|
||||
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.')
|
||||
|
||||
@@ -197,6 +209,8 @@ init -250:
|
||||
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)
|
||||
|
||||
# Selected button.
|
||||
@@ -208,36 +222,22 @@ init -250:
|
||||
|
||||
style.selected_button_text.color = dark_red
|
||||
style.selected_button_text.hover_color = bright_red
|
||||
style.selected_button_text.activate_color = bright_red
|
||||
|
||||
# Disabled button.
|
||||
|
||||
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)
|
||||
style.disabled_button_text.hover_color = (128, 128, 128, 255)
|
||||
|
||||
|
||||
# Bar.
|
||||
style.create('bar', 'default',
|
||||
'(bar) The style that is used by default for bars.')
|
||||
|
||||
style.bar.left_bar = Solid(bright_cyan)
|
||||
style.bar.right_bar = Solid((0, 0, 0, 128))
|
||||
style.bar.left_gutter = 0
|
||||
style.bar.right_gutter = 0
|
||||
|
||||
# Styles that are used when laying out the main menu.
|
||||
style.create('mm_root_window', 'default',
|
||||
'(window) The style used for the root window of the main menu. This is primarily used to set a background for the main menu.')
|
||||
|
||||
style.mm_root_window.background = Solid((0, 0, 0, 255))
|
||||
style.mm_root_window.xfill = True
|
||||
style.mm_root_window.yfill = True
|
||||
|
||||
style.create('mm_menu_window', 'default',
|
||||
'(window, position) A window that contains the choices in the main menu. Change this to change the placement of these choices on the main menu screen.')
|
||||
@@ -259,6 +259,8 @@ init -250:
|
||||
'(window) The style used for the root window of the game menu. This is primarily used to change the background of the game menu.')
|
||||
|
||||
style.gm_root_window.background = Solid((0, 0, 0, 255))
|
||||
style.gm_root_window.xfill = True
|
||||
style.gm_root_window.yfill = True
|
||||
|
||||
style.create('gm_nav_window', 'default',
|
||||
'(window, position) The style used by a window containing buttons that allow the user to navigate through the different screens of the game menu.')
|
||||
@@ -280,13 +282,6 @@ init -250:
|
||||
|
||||
style.create('gm_nav_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_disabled_button', 'disabled_button',
|
||||
'(window, hover) The style of a disabled game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_disabled_button_text', 'disabled_button_text',
|
||||
'(text, hover) The style of the text of a disabled game menu navigation button.')
|
||||
|
||||
|
||||
style.create('file_picker_window', 'default',
|
||||
'(window, position) A window containing the file picker that is used to choose slots for loading and saving.')
|
||||
@@ -295,35 +290,44 @@ init -250:
|
||||
style.file_picker_window.xanchor = 'left'
|
||||
style.file_picker_window.ypos = 0
|
||||
style.file_picker_window.yanchor = 'top'
|
||||
style.file_picker_window.xpadding = 5
|
||||
|
||||
|
||||
style.create('file_picker_navbox', 'default',
|
||||
'(position) The position of the naviation (next/previous) buttons in the file picker.')
|
||||
|
||||
style.file_picker_navbox.xmargin = 10
|
||||
|
||||
style.create('file_picker_image', 'default',
|
||||
'(position) The position of the image in each file picker entry.')
|
||||
|
||||
style.file_picker_image.xminimum = 280
|
||||
style.create('file_picker_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_grid', 'default',
|
||||
'(position) The style of the grid containing the file picker entries.')
|
||||
|
||||
style.file_picker_grid.xfill = True
|
||||
|
||||
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.xmargin = 5
|
||||
style.file_picker_entry.xfill = True
|
||||
style.file_picker_entry.ymargin = 2
|
||||
|
||||
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
|
||||
style.file_picker_entry.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.idle_color = dark_cyan
|
||||
style.file_picker_text.color = dark_cyan
|
||||
style.file_picker_text.hover_color = bright_cyan
|
||||
|
||||
style.create('file_picker_new', 'file_picker_text',
|
||||
@@ -333,6 +337,7 @@ init -250:
|
||||
'(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
|
||||
@@ -343,78 +348,127 @@ init -250:
|
||||
style.create('file_picker_empty_slot', 'file_picker_text',
|
||||
'(text) The style that is used for the empty slot indicator in the file picker.')
|
||||
|
||||
style.create('yesno_prompt', 'default',
|
||||
style.create('yesno_label', 'default',
|
||||
'(text, position) The style used for the prompt in a yes/no dialog.')
|
||||
|
||||
style.yesno_prompt.xpos = 0.5
|
||||
style.yesno_prompt.xanchor = 'center'
|
||||
style.yesno_label.color = green
|
||||
style.yesno_label.textalign = 0.5
|
||||
|
||||
style.yesno_prompt.ypos = 0.25
|
||||
style.yesno_prompt.yanchor = 'center'
|
||||
style.create('yesno_button', 'button',
|
||||
'(window, hover) The style of yes/no buttons.')
|
||||
|
||||
style.yesno_prompt.color = green
|
||||
|
||||
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_button_text', 'button_text',
|
||||
'(window, hover) The style of yes/no button text.')
|
||||
|
||||
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
|
||||
style.yesno_window.xmargin = .1
|
||||
|
||||
# Preferences
|
||||
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(window, position) A window containing an individual preference.')
|
||||
|
||||
style.prefs_pref.xpos = 0.5
|
||||
style.prefs_pref.xanchor = 'center'
|
||||
style.prefs_pref.bottom_margin = 10
|
||||
|
||||
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('prefs_left', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_left.xanchor = 'center'
|
||||
style.prefs_left.xpos = 0.25
|
||||
|
||||
style.create('prefs_right', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_right.xanchor = 'center'
|
||||
style.prefs_right.xpos = 0.75
|
||||
|
||||
style.create('prefs_spinner', 'default',
|
||||
'(position) The position of the prefs spinner.')
|
||||
|
||||
style.prefs_spinner.xpos = 0.5
|
||||
style.prefs_spinner.xanchor = 'center'
|
||||
|
||||
style.create('prefs_spinner_label', 'prefs_label',
|
||||
'(text) This is the style that displays the value of a preference spinner.')
|
||||
|
||||
style.prefs_spinner_label.minwidth = 100
|
||||
style.prefs_spinner_label.textalign = 0.5
|
||||
|
||||
style.create('prefs_spinner_button', 'prefs_button',
|
||||
'(window, hover) The style of the + or - buttons in a preference spinner.')
|
||||
|
||||
style.create('prefs_spinner_button_text', 'prefs_button_text',
|
||||
'(text, hover) The style of the text of the + and - buttons in a preference spinner.')
|
||||
|
||||
|
||||
|
||||
|
||||
# The skip indicator.
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Styles used by internal error messages.
|
||||
|
||||
style.create('error_window', 'default',
|
||||
'(window) The style of the window containing internal error messages.')
|
||||
|
||||
style.error_window.background = Solid((220, 220, 255, 255))
|
||||
style.error_window.xfill = True
|
||||
style.error_window.yfill = True
|
||||
style.error_window.xpadding = 20
|
||||
style.error_window.ypadding = 20
|
||||
|
||||
|
||||
style.create('error_title', 'default',
|
||||
'(text) The style of the text containing the title of an error message.')
|
||||
|
||||
style.error_title.color = (255, 128, 128, 255)
|
||||
|
||||
style.create('error_body', 'default',
|
||||
'(text) The style of the body of an error message.')
|
||||
|
||||
style.error_body.color = (128, 128, 255, 255)
|
||||
|
||||
|
||||
+75
-18
@@ -2,14 +2,6 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
@@ -20,16 +12,19 @@ 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
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
# Add the path to the module.
|
||||
sys.path.append("module")
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
|
||||
@@ -51,14 +46,27 @@ if __name__ == "__main__":
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--lint', dest='lint', default=False, action='store_true',
|
||||
help='Run a number of expensive tests, to try to detect errors in the script.')
|
||||
|
||||
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)
|
||||
|
||||
if not options.lint:
|
||||
import renpy.display.presplash
|
||||
renpy.display.presplash.start(options.game)
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
try:
|
||||
renpy.main.main(options.game)
|
||||
renpy.main.main(options.game, lint=options.lint)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
@@ -72,6 +80,16 @@ if __name__ == "__main__":
|
||||
|
||||
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)
|
||||
|
||||
@@ -96,8 +114,47 @@ if __name__ == "__main__":
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def memory_profile():
|
||||
|
||||
import renpy
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
print
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
objs = gc.get_objects()
|
||||
|
||||
c = { } # count
|
||||
dead_renders = 0
|
||||
|
||||
for i in objs:
|
||||
t = type(i)
|
||||
c[t] = c.get(t, 0) + 1
|
||||
|
||||
if isinstance(i, renpy.display.render.Render):
|
||||
if i.dead:
|
||||
dead_renders += 1
|
||||
|
||||
|
||||
results = [ (count, ty) for ty, count in c.iteritems() ]
|
||||
results.sort()
|
||||
|
||||
for count, ty in results:
|
||||
print count, str(ty)
|
||||
|
||||
if dead_renders:
|
||||
print
|
||||
print "*** found", dead_renders, "dead Renders. ***"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+822
@@ -0,0 +1,822 @@
|
||||
#!/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 $
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
+723
-84
@@ -22,7 +22,16 @@ init:
|
||||
# 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)
|
||||
$ style.window.background = Frame("frame.png", 25, 25)
|
||||
|
||||
# Change some styles involving the margins and padding of the
|
||||
# default window. (We need this, as we use a frame image that
|
||||
# includes a drop-shadow.)
|
||||
$ style.window.xmargin = 0
|
||||
$ style.window.ymargin = 0
|
||||
$ style.window.xpadding = 20
|
||||
$ style.window.top_padding = 5
|
||||
$ style.window.bottom_padding = 15
|
||||
|
||||
# Interface sounds, just for the heck of it.
|
||||
$ style.button.activate_sound = 'click.wav'
|
||||
@@ -30,37 +39,44 @@ init:
|
||||
$ 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')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
# Select the transitions that are used when entering and exiting
|
||||
# the game menu.
|
||||
$ library.enter_transition = pixellate
|
||||
$ library.exit_transition = pixellate
|
||||
|
||||
|
||||
# There used to be a large list of definitions of placements and
|
||||
# transitions here. They've been moved into
|
||||
# common/definitions.rpy, to save space and allow them to be
|
||||
# upgraded more easily. You can still define your own transitions
|
||||
# and placements here, however.
|
||||
|
||||
# $ slowdissove = Dissolve(1.0)
|
||||
# $ whitefade = Fade(.5, 0, .5, color=(255, 255, 255, 255))
|
||||
|
||||
# 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)
|
||||
|
||||
# Now, we declare the images that are used in the program.
|
||||
|
||||
# Backgrounds.
|
||||
image carillon = Image("carillon.jpg")
|
||||
image whitehouse = Image("whitehouse.jpg")
|
||||
image washington = Image("washington.jpg")
|
||||
image carillon = "carillon.jpg"
|
||||
image whitehouse = "whitehouse.jpg"
|
||||
image washington = "washington.jpg"
|
||||
image onememorial = "1memorial.jpg"
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
# Character pictures.
|
||||
image eileen happy = Image("9a_happy.png")
|
||||
image eileen vhappy = Image("9a_vhappy.png")
|
||||
image eileen concerned = Image("9a_concerned.png")
|
||||
image eileen happy = "9a_happy.png"
|
||||
image eileen vhappy = "9a_vhappy.png"
|
||||
image eileen concerned = "9a_concerned.png"
|
||||
|
||||
# Finally, the character object. This object lets us have the
|
||||
# character say dialogue without us having to repeatedly type
|
||||
# her name. It also lets us change the color of her name.
|
||||
|
||||
# Character objects.
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -68,9 +84,9 @@ init:
|
||||
#
|
||||
# label splashscreen:
|
||||
# scene black
|
||||
# show text "American Bishoujo Presents..." with fade
|
||||
# show text "American Bishoujo Presents..." with dissolve
|
||||
# $ renpy.pause(1.0)
|
||||
# hide text with fade
|
||||
# hide text with dissolve
|
||||
#
|
||||
# return
|
||||
|
||||
@@ -90,7 +106,7 @@ label start:
|
||||
|
||||
# Clear the game runtime timer, so it doesn't reflect time spent
|
||||
# sitting at the main menu.
|
||||
$ renpy.clear_game_runtime()
|
||||
$ renpy.clear_game_runtime()
|
||||
|
||||
# Start some music playing in the background.
|
||||
$ renpy.music_start('sun-flower-slow-drag.mid')
|
||||
@@ -101,15 +117,20 @@ 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 dialoge.
|
||||
"Girl" "Hi, and welcome to the Ren'Py 4 demo program."
|
||||
# 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."
|
||||
|
||||
# This instantly replaces the very happy picture of Eileen with
|
||||
# one showing her merely happy. It demonstrates how the show
|
||||
# statement lets characters change emotions.
|
||||
show eileen happy
|
||||
|
||||
|
||||
# Another line of dialogue.
|
||||
"Girl" "My name is Eileen, and while I plan to one day star in a
|
||||
real game, for now I'm here to tell you about Ren'Py."
|
||||
@@ -146,7 +167,7 @@ label choices:
|
||||
|
||||
# This is a menu choice. When chosen, the statements in its
|
||||
# block are executed.
|
||||
"What are some features of Ren'Py games?":
|
||||
"What are some user-visible features of Ren'Py games?":
|
||||
|
||||
# We call the features label. The from clause needs to be
|
||||
# here to ensure that save games work, even after we
|
||||
@@ -162,6 +183,10 @@ label choices:
|
||||
call writing from _call_writing_1
|
||||
jump choices
|
||||
|
||||
"Can you demonstrate more features to me?":
|
||||
call demonstrate from _call_demonstrate_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.)
|
||||
@@ -226,8 +251,9 @@ label writing:
|
||||
e "The show statement shows another image on the screen."
|
||||
|
||||
# The at clause here, displays the character on the left side of
|
||||
# the screen.
|
||||
show eileen happy at left with dissolve
|
||||
# the screen. The with clause causes us to slide from the old
|
||||
# position to the new position.
|
||||
show eileen happy at left with move
|
||||
|
||||
e "Images can take at clauses that specify where on the screen
|
||||
they are shown."
|
||||
@@ -269,59 +295,17 @@ label writing:
|
||||
|
||||
e "... are easily invoked."
|
||||
|
||||
e "As of version 4.2, Ren'Py supports image maps, which are like
|
||||
another form of menu. Let's try one."
|
||||
|
||||
# This is an imagemap. It consists of two images, and a list of
|
||||
# hotspots. For each hotspot we give the coordinates of the left,
|
||||
# top, right, and bottom sides, and the value to return if it is
|
||||
# picked.
|
||||
|
||||
$ result = renpy.imagemap("ground.png", "selected.png", [
|
||||
(100, 100, 300, 400, "eileen"),
|
||||
(500, 100, 700, 400, "lucy")
|
||||
])
|
||||
|
||||
# We've assigned the chosen result from the imagemap to the
|
||||
# result variable. We can use an if statement to vary what
|
||||
# happens based on the user's choice.
|
||||
|
||||
if result == "eileen":
|
||||
show eileen vhappy
|
||||
e "You picked me!"
|
||||
|
||||
elif result == "lucy":
|
||||
show eileen concerned
|
||||
e "It looks like you picked Lucy."
|
||||
|
||||
# Eileen is being a bit possesive here. :-P
|
||||
if date:
|
||||
e "You can forget about Saturday."
|
||||
$ date = False
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "Ren'Py supports music, such as what's playing in the
|
||||
background..."
|
||||
|
||||
# This plays a sound effect.
|
||||
$ renpy.play("18005551212.wav")
|
||||
|
||||
e "... and sound effects, like the one that just played."
|
||||
|
||||
e "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."
|
||||
|
||||
e "Rather than go into this here, you can read all about it in the
|
||||
tutorial."
|
||||
reference."
|
||||
|
||||
e "You can see a number of features in action by asking me to
|
||||
demonstrate them at the next menu."
|
||||
|
||||
e "If you want to make changes, you can edit the script for this
|
||||
game by editing game/script.rpy"
|
||||
@@ -340,8 +324,9 @@ label features:
|
||||
|
||||
$ save_name = "Features"
|
||||
|
||||
e "By providing a range of useful features, we let game authors
|
||||
focus on writing their games."
|
||||
e "Ren'Py provides a number of gameplay features, giving the user
|
||||
a good experience while freeing up the game author to write his
|
||||
game."
|
||||
|
||||
e "What are some of these features? Well, first of all, we take
|
||||
care of displaying the screen, as well as dialogue and menus."
|
||||
@@ -350,9 +335,6 @@ label features:
|
||||
mouse. If you've gotten this far, you've probably figured that
|
||||
out already."
|
||||
|
||||
e "If you press 'f', you can toggle fullscreen mode. Pressing 'm'
|
||||
will toggle music on and off."
|
||||
|
||||
e "Right-clicking or pressing escape will bring you to the game
|
||||
menu."
|
||||
|
||||
@@ -360,6 +342,10 @@ label features:
|
||||
limit the number of save slots available. You can create as
|
||||
many slots as you can stand."
|
||||
|
||||
e "A preferences screen on the game menu lets you change the
|
||||
fullscreen mode, control skipping, text speed, and
|
||||
transitions, and turn sound and music on and off."
|
||||
|
||||
e "The game menu also lets you restart or quit the game. But you
|
||||
wouldn't want to do that, would you?"
|
||||
|
||||
@@ -448,7 +434,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://www.lemmasoft.net/forums/, and click on
|
||||
e "Just go to http://lemmasoft.renai.us/forums/, and click on
|
||||
Ren'Py."
|
||||
|
||||
e "We thank Blue Lemma for hosting our forum."
|
||||
@@ -584,7 +570,7 @@ label ending:
|
||||
if date:
|
||||
e "And I'll see you on Saturday."
|
||||
|
||||
scene black with fade
|
||||
scene black with dissolve
|
||||
|
||||
"Ren'Py and the Ren'Py demo were written by PyTom."
|
||||
|
||||
@@ -604,6 +590,47 @@ label ending:
|
||||
|
||||
$ 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
|
||||
@@ -736,10 +763,622 @@ init:
|
||||
plan[editing] = value
|
||||
editing = None
|
||||
|
||||
|
||||
|
||||
return plan
|
||||
|
||||
init:
|
||||
image movie = Movie()
|
||||
|
||||
python:
|
||||
povname = ""
|
||||
pov = DynamicCharacter("povname", color=(255, 0, 0, 255))
|
||||
|
||||
image eileen animated = Animation(
|
||||
"9a_vhappy.png", 1.0,
|
||||
"9a_happy.png", 1.0)
|
||||
|
||||
image smanim = anim.SMAnimation(
|
||||
"r",
|
||||
anim.State("r", Solid((255, 0, 0, 255))),
|
||||
anim.State("g", Solid((0, 255, 0, 255))),
|
||||
anim.State("b", Solid((0, 0, 255, 255))),
|
||||
|
||||
anim.Edge("r", .5, "g", dissolve),
|
||||
anim.Edge("r", .5, "b", dissolve),
|
||||
|
||||
anim.Edge("g", .5, "r", dissolve),
|
||||
anim.Edge("g", .5, "b", dissolve),
|
||||
|
||||
anim.Edge("b", .5, "r", dissolve),
|
||||
anim.Edge("b", .5, "g", dissolve),
|
||||
)
|
||||
|
||||
image cyan base = Image("cyan.png")
|
||||
|
||||
image cyan crop = im.Crop("cyan.png", 100, 0, 100, 200)
|
||||
|
||||
image cyan composite = im.Composite((200, 300),
|
||||
(0, 0), "cyan.png",
|
||||
(0, 50), "cyan.png",
|
||||
(0, 100), "cyan.png")
|
||||
|
||||
image cyan green = im.Map("cyan.png", bmap=im.ramp(0, 0))
|
||||
|
||||
image cyan alpha = im.Alpha("cyan.png", 0.5)
|
||||
image eileen alpha = im.Alpha("9a_happy.png", 0.5)
|
||||
|
||||
$ cyanpos = Position(xpos=700, xanchor='right', ypos=100, yanchor='top')
|
||||
|
||||
init:
|
||||
|
||||
$ slowcirciris = ImageDissolve("circiris.png", 5.0, 8)
|
||||
$ circirisout = ImageDissolve("circiris.png", 1.0, 8)
|
||||
$ circirisin = ImageDissolve("circiris.png", 1.0, 8, reverse=True)
|
||||
$ demotrans = ImageDissolve("demotrans.png", 3.0, 128)
|
||||
|
||||
image circiris = "circiris.png"
|
||||
|
||||
label demonstrate:
|
||||
|
||||
scene washington
|
||||
show eileen happy
|
||||
|
||||
e "I can give you a demonstration of some of the features in
|
||||
Ren'Py, but you'll have to tell me what it is you'd like to
|
||||
have demonstrated."
|
||||
|
||||
menu demo_menu:
|
||||
|
||||
"Simple transitions, updated in 4.8.5":
|
||||
|
||||
e "Okay, I can tell you about simple transitions. We call
|
||||
them simple because they aren't that flexible."
|
||||
|
||||
e "But don't let that get you down, since they're the
|
||||
transitions you'll probably use the most."
|
||||
|
||||
with None
|
||||
scene whitehouse
|
||||
show eileen happy
|
||||
with dissolve
|
||||
|
||||
e "The dissolve transition is probably the most useful,
|
||||
blending one scene into another."
|
||||
|
||||
with None
|
||||
with fade
|
||||
|
||||
e "The fade transition fades to black, and then fades back
|
||||
in to the new scene."
|
||||
|
||||
e "If you're going to stay at a black screen, you'll
|
||||
probably want to use dissolve rather than fade."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with pixellate
|
||||
|
||||
e "The pixellate transition pixellates out the old scene,
|
||||
switches to the new scene, and then unpixellates that."
|
||||
|
||||
e "It's probably not appropriate for most games, but we
|
||||
think it's kind of neat."
|
||||
|
||||
e "Finally, we can point out that motions can be used as
|
||||
transitions."
|
||||
|
||||
"..."
|
||||
|
||||
"......"
|
||||
|
||||
$ renpy.play('punch.wav')
|
||||
with vpunch
|
||||
|
||||
e "Hey! Pay attention."
|
||||
|
||||
e "I was about to demonstrate vpunch... well, I guess I just
|
||||
did."
|
||||
|
||||
$ renpy.play('punch.wav')
|
||||
with hpunch
|
||||
|
||||
e "We can also shake the screen horizontally, with hpunch."
|
||||
|
||||
"ImageDissolve transitions, added in 4.8.7.":
|
||||
|
||||
e "ImageDissolve allows us to have dissolve transitions that are
|
||||
controlled by images."
|
||||
|
||||
e "This lets us specify very complex transitions, fairly
|
||||
simply."
|
||||
|
||||
e "Let's try some, and then I'll show how they work."
|
||||
|
||||
e "There are two ImageDissolve transitions present by
|
||||
default in the standard library."
|
||||
|
||||
scene black with blinds
|
||||
scene washington
|
||||
show eileen happy
|
||||
with blinds
|
||||
|
||||
e "The blinds transition opens and closes what looks like
|
||||
vertical blinds."
|
||||
|
||||
scene black with squares
|
||||
scene washington
|
||||
show eileen happy
|
||||
with squares
|
||||
|
||||
e "The squares transition uses these squares to show
|
||||
things."
|
||||
|
||||
e "I'm not sure why anyone would want to use it, but it
|
||||
was used in some translated games, so we added it."
|
||||
|
||||
e "There are also a few transitions that aren't in the
|
||||
standard library."
|
||||
|
||||
e "These ones require images the size of the screen, and
|
||||
so we couldn't include them as the size of the screen
|
||||
can change from game to game."
|
||||
|
||||
e "You can find them defined in the source of the demo
|
||||
script."
|
||||
|
||||
scene black with circirisin
|
||||
|
||||
e "We can hide things with a circirisin..."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with circirisout
|
||||
|
||||
e "... and show them again with a circirisout."
|
||||
|
||||
e "It's even possible to have weird custom transitions."
|
||||
|
||||
scene circiris with demotrans
|
||||
|
||||
e "What we're showing here is the picture that's used in
|
||||
the circiris transitions."
|
||||
|
||||
e "If you take a look, the center of it is white, while
|
||||
the edges are darker."
|
||||
|
||||
e "When we use an ImageDissolve, the white will dissolve
|
||||
in first, followed by progressively darker colors."
|
||||
|
||||
e "Let's try it."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with slowcirciris
|
||||
|
||||
|
||||
|
||||
|
||||
e "It's also possible to reverse the transition, so that
|
||||
the black pixels are dissolved in first."
|
||||
|
||||
"CropMove transitions, added in 4.5.":
|
||||
|
||||
e "The CropMove transition class lets us provide a wide
|
||||
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 transition."
|
||||
|
||||
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 "While the slide transitions slide in the new scene, the
|
||||
slideaways slide out the old scene."
|
||||
|
||||
scene whitehouse with slideawayright
|
||||
|
||||
e "Slideawayright..."
|
||||
|
||||
scene washington with slideawayleft
|
||||
|
||||
e "...slideawayleft..."
|
||||
|
||||
scene whitehouse with slideawayup
|
||||
|
||||
e "...slideawayup..."
|
||||
|
||||
scene washington with slideawaydown
|
||||
|
||||
e "and slideawaydown."
|
||||
|
||||
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 "It's enough to make you feel a bit dizzy."
|
||||
|
||||
"Positions and movement, updated in 4.8.":
|
||||
|
||||
e "I'm not stuck standing in the middle of the screen,
|
||||
even though I like being the center of attention."
|
||||
|
||||
e "Positions, given with an at clause, specify where I'm
|
||||
standing."
|
||||
|
||||
e "The move transition moves around images that have
|
||||
changed position."
|
||||
|
||||
e "For example..."
|
||||
|
||||
show eileen happy at offscreenleft with move
|
||||
|
||||
e "I can move over to the offscreenleft position, just off
|
||||
the left side of the screen."
|
||||
|
||||
show eileen happy at left with move
|
||||
|
||||
e "The left position has my left side border the left
|
||||
margin of the screen."
|
||||
|
||||
show eileen happy at center with move
|
||||
|
||||
e "I can also move to the center..."
|
||||
|
||||
show eileen happy at right with move
|
||||
|
||||
e "... the right ..."
|
||||
|
||||
show eileen happy at offscreenright with move
|
||||
|
||||
e "... or even to offscreenright, off the right-hand side
|
||||
of the screen."
|
||||
|
||||
show eileen happy at right with move
|
||||
|
||||
e "We don't limit you to these five positions either. You
|
||||
can always create your own Position objects."
|
||||
|
||||
# This is necessary to restart the time at which we are
|
||||
# shown.
|
||||
hide eileen happy
|
||||
|
||||
show eileen happy at Move((1.0, 1.0, 'right', 'bottom'),
|
||||
(0.0, 1.0, 'left', 'bottom'),
|
||||
4.0, repeat=True, bounce=True)
|
||||
|
||||
e "It's also possible to have a movement happen while
|
||||
showing dialogue on the screen, using the Move function."
|
||||
|
||||
e "Move can repeat a movement, and even have it bounce
|
||||
back and forth, like I'm doing now."
|
||||
|
||||
scene onememorial at Pan((0, 800), (0, 0), 10.0) with dissolve
|
||||
|
||||
e "Finally, we can pan around an image larger than the
|
||||
screen, using the Pan function in an at
|
||||
clause."
|
||||
|
||||
e "That's what we're doing now, panning up a picture of
|
||||
the memorial to the Big Red One."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with dissolve
|
||||
|
||||
"Animation, updated in 4.8.5":
|
||||
|
||||
e "Ren'Py supports a number of ways of creating
|
||||
animations."
|
||||
|
||||
e "These animations let you vary images, independent of
|
||||
the user's clicks."
|
||||
|
||||
show eileen animated
|
||||
|
||||
e "For example, I'm switching my expression back and
|
||||
forth, once a second."
|
||||
|
||||
e "Even though you clicked, I'm still doing it."
|
||||
|
||||
e "This is an example of the Animation function at work."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "The Animation function is limited to simple lists of
|
||||
images, with fixed delays between them."
|
||||
|
||||
e "The sequence can repeat, or can stop after one
|
||||
go-through."
|
||||
|
||||
e "If you want more control, you can use the
|
||||
anim.SMAnimation function."
|
||||
|
||||
e "It can randomly change images, and even apply
|
||||
transitions to changes."
|
||||
|
||||
with None
|
||||
scene smanim
|
||||
show eileen happy
|
||||
with dissolve
|
||||
|
||||
e "Here, we randomly dissolve the background between red,
|
||||
green, and blue images."
|
||||
|
||||
e "Psychadelic."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with dissolve
|
||||
|
||||
e "It's probably best if we stop here, before somebody's
|
||||
brain explodes."
|
||||
|
||||
"Text tags, added in 4.7.":
|
||||
|
||||
e "Text tags let us control the appearance of text that is
|
||||
shown to the user."
|
||||
|
||||
e "Text tags can make text {b}bold{/b}, {i}italic{/i}, or
|
||||
{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 "There are also bold, italic, and underline style properties, which can
|
||||
be styled onto any text."
|
||||
|
||||
e "If you find yourself using text tags on every line, you
|
||||
should probably look at style properties instead."
|
||||
|
||||
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."
|
||||
|
||||
|
||||
"Music, sound and movies, updated in 4.5.":
|
||||
|
||||
e "Ren'Py supports a number of multimedia functions."
|
||||
|
||||
e "You're probably hearing music playing in the
|
||||
background."
|
||||
|
||||
|
||||
$ renpy.music_stop(fadeout=0.5)
|
||||
e "We can stop it..."
|
||||
|
||||
|
||||
$ renpy.music_start('sun-flower-slow-drag.mid')
|
||||
e "... and start it playing again."
|
||||
|
||||
# This plays a sound effect.
|
||||
$ renpy.play("18005551212.wav")
|
||||
|
||||
e "We can also play up to eight channels of sound effects
|
||||
on top of the music."
|
||||
|
||||
e "We ship, in the extras/ directory, code to support
|
||||
characters having voice."
|
||||
|
||||
e "Finally, we support playing mpeg movies."
|
||||
|
||||
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."
|
||||
|
||||
e "That's it for multimedia."
|
||||
|
||||
"Image Operations, added in 4.8.5":
|
||||
|
||||
e "Image operations allow one to manipulate images as they
|
||||
are loaded in."
|
||||
|
||||
e "These are efficent, as they are only evaluated when an
|
||||
image is first loaded."
|
||||
|
||||
e "This way, there's no extra work that needs to be done
|
||||
when each frame is drawn to the screen."
|
||||
|
||||
show eileen happy at left with move
|
||||
show cyan base at cyanpos with dissolve
|
||||
|
||||
e "Let me show you a test image, a simple cyan circle."
|
||||
|
||||
e "We'll be applying some image operations to it, to see
|
||||
how they can be used."
|
||||
|
||||
show cyan crop at cyanpos with dissolve
|
||||
|
||||
e "The im.Crop operation can take the image, and chop it
|
||||
up into a smaller image."
|
||||
|
||||
show cyan composite at cyanpos with dissolve
|
||||
|
||||
e "The im.Composite operation lets us take multiple images,
|
||||
and draw them into a single image."
|
||||
|
||||
e "While you can do this by showing multiple images, this
|
||||
is more efficent, if more complex."
|
||||
|
||||
show cyan green at cyanpos with dissolve
|
||||
|
||||
e "The im.Map operation lets us mess with the red, green,
|
||||
blue, and alpha channels of an image."
|
||||
|
||||
e "In this case, we removed all the blue from the image,
|
||||
leaving only the green component of cyan."
|
||||
|
||||
show cyan alpha at cyanpos with dissolve
|
||||
|
||||
e "The im.Alpha operation can adjust the alpha channel on
|
||||
an image, making things partially transparent."
|
||||
|
||||
show eileen alpha at left with dissolve
|
||||
|
||||
e "It's useful if a character just happens to be ghost."
|
||||
|
||||
with None
|
||||
hide cyan
|
||||
show eileen happy at left
|
||||
with dissolve
|
||||
|
||||
e "But that's not the case with me."
|
||||
|
||||
show eileen happy with move
|
||||
|
||||
|
||||
"User interaction.":
|
||||
|
||||
e "Ren'Py gives a number of ways of interacting with the
|
||||
user."
|
||||
|
||||
e "You've already seen say statements and menus."
|
||||
|
||||
e "We can also prompt the user to enter some text."
|
||||
|
||||
$ povname = renpy.input("What is your name?")
|
||||
|
||||
pov "My name is %(povname)s."
|
||||
|
||||
|
||||
e "Imagemaps let the user click on an image to make a
|
||||
choice."
|
||||
|
||||
# This is an imagemap. It consists of two images, and a list of
|
||||
# hotspots. For each hotspot we give the coordinates of the left,
|
||||
# top, right, and bottom sides, and the value to return if it is
|
||||
# picked.
|
||||
|
||||
$ result = renpy.imagemap("ground.png", "selected.png", [
|
||||
(100, 100, 300, 400, "eileen"),
|
||||
(500, 100, 700, 400, "lucy")
|
||||
])
|
||||
|
||||
# We've assigned the chosen result from the imagemap to the
|
||||
# result variable. We can use an if statement to vary what
|
||||
# happens based on the user's choice.
|
||||
|
||||
if result == "eileen":
|
||||
show eileen vhappy
|
||||
e "You picked me!"
|
||||
|
||||
elif result == "lucy":
|
||||
show eileen concerned
|
||||
e "It looks like you picked Lucy."
|
||||
|
||||
# Eileen is being a bit possesive here. :-P
|
||||
if date:
|
||||
e "You can forget about Saturday."
|
||||
$ date = False
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "While these constructs are probably enough for most
|
||||
visual novels, dating simulations may be more
|
||||
complicated."
|
||||
|
||||
e "The ui functions allow you to create quite complicated
|
||||
interfaces."
|
||||
|
||||
e "For example, try the following scheduling and stats screen,
|
||||
which could be used by a stat-based dating simulation."
|
||||
|
||||
$ day_planner()
|
||||
|
||||
e "The ui functions can be used to rewrite many parts of
|
||||
the interface."
|
||||
|
||||
e "Hopefully, this gives you enough power to write any
|
||||
visual novel you want."
|
||||
|
||||
|
||||
" " # Empty, so we have a blank line.
|
||||
|
||||
"That's enough for me.":
|
||||
|
||||
return
|
||||
|
||||
e "Is there anything else you want demonstrated?"
|
||||
|
||||
jump demo_menu
|
||||
|
||||
|
||||
+40
-8
@@ -10,7 +10,7 @@ def match_times(source, dest):
|
||||
def dosify(s):
|
||||
return s.replace("\n", "\r\n")
|
||||
|
||||
def copy_file(source, dest, license=""):
|
||||
def copy_file(source, dest, license="", dos=True):
|
||||
|
||||
print source, "->", dest
|
||||
|
||||
@@ -21,7 +21,8 @@ def copy_file(source, dest, license=""):
|
||||
|
||||
data = sf.read()
|
||||
if dest.endswith(".txt") or dest.endswith(".py") or dest.endswith(".rpy") or dest.endswith(".bat"):
|
||||
data = dosify(data)
|
||||
if dos:
|
||||
data = dosify(data)
|
||||
|
||||
df.write(data)
|
||||
|
||||
@@ -43,12 +44,18 @@ 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)
|
||||
|
||||
@@ -91,36 +98,61 @@ def main():
|
||||
|
||||
doc_files = [
|
||||
'example.html',
|
||||
'tutorial.html',
|
||||
'reference.html',
|
||||
'style.css',
|
||||
'RELEASING.txt',
|
||||
]
|
||||
|
||||
# Copy doc
|
||||
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("~"))
|
||||
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("~"))
|
||||
|
||||
def cp(x, license=""):
|
||||
copy_file(x, target + "/" + x)
|
||||
copy_tree("dse", target + "/dse",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
copy_tree("extras", target + "/extras",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
copy_tree("scripts", target + "/scripts",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
def cp(x, license="", dos=True):
|
||||
copy_file(x, target + "/" + x, dos=dos)
|
||||
|
||||
cp("CHANGELOG.txt")
|
||||
cp("LICENSE.txt")
|
||||
cp("README_RENPY.txt")
|
||||
cp("archive_images.bat")
|
||||
cp("lint.bat")
|
||||
cp("run_game.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_game.pyw", license=license)
|
||||
copy_file("run_game.py", target + "/run_dse.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_dse.pyw", license=license)
|
||||
cp("archiver.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")
|
||||
|
||||
|
||||
os.mkdir(target + "/module")
|
||||
|
||||
cp("module/README.txt")
|
||||
cp("module/_renpy.pyx")
|
||||
cp("module/_renpy.c")
|
||||
cp("module/core.c")
|
||||
cp("module/renpy.h")
|
||||
cp("module/setup.py")
|
||||
cp("module/setup_mac.py")
|
||||
cp("module/setup_win32.py")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
all:: tutorial.html example.html
|
||||
all:: reference.html example.html
|
||||
|
||||
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py tutorial.xml > tutorial.hi.xml
|
||||
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
|
||||
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
|
||||
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
|
||||
|
||||
|
||||
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml ../demo2/script.rpy
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
Releasing Ren'Py Games
|
||||
|
||||
PyTom <pytom@bishoujo.us>
|
||||
|
||||
Updated: 2005-05-28
|
||||
|
||||
|
||||
Okay, so you've finished creating a game using Ren'Py, and now you
|
||||
want to release it. Congratulations! This tech note will help you to
|
||||
package up your creation and send it into the world.
|
||||
|
||||
The first thing to do is to check to see if you have the latest
|
||||
version of Ren'Py. New versions of Ren'Py fix bugs in old versions. If
|
||||
you're not using the latest version, your game may have known bugs in
|
||||
it. At the very least, ask about what was fixed between versions if
|
||||
you insist on using an older version.
|
||||
|
||||
The next thing to do is to ensure that you actually have a finished
|
||||
and working game. Did you remember to run add_from? That tool will add
|
||||
from clauses to all of the call statements in your program, ensuring
|
||||
that everything continues working after a game is reloaded.
|
||||
|
||||
You'll also want to run the lint.bat tool. This is a tool that will
|
||||
check your Ren'Py game for subtle bugs. If it reports any bugs, you
|
||||
should fix them, and run lint.bat again until they're all fixed. If
|
||||
you've changed the name of the .exe, you'll probably need to edit
|
||||
lint.bat so that it runs. In general, you don't want to release a game
|
||||
with lint errors, or at least a good excuse for said errors.
|
||||
|
||||
If you're not totally sure that everything is A-OK, you can send me
|
||||
the game and I will check it over in confidence. If there's enough
|
||||
demand, I'll put together a tool that will elide the game text, so
|
||||
just the code and structure is left.
|
||||
|
||||
I also ask that you add a Ren'Py credit into your game. I tend to use
|
||||
the line "Powered by Ren'Py.", but feel free to word it however you
|
||||
want.
|
||||
|
||||
Another thing to do is to change the icon of the exe file. The best
|
||||
way to do this is to create a .ico file containing 48x48, 32x32, and
|
||||
16x16 versions of your icon, and then to use PE Resource Explorer to
|
||||
go in and change the icon of the file. Just delete off the existing
|
||||
icon, and then do a command like "Import image resource..." (going
|
||||
from memory here) to add in the new icon. Save the modified
|
||||
executable.
|
||||
|
||||
If you haven't done so already, you may want to rename the
|
||||
executable. This will also allow you to rename the game directory. In
|
||||
the case of moonlight walks, the executable was named moonlight.exe
|
||||
and the game directory was moonlight. The python file run_game.py was
|
||||
renamed moonlight.py. I'll use the name moonlight as a placeholder for
|
||||
the name of your game throughout the rest of this document.
|
||||
|
||||
Now, we have everything that goes into the final distribution. In
|
||||
fact, we have more than enough, as Ren'Py ships with a number of tools
|
||||
that aren't needed to run a game. So we need to decide which files to
|
||||
keep in various circumstances.
|
||||
|
||||
There are basically three different kinds of distributions you can
|
||||
make. The first is a windows executable distribution (abbreviated
|
||||
windows), that allows the user to run your game without having to
|
||||
download any additional software. The second kind is a
|
||||
python-dependent (python) distribution, which can run on any computer
|
||||
that supports python 2.3 and pygame. The final type of distribution
|
||||
that is supported is a cross platform distribution, which can run both
|
||||
on windows and on any computer that supports python.
|
||||
|
||||
To make a windows distribution, you need the files that I'll mark
|
||||
windows or all. To make a python distribution, you need the files that
|
||||
I'll mark python and all. And to make a cross-platform distribution,
|
||||
you'll need all the indicated files.
|
||||
|
||||
Anyway, the list of files that you may concievably want to include as
|
||||
part of your game are:
|
||||
|
||||
|
||||
common/ [all ] - A directory containing common script files that help
|
||||
Ren'Py work.
|
||||
|
||||
lib/ [windows] - A directory containing the code and libraries needed
|
||||
to run Ren'Py on windows.
|
||||
|
||||
moonlight/ [all] - The game directory containing your scripts and
|
||||
data. You probably do not want to distribute the saves/ directory
|
||||
inside here, so remove that first.
|
||||
|
||||
moonlight.exe [windows] - The game executable that the user runs.
|
||||
|
||||
moonlight.py [python] - The main python script that runs your game.
|
||||
|
||||
python23.dll [windows] - The DLL containing the python interpreter. It
|
||||
may change versions in the future, in which case the numbers will
|
||||
change.
|
||||
|
||||
renpy/ [python] - Contains the python source code for Ren'Py.
|
||||
|
||||
module/ [python] - Contains the source code for the _renpy module.
|
||||
|
||||
|
||||
If you're making a python or cross-platform distribution, remember
|
||||
that non-windows platforms have case-sensitive filenames. If you need
|
||||
help testing, I can help with that. (This caveat also goes for data
|
||||
files, like images and music.)
|
||||
|
||||
Anyway, once you have all of the files, you can zip them up (perhaps
|
||||
adding in things like README and license files) and post them on the
|
||||
web. You've just released a Ren'Py game!
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -7,6 +8,7 @@ import time
|
||||
import inspect
|
||||
|
||||
sys.path.append('..')
|
||||
sys.path.append('../module')
|
||||
import renpy
|
||||
|
||||
|
||||
@@ -84,6 +86,12 @@ 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__
|
||||
|
||||
@@ -97,6 +105,9 @@ def function(m):
|
||||
else:
|
||||
args = inspect.formatargspec(*inspect.getargspec(func))
|
||||
|
||||
args = re.sub(r"'\\x00\\x01.*?\\xff'", "im.ramp(0, 255)", args)
|
||||
|
||||
args = re.sub(r'<.*?>', '...', args)
|
||||
|
||||
docparas = []
|
||||
|
||||
@@ -126,6 +137,15 @@ def main():
|
||||
s = f.read()
|
||||
f.close()
|
||||
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
try:
|
||||
renpy.main.main("dump_styles")
|
||||
except "foo":
|
||||
pass
|
||||
|
||||
os.chdir("doc")
|
||||
|
||||
s = re.sub(r"<!-- func (\S+) -->", function, s)
|
||||
s = re.sub(r"<!-- include (\S+) -->", include, s)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,21 @@
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="defnindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//defn">
|
||||
<xsl:sort select="@name" />
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="@name" /></xsl:attribute>
|
||||
<xsl:value-of select="@name"/>
|
||||
(<xsl:value-of select="@type"/>)
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="varindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//var">
|
||||
@@ -161,5 +176,14 @@
|
||||
<xsl:apply-templates />
|
||||
</dd>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="defn">
|
||||
<a><xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute></a>
|
||||
<dt class="var"><b><xsl:value-of select="@name" /></b></dt>
|
||||
<dd>
|
||||
<xsl:apply-templates />
|
||||
</dd>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
</xsl:stylesheet>
|
||||
@@ -0,0 +1,132 @@
|
||||
# This contains code for the new day planner. You probably
|
||||
# don't want to change this file, but it might make sense to
|
||||
# change many of the variables or styles defined here from
|
||||
# other files.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
# A window placed behind the day planner (empty).
|
||||
style.create('dp_window', 'window')
|
||||
style.dp_window.ypos = 120
|
||||
style.dp_window.yanchor = 'top'
|
||||
|
||||
# The grid containing the three choices in the day planner.
|
||||
style.create('dp_grid', 'default')
|
||||
style.dp_grid.xfill = True
|
||||
|
||||
# Windows containing the groups of choices in the day planner.
|
||||
style.create('dp_choice', 'default')
|
||||
style.dp_choice.xpos = 0.5
|
||||
style.dp_choice.xanchor = 'center'
|
||||
|
||||
# Action buttons.
|
||||
style.create('dp_button', 'button')
|
||||
style.create('dp_selected_button', 'selected_button')
|
||||
style.create('dp_button_text', 'button_text')
|
||||
style.create('dp_selected_button_text', 'selected_button_text')
|
||||
|
||||
style.create('dp_done_button', 'button')
|
||||
style.create('dp_done_button_text', 'button_text')
|
||||
|
||||
style.dp_done_button.ypos = 0.95
|
||||
style.dp_done_button.yanchor = 'bottom'
|
||||
|
||||
# Labels.
|
||||
style.create('dp_label', 'default')
|
||||
style.dp_label.xpos = 0.5
|
||||
style.dp_label.xanchor = 'center'
|
||||
|
||||
# The amount of padding between the label and the action
|
||||
# in a choice.
|
||||
dp_padding = 12
|
||||
|
||||
# The amount of padding between the choices and done.
|
||||
dp_done_padding = 32
|
||||
|
||||
# The title of the done button.
|
||||
dp_done_title = "Done Planning Day"
|
||||
|
||||
python:
|
||||
|
||||
# A list of the periods of the day that are present in the
|
||||
# day planner. This can be changed at runtime.
|
||||
dp_period_names = [ "Morning", "Afternoon", "Evening" ]
|
||||
|
||||
# A list of variables that will take the selected choices
|
||||
# for each of the periods named above.
|
||||
dp_period_vars = [ "morning_act",
|
||||
"afternoon_act",
|
||||
"evening_act", ]
|
||||
|
||||
# A map from the period names to a list of the activities
|
||||
# available for that period. This may be conputed before
|
||||
# each call to day_planner. Each entry is a tuple, where
|
||||
# the first element is the name shown to the user, and the
|
||||
# second is the name assigned to the variable for that
|
||||
# period.
|
||||
dp_period_acts = {
|
||||
'Morning': [
|
||||
( 'Attend Class', "class" ),
|
||||
( 'Cut Class', "cut"),
|
||||
],
|
||||
|
||||
'Afternoon': [
|
||||
( 'Study', "study" ),
|
||||
( 'Hang Out', "hang" ),
|
||||
],
|
||||
|
||||
'Evening' : [
|
||||
( 'Exercise', "exercise" ),
|
||||
( 'Play Games', "play" ),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
# We assume that the various period variables have been assigned
|
||||
# default values by this point.
|
||||
label day_planner:
|
||||
|
||||
call show_stats from _call_show_stats_1
|
||||
|
||||
python hide:
|
||||
|
||||
ui.window(style='dp_window')
|
||||
ui.vbox(dp_done_padding)
|
||||
|
||||
ui.grid(len(dp_period_names), 1, xfill=True, style='dp_grid')
|
||||
|
||||
for period, var in zip(dp_period_names, dp_period_vars):
|
||||
|
||||
ui.window(style='dp_choice')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(period, "dp")
|
||||
ui.null(height=dp_padding)
|
||||
|
||||
for label, value in dp_period_acts[period]:
|
||||
|
||||
def clicked(var=var, value=value):
|
||||
setattr(store, var, value)
|
||||
return True
|
||||
|
||||
_button_factory(label, "dp",
|
||||
selected = getattr(store, var) == value,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
_button_factory(dp_done_title, "dp_done",
|
||||
clicked = lambda : False )
|
||||
|
||||
ui.close()
|
||||
|
||||
|
||||
if ui.interact():
|
||||
jump day_planner
|
||||
|
||||
return
|
||||
@@ -0,0 +1,258 @@
|
||||
# The Ren'Py/DSE event dispatcher. This file contains the code that
|
||||
# actually supports the running of events. Specifically, it contains
|
||||
# code that determines which events are available, which events can
|
||||
# run, and to actually run the events that should be run during a
|
||||
# given period.
|
||||
|
||||
# This isn't really intended to be user-changable.
|
||||
|
||||
init -100:
|
||||
python:
|
||||
|
||||
# A list of all of the events that the system knowns about,
|
||||
# it's filtered to determine which events should run when.
|
||||
all_events = [ ]
|
||||
|
||||
# The base class for events. When constructed, an event
|
||||
# automatically adds itself to all_events.
|
||||
#
|
||||
# The first parameter for this is a unique name for the event,
|
||||
# which is also used as the label that is called when the
|
||||
# event executes.
|
||||
#
|
||||
# All other parameters are expressions. These expressions can
|
||||
# be either strings, or objects having an eval method. Many
|
||||
# interesting objects are given below.
|
||||
#
|
||||
# Keyword arguments are also kept on the object. Currently,
|
||||
# there is one useful keyword argument, priority. This
|
||||
# controls the order in which events are in the event list.
|
||||
# (Events with lower priority number are evaluated first. If a
|
||||
# priority is not specified, it's 100.)
|
||||
class event(object):
|
||||
|
||||
def __repr__(self):
|
||||
return '<event ' + self.name + '>'
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
|
||||
self.name = name
|
||||
|
||||
exprs = [ ]
|
||||
|
||||
for i in args:
|
||||
if isinstance(i, basestring):
|
||||
exprs.append(event.evaluate(i))
|
||||
else:
|
||||
exprs.append(i)
|
||||
|
||||
self.exprs = exprs
|
||||
|
||||
self.priority = kwargs.get('priority', 100)
|
||||
|
||||
all_events.append(self)
|
||||
|
||||
# Checks to see if this event is valid. It's calles with
|
||||
# a list of events that have already checked out to be
|
||||
# True, and returns True if this event checks out.
|
||||
def check(self, valid):
|
||||
|
||||
for i in self.exprs:
|
||||
if not i.eval(self.name, valid):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# The base class for all of the event checks given below.
|
||||
class event_check(object):
|
||||
|
||||
def __invert__(self):
|
||||
return event.false(self)
|
||||
|
||||
def __and__(self, other):
|
||||
return event.and_op(self, other)
|
||||
|
||||
def __or__(self, other):
|
||||
return event.or_op(self, other)
|
||||
|
||||
|
||||
# This evaluates the expression given as an argument, and the
|
||||
# returns true if it evaluates to true.
|
||||
class evaluate(event_check):
|
||||
|
||||
def __init__(self, expr):
|
||||
self.expr = expr
|
||||
|
||||
def eval(self, name, valid):
|
||||
return eval(self.expr)
|
||||
|
||||
# If present as a condition to an event, an object of this
|
||||
# type ensures that the event will only execute once.
|
||||
class once(event_check):
|
||||
def eval(self, name, valid):
|
||||
return name not in events_executed
|
||||
|
||||
# Returns True if no event of higher priority can execute,
|
||||
# and false otherwise. In general, solo events should be
|
||||
# the lowest priority, and are run if nothing else is.
|
||||
class solo(event_check):
|
||||
def eval(self, name, valid):
|
||||
|
||||
# True if valid is empty.
|
||||
return not valid
|
||||
|
||||
# Returns True if the given events have happend already,
|
||||
# at any time in the game. False if at least one hasn't
|
||||
# happened yet.
|
||||
class happened(event_check):
|
||||
def __init__(self, *events):
|
||||
self.events = events
|
||||
|
||||
def eval(self, name, valid):
|
||||
for i in self.events:
|
||||
if i not in events_executed:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Evaluates its argument, and returns true if it is false
|
||||
# and vice-versa.
|
||||
class false(event_check):
|
||||
def __init__(self, cond):
|
||||
self.cond = cond
|
||||
|
||||
def eval(self, name, valid):
|
||||
return not self.cond.eval(name, valid)
|
||||
|
||||
# Handles the and operator.
|
||||
class and_op(event_check):
|
||||
|
||||
def __init__(self, *args):
|
||||
self.args = args
|
||||
|
||||
def eval(self, name, valid):
|
||||
for i in self.args:
|
||||
if not i.eval(name, valid):
|
||||
return False
|
||||
return True
|
||||
|
||||
# Handles the or operator.
|
||||
class or_op(event_check):
|
||||
|
||||
def __init__(self, *args):
|
||||
self.args = args
|
||||
|
||||
def eval(self, name, valid):
|
||||
for i in self.args:
|
||||
if i.eval(name, valid):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Returns True if all of the events given as arguments have
|
||||
# happened, or False otherwise.
|
||||
class depends(event_check):
|
||||
def __init__(self, *events):
|
||||
self.events = events
|
||||
|
||||
def eval(self, name, valid):
|
||||
for i in self.events:
|
||||
if i not in events_executed_yesterday:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
# The number of periods to skip.
|
||||
skip_periods = 0
|
||||
|
||||
# This returns True if the current period should be skipped,
|
||||
# or False if the current period should execute. If it returns
|
||||
# True, it decrements skip_periods.
|
||||
def check_skip_period():
|
||||
|
||||
global skip_periods
|
||||
|
||||
if skip_periods:
|
||||
skip_periods -= 1
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# This should be called when the game starts. It inits the various
|
||||
# data structures used by the events.
|
||||
label events_init:
|
||||
|
||||
# Stores the events that have been executed.
|
||||
$ events_executed = { }
|
||||
|
||||
# Stores the events that have been executed before today.
|
||||
# (Where today is ended by a call to events_end_day.)
|
||||
$ events_executed_yesterday = { }
|
||||
|
||||
return
|
||||
|
||||
# This should called at the end of a (game) day, to let things
|
||||
# like depends_yesterday to work.
|
||||
label events_end_day:
|
||||
|
||||
$ skip_periods = 0
|
||||
|
||||
python hide:
|
||||
|
||||
# We can't skip between days.
|
||||
skip_periods = 0
|
||||
|
||||
for k in events_executed:
|
||||
events_executed_yesterday[k] = True
|
||||
|
||||
return
|
||||
|
||||
# This is called once per period, to determine, and then execute, the
|
||||
# events that should be run for that period.
|
||||
label events_run_period:
|
||||
|
||||
$ events = [ ]
|
||||
|
||||
python hide:
|
||||
|
||||
for i in all_events:
|
||||
if i.check(events):
|
||||
events.append(i.name)
|
||||
|
||||
while not check_skip_period() and events:
|
||||
|
||||
$ _event = events.pop(0)
|
||||
$ events_executed[_event] = True
|
||||
|
||||
call expression _event from call_expression_event_1
|
||||
|
||||
return
|
||||
|
||||
# If this is jumped to, it will end the current period immediately,
|
||||
# and return control to the main program.
|
||||
label events_end_period:
|
||||
|
||||
$ skip_period = 1
|
||||
|
||||
return
|
||||
|
||||
# If this is jumped to, it will end the current period and skip
|
||||
# the next period. Use this if, say, the user goes on a date that
|
||||
# takes up two time slots.
|
||||
label events_skip_period:
|
||||
|
||||
$ skip_period = 2
|
||||
return
|
||||
|
||||
|
||||
init 100:
|
||||
python:
|
||||
|
||||
# Sort all events on priority. Schwartzian transform time.
|
||||
all_events = [ (i.priority, i) for i in all_events ]
|
||||
all_events.sort()
|
||||
all_events = [ b for a, b in all_events ]
|
||||
|
||||
|
||||
+747
@@ -0,0 +1,747 @@
|
||||
# This file contains the events that will be part of the game. It's
|
||||
# expected that the user will add and remove events as appropriate
|
||||
# for this game.
|
||||
|
||||
|
||||
# Some characters that are used in events in the game.
|
||||
init:
|
||||
$ t = Character('Teacher')
|
||||
$ gg = Character('Glasses Girl', color=(192, 255, 192, 255))
|
||||
$ sg = Character('Sporty Girl', color=(255, 255, 192, 255))
|
||||
$ bg = Character('Both Girls')
|
||||
|
||||
|
||||
# First up, we define some simple events for the various actions, that
|
||||
# are run only if no higher-priority event is about to occur.
|
||||
|
||||
init:
|
||||
$ event("class", "act == 'class'", event.solo(), priority=200)
|
||||
$ event("cut", "act == 'cut'", event.solo(), priority=200)
|
||||
$ event("study", "act == 'study'", event.solo(), priority=200)
|
||||
$ event("hang", "act == 'hang'", event.solo(), priority=200)
|
||||
$ event("exercise", "act == 'exercise'", event.solo(), priority=200)
|
||||
$ event("play", "act == 'play'", event.solo(), priority=200)
|
||||
|
||||
label class:
|
||||
|
||||
"I make it to class just in time, and proceed to listen to the
|
||||
teacher droning on about a wide range of topics, none of which
|
||||
are remotely interesting."
|
||||
|
||||
return
|
||||
|
||||
label cut:
|
||||
|
||||
"I cut class, and spend the morning goofing off instead."
|
||||
$ intelligence -= 10
|
||||
|
||||
return
|
||||
|
||||
label study:
|
||||
|
||||
"I head on down to the library, and start reading about the topics
|
||||
I should have been reading about in class."
|
||||
|
||||
$ intelligence += 10
|
||||
return
|
||||
|
||||
label hang:
|
||||
|
||||
"I spend the afternoon hanging out with my friends, killing
|
||||
some time."
|
||||
|
||||
return
|
||||
|
||||
label exercise:
|
||||
|
||||
"I decide to go out for a run through the town, to keep myself in
|
||||
shape."
|
||||
|
||||
$ strength += 10
|
||||
return
|
||||
|
||||
label play:
|
||||
|
||||
"I pop a DVD into my video game console, and spend the evening
|
||||
rolling small cities up into balls."
|
||||
|
||||
$ strength -= 10
|
||||
return
|
||||
|
||||
|
||||
# Below here are special events that are triggered when certain
|
||||
# conditions are true.
|
||||
|
||||
# This is an introduction event, that runs once when we first go
|
||||
# to class.
|
||||
|
||||
init:
|
||||
$ event("introduction", "act == 'class'", event.once())
|
||||
|
||||
label introduction:
|
||||
|
||||
"I run to school, and make it to my seat just as the bell
|
||||
signalling the start of class rings."
|
||||
|
||||
t "Before we start, I have an announcement to make."
|
||||
|
||||
t "We will have two new students joining us. Girls, come on in."
|
||||
|
||||
"Two girls walk in, and stand in front of the class."
|
||||
|
||||
"They're twins."
|
||||
|
||||
"Identical twins."
|
||||
|
||||
"Identical black hair, and the same pretty face."
|
||||
|
||||
"Despite that, it's still fairly easy to tell them apart."
|
||||
|
||||
"The one on the left is wearing glasses."
|
||||
|
||||
"Not too thick, but enough to let me know she probably reads alot
|
||||
of books."
|
||||
|
||||
"If I look a little closely, I can find another difference."
|
||||
|
||||
"The one on the right probably exercises a bit more."
|
||||
|
||||
"I can tell by the muscle tone in her legs."
|
||||
|
||||
"I realize that I'm staring at her legs, and quickly look up."
|
||||
|
||||
"Suddenly, I realize that she's been talking for all this town."
|
||||
|
||||
sg "... to this town. And we hope to be friends with all of you."
|
||||
|
||||
sg "Well, that's about it. Sis, do you have anything to say?"
|
||||
|
||||
"The girl with glasses pauses for a second, and then quickly says:"
|
||||
|
||||
gg "{size=-4}It's good to meet you all.{/size}"
|
||||
|
||||
"She stops, and goes back to not saying anything."
|
||||
|
||||
t "Well, if that's all, you can take your seats and we can start
|
||||
the class."
|
||||
|
||||
"They do, and our teacher begins his lecture."
|
||||
|
||||
"I don't think anyone pays much attention to it, however."
|
||||
|
||||
return
|
||||
|
||||
|
||||
# These are the events with glasses girl.
|
||||
|
||||
init:
|
||||
# The glasses girl is studying in the library, but we do not
|
||||
# talk to her.
|
||||
#
|
||||
#
|
||||
$ event("gg_studying",
|
||||
|
||||
# This takes place when the action is 'study'.
|
||||
"act == 'study'",
|
||||
|
||||
# This will only take place if no higher-priority
|
||||
# event will occur.
|
||||
event.solo(),
|
||||
|
||||
# This takes place at least one day after seeing the
|
||||
# introduction event.
|
||||
event.depends("introduction"),
|
||||
|
||||
# This takes priority over the study event.
|
||||
priority=190)
|
||||
|
||||
# She asks to borrow our pen.
|
||||
$ event("borrow_pen",
|
||||
|
||||
# This takes place when we go to study, and we have an int
|
||||
# >= 50.
|
||||
"act == 'study' and intelligence >= 50",
|
||||
|
||||
# It runs only once.
|
||||
event.once(),
|
||||
|
||||
# It requires the introduction event to have run at least
|
||||
# one day before.
|
||||
event.depends("introduction"))
|
||||
|
||||
# After the pen, she smiles when she sees us.
|
||||
$ event("gg_smiling", "act == 'study'",
|
||||
event.solo(), event.depends("borrow_pen"),
|
||||
priority = 180)
|
||||
|
||||
# The bookslide.
|
||||
$ event("bookslide", "act == 'study' and intelligence == 100",
|
||||
event.once(), event.depends("borrow_pen"))
|
||||
|
||||
# She makes us cookies.
|
||||
$ event("cookies", "act == 'study'",
|
||||
event.once(), event.depends("bookslide"))
|
||||
|
||||
# Her solo ending.
|
||||
$ event("gg_confess", "act == 'class'",
|
||||
event.once(), event.depends("cookies"))
|
||||
|
||||
|
||||
label gg_studying:
|
||||
|
||||
"I head to the library, to get some studying done."
|
||||
|
||||
"The glasses girl is there, but she's busy reading a book, taking
|
||||
notes as she does so."
|
||||
|
||||
"I decide not to disturb her, and instead start reading my own
|
||||
book."
|
||||
|
||||
$ intelligence += 10
|
||||
|
||||
return
|
||||
|
||||
label borrow_pen:
|
||||
|
||||
"I head to the library, to get some studying done."
|
||||
|
||||
"The glasses girl is there, but she's busy reading a book."
|
||||
|
||||
"I decide not to disturb her, and instead start reading my own
|
||||
book."
|
||||
|
||||
"Suddenly, I feel a tap on my shoulder."
|
||||
|
||||
"I look up, and see the glasses girl standing right next to me."
|
||||
|
||||
gg "Excuse me, but can I borrow your pen?"
|
||||
|
||||
gg "Mine ran out of ink."
|
||||
|
||||
"I dig through my bag, to find the pen I had stashed there."
|
||||
|
||||
"While I'm looking, I point out that she seems to come to the
|
||||
library alot."
|
||||
|
||||
gg "Hm... I guess you're right."
|
||||
|
||||
gg "There's so much stuff here, and I want to know about it all."
|
||||
|
||||
gg "Surely, you must feel the same way, as you're here almost as
|
||||
much as I am."
|
||||
|
||||
"I don't have the heart to tell her that I'm only here to study so
|
||||
that I don't fail out."
|
||||
|
||||
"My hand brushes the pen, and I quickly pull it out and give it to
|
||||
her."
|
||||
|
||||
gg "Thank you."
|
||||
|
||||
"She says, and she goes back to studying."
|
||||
|
||||
return
|
||||
|
||||
label gg_smiling:
|
||||
|
||||
"I head to the library, to get some studying done."
|
||||
|
||||
"The glasses girl is there, and smiles at me for a second before
|
||||
turning back to her book."
|
||||
|
||||
"I decide not to disturb her, and instead start reading my own
|
||||
book."
|
||||
|
||||
$ intelligence += 10
|
||||
|
||||
return
|
||||
|
||||
label bookslide:
|
||||
|
||||
"I head to the library, to get some studying done."
|
||||
|
||||
"The glasses girl is standing right by the entrance, putting a
|
||||
book back into a bookcase containing science books."
|
||||
|
||||
"It looks quite old, and quite weak, as if it could break at any
|
||||
time."
|
||||
|
||||
"Suddenly, I hear a loud crack come from the bookcase."
|
||||
|
||||
"Without thinking, I throw myself between the girl and the
|
||||
bookcase, pushing her out of the way in the process."
|
||||
|
||||
"As the shelves fail one by one, I'm hit with large textbooks on
|
||||
topics ranging from Astronomy to Zoology."
|
||||
|
||||
"She's safe, but I'm knocked off my feet by the falling books."
|
||||
|
||||
"Before the dust even settled, the girl with glasses realized what
|
||||
happened and asked:"
|
||||
|
||||
gg "Are you alright?"
|
||||
|
||||
"I tell her that I am, all the while rubbing a bruise left by a
|
||||
particularly large Physics book."
|
||||
|
||||
gg "You saved me."
|
||||
|
||||
"She points out. I shrug... I guess I did, but it's not like I'm a
|
||||
hero or anything."
|
||||
|
||||
"She extends out her hand, I take it, and she helps me to get up."
|
||||
|
||||
gg "Wow... Um..."
|
||||
|
||||
"She doesn't know what to say."
|
||||
|
||||
"I suggest that we help clean up the mess, mostly to take her off
|
||||
the spot."
|
||||
|
||||
"She agrees, and together we begin piling the books up into neat
|
||||
piles."
|
||||
|
||||
return
|
||||
|
||||
|
||||
label cookies:
|
||||
|
||||
"I head to the library, to get some studying done."
|
||||
|
||||
"The glasses girl is there, apparently waiting for me."
|
||||
|
||||
"She's holding a package in her hands."
|
||||
|
||||
gg "Here."
|
||||
|
||||
"She says, and she hands me the package."
|
||||
|
||||
"I take it from her, and open it."
|
||||
|
||||
"It contains fresh homemade cookies. Ginger snaps, I think."
|
||||
|
||||
gg "It's to thank you."
|
||||
|
||||
"She points out... She's probably not used to this. Especially
|
||||
with guys."
|
||||
|
||||
"I take one of them, and stick it in my mouth."
|
||||
|
||||
"The taste is exquisite."
|
||||
|
||||
"It's perhaps one of the best cookies I've ever tasted."
|
||||
|
||||
"Of course it is. It's the only cookie I'd ever tasted that was
|
||||
made for me by a beutiful girl."
|
||||
|
||||
"I tell her this... that it's delicious, not the girl part."
|
||||
|
||||
"But I do let slip that if I could eat these every day, I'd be the
|
||||
happiest guy in the world."
|
||||
|
||||
"At this, she can only blush."
|
||||
|
||||
return
|
||||
|
||||
label gg_confess:
|
||||
|
||||
"I once again barely make it to class on time."
|
||||
|
||||
"I sit down, at my desk, and put some of my books into it."
|
||||
|
||||
"My hand brushes a folded sheet of paper, one I didn't remember
|
||||
putting in there."
|
||||
|
||||
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
|
||||
|
||||
"That's all it says... no signature or anything."
|
||||
|
||||
"Lunch is a few hours away, but the time passes like a blur."
|
||||
|
||||
"It's all I can do to avoid racing up to the roof... but I give it
|
||||
some time anyway."
|
||||
|
||||
"It wouldn't make sense for me to get there first."
|
||||
|
||||
"I let two minutes elapse before leaving the classroom, and then
|
||||
slowly walk the flights of stairs up to the roof."
|
||||
|
||||
"Standing there, I find the glasses girl."
|
||||
|
||||
"She's holding what looks like a homemade lunch... big enough for
|
||||
two."
|
||||
|
||||
"I look at it, then her, then remember what I had said after she
|
||||
made me the cookies."
|
||||
|
||||
"Finally, I ask her... \"Does this mean?\""
|
||||
|
||||
"She nods. It's all the confirmation I need."
|
||||
|
||||
"I sit down next to my new girlfriend... and together we start
|
||||
eating her lunch."
|
||||
|
||||
"I'm probably the happiest guy in the world."
|
||||
|
||||
"But I still have to find out her name."
|
||||
|
||||
".:. Ending 1."
|
||||
|
||||
$ renpy.full_restart()
|
||||
|
||||
|
||||
init:
|
||||
|
||||
$ event("catchme", "act == 'exercise'",
|
||||
event.depends('introduction'), event.once())
|
||||
|
||||
$ event("cantcatchme", "act == 'exercise'",
|
||||
event.depends('catchme'), event.solo(), priority=190)
|
||||
|
||||
$ event("caughtme", "act == 'exercise' and strength >= 50",
|
||||
event.depends('catchme'), event.once())
|
||||
|
||||
$ event("together", "act == 'exercise' and strength >= 50",
|
||||
event.depends('caughtme'), event.solo(), priority=180)
|
||||
|
||||
$ event("apart", "act == 'exercise' and strength < 50",
|
||||
event.depends('caughtme'), event.solo(), priority=180)
|
||||
|
||||
$ event("pothole", "act == 'exercise' and strength >= 100",
|
||||
event.depends('caughtme'), event.once())
|
||||
|
||||
$ event("dontsee", "act == 'exercise'",
|
||||
event.depends('pothole'), event.solo(), priority=170)
|
||||
|
||||
$ event("sg_confess", "act == 'class'",
|
||||
event.depends('dontsee'), event.once())
|
||||
|
||||
|
||||
label catchme:
|
||||
|
||||
"I decide to go out for a run, to keep myself in shape."
|
||||
|
||||
"As I'm running through the town, I see a girl."
|
||||
|
||||
"She's one of the twins who transferred into my class."
|
||||
|
||||
"She waves, and comes over to me."
|
||||
|
||||
sg "I didn't know you were a runner."
|
||||
|
||||
"I point out that I'm not really a runner... I just run a little
|
||||
bit at a time."
|
||||
|
||||
"I ask her if she wants to run with me for a while."
|
||||
|
||||
sg "As if! You couldn't keep up with me."
|
||||
|
||||
"I point out that I probably can."
|
||||
|
||||
sg "Well, let's see."
|
||||
|
||||
"We set off running, but she quickly pulls past me."
|
||||
|
||||
sg "See? Well, maybe we can try it again when you're a bit
|
||||
faster."
|
||||
|
||||
sg "Until then, later."
|
||||
|
||||
"Even though I'm jogging, she pulls away as if it is nothing."
|
||||
|
||||
return
|
||||
|
||||
label cantcatchme:
|
||||
|
||||
"I'm out running again, when the sporty girl catches up to me."
|
||||
|
||||
sg "Still at it?"
|
||||
|
||||
sg "Well, keep up the good work. One day you'll be as fast as me!"
|
||||
|
||||
sg "Well, maybe."
|
||||
|
||||
"She pulls out past me, and disappears into the distance. One day
|
||||
I'll catch up to her."
|
||||
|
||||
$ strength += 10
|
||||
|
||||
return
|
||||
|
||||
label caughtme:
|
||||
|
||||
"I'm out running again, when the sporty girl catches up to me."
|
||||
|
||||
sg "Still at it?"
|
||||
|
||||
sg "Well, keep up the good work. One day you'll be as fast as me!"
|
||||
|
||||
sg "Well, maybe."
|
||||
|
||||
|
||||
"Today, however, I'm not about to let this stand unchallenged."
|
||||
|
||||
"I break out into a run, and for the first time ever, I keep up
|
||||
with her."
|
||||
|
||||
"We both run, neck and neck, me keeping up with her."
|
||||
|
||||
"Finally, she starts slowing down, and we come to a stop
|
||||
together."
|
||||
|
||||
sg "Not bad."
|
||||
|
||||
"She pauses to catch her breath."
|
||||
|
||||
sg "You've been practicing, and it really shows."
|
||||
|
||||
sg "You've finally become fast enough to run with me."
|
||||
|
||||
"I nod, accepting her praise."
|
||||
|
||||
sg "We should do this more often... it's better to run with
|
||||
someone else, to keep the challenge up."
|
||||
|
||||
"I nod again."
|
||||
|
||||
sg "Well, shall we go?"
|
||||
|
||||
"I nod a third time, and we take off, running side by side."
|
||||
|
||||
$ strength += 10
|
||||
|
||||
return
|
||||
|
||||
label together:
|
||||
|
||||
"I start running, and meet up with the sporty girl as she passes
|
||||
the street in front of my house."
|
||||
|
||||
"She's still better than me... she's been running for over a mile
|
||||
before reaching this point."
|
||||
|
||||
"Still, I can keep up with her for the rest of the run. And that's
|
||||
not bad."
|
||||
|
||||
$ strength += 10
|
||||
|
||||
return
|
||||
|
||||
label apart:
|
||||
|
||||
"I start running, and meet up with the sporty girl as she passes
|
||||
the street in front of my house."
|
||||
|
||||
"I try to keep up with her, but she pulls away from me."
|
||||
|
||||
"When she's a block away, she slows down and lets me catch up."
|
||||
|
||||
sg "It's your own fault... this is what you get for not
|
||||
practicing."
|
||||
|
||||
"She's right, of course, and I redouble my efforts to try to keep
|
||||
up with her."
|
||||
|
||||
$ strength += 10
|
||||
return
|
||||
|
||||
label pothole:
|
||||
|
||||
"I start running, and meet up with the sporty girl as she passes
|
||||
the street in front of my house."
|
||||
|
||||
"We run together for several miles."
|
||||
|
||||
"I think about how much I've improved in our time together."
|
||||
|
||||
"And, although she probably won't admit it, I think she's improved
|
||||
as well."
|
||||
|
||||
"I guess a little friendly competition is usually for the best."
|
||||
|
||||
"A small yelp pulls me out of my thought."
|
||||
|
||||
sg "Ow!"
|
||||
|
||||
"The sporty girl sits down, grabbing her ankle."
|
||||
|
||||
"I ask her what happened."
|
||||
|
||||
sg "I... hit a... pothole. Twisted my... ankle."
|
||||
|
||||
"I wince in sympathy."
|
||||
|
||||
"We wait a bit. I'm not sure what to do."
|
||||
|
||||
"Finally, I ask her if she can walk on it."
|
||||
|
||||
"She tries for a bit, but then winces in pain."
|
||||
|
||||
sg "No, I don't think so."
|
||||
|
||||
"I realize that we can't stay here."
|
||||
|
||||
"And so, I crouch down and motion for her to climb up onto my
|
||||
back."
|
||||
|
||||
sg "What are you doing?"
|
||||
|
||||
"I explain that she can't stay out in the middle of the street
|
||||
forever, and she won't get any help until I can get her home."
|
||||
|
||||
"And the only way to do that is for me to carry her."
|
||||
|
||||
"She accepts this, and climbs up onto my back."
|
||||
|
||||
"She wraps her arms around my neck, and I place my hands
|
||||
underneath her to make a seat."
|
||||
|
||||
"I stand up, and start carrying her home."
|
||||
|
||||
return
|
||||
|
||||
label dontsee:
|
||||
|
||||
"I go running again."
|
||||
|
||||
"But this time, I don't see the sporty girl."
|
||||
|
||||
"I finish the course that we usually take, but it's not the same
|
||||
without her."
|
||||
|
||||
return
|
||||
|
||||
label sg_confess:
|
||||
|
||||
"I once again barely make it to class on time."
|
||||
|
||||
"I sit down, at my desk, and put some of my books into it."
|
||||
|
||||
"My hand brushes a folded sheet of paper, one I didn't remember
|
||||
putting in there."
|
||||
|
||||
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
|
||||
|
||||
"That's all it says... no signature or anything."
|
||||
|
||||
"Lunch is a few hours away, but the time passes like a blur."
|
||||
|
||||
"It's all I can do to avoid racing up to the roof... but I give it
|
||||
some time anyway."
|
||||
|
||||
"It wouldn't make sense for me to get there first."
|
||||
|
||||
"I let two minutes elapse before leaving the classroom, and then
|
||||
slowly walk the flights of stairs up to the roof."
|
||||
|
||||
"Standing there, I find the sporty girl."
|
||||
|
||||
"She's leaning on a crutch."
|
||||
|
||||
"I look at it for a second, and she notices that."
|
||||
|
||||
sg "I went to the doctor, and he gave me this."
|
||||
|
||||
sg "Looks like we won't be running together for a while."
|
||||
|
||||
"I nod."
|
||||
|
||||
sg "And that's why I asked you here."
|
||||
|
||||
sg "I couldn't stand the though of not seeing you for a few
|
||||
weeks."
|
||||
|
||||
"I search my feelings, and realize I feel the same way."
|
||||
|
||||
sg "So I thought..."
|
||||
|
||||
"She doesn't say it... she doesn't need to."
|
||||
|
||||
"We both know how we feel about each other."
|
||||
|
||||
"And with that, we went from being running partners to partners in
|
||||
a deeper sense."
|
||||
|
||||
"Now if I only knew her name..."
|
||||
|
||||
".:. Ending 2."
|
||||
|
||||
$ renpy.full_restart()
|
||||
|
||||
|
||||
init:
|
||||
|
||||
# This needs to be higher-priority than either girl's ending.
|
||||
$ event('both_confess', 'act == "class"',
|
||||
event.depends("dontsee"), event.depends("cookies"),
|
||||
event.once(), priority = 50)
|
||||
|
||||
label both_confess:
|
||||
|
||||
"I once again barely make it to class on time."
|
||||
|
||||
"I sit down, at my desk, and put some of my books into it."
|
||||
|
||||
"My hand brushes a folded sheet of paper, then another."
|
||||
|
||||
"I take the first one out, and read it."
|
||||
|
||||
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
|
||||
|
||||
"That's all it says... no signature or anything."
|
||||
|
||||
"I take a look at the second one, and it says the same thing."
|
||||
|
||||
"Sure, the handwriting is a little different, but..."
|
||||
|
||||
"Lunch is a few hours away, but the time passes like a blur."
|
||||
|
||||
"It's all I can do to avoid racing up to the roof... but I give it
|
||||
some time anyway."
|
||||
|
||||
"I let two minutes elapse before leaving the classroom, and then
|
||||
slowly walk the flights of stairs up to the roof."
|
||||
|
||||
"Standing there are the twins."
|
||||
|
||||
"Both of them."
|
||||
|
||||
"As in, the two notes came from the two twins."
|
||||
|
||||
"I ask them what they are doing there, feigning ignorance."
|
||||
|
||||
sg "Well, I invited you up here to confess to you..."
|
||||
|
||||
sg "... and then I found out that my sister here was about to do
|
||||
the same thing."
|
||||
|
||||
gg "{size=-4}...I was...{/size}"
|
||||
|
||||
sg "When we found out, we were quite shocked, but after comparing
|
||||
notes, we decide what we're going to do..."
|
||||
|
||||
"I quickly run through the possibilities in my head."
|
||||
|
||||
"The best cases involve them never talking to me again."
|
||||
|
||||
"The worst cases involve me being thrown off the roof."
|
||||
|
||||
bg "We're going to share you!"
|
||||
|
||||
"Eh?"
|
||||
|
||||
"That wasn't something I considered."
|
||||
|
||||
"Each of the girls grabs onto one of my arms."
|
||||
|
||||
"I don't know what the future holds for us..."
|
||||
|
||||
"... and I don't know if this will work out."
|
||||
|
||||
"But I do know that one day I will work up the courage to find out
|
||||
their names."
|
||||
|
||||
".:. Ending 3."
|
||||
|
||||
$ renpy.full_restart()
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
# This is the main program. This can be changed quite a bit to
|
||||
# customize it for your program... But remember what you do, so you
|
||||
# can integrate with a new version of DSE when it comes out.
|
||||
|
||||
# Declare black, so we can use it in the game,
|
||||
init:
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
# This is the entry point into the game.
|
||||
label start:
|
||||
|
||||
# Required to initialize the event engine.
|
||||
call events_init from _call_events_init_1
|
||||
|
||||
# Initialize the default values of some of the variables used in
|
||||
# the game.
|
||||
$ day = 0
|
||||
$ strength = 10
|
||||
$ intelligence = 10
|
||||
|
||||
scene black
|
||||
|
||||
|
||||
# The script here is run before any event.
|
||||
|
||||
"In case you're just tuning in, here's the story of my life to
|
||||
date."
|
||||
|
||||
"I'm a guy in the second year of high school."
|
||||
|
||||
"I'm not good at sports or school or even something as simple as
|
||||
remembering peoples names."
|
||||
|
||||
"In short, I am your usual random loser guy."
|
||||
|
||||
"And this is my story..."
|
||||
|
||||
|
||||
# We jump to day to start the first day.
|
||||
jump day
|
||||
|
||||
|
||||
# This is the label that is jumped to at the start of a day.
|
||||
label day:
|
||||
|
||||
# Increment the day it is.
|
||||
$ day += 1
|
||||
|
||||
# We may also want to compute the name for the day here, but
|
||||
# right now we don't bother.
|
||||
|
||||
"It's day %(day)d."
|
||||
|
||||
# Here, we want to set up some of the default values for the
|
||||
# day planner. In a more complicated game, we would probably
|
||||
# want to add and remove choices from the dp_ variables
|
||||
# (especially dp_period_acts) to reflect the choices the
|
||||
# user has available to him.
|
||||
|
||||
$ morning_act = "class"
|
||||
$ afternoon_act = "hang"
|
||||
$ evening_act = "play"
|
||||
|
||||
# Now, we call the day planner, which may set the act variables
|
||||
# to new values.
|
||||
call day_planner from _call_day_planner_1
|
||||
|
||||
|
||||
# We process each of the three periods of the day, in turn.
|
||||
label morning:
|
||||
|
||||
# Tell the user what period it is.
|
||||
centered "Morning"
|
||||
|
||||
# Set these variables to appropriate values, so they can be
|
||||
# picked up by the expression in the various events defined below.
|
||||
$ period = "morning"
|
||||
$ act = morning_act
|
||||
|
||||
# Execute the events for the morning.
|
||||
call events_run_period from _call_events_run_period_1
|
||||
|
||||
# That's it for the morning, so we fall through to the
|
||||
# afternoon.
|
||||
|
||||
label afternoon:
|
||||
|
||||
# It's possible that we will be skipping the afternoon, if one
|
||||
# of the events in the morning jumped to skip_next_period. If
|
||||
# so, we should skip the afternoon.
|
||||
if check_skip_period():
|
||||
jump evening
|
||||
|
||||
# The rest of this is the same as for the morning.
|
||||
|
||||
centered "Afternoon"
|
||||
|
||||
$ period = "afternoon"
|
||||
$ act = afternoon_act
|
||||
|
||||
call events_run_period from _call_events_run_period_2
|
||||
|
||||
|
||||
label evening:
|
||||
|
||||
# The evening is the same as the afternoon.
|
||||
if check_skip_period():
|
||||
jump night
|
||||
|
||||
centered "Evening"
|
||||
|
||||
$ period = "evening"
|
||||
$ act = evening_act
|
||||
|
||||
call events_run_period from _call_events_run_period_3
|
||||
|
||||
|
||||
label night:
|
||||
|
||||
# This is now the end of the day, and not a period in which
|
||||
# events can be run. We put some boilerplate end-of-day text
|
||||
# in here.
|
||||
|
||||
centered "Night"
|
||||
|
||||
"It's getting late, so I decide to go to sleep."
|
||||
|
||||
# We call events_end_day to let it know that the day is done.
|
||||
call events_end_day from _call_events_end_day_1
|
||||
|
||||
# We force the statistics into the range 0-100.
|
||||
$ intelligence = max(intelligence, 0)
|
||||
$ intelligence = min(intelligence, 100)
|
||||
|
||||
$ strength = max(strength, 0)
|
||||
$ strength = min(strength, 100)
|
||||
|
||||
# And we jump back to day to start the next day. This goes
|
||||
# on forever, until an event ends the game.
|
||||
jump day
|
||||
|
||||
|
||||
# This is the code, that is called from the day planner, to show
|
||||
# the statistics to the user. It can actually show just about
|
||||
# anything, so the statistics are really just a suggestion.
|
||||
|
||||
label show_stats:
|
||||
|
||||
python hide:
|
||||
|
||||
# Add in a line of dialogue asking the question that's on
|
||||
# everybody's mind.
|
||||
narrator("What should I do today?", interact=False)
|
||||
|
||||
# This is a list of the statistics that we are showing to the
|
||||
# user.
|
||||
stats = [
|
||||
('Strength', 100, strength ),
|
||||
('Intelligence', 100, intelligence ),
|
||||
]
|
||||
|
||||
# This is the window that the stats are kept in, if any.
|
||||
ui.window(xpos=0,
|
||||
ypos=0,
|
||||
xanchor='left',
|
||||
yanchor='top',
|
||||
xfill=True,
|
||||
yminimum=0)
|
||||
|
||||
ui.vbox()
|
||||
|
||||
ui.text('Day %d' % day)
|
||||
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()
|
||||
|
||||
return
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
init:
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
label start:
|
||||
label main_menu:
|
||||
|
||||
$ style._write_docs("doc/styles.xml")
|
||||
"Dumped style."
|
||||
$ renpy.renpy.style.write_docs("doc/styles.xml")
|
||||
$ raise "foo"
|
||||
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/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()
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../extras/fullscreen.rpy
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../extras/kanamode.rpy
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../script.rpy
|
||||
@@ -0,0 +1,102 @@
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
|
||||
# Change the size of the thumbnails in the file picker.
|
||||
$ library.thumbnail_width = 60
|
||||
$ library.thumbnail_height = 45
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# This function implements a commentary track, which is a special kind of
|
||||
# say statement that only displays if commentary is set to on in the
|
||||
# new preference that this mode creates. Making this unlockable is easy,
|
||||
# but is left as an exercise for the reader.
|
||||
#
|
||||
# Use it by putting in lines like:
|
||||
#
|
||||
# commentary "Hey! This is commentary!"
|
||||
|
||||
init -10:
|
||||
|
||||
python:
|
||||
|
||||
# The style for the commentary window.
|
||||
style.create('commentary_window', 'window', 'The commentary window.')
|
||||
|
||||
style.commentary_window.background = Solid((0, 0, 0, 192))
|
||||
style.commentary_window.ypos = 0.0
|
||||
style.commentary_window.yanchor = 'top'
|
||||
|
||||
|
||||
# Ensure that the commentary attribute exists.
|
||||
if persistent.commentary is None:
|
||||
persistent.commentary = False
|
||||
|
||||
|
||||
commentary = Character(None, window_style='commentary_window',
|
||||
condition = 'persistent.commentary')
|
||||
|
||||
python hide:
|
||||
|
||||
cp = _Preference('Commentary', 'commentary', [
|
||||
('Enabled', True, 'True'),
|
||||
('Disabled', False, 'True')
|
||||
], base=persistent)
|
||||
|
||||
library.preferences['prefs_right'].append(cp)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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
|
||||
@@ -0,0 +1,262 @@
|
||||
# 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", ui.jumps("gallery")))
|
||||
|
||||
|
||||
label gallery:
|
||||
|
||||
$ gallery()
|
||||
|
||||
jump _main_menu
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# 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
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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))
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# 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_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):
|
||||
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
readback_OldCharacter.__call__(self, what, **kwargs)
|
||||
|
||||
def store_readback(self, who, what):
|
||||
readback_save(self, who, what)
|
||||
|
||||
def readback(self, who, what):
|
||||
self.function(who, 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):
|
||||
narrator.store_readback(None, 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
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# This extra demonstrates the two window say mode. In this mode, the
|
||||
# character's name is placed in a different window than the line said
|
||||
# by that character.
|
||||
#
|
||||
# You can use this by including this file in your game directory. You
|
||||
# then need to change the definitions of Character and
|
||||
# DynamicCharacter object to set function=two_window_say. For example:
|
||||
#
|
||||
# $ e = Character('Eileen', color=(200, 255, 200, 255),
|
||||
# function=two_window_say)
|
||||
#
|
||||
# Once this is done, everything that is said by that character will be
|
||||
# divided across two windows.
|
||||
#
|
||||
# You'll probably also want to customize the styles given below,
|
||||
# especially by setting style.who_window.background to something
|
||||
# a little more attractive.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
style.create('two_window_say_position', 'default',
|
||||
'(position) Used to get the position of the two windows in two window say mode.')
|
||||
|
||||
style.two_window_say_position.ypos = 1.0
|
||||
style.two_window_say_position.yanchor = 'bottom'
|
||||
|
||||
style.create('who_window', 'default',
|
||||
'(window) The style used for the window containing the who label when a Character uses the two_window_say function.')
|
||||
|
||||
style.who_window.background = Solid((0, 0, 255, 128))
|
||||
style.who_window.xminimum = 150
|
||||
style.who_window.xmargin = 10
|
||||
style.who_window.xpadding = 10
|
||||
style.who_window.ymargin = 5
|
||||
style.who_window.ypadding = 5
|
||||
|
||||
|
||||
def two_window_say(who, what,
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
window_style='say_window',
|
||||
who_window_style='who_window',
|
||||
who_prefix='',
|
||||
who_suffix=': ',
|
||||
what_prefix='',
|
||||
what_suffix='',
|
||||
interact=True,
|
||||
slow=True,
|
||||
**properties):
|
||||
|
||||
if interact:
|
||||
ui.saybehavior()
|
||||
|
||||
if who is not None:
|
||||
who = who_prefix + who + who_suffix
|
||||
|
||||
what = what_prefix + what + what_suffix
|
||||
|
||||
ui.vbox(style='two_window_say_position')
|
||||
|
||||
if who is not None:
|
||||
ui.window(style=who_window_style)
|
||||
ui.text(who, style=who_style, **properties)
|
||||
|
||||
ui.window(style=window_style)
|
||||
ui.text(what, style=what_style, slow=slow)
|
||||
|
||||
ui.close()
|
||||
|
||||
if interact:
|
||||
ui.interact()
|
||||
renpy.checkpoint()
|
||||
@@ -0,0 +1,71 @@
|
||||
# This extra contains a basic implementation of voice support. Right
|
||||
# now, voice is given its own toggle, and can either be turned on or
|
||||
# turned off. In the future, we'll probably provide some way of
|
||||
# toggling it on or off for individual characters.
|
||||
#
|
||||
# To use it, place a voice "<wavfile>" line before each voiced line of
|
||||
# dialogue.
|
||||
#
|
||||
# voice "e_1001.wav"
|
||||
# e "Voice support lets you add the spoken word to your games."
|
||||
#
|
||||
# Normally, a voice is cancelled at the start of the next
|
||||
# interaction. If you want a voice to span interactions, call
|
||||
# voice_sustain.
|
||||
#
|
||||
# voice "e_1002.wav"
|
||||
# e "Voice sustain is a technique that allows the same voice file.."
|
||||
#
|
||||
# $ voice_sustain()
|
||||
# e "...to play for two lines of dialogue."
|
||||
|
||||
init -10:
|
||||
|
||||
python:
|
||||
|
||||
# Ensure the voice preference exists.
|
||||
if persistent.voice is None:
|
||||
persistent.voice = True
|
||||
|
||||
_voice = object()
|
||||
_voice.play = None
|
||||
_voice.sustain = False
|
||||
|
||||
# Call this to specify the voice file that will be played for
|
||||
# the user.
|
||||
def voice(file):
|
||||
_voice.play = file
|
||||
|
||||
# Call this to specify that the currently playing voice file
|
||||
# should be sustained through the current interaction.
|
||||
def voice_sustain(ignored=""):
|
||||
_voice.sustain = True
|
||||
|
||||
python hide:
|
||||
|
||||
vp = _Preference('Voice', 'voice', [
|
||||
('Enabled', True, 'True'),
|
||||
('Disabled', False, 'True')
|
||||
], base=persistent)
|
||||
|
||||
library.preferences['prefs_left'].append(vp)
|
||||
|
||||
# This is called on each interaction, to ensure that the
|
||||
# appropriate voice file is played for the user.
|
||||
def voice_interact():
|
||||
|
||||
if not audio.sound_enabled():
|
||||
return
|
||||
|
||||
if not persistent.voice:
|
||||
return
|
||||
|
||||
if _voice.play and not config.skipping:
|
||||
audio.sound_play(_voice.play, channel=1)
|
||||
elif not _voice.sustain:
|
||||
audio.sound_stop(channel=1)
|
||||
|
||||
_voice.play = None
|
||||
_voice.sustain = False
|
||||
|
||||
config.interact_callbacks.append(voice_interact)
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
;;; 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
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
This file explains how to build the _renpy module for distribution on
|
||||
the Mac and Windows.
|
||||
|
||||
(This file is not for distribution, as it doesn't make sense to
|
||||
distribute it.)
|
||||
|
||||
Windows:
|
||||
|
||||
Get into msys.
|
||||
|
||||
cd /t/ab/renpy/module
|
||||
/c/python23/python setup_win32 build --compiler mingw32 bdist_wininst
|
||||
|
||||
|
||||
Macintosh:
|
||||
|
||||
python setup_mac bdist_mpkg --zipdist
|
||||
@@ -0,0 +1,14 @@
|
||||
# This makefile should work on Linux, and might work on other unix-like
|
||||
# platforms as well. It probably won't work on Mac OS X, however.
|
||||
|
||||
all: _renpy.so
|
||||
|
||||
_renpy.c: _renpy.pyx
|
||||
pyrexc _renpy.pyx
|
||||
|
||||
_renpy.so: _renpy.c core.c
|
||||
python setup.py build_ext -i
|
||||
|
||||
clean:
|
||||
-rm _renpy.so
|
||||
-rm -Rf build
|
||||
@@ -0,0 +1,45 @@
|
||||
This directory contains the source code for the _renpy module. This
|
||||
module contains a number of image processing functions that aren't
|
||||
present in pygame. If it's not present, then some of Ren'Py's
|
||||
functionality will be missing. Games should still be playable, but
|
||||
some effects may not be present, or may be present in a degraded form.
|
||||
|
||||
How does this affect you? Well, it depends on the platform you're
|
||||
running on. So please read the appropriate selection below.
|
||||
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
If you're running an exe file under Windows, this doesn't affect you
|
||||
one iota. Run it and be happy.
|
||||
|
||||
If you're running a python script under windows, you can download a
|
||||
precompiled version of the module from the Ren'Py homepage,
|
||||
http://www.bishoujo.us/renpy/.
|
||||
|
||||
|
||||
Macintosh
|
||||
---------
|
||||
|
||||
You'll need to download a mpkg containing the precompiled version of
|
||||
the _renpy module. You can grab it from http://www.bishoujo.us/renpy/.
|
||||
|
||||
|
||||
Linux/Unix
|
||||
----------
|
||||
|
||||
You'll need to compile the module yourself. Ensure that you have the
|
||||
SDL development headers and libraries installed, and that you can run
|
||||
the sdl-config program. Then, in this directory, type:
|
||||
|
||||
python setup.py build_ext -i
|
||||
|
||||
It should autodetect SDL, and build the module in the current
|
||||
directory. If successful, a file named _renpy.so will come into
|
||||
existence. You can then run Ren'Py.
|
||||
|
||||
|
||||
If you have question or problems, please contact us via the Ren'Py web
|
||||
page, http://www.bishoujo.us/renpy/.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/* This file exists to adapt the location that pygame looks for SDL
|
||||
* from into the location where SDL tends to exist on most actual
|
||||
* systems.
|
||||
*/
|
||||
|
||||
#include <SDL/SDL.h>
|
||||
+568
@@ -0,0 +1,568 @@
|
||||
/* Generated by Pyrex 0.9.3 on Sun May 1 16:53:47 2005 */
|
||||
|
||||
#include "Python.h"
|
||||
#include "structmember.h"
|
||||
#ifndef PY_LONG_LONG
|
||||
#define PY_LONG_LONG LONG_LONG
|
||||
#endif
|
||||
#include "renpy.h"
|
||||
|
||||
|
||||
typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
|
||||
typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
|
||||
static PyObject *__Pyx_UnpackItem(PyObject *, int); /*proto*/
|
||||
static int __Pyx_EndUnpack(PyObject *, int); /*proto*/
|
||||
static int __Pyx_PrintItem(PyObject *); /*proto*/
|
||||
static int __Pyx_PrintNewline(void); /*proto*/
|
||||
static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
|
||||
static void __Pyx_ReRaise(void); /*proto*/
|
||||
static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/
|
||||
static PyObject *__Pyx_GetExcValue(void); /*proto*/
|
||||
static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name); /*proto*/
|
||||
static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/
|
||||
static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], int nargs, PyObject **args2, PyObject **kwds2); /*proto*/
|
||||
static void __Pyx_WriteUnraisable(char *name); /*proto*/
|
||||
static void __Pyx_AddTraceback(char *funcname); /*proto*/
|
||||
static PyTypeObject *__Pyx_ImportType(char *module_name, char *class_name, long size); /*proto*/
|
||||
static int __Pyx_SetVtable(PyObject *dict, void *vtable); /*proto*/
|
||||
static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/
|
||||
static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
|
||||
static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
|
||||
static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
|
||||
static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
|
||||
|
||||
static PyObject *__pyx_m;
|
||||
static PyObject *__pyx_b;
|
||||
static int __pyx_lineno;
|
||||
static char *__pyx_filename;
|
||||
staticforward char **__pyx_f;
|
||||
|
||||
/* Declarations from _renpy */
|
||||
|
||||
|
||||
/* Implementation of _renpy */
|
||||
|
||||
|
||||
static PyObject *__pyx_n_pygame;
|
||||
static PyObject *__pyx_n_version;
|
||||
static PyObject *__pyx_n_pixellate;
|
||||
|
||||
static PyObject *__pyx_f_6_renpy_version(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
|
||||
static PyObject *__pyx_f_6_renpy_version(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
|
||||
PyObject *__pyx_r;
|
||||
PyObject *__pyx_1 = 0;
|
||||
static char *__pyx_argnames[] = {0};
|
||||
if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":13 */
|
||||
__pyx_1 = PyInt_FromLong(40008002); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 13; goto __pyx_L1;}
|
||||
__pyx_r = __pyx_1;
|
||||
__pyx_1 = 0;
|
||||
goto __pyx_L0;
|
||||
|
||||
__pyx_r = Py_None; Py_INCREF(__pyx_r);
|
||||
goto __pyx_L0;
|
||||
__pyx_L1:;
|
||||
Py_XDECREF(__pyx_1);
|
||||
__Pyx_AddTraceback("_renpy.version");
|
||||
__pyx_r = 0;
|
||||
__pyx_L0:;
|
||||
return __pyx_r;
|
||||
}
|
||||
|
||||
static PyObject *__pyx_n_isinstance;
|
||||
static PyObject *__pyx_n_Surface;
|
||||
static PyObject *__pyx_n_Exception;
|
||||
static PyObject *__pyx_n_get_bitsize;
|
||||
static PyObject *__pyx_n_lock;
|
||||
static PyObject *__pyx_n_unlock;
|
||||
|
||||
static PyObject *__pyx_k2p;
|
||||
static PyObject *__pyx_k3p;
|
||||
static PyObject *__pyx_k4p;
|
||||
static PyObject *__pyx_k5p;
|
||||
|
||||
static char (__pyx_k2[]) = "pixellate requires a pygame Surface as its first argument.";
|
||||
static char (__pyx_k3[]) = "pixellate requires a pygame Surface as its second argument.";
|
||||
static char (__pyx_k4[]) = "pixellate requires a 24 or 32 bit surface.";
|
||||
static char (__pyx_k5[]) = "pixellate required both surfaces have the same bitsize.";
|
||||
|
||||
static PyObject *__pyx_f_6_renpy_pixellate(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
|
||||
static PyObject *__pyx_f_6_renpy_pixellate(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
|
||||
PyObject *__pyx_v_pysrc = 0;
|
||||
PyObject *__pyx_v_pydst = 0;
|
||||
PyObject *__pyx_v_avgwidth = 0;
|
||||
PyObject *__pyx_v_avgheight = 0;
|
||||
PyObject *__pyx_v_outwidth = 0;
|
||||
PyObject *__pyx_v_outheight = 0;
|
||||
PyObject *__pyx_r;
|
||||
PyObject *__pyx_1 = 0;
|
||||
PyObject *__pyx_2 = 0;
|
||||
PyObject *__pyx_3 = 0;
|
||||
int __pyx_4;
|
||||
int __pyx_5;
|
||||
PyObject *__pyx_6 = 0;
|
||||
int __pyx_7;
|
||||
int __pyx_8;
|
||||
static char *__pyx_argnames[] = {"pysrc","pydst","avgwidth","avgheight","outwidth","outheight",0};
|
||||
if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "OOOOOO", __pyx_argnames, &__pyx_v_pysrc, &__pyx_v_pydst, &__pyx_v_avgwidth, &__pyx_v_avgheight, &__pyx_v_outwidth, &__pyx_v_outheight)) return 0;
|
||||
Py_INCREF(__pyx_v_pysrc);
|
||||
Py_INCREF(__pyx_v_pydst);
|
||||
Py_INCREF(__pyx_v_avgwidth);
|
||||
Py_INCREF(__pyx_v_avgheight);
|
||||
Py_INCREF(__pyx_v_outwidth);
|
||||
Py_INCREF(__pyx_v_outheight);
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":17 */
|
||||
__pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_isinstance); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
__pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_pygame); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
__pyx_3 = PyObject_GetAttr(__pyx_2, __pyx_n_Surface); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_v_pysrc);
|
||||
PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_pysrc);
|
||||
PyTuple_SET_ITEM(__pyx_2, 1, __pyx_3);
|
||||
__pyx_3 = 0;
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_4 = PyObject_IsTrue(__pyx_3); if (__pyx_4 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
__pyx_5 = (!__pyx_4);
|
||||
if (__pyx_5) {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":18 */
|
||||
__pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_Exception); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_k2p);
|
||||
PyTuple_SET_ITEM(__pyx_2, 0, __pyx_k2p);
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__Pyx_Raise(__pyx_3, 0, 0);
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
{__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
|
||||
goto __pyx_L2;
|
||||
}
|
||||
__pyx_L2:;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":20 */
|
||||
__pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_isinstance); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
__pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_pygame); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
__pyx_3 = PyObject_GetAttr(__pyx_2, __pyx_n_Surface); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_v_pydst);
|
||||
PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_pydst);
|
||||
PyTuple_SET_ITEM(__pyx_2, 1, __pyx_3);
|
||||
__pyx_3 = 0;
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_4 = PyObject_IsTrue(__pyx_3); if (__pyx_4 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
__pyx_5 = (!__pyx_4);
|
||||
if (__pyx_5) {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":21 */
|
||||
__pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_Exception); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_k3p);
|
||||
PyTuple_SET_ITEM(__pyx_2, 0, __pyx_k3p);
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__Pyx_Raise(__pyx_3, 0, 0);
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
{__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
|
||||
goto __pyx_L3;
|
||||
}
|
||||
__pyx_L3:;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":23 */
|
||||
__pyx_1 = PyObject_GetAttr(__pyx_v_pysrc, __pyx_n_get_bitsize); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_1 = PyInt_FromLong(24); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
__pyx_2 = PyInt_FromLong(32); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
__pyx_6 = PyTuple_New(2); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
PyTuple_SET_ITEM(__pyx_6, 0, __pyx_1);
|
||||
PyTuple_SET_ITEM(__pyx_6, 1, __pyx_2);
|
||||
__pyx_1 = 0;
|
||||
__pyx_2 = 0;
|
||||
__pyx_4 = PySequence_Contains(__pyx_6, __pyx_3); if (__pyx_4 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
|
||||
__pyx_4 = !__pyx_4;
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
if (__pyx_4) {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":24 */
|
||||
__pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_Exception); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_k4p);
|
||||
PyTuple_SET_ITEM(__pyx_2, 0, __pyx_k4p);
|
||||
__pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__Pyx_Raise(__pyx_3, 0, 0);
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
{__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
|
||||
goto __pyx_L4;
|
||||
}
|
||||
__pyx_L4:;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":26 */
|
||||
__pyx_6 = PyObject_GetAttr(__pyx_v_pydst, __pyx_n_get_bitsize); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
__pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
__pyx_2 = PyObject_CallObject(__pyx_6, __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
__pyx_3 = PyObject_GetAttr(__pyx_v_pysrc, __pyx_n_get_bitsize); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
__pyx_6 = PyTuple_New(0); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
__pyx_1 = PyObject_CallObject(__pyx_3, __pyx_6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
if (PyObject_Cmp(__pyx_2, __pyx_1, &__pyx_5) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
|
||||
__pyx_5 = __pyx_5 != 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
if (__pyx_5) {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":27 */
|
||||
__pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_Exception); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
|
||||
__pyx_6 = PyTuple_New(1); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
|
||||
Py_INCREF(__pyx_k5p);
|
||||
PyTuple_SET_ITEM(__pyx_6, 0, __pyx_k5p);
|
||||
__pyx_2 = PyObject_CallObject(__pyx_3, __pyx_6); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
__Pyx_Raise(__pyx_2, 0, 0);
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
{__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
|
||||
goto __pyx_L5;
|
||||
}
|
||||
__pyx_L5:;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":29 */
|
||||
__pyx_1 = PyObject_GetAttr(__pyx_v_pysrc, __pyx_n_lock); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
|
||||
__pyx_3 = PyTuple_New(0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
|
||||
__pyx_6 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":30 */
|
||||
__pyx_2 = PyObject_GetAttr(__pyx_v_pydst, __pyx_n_lock); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
|
||||
__pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
|
||||
__pyx_3 = PyObject_CallObject(__pyx_2, __pyx_1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":32 */
|
||||
__pyx_6 = PyObject_GetAttr(__pyx_v_pysrc, __pyx_n_get_bitsize); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
|
||||
__pyx_1 = PyObject_CallObject(__pyx_6, __pyx_2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
__pyx_3 = PyInt_FromLong(32); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
|
||||
if (PyObject_Cmp(__pyx_1, __pyx_3, &__pyx_4) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
|
||||
__pyx_4 = __pyx_4 == 0;
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
if (__pyx_4) {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":33 */
|
||||
__pyx_5 = PyInt_AsLong(__pyx_v_avgwidth); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
|
||||
__pyx_4 = PyInt_AsLong(__pyx_v_avgheight); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
|
||||
__pyx_7 = PyInt_AsLong(__pyx_v_outwidth); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
|
||||
__pyx_8 = PyInt_AsLong(__pyx_v_outheight); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
|
||||
pixellate32_core(__pyx_v_pysrc,__pyx_v_pydst,__pyx_5,__pyx_4,__pyx_7,__pyx_8);
|
||||
goto __pyx_L6;
|
||||
}
|
||||
/*else*/ {
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":35 */
|
||||
__pyx_5 = PyInt_AsLong(__pyx_v_avgwidth); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
|
||||
__pyx_4 = PyInt_AsLong(__pyx_v_avgheight); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
|
||||
__pyx_7 = PyInt_AsLong(__pyx_v_outwidth); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
|
||||
__pyx_8 = PyInt_AsLong(__pyx_v_outheight); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
|
||||
pixellate24_core(__pyx_v_pysrc,__pyx_v_pydst,__pyx_5,__pyx_4,__pyx_7,__pyx_8);
|
||||
}
|
||||
__pyx_L6:;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":37 */
|
||||
__pyx_6 = PyObject_GetAttr(__pyx_v_pydst, __pyx_n_unlock); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
|
||||
__pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
|
||||
__pyx_1 = PyObject_CallObject(__pyx_6, __pyx_2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":38 */
|
||||
__pyx_3 = PyObject_GetAttr(__pyx_v_pysrc, __pyx_n_unlock); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
|
||||
__pyx_6 = PyTuple_New(0); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
|
||||
__pyx_2 = PyObject_CallObject(__pyx_3, __pyx_6); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_3); __pyx_3 = 0;
|
||||
Py_DECREF(__pyx_6); __pyx_6 = 0;
|
||||
Py_DECREF(__pyx_2); __pyx_2 = 0;
|
||||
|
||||
__pyx_r = Py_None; Py_INCREF(__pyx_r);
|
||||
goto __pyx_L0;
|
||||
__pyx_L1:;
|
||||
Py_XDECREF(__pyx_1);
|
||||
Py_XDECREF(__pyx_2);
|
||||
Py_XDECREF(__pyx_3);
|
||||
Py_XDECREF(__pyx_6);
|
||||
__Pyx_AddTraceback("_renpy.pixellate");
|
||||
__pyx_r = 0;
|
||||
__pyx_L0:;
|
||||
Py_DECREF(__pyx_v_pysrc);
|
||||
Py_DECREF(__pyx_v_pydst);
|
||||
Py_DECREF(__pyx_v_avgwidth);
|
||||
Py_DECREF(__pyx_v_avgheight);
|
||||
Py_DECREF(__pyx_v_outwidth);
|
||||
Py_DECREF(__pyx_v_outheight);
|
||||
return __pyx_r;
|
||||
}
|
||||
|
||||
static __Pyx_InternTabEntry __pyx_intern_tab[] = {
|
||||
{&__pyx_n_Exception, "Exception"},
|
||||
{&__pyx_n_Surface, "Surface"},
|
||||
{&__pyx_n_get_bitsize, "get_bitsize"},
|
||||
{&__pyx_n_isinstance, "isinstance"},
|
||||
{&__pyx_n_lock, "lock"},
|
||||
{&__pyx_n_pixellate, "pixellate"},
|
||||
{&__pyx_n_pygame, "pygame"},
|
||||
{&__pyx_n_unlock, "unlock"},
|
||||
{&__pyx_n_version, "version"},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static __Pyx_StringTabEntry __pyx_string_tab[] = {
|
||||
{&__pyx_k2p, __pyx_k2, sizeof(__pyx_k2)},
|
||||
{&__pyx_k3p, __pyx_k3, sizeof(__pyx_k3)},
|
||||
{&__pyx_k4p, __pyx_k4, sizeof(__pyx_k4)},
|
||||
{&__pyx_k5p, __pyx_k5, sizeof(__pyx_k5)},
|
||||
{0, 0, 0}
|
||||
};
|
||||
|
||||
static struct PyMethodDef __pyx_methods[] = {
|
||||
{"version", (PyCFunction)__pyx_f_6_renpy_version, METH_VARARGS|METH_KEYWORDS, 0},
|
||||
{"pixellate", (PyCFunction)__pyx_f_6_renpy_pixellate, METH_VARARGS|METH_KEYWORDS, 0},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
DL_EXPORT(void) init_renpy(void); /*proto*/
|
||||
DL_EXPORT(void) init_renpy(void) {
|
||||
PyObject *__pyx_1 = 0;
|
||||
__pyx_m = Py_InitModule4("_renpy", __pyx_methods, 0, 0, PYTHON_API_VERSION);
|
||||
if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
|
||||
__pyx_b = PyImport_AddModule("__builtin__");
|
||||
if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
|
||||
if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
|
||||
if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
|
||||
if (__Pyx_InitStrings(__pyx_string_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":10 */
|
||||
__pyx_1 = __Pyx_Import(__pyx_n_pygame, 0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
|
||||
if (PyObject_SetAttr(__pyx_m, __pyx_n_pygame, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
|
||||
Py_DECREF(__pyx_1); __pyx_1 = 0;
|
||||
|
||||
/* "/home/tom/ab/renpy/module/_renpy.pyx":40 */
|
||||
core_init();
|
||||
return;
|
||||
__pyx_L1:;
|
||||
Py_XDECREF(__pyx_1);
|
||||
__Pyx_AddTraceback("_renpy");
|
||||
}
|
||||
|
||||
static char *__pyx_filenames[] = {
|
||||
"_renpy.pyx",
|
||||
};
|
||||
statichere char **__pyx_f = __pyx_filenames;
|
||||
|
||||
/* Runtime support code */
|
||||
|
||||
static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) {
|
||||
PyObject *__import__ = 0;
|
||||
PyObject *empty_list = 0;
|
||||
PyObject *module = 0;
|
||||
PyObject *global_dict = 0;
|
||||
PyObject *empty_dict = 0;
|
||||
PyObject *list;
|
||||
__import__ = PyObject_GetAttrString(__pyx_b, "__import__");
|
||||
if (!__import__)
|
||||
goto bad;
|
||||
if (from_list)
|
||||
list = from_list;
|
||||
else {
|
||||
empty_list = PyList_New(0);
|
||||
if (!empty_list)
|
||||
goto bad;
|
||||
list = empty_list;
|
||||
}
|
||||
global_dict = PyModule_GetDict(__pyx_m);
|
||||
if (!global_dict)
|
||||
goto bad;
|
||||
empty_dict = PyDict_New();
|
||||
if (!empty_dict)
|
||||
goto bad;
|
||||
module = PyObject_CallFunction(__import__, "OOOO",
|
||||
name, global_dict, empty_dict, list);
|
||||
bad:
|
||||
Py_XDECREF(empty_list);
|
||||
Py_XDECREF(__import__);
|
||||
Py_XDECREF(empty_dict);
|
||||
return module;
|
||||
}
|
||||
|
||||
static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
|
||||
PyObject *result;
|
||||
result = PyObject_GetAttr(dict, name);
|
||||
if (!result)
|
||||
PyErr_SetObject(PyExc_NameError, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
|
||||
Py_XINCREF(type);
|
||||
Py_XINCREF(value);
|
||||
Py_XINCREF(tb);
|
||||
/* First, check the traceback argument, replacing None with NULL. */
|
||||
if (tb == Py_None) {
|
||||
Py_DECREF(tb);
|
||||
tb = 0;
|
||||
}
|
||||
else if (tb != NULL && !PyTraceBack_Check(tb)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"raise: arg 3 must be a traceback or None");
|
||||
goto raise_error;
|
||||
}
|
||||
/* Next, replace a missing value with None */
|
||||
if (value == NULL) {
|
||||
value = Py_None;
|
||||
Py_INCREF(value);
|
||||
}
|
||||
/* Next, repeatedly, replace a tuple exception with its first item */
|
||||
while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
|
||||
PyObject *tmp = type;
|
||||
type = PyTuple_GET_ITEM(type, 0);
|
||||
Py_INCREF(type);
|
||||
Py_DECREF(tmp);
|
||||
}
|
||||
if (PyString_Check(type))
|
||||
;
|
||||
else if (PyClass_Check(type))
|
||||
; /*PyErr_NormalizeException(&type, &value, &tb);*/
|
||||
else if (PyInstance_Check(type)) {
|
||||
/* Raising an instance. The value should be a dummy. */
|
||||
if (value != Py_None) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"instance exception may not have a separate value");
|
||||
goto raise_error;
|
||||
}
|
||||
else {
|
||||
/* Normalize to raise <class>, <instance> */
|
||||
Py_DECREF(value);
|
||||
value = type;
|
||||
type = (PyObject*) ((PyInstanceObject*)type)->in_class;
|
||||
Py_INCREF(type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Not something you can raise. You get an exception
|
||||
anyway, just not what you specified :-) */
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"exceptions must be strings, classes, or "
|
||||
"instances, not %s", type->ob_type->tp_name);
|
||||
goto raise_error;
|
||||
}
|
||||
PyErr_Restore(type, value, tb);
|
||||
return;
|
||||
raise_error:
|
||||
Py_XDECREF(value);
|
||||
Py_XDECREF(type);
|
||||
Py_XDECREF(tb);
|
||||
return;
|
||||
}
|
||||
|
||||
static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
|
||||
while (t->p) {
|
||||
*t->p = PyString_InternFromString(t->s);
|
||||
if (!*t->p)
|
||||
return -1;
|
||||
++t;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
|
||||
while (t->p) {
|
||||
*t->p = PyString_FromStringAndSize(t->s, t->n - 1);
|
||||
if (!*t->p)
|
||||
return -1;
|
||||
++t;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "compile.h"
|
||||
#include "frameobject.h"
|
||||
#include "traceback.h"
|
||||
|
||||
static void __Pyx_AddTraceback(char *funcname) {
|
||||
PyObject *py_srcfile = 0;
|
||||
PyObject *py_funcname = 0;
|
||||
PyObject *py_globals = 0;
|
||||
PyObject *empty_tuple = 0;
|
||||
PyObject *empty_string = 0;
|
||||
PyCodeObject *py_code = 0;
|
||||
PyFrameObject *py_frame = 0;
|
||||
|
||||
py_srcfile = PyString_FromString(__pyx_filename);
|
||||
if (!py_srcfile) goto bad;
|
||||
py_funcname = PyString_FromString(funcname);
|
||||
if (!py_funcname) goto bad;
|
||||
py_globals = PyModule_GetDict(__pyx_m);
|
||||
if (!py_globals) goto bad;
|
||||
empty_tuple = PyTuple_New(0);
|
||||
if (!empty_tuple) goto bad;
|
||||
empty_string = PyString_FromString("");
|
||||
if (!empty_string) goto bad;
|
||||
py_code = PyCode_New(
|
||||
0, /*int argcount,*/
|
||||
0, /*int nlocals,*/
|
||||
0, /*int stacksize,*/
|
||||
0, /*int flags,*/
|
||||
empty_string, /*PyObject *code,*/
|
||||
empty_tuple, /*PyObject *consts,*/
|
||||
empty_tuple, /*PyObject *names,*/
|
||||
empty_tuple, /*PyObject *varnames,*/
|
||||
empty_tuple, /*PyObject *freevars,*/
|
||||
empty_tuple, /*PyObject *cellvars,*/
|
||||
py_srcfile, /*PyObject *filename,*/
|
||||
py_funcname, /*PyObject *name,*/
|
||||
__pyx_lineno, /*int firstlineno,*/
|
||||
empty_string /*PyObject *lnotab*/
|
||||
);
|
||||
if (!py_code) goto bad;
|
||||
py_frame = PyFrame_New(
|
||||
PyThreadState_Get(), /*PyThreadState *tstate,*/
|
||||
py_code, /*PyCodeObject *code,*/
|
||||
py_globals, /*PyObject *globals,*/
|
||||
0 /*PyObject *locals*/
|
||||
);
|
||||
if (!py_frame) goto bad;
|
||||
py_frame->f_lineno = __pyx_lineno;
|
||||
PyTraceBack_Here(py_frame);
|
||||
bad:
|
||||
Py_XDECREF(py_srcfile);
|
||||
Py_XDECREF(py_funcname);
|
||||
Py_XDECREF(empty_tuple);
|
||||
Py_XDECREF(empty_string);
|
||||
Py_XDECREF(py_code);
|
||||
Py_XDECREF(py_frame);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
# -*- python -*-
|
||||
|
||||
# cdef extern class pygame.Surface:
|
||||
# pass
|
||||
|
||||
cdef extern from "renpy.h":
|
||||
void core_init()
|
||||
|
||||
void pixellate32_core(object, object, int, int, int, int)
|
||||
void pixellate24_core(object, object, int, int, int, int)
|
||||
|
||||
void map32_core(object, object,
|
||||
char *,
|
||||
char *,
|
||||
char *,
|
||||
char *)
|
||||
|
||||
void map24_core(object, object,
|
||||
char *,
|
||||
char *,
|
||||
char *)
|
||||
|
||||
void xblur32_core(object, object, int)
|
||||
|
||||
|
||||
void alphamunge_core(object, object, int, int, int, char *)
|
||||
|
||||
|
||||
|
||||
|
||||
import pygame
|
||||
|
||||
def version():
|
||||
return 4008007
|
||||
|
||||
def pixellate(pysrc, pydst, avgwidth, avgheight, outwidth, outheight):
|
||||
|
||||
if not isinstance(pysrc, pygame.Surface):
|
||||
raise Exception("pixellate requires a pygame Surface as its first argument.")
|
||||
|
||||
if not isinstance(pydst, pygame.Surface):
|
||||
raise Exception("pixellate requires a pygame Surface as its second argument.")
|
||||
|
||||
if pysrc.get_bitsize() not in (24, 32):
|
||||
raise Exception("pixellate requires a 24 or 32 bit surface.")
|
||||
|
||||
if pydst.get_bitsize() != pysrc.get_bitsize():
|
||||
raise Exception("pixellate requires both surfaces have the same bitsize.")
|
||||
|
||||
pysrc.lock()
|
||||
pydst.lock()
|
||||
|
||||
if pysrc.get_bitsize() == 32:
|
||||
pixellate32_core(pysrc, pydst, avgwidth, avgheight, outwidth, outheight)
|
||||
else:
|
||||
pixellate24_core(pysrc, pydst, avgwidth, avgheight, outwidth, outheight)
|
||||
|
||||
pydst.unlock()
|
||||
pysrc.unlock()
|
||||
|
||||
|
||||
# Please note that r, g, b, and a are not necessarily red, green, blue
|
||||
# and alpha. Instead, they are the first through fourth byte of data.
|
||||
# The mapping between byte and color/alpha varies from system to
|
||||
# system, and needs to be determined at a higher level.
|
||||
def map(pysrc, pydst, r, g, b, a):
|
||||
|
||||
if not isinstance(pysrc, pygame.Surface):
|
||||
raise Exception("map requires a pygame Surface as its first argument.")
|
||||
|
||||
if not isinstance(pydst, pygame.Surface):
|
||||
raise Exception("map requires a pygame Surface as its second argument.")
|
||||
|
||||
if pysrc.get_bitsize() not in (24, 32):
|
||||
raise Exception("map requires a 24 or 32 bit surface.")
|
||||
|
||||
if pydst.get_bitsize() != pysrc.get_bitsize():
|
||||
raise Exception("map requires both surfaces have the same bitsize.")
|
||||
|
||||
if pydst.get_size() != pysrc.get_size():
|
||||
raise Exception("map requires both surfaces have the same size.")
|
||||
|
||||
pysrc.lock()
|
||||
pydst.lock()
|
||||
|
||||
if pysrc.get_bitsize() == 32:
|
||||
map32_core(pysrc, pydst, r, g, b, a)
|
||||
else:
|
||||
map24_core(pysrc, pydst, r, g, b)
|
||||
|
||||
pydst.unlock()
|
||||
pysrc.unlock()
|
||||
|
||||
def alpha_munge(pysrc, pydst, srcchan, dstchan, amap):
|
||||
|
||||
if not isinstance(pysrc, pygame.Surface):
|
||||
raise Exception("map requires a pygame Surface as its first argument.")
|
||||
|
||||
if not isinstance(pydst, pygame.Surface):
|
||||
raise Exception("map requires a pygame Surface as its second argument.")
|
||||
|
||||
if pysrc.get_bitsize() not in (24, 32):
|
||||
raise Exception("map requires a 24 or 32 bit surface.")
|
||||
|
||||
if pydst.get_bitsize() != pysrc.get_bitsize():
|
||||
raise Exception("map requires both surfaces have the same bitsize.")
|
||||
|
||||
if pydst.get_size() != pysrc.get_size():
|
||||
raise Exception("map requires both surfaces have the same size.")
|
||||
|
||||
|
||||
if pysrc.get_bitsize() == 24:
|
||||
bytes = 3
|
||||
else:
|
||||
bytes = 4
|
||||
|
||||
pysrc.lock()
|
||||
pydst.lock()
|
||||
|
||||
alphamunge_core(pysrc, pydst, bytes, srcchan, dstchan, amap)
|
||||
|
||||
pydst.unlock()
|
||||
pysrc.unlock()
|
||||
|
||||
|
||||
|
||||
# def xblur(pysrc, pydst, radius):
|
||||
|
||||
# if not isinstance(pysrc, pygame.Surface):
|
||||
# raise Exception("blur requires a pygame Surface as its first argument.")
|
||||
|
||||
# if not isinstance(pydst, pygame.Surface):
|
||||
# raise Exception("blur requires a pygame Surface as its second argument.")
|
||||
|
||||
# if pysrc.get_bitsize() not in (24, 32):
|
||||
# raise Exception("blur requires a 24 or 32 bit surface.")
|
||||
|
||||
# if pydst.get_bitsize() != pysrc.get_bitsize():
|
||||
# raise Exception("blur requires both surfaces have the same bitsize.")
|
||||
|
||||
# if pydst.get_size() != pysrc.get_size():
|
||||
# raise Exception("blur requires both surfaces have the same size.")
|
||||
|
||||
# pysrc.lock()
|
||||
# pydst.lock()
|
||||
|
||||
# if pysrc.get_bitsize() == 32:
|
||||
# xblur32_core(pysrc, pydst, radius)
|
||||
# else:
|
||||
# # blur24_core(pysrc, pydst, radius)
|
||||
# assert False
|
||||
|
||||
|
||||
# pydst.unlock()
|
||||
# pysrc.unlock()
|
||||
|
||||
|
||||
|
||||
core_init()
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
pyrexc _renpy.pyx && python setup.py build_ext -i
|
||||
+608
@@ -0,0 +1,608 @@
|
||||
#include "renpy.h"
|
||||
#include <pygame/pygame.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Shows how to do this.
|
||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
/* Initializes the stuff found in this file.
|
||||
*/
|
||||
void core_init() {
|
||||
import_pygame_base();
|
||||
import_pygame_surface();
|
||||
}
|
||||
|
||||
|
||||
/* This pixellates a 32-bit RGBA pygame surface to a destination
|
||||
* surface of a given size.
|
||||
*
|
||||
* pysrc - The source pygame surface, which must be 32-bit RGBA.
|
||||
* pydst - The destination pygame surface, which should be 32-bit
|
||||
* RGBA, and locked.
|
||||
* avgwidth - The width of the pixels that will be averaged together.
|
||||
* avgheight - The height of the pixels that will be averaged
|
||||
* together.
|
||||
* outwidth - The width of pixels that will be written to the output.
|
||||
* outheight - The height of pixels that will be written to the
|
||||
* output.
|
||||
*
|
||||
* We assume that pysrc and pydst have been locked before we are called.
|
||||
*/
|
||||
void pixellate32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int avgwidth,
|
||||
int avgheight,
|
||||
int outwidth,
|
||||
int outheight
|
||||
) {
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
int x, y, i, j;
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
int vw, vh;
|
||||
|
||||
unsigned char *srcpixels;
|
||||
unsigned char *dstpixels;
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (unsigned char *) src->pixels;
|
||||
dstpixels = (unsigned char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
/* Compute the virtual width and height. */
|
||||
vw = ( srcw + avgwidth - 1) / avgwidth;
|
||||
vh = ( srch + avgheight - 1) / avgheight;
|
||||
|
||||
/* Iterate through each of the virtual pixels. */
|
||||
|
||||
for (y = 0; y < vh; y++) {
|
||||
int srcy = avgheight * y;
|
||||
int dsty = outheight * y;
|
||||
|
||||
int srcylimit = srcy + avgheight;
|
||||
int dstylimit = dsty + outheight;
|
||||
|
||||
if (srcylimit > srch) {
|
||||
srcylimit = srch;
|
||||
}
|
||||
|
||||
if (dstylimit > dsth) {
|
||||
dstylimit = dsth;
|
||||
}
|
||||
|
||||
for (x = 0; x < vw; x++) {
|
||||
int srcx = avgwidth * x;
|
||||
int dstx = outwidth * x;
|
||||
|
||||
int srcxlimit = srcx + avgwidth;
|
||||
int dstxlimit = dstx + outheight;
|
||||
|
||||
if (srcxlimit > srcw) {
|
||||
srcxlimit = srcw;
|
||||
}
|
||||
|
||||
if (dstxlimit > dstw) {
|
||||
dstxlimit = dstw;
|
||||
}
|
||||
|
||||
// Please note that these names are just
|
||||
// suggestions... It's possible that alpha will be
|
||||
// in r, for example.
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 0;
|
||||
|
||||
int number = 0;
|
||||
|
||||
// pos always points to the start of the current line.
|
||||
unsigned char *pos = &srcpixels[srcy * srcpitch + srcx * 4];
|
||||
|
||||
/* Sum up the pixel values. */
|
||||
|
||||
for (j = srcy; j < srcylimit; j++) {
|
||||
// po points to the current pixel.
|
||||
unsigned char *po = pos;
|
||||
|
||||
for (i = srcx; i < srcxlimit; i++) {
|
||||
r += *po++;
|
||||
g += *po++;
|
||||
b += *po++;
|
||||
a += *po++;
|
||||
number += 1;
|
||||
}
|
||||
|
||||
pos += srcpitch;
|
||||
}
|
||||
|
||||
/* Compute the average pixel values. */
|
||||
r /= number;
|
||||
g /= number;
|
||||
b /= number;
|
||||
a /= number;
|
||||
|
||||
/* Write out the average pixel values. */
|
||||
pos = &dstpixels[dsty * dstpitch + dstx * 4];
|
||||
for (j = dsty; j < dstylimit; j++) {
|
||||
unsigned char *po = pos;
|
||||
|
||||
for (i = dstx; i < dstxlimit; i++) {
|
||||
*po++ = r;
|
||||
*po++ = g;
|
||||
*po++ = b;
|
||||
*po++ = a;
|
||||
}
|
||||
|
||||
pos += dstpitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This pixellates a 32-bit RGBA pygame surface to a destination
|
||||
* surface of a given size.
|
||||
*
|
||||
* pysrc - The source pygame surface, which must be 32-bit RGBA.
|
||||
* pydst - The destination pygame surface, which should be 32-bit
|
||||
* RGBA, and locked.
|
||||
* avgwidth - The width of the pixels that will be averaged together.
|
||||
* avgheight - The height of the pixels that will be averaged
|
||||
* together.
|
||||
* outwidth - The width of pixels that will be written to the output.
|
||||
* outheight - The height of pixels that will be written to the
|
||||
* output.
|
||||
*
|
||||
* We assume that pysrc and pydst have been locked before we are called.
|
||||
*/
|
||||
void pixellate24_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int avgwidth,
|
||||
int avgheight,
|
||||
int outwidth,
|
||||
int outheight
|
||||
) {
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
int x, y, i, j;
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
int vw, vh;
|
||||
|
||||
unsigned char *srcpixels;
|
||||
unsigned char *dstpixels;
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (unsigned char *) src->pixels;
|
||||
dstpixels = (unsigned char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
/* Compute the virtual width and height. */
|
||||
vw = ( srcw + avgwidth - 1) / avgwidth;
|
||||
vh = ( srch + avgheight - 1) / avgheight;
|
||||
|
||||
/* Iterate through each of the virtual pixels. */
|
||||
|
||||
for (y = 0; y < vh; y++) {
|
||||
int srcy = avgheight * y;
|
||||
int dsty = outheight * y;
|
||||
|
||||
int srcylimit = srcy + avgheight;
|
||||
int dstylimit = dsty + outheight;
|
||||
|
||||
if (srcylimit > srch) {
|
||||
srcylimit = srch;
|
||||
}
|
||||
|
||||
if (dstylimit > dsth) {
|
||||
dstylimit = dsth;
|
||||
}
|
||||
|
||||
for (x = 0; x < vw; x++) {
|
||||
int srcx = avgwidth * x;
|
||||
int dstx = outwidth * x;
|
||||
|
||||
int srcxlimit = srcx + avgwidth;
|
||||
int dstxlimit = dstx + outheight;
|
||||
|
||||
if (srcxlimit > srcw) {
|
||||
srcxlimit = srcw;
|
||||
}
|
||||
|
||||
if (dstxlimit > dstw) {
|
||||
dstxlimit = dstw;
|
||||
}
|
||||
|
||||
// Please note that these names are just
|
||||
// suggestions... It's possible that blue will be
|
||||
// in r, for example.
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
|
||||
int number = 0;
|
||||
|
||||
// pos always points to the start of the current line.
|
||||
unsigned char *pos = &srcpixels[srcy * srcpitch + srcx * 3];
|
||||
|
||||
/* Sum up the pixel values. */
|
||||
|
||||
for (j = srcy; j < srcylimit; j++) {
|
||||
// po points to the current pixel.
|
||||
unsigned char *po = pos;
|
||||
|
||||
for (i = srcx; i < srcxlimit; i++) {
|
||||
r += *po++;
|
||||
g += *po++;
|
||||
b += *po++;
|
||||
number += 1;
|
||||
}
|
||||
|
||||
pos += srcpitch;
|
||||
}
|
||||
|
||||
/* Compute the average pixel values. */
|
||||
r /= number;
|
||||
g /= number;
|
||||
b /= number;
|
||||
|
||||
/* Write out the average pixel values. */
|
||||
pos = &dstpixels[dsty * dstpitch + dstx * 3];
|
||||
for (j = dsty; j < dstylimit; j++) {
|
||||
unsigned char *po = pos;
|
||||
|
||||
for (i = dstx; i < dstxlimit; i++) {
|
||||
*po++ = r;
|
||||
*po++ = g;
|
||||
*po++ = b;
|
||||
}
|
||||
|
||||
pos += dstpitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This expects pysrc and pydst to be surfaces of the same size. It
|
||||
* the source surface to the destination surface, using the r, g, b,
|
||||
* and a maps. These maps are expected to be 256 bytes long, with each
|
||||
* byte corresponding to a possible value of a channel in pysrc,
|
||||
* giving what that value is mapped to in pydst.
|
||||
*/
|
||||
void map32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
char *rmap,
|
||||
char *gmap,
|
||||
char *bmap,
|
||||
char *amap) {
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
int x, y;
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
|
||||
char *srcpixels;
|
||||
char *dstpixels;
|
||||
|
||||
char *srcrow;
|
||||
char *dstrow;
|
||||
char *srcp;
|
||||
char *dstp;
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (char *) src->pixels;
|
||||
dstpixels = (char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
srcrow = srcpixels;
|
||||
dstrow = dstpixels;
|
||||
|
||||
for (y = 0; y < srch; y++) {
|
||||
srcp = srcrow;
|
||||
dstp = dstrow;
|
||||
|
||||
|
||||
for (x = 0; x < srcw; x++) {
|
||||
*dstp++ = rmap[(unsigned char) *srcp++];
|
||||
*dstp++ = gmap[(unsigned char) *srcp++];
|
||||
*dstp++ = bmap[(unsigned char) *srcp++];
|
||||
*dstp++ = amap[(unsigned char) *srcp++];
|
||||
}
|
||||
|
||||
srcrow += srcpitch;
|
||||
dstrow += dstpitch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void map24_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
char *rmap,
|
||||
char *gmap,
|
||||
char *bmap) {
|
||||
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
int x, y;
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
|
||||
char *srcpixels;
|
||||
char *dstpixels;
|
||||
|
||||
char *srcrow;
|
||||
char *dstrow;
|
||||
char *srcp;
|
||||
char *dstp;
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (char *) src->pixels;
|
||||
dstpixels = (char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
srcrow = srcpixels;
|
||||
dstrow = dstpixels;
|
||||
|
||||
for (y = 0; y < srch; y++) {
|
||||
srcp = srcrow;
|
||||
dstp = dstrow;
|
||||
|
||||
|
||||
for (x = 0; x < srcw; x++) {
|
||||
*dstp++ = rmap[(unsigned char) *srcp++];
|
||||
*dstp++ = gmap[(unsigned char) *srcp++];
|
||||
*dstp++ = bmap[(unsigned char) *srcp++];
|
||||
}
|
||||
|
||||
srcrow += srcpitch;
|
||||
dstrow += dstpitch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
void xblur32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int radius) {
|
||||
|
||||
int i, x, y;
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
|
||||
unsigned char *srcpixels;
|
||||
unsigned char *dstpixels;
|
||||
|
||||
int count;
|
||||
|
||||
unsigned char *srcp;
|
||||
unsigned char *dstp;
|
||||
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (unsigned char *) src->pixels;
|
||||
dstpixels = (unsigned char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
int divisor = radius * 2 + 1;
|
||||
|
||||
for (y = 0; y < dsth; y++) {
|
||||
|
||||
// The values of the pixels on the left and right ends of the
|
||||
// line.
|
||||
unsigned char lr, lg, lb, la;
|
||||
unsigned char rr, rg, rb, ra;
|
||||
|
||||
unsigned char *leader = srcpixels + y * srcpitch;
|
||||
unsigned char *trailer = leader;
|
||||
dstp = dstpixels + y * dstpitch;
|
||||
|
||||
lr = *leader;
|
||||
lg = *(leader + 1);
|
||||
lb = *(leader + 2);
|
||||
la = *(leader + 3);
|
||||
|
||||
int sumr = lr * radius;
|
||||
int sumg = lg * radius;
|
||||
int sumb = lb * radius;
|
||||
int suma = la * radius;
|
||||
|
||||
|
||||
for (x = 0; x < radius + 0; x++) {
|
||||
sumr += *leader++;
|
||||
sumg += *leader++;
|
||||
sumb += *leader++;
|
||||
suma += *leader++;
|
||||
}
|
||||
|
||||
// left side of the kernel is off of the screen.
|
||||
for (x = 0; x < radius; x++) {
|
||||
sumr += *leader++;
|
||||
sumg += *leader++;
|
||||
sumb += *leader++;
|
||||
suma += *leader++;
|
||||
|
||||
*dstp++ = sumr / divisor;
|
||||
*dstp++ = sumg / divisor;
|
||||
*dstp++ = sumb / divisor;
|
||||
*dstp++ = suma / divisor;
|
||||
|
||||
sumr -= lr;
|
||||
sumg -= lg;
|
||||
sumb -= lb;
|
||||
suma -= la;
|
||||
}
|
||||
|
||||
int end = srcw - radius - 1;
|
||||
|
||||
// The kernel is fully on the screen.
|
||||
for (; x < end; x++) {
|
||||
sumr += *leader++;
|
||||
sumg += *leader++;
|
||||
sumb += *leader++;
|
||||
suma += *leader++;
|
||||
|
||||
*dstp++ = sumr / divisor;
|
||||
*dstp++ = sumg / divisor;
|
||||
*dstp++ = sumb / divisor;
|
||||
*dstp++ = suma / divisor;
|
||||
|
||||
sumr -= *trailer++;
|
||||
sumg -= *trailer++;
|
||||
sumb -= *trailer++;
|
||||
suma -= *trailer++;
|
||||
}
|
||||
|
||||
rr = *leader++;
|
||||
rg = *leader++;
|
||||
rb = *leader++;
|
||||
ra = *leader++;
|
||||
|
||||
// The kernel is off the right side of the screen.
|
||||
for (; x < srcw; x++) {
|
||||
sumr += rr;
|
||||
sumg += rg;
|
||||
sumb += rb;
|
||||
suma += ra;
|
||||
|
||||
*dstp++ = sumr / divisor;
|
||||
*dstp++ = sumg / divisor;
|
||||
*dstp++ = sumb / divisor;
|
||||
*dstp++ = suma / divisor;
|
||||
|
||||
sumr -= *trailer++;
|
||||
sumg -= *trailer++;
|
||||
sumb -= *trailer++;
|
||||
suma -= *trailer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Alpha Munge takes a channel from the source pixel, maps it, and
|
||||
// sticks it into the alpha channel of the destination, overwriting
|
||||
// the destination's alpha channel.
|
||||
//
|
||||
// It's used to implement SmartDissolve.
|
||||
|
||||
void alphamunge_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int src_bypp, // bytes per pixel.
|
||||
int src_aoff, // alpha offset.
|
||||
int dst_aoff, // alpha offset.
|
||||
char *amap) {
|
||||
|
||||
int x, y;
|
||||
|
||||
SDL_Surface *src;
|
||||
SDL_Surface *dst;
|
||||
|
||||
Uint32 srcpitch, dstpitch;
|
||||
Uint32 srcw, srch;
|
||||
Uint32 dstw, dsth;
|
||||
|
||||
unsigned char *srcpixels;
|
||||
unsigned char *dstpixels;
|
||||
|
||||
unsigned char *srcline;
|
||||
unsigned char *dstline;
|
||||
|
||||
unsigned char *srcp;
|
||||
unsigned char *dstp;
|
||||
|
||||
|
||||
src = PySurface_AsSurface(pysrc);
|
||||
dst = PySurface_AsSurface(pydst);
|
||||
|
||||
srcpixels = (unsigned char *) src->pixels;
|
||||
dstpixels = (unsigned char *) dst->pixels;
|
||||
srcpitch = src->pitch;
|
||||
dstpitch = dst->pitch;
|
||||
srcw = src->w;
|
||||
dstw = dst->w;
|
||||
srch = src->h;
|
||||
dsth = dst->h;
|
||||
|
||||
|
||||
// We assume that src is bigger than dst, and so use dst
|
||||
// to handle everything.
|
||||
|
||||
srcline = srcpixels;
|
||||
dstline = dstpixels;
|
||||
|
||||
for (y = 0; y < dsth; y++) {
|
||||
|
||||
srcp = srcline + src_aoff;
|
||||
dstp = dstline + dst_aoff;
|
||||
|
||||
for (x = 0; x < dstw; x++) {
|
||||
|
||||
*dstp = amap[*srcp];
|
||||
srcp += src_bypp;
|
||||
dstp += 4; // Need an alpha channel.
|
||||
}
|
||||
|
||||
srcline += srcpitch;
|
||||
dstline += dstpitch;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
#ifndef RENPY_H
|
||||
#define RENPY_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
void core_init(void);
|
||||
|
||||
void pixellate32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int avgwidth,
|
||||
int avgheight,
|
||||
int outwidth,
|
||||
int outheight);
|
||||
|
||||
void pixellate24_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int avgwidth,
|
||||
int avgheight,
|
||||
int outwidth,
|
||||
int outheight);
|
||||
|
||||
void map32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
char *rmap,
|
||||
char *gmap,
|
||||
char *bmap,
|
||||
char *amap);
|
||||
|
||||
void map24_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
char *rmap,
|
||||
char *gmap,
|
||||
char *bmap);
|
||||
|
||||
#if 0
|
||||
|
||||
void xblur32_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int radius);
|
||||
|
||||
#endif
|
||||
|
||||
void alphamunge_core(PyObject *pysrc,
|
||||
PyObject *pydst,
|
||||
int src_bypp, // bytes per pixel.
|
||||
int src_aoff, // alpha offset.
|
||||
int dst_aoff, // alpha offset.
|
||||
char *amap);
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Set this to false to disable the use of sdl-config to automatically
|
||||
# figure out the compile arguments.
|
||||
auto_configure = True
|
||||
|
||||
# The following are only respected if auto_configure is False.
|
||||
include_dirs = [ "." ]
|
||||
libraries = [ "SDL" ]
|
||||
extra_compile_args = [ ]
|
||||
extra_link_args = [ ]
|
||||
|
||||
import distutils.core
|
||||
|
||||
def common():
|
||||
|
||||
renpy_extension = distutils.core.Extension(
|
||||
"_renpy",
|
||||
[ "core.c", "_renpy.c" ],
|
||||
extra_compile_args=extra_compile_args,
|
||||
extra_link_args=extra_link_args,
|
||||
include_dirs=include_dirs,
|
||||
libraries=libraries,
|
||||
)
|
||||
# include_dirs=[ "." ],
|
||||
# libraries=[ "SDL" ],
|
||||
|
||||
distutils.core.setup(
|
||||
name = "renpy_module",
|
||||
version = "4.8.7",
|
||||
ext_modules = [ renpy_extension ],
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if auto_configure:
|
||||
try:
|
||||
import os
|
||||
|
||||
extra_compile_args = os.popen("sdl-config --cflags").read().split()
|
||||
extra_link_args = os.popen("sdl-config --libs").read().split()
|
||||
include_dirs = [ ]
|
||||
libraries = [ ]
|
||||
|
||||
except:
|
||||
|
||||
print "I was unable to automatically configure the Ren'Py module."
|
||||
print "Perhaps sdl-config was not found, or could not be run."
|
||||
print
|
||||
print "Hopefully, you can figure it out yourself from the traceback below."
|
||||
print "Otherwise, email pytom@bishoujo.us for help."
|
||||
print
|
||||
raise
|
||||
|
||||
common()
|
||||
@@ -0,0 +1,12 @@
|
||||
# This file will really only work on my Win32 system, as it uses
|
||||
# sdl-config directly.
|
||||
|
||||
import setup
|
||||
import bdist_mpkg
|
||||
|
||||
setup.extra_compile_args = [ "-framework", "SDL", "-I/Library/Frameworks/SDL.framework/Headers" ]
|
||||
setup.extra_link_args = [ "-framework", "SDL" ]
|
||||
setup.includes = [ ]
|
||||
setup.libraries = [ ]
|
||||
|
||||
setup.common()
|
||||
@@ -0,0 +1,11 @@
|
||||
# This file will really only work on my Win32 system, as it uses
|
||||
# sdl-config directly.
|
||||
|
||||
import setup
|
||||
|
||||
setup.extra_compile_args = [ "-O3", "-Ic:\\msys\\1.0\\local\\include\\SDL" ]
|
||||
setup.extra_link_args = [ "-Lc:\\msys\\1.0\\local\\lib", "-lSDL" ]
|
||||
setup.includes = [ ]
|
||||
setup.libraries = [ ]
|
||||
|
||||
setup.common()
|
||||
@@ -0,0 +1,34 @@
|
||||
import pygame
|
||||
import _renpy
|
||||
import time
|
||||
|
||||
pygame.init()
|
||||
|
||||
screen = pygame.display.set_mode((800, 600), 0, 32)
|
||||
|
||||
s = pygame.image.load("whitehouse.jpg")
|
||||
# s = s.convert_alpha()
|
||||
s2 = pygame.Surface(s.get_size(), 0, s)
|
||||
|
||||
|
||||
px = 2
|
||||
|
||||
for r in range(10):
|
||||
|
||||
for px in range(1, 10) + range(10, 0, -1):
|
||||
start = time.time()
|
||||
px = 2 ** px
|
||||
|
||||
for i in range(5):
|
||||
_renpy.pixellate(s, s2, px, px, px, px)
|
||||
|
||||
print px, ( time.time() - start ) / 4.0
|
||||
|
||||
screen.blit(s2, (0, 0))
|
||||
pygame.display.flip()
|
||||
|
||||
|
||||
while True:
|
||||
|
||||
if pygame.event.wait().type == pygame.constants.QUIT:
|
||||
break
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
Well, [url="http://www.noradsanta.org"]NORAD has locked its
|
||||
interceptors onto Santa[/url], so I guess that means it's
|
||||
Christmas. And what a better way to celebrate than a new release of
|
||||
Ren'Py. (Well, how about with your families or something like that?)
|
||||
|
||||
Ren'Py 4.4 "Christmas Bonus" contains a number of feature
|
||||
enhancements, speed improvements, and bug fixes. Along with improved
|
||||
support for visual novels, this release lays the foundations for
|
||||
Stats-Based Dating Simulations. And it's my gift to you, downloadable
|
||||
from the Ren'Py website,
|
||||
|
||||
[url]http://www.bishoujo.us/renpy/[/url]
|
||||
|
||||
Upgrading a script from recent (4.2 or better) versions of Ren'Py
|
||||
should be as simple as copying over your old game directory, and
|
||||
perhaps deleting a line setting library.file_page_length.
|
||||
|
||||
This is a fairly major release, containing a bunch of new features and
|
||||
bug fixes. This is what you should be using if you want to make a
|
||||
Ren'Py game, at least until a new version comes out.
|
||||
|
||||
I'd like to thank Mikey for testing a pre-release of this to ensure
|
||||
that all the bugs with UTF-8 were well and truly squashed. Thanks to
|
||||
him, I now know what a LOWERCASE L WITH CARON is.
|
||||
|
||||
Anyway, as usual, feel free to post, email, or IM me if you have any
|
||||
questions, problems, comments, or suggestions. And have a great
|
||||
holiday, everyone.
|
||||
|
||||
[b]New in 4.4[/b]
|
||||
|
||||
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.
|
||||
|
||||
[b]Speed Enhancements:[/b]
|
||||
|
||||
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.
|
||||
+14
-8
@@ -2,9 +2,9 @@
|
||||
# order.
|
||||
|
||||
# Some version numbers and things.
|
||||
version = "Ren'Py 4.4.2"
|
||||
script_version = 5
|
||||
savegame_suffix = "-3.save"
|
||||
version = "Ren'Py 4.8.7"
|
||||
script_version = 8007
|
||||
savegame_suffix = "-8.4.save"
|
||||
|
||||
|
||||
# Can be first, because has no dependencies, and may be imported
|
||||
@@ -19,23 +19,29 @@ 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
|
||||
import renpy.display.presplash
|
||||
import renpy.display.module
|
||||
import renpy.display.render # Most display stuff depends on this.
|
||||
import renpy.display.core # object
|
||||
import renpy.display.surface
|
||||
import renpy.display.audio
|
||||
import renpy.display.text # core
|
||||
import renpy.display.layout # core
|
||||
import renpy.display.behavior # layout
|
||||
import renpy.display.transition # core
|
||||
import renpy.display.image # core, behavior
|
||||
import renpy.display.im
|
||||
import renpy.display.image # core, behavior, im
|
||||
import renpy.display.video
|
||||
import renpy.display.focus
|
||||
import renpy.display.anim
|
||||
|
||||
import renpy.ui
|
||||
import renpy.lint
|
||||
|
||||
import renpy.exports
|
||||
import renpy.config # depends on lots.
|
||||
|
||||
+99
-80
@@ -1,3 +1,4 @@
|
||||
|
||||
# This file contains the AST for the Ren'Py script language. Each class
|
||||
# here corresponds to a statement in the script language.
|
||||
|
||||
@@ -29,6 +30,13 @@ class Node(object):
|
||||
# a node from that file.
|
||||
serials = { }
|
||||
|
||||
__slots__ = [
|
||||
'name',
|
||||
'filename',
|
||||
'linenumber',
|
||||
'next',
|
||||
]
|
||||
|
||||
def __init__(self, loc):
|
||||
"""
|
||||
Initializes this Node object.
|
||||
@@ -117,9 +125,14 @@ def say_menu_with(expression):
|
||||
if renpy.game.preferences.transitions:
|
||||
renpy.game.interface.set_transition(what)
|
||||
|
||||
|
||||
class Say(Node):
|
||||
|
||||
__slots__ = [
|
||||
'who',
|
||||
'what',
|
||||
'with',
|
||||
]
|
||||
|
||||
def __init__(self, loc, who, what, with):
|
||||
|
||||
super(Say, self).__init__(loc)
|
||||
@@ -136,12 +149,17 @@ class Say(Node):
|
||||
who = None
|
||||
|
||||
say_menu_with(self.with)
|
||||
renpy.exports.say(who, self.what % renpy.game.store)
|
||||
renpy.exports.say(who, self.what)
|
||||
|
||||
return self.next
|
||||
|
||||
class Init(Node):
|
||||
|
||||
__slots__ = [
|
||||
'block',
|
||||
'priority',
|
||||
]
|
||||
|
||||
def __init__(self, loc, block, priority):
|
||||
super(Init, self).__init__(loc)
|
||||
|
||||
@@ -169,6 +187,11 @@ class Init(Node):
|
||||
|
||||
class Label(Node):
|
||||
|
||||
__slots__ = [
|
||||
'name',
|
||||
'block',
|
||||
]
|
||||
|
||||
def __init__(self, loc, name, block):
|
||||
"""
|
||||
Constructs a new Label node.
|
||||
@@ -199,6 +222,11 @@ class Label(Node):
|
||||
|
||||
class Python(Node):
|
||||
|
||||
__slots__ = [
|
||||
'hide',
|
||||
'bytecode',
|
||||
]
|
||||
|
||||
def __init__(self, loc, python_code, hide=False):
|
||||
"""
|
||||
@param python_code: Properly-indented python code.
|
||||
@@ -209,12 +237,15 @@ class Python(Node):
|
||||
|
||||
super(Python, self).__init__(loc)
|
||||
|
||||
filename = loc[0]
|
||||
lineno = loc[1]
|
||||
|
||||
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)
|
||||
self.bytecode = renpy.python.py_compile_exec_bytecode(python_code, filename=filename, lineno=lineno)
|
||||
renpy.game.exception_info = old_ei
|
||||
|
||||
|
||||
@@ -225,6 +256,11 @@ class Python(Node):
|
||||
|
||||
class Image(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imgname',
|
||||
'expr',
|
||||
]
|
||||
|
||||
def __init__(self, loc, name, expr):
|
||||
"""
|
||||
@param name: The name of the image being defined.
|
||||
@@ -235,59 +271,16 @@ class Image(Node):
|
||||
|
||||
super(Image, self).__init__(loc)
|
||||
|
||||
self.name = name
|
||||
self.imgname = name
|
||||
self.expr = expr
|
||||
|
||||
def execute(self):
|
||||
import renpy.exports as exports
|
||||
|
||||
if not renpy.game.init_phase:
|
||||
raise Exception("image statement should only be inside an init: block.")
|
||||
|
||||
|
||||
img = renpy.python.py_eval(self.expr)
|
||||
exports.images[self.name] = img
|
||||
renpy.exports.image(self.imgname, img)
|
||||
|
||||
return self.next
|
||||
|
||||
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.
|
||||
|
||||
@param hide: Reduces error checking, and changes the sticky position
|
||||
logic.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
# Now, apply the at_list, from left to right.
|
||||
for i in at_list:
|
||||
img = renpy.python.py_eval(i)(img)
|
||||
|
||||
return key, img
|
||||
|
||||
def predict_imspec(imspec, callback):
|
||||
"""
|
||||
Call this to use the given callback to predict the image named
|
||||
@@ -298,11 +291,16 @@ def predict_imspec(imspec, callback):
|
||||
return
|
||||
|
||||
im = renpy.exports.images[imspec[0]]
|
||||
|
||||
im.predict(callback)
|
||||
|
||||
|
||||
class Show(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imspec',
|
||||
]
|
||||
|
||||
def __init__(self, loc, imspec):
|
||||
"""
|
||||
@param imspec: A triple consisting of an image name (itself a
|
||||
@@ -316,10 +314,10 @@ class Show(Node):
|
||||
|
||||
def execute(self):
|
||||
|
||||
key, img = imspec_common(self.imspec)
|
||||
|
||||
sls = renpy.game.context().scene_lists
|
||||
sls.add('master', img, key)
|
||||
name, at_list = self.imspec
|
||||
at_list = [ renpy.python.py_eval(i) for i in at_list ]
|
||||
|
||||
renpy.exports.show(name, *at_list)
|
||||
|
||||
return self.next
|
||||
|
||||
@@ -330,6 +328,10 @@ class Show(Node):
|
||||
|
||||
class Scene(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imspec',
|
||||
]
|
||||
|
||||
def __init__(self, loc, imgspec):
|
||||
"""
|
||||
@param imspec: A triple consisting of an image name (itself a
|
||||
@@ -344,17 +346,14 @@ class Scene(Node):
|
||||
|
||||
def execute(self):
|
||||
|
||||
import renpy.exports as exports
|
||||
|
||||
sls = renpy.game.context().scene_lists
|
||||
|
||||
sls.clear('master')
|
||||
sls.sticky_positions.clear()
|
||||
renpy.exports.scene()
|
||||
|
||||
if self.imspec:
|
||||
key, img = imspec_common(self.imspec)
|
||||
|
||||
sls.add('master', img, key)
|
||||
|
||||
name, at_list = self.imspec
|
||||
at_list = [ renpy.python.py_eval(i) for i in at_list ]
|
||||
|
||||
renpy.exports.show(name, *at_list)
|
||||
|
||||
return self.next
|
||||
|
||||
@@ -367,6 +366,10 @@ class Scene(Node):
|
||||
|
||||
class Hide(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imspec',
|
||||
]
|
||||
|
||||
def __init__(self, loc, imgspec):
|
||||
"""
|
||||
@param imspec: A triple consisting of an image name (itself a
|
||||
@@ -380,17 +383,15 @@ class Hide(Node):
|
||||
|
||||
def execute(self):
|
||||
|
||||
import renpy.exports as exports
|
||||
|
||||
sls = renpy.game.context().scene_lists
|
||||
|
||||
key, img = imspec_common(self.imspec, hide=True)
|
||||
|
||||
sls.remove('master', key)
|
||||
|
||||
renpy.exports.hide(self.imspec[0])
|
||||
return self.next
|
||||
|
||||
class With(Node):
|
||||
|
||||
__slots__ = [
|
||||
'expr',
|
||||
]
|
||||
|
||||
def __init__(self, loc, expr):
|
||||
"""
|
||||
@param expr: An expression giving a transition or None.
|
||||
@@ -403,22 +404,18 @@ class With(Node):
|
||||
def execute(self):
|
||||
trans = renpy.python.py_eval(self.expr)
|
||||
|
||||
# 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)
|
||||
renpy.exports.with(trans)
|
||||
|
||||
return self.next
|
||||
|
||||
|
||||
class Call(Node):
|
||||
|
||||
__slots__ = [
|
||||
'label',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, label, expression):
|
||||
|
||||
super(Call, self).__init__(loc)
|
||||
@@ -441,6 +438,8 @@ class Call(Node):
|
||||
|
||||
class Return(Node):
|
||||
|
||||
__slots__ = [ ]
|
||||
|
||||
# No __init__ needed.
|
||||
|
||||
# We don't care what the next node is.
|
||||
@@ -459,6 +458,12 @@ class Return(Node):
|
||||
|
||||
class Menu(Node):
|
||||
|
||||
__slots__ = [
|
||||
'items',
|
||||
'set',
|
||||
'with',
|
||||
]
|
||||
|
||||
def __init__(self, loc, items, set, with):
|
||||
super(Menu, self).__init__(loc)
|
||||
|
||||
@@ -517,6 +522,11 @@ class Menu(Node):
|
||||
# instead.
|
||||
class Jump(Node):
|
||||
|
||||
__slots__ = [
|
||||
'target',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, target, expression):
|
||||
super(Jump, self).__init__(loc)
|
||||
|
||||
@@ -545,11 +555,18 @@ class Jump(Node):
|
||||
# GNDN
|
||||
class Pass(Node):
|
||||
|
||||
__slots__ = [ ]
|
||||
|
||||
def execute(self):
|
||||
return self.next
|
||||
|
||||
class While(Node):
|
||||
|
||||
__slots__ = [
|
||||
'condition',
|
||||
'block',
|
||||
]
|
||||
|
||||
def __init__(self, loc, condition, block):
|
||||
super(While, self).__init__(loc)
|
||||
|
||||
@@ -576,6 +593,8 @@ class While(Node):
|
||||
|
||||
class If(Node):
|
||||
|
||||
__slots__ = [ 'entries' ]
|
||||
|
||||
def __init__(self, loc, entries):
|
||||
"""
|
||||
@param entries: A list of (condition, block) tuples.
|
||||
|
||||
+68
-35
@@ -2,6 +2,9 @@
|
||||
# This includes both simple settings (like the screen dimensions) and
|
||||
# methods that perform standard tasks, like the say and menu methods.
|
||||
|
||||
# This will be deleted by the end of this file.
|
||||
import renpy
|
||||
|
||||
# The title of the game window.
|
||||
window_title = "A Ren'Py Game"
|
||||
|
||||
@@ -47,9 +50,9 @@ profile = False
|
||||
# The directory save files will be saved to.
|
||||
savedir = None
|
||||
|
||||
# The number of images that are allowed to live in the image cache
|
||||
# at once.
|
||||
image_cache_size = 10
|
||||
# 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 statements we will analyze when doing predictive
|
||||
# loading. Please note that this is a total number of statements in a
|
||||
@@ -83,73 +86,97 @@ force_archives = False
|
||||
# An image file containing the mouse cursor, if one is defined.
|
||||
mouse = None
|
||||
|
||||
# The distance the keyboard moves the mouse, per 50 ms tick, in pixels.
|
||||
keymouse_distance = 5
|
||||
|
||||
# The default sound playback sample rate.
|
||||
sound_sample_rate = 44100
|
||||
|
||||
# How fast text is displayed on the screen, by default.
|
||||
annoying_text_cps = None
|
||||
|
||||
# The amount of time music is faded out between tracks.
|
||||
fade_music = 0.0
|
||||
|
||||
# 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' ]
|
||||
|
||||
# A list of layers that are displayed above all other layers.
|
||||
top_layers = [ ]
|
||||
|
||||
# True if we want to show overlays during wait statements, or
|
||||
# false otherwise.
|
||||
overlay_during_wait = True
|
||||
|
||||
# True if we want to allow the fast dissolve.
|
||||
enable_fast_dissolve = 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', 'mouse_4' ],
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouse_3' ],
|
||||
hide_windows = [ 'mouse_2' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
keymouse_right = [ 'K_RIGHT' ],
|
||||
keymouse_up = [ 'K_UP' ],
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouse_1' ],
|
||||
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
|
||||
menu_keyup = [ 'K_UP' ],
|
||||
menu_keydown = [ 'K_DOWN' ],
|
||||
|
||||
# Focus.
|
||||
focus_left = [ 'K_LEFT' ],
|
||||
focus_right = [ 'K_RIGHT' ],
|
||||
focus_up = [ 'K_UP' ],
|
||||
focus_down = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouse_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Input.
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouse_1' ],
|
||||
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
)
|
||||
|
||||
# A list of functions that are called before each interaction.
|
||||
interact_callbacks = [ ]
|
||||
|
||||
# A function that is called when a music track ends, perhaps to
|
||||
# play another track.
|
||||
music_end_event = None
|
||||
|
||||
# A function that is called to tokenize text.
|
||||
text_tokenizer = renpy.display.text.text_tokenizer
|
||||
|
||||
# The number of frames that Ren'Py has shown.
|
||||
frames = 0
|
||||
|
||||
del renpy
|
||||
|
||||
def backup():
|
||||
|
||||
import copy
|
||||
import types
|
||||
|
||||
global _globals
|
||||
_globals = globals().copy()
|
||||
@@ -158,7 +185,13 @@ def backup():
|
||||
del _globals["reload"]
|
||||
del _globals["__builtins__"]
|
||||
|
||||
_globals = copy.deepcopy(_globals)
|
||||
for k, v in _globals.items():
|
||||
|
||||
if k == "text_tokenizer":
|
||||
continue
|
||||
|
||||
_globals[k] = copy.deepcopy(v)
|
||||
|
||||
|
||||
def reload():
|
||||
globals().update(_globals)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# This file contains support for state-machine controlled animations.
|
||||
|
||||
import renpy
|
||||
import random
|
||||
|
||||
class State(object):
|
||||
"""
|
||||
This creates a state that can be used in a SMAnimation.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, name, image, *atlist):
|
||||
"""
|
||||
@param name: A string giving the name of this state.
|
||||
|
||||
@param image: The displayable that is shown to the user while
|
||||
we are in (entering) this state. For convenience, this can
|
||||
also be a string or tuple, which is interpreted with Image.
|
||||
|
||||
image should be None when this State is used with motion,
|
||||
to indicate that the image will be replaced with the child of
|
||||
the motion.
|
||||
|
||||
@param atlist: A list of functions to call on the image. (In
|
||||
general, if something can be used in an at clause, it can be
|
||||
used here as well.)
|
||||
"""
|
||||
|
||||
if image and not isinstance(image, renpy.display.core.Displayable):
|
||||
image = renpy.display.image.Image(image)
|
||||
|
||||
self.name = name
|
||||
self.image = image
|
||||
self.atlist = atlist
|
||||
|
||||
|
||||
def add(self, sma):
|
||||
sma.states[self.name] = self
|
||||
|
||||
def get_image(self):
|
||||
rv = self.image
|
||||
|
||||
for i in self.atlist:
|
||||
rv = i(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def motion_copy(self, child):
|
||||
|
||||
if self.image is not None:
|
||||
child = self.mage
|
||||
|
||||
return State(self.name, child, *self.atlist)
|
||||
|
||||
|
||||
class Edge(object):
|
||||
"""
|
||||
This creates an edge that can be used with a SMAnimation.
|
||||
"""
|
||||
|
||||
def __init__(self, old, delay, new, trans=None, prob=1):
|
||||
"""
|
||||
@param old: The name (a string) of the state that this transition is from.
|
||||
|
||||
@param delay: The number of seconds that this transition takes.
|
||||
|
||||
@param new: The name (a string) of the state that this transition is to.
|
||||
|
||||
@param trans: The transition that will be used to show the
|
||||
image found in the new state. If None, the image is show
|
||||
immediately.
|
||||
|
||||
When used with an SMMotion, the transition should probably be
|
||||
move.
|
||||
|
||||
@param prob: The number of times this edge is added. This can
|
||||
be used to make a transition more probable then others. For
|
||||
example, if one transition out of a state has prob=5, and the
|
||||
other has prob=1, then the one with prob=5 will execute 5/6 of
|
||||
the time, while the one with prob=1 will only occur 1/6 of the
|
||||
time. (Don't make this too large, as memory use is proportional to
|
||||
this value.)
|
||||
"""
|
||||
|
||||
self.old = old
|
||||
self.delay = delay
|
||||
self.new = new
|
||||
self.trans = trans
|
||||
self.prob = prob
|
||||
|
||||
def add(self, sma):
|
||||
for i in range(0, self.prob):
|
||||
sma.edges.setdefault(self.old, []).append(self)
|
||||
|
||||
|
||||
class SMAnimation(renpy.display.core.Displayable):
|
||||
"""
|
||||
This creates a state-machine animation. Such an animation is
|
||||
created by randomly traversing the edges between states in a
|
||||
defined state machine. Each state corresponds to an image shown to
|
||||
the user, with the edges corresponding to the amount of time an
|
||||
image is shown, and the transition it is shown with.
|
||||
|
||||
Images are shown, perhaps with a transition, when we are
|
||||
transitioning into a state containing that image.
|
||||
"""
|
||||
|
||||
def __init__(self, initial, *args, **properties):
|
||||
"""
|
||||
@param initial: The name (a string) of the initial state we
|
||||
start in.
|
||||
|
||||
This accepts as additional arguments the anim.State and
|
||||
anim.Edge objects that are used to make up this state
|
||||
machine.
|
||||
"""
|
||||
|
||||
if 'delay' in properties:
|
||||
self.delay = properties['delay']
|
||||
del properties['delay']
|
||||
else:
|
||||
self.delay = None
|
||||
|
||||
super(SMAnimation, self).__init__(**properties)
|
||||
|
||||
self.properties = properties
|
||||
|
||||
# The initial state.
|
||||
self.initial = initial
|
||||
|
||||
# A map from state name to State object.
|
||||
self.states = { }
|
||||
|
||||
# A map from state name to list of Edge objects.
|
||||
self.edges = { }
|
||||
|
||||
for i in args:
|
||||
i.add(self)
|
||||
|
||||
# The time at which the current edge started. If None, will be
|
||||
# set to st by render.
|
||||
self.edge_start = None
|
||||
|
||||
# A cache for what the current edge looks like when rendered.
|
||||
self.edge_cache = None
|
||||
|
||||
# The current edge.
|
||||
self.edge = None
|
||||
|
||||
# The state we're in.
|
||||
self.state = None
|
||||
|
||||
def predict(self, callback):
|
||||
for i in self.states.itervalues():
|
||||
i.image.predict(callback)
|
||||
|
||||
def pick_edge(self, state):
|
||||
"""
|
||||
This randomly picks an edge out of the given state, if
|
||||
one exists. It updates self.edge if a transition has
|
||||
been selected, or returns None if none can be found. It also
|
||||
updates self.image to be the new image on the selected edge.
|
||||
"""
|
||||
|
||||
|
||||
if state not in self.edges:
|
||||
self.edge = None
|
||||
return
|
||||
|
||||
edges = self.edges[state]
|
||||
self.edge = random.choice(edges)
|
||||
self.state = self.edge.new
|
||||
|
||||
def update_cache(self):
|
||||
"""
|
||||
Places the correct Displayable into the edge cache, based on
|
||||
what is contained in the given edge. This takes into account
|
||||
the old and new states, and any transition that is present.
|
||||
"""
|
||||
|
||||
im = self.states[self.edge.new].get_image()
|
||||
|
||||
if self.edge.trans:
|
||||
im = self.edge.trans(old_widget=self.states[self.edge.old].get_image(),
|
||||
new_widget=im)
|
||||
|
||||
self.edge_cache = im
|
||||
|
||||
def get_placement(self):
|
||||
|
||||
if self.edge_cache:
|
||||
return self.edge_cache.get_placement()
|
||||
|
||||
if self.state:
|
||||
return self.states[self.state].get_image().get_placement()
|
||||
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.edge_start is None or st < self.edge_start:
|
||||
self.edge_start = st
|
||||
self.edge_cache = None
|
||||
self.pick_edge(self.initial)
|
||||
|
||||
while self.edge and st > self.edge_start + self.edge.delay:
|
||||
self.edge_start += self.edge.delay
|
||||
|
||||
self.edge_cache = None
|
||||
self.pick_edge(self.edge.new)
|
||||
|
||||
# If edge is None, then we have a permanent, static picture. Deal
|
||||
# with that.
|
||||
|
||||
if not self.edge:
|
||||
im = renpy.display.render.render(self.states[self.state].get_image(),
|
||||
width, height,
|
||||
st - self.edge_start)
|
||||
|
||||
|
||||
# Otherwise, we have another edge.
|
||||
|
||||
else:
|
||||
if not self.edge_cache:
|
||||
self.update_cache()
|
||||
|
||||
im = renpy.display.render.render(self.edge_cache, width, height, st - self.edge_start)
|
||||
|
||||
renpy.display.render.redraw(self.edge_cache, self.edge.delay - (st - self.edge_start))
|
||||
|
||||
|
||||
iw, ih = im.get_size()
|
||||
|
||||
rv = renpy.display.render.Render(iw, ih)
|
||||
rv.blit(im, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
def __call__(self, child=None, new_widget=None, old_widget=None):
|
||||
"""
|
||||
Used when this SMAnimation is used as a SMMotion. This creates
|
||||
a duplicate of the animation, with all states containing None
|
||||
as the image having that None replaced with the image that is provided here.
|
||||
"""
|
||||
|
||||
if child is None:
|
||||
child = new_widget
|
||||
|
||||
args = [ ]
|
||||
|
||||
for state in self.states.itervalues():
|
||||
args.append(state.motion_copy(child))
|
||||
|
||||
for edges in self.edges.itervalues():
|
||||
args.extend(edges)
|
||||
|
||||
return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
|
||||
|
||||
def Animation(*args):
|
||||
"""
|
||||
A Displayable that draws an animation, which is a series of images
|
||||
that are displayed with time delays between them.
|
||||
|
||||
Odd (first, third, fifth, etc.) arguments to Animation are
|
||||
interpreted as image filenames, while even arguments are the time
|
||||
to delay between each image. If the number of arguments is odd,
|
||||
the animation will stop with the last image (well, actually delay
|
||||
for a year before looping). Otherwise, the animation will restart
|
||||
after the final delay time.
|
||||
"""
|
||||
|
||||
sm = [ 0 ]
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
|
||||
if i % 2 == 0:
|
||||
sm.append(State(i, arg))
|
||||
|
||||
else:
|
||||
|
||||
if i == len(args) - 1:
|
||||
new = 0
|
||||
else:
|
||||
new = i + 1
|
||||
|
||||
sm.append(Edge(i - 1, arg, new))
|
||||
|
||||
return SMAnimation(*sm)
|
||||
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
# 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.
|
||||
import os
|
||||
|
||||
# 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
|
||||
|
||||
if 'RENPY_DISABLE_SOUND' in os.environ:
|
||||
mixer_works = False
|
||||
return
|
||||
|
||||
try:
|
||||
bufsize = 4096
|
||||
|
||||
if 'RENPY_SOUND_BUFSIZE' in os.environ:
|
||||
bufsize = int(os.environ['RENPY_SOUND_BUFSIZE'])
|
||||
|
||||
pygame.mixer.init(renpy.config.sound_sample_rate, -16, 2, bufsize)
|
||||
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 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_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
|
||||
|
||||
fn = renpy.loader.transfn(filename)
|
||||
fn = str(fn)
|
||||
|
||||
pygame.mixer.music.load(fn)
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
# This works around a race condition in pygame/SDL_mixer.
|
||||
if seconds <= 0.0:
|
||||
music_stop()
|
||||
|
||||
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)
|
||||
+114
-302
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
# import renpy.display.core as core
|
||||
# import renpy.display.layout as layout
|
||||
@@ -23,7 +24,13 @@ def map_event(ev, name):
|
||||
keys = renpy.config.keymap[name]
|
||||
|
||||
if ev.type == MOUSEBUTTONDOWN:
|
||||
if ( "mouse_" + str(ev.button) ) in keys:
|
||||
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
|
||||
@@ -79,7 +86,7 @@ def skipping(ev):
|
||||
|
||||
return
|
||||
|
||||
class Keymap(renpy.display.layout.Container):
|
||||
class Keymap(renpy.display.layout.Null):
|
||||
"""
|
||||
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
|
||||
@@ -87,46 +94,42 @@ class Keymap(renpy.display.layout.Container):
|
||||
"""
|
||||
|
||||
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):
|
||||
action()
|
||||
rv = action()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
def render(self, width, height, st):
|
||||
return None
|
||||
# def render(self, width, height, st):
|
||||
# return None
|
||||
|
||||
class KeymouseBehavior(renpy.display.layout.Null):
|
||||
class PauseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class that causes the keyboard to move the mouse. It's
|
||||
useful on the game and key menus, as well as in imagemaps and the
|
||||
like.
|
||||
This is a class implementing the Pause behavior, which is to
|
||||
return a value after a certain amount of time has elapsed.
|
||||
"""
|
||||
|
||||
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:
|
||||
|
||||
pressed = pygame.key.get_pressed()
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
ox, oy = x, y
|
||||
|
||||
if is_pressed(pressed, "keymouse_left"):
|
||||
x -= renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_right"):
|
||||
x += renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_up"):
|
||||
y -= renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_down"):
|
||||
y += renpy.config.keymouse_distance
|
||||
|
||||
if (x, y) != (ox, oy):
|
||||
pygame.mouse.set_pos((x, y))
|
||||
|
||||
return None
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and ev.duration > self.delay:
|
||||
return self.result
|
||||
|
||||
|
||||
class SayBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
@@ -136,18 +139,13 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
mouse button.
|
||||
"""
|
||||
|
||||
def __init__(self, delay=None):
|
||||
super(SayBehavior, self).__init__()
|
||||
focusable = True
|
||||
|
||||
self.delay = delay
|
||||
def __init__(self, default=True, **properties):
|
||||
super(SayBehavior, self).__init__(default=default, **properties)
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and ev.duration > self.delay:
|
||||
return False
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
renpy.config.allow_skipping and renpy.config.skipping and \
|
||||
ev.duration > renpy.config.skip_delay / 1000.0:
|
||||
@@ -157,8 +155,7 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
elif renpy.game.context().seen_current(True):
|
||||
return True
|
||||
|
||||
|
||||
if map_event(ev, "dismiss"):
|
||||
if map_event(ev, "dismiss") and self.is_focused():
|
||||
return True
|
||||
|
||||
if map_event(ev, "rollforward"):
|
||||
@@ -166,231 +163,78 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
class Menu(renpy.display.layout.VBox):
|
||||
|
||||
def __init__(self, menuitems, **properties):
|
||||
"""
|
||||
@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.
|
||||
"""
|
||||
|
||||
super(Menu, self).__init__(**properties)
|
||||
|
||||
self.selected = None
|
||||
self.results = [ ]
|
||||
|
||||
self.caption_style = renpy.style.Style('menu_caption', { })
|
||||
self.selected_style = renpy.style.Style('menu_choice', { })
|
||||
self.unselected_style = renpy.style.Style('menu_choice', { })
|
||||
|
||||
self.selected_style.set_prefix('hover_')
|
||||
self.unselected_style.set_prefix('idle_')
|
||||
|
||||
for i, (caption, result) in enumerate(menuitems):
|
||||
self.add(renpy.display.text.Text(caption))
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
# print ev
|
||||
# print x, y
|
||||
|
||||
old_selected = self.selected
|
||||
|
||||
# Change selection based on mouse position.
|
||||
if ev.type == MOUSEMOTION:
|
||||
target = self.child_at_point(x, y)
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
if self.results[target] is not None:
|
||||
self.selected = target
|
||||
|
||||
# Make selection based on mouse click position.
|
||||
if map_event(ev, "menu_mouseselect"):
|
||||
target = self.child_at_point(x, y)
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
if self.results[target] is not None:
|
||||
renpy.sound.play(self.selected_style.activate_sound)
|
||||
return self.results[target]
|
||||
|
||||
# Change selection based on keypress.
|
||||
if map_event(ev, "menu_keydown"):
|
||||
|
||||
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 map_event(ev, "menu_keyup"):
|
||||
|
||||
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 map_event(ev, "menu_keyselect"):
|
||||
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,
|
||||
hovered=None, **properties):
|
||||
|
||||
super(Button, self).__init__(child, style=style, **properties)
|
||||
self.style.set_prefix('idle_')
|
||||
|
||||
self.old_hover = False
|
||||
self.activated = False
|
||||
self.clicked = clicked
|
||||
self.hovered = hovered
|
||||
self.focusable = clicked is not None
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.old_hover:
|
||||
self.set_style_prefix('hover_')
|
||||
else:
|
||||
self.set_style_prefix('idle_')
|
||||
rv = super(Button, self).render(width, height, st)
|
||||
|
||||
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
|
||||
|
||||
return super(Button, self).render(width, height, st)
|
||||
|
||||
def set_hover(self, hover):
|
||||
"""
|
||||
Called when we change from hovered to un-hovered, or
|
||||
vice-versa.
|
||||
"""
|
||||
|
||||
# if hover:
|
||||
# self.style.set_prefix('hover_')
|
||||
# else:
|
||||
# self.style.set_prefix('idle_')
|
||||
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
inside = False
|
||||
# We deactivate on an event.
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
|
||||
width, height = self.window_size
|
||||
|
||||
if x >= 0 and x < width and y >= 0 and y < height:
|
||||
inside = True
|
||||
|
||||
if self.style.enable_hover and ev.type == MOUSEMOTION:
|
||||
|
||||
if self.old_hover != inside:
|
||||
self.old_hover = inside
|
||||
self.set_hover(inside)
|
||||
|
||||
if inside:
|
||||
if self.hovered:
|
||||
self.hovered()
|
||||
|
||||
renpy.sound.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if map_event(ev, "button_select"):
|
||||
if inside and self.clicked:
|
||||
renpy.sound.play(self.style.activate_sound)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
if self.focusable:
|
||||
if self.is_focused():
|
||||
self.set_style_prefix('hover_')
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
self.set_style_prefix('idle_')
|
||||
else:
|
||||
self.set_style_prefix('insensitive_')
|
||||
|
||||
# If not focused, ignore all events.
|
||||
if not self.is_focused():
|
||||
return None
|
||||
|
||||
# If clicked,
|
||||
if map_event(ev, "button_select") and self.clicked:
|
||||
|
||||
self.activated = True
|
||||
|
||||
self.set_style_prefix('activate_')
|
||||
renpy.display.audio.play(self.style.sound)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
return None
|
||||
|
||||
return super(Button, self).event(ev, x, y)
|
||||
|
||||
# Reimplementation of the TextButton widget as a Button and a Text
|
||||
# widget.
|
||||
def TextButton(text, style='button', text_style='button_text',
|
||||
clicked=None):
|
||||
clicked=None, **properties):
|
||||
|
||||
text = renpy.display.text.Text(text, style=text_style)
|
||||
return Button(text, style=style, clicked=clicked)
|
||||
return Button(text, style=style, clicked=clicked, **properties)
|
||||
|
||||
|
||||
|
||||
# class TextButton(Button):
|
||||
|
||||
# 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):
|
||||
"""
|
||||
@@ -398,35 +242,50 @@ class Input(renpy.display.text.Text):
|
||||
"""
|
||||
|
||||
def __init__(self, default, length=None,
|
||||
style='input_text', **properties):
|
||||
super(Input, self).__init__(default + "_", style=style, **properties)
|
||||
style='input_text',
|
||||
allow=None,
|
||||
exclude=None,
|
||||
**properties):
|
||||
|
||||
super(Input, self).__init__(default.replace("{", "{{") + "_", 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]
|
||||
|
||||
self.set_text(self.content + "_")
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
elif map_event(ev, "input_enter"):
|
||||
return self.content
|
||||
|
||||
elif ev.type == KEYDOWN and ev.unicode:
|
||||
if ord(ev.unicode[0]) < 32:
|
||||
return None
|
||||
return None
|
||||
|
||||
if self.length and len(self.content) >= self.length:
|
||||
return None
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
if self.allow and ev.unicode not in self.allow:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
if self.exclude and ev.unicode in self.exclude:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
self.content += ev.unicode
|
||||
|
||||
self.set_text(self.content + "_")
|
||||
renpy.game.interface.redraw(0)
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
|
||||
class Bar(renpy.display.core.Displayable):
|
||||
@@ -435,7 +294,7 @@ class Bar(renpy.display.core.Displayable):
|
||||
to clicks on that value.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height, range, value, clicked=None,
|
||||
def __init__(self, width, height, range, value,
|
||||
style='bar', **properties):
|
||||
|
||||
super(Bar, self).__init__()
|
||||
@@ -447,71 +306,24 @@ class Bar(renpy.display.core.Displayable):
|
||||
self.range = range
|
||||
self.value = value
|
||||
|
||||
self.clicked = clicked
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if not self.clicked:
|
||||
return
|
||||
|
||||
if not map_event(ev, 'bar_click'):
|
||||
return
|
||||
|
||||
if not (0 <= x < self.width and 0 <= y <= self.height):
|
||||
return
|
||||
|
||||
print x, y
|
||||
|
||||
lgutter = self.style.left_gutter
|
||||
rgutter = self.style.right_gutter
|
||||
|
||||
if x < lgutter:
|
||||
value = 0
|
||||
elif x > self.width - rgutter:
|
||||
value = self.range
|
||||
else:
|
||||
barwidth = self.width - lgutter - rgutter
|
||||
|
||||
# This makes it easier to select 100%.
|
||||
x = x - lgutter
|
||||
x = x + (barwidth / self.range // 2)
|
||||
|
||||
value = x * self.range / barwidth
|
||||
|
||||
value = max(value, 0)
|
||||
|
||||
|
||||
rv = self.clicked(value)
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
width = self.width
|
||||
height = self.height
|
||||
|
||||
# The amount of space taken up by the bars.
|
||||
|
||||
if self.clicked:
|
||||
lgutter = self.style.left_gutter
|
||||
rgutter = self.style.right_gutter
|
||||
else:
|
||||
lgutter = 0
|
||||
rgutter = 0
|
||||
lgutter = 0
|
||||
rgutter = 0
|
||||
|
||||
barwidth = width - lgutter - rgutter
|
||||
|
||||
left_width = barwidth * self.value // self.range
|
||||
right_width = barwidth - left_width
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
lsurf = self.style.left_bar.render(left_width, height, st)
|
||||
rsurf = self.style.right_bar.render(right_width, height, st)
|
||||
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))
|
||||
@@ -534,20 +346,20 @@ class Conditional(renpy.display.layout.Container):
|
||||
self.condition = condition
|
||||
self.null = renpy.display.layout.Null()
|
||||
|
||||
self.state = eval(self.condition, renpy.game.store)
|
||||
self.state = eval(self.condition, vars(renpy.store))
|
||||
|
||||
def render(self, width, height, st):
|
||||
if self.state:
|
||||
return self.child.render(width, height, st)
|
||||
return render(self.child, width, height, st)
|
||||
else:
|
||||
return self.null.render(width, height, st)
|
||||
return render(self.null, width, height, st)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
state = eval(self.condition, renpy.game.store)
|
||||
state = eval(self.condition, vars(renpy.store))
|
||||
|
||||
if state != self.state:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
self.state = state
|
||||
|
||||
|
||||
+453
-251
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,728 @@
|
||||
# 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)
|
||||
|
||||
def predict_files(self):
|
||||
"""
|
||||
Returns a list of files that will be accessed when this image
|
||||
operation is performed.
|
||||
"""
|
||||
|
||||
return [ ]
|
||||
|
||||
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
|
||||
|
||||
def predict_files(self):
|
||||
return [ self.filename ]
|
||||
|
||||
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
|
||||
|
||||
def predict_files(self):
|
||||
|
||||
rv = [ ]
|
||||
|
||||
for i in self.images:
|
||||
rv.extend(i.predict_files())
|
||||
|
||||
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))
|
||||
|
||||
def predict_files(self):
|
||||
return self.image.predict_files()
|
||||
|
||||
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)
|
||||
def predict_files(self):
|
||||
return self.image.predict_files()
|
||||
|
||||
|
||||
|
||||
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 predict_files(self):
|
||||
return self.image.predict_files()
|
||||
|
||||
|
||||
|
||||
def ramp(start, end):
|
||||
"""
|
||||
Returns a 256 character linear ramp, where the first character has
|
||||
the value start and the last character has the value end. Such a
|
||||
ramp can be used as a map argument of im.Map.
|
||||
"""
|
||||
|
||||
chars = [ ]
|
||||
|
||||
for i in range(0, 256):
|
||||
i = i / 255.0
|
||||
chars.append(chr(int( end * i + start * (1.0 - i) ) ) )
|
||||
|
||||
return "".join(chars)
|
||||
|
||||
|
||||
identity = ramp(0, 255)
|
||||
|
||||
class Map(ImageBase):
|
||||
"""
|
||||
This adjusts the colors of the image that is its child. It takes
|
||||
as arguments 4 256 character strings. If a pixel channel has a
|
||||
value of 192, then the value of the 192nd character in the string
|
||||
is used for the mapped pixel component.
|
||||
"""
|
||||
|
||||
def __init__(self, im, rmap=identity, gmap=identity, bmap=identity,
|
||||
amap=identity, force_alpha=False):
|
||||
|
||||
im = image(im)
|
||||
|
||||
super(Map, self).__init__(im, rmap, gmap, bmap, amap, force_alpha)
|
||||
|
||||
self.image = im
|
||||
self.rmap = rmap
|
||||
self.gmap = gmap
|
||||
self.bmap = bmap
|
||||
self.amap = amap
|
||||
|
||||
self.force_alpha = force_alpha
|
||||
|
||||
def load(self):
|
||||
|
||||
surf = cache.get(self.image)
|
||||
|
||||
if not renpy.display.module.can_map:
|
||||
return surf
|
||||
|
||||
if self.force_alpha and not (surf.get_flags() & SRCALPHA):
|
||||
surf = surf.convert_alpha()
|
||||
|
||||
rv = pygame.Surface(surf.get_size(), surf.get_flags(), surf)
|
||||
|
||||
renpy.display.module.map(surf, rv,
|
||||
self.rmap, self.gmap, self.bmap, self.amap)
|
||||
|
||||
return rv
|
||||
|
||||
def predict_files(self):
|
||||
return self.image.predict_files()
|
||||
|
||||
def Alpha(image, alpha):
|
||||
"""
|
||||
Returns an alpha-mapped version of the image. Alpha is the maximum
|
||||
alpha that this image can have, a number between 0.0 (fully
|
||||
transparent) and 1.0 (opaque).
|
||||
|
||||
If an image already has an alpha channel, values in that alpha
|
||||
channel are reduced as appropriate.
|
||||
"""
|
||||
|
||||
amap = ramp(0, int(255 * alpha))
|
||||
|
||||
return Map(image, identity, identity, identity, amap, force_alpha=True)
|
||||
|
||||
class Tile(ImageBase):
|
||||
"""
|
||||
This tiles the image, repeating it vertically and horizontally
|
||||
until it is as large as the specified size. If no size is given,
|
||||
then the size defaults to the size of the screen.
|
||||
"""
|
||||
|
||||
def __init__(self, im, size=None):
|
||||
|
||||
im = image(im)
|
||||
|
||||
if size is None:
|
||||
size = (renpy.config.screen_width, renpy.config.screen_height)
|
||||
|
||||
super(Tile, self).__init__(im, size)
|
||||
self.image = im
|
||||
self.size = size
|
||||
|
||||
def load(self):
|
||||
|
||||
surf = cache.get(self.image)
|
||||
|
||||
width, height = self.size
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
|
||||
rv = pygame.Surface(self.size, 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
for y in range(0, height, sh):
|
||||
for x in range(0, width, sw):
|
||||
rv.blit(surf, (x, y))
|
||||
|
||||
return rv
|
||||
|
||||
def predict_files(self):
|
||||
return self.image.predict_files()
|
||||
|
||||
|
||||
|
||||
def image(arg, loose=False, **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 the loose argument is False, then this will report an error if an
|
||||
arbitrary argument is given. If it's True, then the argument is passed
|
||||
through unchanged.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
if loose:
|
||||
return arg
|
||||
|
||||
if 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))
|
||||
+87
-399
@@ -1,197 +1,14 @@
|
||||
# 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 *
|
||||
|
||||
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 = [ ]
|
||||
|
||||
def really_load_image(self, fn):
|
||||
"""
|
||||
This is called by load_image, and does the actual loading of
|
||||
images. This may be a load of an image, or perhaps the
|
||||
compositing of images if fn is a tuple.
|
||||
"""
|
||||
|
||||
# If fn is not a single filename but a tuple, we composite the
|
||||
# elements of the tuple.
|
||||
if isinstance(fn, tuple):
|
||||
if not tuple:
|
||||
raise Exception("Trying to create a composite image from an empty tuple.")
|
||||
|
||||
base = self.load_image(fn[0])
|
||||
|
||||
rv = pygame.Surface(base.get_size(), 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
rv.blit(base, (0, 0))
|
||||
|
||||
for i in fn[1:]:
|
||||
layer = self.load_image(i)
|
||||
rv.blit(layer, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
im = pygame.image.load(renpy.loader.load(fn), fn)
|
||||
im = im.convert_alpha()
|
||||
|
||||
return im
|
||||
|
||||
|
||||
# Forces an image load, regardless of if the cache is full or not.
|
||||
def load_image(self, fn):
|
||||
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)
|
||||
|
||||
# iw, ih = im.get_size()
|
||||
|
||||
# surf = renpy.display.surface.Surface(iw, ih)
|
||||
# surf.blit(im, (0, 0))
|
||||
|
||||
im = self.really_load_image(fn)
|
||||
|
||||
self.surface_map[fn] = im
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
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.
|
||||
|
||||
If the filename is not a single string but instead a tuple of
|
||||
strings, the image is considered to be"layered". In this case,
|
||||
the image will be the size of the first image in the tuple, and
|
||||
other images will be aligned with the upper-left corner of the
|
||||
image.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
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)
|
||||
Image = renpy.display.im.image
|
||||
|
||||
class UncachedImage(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -200,11 +17,17 @@ 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):
|
||||
@@ -212,30 +35,31 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, w, h, st):
|
||||
sw, sh = self.surf.get_size()
|
||||
rv = renpy.display.surface.Surface(sw, sh)
|
||||
rv = renpy.display.render.Render(sw, sh)
|
||||
rv.blit(self.surf, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
# Should never be called, but what the hey?
|
||||
def predict(self, callback):
|
||||
callback(self.filename)
|
||||
return None
|
||||
|
||||
class ImageReference(renpy.display.core.Displayable):
|
||||
"""
|
||||
This is a reference to an image or animation that is kept
|
||||
in exports.images.
|
||||
|
||||
@ivar name: The name of the image.
|
||||
|
||||
Not serialized:
|
||||
|
||||
@ivar target: If defined, a pointer to the thing that name resolves to.
|
||||
ImageReference objects are used to reference images by their name,
|
||||
which is a tuple of strings corresponding to the name used to define
|
||||
the image in an image statment.
|
||||
"""
|
||||
|
||||
nosave = [ 'target' ]
|
||||
|
||||
def __init__(self, name):
|
||||
"""
|
||||
@param name: A tuple of strings, the name of the image.
|
||||
"""
|
||||
|
||||
super(ImageReference, self).__init__()
|
||||
|
||||
self.name = name
|
||||
|
||||
def find_target(self):
|
||||
@@ -245,8 +69,7 @@ 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)
|
||||
@@ -276,17 +99,22 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if not hasattr(self, 'target'):
|
||||
self.find_target()
|
||||
|
||||
return self.target.render(width, height, st)
|
||||
return render(self.target, 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):
|
||||
@@ -298,17 +126,20 @@ 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):
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv.fill(self.color)
|
||||
si = renpy.display.im.SolidImage(self.color,
|
||||
width,
|
||||
height)
|
||||
|
||||
return rv
|
||||
return render(si, width, height, st)
|
||||
|
||||
class Frame(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -322,11 +153,10 @@ class Frame(renpy.display.core.Displayable):
|
||||
the center of the image is scaled in both x and y directions.
|
||||
"""
|
||||
|
||||
nosave = [ 'cache' ]
|
||||
|
||||
def __init__(self, filename, xborder, yborder):
|
||||
def __init__(self, image, xborder, yborder):
|
||||
"""
|
||||
@param filename: The file that the original image will be read from.
|
||||
@param image: The image (which may be a filename or image
|
||||
object) that will be scaled.
|
||||
|
||||
@param xborder: The number of pixels in the x direction to use as
|
||||
a border.
|
||||
@@ -334,108 +164,32 @@ 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 file share a dimension
|
||||
For better performance, have the image share a dimension
|
||||
length in common with the size the frame will be rendered
|
||||
at. We detect this and avoid scaling if possible.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
super(Frame, self).__init__()
|
||||
|
||||
self.image = Image(image)
|
||||
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)
|
||||
|
||||
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
|
||||
return render(fi, width, height, st)
|
||||
|
||||
source = cache.load_image(self.filename)
|
||||
sw, sh = source.get_size()
|
||||
def predict(self, callback):
|
||||
self.image.predict(callback)
|
||||
|
||||
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):
|
||||
# This class has been replaced with a function in anim.
|
||||
class OldAnimation(renpy.display.core.Displayable):
|
||||
"""
|
||||
A Displayable that draws an animation, which is a series of images
|
||||
that are displayed with time delays between them.
|
||||
@@ -451,13 +205,15 @@ 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(arg)
|
||||
self.images.append(Image(arg))
|
||||
else:
|
||||
self.delays.append(arg)
|
||||
|
||||
@@ -470,107 +226,35 @@ class Animation(renpy.display.core.Displayable):
|
||||
|
||||
for image, delay in zip(self.images, self.delays):
|
||||
if t < delay:
|
||||
renpy.game.interface.redraw(delay - t)
|
||||
return cache.load_image(image)
|
||||
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
|
||||
|
||||
else:
|
||||
t = t - delay
|
||||
|
||||
def predict(self, callback):
|
||||
for i in self.images:
|
||||
callback(i)
|
||||
i.predict(callback)
|
||||
|
||||
def get_placement(self):
|
||||
return renpy.game.style.image_placement
|
||||
|
||||
class ImageMap(renpy.display.core.Displayable):
|
||||
"""
|
||||
The displayable that implements renpy.imagemap.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, ground, selected, hotspots, unselected=None,
|
||||
style='imagemap', **properties):
|
||||
|
||||
self.ground = ground
|
||||
self.selected = selected
|
||||
self.hotspots = hotspots
|
||||
|
||||
if not unselected:
|
||||
self.unselected = self.ground
|
||||
else:
|
||||
self.unselected = unselected
|
||||
|
||||
self.active = None
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
# This doesn't do anything quite yet.
|
||||
def predict(self, callback):
|
||||
callback(self.ground)
|
||||
callback(self.selected)
|
||||
callback(self.unselected)
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
ground = cache.load_image(self.ground)
|
||||
selected = cache.load_image(self.selected)
|
||||
unselected = cache.load_image(self.unselected)
|
||||
|
||||
width, height = ground.get_size()
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv.blit(ground, (0, 0))
|
||||
|
||||
for i, hotspot in enumerate(self.hotspots):
|
||||
|
||||
x0, y0, x1, y1, result = hotspot
|
||||
|
||||
if i == self.active:
|
||||
source = selected
|
||||
else:
|
||||
source = unselected
|
||||
|
||||
subsurface = source.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 renpy.display.behavior.map_event(ev, "imagemap_select"):
|
||||
renpy.sound.play(self.style.activate_sound)
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
class ImageButton(renpy.display.behavior.Button):
|
||||
"""
|
||||
Used to implement the guts of an image button.
|
||||
"""
|
||||
|
||||
def __init__(self, idle_image, hover_image, style='image_button',
|
||||
def __init__(self, idle_image, hover_image,
|
||||
style='image_button',
|
||||
image_style='image_button_image',
|
||||
clicked=None, hovered=None):
|
||||
clicked=None, hovered=None, **properties):
|
||||
|
||||
self.idle_image = Image(idle_image, style=image_style)
|
||||
self.idle_image.style.set_prefix("idle_")
|
||||
@@ -580,14 +264,18 @@ class ImageButton(renpy.display.behavior.Button):
|
||||
super(ImageButton, self).__init__(self.idle_image,
|
||||
style=style,
|
||||
clicked=clicked,
|
||||
hovered=hovered)
|
||||
hovered=hovered,
|
||||
**properties)
|
||||
|
||||
def predict(self, callback):
|
||||
self.idle_image.predict(callback)
|
||||
self.hover_image.predict(callback)
|
||||
|
||||
def focus(self, default=False):
|
||||
self.child = self.hover_image
|
||||
super(ImageButton, self).focus(default=default)
|
||||
|
||||
def set_hover(self, hover):
|
||||
super(ImageButton, self).set_hover(hover)
|
||||
|
||||
if hover:
|
||||
self.child = self.hover_image
|
||||
else:
|
||||
self.child = self.idle_image
|
||||
def unfocus(self):
|
||||
self.child = self.idle_image
|
||||
super(ImageButton, self).unfocus()
|
||||
|
||||
|
||||
+421
-190
@@ -5,6 +5,19 @@ 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):
|
||||
"""
|
||||
@@ -14,14 +27,20 @@ class Null(renpy.display.core.Displayable):
|
||||
"""
|
||||
|
||||
def __init__(self, width=0, height=0, style='default', **properties):
|
||||
super(Null, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, 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):
|
||||
return renpy.display.surface.Surface(self.width, self.height)
|
||||
rv = renpy.display.render.Render(self.width, self.height)
|
||||
|
||||
if self.focusable:
|
||||
rv.add_focus(self, None, None, None, None, None)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Container(renpy.display.core.Displayable):
|
||||
@@ -44,16 +63,24 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *args, **properties):
|
||||
|
||||
super(Container, self).__init__()
|
||||
|
||||
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)
|
||||
|
||||
@@ -73,7 +100,7 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = self.child.render(width, height, st)
|
||||
rv = render(self.child, width, height, st)
|
||||
self.offsets = [ (0, 0) ]
|
||||
self.sizes = [ rv.get_size() ]
|
||||
|
||||
@@ -83,7 +110,10 @@ class Container(renpy.display.core.Displayable):
|
||||
return self.child.get_placement()
|
||||
|
||||
def event(self, ev, x, y):
|
||||
for i, (xo, yo) in zip(self.children, self.offsets):
|
||||
children_offsets = zip(self.children, self.offsets)
|
||||
children_offsets.reverse()
|
||||
|
||||
for i, (xo, yo) in children_offsets:
|
||||
rv = i.event(ev, x - xo, y - yo)
|
||||
if rv is not None:
|
||||
return rv
|
||||
@@ -113,9 +143,82 @@ 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 = [ ]
|
||||
|
||||
# A map from layer name to the widget corresponding to
|
||||
# that layer.
|
||||
self.layers = None
|
||||
|
||||
# The scene list for this widget.
|
||||
self.scene_list = [ ]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
self.scene_list.extend(l)
|
||||
|
||||
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
|
||||
@@ -134,14 +237,12 @@ class Position(Container):
|
||||
child of this widget is placed.
|
||||
"""
|
||||
|
||||
super(Position, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
super(Position, self).__init__(style=style, **properties)
|
||||
self.add(child)
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
surf = self.child.render(width, height, st)
|
||||
surf = render(self.child, width, height, st)
|
||||
cw, ch = surf.get_size()
|
||||
|
||||
self.offsets = [ (0, 0) ]
|
||||
@@ -152,7 +253,104 @@ 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,
|
||||
transpose=False,
|
||||
style='default', **properties):
|
||||
"""
|
||||
@param cols: The number of columns in this widget.
|
||||
|
||||
@params rows: The number of rows in this widget.
|
||||
|
||||
@params transpose: True if the grid should be transposed.
|
||||
"""
|
||||
|
||||
super(Grid, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
|
||||
self.padding = padding
|
||||
self.transpose = transpose
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
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.")
|
||||
|
||||
# If necessary, transpose the grid (kinda hacky, but it works here.)
|
||||
if self.transpose:
|
||||
self.transpose = False
|
||||
|
||||
old_children = self.children[:]
|
||||
|
||||
for y in range(0, rows):
|
||||
for x in range(0, cols):
|
||||
self.children[x + y * cols] = old_children[ y + x * rows ]
|
||||
|
||||
|
||||
# Now, start the actual rendering.
|
||||
|
||||
renwidth = width
|
||||
renheight = height
|
||||
|
||||
if self.style.xfill:
|
||||
renwidth = (width - (cols - 1) * padding) / cols
|
||||
if self.style.yfill:
|
||||
renheight = (height - (rows - 1) * padding) / rows
|
||||
|
||||
renders = [ render(i, renwidth, renheight, 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 = renwidth
|
||||
|
||||
if self.style.yfill:
|
||||
cheight = renheight
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -192,7 +390,7 @@ class HBox(Container):
|
||||
for i in self.children:
|
||||
|
||||
xoffsets.append(xo)
|
||||
surf = i.render(remwidth, height, st)
|
||||
surf = render(i, remwidth, height, st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
@@ -209,7 +407,7 @@ class HBox(Container):
|
||||
|
||||
width = xo - self.padding
|
||||
|
||||
rv = renpy.display.surface.Surface(width, myheight)
|
||||
rv = renpy.display.render.Render(width, myheight)
|
||||
|
||||
for surf, child, xo in zip(surfaces, self.children, xoffsets):
|
||||
sw, sh = surf.get_size()
|
||||
@@ -259,7 +457,7 @@ class VBox(Container):
|
||||
|
||||
yoffsets.append(yo)
|
||||
|
||||
surf = i.render(width, remheight, st)
|
||||
surf = render(i, width, remheight, st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
@@ -276,7 +474,7 @@ class VBox(Container):
|
||||
|
||||
height = yo - self.padding
|
||||
|
||||
rv = renpy.display.surface.Surface(mywidth, height)
|
||||
rv = renpy.display.render.Render(mywidth, height)
|
||||
|
||||
for surf, child, yo in zip(surfaces, self.children, yoffsets):
|
||||
|
||||
@@ -288,39 +486,6 @@ 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):
|
||||
"""
|
||||
@@ -340,56 +505,75 @@ class Window(Container):
|
||||
|
||||
def __init__(self, child, style='window', **properties):
|
||||
|
||||
super(Window, self).__init__()
|
||||
|
||||
super(Window, self).__init__(style=style, **properties)
|
||||
self.add(child)
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
# save typing and screen space.
|
||||
# save some typing.
|
||||
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 = self.child.render(width - 2 * style.xmargin - 2 * style.xpadding,
|
||||
height - 2 * style.ymargin - 2 * style.ypadding,
|
||||
st)
|
||||
surf = render(self.child,
|
||||
width - cxmargin - cxpadding,
|
||||
height - cymargin - cypadding,
|
||||
st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
# If we don't fill, shrink our size to fit.
|
||||
|
||||
if not style.xfill:
|
||||
width = max(2 * style.xmargin + 2 * style.xpadding + sw, style.xminimum)
|
||||
width = max(cxmargin + cxpadding + sw, xminimum)
|
||||
|
||||
if not style.yfill:
|
||||
height = max(2 * style.ymargin + 2 * style.ypadding + sh, style.yminimum)
|
||||
height = max(cymargin + cypadding + sh, yminimum)
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(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 - 2 * style.xmargin
|
||||
bh = height - 2 * style.ymargin
|
||||
bw = width - cxmargin
|
||||
bh = height - cymargin
|
||||
|
||||
back = style.background.render(bw, bh, st)
|
||||
back = render(style.background, bw, bh, st)
|
||||
|
||||
rv.blit(back,
|
||||
(style.xmargin, style.ymargin))
|
||||
(left_margin, top_margin))
|
||||
# (0, 0, bw, bh))
|
||||
|
||||
offsets = self.child.place(rv,
|
||||
style.xmargin + style.xpadding,
|
||||
style.ymargin + style.ypadding,
|
||||
width - 2 * (style.xmargin + style.xpadding),
|
||||
height - 2 * (style.ymargin + style.ypadding),
|
||||
left_margin + left_padding,
|
||||
top_margin + top_padding,
|
||||
width - cxmargin - cxpadding,
|
||||
height - cymargin - cypadding,
|
||||
surf)
|
||||
|
||||
|
||||
self.offsets = [ offsets ]
|
||||
self.sizes = [ (sw, sh) ]
|
||||
@@ -399,163 +583,210 @@ class Window(Container):
|
||||
return rv
|
||||
|
||||
|
||||
class Pan(Container):
|
||||
class Motion(Container):
|
||||
"""
|
||||
This is used to pan over a child displayable, which is almost
|
||||
always an image. It works by interpolating the placement of the
|
||||
upper-left corner of the image, over time. It's only really
|
||||
suitable for use with images that are larger than the screen, as
|
||||
we don't do any cropping on the image.
|
||||
This is used to move a child displayable around the screen. It
|
||||
works by supplying a time value to a user-supplied function,
|
||||
which is in turn expected to return a pair giving the x and y
|
||||
location of the upper-left-hand corner of the child, or a
|
||||
4-tuple giving that and the xanchor and yanchor of the child.
|
||||
|
||||
The time value is a floating point number that ranges from 0 to
|
||||
1. If repeat is True, then the motion repeats every period
|
||||
sections. (Otherwise, it stops.) If bounce is true, the
|
||||
time value varies from 0 to 1 to 0 again.
|
||||
|
||||
The function supplied needs to be pickleable, which means it needs
|
||||
to be defined as a name in an init block. It cannot be a lambda or
|
||||
anonymous inner function. If you can get away with using Pan or
|
||||
Move, use them instead.
|
||||
|
||||
Please note that floats and ints are interpreted as for xpos and
|
||||
ypos, with floats being considered fractions of the screen.
|
||||
"""
|
||||
|
||||
def __init__(self, startpos, endpos, time, child,
|
||||
style='image_placement', **properties):
|
||||
def __init__(self, function, period, child=None, new_widget=None, old_widget=None, repeat=False, bounce=False, delay=None, style='default', **properties):
|
||||
"""
|
||||
@param child: The child displayable.
|
||||
|
||||
@param startpos: The initial coordinates of the upper-left
|
||||
corner of the screen, relative to the image.
|
||||
@param new_widget: If child is None, it is set to new_widget,
|
||||
so that we can speak the transition protocol.
|
||||
|
||||
@param endpos: The coordinates of the upper-left corner of the
|
||||
screen, relative to the image, after time has elapsed.
|
||||
@param old_widget: Ignored, for compatibility with the transition protocol.
|
||||
|
||||
@param time: The time it takes to pan from startpos to endpos.
|
||||
@param function: A function that takes a floating point value and returns
|
||||
an xpos, ypos tuple.
|
||||
|
||||
@param period: The amount of time it takes to go through one cycle, in seconds.
|
||||
|
||||
@param repeat: Should we repeat after a period is up?
|
||||
|
||||
@param bounce: Should we bounce?
|
||||
|
||||
@param delay: If we are used as a transition, how long we should take. If None, defaults to period.
|
||||
|
||||
This can also be used as a transition. When used as a
|
||||
transition, the motion is applied to the new_widget for delay
|
||||
seconds.
|
||||
"""
|
||||
|
||||
super(Pan, self).__init__()
|
||||
self.add(child)
|
||||
if child is None:
|
||||
child = new_widget
|
||||
|
||||
self.startpos = startpos
|
||||
self.endpos = endpos
|
||||
self.time = time
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
if delay is None:
|
||||
delay = period
|
||||
|
||||
super(Motion, self).__init__(style=style, **properties)
|
||||
|
||||
self.child = child
|
||||
self.function = function
|
||||
self.period = period
|
||||
self.repeat = repeat
|
||||
self.bounce = bounce
|
||||
self.delay = delay
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
surf = self.child.render(width, height, st)
|
||||
self.sizes = [ surf.get_size() ]
|
||||
|
||||
x0, y0 = self.startpos
|
||||
x1, y1 = self.endpos
|
||||
|
||||
if self.time > 0:
|
||||
tfrac = (st / self.time)
|
||||
else:
|
||||
tfrac = 1.0
|
||||
|
||||
if tfrac > 1.0:
|
||||
tfrac = 1.0
|
||||
|
||||
xo = int(x0 * (1.0 - tfrac) + x1 * tfrac)
|
||||
yo = int(y0 * (1.0 - tfrac) + y1 * tfrac)
|
||||
|
||||
if self.repeat:
|
||||
st = st % self.period
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
self.offsets = [ (-xo, -yo) ]
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
|
||||
# print surf
|
||||
|
||||
subsurf = surf.subsurface((xo, yo, width, height))
|
||||
rv.blit(subsurf, (0, 0))
|
||||
|
||||
# rv.blit(surf, (-xo, -yo))
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
return rv
|
||||
|
||||
class Move(Container):
|
||||
"""
|
||||
This moves a child relative to the thing containing it. This
|
||||
motion is done by manipulating the xpos and ypos properties in a
|
||||
placement style.
|
||||
"""
|
||||
|
||||
def __init__(self, startpos, endpos, time, child,
|
||||
style='default', **properties):
|
||||
|
||||
super(Move, self).__init__()
|
||||
self.add(child)
|
||||
|
||||
self.startpos = startpos
|
||||
self.endpos = endpos
|
||||
self.time = time
|
||||
|
||||
self.st = 0.0
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
st = self.st
|
||||
|
||||
x0, y0 = self.startpos
|
||||
x1, y1 = self.endpos
|
||||
|
||||
if self.time > 0:
|
||||
tfrac = (st / self.time)
|
||||
else:
|
||||
tfrac = 1.0
|
||||
if st > self.period:
|
||||
st = self.period
|
||||
else:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
st /= self.period
|
||||
|
||||
if tfrac > 1.0:
|
||||
tfrac = 1.0
|
||||
if self.bounce:
|
||||
st = st * 2
|
||||
if st > 1.0:
|
||||
st = 2.0 - st
|
||||
|
||||
xo = x0 * (1.0 - tfrac) + x1 * tfrac
|
||||
yo = y0 * (1.0 - tfrac) + y1 * tfrac
|
||||
res = self.function(st)
|
||||
|
||||
if isinstance(x1, int):
|
||||
xo = int(xo)
|
||||
if len(res) == 2:
|
||||
self.style.xpos, self.style.ypos = res
|
||||
else:
|
||||
self.style.xpos, self.style.ypos, self.style.xanchor, self.style.yanchor = res
|
||||
|
||||
if isinstance(y1, int):
|
||||
yo = int(yo)
|
||||
child = render(self.child, width, height, st)
|
||||
cw, ch = child.get_size()
|
||||
|
||||
self.style.xpos = xo
|
||||
self.style.ypos = yo
|
||||
rv = renpy.display.render.Render(cw, ch)
|
||||
rv.blit(child, (0, 0))
|
||||
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
self.st = st
|
||||
rv = self.child.render(width, height, st)
|
||||
|
||||
self.sizes = [ rv.get_size() ]
|
||||
self.sizes = [ child.get_size() ]
|
||||
self.offsets = [ (0, 0) ]
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Interpolate(object):
|
||||
|
||||
anchors = {
|
||||
'top' : 0.0,
|
||||
'center' : 0.5,
|
||||
'bottom' : 1.0,
|
||||
'left' : 0.0,
|
||||
'right' : 1.0,
|
||||
}
|
||||
|
||||
def __init__(self, start, end):
|
||||
|
||||
if len(start) != len(end):
|
||||
raise Exception("The start and end must have the same number of arguments.")
|
||||
|
||||
self.start = [ self.anchors.get(i, i) for i in start ]
|
||||
self.end = [ self.anchors.get(i, i) for i in end ]
|
||||
|
||||
def __call__(self, t):
|
||||
|
||||
def interp(a, b):
|
||||
|
||||
rv = (1.0 - t) * a + t * b
|
||||
|
||||
if isinstance(a, int) and isinstance(b, int):
|
||||
return int(rv)
|
||||
else:
|
||||
return rv
|
||||
|
||||
return [ interp(a, b) for a, b in zip(self.start, self.end) ]
|
||||
|
||||
|
||||
def Pan(startpos, endpos, time, child=None, repeat=False, bounce=False,
|
||||
style='default', **properties):
|
||||
"""
|
||||
This is used to pan over a child displayable, which is almost
|
||||
always an image. It works by interpolating the placement of the
|
||||
upper-left corner of the screen, over time. It's only really
|
||||
suitable for use with images that are larger than the screen,
|
||||
and we don't do any cropping on the image.
|
||||
|
||||
@param startpos: The initial coordinates of the upper-left
|
||||
corner of the screen, relative to the image.
|
||||
|
||||
@param endpos: The coordinates of the upper-left corner of the
|
||||
screen, relative to the image, after time has elapsed.
|
||||
|
||||
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.)
|
||||
@param time: The time it takes to pan from startpos to endpos.
|
||||
|
||||
@param child: The child displayable.
|
||||
|
||||
@param repeat: True if we should repeat this forever.
|
||||
|
||||
@param bounce: True if we should bounce from the start to the end
|
||||
to the start.
|
||||
|
||||
This can be used as a transition. See Motion for details.
|
||||
"""
|
||||
|
||||
def __init__(self, maxwidth, maxheight, child,
|
||||
style='default', **properties):
|
||||
x0, y0 = startpos
|
||||
x1, y1 = endpos
|
||||
|
||||
return Motion(Interpolate((-x0, -y0), (-x1, -y1)),
|
||||
time,
|
||||
child,
|
||||
repeat=repeat,
|
||||
bounce=bounce,
|
||||
style=style,
|
||||
**properties)
|
||||
|
||||
super(Sizer, self).__init__()
|
||||
self.add(child)
|
||||
def Move(startpos, endpos, time, child=None, repeat=False, bounce=False,
|
||||
style='default', **properties):
|
||||
"""
|
||||
This is used to pan over a child displayable relative to
|
||||
the containing area. It works by interpolating the placement of the
|
||||
the child, over time.
|
||||
|
||||
self.maxwidth = maxwidth
|
||||
self.maxheight = maxheight
|
||||
@param startpos: The initial coordinates of the child
|
||||
relative to the containing area.
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
@param endpos: The coordinates of the child at the end of the
|
||||
move.
|
||||
|
||||
@param time: The time it takes to move from startpos to endpos.
|
||||
|
||||
def render(self, width, height, st):
|
||||
@param child: The child displayable.
|
||||
|
||||
if self.maxwidth:
|
||||
width = min(width, self.maxwidth)
|
||||
@param repeat: True if we should repeat this forever.
|
||||
|
||||
@param bounce: True if we should bounce from the start to the end
|
||||
to the start.
|
||||
|
||||
This can be used as a transition. See Motion for details.
|
||||
"""
|
||||
|
||||
return Motion(Interpolate(startpos, endpos),
|
||||
time,
|
||||
child,
|
||||
repeat=repeat,
|
||||
bounce=bounce,
|
||||
style=style,
|
||||
**properties)
|
||||
|
||||
if self.maxheight:
|
||||
height = min(height, self.maxheight)
|
||||
|
||||
return super(Sizer, self).render(width, height, st)
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# This file mediates access to the _renpy module, which is a C module that
|
||||
# allows us to enhance the feature set of pygame in a renpy specific way.
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import _renpy
|
||||
version = _renpy.version()
|
||||
except:
|
||||
# If for any reason we can't import the module, we have a version
|
||||
# number of 0.
|
||||
|
||||
print "The _renpy module was not found. Please read module/README.txt for"
|
||||
print "more information."
|
||||
|
||||
version = 0
|
||||
|
||||
|
||||
def convert_and_call(function, src, dst, *args):
|
||||
"""
|
||||
This calls the function with the source and destination
|
||||
surface. The surfaces must have the same alpha.
|
||||
|
||||
If the surfaces are not 24 or 32 bits per pixel, or don't have the
|
||||
same format, they are converted and then converted back.
|
||||
"""
|
||||
|
||||
if dst.get_flags() & SRCALPHA != src.get_flags() & SRCALPHA:
|
||||
raise Exception("Surface alphas do not match.")
|
||||
|
||||
dstsize = dst.get_bitsize()
|
||||
|
||||
if dst.get_bitsize() in (24, 32):
|
||||
target = dst
|
||||
else:
|
||||
if dst.get_flags() & SRCALPHA:
|
||||
target = pygame.Surface(dst.get_size(), SRCALPHA, 32)
|
||||
else:
|
||||
target = pygame.Surface(dst.get_size(), 0, 24)
|
||||
|
||||
if src.get_bitsize() == target.get_bitsize():
|
||||
source = src
|
||||
else:
|
||||
source = src.convert(target)
|
||||
|
||||
function(source, target, *args)
|
||||
|
||||
if target is not dst:
|
||||
dst.blit(target, (0, 0))
|
||||
|
||||
|
||||
if version >= 4008002:
|
||||
|
||||
can_pixellate = True
|
||||
|
||||
def pixellate(src, dst, avgwidth, avgheight, outwidth, outheight):
|
||||
"""
|
||||
This pixellates the source surface. First, every pixel in the
|
||||
source surface is projected onto a virtual surface, such that
|
||||
the average value of every avgwidth x avgheight pixels becomes
|
||||
one virtual pixel. It then gets projected back onto the
|
||||
destination surface at a ratio of one virtual pixel to every
|
||||
outwidth x outheight destination pixels.
|
||||
|
||||
If either src or dst is not a 24 or 32 bit surface, they are
|
||||
converted... but that may be a significant performance hit.
|
||||
|
||||
The two surfaces must either have the same alpha or no alpha.
|
||||
"""
|
||||
|
||||
convert_and_call(_renpy.pixellate,
|
||||
src, dst,
|
||||
avgwidth, avgheight,
|
||||
outwidth, outheight)
|
||||
|
||||
|
||||
def scale(s, size):
|
||||
"""
|
||||
Scales down the supplied pygame surface by the given X and Y
|
||||
factors.
|
||||
|
||||
Always works, but may not be high quality.
|
||||
"""
|
||||
|
||||
width, height = s.get_size()
|
||||
|
||||
dx, dy = size
|
||||
|
||||
if s.get_flags() & SRCALPHA:
|
||||
d = pygame.Surface((dx, dy), SRCALPHA)
|
||||
else:
|
||||
d = pygame.Surface((dx, dy), 0)
|
||||
|
||||
pixellate(s, d, width / dx, height / dy, 1, 1)
|
||||
|
||||
return d
|
||||
|
||||
else:
|
||||
|
||||
can_pixellate = False
|
||||
|
||||
def scale(s, size):
|
||||
|
||||
return pygame.transform.scale(s, size)
|
||||
|
||||
|
||||
|
||||
|
||||
def slow_endian_order(shifts, masks, r, g, b, a):
|
||||
|
||||
has_alpha = masks[3]
|
||||
|
||||
if not has_alpha:
|
||||
|
||||
l = zip(shifts, (r, g, b))
|
||||
l.sort()
|
||||
|
||||
if sys.byteorder == 'big':
|
||||
l.reverse()
|
||||
|
||||
return [ j for i, j in l] + [ a ]
|
||||
|
||||
|
||||
l = zip(shifts, (r, g, b, a))
|
||||
l.sort()
|
||||
|
||||
if sys.byteorder == 'big':
|
||||
l.reverse()
|
||||
|
||||
return [ j for i, j in l]
|
||||
|
||||
|
||||
endian_order_cache = { }
|
||||
|
||||
|
||||
def endian_order(src, r, g, b, a):
|
||||
"""
|
||||
Returns the four arguments, in endian-order.
|
||||
"""
|
||||
|
||||
shifts = src.get_shifts()
|
||||
|
||||
try:
|
||||
func = endian_order_cache[shifts]
|
||||
|
||||
except KeyError:
|
||||
masks = src.get_masks()
|
||||
order = slow_endian_order(shifts, masks, 'r', 'g', 'b', 'a')
|
||||
func = eval( "lambda r, g, b, a : (" + ", ".join(order) + ")")
|
||||
endian_order_cache[shifts] = func
|
||||
|
||||
return func(r, g, b, a)
|
||||
|
||||
|
||||
|
||||
if version >= 4008005:
|
||||
|
||||
can_map = True
|
||||
|
||||
def map(src, dst, rmap, gmap, bmap, amap):
|
||||
"""
|
||||
This maps the colors between two surfaces. The various map
|
||||
parameters must be 256 character long strings, with the value
|
||||
of a character at a given offset being what a particular pixel
|
||||
component value is mapped to.
|
||||
"""
|
||||
|
||||
convert_and_call(_renpy.map,
|
||||
src, dst,
|
||||
*endian_order(dst, rmap, gmap, bmap, amap))
|
||||
|
||||
|
||||
else:
|
||||
|
||||
can_map = False
|
||||
|
||||
|
||||
if version >= 4008007:
|
||||
|
||||
can_munge = True
|
||||
|
||||
def alpha_munge(src, dst, amap):
|
||||
"""
|
||||
This samples the red channel from src, maps it through amap, and
|
||||
place it into the alpha channel of amap.
|
||||
"""
|
||||
|
||||
if src.get_size() != dst.get_size():
|
||||
return
|
||||
|
||||
red = list(endian_order(src, 1, 2, 3, 4)).index(1)
|
||||
alpha = list(endian_order(dst, 1, 2, 3, 4)).index(4)
|
||||
|
||||
_renpy.alpha_munge(src, dst, red, alpha, amap)
|
||||
|
||||
else:
|
||||
|
||||
can_munge = True
|
||||
|
||||
def alpha_munge(src, dst, amap):
|
||||
return
|
||||
@@ -0,0 +1,55 @@
|
||||
# Pre-splash code. The goal of this code is to try to get a pre-splash
|
||||
# screen up as soon as possible, to let the user know something is
|
||||
# going on.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pygame.display
|
||||
|
||||
# The directory from which presplash images are loaded.
|
||||
gamedir = None
|
||||
|
||||
# Are we actually using presplash?
|
||||
active = False
|
||||
|
||||
# Called at the start of the presplash process. This determines if
|
||||
# we're even doing presplash, and if so what will be shown to the
|
||||
# user.
|
||||
#
|
||||
# As this is called before any of the renpy modules are even loaded,
|
||||
# we need to be careful.
|
||||
def start(_gamedir):
|
||||
|
||||
global gamedir
|
||||
global active
|
||||
|
||||
gamedir = _gamedir
|
||||
|
||||
if not os.path.exists(gamedir + "/presplash.png"):
|
||||
return
|
||||
|
||||
active = True
|
||||
|
||||
os.environ['SDL_VIDEO_CENTERED'] = "1"
|
||||
pygame.display.init()
|
||||
|
||||
img = pygame.image.load(gamedir + "/presplash.png")
|
||||
screen = pygame.display.set_mode(img.get_size())
|
||||
screen.blit(img, (0, 0))
|
||||
pygame.display.update()
|
||||
|
||||
|
||||
# Called just before we initialize the display for real, to
|
||||
# hide the splash, and terminate window centering.
|
||||
def end():
|
||||
|
||||
global active
|
||||
|
||||
if not active:
|
||||
return
|
||||
|
||||
active = False
|
||||
|
||||
del os.environ['SDL_VIDEO_CENTERED']
|
||||
pygame.display.quit()
|
||||
|
||||
@@ -0,0 +1,611 @@
|
||||
# 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.style.xmaximum is not None:
|
||||
width = min(widget.style.xmaximum, width)
|
||||
|
||||
if widget.style.ymaximum is not None:
|
||||
height = min(widget.style.ymaximum, height)
|
||||
|
||||
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))
|
||||
@@ -1,186 +0,0 @@
|
||||
# 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.set_alpha(0)
|
||||
# dest.fill(self.color, [ x, y, self.width, self.height ])
|
||||
|
||||
# We only cache a single solid... but that should be enough to handle
|
||||
# some important cases, like button and window backgrounds.
|
||||
class SolidCache(object):
|
||||
|
||||
def __init__(self):
|
||||
self.size = None
|
||||
self.color = None
|
||||
self.cached = None
|
||||
|
||||
def create(self, size, color):
|
||||
if size == self.size and color == self.color:
|
||||
return self.cached
|
||||
|
||||
self.size = size
|
||||
self.color = color
|
||||
|
||||
surf = pygame.Surface(size, 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
surf.fill(color)
|
||||
|
||||
self.cached = surf
|
||||
return surf
|
||||
|
||||
|
||||
solid_cache = SolidCache()
|
||||
|
||||
class Surface(object):
|
||||
"""
|
||||
This is our own surface object, which is a node in a tree in which
|
||||
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 = solid_cache.create((self.width, self.height), color)
|
||||
|
||||
# surf = pygame.Surface((self.width, self.height), 0,
|
||||
# renpy.game.interface.display.sample_surface)
|
||||
|
||||
# surf.fill(color)
|
||||
|
||||
# surf = FilledSurface(self.width, self.height, color)
|
||||
|
||||
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
|
||||
+464
-144
@@ -1,47 +1,160 @@
|
||||
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):
|
||||
def get_font(fn, size, bold=False, italics=False, underline=False):
|
||||
from renpy.loader import transfn
|
||||
|
||||
if (fn, size) in _font_cache:
|
||||
return _font_cache[(fn, size)]
|
||||
if (fn, size, bold, italics, underline) in _font_cache:
|
||||
return _font_cache[(fn, size, bold, italics, underline)]
|
||||
|
||||
try:
|
||||
rv = pygame.font.Font(transfn(fn), size)
|
||||
rv.set_bold(bold)
|
||||
rv.set_italic(italics)
|
||||
except:
|
||||
rv = pygame.font.SysFont(fn, size)
|
||||
rv = pygame.font.SysFont(fn, size, bold, italics)
|
||||
|
||||
_font_cache[(fn, size)] = rv
|
||||
rv.set_underline(underline)
|
||||
|
||||
_font_cache[(fn, size, bold, italics, underline)] = 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
|
||||
|
||||
def text_tokenizer(s, style):
|
||||
"""
|
||||
This functions is used to tokenize text. It's called when laying
|
||||
out a Text widget, and is given the string that is the text of the
|
||||
widget, and the style associated with the widget.
|
||||
|
||||
It's expected to yield some number of pairs. In each pair, the
|
||||
first element is the kind of token found, and the second element
|
||||
is the text corresponding to that token. The following token
|
||||
types are defined:
|
||||
|
||||
"newline" -- A newline, which when encountered starts a new line.
|
||||
|
||||
"word" -- A word of text. A line will never be broken inside of
|
||||
a word.
|
||||
|
||||
"space" -- A space. Spaces are always placed on the current line,
|
||||
and will never be placed as the start of a line.
|
||||
|
||||
"tag" -- A text tag. If encountered, the second element should be
|
||||
the name of the tag, without any enclosing braces.
|
||||
"""
|
||||
|
||||
regexp = r"""(?x)
|
||||
(?P<space>\ )
|
||||
| \{(?P<tag>[^{}]+)\}
|
||||
| (?P<untag>\{\{)
|
||||
| (?P<newline>\n)
|
||||
| (?P<word>[^ \n\{]+)
|
||||
"""
|
||||
|
||||
for m in re.finditer(regexp, s):
|
||||
|
||||
if m.group('space'):
|
||||
yield 'space', m.group('space')
|
||||
elif m.group('word'):
|
||||
yield 'word', m.group('word')
|
||||
elif m.group('tag'):
|
||||
yield 'tag', m.group('tag')
|
||||
elif m.group('untag'):
|
||||
yield 'word', '{'
|
||||
elif m.group('newline'):
|
||||
yield 'newline', m.group('newline')
|
||||
|
||||
|
||||
class Text(renpy.display.core.Displayable):
|
||||
"""
|
||||
A Displayable that can display text on the screen.
|
||||
A displayable that can format and display text on the screen.
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
@ivar style: The style that is used to display the text.
|
||||
@ivar text: The text that is being displayed.
|
||||
|
||||
The following aren't serialized, but are reconstructed the first
|
||||
time this is redrawn:
|
||||
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_linewidths',
|
||||
'laidout_width', 'laidout_height', 'width' ]
|
||||
|
||||
@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 after_setstate(self):
|
||||
self.laidout = None
|
||||
|
||||
def __init__(self, text, slow=False, style='default', **properties):
|
||||
"""
|
||||
@@ -54,11 +167,14 @@ 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
|
||||
|
||||
@@ -82,131 +198,10 @@ class Text(renpy.display.core.Displayable):
|
||||
"""
|
||||
This is called after this widget has been updated by
|
||||
set_text or set_style.
|
||||
"""
|
||||
"""
|
||||
|
||||
try:
|
||||
del self.laidout
|
||||
del self.width
|
||||
del self.height
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def layout(self, width):
|
||||
"""
|
||||
Called to split the text into a string with newline characters
|
||||
at line endings where wrapping has occured.
|
||||
"""
|
||||
|
||||
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
lines = [ ]
|
||||
pars = self.text.split('\n')
|
||||
|
||||
lh = 0
|
||||
|
||||
maxwidth = 0
|
||||
|
||||
# The indent of the current line.
|
||||
indent = self.style.first_indent
|
||||
|
||||
|
||||
for p in pars:
|
||||
words = p.split(' ')
|
||||
|
||||
line = ""
|
||||
|
||||
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
|
||||
|
||||
|
||||
lw, lh = font.size(line + " " + w)
|
||||
|
||||
if lw + indent < width:
|
||||
line += " " + w
|
||||
maxwidth = max(maxwidth, lw)
|
||||
else:
|
||||
lines.append(line)
|
||||
line = w
|
||||
indent = self.style.rest_indent
|
||||
|
||||
lines.append(line)
|
||||
|
||||
|
||||
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(self, width, height, st):
|
||||
|
||||
if self.style.drop_shadow:
|
||||
dsxo, dsyo = self.style.drop_shadow
|
||||
else:
|
||||
dsxo, dsyo = 0, 0
|
||||
|
||||
if not hasattr(self, "laidout"):
|
||||
self.layout(width - dsxo)
|
||||
|
||||
surf = renpy.display.surface.Surface(self.width + dsxo, self.height + dsyo)
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
laidout = self.laidout
|
||||
|
||||
# Annoying text hack.
|
||||
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
|
||||
chars = int(st * renpy.config.annoying_text_cps)
|
||||
if chars < len(laidout):
|
||||
laidout = laidout[:chars]
|
||||
renpy.game.interface.redraw(0)
|
||||
else:
|
||||
self.slow = False
|
||||
else:
|
||||
self.slow = False
|
||||
|
||||
|
||||
lines = laidout.split('\n')
|
||||
|
||||
first_indent = self.style.first_indent
|
||||
rest_indent = self.style.rest_indent
|
||||
|
||||
|
||||
# Common rendering code.
|
||||
def render_lines(x, y, color):
|
||||
|
||||
y += self.style.text_y_fudge
|
||||
|
||||
indent = first_indent
|
||||
|
||||
for l in lines:
|
||||
ls = font.render(l, self.style.antialias, color)
|
||||
|
||||
lw, lh = ls.get_size()
|
||||
lw += indent
|
||||
|
||||
xo = int((self.width - lw) * self.style.textalign)
|
||||
surf.blit(ls, (x + xo + indent, y + font.get_descent()))
|
||||
|
||||
y += font.get_linesize() + self.style.line_height_fudge
|
||||
indent = rest_indent
|
||||
|
||||
|
||||
|
||||
|
||||
# Render drop-shadow.
|
||||
if self.style.drop_shadow:
|
||||
render_lines(dsxo, dsyo, self.style.drop_shadow_color)
|
||||
|
||||
# Render foreground.
|
||||
render_lines(0, 0, self.style.color)
|
||||
|
||||
return surf
|
||||
self.laidout = None
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
@@ -221,6 +216,329 @@ class Text(renpy.display.core.Displayable):
|
||||
self.slow = False
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
lines = [ ]
|
||||
line = [ ]
|
||||
|
||||
# The height of the current line, in pixels, not including
|
||||
# line_spacing.
|
||||
lineheight = 0
|
||||
|
||||
# A list of same.
|
||||
lineheights = [ ]
|
||||
|
||||
# The width of the current line.
|
||||
linewidth = 0
|
||||
|
||||
# A list of the same.
|
||||
linewidths = [ ]
|
||||
|
||||
# The maximum linewidth.
|
||||
maxwidth = 0
|
||||
|
||||
# The current text.
|
||||
cur = ""
|
||||
|
||||
# The width, in pixels, of cur.
|
||||
curwidth = 0
|
||||
|
||||
# The remaining width of the line, not including the text in
|
||||
# cur.
|
||||
remwidth = width - indent()
|
||||
|
||||
if not self.text:
|
||||
text = " "
|
||||
else:
|
||||
text = self.text
|
||||
|
||||
# for i in re.split(r'( |\{[^{}]+\}|\{\{|\n)', text):
|
||||
for kind, i in renpy.config.text_tokenizer(text, self.style):
|
||||
|
||||
# Newline.
|
||||
if kind == "newline":
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
maxwidth = max(maxwidth, linewidth + curwidth)
|
||||
cur = ""
|
||||
|
||||
lines.append(line)
|
||||
lineheights.append(lineheight)
|
||||
linewidths.append(curwidth)
|
||||
|
||||
line = [ ]
|
||||
linewidth = 0
|
||||
curwidth, lineheight = tsl[-1].sizes(" ")
|
||||
remwidth = width - indent()
|
||||
|
||||
continue
|
||||
|
||||
elif kind == "tag":
|
||||
|
||||
# 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)
|
||||
|
||||
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))
|
||||
|
||||
else:
|
||||
raise Exception("Text tag %s was not recognized. Case and spacing matter here.")
|
||||
|
||||
continue
|
||||
|
||||
elif kind == "space":
|
||||
# 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
|
||||
|
||||
elif kind == "word":
|
||||
|
||||
# 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?
|
||||
oldcurwidth = curwidth
|
||||
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)
|
||||
|
||||
linewidths.append(width + oldcurwidth - remwidth)
|
||||
|
||||
cur = i
|
||||
curwidth, lineheight = tsl[-1].sizes(cur)
|
||||
remwidth = width - indent()
|
||||
linewidth = 0
|
||||
else:
|
||||
cur = cur + i
|
||||
lineheight = max(lh, lineheight)
|
||||
|
||||
else:
|
||||
raise Exception("Unknown text token kind %s." % kind)
|
||||
|
||||
# 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)
|
||||
linewidths.append(curwidth)
|
||||
|
||||
self.laidout = lines
|
||||
self.laidout_lineheights = lineheights
|
||||
self.laidout_linewidths = linewidths
|
||||
self.laidout_width = max(max(linewidths), self.style.minwidth)
|
||||
self.laidout_height = sum(lineheights) + len(lineheights) * self.style.line_spacing
|
||||
|
||||
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, line_width in zip(self.laidout, self.laidout_lineheights, self.laidout_linewidths):
|
||||
x = xo + indent + self.style.textalign * (self.laidout_width - line_width)
|
||||
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.game.preferences.text_cps:
|
||||
length = int(st * renpy.game.preferences.text_cps)
|
||||
else:
|
||||
length = sys.maxint
|
||||
self.slow = False
|
||||
|
||||
if self.style.drop_shadow:
|
||||
dsxo, dsyo = self.style.drop_shadow
|
||||
|
||||
absxo = abs(dsxo)
|
||||
absyo = abs(dsyo)
|
||||
|
||||
width -= absxo
|
||||
|
||||
if dsxo < 0:
|
||||
xo = -dsxo
|
||||
dsxo = 0
|
||||
else:
|
||||
xo = 0
|
||||
|
||||
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)
|
||||
|
||||
if self.style.drop_shadow:
|
||||
self.render_pass(rv, dsxo, dsyo, self.style.drop_shadow_color, False, length)
|
||||
|
||||
self.slow = not self.render_pass(rv, xo, yo, self.style.color, True, length)
|
||||
|
||||
if self.slow:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class ParameterizedText(object):
|
||||
"""
|
||||
This can be used as an image. When used, this image is expected to
|
||||
@@ -242,3 +560,5 @@ class ParameterizedText(object):
|
||||
|
||||
return Text(string, style=self.style, **self.properties)
|
||||
|
||||
def predict(self, callback):
|
||||
return
|
||||
|
||||
+857
-84
@@ -1,125 +1,898 @@
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
# We used time too many other times. :-(
|
||||
from time import time as now
|
||||
|
||||
# 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):
|
||||
"""
|
||||
This is the base class of all transitions. It takes care of event
|
||||
dispatching (primarily by passing all events off to a SayBehavior.)
|
||||
This is the base class of most transitions. It takes care of event
|
||||
dispatching.
|
||||
"""
|
||||
|
||||
def __init__(self, delay):
|
||||
super(Transition, self).__init__()
|
||||
self.delay = delay
|
||||
self.offsets = [ ]
|
||||
self.events = True
|
||||
|
||||
def event(self, ev, x, y):
|
||||
event_list = self.new_scene_list[:]
|
||||
event_list.reverse()
|
||||
if self.events:
|
||||
return self.new_widget.event(ev, x, y)
|
||||
else:
|
||||
return None
|
||||
|
||||
offsets = self.offsets[:]
|
||||
offsets.reverse()
|
||||
def find_focusable(self, callback, focus_name):
|
||||
self.new_widget.find_focusable(callback, focus_name)
|
||||
|
||||
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):
|
||||
class NoTransition(Transition):
|
||||
"""
|
||||
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.
|
||||
This is a transition that doesn't do anything, and simply displays
|
||||
the new_widget for a specified amount of time. It's almost
|
||||
certainly not interesting by itself, but it may come in quite
|
||||
handy as part of a MultipleTransition.
|
||||
"""
|
||||
|
||||
def __init__(self, out_time, hold_time, in_time,
|
||||
old_scene_list, new_scene_list, color=(0, 0, 0)):
|
||||
def __init__(self, delay, old_widget=None, new_widget=None):
|
||||
super(NoTransition, self).__init__(delay)
|
||||
|
||||
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_scene_list = old_scene_list
|
||||
self.new_scene_list = new_scene_list
|
||||
self.color = color
|
||||
|
||||
# self.frames = 0
|
||||
|
||||
# def __del__(self):
|
||||
# print "Faded using", self.frames, "frames."
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
self.events = True
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
# self.frames += 1
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
|
||||
events = False
|
||||
|
||||
if st < self.out_time:
|
||||
scene_list = self.old_scene_list
|
||||
alpha = int(255 * (st / self.out_time))
|
||||
|
||||
elif st < self.out_time + self.hold_time:
|
||||
scene_list = None
|
||||
alpha = 255
|
||||
|
||||
else:
|
||||
scene_list = self.new_scene_list
|
||||
alpha = 255 - int(255 * ((st - self.out_time - self.hold_time) / self.in_time))
|
||||
events = True
|
||||
|
||||
if scene_list:
|
||||
surf, offsets = renpy.display.core.render_scene_list(scene_list,
|
||||
width,
|
||||
height)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
if events:
|
||||
self.offsets = offsets
|
||||
|
||||
# Just to be sure.
|
||||
if alpha < 0:
|
||||
alpha = 0
|
||||
|
||||
if alpha > 255:
|
||||
alpha = 255
|
||||
|
||||
rv.fill(self.color[:3] + (alpha,))
|
||||
|
||||
if st < self.in_time + self.hold_time + self.out_time:
|
||||
renpy.game.interface.redraw(0)
|
||||
rv.blit(renpy.display.render.render(self.new_widget,
|
||||
width,
|
||||
height,
|
||||
st), (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
class Dissolve(Transition):
|
||||
|
||||
def __init__(self, time, old_scene_list, new_scene_list):
|
||||
class MultipleTransition(Transition):
|
||||
"""
|
||||
This is a transition that can sequence between multiple screens,
|
||||
showing a different transition between each.
|
||||
|
||||
This must be supplied with a tuple containing an odd number of
|
||||
components. The first, third, and so on components are interpreted
|
||||
as screens that can be shown to the user, while the even components
|
||||
are transitions between those screens.
|
||||
|
||||
A screen can be any displayable, but normally an Image or Solid is
|
||||
most appropriate. An screen can also be False to represent the screen
|
||||
we are transitioning from, or True to represent the screen we are
|
||||
transitioning to. Almost always, the first argument will be False
|
||||
and the last will be True.
|
||||
"""
|
||||
|
||||
def __init__(self, args, old_widget=None, new_widget=None):
|
||||
|
||||
if len(args) % 2 != 1 or len(args) < 3:
|
||||
raise Exception("MultipleTransition requires an odd number of arguments, and at least 3 arguments.")
|
||||
|
||||
self.transitions = [ ]
|
||||
|
||||
def oldnew(w):
|
||||
if w is False:
|
||||
return old_widget
|
||||
if w is True:
|
||||
return new_widget
|
||||
return w
|
||||
|
||||
for old, trans, new in zip(args[0::2], args[1::2], args[2::2]):
|
||||
old = oldnew(old)
|
||||
new = oldnew(new)
|
||||
|
||||
self.transitions.append(trans(old_widget=old, new_widget=new))
|
||||
|
||||
super(MultipleTransition, self).__init__(sum([i.delay for i in self.transitions]))
|
||||
|
||||
self.event_target = None
|
||||
self.time_offset = 0
|
||||
self.new_widget = self.transitions[-1]
|
||||
self.events = False
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
while True:
|
||||
trans = self.transitions[0]
|
||||
stoff = st - self.time_offset
|
||||
|
||||
if stoff < trans.delay:
|
||||
break
|
||||
|
||||
if len(self.transitions) == 1:
|
||||
break
|
||||
|
||||
self.time_offset += trans.delay
|
||||
self.transitions.pop(0)
|
||||
|
||||
if len(self.transitions) == 1:
|
||||
self.events = True
|
||||
|
||||
self.event_target = trans
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(renpy.display.render.render(trans, width, height, stoff), (0,0))
|
||||
|
||||
if stoff > 0:
|
||||
renpy.display.render.redraw(self, stoff)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def Fade(out_time, hold_time, in_time,
|
||||
old_widget=None, new_widget=None,
|
||||
color=None,
|
||||
widget=None,
|
||||
):
|
||||
|
||||
"""
|
||||
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 fade to. A tuple containing
|
||||
three components, each between 0 or 255. This can also be None.
|
||||
|
||||
@param widget: This is a widget that will be faded to, if color
|
||||
is None. This allows a fade to be to an image rather than just
|
||||
a solid color.
|
||||
|
||||
If both color and widget are None, then the fade is to black.
|
||||
"""
|
||||
|
||||
dissolve = renpy.curry.curry(Dissolve)
|
||||
notrans = renpy.curry.curry(NoTransition)
|
||||
|
||||
if color:
|
||||
widget = renpy.display.image.Solid(color)
|
||||
|
||||
if not widget:
|
||||
widget = renpy.display.image.Solid((0, 0, 0, 255))
|
||||
|
||||
args = [ False, dissolve(out_time), widget ]
|
||||
|
||||
if hold_time:
|
||||
args.extend([ notrans(hold_time), widget, ])
|
||||
|
||||
args.extend([dissolve(in_time), True ])
|
||||
|
||||
return MultipleTransition(args, old_widget=old_widget, new_widget=new_widget)
|
||||
|
||||
|
||||
# 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 Pixellate(Transition):
|
||||
"""
|
||||
This pixellates out the old scene, and then pixellates in the new
|
||||
scene, taking the given amount of time and the given number of pixellate
|
||||
steps in each direction.
|
||||
"""
|
||||
|
||||
def __init__(self, time, steps, old_widget=None, new_widget=None):
|
||||
|
||||
if not renpy.display.module.can_pixellate:
|
||||
time = 0
|
||||
|
||||
super(Pixellate, self).__init__(time)
|
||||
|
||||
self.time = time
|
||||
self.steps = steps
|
||||
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
|
||||
self.surface = None
|
||||
self.surface_size = None
|
||||
|
||||
self.events = False
|
||||
|
||||
self.quantum = time / ( 2 * steps )
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if st >= self.time:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
step = st // self.quantum + 1
|
||||
visible = self.old_widget
|
||||
|
||||
if step > self.steps:
|
||||
step = (self.steps * 2) - step + 1
|
||||
visible = self.new_widget
|
||||
self.events = True
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rdr = render(visible, width, height, st)
|
||||
|
||||
# No alpha support.
|
||||
surf = rdr.pygame_surface(False)
|
||||
|
||||
if surf.get_size() != self.surface_size:
|
||||
self.surface_size = surf.get_size()
|
||||
self.surface = pygame.Surface(self.surface_size, surf.get_flags(), surf)
|
||||
|
||||
px = 2 ** step
|
||||
|
||||
renpy.display.module.pixellate(surf, self.surface, px, px, px, px)
|
||||
renpy.display.render.mutated_surface(self.surface)
|
||||
|
||||
rv.blit(self.surface, (0, 0))
|
||||
|
||||
if self.events:
|
||||
rv.focuses.extend(rdr.focuses)
|
||||
|
||||
# renpy.display.render.redraw(self, self.quantum - st % self.quantum)
|
||||
|
||||
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 time: The amount of time the dissolve will take.
|
||||
"""
|
||||
|
||||
def __init__(self, time, old_widget=None, new_widget=None):
|
||||
super(Dissolve, self).__init__(time)
|
||||
|
||||
self.time = time
|
||||
self.old_scene_list = old_scene_list
|
||||
self.new_scene_list = new_scene_list
|
||||
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
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rsl = renpy.display.core.render_scene_list
|
||||
if st >= self.time:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
rv, offsets = rsl(self.old_scene_list, width, height)
|
||||
surftree, self.offsets = rsl(self.new_scene_list, width, height)
|
||||
surf = surftree.pygame_surface(False)
|
||||
if st < self.time:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
alpha = min(255, int(255 * st / self.time))
|
||||
|
||||
surf.set_alpha(alpha)
|
||||
rv.blit(surf, (0, 0))
|
||||
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 renpy.config.enable_fast_dissolve and id(top) == self.old_top and id(bottom) == self.old_bottom and hasattr(self.new_widget, 'layers'):
|
||||
# Fast rendering path. Only used for full-screen, top-level, renders.
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def MoveTransition(delay, old_widget=None, new_widget=None):
|
||||
"""
|
||||
This transition attempts to find images that have changed
|
||||
position, and moves them from the old position to the new
|
||||
transition, taking delay seconds to complete the move.
|
||||
|
||||
Images are considered to be the same if they have the same tag, in
|
||||
the same way that the tag is used to determine which image to
|
||||
replace or to hide.
|
||||
|
||||
If you use this transition to slide an image off the side of the
|
||||
screen, remember to hide it when you are done.
|
||||
"""
|
||||
|
||||
def position(d):
|
||||
|
||||
placement = d.get_placement()
|
||||
xpos = placement.xpos
|
||||
ypos = placement.ypos
|
||||
|
||||
if isinstance(xpos, float):
|
||||
xpos = int(renpy.config.screen_width * xpos)
|
||||
|
||||
if isinstance(ypos, float):
|
||||
ypos = int(renpy.config.screen_height * ypos)
|
||||
|
||||
return xpos, ypos, placement.xanchor, placement.yanchor
|
||||
|
||||
|
||||
def merge_slide(old, new):
|
||||
|
||||
|
||||
# If new does not have .layers or .scene_list, then we simply
|
||||
# insert a move from the old position to the new position.
|
||||
|
||||
if not hasattr(new, 'layers') and not hasattr(new, 'scene_list'):
|
||||
return renpy.display.layout.Move(position(old),
|
||||
position(new),
|
||||
delay,
|
||||
new,
|
||||
)
|
||||
|
||||
# If we're in the root widget, merge the child widgets for
|
||||
# each layer.
|
||||
if new.layers:
|
||||
assert old.layers
|
||||
|
||||
rv = renpy.display.layout.Fixed()
|
||||
rv.layers = { }
|
||||
|
||||
for layer in renpy.config.layers:
|
||||
|
||||
f = new.layers[layer]
|
||||
|
||||
if isinstance(f, renpy.display.layout.Fixed) and f.scene_list:
|
||||
f = merge_slide(old.layers[layer], new.layers[layer])
|
||||
|
||||
rv.layers[layer] = f
|
||||
rv.add(f)
|
||||
|
||||
return rv
|
||||
|
||||
# Otherwise, we recompute the scene list for the two widgets, merging
|
||||
# as appropriate.
|
||||
|
||||
tags = { }
|
||||
|
||||
for tag, time, d in old.scene_list:
|
||||
|
||||
if tag is None:
|
||||
continue
|
||||
|
||||
tags[tag] = d
|
||||
|
||||
newsl = [ ]
|
||||
|
||||
for tag, time, d in new.scene_list:
|
||||
|
||||
if tag is None or tag not in tags:
|
||||
newsl.append((tag, time, d))
|
||||
continue
|
||||
|
||||
oldpos = position(tags[tag])
|
||||
newpos = position(d)
|
||||
|
||||
if oldpos == newpos:
|
||||
newsl.append((tag, time, d))
|
||||
continue
|
||||
|
||||
move = renpy.display.layout.Move(position(tags[tag]),
|
||||
position(d),
|
||||
delay,
|
||||
d,
|
||||
)
|
||||
|
||||
newsl.append((tag, now(), move))
|
||||
|
||||
rv = renpy.display.layout.Fixed()
|
||||
rv.append_scene_list(newsl)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
rv = merge_slide(old_widget, new_widget)
|
||||
rv.delay = delay
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class ImageDissolve(Transition):
|
||||
"""
|
||||
This dissolves the old scene into the new scene, using an image
|
||||
to control the dissolve process.
|
||||
|
||||
A list of values is used to control this mapping. This list of
|
||||
values consists 256 fully transparent values, a ramp (of a
|
||||
specified number of steps) from full transparency to full opacity,
|
||||
and 256 fully opaque values. A 256 entry window is slid over this
|
||||
list, and the values found within are used to map the red channel
|
||||
of the image onto the opacity of the new scene.
|
||||
|
||||
Basically, this means that while pixels come in first, black last,
|
||||
and the ramp controls the sharpness of the transition.
|
||||
|
||||
@param image: The image that will be used to control this
|
||||
transition. The image should be the same size as the scene being
|
||||
dissolved.
|
||||
|
||||
@param time: The amount of time the dissolve will take.
|
||||
|
||||
@param ramplen: The number of pixels of ramp to use. This defaults
|
||||
to 8.
|
||||
|
||||
@param reverse: This reverses the ramp and the direction of the window
|
||||
slide. When True, black pixels dissolve in first, and while pixels come
|
||||
in last.
|
||||
"""
|
||||
|
||||
def __init__(self, image, time, ramplen=8, reverse=False,
|
||||
old_widget=None, new_widget=None):
|
||||
|
||||
super(ImageDissolve, 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_ramp = '\x00' * 256
|
||||
|
||||
self.image = renpy.display.im.load_image(image)
|
||||
|
||||
# Precompute the ramp.
|
||||
|
||||
ramp = '\x00' * 256
|
||||
|
||||
for i in range(ramplen):
|
||||
ramp += chr(255 * i / ramplen)
|
||||
|
||||
ramp += '\xff' * 256
|
||||
|
||||
if reverse:
|
||||
ramp = list(ramp)
|
||||
ramp.reverse()
|
||||
ramp = ''.join(ramp)
|
||||
|
||||
self.ramp = ramp
|
||||
self.steps = ramplen + 256
|
||||
|
||||
self.reverse = reverse
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if st >= self.time:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
step = int(self.steps * st / self.time)
|
||||
|
||||
if self.reverse:
|
||||
step = self.steps - step
|
||||
|
||||
ramp = self.ramp[step:step+256]
|
||||
|
||||
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(True)
|
||||
renpy.display.render.mutated_surface(surf)
|
||||
|
||||
rv.focuses.extend(top.focuses)
|
||||
|
||||
if renpy.config.enable_fast_dissolve and id(top) == self.old_top and id(bottom) == self.old_bottom and hasattr(self.new_widget, 'layers'):
|
||||
# Fast rendering path. Only used for full-screen, top-level, renders.
|
||||
|
||||
fast_ramp = [ ]
|
||||
|
||||
for new, old in zip(ramp, self.old_ramp):
|
||||
|
||||
new = ord(new)
|
||||
old = ord(old)
|
||||
|
||||
if new >= 255:
|
||||
fast_ramp.append('\xff')
|
||||
continue
|
||||
|
||||
change = 255 * ( new - old ) / ( 255 - old )
|
||||
fast_ramp.append(chr(int(change)))
|
||||
|
||||
renpy.display.module.alpha_munge(self.image, surf,
|
||||
''.join(fast_ramp))
|
||||
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
else:
|
||||
|
||||
# Complete rendering path.
|
||||
|
||||
rv.blit(bottom, (0, 0), focus=False)
|
||||
|
||||
renpy.display.module.alpha_munge(self.image, surf, ramp)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
self.old_ramp = ramp
|
||||
|
||||
|
||||
self.old_top = id(top)
|
||||
self.old_bottom = id(bottom)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
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)
|
||||
|
||||
+31
-7
@@ -22,6 +22,8 @@ class Context(object):
|
||||
@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):
|
||||
@@ -30,14 +32,17 @@ class Context(object):
|
||||
self.return_stack = [ ]
|
||||
self.rollback = rollback
|
||||
self.runtime = 0
|
||||
|
||||
self.info = renpy.python.RevertableObject()
|
||||
self.seen = False
|
||||
|
||||
oldsl = None
|
||||
if context:
|
||||
oldsl = context.scene_lists
|
||||
self.runtime = context.runtime
|
||||
|
||||
import renpy.display.core as dcore
|
||||
self.scene_lists = dcore.SceneLists(oldsl)
|
||||
vars(self.info).update(vars(context.info))
|
||||
|
||||
self.scene_lists = renpy.display.core.SceneLists(oldsl)
|
||||
|
||||
def goto_label(self, node_name):
|
||||
"""
|
||||
@@ -65,23 +70,35 @@ class Context(object):
|
||||
if self.rollback and renpy.game.log:
|
||||
renpy.game.log.begin()
|
||||
|
||||
self.seen = False
|
||||
|
||||
try:
|
||||
node = node.execute()
|
||||
except renpy.game.JumpException, e:
|
||||
node = renpy.game.script.lookup(e.args[0])
|
||||
|
||||
renpy.game.seen_ever[self.current] = True
|
||||
renpy.game.seen_session[self.current] = True
|
||||
|
||||
if self.seen:
|
||||
renpy.game.seen_ever[self.current] = True
|
||||
renpy.game.seen_session[self.current] = True
|
||||
|
||||
if self.rollback and renpy.game.log:
|
||||
renpy.game.log.complete()
|
||||
|
||||
|
||||
def mark_seen(self):
|
||||
"""
|
||||
Marks the current statement as one that has been seen by the user.
|
||||
"""
|
||||
|
||||
self.seen = True
|
||||
|
||||
def call(self, label, return_site=None):
|
||||
"""
|
||||
Calls the named label.
|
||||
"""
|
||||
|
||||
if not self.current:
|
||||
raise "Context not capable of executing Ren'Py code."
|
||||
|
||||
if return_site is None:
|
||||
return_site is self.current
|
||||
|
||||
@@ -116,6 +133,7 @@ class Context(object):
|
||||
rv.current = self.current
|
||||
rv.scene_lists = self.scene_lists.rollback_copy()
|
||||
rv.runtime = self.runtime
|
||||
rv.info = self.info
|
||||
|
||||
return rv
|
||||
|
||||
@@ -126,6 +144,9 @@ class Context(object):
|
||||
they will be potentially loaded.
|
||||
"""
|
||||
|
||||
if not self.current:
|
||||
return
|
||||
|
||||
nodes = [ renpy.game.script.lookup(self.current) ]
|
||||
|
||||
for i in range(0, renpy.config.predict_statements):
|
||||
@@ -155,6 +176,9 @@ class Context(object):
|
||||
we've finished this statement in the current session.
|
||||
"""
|
||||
|
||||
if not self.current:
|
||||
return False
|
||||
|
||||
if ever:
|
||||
seen = renpy.game.seen_ever
|
||||
else:
|
||||
|
||||
+232
-68
@@ -6,15 +6,16 @@
|
||||
import renpy
|
||||
|
||||
# Many of these shouldn't be used directly.
|
||||
from renpy.display.layout import *
|
||||
from renpy.display.text import *
|
||||
from renpy.display.behavior import *
|
||||
from renpy.display.image import *
|
||||
# from renpy.display.layout import *
|
||||
from renpy.display.text import ParameterizedText
|
||||
from renpy.display.behavior import Keymap
|
||||
# from renpy.display.image import *
|
||||
|
||||
from renpy.curry import curry
|
||||
from renpy.music import music_start, music_stop
|
||||
from renpy.sound import play
|
||||
from renpy.loadsave import *
|
||||
# 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
|
||||
|
||||
@@ -32,8 +33,8 @@ def checkpoint():
|
||||
|
||||
renpy.game.log.checkpoint()
|
||||
|
||||
def interact(*widgets, **kwargs):
|
||||
return renpy.game.interface.interact(transient=widgets, **kwargs)
|
||||
# def interact(**kwargs):
|
||||
# return renpy.game.interface.interact(**kwargs)
|
||||
|
||||
def scene_lists(index=-1):
|
||||
"""
|
||||
@@ -43,57 +44,92 @@ def scene_lists(index=-1):
|
||||
|
||||
return renpy.game.context(index).scene_lists
|
||||
|
||||
def scene_list_add(listname, displayable, key=None):
|
||||
def image(name, img):
|
||||
"""
|
||||
Adds the displayable to the named scene list, with the
|
||||
given key if one is provided.
|
||||
This is used to execute the image statment. It takes as arguments
|
||||
an image name and an image object, and associates the image name
|
||||
with the image object.
|
||||
|
||||
@param listname: One of 'master' or 'transient'.
|
||||
Like the image statment, this function should only be executed
|
||||
in init blocks.
|
||||
|
||||
@param name: The image name, a tuple of strings.
|
||||
|
||||
@param img: The displayable that is associated with that name. If this
|
||||
is a string or tuple, it is interpreted as an argument to Image.
|
||||
"""
|
||||
|
||||
if listname not in ('master', 'transient'):
|
||||
raise Exception("Scene list '%s' doesn't exist." % listname)
|
||||
if not renpy.game.init_phase:
|
||||
raise Exception("Images may only be declared inside init blocks.")
|
||||
|
||||
scene_lists().add(listname, displayable, key)
|
||||
img = renpy.display.im.image(img, loose=True)
|
||||
|
||||
def show(at_disp, with_disp=None, key=None):
|
||||
images[name] = img
|
||||
|
||||
|
||||
def show(name, *at_list):
|
||||
"""
|
||||
Shows the displayable, as if it was added to a scene list with the show command.
|
||||
This is used to execute the show statement, adding the named image
|
||||
to the screen as part of the master layer.
|
||||
|
||||
@param at_disp: The displayable that is added to the screen as if
|
||||
it was the result of the at clause in the show statement.
|
||||
@param name: The name of the image to add to the screen. This is a tuple
|
||||
of strings, one string for each component of the image name.
|
||||
|
||||
@param with_displ: The displayable that is added to the screen as
|
||||
if it was the result of the with clause in the show statement. If
|
||||
None, then this is taken from the at command.
|
||||
|
||||
@param key: The key that is used to remove this displayable from
|
||||
the scene list, or None if there is no key.
|
||||
"""
|
||||
|
||||
if not with_disp:
|
||||
with_disp = at_disp
|
||||
|
||||
scene_list_add('master', at_disp, key)
|
||||
scene_list_add('transient', at_disp, key)
|
||||
|
||||
def hide(key):
|
||||
"""
|
||||
Removes items named with the given key from the scene lists.
|
||||
@param at_list: The at list, a list of functions that are applied
|
||||
to the image when shown. The members of the at list need to
|
||||
be pickleable if sticky_positions is True.
|
||||
"""
|
||||
|
||||
sls = scene_lists()
|
||||
key = name[0]
|
||||
|
||||
if renpy.config.sticky_positions:
|
||||
if not at_list and key in sls.sticky_positions:
|
||||
at_list = sls.sticky_positions[key]
|
||||
|
||||
sls.sticky_positions[key] = at_list
|
||||
|
||||
img = renpy.display.image.ImageReference(name)
|
||||
for i in at_list:
|
||||
img = i(img)
|
||||
|
||||
# Update the list of images we have ever seen.
|
||||
renpy.game.persistent._seen_images[tuple(name)] = True
|
||||
|
||||
sls.add('master', img, key)
|
||||
|
||||
|
||||
def hide(name):
|
||||
"""
|
||||
This finds items in the master layer that have the same name
|
||||
as the first component of the given name, and removes them
|
||||
from the master layer. This is used to execute the hide
|
||||
statement.
|
||||
|
||||
@param name: The name of an image. A tuple of strings, but only
|
||||
the first component of this tuple is ever accessed.
|
||||
"""
|
||||
|
||||
sls = scene_lists()
|
||||
key = name[0]
|
||||
sls.remove('master', key)
|
||||
sls.remove('transient', key)
|
||||
|
||||
if key in sls.sticky_positions:
|
||||
del sls.sticky_positions[key]
|
||||
|
||||
|
||||
|
||||
def scene():
|
||||
"""
|
||||
This clears the scene lists, as if the scene statement executed.
|
||||
This clears out the master layer. This is used in the execution of
|
||||
the scene statment, but only to clear out the layer. If you want
|
||||
to then add something new, call renpy.show after this.
|
||||
"""
|
||||
|
||||
sls = scene_lists()
|
||||
sls.clear('master')
|
||||
sls.clear('transient')
|
||||
sls.sticky_positions.clear()
|
||||
|
||||
|
||||
def watch(expression, style='default', **properties):
|
||||
"""
|
||||
@@ -106,12 +142,12 @@ def watch(expression, style='default', **properties):
|
||||
"""
|
||||
|
||||
def overlay_func():
|
||||
return [ renpy.display.text.Text(renpy.python.py_eval(expression),
|
||||
style=style, **properties) ]
|
||||
renpy.ui.text(renpy.python.py_eval(expression),
|
||||
style=style, **properties)
|
||||
|
||||
renpy.config.overlay_functions.append(overlay_func)
|
||||
|
||||
def input(prompt, default='', length=None):
|
||||
def input(prompt, default='', allow=None, exclude='{}', length=None):
|
||||
"""
|
||||
This pops up a window requesting that the user enter in some text.
|
||||
It returns the entered text.
|
||||
@@ -122,15 +158,23 @@ def input(prompt, default='', 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.
|
||||
"""
|
||||
|
||||
vbox = renpy.display.layout.VBox()
|
||||
win = renpy.display.layout.Window(vbox, style='input_window')
|
||||
renpy.ui.window(style='input_window')
|
||||
renpy.ui.vbox()
|
||||
|
||||
vbox.add(renpy.display.text.Text(prompt, style='input_prompt'))
|
||||
vbox.add(renpy.display.behavior.Input(default, length=length, style='input_text'))
|
||||
renpy.ui.text(prompt, style='input_prompt')
|
||||
renpy.ui.input(default, length=length, style='input_text', allow=allow, exclude=exclude)
|
||||
|
||||
return interact(win)
|
||||
renpy.ui.close()
|
||||
|
||||
return renpy.ui.interact()
|
||||
|
||||
def menu(items, set_expr):
|
||||
"""
|
||||
@@ -186,11 +230,27 @@ def display_menu(items, window_style='menu_window'):
|
||||
renpy.ui.window(style=window_style)
|
||||
renpy.ui.menu(items)
|
||||
|
||||
rv = interact()
|
||||
rv = renpy.ui.interact()
|
||||
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):
|
||||
"""
|
||||
@@ -200,13 +260,13 @@ def say(who, what):
|
||||
"""
|
||||
|
||||
# Interpolate variables.
|
||||
what = what % renpy.game.store
|
||||
what = what % tag_quoting_dict
|
||||
|
||||
if who is None:
|
||||
who = renpy.store.narrator
|
||||
|
||||
if isinstance(who, (str, unicode)):
|
||||
display_say(who, what, what_style='say_dialogue')
|
||||
renpy.store.say(who, what)
|
||||
else:
|
||||
who(what)
|
||||
|
||||
@@ -219,6 +279,7 @@ def display_say(who, what, who_style='say_label',
|
||||
what_suffix='',
|
||||
interact=True,
|
||||
slow=True,
|
||||
image=False,
|
||||
**properties):
|
||||
"""
|
||||
@param who: Who is saying the dialogue, or None if it's not being
|
||||
@@ -231,20 +292,20 @@ def display_say(who, what, who_style='say_label',
|
||||
"""
|
||||
|
||||
# If we're going to do an interaction, then saybehavior needs
|
||||
# to be here, right before ui.text.
|
||||
# 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
|
||||
|
||||
renpy.ui.window(style=window_style)
|
||||
renpy.ui.vbox(padding=10)
|
||||
|
||||
if who is not None:
|
||||
if who is not None and not image:
|
||||
who = who_prefix + who + who_suffix
|
||||
renpy.ui.text(who, style=who_style, **properties)
|
||||
elif who is not None and image:
|
||||
renpy.ui.image(who, style=who_style, **properties)
|
||||
|
||||
renpy.ui.text(what, style=what_style, slow=slow)
|
||||
renpy.ui.close()
|
||||
@@ -288,9 +349,7 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
|
||||
renpy.ui.imagemap(ground, selected, hotspots, unselected=unselected,
|
||||
style=style, **properties)
|
||||
|
||||
renpy.ui.keymousebehavior()
|
||||
|
||||
rv = interact(suppress_overlay=(not overlays))
|
||||
rv = renpy.ui.interact(suppress_overlay=(not overlays))
|
||||
checkpoint()
|
||||
return rv
|
||||
|
||||
@@ -298,7 +357,9 @@ 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.
|
||||
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.
|
||||
|
||||
@param delay: The number of seconds to delay.
|
||||
|
||||
@@ -313,15 +374,45 @@ def pause(delay=None, music=None):
|
||||
"""
|
||||
|
||||
if music is not None:
|
||||
newdelay = renpy.music.music_delay(music)
|
||||
newdelay = renpy.display.audio.music_delay(music)
|
||||
|
||||
if newdelay is not None:
|
||||
delay = newdelay
|
||||
|
||||
sayb = renpy.display.behavior.SayBehavior(delay=delay)
|
||||
scene_list_add('transient', sayb)
|
||||
|
||||
return interact()
|
||||
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
|
||||
|
||||
|
||||
def with(trans):
|
||||
"""
|
||||
@@ -339,7 +430,7 @@ def with(trans):
|
||||
renpy.game.interface.set_transition(trans)
|
||||
return renpy.game.interface.interact(show_mouse=False,
|
||||
trans_pause=True,
|
||||
suppress_overlay=True)
|
||||
suppress_overlay=not renpy.config.overlay_during_wait)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -407,6 +498,15 @@ def jump(label):
|
||||
|
||||
raise renpy.game.JumpException(label)
|
||||
|
||||
def jumpoutofcontext(label):
|
||||
"""
|
||||
Causes control to leave the current context, and then to be
|
||||
transferred in the parent context to the given label.
|
||||
"""
|
||||
|
||||
raise renpy.game.JumpOutException(label)
|
||||
|
||||
|
||||
def screenshot(filename):
|
||||
"""
|
||||
Saves a screenshot in the named filename.
|
||||
@@ -423,17 +523,37 @@ def windows():
|
||||
import sys
|
||||
return hasattr(sys, 'winver')
|
||||
|
||||
def transition(trans):
|
||||
def version():
|
||||
"""
|
||||
Returns a string containing the current version of Ren'Py, prefixed with the
|
||||
string "Ren\'Py ".
|
||||
"""
|
||||
|
||||
return renpy.version
|
||||
|
||||
def module_version():
|
||||
"""
|
||||
Returns a number corresponding to the current version of the Ren'Py module,
|
||||
or 0 if the module wasn't loaded.
|
||||
"""
|
||||
|
||||
return renpy.display.module.version
|
||||
|
||||
def transition(trans, layer=None):
|
||||
"""
|
||||
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)
|
||||
renpy.game.interface.set_transition(trans, layer)
|
||||
|
||||
def clear_game_runtime():
|
||||
"""
|
||||
@@ -458,6 +578,50 @@ def get_game_runtime():
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
return renpy.loader.loadable(filename)
|
||||
|
||||
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
|
||||
|
||||
|
||||
call_in_new_context = renpy.game.call_in_new_context
|
||||
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
|
||||
|
||||
invoke_in_new_context = renpy.game.invoke_in_new_context
|
||||
|
||||
+47
-11
@@ -21,11 +21,6 @@ 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.
|
||||
@@ -82,7 +77,9 @@ class Preferences(object):
|
||||
self.sound = True
|
||||
self.music = True
|
||||
self.skip_unseen = False
|
||||
self.fast_text = False
|
||||
|
||||
self.text_cps = 0
|
||||
|
||||
|
||||
# 2 - All transitions.
|
||||
# 1 - Only non-default transitions.
|
||||
@@ -104,7 +101,7 @@ class RestartException(Exception):
|
||||
This class will be used to convey to the system that the context has
|
||||
been changed, and therefore execution needs to be restarted.
|
||||
"""
|
||||
|
||||
|
||||
class FullRestartException(Exception):
|
||||
"""
|
||||
An exception of this type forces a hard restart, completely
|
||||
@@ -124,6 +121,12 @@ class JumpException(Exception):
|
||||
to the named label.
|
||||
"""
|
||||
|
||||
class JumpOutException(Exception):
|
||||
"""
|
||||
This should be raised with a label as the only argument. This exits
|
||||
the current context, and then raises a JumpException.
|
||||
"""
|
||||
|
||||
def context(index=-1):
|
||||
"""
|
||||
Return the current execution context, or the context at the
|
||||
@@ -132,19 +135,52 @@ def context(index=-1):
|
||||
|
||||
return contexts[index]
|
||||
|
||||
def invoke_in_new_context(callable):
|
||||
"""
|
||||
This pushes the current context, and invokes the given python
|
||||
function in a new context. When that function returns or raises an
|
||||
exception, it removes the new context, and restores the current
|
||||
context.
|
||||
|
||||
Please note that the context so created cannot execute renpy
|
||||
code. So exceptions that change the flow of renpy code (like
|
||||
the one created by renpy.jump) cause this context to terminate,
|
||||
and are handled by the next higher context.
|
||||
|
||||
If you want to execute renpy code from the function, you can call
|
||||
it with renpy.call_in_new_context.
|
||||
|
||||
Use this to begin a second interaction with the user while
|
||||
inside an interaction.
|
||||
"""
|
||||
|
||||
context = renpy.execution.Context(False, contexts[-1])
|
||||
contexts.append(context)
|
||||
|
||||
try:
|
||||
return callable()
|
||||
finally:
|
||||
contexts.pop()
|
||||
|
||||
def call_in_new_context(label):
|
||||
"""
|
||||
This code creates a new context, and starts executing code from
|
||||
that label in the new context. Rollback is disabled in the
|
||||
new context. (Actually, it will just bring you back to the
|
||||
real context.)
|
||||
|
||||
Use this to begin a second interaction with the user while
|
||||
inside an interaction.
|
||||
"""
|
||||
|
||||
context = renpy.execution.Context(False, contexts[-1])
|
||||
contexts.append(context)
|
||||
|
||||
context.goto_label(label)
|
||||
context.run()
|
||||
try:
|
||||
context.goto_label(label)
|
||||
context.run()
|
||||
contexts.pop()
|
||||
except renpy.game.JumpOutException, e:
|
||||
contexts.pop()
|
||||
raise renpy.game.JumpException(e.args[0])
|
||||
|
||||
contexts.pop()
|
||||
interface.redraw(0)
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
import renpy
|
||||
import sys
|
||||
import codecs
|
||||
import os
|
||||
import time
|
||||
|
||||
# Things to check in lint.
|
||||
#
|
||||
# Image files exist, and are of the right case.
|
||||
# Jump/Call targets defined.
|
||||
# Say whos can evaluate.
|
||||
# Call followed by say.
|
||||
# Show/Scene valid.
|
||||
# At valid.
|
||||
# With valid.
|
||||
# Hide maybe valid.
|
||||
# Expressions can compile.
|
||||
|
||||
# Reports a message to the user.
|
||||
def report(node, msg, *args):
|
||||
out = "%s:%d " % (node.filename, node.linenumber)
|
||||
out += msg % args
|
||||
print
|
||||
print out.encode('utf-8')
|
||||
|
||||
added = { }
|
||||
|
||||
# Reports additional information about a message, the first time it
|
||||
# occurs.
|
||||
def add(msg):
|
||||
if not msg in added:
|
||||
added[msg] = True
|
||||
print unicode(msg).encode('utf-8')
|
||||
|
||||
|
||||
# Trys to evaluate an expression, announcing an error if it fails.
|
||||
def try_eval(node, where, expr, additional=None):
|
||||
|
||||
try:
|
||||
renpy.python.py_eval(expr)
|
||||
except:
|
||||
report(node, "Could not evaluate '%s', in %s.", expr, where)
|
||||
if additional:
|
||||
add(additional)
|
||||
|
||||
# Returns True of the expression can be compiled as python, False
|
||||
# otherwise.
|
||||
def try_compile(node, where, expr):
|
||||
|
||||
try:
|
||||
renpy.python.py_compile_eval_bytecode(expr)
|
||||
except:
|
||||
report(node, "'%s' could not be compiled as a python expression, %s.", expr, where)
|
||||
|
||||
|
||||
# This reports an error if we're sure that the image with the given name
|
||||
# does not exist.
|
||||
def image_exists(node, name):
|
||||
|
||||
name = list(name)
|
||||
names = " ".join(name)
|
||||
|
||||
while name:
|
||||
if tuple(name) in renpy.exports.images:
|
||||
return
|
||||
|
||||
name.pop()
|
||||
|
||||
report(node, "The image named '%s' was not declared.", names)
|
||||
|
||||
|
||||
|
||||
# Lints ast.Image nodes.
|
||||
def check_image(node):
|
||||
|
||||
name = " ".join(node.imgname)
|
||||
files = [ ]
|
||||
|
||||
def files_callback(img):
|
||||
files.extend(img.predict_files())
|
||||
|
||||
renpy.exports.images[node.imgname].predict(files_callback)
|
||||
|
||||
for fn in files:
|
||||
|
||||
if not renpy.loader.loadable(fn):
|
||||
report(node, "Image '%s' uses file '%s', which is not loadable.", name, fn)
|
||||
continue
|
||||
|
||||
if renpy.loader.transfn(fn) and \
|
||||
fn.lower() in filenames and \
|
||||
fn != filenames[fn.lower()]:
|
||||
report(node, "Filename case mismatch for image '%s'. '%s' was used in the script, but '%s' was found on disk.", name, fn, filenames[fn.lower()])
|
||||
|
||||
add("Case mismatches can lead to problems on Mac, Linux/Unix, and when archiving images. To fix them, either rename the file on disk, or the filename use in the script.")
|
||||
continue
|
||||
|
||||
|
||||
# Lints ast.Show and ast.Scene nodets.
|
||||
def check_show(node):
|
||||
|
||||
# A Scene may have an empty imspec.
|
||||
if not node.imspec:
|
||||
return
|
||||
|
||||
name, at_list = node.imspec
|
||||
|
||||
image_exists(node, name)
|
||||
|
||||
for i in at_list:
|
||||
try_eval(node, "the at list of a scene or show statment", i, "Perhaps you forgot to declare, or misspelled, a position?")
|
||||
|
||||
|
||||
# Lints ast.Hide.
|
||||
|
||||
def check_hide(node):
|
||||
|
||||
name, at_list = node.imspec
|
||||
|
||||
if name[0] not in image_prefixes:
|
||||
report(node, "The image prefix '%s' is not the prefix of any declared image.", name[0])
|
||||
|
||||
for i in at_list:
|
||||
try_eval(node, "at list of hide statment", i)
|
||||
|
||||
def check_with(node):
|
||||
try_eval(node, "a with statement or clause", node.expr, "Perhaps you forgot to declare, or misspelled, a transition?")
|
||||
|
||||
|
||||
def check_say(node):
|
||||
|
||||
if node.who:
|
||||
try_eval(node, "the who part of a say statement", node.who, "Perhaps you forgot to declare a character?")
|
||||
|
||||
if node.with:
|
||||
try_eval(node, "the with clause of a say statement", node.with, "Perhaps you forgot to declare, or misspelled, a transition?")
|
||||
|
||||
def check_menu(node):
|
||||
|
||||
if node.with:
|
||||
try_eval(node, "the with clause of a menu statement", node.with, "Perhaps you forgot to declare, or misspelled, a transition?")
|
||||
|
||||
if not [ (l, c, b) for l, c, b in node.items if l ]:
|
||||
report(node, "The menu does not contain any selectable choices.")
|
||||
|
||||
for l, c, b in node.items:
|
||||
if c:
|
||||
try_compile(node, "in the if clause of a menuitem", c)
|
||||
|
||||
|
||||
def check_jump(node):
|
||||
|
||||
if node.expression:
|
||||
return
|
||||
|
||||
if not renpy.game.script.has_label(node.target):
|
||||
report(node, "The jump is to notexistent label '%s'.", node.target)
|
||||
|
||||
def check_call(node):
|
||||
|
||||
if not isinstance(node.next.name, basestring):
|
||||
report(node, "The call does not have a from clause associated with it.")
|
||||
add("You can add from clauses to calls automatically by running the add_from program.")
|
||||
add("This is necessary to ensure saves can be loaded even when the script changes.")
|
||||
|
||||
if node.expression:
|
||||
return
|
||||
|
||||
if not renpy.game.script.has_label(node.label):
|
||||
report(node, "The call is to notexistent label '%s'.", node.label)
|
||||
|
||||
def check_while(node):
|
||||
try_compile(node, "in the condition of the while statement", node.condition)
|
||||
|
||||
def check_if(node):
|
||||
|
||||
for condition, block in node.entries:
|
||||
try_compile(node, "in a condition of the if statement", condition)
|
||||
|
||||
def lint():
|
||||
"""
|
||||
The master lint function, that's responsible for staging all of the
|
||||
other checks.
|
||||
"""
|
||||
|
||||
print codecs.BOM_UTF8
|
||||
print unicode(renpy.version + " lint report, generated at: " + time.ctime()).encode("utf-8")
|
||||
|
||||
|
||||
# This is used to support the check_image.
|
||||
global filenames
|
||||
filenames = { }
|
||||
|
||||
for d in renpy.config.searchpath:
|
||||
for fn in os.listdir(d):
|
||||
filenames[fn.lower()] = fn
|
||||
|
||||
# This supports check_hide.
|
||||
global image_prefixes
|
||||
image_prefixes = { }
|
||||
|
||||
for k in renpy.exports.images:
|
||||
image_prefixes[k[0]] = True
|
||||
|
||||
# Iterate through every statement in the program, processing
|
||||
# them. We sort them in filename, linenumber order.
|
||||
|
||||
all_stmts = [ (i.filename, i.linenumber, i) for i in renpy.game.script.all_stmts ]
|
||||
all_stmts.sort()
|
||||
|
||||
say_words = 0
|
||||
say_count = 0
|
||||
menu_count = 0
|
||||
|
||||
for fn, ln, node in all_stmts:
|
||||
|
||||
if isinstance(node, renpy.ast.Image):
|
||||
check_image(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.Show):
|
||||
check_show(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.Scene):
|
||||
check_show(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.Hide):
|
||||
check_hide(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.With):
|
||||
check_with(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.Say):
|
||||
check_say(node)
|
||||
say_count += 1
|
||||
say_words += len(node.what.split())
|
||||
|
||||
|
||||
elif isinstance(node, renpy.ast.Menu):
|
||||
check_menu(node)
|
||||
menu_count += 1
|
||||
|
||||
elif isinstance(node, renpy.ast.Jump):
|
||||
check_jump(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.Call):
|
||||
check_call(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.While):
|
||||
check_while(node)
|
||||
|
||||
elif isinstance(node, renpy.ast.If):
|
||||
check_if(node)
|
||||
|
||||
print
|
||||
print
|
||||
print "The game contains", say_count, "lines of dialogue containing a total of", say_words, "words, and", menu_count, "menus."
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
import renpy
|
||||
import os.path
|
||||
from cPickle import loads
|
||||
from pickle import loads
|
||||
from cStringIO import StringIO
|
||||
|
||||
archives = [ ]
|
||||
@@ -57,6 +57,24 @@ def load(name):
|
||||
|
||||
raise Exception("Couldn't find file '%s'." % name)
|
||||
|
||||
def loadable(name):
|
||||
"""
|
||||
Returns True if the name is loadable with load, False if it is not.
|
||||
"""
|
||||
|
||||
try:
|
||||
transfn(name)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
for prefix, index in archives:
|
||||
if name in index:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def transfn(name):
|
||||
"""
|
||||
Tries to translate the name to a file that exists in one of the
|
||||
|
||||
+25
-11
@@ -1,6 +1,6 @@
|
||||
# This file contains functions that load and save the game state.
|
||||
|
||||
from cPickle import dumps, loads, HIGHEST_PROTOCOL
|
||||
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
||||
import cStringIO
|
||||
import zipfile
|
||||
import time
|
||||
@@ -15,8 +15,12 @@ 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, id(o)
|
||||
print prefix, "@%x" % id(o)
|
||||
return
|
||||
|
||||
seen[id(o)] = True
|
||||
@@ -131,19 +135,28 @@ def saved_games():
|
||||
|
||||
for f in files:
|
||||
|
||||
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
|
||||
extra_info = zf.read("extra_info")
|
||||
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
|
||||
zf.close()
|
||||
|
||||
screenshot = renpy.display.image.UncachedImage(sio, "screenshot.tga", False)
|
||||
try:
|
||||
|
||||
f = f[:-len(savegame_suffix)]
|
||||
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
|
||||
extra_info = zf.read("extra_info")
|
||||
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
|
||||
zf.close()
|
||||
|
||||
saveinfo[f] = screenshot, extra_info
|
||||
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(filename):
|
||||
"""
|
||||
Loads the game from the given file. This function never returns.
|
||||
@@ -152,4 +165,5 @@ def load(filename):
|
||||
zf = zipfile.ZipFile(renpy.config.savedir + "/" + filename + savegame_suffix, "r")
|
||||
log = loads(zf.read("log"))
|
||||
zf.close()
|
||||
|
||||
log.unfreeze()
|
||||
|
||||
+20
-8
@@ -12,9 +12,9 @@
|
||||
import renpy
|
||||
import renpy.game as game
|
||||
import os
|
||||
from cPickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
from pickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
|
||||
def run(restart=False):
|
||||
def run(restart=False, lint=False):
|
||||
"""
|
||||
This is called during a single run of the script. Restarting the script
|
||||
will cause this to change.
|
||||
@@ -54,6 +54,10 @@ 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 = { }
|
||||
@@ -66,11 +70,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
|
||||
|
||||
@@ -88,14 +92,21 @@ def run(restart=False):
|
||||
renpy.game.exception_info = 'After initialization, but before game start.'
|
||||
|
||||
# Rebuild the various style caches.
|
||||
game.style._build_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 = game.store.copy()
|
||||
game.clean_store = vars(renpy.store).copy()
|
||||
|
||||
if lint:
|
||||
renpy.lint.lint()
|
||||
return
|
||||
|
||||
# Remove the list of all statements from the script.
|
||||
game.script.all_stmts = None
|
||||
|
||||
# Re-Initialize the log.
|
||||
game.log = renpy.python.RollbackLog()
|
||||
@@ -124,7 +135,8 @@ def run(restart=False):
|
||||
# We get this when the context has changed, and so we go and
|
||||
# start running from the new context.
|
||||
except game.RestartException, e:
|
||||
pass
|
||||
renpy.game.contexts = e.args[0]
|
||||
continue
|
||||
|
||||
except game.QuitException, e:
|
||||
break
|
||||
@@ -136,7 +148,7 @@ def run(restart=False):
|
||||
|
||||
# And, we're done.
|
||||
|
||||
def main(basepath):
|
||||
def main(basepath, lint=False):
|
||||
|
||||
renpy.game.exception_info = 'While loading the script.'
|
||||
|
||||
@@ -154,7 +166,7 @@ def main(basepath):
|
||||
|
||||
while True:
|
||||
try:
|
||||
run(restart)
|
||||
run(restart, lint=lint)
|
||||
break
|
||||
except game.FullRestartException, e:
|
||||
restart = True
|
||||
|
||||
-228
@@ -1,228 +0,0 @@
|
||||
# This module contains code that handles the playing of music.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
playing_midi = True
|
||||
fading = False
|
||||
master_music_volume = 1.0
|
||||
|
||||
|
||||
windows_magic = False
|
||||
|
||||
if hasattr(sys, 'winver'):
|
||||
|
||||
midi_msf = 0.0
|
||||
|
||||
last_raw_volume = -1
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
global last_raw_volume
|
||||
last_raw_volume = read_raw_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
|
||||
|
||||
def set_music_volume(vol):
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
playing_midi = False
|
||||
|
||||
# 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")
|
||||
|
||||
|
||||
# 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
|
||||
global fading
|
||||
|
||||
compute_midi_msf()
|
||||
set_music_volume(1.0)
|
||||
|
||||
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))
|
||||
fading = True
|
||||
else:
|
||||
if not pygame.mixer.music.get_busy():
|
||||
fn, loops, startpos = new_music
|
||||
|
||||
fading = False
|
||||
detect_midi(fn)
|
||||
|
||||
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
|
||||
pygame.mixer.music.play(loops, startpos)
|
||||
|
||||
set_music_volume(master_music_volume)
|
||||
|
||||
current_music = new_music
|
||||
|
||||
except pygame.error, e:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
print "Error while trying to play music:", str(e)
|
||||
+35
-10
@@ -5,6 +5,7 @@ import codecs
|
||||
import re
|
||||
import os
|
||||
|
||||
import renpy
|
||||
import renpy.ast as ast
|
||||
|
||||
class ParseError(Exception):
|
||||
@@ -142,7 +143,7 @@ def list_logical_lines(filename):
|
||||
|
||||
|
||||
if line != "":
|
||||
raise ParseError(filename, number, "is not terminated with a newline.")
|
||||
raise ParseError(filename, start_number, "is not terminated with a newline (check quotes and parenthesis).")
|
||||
|
||||
return rv
|
||||
|
||||
@@ -171,13 +172,11 @@ def group_logical_lines(lines):
|
||||
|
||||
if l[index] == '\t':
|
||||
index += 1
|
||||
depth = depth + 8 - (16 % 8)
|
||||
depth = depth + 8 - (depth % 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
|
||||
@@ -466,12 +465,17 @@ class Lexer(object):
|
||||
if c == 'u':
|
||||
self.pos += 1
|
||||
|
||||
if self.eol():
|
||||
if self.pos == len(self.text):
|
||||
self.pos -= 1
|
||||
return False
|
||||
|
||||
c = self.text[self.pos]
|
||||
|
||||
if c not in ('"', "'"):
|
||||
if c not in ('"', "'"):
|
||||
self.pos -= 1
|
||||
return False
|
||||
|
||||
elif c not in ('"', "'"):
|
||||
return False
|
||||
|
||||
delim = c
|
||||
@@ -513,7 +517,7 @@ class Lexer(object):
|
||||
while self.match(r'\.'):
|
||||
n = self.name()
|
||||
if not n:
|
||||
self.parse_error('expecting name.')
|
||||
self.error('expecting name.')
|
||||
|
||||
rv += "." + n
|
||||
|
||||
@@ -698,20 +702,37 @@ class Lexer(object):
|
||||
def python_block(self):
|
||||
"""
|
||||
Returns the subblock of this code, and subblocks of that
|
||||
subblock, as indented python code.
|
||||
subblock, as indented python code. This tries to insert
|
||||
whitespace to ensure line numbers match up.
|
||||
"""
|
||||
|
||||
rv = [ ]
|
||||
|
||||
# Something to hold the expected line number.
|
||||
class Object(object):
|
||||
pass
|
||||
o = Object()
|
||||
o.line = self.number
|
||||
|
||||
def process(block, indent):
|
||||
|
||||
for fn, ln, text, subblock in block:
|
||||
rv.append(indent + text + '\n')
|
||||
|
||||
if o.line > ln:
|
||||
assert False
|
||||
|
||||
while o.line < ln:
|
||||
rv.append(indent + '\n')
|
||||
o.line += 1
|
||||
|
||||
linetext = indent + text + '\n'
|
||||
|
||||
rv.append(linetext)
|
||||
o.line += linetext.count('\n')
|
||||
|
||||
process(subblock, indent + ' ')
|
||||
|
||||
process(self.subblock, '')
|
||||
|
||||
return ''.join(rv)
|
||||
|
||||
def parse_image_name(l):
|
||||
@@ -814,6 +835,8 @@ def parse_menu(l, loc):
|
||||
l.expect_noblock('with clause')
|
||||
l.advance()
|
||||
|
||||
continue
|
||||
|
||||
if l.keyword('set'):
|
||||
set = l.require(l.simple_expression)
|
||||
l.expect_eol()
|
||||
@@ -1184,6 +1207,8 @@ 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)
|
||||
|
||||
+67
-29
@@ -6,7 +6,7 @@
|
||||
|
||||
from compiler import parse
|
||||
from compiler.pycodegen import ModuleCodeGenerator, ExpressionCodeGenerator
|
||||
from compiler.misc import set_filename
|
||||
# from compiler.misc import set_filename
|
||||
import compiler.ast as ast
|
||||
|
||||
import marshal
|
||||
@@ -62,7 +62,7 @@ def reached(obj, path, reachable):
|
||||
def reached_vars(store, reachable):
|
||||
"""
|
||||
Marks everything reachable from the variables in the store
|
||||
as reachable.
|
||||
or from the context info objects as reachable.
|
||||
|
||||
@param store: A map from variable name to variable value.
|
||||
@param reachable: A dictionary mapping reached object ids to
|
||||
@@ -72,7 +72,9 @@ 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.
|
||||
|
||||
@@ -116,33 +118,56 @@ def recursively_replace(o, func):
|
||||
|
||||
return o
|
||||
|
||||
def py_compile(source, mode):
|
||||
def set_filename(filename, offset, tree):
|
||||
"""Set the filename attribute to filename on every node in tree"""
|
||||
worklist = [tree]
|
||||
while worklist:
|
||||
node = worklist.pop(0)
|
||||
node.filename = filename
|
||||
|
||||
lineno = getattr(node, 'lineno', None)
|
||||
if lineno is not None:
|
||||
node.lineno = lineno + offset
|
||||
|
||||
worklist.extend(node.getChildNodes())
|
||||
|
||||
|
||||
def py_compile(source, mode, filename='<none>', lineno=1):
|
||||
"""
|
||||
Compiles the given source code using the supplied codegenerator.
|
||||
Lists, List Comprehensions, and Dictionaries are wrapped when
|
||||
appropriate.
|
||||
|
||||
@param source: The sourccode, as a string.
|
||||
|
||||
@param mode: 'exec' or 'eval'.
|
||||
|
||||
@param filename: The filename that the source code is taken from.
|
||||
|
||||
@param lineno: The line number of the first line of the source code.
|
||||
"""
|
||||
|
||||
source = source.encode('raw_unicode_escape')
|
||||
tree = parse(source, mode)
|
||||
|
||||
recursively_replace(tree, wrap_node)
|
||||
|
||||
if mode == 'exec':
|
||||
set_filename("<none>", tree)
|
||||
set_filename(filename, lineno - 1, tree)
|
||||
cg = ModuleCodeGenerator(tree)
|
||||
else:
|
||||
set_filename("<none>", tree)
|
||||
set_filename(filename, lineno - 1, tree)
|
||||
cg = ExpressionCodeGenerator(tree)
|
||||
|
||||
return cg.getCode()
|
||||
|
||||
def py_compile_exec_bytecode(source):
|
||||
code = py_compile(source, 'exec')
|
||||
def py_compile_exec_bytecode(source, **kwargs):
|
||||
code = py_compile(source, 'exec', **kwargs)
|
||||
return marshal.dumps(code)
|
||||
|
||||
def py_compile_eval_bytecode(source):
|
||||
def py_compile_eval_bytecode(source, **kwargs):
|
||||
source = source.strip()
|
||||
code = py_compile(source, 'exec')
|
||||
code = py_compile(source, 'exec', **kwargs)
|
||||
return marshal.dumps(code)
|
||||
|
||||
|
||||
@@ -356,10 +381,10 @@ class Rollback(renpy.object.Object):
|
||||
for t in self.store:
|
||||
if len(t) == 2:
|
||||
k, v = t
|
||||
renpy.game.store[k] = v
|
||||
vars(renpy.store)[k] = v
|
||||
else:
|
||||
k, = t
|
||||
del renpy.game.store[k]
|
||||
del vars(renpy.store)[k]
|
||||
|
||||
renpy.game.contexts = [ self.context ]
|
||||
rng.pushback(self.random)
|
||||
@@ -415,6 +440,12 @@ class RollbackLog(renpy.object.Object):
|
||||
state needs to be saved for rollbacking.
|
||||
"""
|
||||
|
||||
# If the transient scene list is not empty, then we do
|
||||
# not begin a new rollback, as the TSL will be purged
|
||||
# after a rollback is complete.
|
||||
if not renpy.game.contexts[0].scene_lists.transient_is_empty():
|
||||
return
|
||||
|
||||
# If the log is too long, try pruning it to a label.
|
||||
if len(self.log) > renpy.config.rollback_length:
|
||||
rb = self.log[-renpy.config.rollback_length]
|
||||
@@ -428,7 +459,7 @@ class RollbackLog(renpy.object.Object):
|
||||
self.log.append(self.current)
|
||||
|
||||
self.mutated = { }
|
||||
self.old_store = renpy.game.store.copy()
|
||||
self.old_store = vars(renpy.store).copy()
|
||||
|
||||
def complete(self):
|
||||
"""
|
||||
@@ -439,7 +470,7 @@ class RollbackLog(renpy.object.Object):
|
||||
occurs.
|
||||
"""
|
||||
|
||||
new_store = renpy.game.store
|
||||
new_store = vars(renpy.store)
|
||||
store = [ ]
|
||||
|
||||
|
||||
@@ -484,9 +515,11 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
rv = { }
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
for k in self.ever_been_changed.keys():
|
||||
if k in renpy.game.store:
|
||||
rv[k] = renpy.game.store[k]
|
||||
if k in store:
|
||||
rv[k] = store[k]
|
||||
|
||||
return rv
|
||||
|
||||
@@ -574,14 +607,14 @@ class RollbackLog(renpy.object.Object):
|
||||
rb.rollback()
|
||||
|
||||
# Disable the next transition, as it's pointless.
|
||||
renpy.game.interface.supress_transition = True
|
||||
renpy.game.interface.suppress_transition = True
|
||||
|
||||
# If necessary, reset the RNG.
|
||||
if force:
|
||||
rng.reset()
|
||||
|
||||
# Restart the game with the new state.
|
||||
raise renpy.game.RestartException()
|
||||
raise renpy.game.RestartException(renpy.game.contexts[:])
|
||||
|
||||
def freeze(self):
|
||||
"""
|
||||
@@ -617,14 +650,15 @@ class RollbackLog(renpy.object.Object):
|
||||
renpy.game.log = self
|
||||
|
||||
# Restore the store.
|
||||
renpy.game.store.clear()
|
||||
renpy.game.store.update(renpy.game.clean_store)
|
||||
store = vars(renpy.store)
|
||||
store.clear()
|
||||
store.update(renpy.game.clean_store)
|
||||
|
||||
for k in self.ever_been_changed:
|
||||
if k in renpy.game.store:
|
||||
del renpy.game.store[k]
|
||||
if k in store:
|
||||
del store[k]
|
||||
|
||||
renpy.game.store.update(self.frozen_roots)
|
||||
store.update(self.frozen_roots)
|
||||
self.frozen_roots = None
|
||||
|
||||
# Now, rollback to an acceptable point.
|
||||
@@ -634,31 +668,35 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
def py_exec_bytecode(bytecode, hide=False):
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if hide:
|
||||
locals = { }
|
||||
else:
|
||||
locals = renpy.game.store
|
||||
locals = store
|
||||
|
||||
exec marshal.loads(bytecode) in renpy.game.store, locals
|
||||
exec marshal.loads(bytecode) in store, locals
|
||||
|
||||
|
||||
def py_exec(source, hide=False):
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if hide:
|
||||
locals = { }
|
||||
else:
|
||||
locals = renpy.game.store
|
||||
locals = store
|
||||
|
||||
|
||||
exec py_compile(source, 'exec') in renpy.game.store, locals
|
||||
exec py_compile(source, 'exec') in store, locals
|
||||
|
||||
def py_eval_bytecode(bytecode):
|
||||
|
||||
return eval(marshal.loads(bytecode), renpy.game.store)
|
||||
return eval(marshal.loads(bytecode), vars(renpy.store))
|
||||
|
||||
def py_eval(source):
|
||||
source = source.strip()
|
||||
|
||||
return eval(py_compile(source, 'eval'),
|
||||
renpy.game.store)
|
||||
vars(renpy.store))
|
||||
|
||||
|
||||
+18
-4
@@ -6,7 +6,7 @@ import renpy
|
||||
import os.path
|
||||
import os
|
||||
|
||||
from cPickle import loads, dumps
|
||||
from pickle import loads, dumps
|
||||
|
||||
# The version of the dumped script.
|
||||
script_version = renpy.script_version
|
||||
@@ -33,6 +33,10 @@ class Script(object):
|
||||
@ivar initcode: A list of priority, Node tuples that should be
|
||||
executed in ascending priority order at init time.
|
||||
|
||||
@ivar all_stmts: A list of all statements, that have been found
|
||||
in every file. Useful for lint, but tossed if lint is not performed
|
||||
to save memory.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, node_callback=None):
|
||||
@@ -43,7 +47,7 @@ class Script(object):
|
||||
|
||||
self.namemap = { }
|
||||
self.initcode = [ ]
|
||||
|
||||
self.all_stmts = [ ]
|
||||
|
||||
# A list of all files in the search directories.
|
||||
dirlist = [ ]
|
||||
@@ -91,7 +95,15 @@ class Script(object):
|
||||
raise Exception("Could not load %s or %s." % (fn, alt))
|
||||
|
||||
|
||||
self.initcode.sort()
|
||||
# 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 ]
|
||||
|
||||
|
||||
|
||||
# Do some generic init here.
|
||||
|
||||
@@ -100,7 +112,7 @@ class Script(object):
|
||||
if fn.endswith(".rpy"):
|
||||
stmts = renpy.parser.parse(fn)
|
||||
f = file(fn + "c", "wb")
|
||||
f.write(dumps((script_version, stmts)).encode('zlib'))
|
||||
f.write(dumps((script_version, stmts), -1).encode('zlib'))
|
||||
f.close()
|
||||
elif fn.endswith(".rpyc"):
|
||||
f = file(fn, "rb")
|
||||
@@ -157,6 +169,8 @@ class Script(object):
|
||||
if init:
|
||||
self.initcode.append(init)
|
||||
|
||||
self.all_stmts.extend(all_stmts)
|
||||
|
||||
return True
|
||||
|
||||
def lookup(self, label):
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Plays sounds.
|
||||
|
||||
import renpy
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
def 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 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_sound:
|
||||
raise
|
||||
|
||||
+93
-86
@@ -6,6 +6,9 @@
|
||||
import renpy
|
||||
|
||||
import renpy.ui as ui
|
||||
import renpy.display.im as im
|
||||
import renpy.display.anim as anim
|
||||
import renpy.display.audio as audio
|
||||
|
||||
from renpy.python import RevertableList as __renpy__list__
|
||||
list = __renpy__list__
|
||||
@@ -19,15 +22,24 @@ from renpy.python import RevertableObject as object
|
||||
|
||||
config = renpy.config
|
||||
Image = renpy.display.image.Image
|
||||
ImageReference = renpy.display.image.ImageReference
|
||||
Solid = renpy.display.image.Solid
|
||||
Frame = renpy.display.image.Frame
|
||||
Animation = renpy.display.image.Animation
|
||||
Null = renpy.display.layout.Null
|
||||
# Animation = renpy.display.image.Animation
|
||||
Animation = anim.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)
|
||||
|
||||
Motion = renpy.curry.curry(renpy.display.layout.Motion)
|
||||
Fade = renpy.curry.curry(renpy.display.transition.Fade)
|
||||
Dissolve = renpy.curry.curry(renpy.display.transition.Dissolve)
|
||||
ImageDissolve = renpy.curry.curry(renpy.display.transition.ImageDissolve)
|
||||
CropMove = renpy.curry.curry(renpy.display.transition.CropMove)
|
||||
Pixellate = renpy.curry.curry(renpy.display.transition.Pixellate)
|
||||
MoveTransition = renpy.curry.curry(renpy.display.transition.MoveTransition)
|
||||
|
||||
def _return(v):
|
||||
"""
|
||||
@@ -40,8 +52,6 @@ def _return(v):
|
||||
_return = renpy.curry.curry(_return)
|
||||
|
||||
# Note that this is really a RevertableObject.
|
||||
# TODO: Move this someplace saner. Like perhaps to .exports. But
|
||||
# be sure to change the base class after the move!
|
||||
|
||||
class Character(object):
|
||||
"""
|
||||
@@ -58,6 +68,9 @@ class Character(object):
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
window_style='say_window',
|
||||
function = renpy.exports.display_say,
|
||||
condition=None,
|
||||
dynamic=False,
|
||||
**properties):
|
||||
"""
|
||||
@param name: The name of the character, as shown to the user.
|
||||
@@ -72,12 +85,6 @@ class Character(object):
|
||||
@param window_style: The name of the style of the window
|
||||
containing all the dialogue.
|
||||
|
||||
@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 ':')
|
||||
@@ -86,10 +93,29 @@ class Character(object):
|
||||
|
||||
@param what_suffix: A suffix that is appended to the text body.
|
||||
|
||||
@param function: The function that is called to actually display
|
||||
this dialogue. This should either be renpy.display_say, or a function
|
||||
with the same signature as it.
|
||||
|
||||
@param condition: A string containing a python expression, or
|
||||
None. If not None, the condition is evaluated when each line
|
||||
of dialogue is said. If it evaluates to False, the dialogue is
|
||||
not shown to the user.
|
||||
|
||||
@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.
|
||||
|
||||
@param properties: Additional style properties, that are
|
||||
applied to the label containing the character's name.
|
||||
|
||||
@param dynamic: If true, the name is interpreted as a python
|
||||
expression, which is evaluated to get the name that will be
|
||||
used by the rest of the code.
|
||||
|
||||
@param image: If true, the name is considered to be the name
|
||||
of an image, which is rendered in place of the who label.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
@@ -97,100 +123,81 @@ class Character(object):
|
||||
self.what_style = what_style
|
||||
self.window_style = window_style
|
||||
self.properties = properties
|
||||
self.function = function
|
||||
self.condition = condition
|
||||
self.dynamic = dynamic
|
||||
|
||||
def __call__(self, what):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
**self.properties)
|
||||
def check_condition(self):
|
||||
"""
|
||||
Returns true if we should show this line of dialogue.
|
||||
"""
|
||||
|
||||
class DynamicCharacter(object):
|
||||
if self.condition is None:
|
||||
return True
|
||||
|
||||
import renpy.python as python
|
||||
|
||||
return python.py_eval(self.condition)
|
||||
|
||||
|
||||
def store_readback(self, who, what):
|
||||
"""
|
||||
This is called when a say occurs, to store the information
|
||||
about what is said into the readback buffers.
|
||||
"""
|
||||
|
||||
return
|
||||
|
||||
def __call__(self, what, interact=True):
|
||||
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
name = self.name
|
||||
|
||||
if self.dynamic:
|
||||
import renpy.python as python
|
||||
name = python.py_eval(name)
|
||||
|
||||
self.store_readback(name, what)
|
||||
|
||||
self.function(name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
interact=interact,
|
||||
**self.properties)
|
||||
|
||||
def DynamicCharacter(name_expr, **properties):
|
||||
"""
|
||||
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.
|
||||
|
||||
This is now exactly the same as constructing a character with
|
||||
dynamic=True.
|
||||
"""
|
||||
|
||||
import renpy.config as config
|
||||
return Character(name_expr, dynamic=True, **properties)
|
||||
|
||||
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):
|
||||
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,
|
||||
**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
|
||||
|
||||
def narrator(what):
|
||||
renpy.display_say(None, what, what_style='say_thought')
|
||||
# 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
|
||||
|
||||
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)
|
||||
# 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
|
||||
|
||||
+163
-112
@@ -1,142 +1,193 @@
|
||||
import renpy
|
||||
|
||||
# A list of style prefixes we care about, including no prefix.
|
||||
prefixes = [ 'hover_', 'idle_', '' ]
|
||||
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', and to everyone as renpy.game.style. It's responsible for
|
||||
mapping style names to styles.
|
||||
This is the singleton object that is exported into the store
|
||||
as style
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._style_list = [ ]
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return style_map[name]
|
||||
except:
|
||||
raise Exception('The style %s does not exist.' % name)
|
||||
|
||||
def create(self, name, parent='default', description=''):
|
||||
"""
|
||||
Creates a new style with the given parent and description, and
|
||||
adds it to the StyleManager.
|
||||
"""
|
||||
def create(self, name, parent, description=None):
|
||||
style_map[name] = Style(parent, { })
|
||||
|
||||
if parent and not hasattr(self, parent):
|
||||
raise Exception("Style '%s' has non-existent parent '%s'." % (name, parent))
|
||||
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)
|
||||
|
||||
s = Style(parent, defer=True)
|
||||
s.name = name
|
||||
s.parent = parent
|
||||
s.description = description
|
||||
style.cache.update(parent.cache)
|
||||
|
||||
setattr(self, name, s)
|
||||
style.cache.update(style.properties)
|
||||
|
||||
self._style_list.append(s)
|
||||
# This builds all pending styles, recursing to ensure that they are built
|
||||
# in the right order.
|
||||
def build_styles():
|
||||
|
||||
def _build_style_caches(self):
|
||||
global styles_pending
|
||||
global styles_built
|
||||
|
||||
for i in self._style_list:
|
||||
i.build_cache()
|
||||
for s in styles_pending:
|
||||
build_style(s, True)
|
||||
|
||||
def _write_docs(self, filename):
|
||||
|
||||
f = file(filename, "w")
|
||||
|
||||
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()
|
||||
styles_pending = None
|
||||
styles_built = True
|
||||
|
||||
|
||||
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(properties=self.properties,
|
||||
prefix=self.prefix,
|
||||
parent=self.parent)
|
||||
|
||||
return dict(prefix = self.prefix,
|
||||
parent = self.parent,
|
||||
cache = { },
|
||||
properties = self.properties)
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
vars(self).update(state)
|
||||
build_style(self)
|
||||
|
||||
# This should always work, as only one layer of these styles will
|
||||
# be serialized.
|
||||
def __init__(self, parent, properties):
|
||||
|
||||
self.build_cache()
|
||||
fields = dict(
|
||||
prefix = 'insensitive_',
|
||||
parent = parent,
|
||||
cache = { },
|
||||
properties = { },
|
||||
)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
|
||||
for prefix in prefixes:
|
||||
prefkey = prefix + key
|
||||
|
||||
self.properties[prefkey] = value
|
||||
self.cache[prefkey] = value
|
||||
|
||||
def __getattr__(self, key):
|
||||
|
||||
cache = self.cache
|
||||
|
||||
try:
|
||||
return cache[self.prefix + key]
|
||||
except KeyError:
|
||||
raise AttributeError("Style property '%s' not found." % key)
|
||||
|
||||
def __delattr__(self, key):
|
||||
del self.properties[key]
|
||||
del self.cache[key]
|
||||
|
||||
def lookup(self, key, prefix):
|
||||
|
||||
cache = self.cache
|
||||
|
||||
try:
|
||||
return cache.get(prefix + key, cache[key])
|
||||
except KeyError:
|
||||
raise AttributeError("Style property '%s' not found." % key)
|
||||
vars(self).update(fields)
|
||||
|
||||
if styles_built:
|
||||
build_style(self)
|
||||
else:
|
||||
styles_pending.append(self)
|
||||
|
||||
compute_properties(self, properties)
|
||||
|
||||
def set_prefix(self, prefix):
|
||||
vars(self)["prefix"] = prefix
|
||||
self.prefix = prefix
|
||||
|
||||
|
||||
def build_cache(self):
|
||||
vars(self)["cache"] = { }
|
||||
|
||||
self.cache = { }
|
||||
|
||||
if self.parent:
|
||||
self.cache.update(getattr(renpy.game.style, self.parent).cache)
|
||||
|
||||
self.cache.update(self.properties)
|
||||
|
||||
def __init__(self, parent, properties=None, defer=False):
|
||||
|
||||
if parent and not hasattr(renpy.game.style, parent):
|
||||
raise Exception("Style '%s' is not known." % parent)
|
||||
|
||||
if not properties:
|
||||
properties = { }
|
||||
|
||||
for k in properties.keys():
|
||||
for p in prefixes:
|
||||
if p + k not in properties:
|
||||
properties[p + k] = properties[k]
|
||||
|
||||
vars(self)["parent"] = parent
|
||||
vars(self)["prefix"] = ''
|
||||
vars(self)["properties"] = properties
|
||||
vars(self)["cache"] = { }
|
||||
|
||||
if not defer:
|
||||
self.build_cache()
|
||||
def __getattr__(self, name):
|
||||
return self.cache[self.prefix + name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
compute_properties(self, { name : value } )
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
+174
-34
@@ -16,11 +16,10 @@
|
||||
|
||||
import renpy
|
||||
|
||||
# The current widget (None to add widgets directly to the
|
||||
# transient display list.)
|
||||
current = None
|
||||
# The current widget. (Should never become None.)
|
||||
current = 'transient'
|
||||
|
||||
# A stack of current widgets.
|
||||
# A stack of current widgets and/or layers.
|
||||
current_stack = [ ]
|
||||
|
||||
# True if the current widget should be used at most once.
|
||||
@@ -36,15 +35,17 @@ def interact(**kwargs):
|
||||
@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.
|
||||
@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 stack. Did you forget a ui.close()?")
|
||||
raise Exception("ui.interact called with non-empty widget/layer stack. Did you forget a ui.close() somewhere?")
|
||||
|
||||
return renpy.game.interface.interact(**kwargs)
|
||||
|
||||
rv = renpy.game.interface.interact(**kwargs)
|
||||
renpy.game.context(-1).mark_seen()
|
||||
return rv
|
||||
|
||||
def add(w, make_current=False, once=False):
|
||||
"""
|
||||
@@ -56,8 +57,8 @@ def add(w, make_current=False, once=False):
|
||||
global current
|
||||
global current_once
|
||||
|
||||
if current is None:
|
||||
renpy.game.context(-1).scene_lists.add('transient', w)
|
||||
if isinstance(current, str):
|
||||
renpy.game.context(-1).scene_lists.add(current, w)
|
||||
else:
|
||||
current.add(w)
|
||||
|
||||
@@ -73,19 +74,38 @@ def add(w, make_current=False, once=False):
|
||||
|
||||
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 and name not in renpy.config.top_layers:
|
||||
raise Exception("'%s' is not a known layer." % name)
|
||||
|
||||
current_stack.append(current)
|
||||
current_once = False
|
||||
current = name
|
||||
|
||||
def close():
|
||||
"""
|
||||
Closes the current widget. This means that we will no longer add
|
||||
things to the current widget, but will instead start adding things
|
||||
to the parent of that widget, or the screen if the widget has no
|
||||
parent. Calling this when there is no current widget will lead to
|
||||
an exception being thrown.
|
||||
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 current is None:
|
||||
raise Exception("ui.close() called when there is no open widget.")
|
||||
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.")
|
||||
@@ -148,6 +168,37 @@ def vbox(padding=0, **properties):
|
||||
|
||||
return add(renpy.display.layout.VBox(padding, **properties), True)
|
||||
|
||||
def grid(cols, rows, padding=0, transpose=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. This condition is relaxed in the appropriate
|
||||
dimension if xfill or yfill is set.
|
||||
|
||||
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.
|
||||
(Otherwise, xfill and yfill are inherited from the style.)
|
||||
|
||||
@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.
|
||||
@param transpose: If True, grid will fill down columns before filling across rows.
|
||||
"""
|
||||
|
||||
return add(renpy.display.layout.Grid(cols, rows, padding, transpose=transpose, **properties), True)
|
||||
|
||||
def fixed(**properties):
|
||||
"""
|
||||
This creates a layout that places widgets at fixed locations
|
||||
@@ -175,8 +226,8 @@ def sizer(maxwidth=None, maxheight=None, **properties):
|
||||
|
||||
@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),
|
||||
|
||||
return add(renpy.display.layout.Container(xmaximum=maxwidth, ymaximum=maxheight, **properties),
|
||||
True, True)
|
||||
|
||||
|
||||
@@ -196,23 +247,51 @@ def keymousebehavior():
|
||||
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 add(renpy.display.behavior.KeymouseBehavior())
|
||||
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 if the current statement has already been seen. This widget
|
||||
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 menu(menuitems, **properties):
|
||||
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
|
||||
@@ -221,12 +300,45 @@ def menu(menuitems, **properties):
|
||||
@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 renpy.interact() if this item is selected, or None
|
||||
returned from ui.interact() if this item is selected, or None
|
||||
if this item is a non-selectable caption.
|
||||
"""
|
||||
|
||||
return add(renpy.display.behavior.Menu(menuitems, **properties))
|
||||
# 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):
|
||||
"""
|
||||
@@ -237,16 +349,33 @@ def image(filename, **properties):
|
||||
return add(renpy.display.image.Image(filename, **properties))
|
||||
|
||||
def imagemap(ground, selected, hotspots, unselected=None,
|
||||
style='imagemap', button_style='imagemap_button',
|
||||
**properties):
|
||||
"""
|
||||
This is the widget that implements imagemaps. Parameters are
|
||||
This is called to create imagemaps. Parameters are
|
||||
roughtly the same as renpy.imagemap. The value of the hotspot is
|
||||
returned when renpy.interact() returns.
|
||||
returned when ui.interact() returns.
|
||||
"""
|
||||
|
||||
return add(renpy.display.image.ImageMap(ground, selected,
|
||||
hotspots, unselected,
|
||||
**properties))
|
||||
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):
|
||||
@@ -254,7 +383,7 @@ 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 renpy.interact().
|
||||
is returned from ui.interact().
|
||||
|
||||
Buttons created with this function contain another widget,
|
||||
specifically the next widget to be added. As a convenience, one
|
||||
@@ -272,7 +401,7 @@ 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 renpy.interact().
|
||||
is returned from ui.interact().
|
||||
|
||||
@param text: The text of this button.
|
||||
|
||||
@@ -295,7 +424,7 @@ def imagebutton(idle_image, hover_image, clicked=None,
|
||||
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 renpy.interact().
|
||||
value is returned from ui.interact().
|
||||
|
||||
@param idle_image: The file name of the image used when this
|
||||
button is idle.
|
||||
@@ -337,7 +466,7 @@ def bar(width, height, range, value, clicked=None, **properties):
|
||||
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
|
||||
renpy.interact().
|
||||
ui.interact().
|
||||
|
||||
For best results, if clicked is set then width should be at least
|
||||
twice as big as range.
|
||||
@@ -381,3 +510,14 @@ def _jumps(label):
|
||||
|
||||
jumps = renpy.curry.curry(_jumps)
|
||||
|
||||
|
||||
def _jumpsoutofcontext(label):
|
||||
"""
|
||||
This exits the current context, and in the parent context jumps to
|
||||
the named label. It's intended to be used as the clicked argument
|
||||
to a button.
|
||||
"""
|
||||
|
||||
raise renpy.game.JumpOutException(label)
|
||||
|
||||
jumpsoutofcontext = renpy.curry.curry(_jumpsoutofcontext)
|
||||
|
||||
+75
-18
@@ -2,14 +2,6 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
@@ -20,16 +12,19 @@ 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
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
# Add the path to the module.
|
||||
sys.path.append("module")
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
|
||||
@@ -51,14 +46,27 @@ if __name__ == "__main__":
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--lint', dest='lint', default=False, action='store_true',
|
||||
help='Run a number of expensive tests, to try to detect errors in the script.')
|
||||
|
||||
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)
|
||||
|
||||
if not options.lint:
|
||||
import renpy.display.presplash
|
||||
renpy.display.presplash.start(options.game)
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
try:
|
||||
renpy.main.main(options.game)
|
||||
renpy.main.main(options.game, lint=options.lint)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
@@ -72,6 +80,16 @@ if __name__ == "__main__":
|
||||
|
||||
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)
|
||||
|
||||
@@ -96,8 +114,47 @@ if __name__ == "__main__":
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def memory_profile():
|
||||
|
||||
import renpy
|
||||
|
||||
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()
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
run_game.py
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user