Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b51312b21d |
+458
@@ -1,3 +1,461 @@
|
||||
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
|
||||
------------
|
||||
|
||||
|
||||
@@ -45,3 +45,8 @@ 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
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=programs,
|
||||
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py" ],
|
||||
console=[ "archiver.py", "add_from.py", "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
|
||||
+21
-431
@@ -20,56 +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" ),
|
||||
( "Preferences", "_preferences" ),
|
||||
( "Quit Game", "_quit" ),
|
||||
]
|
||||
|
||||
# The contents of the game menu choices.
|
||||
library.game_menu = [
|
||||
( "return", "Return", "_return", 'True'),
|
||||
( "prefs", "Preferences", "_prefs_screen", 'True' ),
|
||||
( "save", "Save Game", "_save_screen", '_can_save' ),
|
||||
( "load", "Load Game", "_load_screen", 'True'),
|
||||
( "mainmenu", "Main Menu", "_full_restart", 'not _at_main_menu' ),
|
||||
( "quit", "Quit", "_quit_screen", 'True' ),
|
||||
]
|
||||
# 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
|
||||
|
||||
# Transition that occurs when entering the game menu.
|
||||
library.enter_transition = None
|
||||
|
||||
# Transition that occurs when leaving the game menu.
|
||||
library.exit_transition = None
|
||||
|
||||
# True if the skip indicator should be shown.
|
||||
library.skip_indicator = True
|
||||
|
||||
@@ -77,12 +38,6 @@ init -500:
|
||||
# taking place.
|
||||
save_name = ''
|
||||
|
||||
# True if we're at the main menu, false otherwise.
|
||||
_at_main_menu = False
|
||||
|
||||
# True if we can save, false otherwise.
|
||||
_can_save = True
|
||||
|
||||
def _button_factory(label,
|
||||
type=None,
|
||||
selected=None,
|
||||
@@ -113,11 +68,11 @@ init -500:
|
||||
|
||||
style = type
|
||||
|
||||
if selected:
|
||||
if selected and not disabled:
|
||||
style += "_selected"
|
||||
|
||||
if disabled:
|
||||
style += "_disabled"
|
||||
clicked = None
|
||||
|
||||
style = style + "_button"
|
||||
text_style = style + "_text"
|
||||
@@ -208,399 +163,34 @@ label _hide_windows:
|
||||
return
|
||||
|
||||
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
label _start:
|
||||
|
||||
if renpy.has_label("splashscreen") and not _restart:
|
||||
call splashscreen
|
||||
|
||||
jump _main_menu
|
||||
|
||||
# This shows the main menu to the user.
|
||||
label _main_menu:
|
||||
|
||||
# Let the user completely override the main menu.
|
||||
if renpy.has_label("main_menu"):
|
||||
jump main_menu
|
||||
|
||||
label _library_main_menu:
|
||||
|
||||
scene
|
||||
|
||||
python hide:
|
||||
|
||||
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='mm_root_window')
|
||||
ui.fixed()
|
||||
|
||||
ui.window(style='mm_menu_window')
|
||||
ui.vbox()
|
||||
|
||||
for text, label in library.main_menu:
|
||||
_button_factory(text, "mm", clicked=ui.returns(label))
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
store._result = ui.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
$ renpy.jump(_result)
|
||||
|
||||
return
|
||||
|
||||
# Used to call the game menu.
|
||||
label _continue:
|
||||
$ _can_save = False
|
||||
$ _at_main_menu = True
|
||||
|
||||
$ renpy.call_in_new_context("_game_menu_load")
|
||||
|
||||
$ _can_save = True
|
||||
$ _at_main_menu = False
|
||||
|
||||
jump _library_main_menu
|
||||
|
||||
# Used to call the game menu.
|
||||
label _preferences:
|
||||
$ _can_save = False
|
||||
$ _at_main_menu = True
|
||||
|
||||
$ renpy.call_in_new_context("_game_menu_preferences")
|
||||
|
||||
$ _can_save = True
|
||||
$ _at_main_menu = False
|
||||
|
||||
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:
|
||||
|
||||
clicked = ui.jumps(target)
|
||||
disabled = False
|
||||
|
||||
if not eval(enabled):
|
||||
disabled = True
|
||||
clicked = ui.returns(None)
|
||||
|
||||
_button_factory(label, "gm_nav", selected=(key==selected),
|
||||
disabled=disabled, clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
def _game_interact():
|
||||
|
||||
return ui.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')
|
||||
|
||||
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) - 1) // library.file_page_length * library.file_page_length
|
||||
|
||||
if fpi < 0:
|
||||
fpi = 0
|
||||
|
||||
|
||||
# The length of a half-page of files.
|
||||
hfpl = library.file_page_length // 2
|
||||
|
||||
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.
|
||||
|
||||
# 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 * library.file_page_length
|
||||
tb(fpi != target, str(i + 1), ui.returns(("fpiset", target)))
|
||||
|
||||
# Next
|
||||
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
|
||||
|
||||
# Done with nav buttons.
|
||||
ui.close()
|
||||
|
||||
# This draws a single slot.
|
||||
def entry(offset):
|
||||
i = fpi + offset
|
||||
|
||||
name = str(i + 1)
|
||||
|
||||
if name not in saves:
|
||||
_render_new_slot(name, save)
|
||||
else:
|
||||
_render_savefile(name, saves[name], newest)
|
||||
|
||||
|
||||
# Actually draw a slot.
|
||||
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.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.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 _enter_game_menu:
|
||||
scene
|
||||
$ renpy.movie_stop()
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
|
||||
if library.enter_transition:
|
||||
$ renpy.transition(library.enter_transition)
|
||||
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)
|
||||
|
||||
return
|
||||
|
||||
|
||||
# Entry points from the game into menu-space.
|
||||
label _game_menu:
|
||||
label _game_menu_save:
|
||||
call _enter_game_menu
|
||||
jump _save_screen
|
||||
|
||||
label _game_menu_load:
|
||||
call _enter_game_menu
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu_preferences:
|
||||
call _enter_game_menu
|
||||
jump _prefs_screen
|
||||
|
||||
label _confirm_quit:
|
||||
call _enter_game_menu
|
||||
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:
|
||||
|
||||
if library.exit_transition:
|
||||
$ renpy.transition(library.exit_transition)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
+122
-26
@@ -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
|
||||
@@ -47,16 +52,16 @@ init -450:
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(_preferences, self.field)
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
for name, value in values:
|
||||
|
||||
def clicked(value=value):
|
||||
setattr(_preferences, self.field, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
_button_factory(name, "prefs",
|
||||
@@ -64,6 +69,88 @@ init -450:
|
||||
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:
|
||||
@@ -72,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', [
|
||||
@@ -90,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'),
|
||||
@@ -103,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:
|
||||
|
||||
@@ -118,17 +216,15 @@ label _prefs_screen:
|
||||
_game_nav("prefs")
|
||||
|
||||
ui.window(style='prefs_window')
|
||||
ui.grid(2, 1, xfill=True)
|
||||
ui.fixed()
|
||||
|
||||
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
|
||||
for i in library.left_preferences:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
|
||||
for i in library.right_preferences:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
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()
|
||||
|
||||
|
||||
+93
-40
@@ -64,10 +64,11 @@ init -1000:
|
||||
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',
|
||||
@@ -123,13 +124,18 @@ init -1000:
|
||||
# Styles that are used for menus.
|
||||
|
||||
style.create('menu', 'default',
|
||||
"(sound, position) The style that is used for menus themselves.")
|
||||
"(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.")
|
||||
|
||||
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
|
||||
@@ -175,9 +181,12 @@ init -1000:
|
||||
|
||||
# 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.')
|
||||
|
||||
@@ -201,6 +210,7 @@ init -1000:
|
||||
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.
|
||||
@@ -214,36 +224,20 @@ init -1000:
|
||||
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)
|
||||
style.disabled_button_text.activate_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.')
|
||||
@@ -265,6 +259,8 @@ init -1000:
|
||||
'(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.')
|
||||
@@ -286,13 +282,6 @@ init -1000:
|
||||
|
||||
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.')
|
||||
@@ -301,6 +290,7 @@ init -1000:
|
||||
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',
|
||||
@@ -310,24 +300,26 @@ init -1000:
|
||||
|
||||
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_nav_disabled_button', 'disabled_button',
|
||||
'(window, hover) The style that is used for disabled file picker navigation buttons.')
|
||||
style.create('file_picker_nav_disabled_button_text', 'disabled_button_text',
|
||||
'(text) The style that is used for the label of disabled 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))
|
||||
|
||||
@@ -335,7 +327,7 @@ init -1000:
|
||||
'(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',
|
||||
@@ -360,6 +352,7 @@ init -1000:
|
||||
'(text, position) The style used for the prompt in a yes/no dialog.')
|
||||
|
||||
style.yesno_label.color = green
|
||||
style.yesno_label.textalign = 0.5
|
||||
|
||||
style.create('yesno_button', 'button',
|
||||
'(window, hover) The style of yes/no buttons.')
|
||||
@@ -372,15 +365,17 @@ init -1000:
|
||||
|
||||
style.yesno_window.xfill = True
|
||||
style.yesno_window.yminimum = 0.5
|
||||
style.yesno_window.xmargin = .1
|
||||
|
||||
# Preferences
|
||||
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(window, position) The position of the box containing an individual preference.')
|
||||
'(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.')
|
||||
@@ -412,6 +407,41 @@ init -1000:
|
||||
|
||||
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.')
|
||||
@@ -419,3 +449,26 @@ init -1000:
|
||||
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)
|
||||
|
||||
|
||||
+19
-16
@@ -2,15 +2,6 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
@@ -25,17 +16,17 @@ import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
# Add the path to the module.
|
||||
sys.path.append("module")
|
||||
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
@@ -55,6 +46,9 @@ def 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.')
|
||||
|
||||
@@ -64,8 +58,15 @@ def main():
|
||||
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:
|
||||
|
||||
@@ -121,6 +122,8 @@ def main():
|
||||
|
||||
def memory_profile():
|
||||
|
||||
import renpy
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
|
||||
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.
+629
-259
File diff suppressed because it is too large
Load Diff
+26
-5
@@ -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)
|
||||
|
||||
@@ -99,6 +100,7 @@ def main():
|
||||
'example.html',
|
||||
'reference.html',
|
||||
'style.css',
|
||||
'RELEASING.txt',
|
||||
]
|
||||
|
||||
# Copy doc
|
||||
@@ -112,26 +114,45 @@ def main():
|
||||
copy_tree("common", target + "/common",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
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("~"))
|
||||
|
||||
def cp(x, license=""):
|
||||
copy_file(x, target + "/" + x)
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -103,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 = []
|
||||
|
||||
@@ -132,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)
|
||||
|
||||
+611
-117
@@ -590,13 +590,15 @@ overlaid on top of the screen.
|
||||
<rule>image_statement -> "image" image_name "=" python_expression</rule>
|
||||
|
||||
<p>
|
||||
The first display statement is the image statement, which does
|
||||
binds an image name with a displayable defining that
|
||||
image. As the list of name bindings, is never saved, the image
|
||||
statement can only appear inside of an init block. The most
|
||||
popular python expression to use here is Image, which takes as
|
||||
an argument an image filename to load. Another popular choice is
|
||||
Animation, which is defined elsewhere in this document.
|
||||
|
||||
The first display statement is the image statement, which does binds
|
||||
an image name with a displayable defining that image. As the list of
|
||||
image to name bindings is never saved, the image statement can only
|
||||
appear inside of an init block. The argument given here is passed to
|
||||
<a href="#">Image</a> in loose mode. Among other things, this means that if the
|
||||
argument is a single string, it is interpreted as the filename of an
|
||||
image. If another displayable (such as Animation) is given, it is
|
||||
passed through unchanged.
|
||||
|
||||
</p><p>
|
||||
|
||||
@@ -606,8 +608,8 @@ overlaid on top of the screen.
|
||||
|
||||
<example>
|
||||
init:
|
||||
image eileen happy = Image("eileen/happy.png")
|
||||
image eileen upset = Image("eileen/upset.png")
|
||||
image eileen happy = "eileen/happy.png"
|
||||
image eileen upset = "eileen/upset.png"
|
||||
</example>
|
||||
|
||||
<rule>show_statement -> "show" image_spec</rule>
|
||||
@@ -704,6 +706,18 @@ hide eileen
|
||||
most scenes needed in a visual novel type game.
|
||||
</p>
|
||||
|
||||
<h4>Programmatic Equivalents</h4>
|
||||
|
||||
<p>
|
||||
Each of the four statements given above has an equivalent python
|
||||
function, documented below.
|
||||
</p>
|
||||
|
||||
<!-- func renpy.image -->
|
||||
<!-- func renpy.scene -->
|
||||
<!-- func renpy.show -->
|
||||
<!-- func renpy.hide -->
|
||||
|
||||
<h4>Parameterized Images</h4>
|
||||
|
||||
<p>
|
||||
@@ -771,6 +785,8 @@ init:
|
||||
image eileen concerned = Image("9a_concerned.png")
|
||||
</example>
|
||||
|
||||
<!-- func ImageReference -->
|
||||
|
||||
<!-- func Solid -->
|
||||
|
||||
<!-- func Frame -->
|
||||
@@ -780,6 +796,41 @@ init:
|
||||
style.window.background = Frame("frame.png", 125, 25)
|
||||
</example>
|
||||
|
||||
<p>
|
||||
Frames are normally used in conjunction with styles, to provide
|
||||
the displayable that is the background for a window containing a
|
||||
menu or dialogue.
|
||||
</p>
|
||||
|
||||
<!-- func renpy.ParameterizedText -->
|
||||
|
||||
<p>
|
||||
This is used to implement the text image. The user may also want
|
||||
to instantiate their own ParameterizedText object (in an init
|
||||
block) if they want to have more than one bit of text on the
|
||||
screen at once, or if they want to change the style (and therefore
|
||||
the position) of that text.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
image text1 = renpy.ParameterizedText(ypos=0.25)
|
||||
|
||||
show text "centered."
|
||||
show text1 "1/4 of the way down the screen."
|
||||
</example>
|
||||
|
||||
|
||||
<h4>Animation Functions</h4>
|
||||
|
||||
<p>
|
||||
Occasionally, one will want an image that changes while on the
|
||||
screen. Ren'Py provides two ways of doing this. The Animation function
|
||||
creates an animation from a simple list of images, while the
|
||||
anim.SMAnimation function creates a more complex, state-machine
|
||||
controlled animation.
|
||||
</p>
|
||||
|
||||
<!-- func Animation -->
|
||||
|
||||
<example>
|
||||
@@ -788,12 +839,54 @@ init:
|
||||
"frame_2.png", 0.25,
|
||||
"frame_3.png", 0.25)
|
||||
</example>
|
||||
|
||||
|
||||
<p>
|
||||
Frames are normally used in conjunction with styles, to provide
|
||||
the displayable that is the background for a window containing a
|
||||
menu or dialogue.
|
||||
</p>
|
||||
<!-- func anim.SMAnimation -->
|
||||
|
||||
<!-- func anim.State -->
|
||||
|
||||
<!-- func anim.Edge -->
|
||||
|
||||
<p>
|
||||
We present two examples of this in action. The first shows how one can
|
||||
create a character that ocassionally, randomly, performs a 3-frame
|
||||
blink about once a minute.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
image blinking = anim.SMAnimation("a",
|
||||
anim.State("a", "eyes_open.png"),
|
||||
|
||||
# This edge keeps us showing the eyes open for a second.
|
||||
anim.Edge("a", 1.0, "a", prob=60),
|
||||
|
||||
# This edge causes the eyes to start closing...
|
||||
anim.Edge("a", 0.25, "b"),
|
||||
|
||||
# ..because it brings us here.
|
||||
anim.State("b", "eyes_half.png"),
|
||||
|
||||
# And so on...
|
||||
anim.Edge("b", 0.25, "c"),
|
||||
anim.State("c", "eyes_closed.png"),
|
||||
anim.Edge("c", 0.25, "d"),
|
||||
anim.State("d", "eyes_half.png"),
|
||||
|
||||
# And back to a.
|
||||
anim.Edge("d", 0.5, "a")
|
||||
)
|
||||
</example>
|
||||
|
||||
<p>
|
||||
Remember, State can take a Position, and Edge can take a
|
||||
transition. This lets you move things around the screen, dissolve
|
||||
images into others, and do all sorts of complicated, unexpected,
|
||||
things. (But be careful... not all transitions do what you'd expect
|
||||
when used with SMAnimation.)
|
||||
</p>
|
||||
|
||||
<h4>Position and Motion Functions</h4>
|
||||
|
||||
<function name="Position" sig="(**properties)">
|
||||
|
||||
@@ -819,34 +912,49 @@ init:
|
||||
show eileen happy at left
|
||||
</example>
|
||||
|
||||
<!-- func renpy.ParameterizedText -->
|
||||
<function name="Motion" sig="(function, period, repeat=False, bounce=False, **properties)">
|
||||
|
||||
<p>
|
||||
Motion, when given the appropriate arguments, returns an object that
|
||||
when given as the at clause of an image causes an image to be moved on
|
||||
the screen. Function is a function that, when given a number between 0
|
||||
and 1, returns two or four values. The first two values are
|
||||
interpreted as the xpos and the ypos of the motion. (Please note that
|
||||
if these values are floating point numbers, they are interpreted as a
|
||||
fraction of the screen. If they are integers, they are interpreted as
|
||||
the absolute position of the anchor of the motion.) If four values are
|
||||
returned, the third and fourth values are interpreted as an xanchor
|
||||
and yanchor.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is used to implement the text image. The user may also want
|
||||
to instantiate their own ParameterizedText object (in an init
|
||||
block) if they want to have more than one bit of text on the
|
||||
screen at once, or if they want to change the style (and therefore
|
||||
the position) of that text.
|
||||
</p>
|
||||
<p>
|
||||
Please note that the function may be pickeled, which means that it
|
||||
cannot be an inner function or a lambda, but must be a function
|
||||
defined in an init block of your script. In general, it's better to
|
||||
use a Pan or a Move, rather than defining your own motion.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
image text1 = renpy.ParameterizedText(ypos=0.25)
|
||||
<p>
|
||||
Period is the time, in seconds, it takes to complete one cycle of
|
||||
a motion. If repeat is True, then the cycle repeats when it finishes,
|
||||
if False, the motion stops after one period. If bounce is True, the
|
||||
argument to the function goes from 0 to 1 to 0 in a single period, if
|
||||
False, it goes from 0 to 1.
|
||||
</p>
|
||||
|
||||
show text "centered."
|
||||
show text1 "1/4 of the way down the screen."
|
||||
</example>
|
||||
</function>
|
||||
|
||||
<function name="Pan" sig="(startpos, endpos, time)">
|
||||
<function name="Pan" sig="(startpos, endpos, time, repeat=False, bounce=False, **properties)">
|
||||
|
||||
<p>
|
||||
|
||||
Pan, when given the appropriate arguments, gives an object that
|
||||
can be passed to the at clause of that image to cause the image to
|
||||
can be passed to the at clause of an image to cause the image to
|
||||
be panned on the screen. The parameters startpos and endpos are
|
||||
tuples, containing the x and y coordinates of the upper-left hand
|
||||
corner of the screen relative to the image. Time is the time it
|
||||
will take this position to move from startpos to endpos.
|
||||
will take this position to move from startpos to endpos. Repeat
|
||||
and bounce are as for Motion.
|
||||
|
||||
</p><p>
|
||||
|
||||
@@ -884,7 +992,7 @@ scene marspan at Pan((0, 0), (1600, 0), 10.0)
|
||||
|
||||
</function>
|
||||
|
||||
<function name="Move" sig="(startpos, endpos, time, **properties)">
|
||||
<function name="Move" sig="(startpos, endpos, time, repeat=False, bounce=False, **properties)">
|
||||
|
||||
<p>
|
||||
Move is similar to Pan, insofar as it involves moving
|
||||
@@ -899,7 +1007,9 @@ scene marspan at Pan((0, 0), (1600, 0), 10.0)
|
||||
position to the ending position, and extra position
|
||||
properties. The positions are given as tuples containing xpos
|
||||
and ypos properties. The positions may be integer or floating
|
||||
point, but it's not permissable to mix the two.
|
||||
point, but it's not permissable to mix the two. Repeat and
|
||||
bounce are as for Motion.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -916,9 +1026,24 @@ show ball at Move((0.0, 0.0), (1.0, 1.0), 10.0,
|
||||
|
||||
<p>
|
||||
In general, one wants to use Pan when an image is bigger than the
|
||||
screen, and Move when it is smaller.
|
||||
screen, and Move when it is smaller. Both Pan and Move are special
|
||||
cases of Motion.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
anim.SMAnimation can also be used to declare complicated motions. Use
|
||||
None instead of an image in States, and supply a move transition
|
||||
when moving between states. A SMAnimation so created can be passed in
|
||||
to the at clause of an image, allowing it to move things around the
|
||||
screen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
These movement clauses can also be used as transitions, in which case
|
||||
they affect the position of a single layer or the entire screen, as
|
||||
appropriate.
|
||||
</p>
|
||||
|
||||
<h3>Transitions</h3>
|
||||
|
||||
<p>
|
||||
@@ -1086,8 +1211,14 @@ begins.
|
||||
|
||||
<!-- func Dissolve -->
|
||||
|
||||
<!-- func ImageDissolve -->
|
||||
|
||||
<!-- func CropMove -->
|
||||
|
||||
<!-- func Pixellate -->
|
||||
|
||||
<!-- func MoveTransition -->
|
||||
|
||||
<p>
|
||||
Some transitions can also be applied to specific layers, using the
|
||||
renpy.transition function (documented below). Only transitions that
|
||||
@@ -1343,8 +1474,7 @@ menu:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
# Toggle fullscreen mode.
|
||||
$ config.fullscreen = not config.fullscreen
|
||||
$ score += 1
|
||||
|
||||
# Pointless python that uses a loop.
|
||||
python:
|
||||
@@ -1371,6 +1501,14 @@ python:
|
||||
the game directory. Otherwise, the default game directory "game"
|
||||
is used.
|
||||
|
||||
</p><p>
|
||||
|
||||
It next checks to see if the file "presplash.png" is present in the
|
||||
game directory. If this file is present, it is shown to the user, in
|
||||
its own window, until just after the init code has finished
|
||||
running. The size of the window is determined by the size of the
|
||||
presplash image, and we attempt to center this window on the screen.
|
||||
|
||||
</p><p>
|
||||
|
||||
It then tries tries to parse all the .rpy files in the game
|
||||
@@ -1722,16 +1860,28 @@ e "Pleased to meet you, %(name)s."
|
||||
|
||||
<!-- func renpy.watch -->
|
||||
|
||||
<!-- func renpy.windows -->
|
||||
<!-- func renpy.loadable -->
|
||||
|
||||
<!-- func renpy.exists -->
|
||||
|
||||
|
||||
<!-- func renpy.clear_game_runtime -->
|
||||
|
||||
<!-- func renpy.get_game_runtime -->
|
||||
|
||||
|
||||
<!-- func color -->
|
||||
|
||||
<p>
|
||||
Contexts store the current scene lists and execution location. Ren'Py
|
||||
supports a stack of contexts, but only the top-level context is saved
|
||||
to the save file.
|
||||
</p>
|
||||
|
||||
<!-- func renpy.context -->
|
||||
|
||||
<!-- func renpy.call_in_new_context -->
|
||||
|
||||
<!-- func renpy.invoke_in_new_context -->
|
||||
|
||||
<p>
|
||||
Finally, there exists an object named renpy.random. This object is
|
||||
a random number generator that implements the <a
|
||||
@@ -2020,10 +2170,11 @@ init:
|
||||
stdout (wherever that may go to).
|
||||
</var>
|
||||
|
||||
<var name="config.image_cache_size" value="10">
|
||||
This is the number of images that can be in the image cache at
|
||||
once. This is mostly a suggestion, as if Ren'Py ever needs to
|
||||
display more than this number of images at once, it will cache
|
||||
<var name="config.image_cache_size" value="8">
|
||||
|
||||
This is used to set the size of the image cache, as a multiple of the
|
||||
screen size. This is mostly a suggestion, as if Ren'Py ever needs to
|
||||
display more than this amount of images at once, it will cache
|
||||
them all. But it will stop predictively loading images when the
|
||||
cache is filled beyond this, and it will try to drop images from
|
||||
the cache until it shrinks below this size. If this is too
|
||||
@@ -2047,6 +2198,11 @@ init:
|
||||
the image cache change.
|
||||
</var>
|
||||
|
||||
<var name="config.load_before_transitions" value="True">
|
||||
If True, the start of transitions will be delayed until the images
|
||||
used by said transitions have finished loading.
|
||||
</var>
|
||||
|
||||
<var name="config.allow_skipping" value="True">
|
||||
If set to False, the user is not able to skip over the text of the
|
||||
game.
|
||||
@@ -2079,9 +2235,9 @@ load scripts, as scripts will be loaded before it can be set.
|
||||
is slightly increased.
|
||||
</var>
|
||||
|
||||
<var name="config.keymouse_distance" value="5">
|
||||
This gives the number of pixels that the mouse is moved every
|
||||
1/20th of a second when the keymouse is being used.
|
||||
<var name="config.focus_crossrange_penalty" value="1024">
|
||||
This is the amount of penalty to apply to moves perpendicular to the
|
||||
selected direction of motion, when moving focus with the keyboard.
|
||||
</var>
|
||||
|
||||
<var name="config.sound_sample_rate" value="44100">
|
||||
@@ -2096,14 +2252,6 @@ load scripts, as scripts will be loaded before it can be set.
|
||||
list. See the section on overlays for more.
|
||||
</var>
|
||||
|
||||
<var name="config.annoying_text_cps" value="None">
|
||||
If not None, dialogue and thoughts will be "typed" onto the
|
||||
screen a few characters at a time. The value of this is the
|
||||
number of characters that will be typed onto the screen in a
|
||||
second. I strongly reccomend keeping this None. Not only does
|
||||
this interact poorly with the rest of Ren'Py, it's annoying.
|
||||
</var>
|
||||
|
||||
<var name="config.fade_music" value="0.0">
|
||||
This is the amount of time in seconds to spend fading the old track out
|
||||
before a new music track starts. This should probably be fairly
|
||||
@@ -2145,16 +2293,64 @@ cleared before the overlay functions are called. "overlay" should
|
||||
always be in this list.
|
||||
</var>
|
||||
|
||||
<var name="config.top_layers" value="[ ]" >
|
||||
This is a list of names of layers that are displayed above all other
|
||||
layers, and do not participate in a transition that is applied to all
|
||||
layers. If a layer name is listed here, it should not be listed in
|
||||
config.layers.
|
||||
</var>
|
||||
|
||||
<var name="config.overlay_during_wait" value="True" >
|
||||
True if we want overlays to be shown during wait statements, or False
|
||||
if we'd prefer that they be hidden during the wait statements.
|
||||
</var>
|
||||
|
||||
<var name="library.file_page_length" value="10">
|
||||
This is the number of save slots that are shown in the picker
|
||||
that's used by the load and save portions of the game menu. This
|
||||
should probably be an even number.
|
||||
</var>
|
||||
<var name="config.enable_fast_dissolve" value="True" >
|
||||
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.
|
||||
</var>
|
||||
|
||||
<var name="config.text_tokenizer" value="..." >
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p><p>
|
||||
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:
|
||||
</p>
|
||||
<ul>
|
||||
|
||||
<li>"newline" -- A newline, which when encountered starts a new line.</li>
|
||||
|
||||
<li>"word" -- A word of text. A line will never be broken inside of
|
||||
a word.</li>
|
||||
|
||||
<li>"space" -- A space. Spaces are always placed on the current line,
|
||||
and will never be placed as the start of a line.</li>
|
||||
|
||||
<li>"tag" -- A text tag. If encountered, the second element should be
|
||||
the name of the tag, without any enclosing braces.</li>
|
||||
</ul>
|
||||
</var>
|
||||
|
||||
<var name="library.file_page_cols" value = "2" >
|
||||
This is the number of columns of save slots that are show in the
|
||||
picker that's used in the load and save screens of the game
|
||||
menu.
|
||||
</var>
|
||||
|
||||
<var name="library.file_page_rows" value = "5" >
|
||||
This is the number of rows of save slots that are show in the
|
||||
picker that's used in the load and save screens of the game
|
||||
menu.
|
||||
</var>
|
||||
|
||||
<var name="library.file_quick_access_pages" value="5">
|
||||
The number of pages of the file picker to provide quick access
|
||||
@@ -2178,14 +2374,14 @@ the file picker.
|
||||
thumbnail is shown to the user.
|
||||
</var>
|
||||
|
||||
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("Preferences", "_preferences"), ("Quit Game", "_quit") ]'>
|
||||
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", ... ), ("Preferences", ... ), ("Quit Game", ...) ]'>
|
||||
This is used to give the main menu that is shown to the user
|
||||
when the game first starts. It is a list of tuples, where the
|
||||
first element of each tuple is the title of the menu button, and
|
||||
the second element is a label that we jump to when that button
|
||||
is selected. Some useful labels to use here are "_continue",
|
||||
which brings up the game menu in load mode, "_preferences", which brings up the preferences screen,
|
||||
and "_quit", which terminates Ren'Py.
|
||||
is selected. (This jump exits the context in which the start
|
||||
menu executes.) The second element may also be a function, in
|
||||
which case it is called when the item is selected.
|
||||
</var>
|
||||
|
||||
<var name="library.has_music" value="True">
|
||||
@@ -2200,6 +2396,11 @@ the file picker.
|
||||
game does not have any sound effects.
|
||||
</var>
|
||||
|
||||
<var name="library.has_cps" value="True">
|
||||
If True, the preference for changing the text speed is shown to the
|
||||
user.
|
||||
</var>
|
||||
|
||||
<var name="library.has_transitions" value="True">
|
||||
If True, the preference for enabling and disabling transitions
|
||||
is presented to the user. This should be set to False if the
|
||||
@@ -2520,7 +2721,9 @@ top_padding and bottom_padding to the same value.
|
||||
|
||||
<prop name="xanchor">
|
||||
Controls the placement of the anchor within the widget, in the x
|
||||
dimension. This can be one of 'left', 'center', or 'right'.
|
||||
dimension. This can be one of 'left', 'center', or 'right', or a
|
||||
number between 0 and 1, where 0 is the left edge and 1 is the
|
||||
right edge.
|
||||
</prop>
|
||||
|
||||
<prop name="ypos">
|
||||
@@ -2532,9 +2735,23 @@ top_padding and bottom_padding to the same value.
|
||||
|
||||
<prop name="yanchor">
|
||||
Controls the placement of the anchor within the widget, in the y
|
||||
dimension. This can be one of 'top', 'center', or 'bottom'.
|
||||
dimension. This can be one of 'top', 'center', or 'bottom', or a
|
||||
number between 0 and 1, where 0 is the top edge and 1 is the
|
||||
bottom edge.
|
||||
</prop>
|
||||
|
||||
<prop name="xmaximum">
|
||||
If not None, this property gives the maximum width of this widget.
|
||||
(Most widgets respect this, but some have a fixed size which this
|
||||
does not change.)
|
||||
</prop>
|
||||
|
||||
<prop name="ymaximum">
|
||||
If not None, this property gives the maximum height of this widget.
|
||||
(Most widgets respect this, but some have a fixed size which this
|
||||
does not change.)
|
||||
</prop>
|
||||
|
||||
<h4>Sound Properties</h4>
|
||||
|
||||
<p>
|
||||
@@ -2560,12 +2777,7 @@ top_padding and bottom_padding to the same value.
|
||||
|
||||
<p>
|
||||
The ui.bar() widget has a few properties that are specific to
|
||||
it. These properties control the look of the bars, and the size of
|
||||
the gutters on either side of a clickable widget. The gutters are
|
||||
empty space on the left and right of a clickable bar, and serve to
|
||||
make it easy to set the maximum and minimum value of the bar
|
||||
widget. Clicking on the left gutter sets the minimum value, while the
|
||||
right gutter sets the maximum value.
|
||||
it, that control the look of the bars.
|
||||
</p>
|
||||
|
||||
<prop name="left_bar">
|
||||
@@ -2580,29 +2792,22 @@ Displayable needs to be one that can change its size, such as a Solid
|
||||
or a Frame.
|
||||
</prop>
|
||||
|
||||
<prop name="left_gutter">
|
||||
The size of the left gutter of a clickable bar, in pixels.
|
||||
</prop>
|
||||
|
||||
<prop name="right_gutter">
|
||||
The size of the left gutter of a clickable bar, in pixels.
|
||||
</prop>
|
||||
|
||||
|
||||
|
||||
<h4>Hovering</h4>
|
||||
|
||||
<p>
|
||||
In Ren'Py, buttons and their contents support the idea of hovering:
|
||||
using different sets of properties for different states of the
|
||||
widget. If the widget has been selected, it is considered to be
|
||||
activated. Otherwise, if the mouse is over the widget, then that widget
|
||||
is hovered, else it is idle. On these widgets, Ren'Py will look up
|
||||
a property prefixed with "activate_", "hover_", "idle_", (as appropriate) in a
|
||||
style, before looking up an unprefixed property. If neither form
|
||||
is found on a style, the process is repeated again on the parent
|
||||
style. (The search pattern for background on a hovered button
|
||||
goes: button.hover_background, button.background,
|
||||
In Ren'Py, buttons and their contents support the idea of
|
||||
hovering: using different sets of properties for different states
|
||||
of the widget. If the widget cannot be given focus, it is
|
||||
considered to be insensitive. If the widget has been selected by
|
||||
the user, it is considered to be activated. Otherwise, if the
|
||||
widget has focus, then that widget is hovered, else it is idle. On
|
||||
these widgets, Ren'Py will look up a property prefixed with
|
||||
"insensitive_", "activate_", "hover_", or "idle_" (as appropriate)
|
||||
in a style, before looking up an unprefixed property. If neither
|
||||
form is found on a style, the process is repeated again on the
|
||||
parent style. (The search pattern for background on a hovered
|
||||
button goes: button.hover_background, button.background,
|
||||
default.hover_background, default.background.)
|
||||
</p>
|
||||
|
||||
@@ -2655,7 +2860,7 @@ init:
|
||||
init:
|
||||
$ style.button.color = (128, 255, 128, 255)
|
||||
$ style.button.xpos = 0
|
||||
$ style.button_idled.background = \
|
||||
$ style.button.idle_background = \
|
||||
renpy.Frame(renpy.Image("button_idled.png"),
|
||||
xborder=10, yborder=10)
|
||||
$ style.button_hover.background = \
|
||||
@@ -2954,31 +3159,26 @@ Ren'Py, and as of version 4.5 is as follows:
|
||||
|
||||
<example>
|
||||
keymap = dict(
|
||||
|
||||
# Bindings present almost everywhere, unless explicitly
|
||||
# disabled.
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mousedown_3' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
keymouse_right = [ 'K_RIGHT' ],
|
||||
keymouse_up = [ 'K_UP' ],
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouseup_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 = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
@@ -2986,12 +3186,6 @@ keymap = dict(
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouseup_1' ],
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
@@ -3079,6 +3273,18 @@ changed by a call to ui.layer(), followed by a matching call to
|
||||
ui.close() when done adding widgets to the new layer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Widgets can be added to a focus group by supplying the name of the
|
||||
focus group as the focus parameter to a ui widget. If the same focus
|
||||
group exists on two interactions, Ren'Py will ensure that if the nth
|
||||
widget is selected in the first interaction, the nth widget will be
|
||||
given focus at the start of the second interaction. If no widget is
|
||||
given focus in this way, then Ren'Py looks for one that has had the
|
||||
default=True argument supplied to it. If it finds such a widget, then
|
||||
it focuses it. Otherwise, it uses fallback rules to determine if a
|
||||
widget should be given focus.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following are the functions available in the ui module, which is
|
||||
automatically present in the game namespace.
|
||||
@@ -3118,12 +3324,12 @@ automatically present in the game namespace.
|
||||
|
||||
<!-- func ui.bar -->
|
||||
|
||||
<!-- func ui.null -->
|
||||
|
||||
<!-- func ui.saybehavior -->
|
||||
|
||||
<!-- func ui.pausebehavior -->
|
||||
|
||||
<!-- func ui.keymousebehavior -->
|
||||
|
||||
<h4>Functions</h4>
|
||||
|
||||
<!-- func ui.interact -->
|
||||
@@ -3145,12 +3351,294 @@ various button widgets.
|
||||
This function returns a function that, when called, jumps the game to
|
||||
the given label, ending the current interaction in the process. It's
|
||||
best used to supply the clicked argument to the various button
|
||||
widgets. (Most of the time, you want to use ui.returns and a series of
|
||||
if statements to jump or call the appropriate label, rather than
|
||||
relying on this function.)
|
||||
widgets.
|
||||
</p>
|
||||
</function>
|
||||
|
||||
<function name="ui.jumpsoutofcontext" sig="(label)">
|
||||
<p>
|
||||
This function returns a function that, when called, 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.
|
||||
</p>
|
||||
</function>
|
||||
|
||||
<h3>Image Manipulators</h3>
|
||||
|
||||
<p>
|
||||
Image manipulators are objects that can return images. Every image
|
||||
manipulator can be used as a widget, but the opposite is not
|
||||
true. Image manipulators are used to implement the Image function,
|
||||
but constructing them directly can give you finer control.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When an image manipulator requires another as input, the second image
|
||||
manipulator can be specified in any of a number of ways. The first way
|
||||
is as a string, which is turned into an im.Image. The second is a
|
||||
tuple, which is turned into an im.Composite using the rules for tuples
|
||||
given in Image. The third is that an image manipulator can be
|
||||
specified directly, in which case it is passed through unchanged.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Widgets may not be used as arguments to image manipulators. This is
|
||||
because image manipulators are very efficent, caching the computed
|
||||
images on their first load.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The image manipulators are:
|
||||
</p>
|
||||
|
||||
<!-- func im.Image -->
|
||||
|
||||
<!-- func im.Crop -->
|
||||
|
||||
<!-- func im.Composite -->
|
||||
|
||||
<!-- func im.Scale -->
|
||||
|
||||
<!-- func im.Rotozoom -->
|
||||
|
||||
<!-- func im.Alpha -->
|
||||
|
||||
<!-- func im.Map -->
|
||||
|
||||
<!-- func im.ramp -->
|
||||
|
||||
<!-- func im.Tile -->
|
||||
|
||||
<h3>Low-Level Audio Functions</h3>
|
||||
|
||||
<p>
|
||||
These functions manipulate Ren'Py audio subsystem directly. Unlike the
|
||||
high-level music functions, these functions do not manipulate
|
||||
persistent state. As a result, calls to these functions will not
|
||||
persistent across sessions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Still, these functions are useful to allow access to behavior that
|
||||
is not yet exposed through the high-level interface. They are also
|
||||
useful for a person who wants to change the high-level music or sound
|
||||
interfaces. So, we've documented them here.
|
||||
</p>
|
||||
|
||||
<h4>Sound Functions</h4>
|
||||
|
||||
<!-- func audio.sound_enabled -->
|
||||
|
||||
<!-- func audio.sound_play -->
|
||||
|
||||
<!-- func audio.sound_stop -->
|
||||
|
||||
<h4>Music Functions</h4>
|
||||
|
||||
<p>
|
||||
The low-level music system deals with two music files at once, a
|
||||
playing file and a queued file. When the playing file finishes
|
||||
playing, the queued file is begun, and queued file becomes
|
||||
empty.
|
||||
</p>
|
||||
|
||||
<!-- func audio.music_enabled -->
|
||||
|
||||
<!-- func audio.music_play -->
|
||||
|
||||
<!-- func audio.music_queue -->
|
||||
|
||||
<!-- func audio.music_stop -->
|
||||
|
||||
<!-- func audio.music_fadeout -->
|
||||
|
||||
<!-- func audio.music_fading -->
|
||||
|
||||
<!-- func audio.music_filenames -->
|
||||
|
||||
<!-- func audio.music_pause -->
|
||||
|
||||
<!-- func audio.music_unpause -->
|
||||
|
||||
<h4>Music Callbacks</h4>
|
||||
|
||||
<var name="config.interact_callbacks" value="...">
|
||||
A list of callbacks that are called (without any arguments) at the start (or
|
||||
restart) of each interaction. It's intended to allow the system to
|
||||
restore music after a context change, or to implement changes in
|
||||
the music that occured before that interaction. It will also help to
|
||||
support voice.
|
||||
</var>
|
||||
|
||||
<var name="config.music_end_event" value="...">
|
||||
This callback is called (without any arguments) when a track has
|
||||
finished playing. If a second track has been queued, it will have
|
||||
started before this callback is called. Usually, this callback is
|
||||
used to queue up another track when one ends. It can also be used
|
||||
to start the music playing again after a fadeout.
|
||||
</var>
|
||||
|
||||
|
||||
<h3>Default Definitions</h3>
|
||||
|
||||
<p>
|
||||
This section documents a number of definitions that are found in the
|
||||
Ren'Py standard library. These include default instatiations of the
|
||||
various transitions and placements, and even one image.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
These definitions are found in common/definitions.rpy. You
|
||||
shouldn't change them there, but should instead copy the corresponding
|
||||
line into your script file, and change that instead.
|
||||
</p>
|
||||
|
||||
<h4>Positions</h4>
|
||||
|
||||
<dl>
|
||||
|
||||
<defn name="left" type="position">
|
||||
A Position in which the left side of the image is aligned with the
|
||||
left side of the screen.
|
||||
</defn>
|
||||
|
||||
<defn name="right" type="position">
|
||||
A position in which the right side of the image is aligned with the
|
||||
right side of the screen.
|
||||
</defn>
|
||||
|
||||
<defn name="center" type="position">
|
||||
A position in which the image is centered horizontally on the screen.
|
||||
</defn>
|
||||
|
||||
<defn name="offscreenleft" type="position">
|
||||
A position in which the image is placed just off the left side of the
|
||||
screen. Please note that an image placed in this position, while not
|
||||
visible to the user, still consumes resources, and so images should be
|
||||
hidden when not visible. This position is
|
||||
intended to be used with the move transition.
|
||||
</defn>
|
||||
|
||||
<defn name="offscreenright" type="position">
|
||||
A position in which the image is placed just off the right side of the
|
||||
screen. Please note that an image placed in this position, while not
|
||||
visible to the user, still consumes resources, and so images should be
|
||||
hidden when not visible. This position is
|
||||
intended to be used with the move transition.
|
||||
</defn>
|
||||
|
||||
</dl>
|
||||
|
||||
<h4>Transitions</h4>
|
||||
|
||||
<dl>
|
||||
|
||||
<defn name="fade" type="transition">
|
||||
An instance of the Fade transition that takes 0.5 seconds to fade
|
||||
to black, and then 0.5 seconds to fade to the new screen.
|
||||
</defn>
|
||||
|
||||
<defn name="dissolve" type="transition">
|
||||
An instance of the Dissolve transition that takes 0.5 seconds to complete.
|
||||
</defn>
|
||||
|
||||
<defn name="pixellate" type="transition">
|
||||
An instance of the Pixellate transition, which takes 1 second to
|
||||
complete, and creates pixels as big as 32x32 over the course of 5
|
||||
steps in either direction.
|
||||
</defn>
|
||||
|
||||
<defn name="move" type="transition">
|
||||
An instance of the MoveTransition transition, this takes 0.5 seconds
|
||||
to move images that changed position to their new locations.
|
||||
</defn>
|
||||
|
||||
<defn name="vpunch" type="transition">
|
||||
When invoked, this transition shakes the screen vertically for a
|
||||
quarter second.
|
||||
</defn>
|
||||
|
||||
<defn name="hpunch" type="transition">
|
||||
When invoked, this transition shakes the screen horizontally for a
|
||||
quarter second.
|
||||
</defn>
|
||||
|
||||
<defn name="blinds" type="transition">
|
||||
Transitions the screen in a vertical blinds effect lasting 1 second.
|
||||
</defn>
|
||||
|
||||
<defn name="squares" type="transition">
|
||||
Transitions the screen in a vertical blinds effect lasting 1 second.
|
||||
</defn>
|
||||
|
||||
<defn name="wiperight" type="transition">
|
||||
An instance of CropMove that takes 1 second to wipe the screen right.
|
||||
</defn>
|
||||
|
||||
<defn name="wipeleft" type="transition">
|
||||
An instance of CropMove that takes 1 second to wipe the screen left.
|
||||
</defn>
|
||||
|
||||
<defn name="wipeup" type="transition">
|
||||
An instance of CropMove that takes 1 second to wipe the screen up.
|
||||
</defn>
|
||||
|
||||
<defn name="wipedown" type="transition">
|
||||
An instance of CropMove that takes 1 second to wipe the screen down.
|
||||
</defn>
|
||||
|
||||
<defn name="slideright" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen right.
|
||||
</defn>
|
||||
|
||||
<defn name="slideleft" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen left.
|
||||
</defn>
|
||||
|
||||
<defn name="slideup" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen up.
|
||||
</defn>
|
||||
|
||||
<defn name="slidedown" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen down.
|
||||
</defn>
|
||||
|
||||
<defn name="slideawayright" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen away and to the right.
|
||||
</defn>
|
||||
|
||||
<defn name="slideawayleft" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen away and to the left.
|
||||
</defn>
|
||||
|
||||
<defn name="slideawayup" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen away and to the up.
|
||||
</defn>
|
||||
|
||||
<defn name="slideawaydown" type="transition">
|
||||
An instance of CropMove that takes 1 second to slide the screen away and to the down.
|
||||
</defn>
|
||||
|
||||
<defn name="irisout" type="transition">
|
||||
An instance of CropMove that irises the screen out for 1 second.
|
||||
</defn>
|
||||
|
||||
<defn name="irisin" type="transition">
|
||||
An instance of CropMove that irises the screen in for 1 second.
|
||||
</defn>
|
||||
|
||||
</dl>
|
||||
|
||||
<h4>Images</h4>
|
||||
|
||||
<dl>
|
||||
|
||||
<defn name="black" type="image">
|
||||
This is a predefinition of a Solid black image. It's here to serve
|
||||
as a reasonable default image, for use in tutorials and incomplete games.
|
||||
</defn>
|
||||
|
||||
</dl>
|
||||
|
||||
<h3>Function Index</h3>
|
||||
|
||||
@@ -3168,6 +3656,10 @@ relying on this function.)
|
||||
|
||||
<styleindex/>
|
||||
|
||||
<h3>Definition Index</h3>
|
||||
|
||||
<defnindex />
|
||||
|
||||
<h3>Example Script</h3>
|
||||
|
||||
<p>
|
||||
@@ -3179,4 +3671,6 @@ relying on this function.)
|
||||
|
||||
</doc>
|
||||
|
||||
<!-- (define-key xml-mode-map [(control return)] 'tompy-xml-ctrlret) -->
|
||||
<!-- (define-key xml-mode-map [(control return)] 'tompy-xml-ctrlret) -->
|
||||
<!-- -->
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
+1
-6
@@ -11,11 +11,6 @@ init 1:
|
||||
$ style.button_text.size = 20
|
||||
$ style.file_picker_text.size = 14
|
||||
|
||||
# Perhaps change the fudge factor on windows, if line spacing
|
||||
# looks weird.
|
||||
if renpy.windows():
|
||||
$ style.default.line_height_fudge = -4
|
||||
|
||||
$ style.file_picker_entry.xminimum = 320
|
||||
# Change the size of the thumbnails in the file picker.
|
||||
$ library.thumbnail_width = 60
|
||||
$ library.thumbnail_height = 45
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
|
||||
|
||||
+14
-34
@@ -115,7 +115,6 @@ init -100:
|
||||
|
||||
# Save the old character object.
|
||||
readback_OldCharacter = Character
|
||||
readback_OldDynamicCharacter = DynamicCharacter
|
||||
readback_oldmenu = menu
|
||||
|
||||
class Character(readback_OldCharacter):
|
||||
@@ -128,41 +127,22 @@ init -100:
|
||||
self.readback_style = readback_style
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
readback_OldCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, what)
|
||||
|
||||
def readback(self, what):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
|
||||
|
||||
class DynamicCharacter(readback_OldDynamicCharacter):
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldDynamicCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
name = renpy.renpy.python.py_eval(self.name_expr)
|
||||
readback_OldDynamicCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, name, what)
|
||||
|
||||
def readback(self, name, what):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
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):
|
||||
@@ -179,7 +159,7 @@ init -100:
|
||||
say = Sayer()
|
||||
|
||||
def readback(what):
|
||||
readback_save(narrator, what)
|
||||
narrator.store_readback(None, what)
|
||||
|
||||
def menu(menuitems):
|
||||
rv = readback_oldmenu(menuitems)
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
+10
-4
@@ -2,9 +2,9 @@
|
||||
# order.
|
||||
|
||||
# Some version numbers and things.
|
||||
version = "Ren'Py 4.7.2"
|
||||
script_version = 7
|
||||
savegame_suffix = "-7.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
|
||||
@@ -25,6 +25,8 @@ import renpy.script
|
||||
import renpy.style
|
||||
|
||||
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.audio
|
||||
@@ -32,10 +34,14 @@ 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.
|
||||
|
||||
+95
-68
@@ -30,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.
|
||||
@@ -120,6 +127,12 @@ def say_menu_with(expression):
|
||||
|
||||
class Say(Node):
|
||||
|
||||
__slots__ = [
|
||||
'who',
|
||||
'what',
|
||||
'with',
|
||||
]
|
||||
|
||||
def __init__(self, loc, who, what, with):
|
||||
|
||||
super(Say, self).__init__(loc)
|
||||
@@ -142,6 +155,11 @@ class Say(Node):
|
||||
|
||||
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
|
||||
@@ -304,6 +297,10 @@ def predict_imspec(imspec, callback):
|
||||
|
||||
class Show(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imspec',
|
||||
]
|
||||
|
||||
def __init__(self, loc, imspec):
|
||||
"""
|
||||
@param imspec: A triple consisting of an image name (itself a
|
||||
@@ -317,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
|
||||
|
||||
@@ -331,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
|
||||
@@ -345,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
|
||||
|
||||
@@ -368,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
|
||||
@@ -381,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.
|
||||
@@ -411,6 +411,11 @@ class With(Node):
|
||||
|
||||
class Call(Node):
|
||||
|
||||
__slots__ = [
|
||||
'label',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, label, expression):
|
||||
|
||||
super(Call, self).__init__(loc)
|
||||
@@ -433,6 +438,8 @@ class Call(Node):
|
||||
|
||||
class Return(Node):
|
||||
|
||||
__slots__ = [ ]
|
||||
|
||||
# No __init__ needed.
|
||||
|
||||
# We don't care what the next node is.
|
||||
@@ -451,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)
|
||||
|
||||
@@ -509,6 +522,11 @@ class Menu(Node):
|
||||
# instead.
|
||||
class Jump(Node):
|
||||
|
||||
__slots__ = [
|
||||
'target',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, target, expression):
|
||||
super(Jump, self).__init__(loc)
|
||||
|
||||
@@ -537,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)
|
||||
|
||||
@@ -568,6 +593,8 @@ class While(Node):
|
||||
|
||||
class If(Node):
|
||||
|
||||
__slots__ = [ 'entries' ]
|
||||
|
||||
def __init__(self, loc, entries):
|
||||
"""
|
||||
@param entries: A list of (condition, block) tuples.
|
||||
|
||||
+47
-30
@@ -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,15 +86,9 @@ force_archives = False
|
||||
# An image file containing the mouse cursor, if one is defined.
|
||||
mouse = None
|
||||
|
||||
# The distance the keyboard moves the mouse, per 50 ms tick, in pixels.
|
||||
keymouse_distance = 5
|
||||
|
||||
# The default sound playback sample rate.
|
||||
sound_sample_rate = 44100
|
||||
|
||||
# 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
|
||||
|
||||
@@ -109,10 +106,24 @@ transient_layers = [ 'transient' ]
|
||||
# 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(
|
||||
|
||||
@@ -123,24 +134,18 @@ keymap = dict(
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
|
||||
# Say.
|
||||
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 = [ 'mouseup_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 = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
@@ -148,24 +153,30 @@ keymap = dict(
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouseup_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()
|
||||
@@ -174,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+401
-173
@@ -9,6 +9,7 @@
|
||||
import pygame
|
||||
import renpy
|
||||
import sys # to detect windows.
|
||||
import os
|
||||
|
||||
# The Windows Volume Management Strategy (tm).
|
||||
|
||||
@@ -45,7 +46,17 @@ def init():
|
||||
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:
|
||||
@@ -168,180 +179,9 @@ def init():
|
||||
|
||||
playing_midi = False
|
||||
|
||||
# This detects if the filename is a midi, and sets playing_midi
|
||||
# appropriately.
|
||||
def detect_midi(fn):
|
||||
if mixer_works:
|
||||
pygame.mixer.music.set_endevent(renpy.display.core.MUSICEND)
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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()
|
||||
|
||||
def restore_music():
|
||||
"""
|
||||
This makes sure that the current music matches the music found in
|
||||
the context.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
if not mixer_enabled:
|
||||
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)
|
||||
# Plays sounds.
|
||||
|
||||
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 mixer_works:
|
||||
return
|
||||
|
||||
if not fn:
|
||||
return
|
||||
|
||||
if not renpy.game.preferences.sound:
|
||||
return
|
||||
|
||||
if not mixer_enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
sound = pygame.mixer.Sound(renpy.loader.load(fn))
|
||||
sound.play()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def pre_init():
|
||||
try:
|
||||
bufsize = 4096
|
||||
|
||||
import os
|
||||
|
||||
if 'RENPY_SOUND_BUFSIZE' in os.environ:
|
||||
bufsize = int(os.environ('RENPY_SOUND_BUFSIZE'))
|
||||
|
||||
pygame.mixer.pre_init(renpy.config.sound_sample_rate, -16, 2, bufsize)
|
||||
except:
|
||||
try:
|
||||
pygame.mixer.pre_init()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def disable_mixer():
|
||||
"""
|
||||
@@ -356,6 +196,7 @@ def disable_mixer():
|
||||
|
||||
if mixer_enabled:
|
||||
try:
|
||||
music_stop()
|
||||
pygame.mixer.quit()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
@@ -383,3 +224,390 @@ def enable_mixer():
|
||||
|
||||
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)
|
||||
|
||||
+50
-291
@@ -111,35 +111,6 @@ class Keymap(renpy.display.layout.Null):
|
||||
# def render(self, width, height, st):
|
||||
# return None
|
||||
|
||||
class KeymouseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class that causes the keyboard to move the mouse. It's
|
||||
useful on the game and key menus, as well as in imagemaps and the
|
||||
like.
|
||||
"""
|
||||
|
||||
def event(self, ev, x, y):
|
||||
if ev.type == renpy.display.core.DISPLAYTIME:
|
||||
|
||||
pressed = pygame.key.get_pressed()
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
ox, oy = x, y
|
||||
|
||||
if 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
|
||||
|
||||
class PauseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class implementing the Pause behavior, which is to
|
||||
@@ -168,10 +139,11 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
mouse button.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(SayBehavior, self).__init__()
|
||||
|
||||
focusable = True
|
||||
|
||||
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 \
|
||||
@@ -183,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"):
|
||||
@@ -192,211 +163,70 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
class Menu(renpy.display.layout.VBox):
|
||||
|
||||
def __init__(self, menuitems, style='menu', **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__(style=style, **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):
|
||||
|
||||
if result is not None:
|
||||
style = 'menu_choice'
|
||||
else:
|
||||
style = 'menu_caption'
|
||||
|
||||
self.add(renpy.display.text.Text(caption, style=style))
|
||||
|
||||
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:
|
||||
continue
|
||||
|
||||
# Actual choices change color if they are selected or not.
|
||||
if i == self.selected:
|
||||
child.set_style_prefix('hover_')
|
||||
else:
|
||||
child.set_style_prefix('idle_')
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
Processes events.
|
||||
"""
|
||||
|
||||
# print ev
|
||||
# print x, y
|
||||
|
||||
old_selected = self.selected
|
||||
mouse_select = False
|
||||
|
||||
# Change selection based on mouse position.
|
||||
if ev.type == MOUSEMOTION or 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:
|
||||
self.selected = target
|
||||
mouse_select = True
|
||||
|
||||
# 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
|
||||
|
||||
# If the selected item changed, update the display.
|
||||
if self.selected != old_selected:
|
||||
|
||||
self.children[self.selected].set_style_prefix("hover_")
|
||||
self.children[old_selected].set_style_prefix("idle_")
|
||||
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
# renpy.display.render.redraw(self, 0)
|
||||
|
||||
# Make selection based on keypress or mouse click.
|
||||
if map_event(ev, "menu_keyselect") or \
|
||||
(mouse_select and map_event(ev, "menu_mouseselect")):
|
||||
|
||||
self.children[self.selected].set_style_prefix("activate_")
|
||||
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
return self.results[self.selected]
|
||||
|
||||
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.activated = False
|
||||
|
||||
self.old_hover = False
|
||||
self.clicked = clicked
|
||||
self.hovered = hovered
|
||||
self.focusable = clicked is not None
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.activated:
|
||||
self.set_style_prefix('activate_')
|
||||
elif 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.display.render.redraw(self, 0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
# We deactivate on an event.
|
||||
self.activated = False
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
|
||||
inside = 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.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if map_event(ev, "button_select"):
|
||||
if inside and self.clicked:
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
|
||||
self.activated = True
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
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',
|
||||
@@ -405,29 +235,6 @@ def TextButton(text, style='button', text_style='button_text',
|
||||
text = renpy.display.text.Text(text, style=text_style)
|
||||
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):
|
||||
"""
|
||||
@@ -457,13 +264,12 @@ class Input(renpy.display.text.Text):
|
||||
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:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
@@ -488,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__()
|
||||
@@ -500,61 +306,14 @@ 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
|
||||
|
||||
@@ -587,7 +346,7 @@ 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:
|
||||
@@ -597,7 +356,7 @@ class Conditional(renpy.display.layout.Container):
|
||||
|
||||
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.display.render.redraw(self, 0)
|
||||
|
||||
+192
-94
@@ -2,15 +2,16 @@
|
||||
# window.
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
import os
|
||||
import time
|
||||
import cStringIO
|
||||
|
||||
# KEYREPEATEVENT = USEREVENT + 1
|
||||
DISPLAYTIME = USEREVENT + 2
|
||||
MUSICEND = USEREVENT + 3
|
||||
|
||||
# The number of msec
|
||||
DISPLAYTIME_INTERVAL = 50
|
||||
@@ -33,9 +34,40 @@ class Displayable(renpy.object.Object):
|
||||
their fields.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.style = None
|
||||
self.style_prefix = None
|
||||
focusable = False
|
||||
|
||||
def __init__(self, focus=None, default=False, style='default', **properties):
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.style_prefix = 'insensitive_'
|
||||
self.focus_name = focus
|
||||
self.default = default
|
||||
|
||||
def find_focusable(self, callback, focus_name):
|
||||
if self.focusable:
|
||||
callback(self, self.focus_name or focus_name)
|
||||
|
||||
|
||||
def focus(self, default=False):
|
||||
"""
|
||||
Called to indicate that this widget has the focus.
|
||||
"""
|
||||
|
||||
if self.style.enable_hover:
|
||||
self.set_style_prefix("hover_")
|
||||
|
||||
if not default:
|
||||
renpy.display.audio.play(self.style.sound)
|
||||
|
||||
def unfocus(self):
|
||||
"""
|
||||
Called to indicate that this widget has become unfocused.
|
||||
"""
|
||||
|
||||
if self.style.enable_hover:
|
||||
self.set_style_prefix("idle_")
|
||||
|
||||
def is_focused(self):
|
||||
return renpy.game.context().scene_lists.focused is self
|
||||
|
||||
def set_style_prefix(self, prefix):
|
||||
"""
|
||||
@@ -46,9 +78,7 @@ class Displayable(renpy.object.Object):
|
||||
if prefix == self.style_prefix:
|
||||
return
|
||||
|
||||
if self.style:
|
||||
self.style.set_prefix(prefix)
|
||||
|
||||
self.style.set_prefix(prefix)
|
||||
self.style_prefix = prefix
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
@@ -106,8 +136,8 @@ class Displayable(renpy.object.Object):
|
||||
the images it may want to load.
|
||||
"""
|
||||
|
||||
return
|
||||
|
||||
if self.style and self.style.background:
|
||||
self.style.background.predict(callback)
|
||||
|
||||
def place(self, dest, x, y, width, height, surf):
|
||||
"""
|
||||
@@ -143,14 +173,21 @@ class Displayable(renpy.object.Object):
|
||||
if isinstance(xoff, float):
|
||||
xoff = int(xoff * width)
|
||||
|
||||
if style.xanchor == 'left':
|
||||
xoff -= 0
|
||||
elif style.xanchor == 'center':
|
||||
xoff -= sw / 2
|
||||
elif style.xanchor == 'right':
|
||||
xoff -= sw
|
||||
else:
|
||||
raise Exception("xanchor '%s' is not known." % style.xanchor)
|
||||
|
||||
xanchor = style.xanchor
|
||||
|
||||
if xanchor == 'left':
|
||||
xanchor = 0.0
|
||||
elif xanchor == 'center':
|
||||
xanchor = 0.5
|
||||
elif xanchor == 'right':
|
||||
xanchor = 1.0
|
||||
elif not isinstance(xanchor, (float, int)):
|
||||
raise Exception("xanchor %r is not known." % xanchor)
|
||||
|
||||
xoff -= int(sw * xanchor)
|
||||
|
||||
|
||||
|
||||
xoff += x
|
||||
|
||||
@@ -160,15 +197,18 @@ class Displayable(renpy.object.Object):
|
||||
if isinstance(yoff, float):
|
||||
yoff = int(yoff * height)
|
||||
|
||||
if style.yanchor == 'top':
|
||||
yoff -= 0
|
||||
elif style.yanchor == 'center':
|
||||
yoff -= sh / 2
|
||||
elif style.yanchor == 'bottom':
|
||||
yoff -= sh
|
||||
else:
|
||||
raise Exception("yanchor '%s' is not known." % style.yanchor)
|
||||
yanchor = style.yanchor
|
||||
|
||||
if yanchor == 'top':
|
||||
yanchor = 0.0
|
||||
elif yanchor == 'center':
|
||||
yanchor = 0.5
|
||||
elif style.yanchor == 'bottom':
|
||||
yanchor = 1.0
|
||||
elif not isinstance(yanchor, (float, int)):
|
||||
raise Exception("yanchor %r is not known." % yanchor)
|
||||
|
||||
yoff -= int(sh * yanchor)
|
||||
yoff += y
|
||||
|
||||
# print self, xoff, yoff
|
||||
@@ -193,8 +233,7 @@ class SceneLists(object):
|
||||
@ivar transient: The current transient display list.
|
||||
@ivar overlay: The current overlay display list.
|
||||
|
||||
@ivar music: Opaque information about the music that is being played.
|
||||
|
||||
@ivar focused: The widget that is currently focused.
|
||||
"""
|
||||
|
||||
def __init__(self, oldsl=None):
|
||||
@@ -203,22 +242,26 @@ class SceneLists(object):
|
||||
|
||||
if oldsl:
|
||||
|
||||
for i in renpy.config.layers:
|
||||
for i in renpy.config.layers + renpy.config.top_layers:
|
||||
self.layers[i] = oldsl.layers[i][:]
|
||||
|
||||
for i in renpy.config.overlay_layers:
|
||||
self.clear(i)
|
||||
|
||||
self.replace_transient()
|
||||
|
||||
self.music = oldsl.music
|
||||
self.sticky_positions = oldsl.sticky_positions.copy()
|
||||
self.movie = oldsl.movie
|
||||
self.focused = None
|
||||
|
||||
else:
|
||||
for i in renpy.config.layers:
|
||||
for i in renpy.config.layers + renpy.config.top_layers:
|
||||
self.layers[i] = [ ]
|
||||
|
||||
self.music = None
|
||||
self.movie = None
|
||||
self.sticky_positions = { }
|
||||
self.focused = None
|
||||
|
||||
def rollback_copy(self):
|
||||
"""
|
||||
@@ -227,9 +270,7 @@ class SceneLists(object):
|
||||
"""
|
||||
|
||||
rv = SceneLists(self)
|
||||
|
||||
for i in renpy.config.overlay_layers:
|
||||
rv.layers[i] = [ ]
|
||||
rv.focused = None
|
||||
|
||||
# rv.master = self.master[:]
|
||||
# rv.transient = self.transient[:]
|
||||
@@ -251,6 +292,21 @@ class SceneLists(object):
|
||||
for i in renpy.config.transient_layers:
|
||||
self.layers[i] = [ ]
|
||||
|
||||
def transient_is_empty(self):
|
||||
"""
|
||||
This returns True if all transient layers are empty. This is
|
||||
used by the rollback code, as we can't start a new rollback
|
||||
if there is something in a transient layer (as things in the
|
||||
transient layer may contain objects that cannot be pickled,
|
||||
like lambdas.)
|
||||
"""
|
||||
|
||||
for i in renpy.config.transient_layers:
|
||||
if self.layers[i]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add(self, layer, thing, key=None):
|
||||
"""
|
||||
This is called to add something to a layer. Layer is
|
||||
@@ -339,18 +395,22 @@ class Display(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Ensure that we kill off the presplash.
|
||||
renpy.display.presplash.end()
|
||||
|
||||
# Ensure that we kill off the movie when changing screen res.
|
||||
renpy.display.video.movie_stop(clear=False)
|
||||
|
||||
renpy.display.audio.pre_init()
|
||||
pygame.init()
|
||||
pygame.display.init()
|
||||
pygame.font.init()
|
||||
renpy.display.audio.init()
|
||||
|
||||
self.fullscreen = renpy.game.preferences.fullscreen
|
||||
fsflag = 0
|
||||
|
||||
if self.fullscreen:
|
||||
fullscreen = self.fullscreen and not os.environ.get('RENPY_DISABLE_FULLSCREEN', False)
|
||||
|
||||
if fullscreen:
|
||||
fsflag = FULLSCREEN
|
||||
|
||||
# The window we display things in.
|
||||
@@ -372,12 +432,12 @@ class Display(object):
|
||||
pygame.display.set_caption(renpy.config.window_title)
|
||||
|
||||
if renpy.config.window_icon:
|
||||
pygame.display.set_icon(renpy.display.image.cache.load_image(renpy.config.window_icon))
|
||||
pygame.display.set_icon(renpy.display.im.load_image(renpy.config.window_icon))
|
||||
|
||||
|
||||
# Load the mouse image, if any.
|
||||
if renpy.config.mouse:
|
||||
self.mouse = renpy.display.image.cache.load_image(renpy.config.mouse)
|
||||
self.mouse = renpy.display.im.load_image(renpy.config.mouse)
|
||||
pygame.mouse.set_visible(False)
|
||||
else:
|
||||
self.mouse = None
|
||||
@@ -461,6 +521,7 @@ class Display(object):
|
||||
|
||||
self.suppress_mouse = suppress_blit
|
||||
|
||||
renpy.display.focus.take_focuses(surftree.focuses)
|
||||
|
||||
def save_screenshot(self, filename):
|
||||
"""
|
||||
@@ -475,8 +536,8 @@ class Display(object):
|
||||
contents of the window.
|
||||
"""
|
||||
|
||||
surf = pygame.transform.scale(self.window, scale)
|
||||
|
||||
surf = renpy.display.module.scale(self.window, scale)
|
||||
|
||||
sio = cStringIO.StringIO()
|
||||
pygame.image.save(surf, sio)
|
||||
rv = sio.getvalue()
|
||||
@@ -564,16 +625,13 @@ class Interface(object):
|
||||
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
|
||||
# We don't want the overlay to be clear here.
|
||||
old_old_scene = self.old_scene
|
||||
|
||||
if not renpy.config.overlay_during_wait:
|
||||
self.old_scene = self.compute_scene(scene_lists)
|
||||
|
||||
if renpy.config.overlay_during_wait and old_old_scene:
|
||||
for i in renpy.config.overlay_layers:
|
||||
scene_lists.clear(i)
|
||||
|
||||
self.old_scene = self.compute_scene(scene_lists)
|
||||
|
||||
|
||||
# self.old_scene.append_scene_list(scene_lists.master)
|
||||
self.old_scene[i] = old_old_scene[i]
|
||||
|
||||
def set_transition(self, transition, layer=None):
|
||||
"""
|
||||
@@ -611,12 +669,12 @@ class Interface(object):
|
||||
return rv
|
||||
|
||||
# We load at most one image per wait.
|
||||
if renpy.display.image.cache.needs_preload():
|
||||
ev = pygame.event.poll()
|
||||
if ev.type != NOEVENT:
|
||||
return ev
|
||||
if renpy.display.im.cache.needs_preload():
|
||||
ev = pygame.event.poll()
|
||||
if ev.type != NOEVENT:
|
||||
return ev
|
||||
|
||||
renpy.display.image.cache.preload()
|
||||
renpy.display.im.cache.preload()
|
||||
|
||||
return pygame.event.wait()
|
||||
|
||||
@@ -624,22 +682,18 @@ class Interface(object):
|
||||
def compute_scene(self, scene_lists):
|
||||
"""
|
||||
This converts scene lists into a dictionary mapping layer
|
||||
name to a Fixed containing that layer. None is mapped
|
||||
to a fixed containing all of the layers.
|
||||
name to a Fixed containing that layer.
|
||||
"""
|
||||
|
||||
rv = { }
|
||||
|
||||
root = renpy.display.layout.Fixed()
|
||||
for layer in renpy.config.layers + renpy.config.top_layers:
|
||||
|
||||
f = renpy.display.layout.Fixed(focus=layer)
|
||||
|
||||
for layer in renpy.config.layers:
|
||||
f = renpy.display.layout.Fixed()
|
||||
f.append_scene_list(scene_lists.layers[layer])
|
||||
|
||||
rv[layer] = f
|
||||
root.add(f)
|
||||
|
||||
rv[None] = root
|
||||
|
||||
return rv
|
||||
|
||||
@@ -652,16 +706,20 @@ class Interface(object):
|
||||
|
||||
# These things can be done once per interaction.
|
||||
|
||||
repeat = True
|
||||
try:
|
||||
|
||||
while repeat:
|
||||
repeat, rv = self.interact_core(**kwargs)
|
||||
repeat = True
|
||||
|
||||
while repeat:
|
||||
repeat, rv = self.interact_core(**kwargs)
|
||||
|
||||
# Clean out transient stuff at the end of an interaction.
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
scene_lists.replace_transient()
|
||||
return rv
|
||||
|
||||
finally:
|
||||
|
||||
return rv
|
||||
# Clean out transient stuff at the end of an interaction.
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
scene_lists.replace_transient()
|
||||
|
||||
|
||||
def interact_core(self,
|
||||
@@ -699,8 +757,15 @@ class Interface(object):
|
||||
|
||||
# frames = 0
|
||||
|
||||
# Update the music, if necessary.
|
||||
for i in pygame.event.get([ MUSICEND ]):
|
||||
renpy.display.audio.music_end_event()
|
||||
|
||||
for i in renpy.config.interact_callbacks:
|
||||
i()
|
||||
|
||||
# Tick time forward.
|
||||
renpy.display.image.cache.tick()
|
||||
renpy.display.im.cache.tick()
|
||||
|
||||
# Set up key repeats.
|
||||
# pygame.time.set_timer(KEYREPEATEVENT, renpy.config.skip_delay)
|
||||
@@ -711,17 +776,11 @@ class Interface(object):
|
||||
# Clear some events.
|
||||
pygame.event.clear((MOUSEMOTION, DISPLAYTIME,
|
||||
MOUSEBUTTONUP, MOUSEBUTTONDOWN))
|
||||
|
||||
|
||||
# Figure out the scene list we want to show.
|
||||
start_time = time.time()
|
||||
# Figure out the scene list we want to show.
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
|
||||
|
||||
# Figure out what the overlay layer should look like.
|
||||
for i in renpy.config.overlay_layers:
|
||||
scene_lists.clear(i)
|
||||
|
||||
renpy.ui.layer("overlay")
|
||||
|
||||
if not suppress_overlay:
|
||||
@@ -739,26 +798,54 @@ class Interface(object):
|
||||
# The root widget of everything that is displayed on the screen.
|
||||
root_widget = renpy.display.layout.Fixed()
|
||||
root_widget.append_scene_list(underlay)
|
||||
root_widget.layers = { }
|
||||
|
||||
# Figure out the scene. (All of the layers, and the root.)
|
||||
scene = self.compute_scene(scene_lists)
|
||||
|
||||
|
||||
# If necessary, load all images here.
|
||||
if renpy.config.load_before_transition:
|
||||
for w in scene.itervalues():
|
||||
w.predict(renpy.display.im.cache.get)
|
||||
|
||||
# The start time of transitions.
|
||||
start_time = time.time()
|
||||
|
||||
# The root widget of all of the layers.
|
||||
layers_root = renpy.display.layout.Fixed()
|
||||
layers_root.layers = { }
|
||||
|
||||
# Add layers (perhaps with transitions) to the layers root.
|
||||
for layer in renpy.config.layers:
|
||||
if layer in self.transition and self.old_scene and not self.suppress_transition:
|
||||
|
||||
def add_layer(where, layer):
|
||||
if self.transition.get(layer, None) and self.old_scene and not self.suppress_transition:
|
||||
|
||||
trans = self.transition[layer](old_widget=self.old_scene[layer],
|
||||
new_widget=scene[layer])
|
||||
layers_root.add(trans, start_time)
|
||||
where.add(trans, start_time)
|
||||
where.layers[layer] = trans
|
||||
|
||||
else:
|
||||
layers_root.add(scene[layer], start_time)
|
||||
where.layers[layer] = scene[layer]
|
||||
where.add(scene[layer], start_time)
|
||||
|
||||
|
||||
|
||||
# Add layers (perhaps with transitions) to the layers root.
|
||||
for layer in renpy.config.layers:
|
||||
add_layer(layers_root, layer)
|
||||
|
||||
# Add layers_root to root_widget, perhaps through a transition.
|
||||
if None in self.transition and self.old_scene and not self.suppress_transition:
|
||||
trans = self.transition[None](old_widget=self.old_scene[None],
|
||||
|
||||
# Compute what the old root should be.
|
||||
old_root = renpy.display.layout.Fixed()
|
||||
old_root.layers = { }
|
||||
|
||||
for layer in renpy.config.layers:
|
||||
old_root.layers[layer] = self.old_scene[layer]
|
||||
old_root.add(self.old_scene[layer], start_time)
|
||||
|
||||
trans = self.transition[None](old_widget=old_root,
|
||||
new_widget=layers_root)
|
||||
|
||||
root_widget.add(trans, start_time)
|
||||
@@ -774,6 +861,11 @@ class Interface(object):
|
||||
root_widget.add(layers_root, start_time)
|
||||
|
||||
|
||||
# Add top_layers to the root_widget.
|
||||
for layer in renpy.config.top_layers:
|
||||
add_layer(root_widget, layer)
|
||||
|
||||
|
||||
# Now, update various things regarding scenes and transitions,
|
||||
# so we are ready for a new interaction or a restart.
|
||||
self.old_scene = scene
|
||||
@@ -784,13 +876,16 @@ class Interface(object):
|
||||
# Okay, from here on we now have a single root widget (root_widget),
|
||||
# which we will try to show to the user.
|
||||
|
||||
# Figure out what should be focused.
|
||||
renpy.display.focus.before_interact(root_widget)
|
||||
|
||||
# Redraw the screen.
|
||||
renpy.display.render.process_redraws()
|
||||
needs_redraw = True
|
||||
|
||||
# Post an event that moves us to the current mouse position.
|
||||
pygame.event.post(pygame.event.Event(MOUSEMOTION,
|
||||
pos=pygame.mouse.get_pos()))
|
||||
# pygame.event.post(pygame.event.Event(MOUSEMOTION,
|
||||
# pos=pygame.mouse.get_pos()))
|
||||
|
||||
# We only want to do prediction once, but we will defer it as
|
||||
# long as possible.
|
||||
@@ -848,14 +943,18 @@ class Interface(object):
|
||||
|
||||
# Predict images, if we haven't done so already.
|
||||
if not did_prediction and not self.event_peek():
|
||||
renpy.game.context().predict(renpy.display.image.cache.preload_image)
|
||||
root_widget.predict(renpy.display.image.cache.preload_image)
|
||||
root_widget.predict(renpy.display.im.cache.preload_image)
|
||||
renpy.game.context().predict(renpy.display.im.cache.preload_image)
|
||||
did_prediction = True
|
||||
|
||||
try:
|
||||
ev = self.event_wait()
|
||||
self.profile_time = time.time()
|
||||
|
||||
# A song just ended.
|
||||
if ev.type == MUSICEND:
|
||||
renpy.display.audio.music_end_event()
|
||||
|
||||
if ev.type == DISPLAYTIME:
|
||||
|
||||
events = 1 + len(pygame.event.get([DISPLAYTIME]))
|
||||
@@ -865,12 +964,6 @@ class Interface(object):
|
||||
ev = pygame.event.Event(DISPLAYTIME, {},
|
||||
duration=(time.time() - start_time))
|
||||
|
||||
# Update the playing music, if necessary.
|
||||
|
||||
# This needs to be here so that we eventually start a
|
||||
# new song at the end of a fadeout.
|
||||
renpy.display.audio.restore_music()
|
||||
|
||||
# Handle skipping.
|
||||
renpy.display.behavior.skipping(ev)
|
||||
|
||||
@@ -890,8 +983,9 @@ class Interface(object):
|
||||
if len(evs):
|
||||
ev = evs[-1]
|
||||
|
||||
# x, y = getattr(ev, 'pos', (0, 0))
|
||||
renpy.display.focus.event_handler(ev)
|
||||
|
||||
# x, y = getattr(ev, 'pos', (0, 0))
|
||||
x, y = pygame.mouse.get_pos()
|
||||
|
||||
rv = root_widget.event(ev, x, y)
|
||||
@@ -914,6 +1008,10 @@ class Interface(object):
|
||||
|
||||
finally:
|
||||
|
||||
# Clean out the overlay layers.
|
||||
for i in renpy.config.overlay_layers:
|
||||
scene_lists.clear(i)
|
||||
|
||||
# pygame.time.set_timer(KEYREPEATEVENT, 0)
|
||||
pygame.time.set_timer(DISPLAYTIME, 0)
|
||||
|
||||
|
||||
@@ -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))
|
||||
+53
-408
@@ -1,209 +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))
|
||||
|
||||
renpy.display.render.mutated_surface(rv)
|
||||
|
||||
return rv
|
||||
|
||||
im = pygame.image.load(renpy.loader.load(fn), fn)
|
||||
|
||||
if im.get_flags() & SRCALPHA:
|
||||
im = im.convert_alpha()
|
||||
else:
|
||||
im = im.convert()
|
||||
|
||||
renpy.display.render.mutated_surface(im)
|
||||
|
||||
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.render.Render(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.
|
||||
"""
|
||||
|
||||
super(Image, self).__init__()
|
||||
|
||||
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.render.Render(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):
|
||||
"""
|
||||
@@ -241,19 +46,18 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
|
||||
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
|
||||
@@ -265,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)
|
||||
@@ -323,7 +126,8 @@ 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__()
|
||||
@@ -331,10 +135,11 @@ class Solid(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = renpy.display.render.Render(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):
|
||||
"""
|
||||
@@ -348,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.
|
||||
@@ -360,107 +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.
|
||||
"""
|
||||
|
||||
super(Frame, self).__init__()
|
||||
|
||||
self.filename = filename
|
||||
self.image = Image(image)
|
||||
self.xborder = xborder
|
||||
self.yborder = yborder
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
dest = renpy.display.render.Render(width, height)
|
||||
dw, dh = width, height
|
||||
|
||||
source = cache.load_image(self.filename)
|
||||
sw, sh = source.get_size()
|
||||
fi = renpy.display.im.FrameImage(self.image,
|
||||
self.xborder,
|
||||
self.yborder,
|
||||
width,
|
||||
height)
|
||||
|
||||
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
|
||||
return render(fi, width, height, st)
|
||||
|
||||
def predict(self, callback):
|
||||
callback(self.filename)
|
||||
|
||||
class Animation(renpy.display.core.Displayable):
|
||||
self.image.predict(callback)
|
||||
|
||||
# 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.
|
||||
@@ -476,7 +205,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
animation will restart after the final delay time.
|
||||
"""
|
||||
|
||||
super(Animation, self).__init__()
|
||||
super(Animation, self).__init__(style='image_placement')
|
||||
|
||||
self.images = [ ]
|
||||
self.delays = [ ]
|
||||
@@ -484,7 +213,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
for i, arg in enumerate(args):
|
||||
|
||||
if i % 2 == 0:
|
||||
self.images.append(arg)
|
||||
self.images.append(Image(arg))
|
||||
else:
|
||||
self.delays.append(arg)
|
||||
|
||||
@@ -499,7 +228,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
if t < delay:
|
||||
renpy.display.render.redraw(self, delay - t)
|
||||
|
||||
im = cache.load_image(image)
|
||||
im = render(image, width, height, st)
|
||||
width, height = im.get_size()
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(im, (0, 0))
|
||||
@@ -511,105 +240,21 @@ class Animation(renpy.display.core.Displayable):
|
||||
|
||||
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):
|
||||
|
||||
super(ImageMap, self).__init__()
|
||||
|
||||
self.ground = ground
|
||||
self.selected = selected
|
||||
self.hotspots = hotspots
|
||||
|
||||
if not unselected:
|
||||
self.unselected = self.ground
|
||||
else:
|
||||
self.unselected = unselected
|
||||
|
||||
self.active = None
|
||||
self.last_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.render.Render(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))
|
||||
renpy.display.render.mutated_surface(subsurface)
|
||||
|
||||
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.display.render.redraw(self, 0)
|
||||
|
||||
if active is not None:
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if active is None:
|
||||
return None
|
||||
|
||||
if renpy.display.behavior.map_event(ev, "imagemap_select"):
|
||||
renpy.display.audio.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_")
|
||||
@@ -619,18 +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 set_hover(self, hover):
|
||||
super(ImageButton, self).set_hover(hover)
|
||||
def focus(self, default=False):
|
||||
self.child = self.hover_image
|
||||
super(ImageButton, self).focus(default=default)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
+233
-139
@@ -27,9 +27,7 @@ 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
|
||||
|
||||
@@ -37,7 +35,12 @@ class Null(renpy.display.core.Displayable):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
return renpy.display.render.Render(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):
|
||||
@@ -60,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)
|
||||
|
||||
@@ -132,6 +143,8 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
def predict(self, callback):
|
||||
|
||||
super(Container, self).predict(callback)
|
||||
|
||||
for i in self.children:
|
||||
i.predict(callback)
|
||||
|
||||
@@ -147,13 +160,21 @@ class Fixed(Container):
|
||||
|
||||
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__()
|
||||
self.style = renpy.style.Style(style, 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)
|
||||
@@ -162,6 +183,8 @@ class Fixed(Container):
|
||||
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)
|
||||
|
||||
@@ -214,9 +237,7 @@ 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):
|
||||
@@ -240,11 +261,14 @@ class Grid(Container):
|
||||
"""
|
||||
|
||||
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__()
|
||||
@@ -255,6 +279,10 @@ class Grid(Container):
|
||||
self.rows = rows
|
||||
|
||||
self.padding = padding
|
||||
self.transpose = transpose
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
@@ -266,7 +294,28 @@ class Grid(Container):
|
||||
if len(self.children) != cols * rows:
|
||||
raise Exception("Grid not completely full.")
|
||||
|
||||
renders = [ render(i, width, height, st) for i in self.children ]
|
||||
# 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
|
||||
@@ -277,10 +326,10 @@ class Grid(Container):
|
||||
cheight = max(cheight, h)
|
||||
|
||||
if self.style.xfill:
|
||||
cwidth = (width - (cols - 1) * padding) / cols
|
||||
cwidth = renwidth
|
||||
|
||||
if self.style.yfill:
|
||||
cheight = (height - (rows - 1) * padding) / rows
|
||||
cheight = renheight
|
||||
|
||||
width = cwidth * cols + padding * (cols - 1)
|
||||
height = cheight * rows + padding * (rows - 1)
|
||||
@@ -456,17 +505,15 @@ 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)
|
||||
@@ -536,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 = render(self.child, width, height, st)
|
||||
self.sizes = [ surf.get_size() ]
|
||||
|
||||
x0, y0 = self.startpos
|
||||
x1, y1 = self.endpos
|
||||
|
||||
if self.time > 0:
|
||||
tfrac = (st / self.time)
|
||||
else:
|
||||
tfrac = 1.0
|
||||
|
||||
if tfrac > 1.0:
|
||||
tfrac = 1.0
|
||||
|
||||
xo = int(x0 * (1.0 - tfrac) + x1 * tfrac)
|
||||
yo = int(y0 * (1.0 - tfrac) + y1 * tfrac)
|
||||
|
||||
|
||||
self.offsets = [ (-xo, -yo) ]
|
||||
|
||||
rv = renpy.display.render.Render(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:
|
||||
if self.repeat:
|
||||
st = st % self.period
|
||||
renpy.display.render.redraw(self, 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 = render(self.child, width, height, st)
|
||||
|
||||
self.sizes = [ rv.get_size() ]
|
||||
self.sizes = [ child.get_size() ]
|
||||
self.offsets = [ (0, 0) ]
|
||||
|
||||
if st < self.time:
|
||||
renpy.display.render.redraw(self, 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()
|
||||
|
||||
+75
-6
@@ -62,6 +62,12 @@ 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]
|
||||
|
||||
@@ -132,7 +138,6 @@ def render_screen(widget, width, height, st):
|
||||
global new_renders
|
||||
global mutated_surfaces
|
||||
|
||||
redraw_queue = [ ]
|
||||
mutated_surfaces = { }
|
||||
|
||||
rv = render(widget, width, height, st)
|
||||
@@ -149,6 +154,15 @@ def render_screen(widget, width, height, st):
|
||||
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 = [ ]
|
||||
@@ -295,9 +309,11 @@ class Render(object):
|
||||
# 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 = [ ]
|
||||
@@ -310,6 +326,10 @@ class Render(object):
|
||||
|
||||
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
|
||||
@@ -371,8 +391,9 @@ class Render(object):
|
||||
|
||||
# Removes cycles.
|
||||
self.render_of = [ ]
|
||||
self.focuses = [ ]
|
||||
|
||||
def blit(self, source, (x, y)):
|
||||
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,
|
||||
@@ -385,8 +406,18 @@ class Render(object):
|
||||
|
||||
source.parents.append(self)
|
||||
self.children.append(source)
|
||||
|
||||
self.blittables.append((x, y, 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):
|
||||
@@ -459,7 +490,7 @@ class Render(object):
|
||||
|
||||
return rv
|
||||
|
||||
def subsurface(self, pos):
|
||||
def subsurface(self, pos, focus=False):
|
||||
"""
|
||||
Returns a subsurface of this render.
|
||||
"""
|
||||
@@ -477,6 +508,24 @@ class Render(object):
|
||||
|
||||
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
|
||||
@@ -539,4 +588,24 @@ class Render(object):
|
||||
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))
|
||||
|
||||
+111
-49
@@ -99,13 +99,59 @@ class TextStyle(object):
|
||||
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 format and display text on the screen.
|
||||
"""
|
||||
|
||||
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_width', 'laidout_height', 'width' ]
|
||||
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_linewidths',
|
||||
'laidout_width', 'laidout_height', 'width' ]
|
||||
|
||||
def after_setstate(self):
|
||||
self.laidout = None
|
||||
@@ -211,6 +257,9 @@ class Text(renpy.display.core.Displayable):
|
||||
# The width of the current line.
|
||||
linewidth = 0
|
||||
|
||||
# A list of the same.
|
||||
linewidths = [ ]
|
||||
|
||||
# The maximum linewidth.
|
||||
maxwidth = 0
|
||||
|
||||
@@ -224,10 +273,16 @@ class Text(renpy.display.core.Displayable):
|
||||
# cur.
|
||||
remwidth = width - indent()
|
||||
|
||||
for i in re.split(r'( |\{[^{]+\}|\{\{|\n)', self.text):
|
||||
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 i == "\n":
|
||||
if kind == "newline":
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
maxwidth = max(maxwidth, linewidth + curwidth)
|
||||
@@ -235,6 +290,7 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
lines.append(line)
|
||||
lineheights.append(lineheight)
|
||||
linewidths.append(curwidth)
|
||||
|
||||
line = [ ]
|
||||
linewidth = 0
|
||||
@@ -243,14 +299,10 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
continue
|
||||
|
||||
elif i == "{{":
|
||||
i = "{"
|
||||
# We want to render this like a word, so no continue.
|
||||
|
||||
elif i.startswith("{"):
|
||||
elif kind == "tag":
|
||||
|
||||
# Are we closing a tag?
|
||||
if i.startswith("{/"):
|
||||
if i.startswith("/"):
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
cur = ""
|
||||
@@ -277,23 +329,23 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
tsl.append(TextStyle(tsl[-1]))
|
||||
|
||||
if i == "{b}":
|
||||
if i == "b":
|
||||
tsl[-1].bold = True
|
||||
|
||||
elif i == "{i}":
|
||||
elif i == "i":
|
||||
tsl[-1].italic = True
|
||||
|
||||
elif i == "{u}":
|
||||
elif i == "u":
|
||||
tsl[-1].underline = True
|
||||
|
||||
elif i == "{plain}":
|
||||
elif i == "plain":
|
||||
tsl[-1].bold = False
|
||||
tsl[-1].italic = False
|
||||
tsl[-1].underline = False
|
||||
|
||||
elif i.startswith("{size"):
|
||||
elif i.startswith("size"):
|
||||
|
||||
m = re.match(r'\{size=(\+|-|)(\d+)\}', i)
|
||||
m = re.match(r'size=(\+|-|)(\d+)', i)
|
||||
|
||||
if not m:
|
||||
raise Exception('Size tag %s could not be parsed.' % i)
|
||||
@@ -305,9 +357,9 @@ class Text(renpy.display.core.Displayable):
|
||||
else:
|
||||
tsl[-1].size = int(m.group(2))
|
||||
|
||||
elif i.startswith("{color"):
|
||||
elif i.startswith("color"):
|
||||
|
||||
m = re.match(r'\{color=(\#?[a-fA-F0-9]+)\}', i)
|
||||
m = re.match(r'color=(\#?[a-fA-F0-9]+)', i)
|
||||
|
||||
if not m:
|
||||
raise Exception('Color tag %s could not be parsed.' % i)
|
||||
@@ -319,7 +371,7 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
continue
|
||||
|
||||
elif i == ' ':
|
||||
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.
|
||||
@@ -330,34 +382,42 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
continue
|
||||
|
||||
# If we made it here, then we have normal text.
|
||||
elif kind == "word":
|
||||
|
||||
# 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
|
||||
# If we made it here, then we have normal text.
|
||||
|
||||
# Should we wrap?
|
||||
curwidth, lh = tsl[-1].sizes(cur + i)
|
||||
# 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
|
||||
|
||||
if curwidth > remwidth:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
lines.append(line)
|
||||
# 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)
|
||||
|
||||
maxwidth = max(maxwidth, linewidth)
|
||||
|
||||
line = [ ]
|
||||
lineheights.append(lineheight)
|
||||
|
||||
cur = i
|
||||
curwidth, lineheight = tsl[-1].sizes(cur)
|
||||
remwidth = width - indent()
|
||||
linewidth = 0
|
||||
else:
|
||||
cur = cur + i
|
||||
lineheight = max(lh, lineheight)
|
||||
raise Exception("Unknown text token kind %s." % kind)
|
||||
|
||||
# We're done. Let's close up.
|
||||
|
||||
@@ -371,11 +431,12 @@ class Text(renpy.display.core.Displayable):
|
||||
if line:
|
||||
lines.append(line)
|
||||
lineheights.append(lineheight)
|
||||
|
||||
linewidths.append(curwidth)
|
||||
|
||||
self.laidout = lines
|
||||
self.laidout_lineheights = lineheights
|
||||
self.laidout_width = max(maxwidth, self.style.minwidth)
|
||||
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):
|
||||
@@ -393,8 +454,8 @@ class Text(renpy.display.core.Displayable):
|
||||
antialias = self.style.antialias
|
||||
line_spacing = self.style.line_spacing
|
||||
|
||||
for line, line_height in zip(self.laidout, self.laidout_lineheights):
|
||||
x = xo + indent
|
||||
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
|
||||
@@ -424,8 +485,8 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
|
||||
length = int(st * renpy.config.annoying_text_cps)
|
||||
if self.slow and renpy.game.preferences.text_cps:
|
||||
length = int(st * renpy.game.preferences.text_cps)
|
||||
else:
|
||||
length = sys.maxint
|
||||
self.slow = False
|
||||
@@ -452,7 +513,7 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
self.layout(width - absxo)
|
||||
|
||||
rv = renpy.display.render.Render(self.laidout_width + absxo, self.laidout_height + 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)
|
||||
@@ -499,4 +560,5 @@ class ParameterizedText(object):
|
||||
|
||||
return Text(string, style=self.style, **self.properties)
|
||||
|
||||
|
||||
def predict(self, callback):
|
||||
return
|
||||
|
||||
+450
-67
@@ -3,6 +3,9 @@ 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.
|
||||
@@ -55,13 +58,13 @@ def refactor_fixed(in_old, in_new):
|
||||
|
||||
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):
|
||||
@@ -70,7 +73,115 @@ class Transition(renpy.display.core.Displayable):
|
||||
else:
|
||||
return None
|
||||
|
||||
class Fade(Transition):
|
||||
def find_focusable(self, callback, focus_name):
|
||||
self.new_widget.find_focusable(callback, focus_name)
|
||||
|
||||
class NoTransition(Transition):
|
||||
"""
|
||||
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, delay, old_widget=None, new_widget=None):
|
||||
super(NoTransition, self).__init__(delay)
|
||||
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
self.events = True
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
rv.blit(renpy.display.render.render(self.new_widget,
|
||||
width,
|
||||
height,
|
||||
st), (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
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
|
||||
@@ -88,69 +199,34 @@ class Fade(Transition):
|
||||
fading from the solid color to the new scene. A float, given as
|
||||
seconds.
|
||||
|
||||
@param color: The solid color that will be faded
|
||||
to. This is an RGB triple, where each element is in the range 0
|
||||
to 255. This defaults to black.
|
||||
@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.
|
||||
"""
|
||||
|
||||
def __init__(self, out_time, hold_time, in_time,
|
||||
old_widget=None, new_widget=None, color=(0, 0, 0)):
|
||||
dissolve = renpy.curry.curry(Dissolve)
|
||||
notrans = renpy.curry.curry(NoTransition)
|
||||
|
||||
if color:
|
||||
widget = renpy.display.image.Solid(color)
|
||||
|
||||
super(Fade, self).__init__(out_time + hold_time + in_time)
|
||||
if not widget:
|
||||
widget = renpy.display.image.Solid((0, 0, 0, 255))
|
||||
|
||||
self.out_time = out_time
|
||||
self.hold_time = hold_time
|
||||
self.in_time = in_time
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
self.color = color
|
||||
args = [ False, dissolve(out_time), widget ]
|
||||
|
||||
# self.frames = 0
|
||||
if hold_time:
|
||||
args.extend([ notrans(hold_time), widget, ])
|
||||
|
||||
# def __del__(self):
|
||||
# print "Faded using", self.frames, "frames."
|
||||
args.extend([dissolve(in_time), True ])
|
||||
|
||||
def render(self, width, height, st):
|
||||
return MultipleTransition(args, old_widget=old_widget, new_widget=new_widget)
|
||||
|
||||
# self.frames += 1
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
events = False
|
||||
|
||||
if st < self.out_time:
|
||||
widget = self.old_widget
|
||||
alpha = int(255 * (st / self.out_time))
|
||||
|
||||
elif st < self.out_time + self.hold_time:
|
||||
widget = None
|
||||
alpha = 255
|
||||
|
||||
else:
|
||||
widget = self.new_widget
|
||||
alpha = 255 - int(255 * ((st - self.out_time - self.hold_time) / self.in_time))
|
||||
events = True
|
||||
|
||||
if widget:
|
||||
surf = render(widget, width, height, st)
|
||||
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
self.events = events
|
||||
|
||||
# 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.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
# This was a nifty idea that just didn't work out, since we can't vary
|
||||
# the alpha on an image with an alpha channel. Too bad.
|
||||
@@ -212,6 +288,73 @@ class Fade(Transition):
|
||||
# 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):
|
||||
"""
|
||||
@@ -219,7 +362,7 @@ class Dissolve(Transition):
|
||||
overlaying the new scene on top of the old scene and varying its
|
||||
alpha from 0 to 255.
|
||||
|
||||
@param delay: The amount of time the dissolve will take.
|
||||
@param time: The amount of time the dissolve will take.
|
||||
"""
|
||||
|
||||
def __init__(self, time, old_widget=None, new_widget=None):
|
||||
@@ -253,9 +396,10 @@ class Dissolve(Transition):
|
||||
surf = top.pygame_surface(False)
|
||||
renpy.display.render.mutated_surface(surf)
|
||||
|
||||
if id(top) == self.old_top and id(bottom) == self.old_bottom:
|
||||
rv.focuses.extend(top.focuses)
|
||||
|
||||
# Fast rendering path.
|
||||
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)
|
||||
@@ -271,7 +415,7 @@ class Dissolve(Transition):
|
||||
|
||||
# Complete rendering path.
|
||||
|
||||
rv.blit(bottom, (0, 0))
|
||||
rv.blit(bottom, (0, 0), focus=False)
|
||||
surf.set_alpha(alpha, RLEACCEL)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
@@ -352,7 +496,7 @@ class CropMove(Transition):
|
||||
image. Otherwise, the top layer contains the old image.
|
||||
"""
|
||||
|
||||
|
||||
super(CropMove, self).__init__(time)
|
||||
self.time = time
|
||||
|
||||
if mode == "wiperight":
|
||||
@@ -503,13 +647,252 @@ class CropMove(Transition):
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
rv.blit(render(self.bottom, width, height, st), (0, 0))
|
||||
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)
|
||||
rv.blit(ss, pos)
|
||||
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.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
|
||||
|
||||
|
||||
@@ -160,7 +160,6 @@ class Movie(renpy.display.layout.Null):
|
||||
def render(self, width, height, st):
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
|
||||
if surface:
|
||||
renpy.display.render.mutated_surface(surface)
|
||||
|
||||
|
||||
+30
-5
@@ -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,12 +32,16 @@ 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
|
||||
|
||||
vars(self.info).update(vars(context.info))
|
||||
|
||||
self.scene_lists = renpy.display.core.SceneLists(oldsl)
|
||||
|
||||
def goto_label(self, node_name):
|
||||
@@ -64,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
|
||||
|
||||
@@ -115,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
|
||||
|
||||
@@ -125,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):
|
||||
@@ -154,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:
|
||||
|
||||
+119
-47
@@ -6,16 +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.display.audio import music_start, music_stop
|
||||
# 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 *
|
||||
from renpy.loadsave import load, save, saved_games
|
||||
from renpy.python import py_eval as eval
|
||||
from renpy.python import rng as random
|
||||
|
||||
@@ -44,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):
|
||||
"""
|
||||
@@ -107,8 +142,8 @@ 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)
|
||||
|
||||
@@ -202,8 +237,11 @@ def display_menu(items, window_style='menu_window'):
|
||||
|
||||
class TagQuotingDict(object):
|
||||
def __getitem__(self, key):
|
||||
if key in renpy.game.store:
|
||||
rv = renpy.game.store[key]
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if key in store:
|
||||
rv = store[key]
|
||||
|
||||
if isinstance(rv, (str, unicode)):
|
||||
rv = rv.replace("{", "{{")
|
||||
@@ -241,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
|
||||
@@ -257,16 +296,16 @@ def display_say(who, what, who_style='say_label',
|
||||
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()
|
||||
@@ -310,8 +349,6 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
|
||||
renpy.ui.imagemap(ground, selected, hotspots, unselected=unselected,
|
||||
style=style, **properties)
|
||||
|
||||
renpy.ui.keymousebehavior()
|
||||
|
||||
rv = renpy.ui.interact(suppress_overlay=(not overlays))
|
||||
checkpoint()
|
||||
return rv
|
||||
@@ -461,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.
|
||||
@@ -485,6 +531,14 @@ def version():
|
||||
|
||||
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
|
||||
@@ -524,6 +578,15 @@ 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
|
||||
@@ -549,7 +612,16 @@ def restart_interaction():
|
||||
|
||||
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.force_redraw = True
|
||||
|
||||
+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."
|
||||
@@ -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
|
||||
|
||||
+2
-1
@@ -156,7 +156,7 @@ def saved_games():
|
||||
newest = None
|
||||
|
||||
return saveinfo, newest
|
||||
|
||||
|
||||
def load(filename):
|
||||
"""
|
||||
Loads the game from the given file. This function never returns.
|
||||
@@ -165,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()
|
||||
|
||||
+19
-7
@@ -14,7 +14,7 @@ import renpy.game as game
|
||||
import os
|
||||
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
|
||||
|
||||
+32
-10
@@ -143,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
|
||||
|
||||
@@ -172,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
|
||||
@@ -467,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
|
||||
@@ -514,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
|
||||
|
||||
@@ -699,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):
|
||||
@@ -815,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()
|
||||
|
||||
+66
-28
@@ -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
|
||||
|
||||
@@ -581,7 +614,7 @@ class RollbackLog(renpy.object.Object):
|
||||
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))
|
||||
|
||||
|
||||
+8
-2
@@ -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 = [ ]
|
||||
@@ -108,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")
|
||||
@@ -165,6 +169,8 @@ class Script(object):
|
||||
if init:
|
||||
self.initcode.append(init)
|
||||
|
||||
self.all_stmts.extend(all_stmts)
|
||||
|
||||
return True
|
||||
|
||||
def lookup(self, label):
|
||||
|
||||
+80
-46
@@ -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,17 +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):
|
||||
"""
|
||||
@@ -42,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):
|
||||
"""
|
||||
@@ -60,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.
|
||||
@@ -74,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 ':')
|
||||
@@ -88,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
|
||||
@@ -99,54 +123,64 @@ 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 check_condition(self):
|
||||
"""
|
||||
Returns true if we should show this line of dialogue.
|
||||
"""
|
||||
|
||||
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):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
interact=interact,
|
||||
**self.properties)
|
||||
|
||||
class DynamicCharacter(object):
|
||||
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
|
||||
|
||||
def __init__(self, name_expr,
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
window_style='say_window',
|
||||
**properties):
|
||||
"""
|
||||
@param name_expr: An expression that, when evaluated, should yield
|
||||
the name of the character, as a string.
|
||||
|
||||
All other parameters are as for Character.
|
||||
"""
|
||||
|
||||
self.name_expr = name_expr
|
||||
self.who_style = who_style
|
||||
self.what_style = what_style
|
||||
self.window_style = window_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what, interact=True):
|
||||
import renpy.python as python
|
||||
|
||||
renpy.display_say(python.py_eval(self.name_expr),
|
||||
what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
interact=interact,
|
||||
**self.properties)
|
||||
return Character(name_expr, dynamic=True, **properties)
|
||||
|
||||
# The color function. (Moved, since text needs it, too.)
|
||||
color = renpy.display.text.color
|
||||
|
||||
+156
-121
@@ -1,158 +1,193 @@
|
||||
import renpy
|
||||
|
||||
# A list of style prefixes we care about, including no prefix.
|
||||
prefixes = [ 'hover_', 'idle_', 'activate_', '' ]
|
||||
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):
|
||||
|
||||
if key in substitutes:
|
||||
for i in substitutes[key]:
|
||||
self.__setattr__(i, value)
|
||||
|
||||
|
||||
return
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
+71
-17
@@ -42,8 +42,10 @@ def interact(**kwargs):
|
||||
if current_stack:
|
||||
raise Exception("ui.interact called with non-empty widget/layer stack. Did you forget a ui.close() somewhere?")
|
||||
|
||||
return renpy.game.interface.interact(**kwargs)
|
||||
|
||||
rv = renpy.game.interface.interact(**kwargs)
|
||||
renpy.game.context(-1).mark_seen()
|
||||
return rv
|
||||
|
||||
def add(w, make_current=False, once=False):
|
||||
"""
|
||||
@@ -85,7 +87,7 @@ def layer(name):
|
||||
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:
|
||||
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)
|
||||
@@ -166,10 +168,10 @@ def vbox(padding=0, **properties):
|
||||
|
||||
return add(renpy.display.layout.VBox(padding, **properties), True)
|
||||
|
||||
def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
|
||||
def grid(cols, rows, padding=0, transpose=False, **properties):
|
||||
"""
|
||||
This creates a layout that places widgets in an evenly spaced
|
||||
grid. New widges are added to this vbox unil ui.close() is called.
|
||||
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
|
||||
@@ -178,23 +180,24 @@ def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
|
||||
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.
|
||||
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, xfill=xfill, yfill=yfill, **properties), True)
|
||||
|
||||
|
||||
return add(renpy.display.layout.Grid(cols, rows, padding, transpose=transpose, **properties), True)
|
||||
|
||||
def fixed(**properties):
|
||||
"""
|
||||
@@ -223,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)
|
||||
|
||||
|
||||
@@ -244,9 +247,12 @@ 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():
|
||||
"""
|
||||
@@ -280,7 +286,12 @@ def pausebehavior(delay, result=False):
|
||||
|
||||
return add(renpy.display.behavior.PauseBehavior(delay, result))
|
||||
|
||||
def menu(menuitems, **properties):
|
||||
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
|
||||
@@ -293,7 +304,22 @@ def menu(menuitems, **properties):
|
||||
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):
|
||||
"""
|
||||
@@ -323,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 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):
|
||||
@@ -467,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)
|
||||
|
||||
+19
-16
@@ -2,15 +2,6 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
@@ -25,17 +16,17 @@ import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
# Add the path to the module.
|
||||
sys.path.append("module")
|
||||
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
@@ -55,6 +46,9 @@ def 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.')
|
||||
|
||||
@@ -64,8 +58,15 @@ def main():
|
||||
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:
|
||||
|
||||
@@ -121,6 +122,8 @@ def main():
|
||||
|
||||
def memory_profile():
|
||||
|
||||
import renpy
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+39
-7
@@ -1,6 +1,6 @@
|
||||
init:
|
||||
$ config.window_title = "Test Game"
|
||||
$ config.profile = True
|
||||
$ config.profile = False
|
||||
|
||||
$ config.debug_sound = True
|
||||
|
||||
@@ -22,16 +22,17 @@ init:
|
||||
# Styles.
|
||||
$ style.window.background = Frame("frame.png", 120, 25)
|
||||
|
||||
$ style.menu.hover_sound = "chev1.wav"
|
||||
$ style.menu.activate_sound = "chev2.wav"
|
||||
$ style.menu_choice_button.hover_sound = "chev1.wav"
|
||||
$ style.menu_choice_button.activate_sound = "chev2.wav"
|
||||
|
||||
$ style.imagemap.hover_sound = "chev3.wav"
|
||||
$ style.imagemap.activate_sound = "chev4.wav"
|
||||
$ style.imagemap_button.hover_sound = "chev3.wav"
|
||||
$ style.imagemap_button.activate_sound = "chev4.wav"
|
||||
|
||||
$ style.button.hover_sound = "chev6.wav"
|
||||
$ style.button.activate_sound = "chev7.wav"
|
||||
|
||||
$ style.mm_root_window.background = renpy.Solid((128, 128, 128, 255))
|
||||
# $ style.mm_root_window.background = Solid((128, 128, 128, 255))
|
||||
# $ style.gm_root_window.background = Solid((128, 128, 128, 255))
|
||||
|
||||
# Backgrounds.
|
||||
image whitehouse = Image("whitehouse.jpg")
|
||||
@@ -45,12 +46,13 @@ init:
|
||||
|
||||
image eileen anim = Animation("9a_happy.png", 0.25,
|
||||
"9a_vhappy.png", 0.25,
|
||||
"9a_concerned.png")
|
||||
"9a_concerned.png", 0.5)
|
||||
|
||||
image movie = Movie()
|
||||
|
||||
# Character objects.
|
||||
$ e = Character('Eileen', color=(200, 255, 200, 255))
|
||||
$ w = Character('Walter')
|
||||
$ pov = DynamicCharacter('pov_name', color=(255, 0, 0, 255))
|
||||
$ pov_name = '????'
|
||||
|
||||
@@ -82,6 +84,10 @@ label main_menu:
|
||||
# Stuff that happens before the real main menu.
|
||||
|
||||
scene black
|
||||
|
||||
show eileen anim
|
||||
|
||||
$ renpy.pause()
|
||||
|
||||
|
||||
show text "American Bishoujo\nPresents" \
|
||||
@@ -107,6 +113,32 @@ label start:
|
||||
scene whitehouse
|
||||
show eileen happy beret
|
||||
|
||||
e "Okay, let's play stargate."
|
||||
|
||||
voice "chev1.wav"
|
||||
w "Chevron one encoded!"
|
||||
|
||||
voice "chev2.wav"
|
||||
w "Chevron two encoded!"
|
||||
|
||||
voice "chev3.wav"
|
||||
w "Chevron three encoded!"
|
||||
|
||||
voice "chev4.wav"
|
||||
w "Chevron four encoded!"
|
||||
|
||||
voice "chev5.wav"
|
||||
w "Chevron five encoded!"
|
||||
|
||||
voice "chev6.wav"
|
||||
w "Chevron six encoded!"
|
||||
|
||||
voice "chev7.wav"
|
||||
w "Chevron seven..."
|
||||
|
||||
voice_sustain ""
|
||||
w "... locked!"
|
||||
|
||||
|
||||
python:
|
||||
ui.sizer(200, None)
|
||||
|
||||
Reference in New Issue
Block a user