Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b51312b21d | |||
| ab5d5c0e40 | |||
| c28639ef0b | |||
| a3b3d26816 | |||
| ee6bbf5d9c | |||
| 6e406cdae0 | |||
| 8ef2d97247 | |||
| e9be405aaf | |||
| 23dbab98d2 | |||
| 2865599e34 | |||
| fb5957186a | |||
| 8d2e592589 | |||
| c7714a69fd | |||
| 6d2dce1704 | |||
| 8ab32e8dba | |||
| 00ee770512 | |||
| c5761728b3 | |||
| d33aa31101 | |||
| 34cfc4fa40 | |||
| d603d62680 | |||
| 4991c5761c | |||
| cf3f8afd9f | |||
| 31fc8b213e | |||
| 74be98faba | |||
| b4e6b24a86 | |||
| 7c1980775e | |||
| 90d586968e | |||
| af0778b8bc | |||
| 23bccf06cb | |||
| 7558b20b1e | |||
| 8fbb785674 | |||
| d2210cf08f | |||
| 8d8ba4fb00 | |||
| 915df21775 | |||
| 59c7baf1ff | |||
| dc0d2f8039 | |||
| bcf752d3a6 | |||
| d95cd1f6a0 | |||
| c9f025dab2 | |||
| d484cc2906 | |||
| 18cf867432 | |||
| 149a2904a5 | |||
| ef2e78616f | |||
| 2711f79080 | |||
| fa31e03d06 | |||
| 6a27954677 | |||
| eab1edd5b4 | |||
| b9624fe922 | |||
| 69727f3baf | |||
| 0551eb6135 | |||
| c0b8ca8777 | |||
| 7af2e3d066 | |||
| 0a857f8879 | |||
| 839073ad8c | |||
| eb22c24884 |
@@ -1,3 +1,975 @@
|
||||
New in Ren'Py 4.8.7
|
||||
-------------------
|
||||
|
||||
The new DSE dating-sim engine code is now present in the dse/
|
||||
directory. It's not documented, apart from the comments in the source
|
||||
code, but it can be run by running the new run_dse program.
|
||||
|
||||
The image statement now passes its argument to the Image function,
|
||||
in loose mode. This means that strings and tuples are turned into
|
||||
single and composited images, respectively. So it's now possible to
|
||||
write:
|
||||
|
||||
init:
|
||||
image control room = "control_room.jpg"
|
||||
|
||||
instead of having to write:
|
||||
|
||||
init:
|
||||
image control room = Image("control_room.jpg")
|
||||
|
||||
Of course, the old way still works.
|
||||
|
||||
There is now a new transition type, ImageDissolve. It lets us creat
|
||||
dissolve transitions in which pixels are dissolved at different times,
|
||||
which can be used for a range of effects. Two such effects are
|
||||
'squares' and 'blinds'. A number of other types are defined in the
|
||||
source of the demo game, in their own section.
|
||||
|
||||
A new im.Tile image operator has been introduced. This operator tiles
|
||||
a source image until it reaches a specified size, or the size of the
|
||||
screen if no size is specified.
|
||||
|
||||
Ren'Py now supports a presplash screen. If the file presplash.png is
|
||||
present in the game directory, it is shown to the user on
|
||||
startup. This occurs before any loading or initialization occurs, so
|
||||
the size of the window it is shown in is determined by the size of the
|
||||
presplash.png image itself.
|
||||
|
||||
As per user request, we have made programmatic equivalents of the
|
||||
image, scene, show, and hide statements available. These functions are
|
||||
named renpy.image, renpy.scene, renpy.show, and renpy.hide,
|
||||
respectively.
|
||||
|
||||
Ren'Py now supports a new classification of layer, top layers. Top
|
||||
layers are layers that are displayed above any transition. This is
|
||||
useful if you want to have an overlay that is shown even when a
|
||||
transition is in progress. To have the overlays be displayed as a
|
||||
top_layer, remove the string 'overlay' from config.layers and append
|
||||
it to config.top_layers.
|
||||
|
||||
A new config variable, config.enable_fast_dissolve, was
|
||||
introduced. Setting this to False can fix a potential bug in the
|
||||
dissolve transition when used with an overlay layer that has an alpha
|
||||
channel that is not fully transparent or opaque, at the cost of 25% of
|
||||
the performance of dissolve. It usually can be kept at True with no
|
||||
ill effects.
|
||||
|
||||
Fade has been reimplemented in terms of Dissolve. This means that
|
||||
fades will be as fast as dissolves. Fade now also can take a widget,
|
||||
instead of a color, as an argument. If this widget is an Image, it
|
||||
means that we will dissolve from the old screen to the image, hold at
|
||||
the image for a given amount of time, and then dissolve from the image
|
||||
to the new screen.
|
||||
|
||||
The ImageReference class is now documented and present in the game's
|
||||
namespace. This lets us refer to an image by name from user code.
|
||||
|
||||
Added a RENPY_DISABLE_SOUND environment variable, which if set
|
||||
disables support for sound in Ren'Py. (For use on platforms where
|
||||
sound doesn't quite work.)
|
||||
|
||||
Fixed a bug in lint.
|
||||
|
||||
Fixed bugs that caused overlays to jump in and out during transitions,
|
||||
restoring the pre-4.8 behavior.
|
||||
|
||||
The definitions found in common/definitions.rpy are now documented in
|
||||
the reference. This gives a default set of transitions and positions,
|
||||
as well as an image (black).
|
||||
|
||||
|
||||
New in 4.8.6
|
||||
------------
|
||||
|
||||
Ren'Py now takes a new option, --lint. (Or for those of you under
|
||||
Windows, run lint.bat, editing it if necessary.) This option, when
|
||||
supplied, causes Ren'Py to run checks for dozens of errors in the
|
||||
script. Many of these checks test for problems that will only occur
|
||||
at runtime, on platforms other than Windows, or when loading a
|
||||
save-game after a change in script. This is the subtle stuff that my
|
||||
script-review service was for... now it's automated, so you don't need
|
||||
me anymore.
|
||||
|
||||
Lint should be run before you release any game with Ren'Py, from now
|
||||
on. In general, you should not release a game if lint gives you any
|
||||
errors, but instead you should fix those errors first.
|
||||
|
||||
Lint also produces a count of the number of say statments and menus in
|
||||
a program.
|
||||
|
||||
DynamicCharacter and Character have been unified into a single class,
|
||||
such that now calling DynamicCharacter is equivalent to calling
|
||||
Character with dynamic=True.
|
||||
|
||||
Character (and DynamicCharacter, and renpy.display_say) now take a new
|
||||
argument, image. If this argument is set to True, then the name of the
|
||||
character is interpreted as an image filename. This image is used
|
||||
where the character's name would go, in place of a textual name.
|
||||
|
||||
Fixed a bug that prevented newlines from being recognized in text
|
||||
widgets. (Introduced when I rewrote tokenizing in the previous
|
||||
release.)
|
||||
|
||||
Fixed a bug in the _renpy module, in which we accidentally used the
|
||||
height, rather than the width, of an image.
|
||||
|
||||
New in 4.8.5
|
||||
------------
|
||||
|
||||
Added a new state-machine based animation function, anim.SMAnimation.
|
||||
This function creates animations by applying images and transitions
|
||||
specified by the states and edges of a nondeterminstic state
|
||||
machine. The result is that it's possible to specify complicated
|
||||
random patterns of behavior. This can allow a character to blink
|
||||
randomly, allow random backgrounds to be shown, and even allow things
|
||||
to randomly move around the screen.
|
||||
|
||||
The SMAnimation framework can also be used to describe very
|
||||
complicated, even nondeterministic, motions. This is done by declaring
|
||||
a SMAnimation with None for the images, and then passing it in to the
|
||||
at clause.
|
||||
|
||||
I should point out that despite the complexity of the interface, the S
|
||||
and M in SMAnimation stand for state machine, and not anything
|
||||
else. Honest.
|
||||
|
||||
All motions (Motion, Pan, Move, and anim.SMAnimation when used as a
|
||||
motion) can now be used as transitions, in which case the motion is
|
||||
applied to the screen (or layer, as appropriate). This let us put
|
||||
together two new transitions, hpunch and vpunch, which shake the
|
||||
screen horizontally and vertically, as appropriate. Useful for when
|
||||
the POV character gets punched by a particularly feisty girl.
|
||||
|
||||
There are now two new image operators. im.Map can map the pixels
|
||||
values in an image to different values. Using this, one can adjust the
|
||||
colors of an image. The im.Alpha function is built on top of this, and
|
||||
allows one to alter the alpha of an image. (It even adjusts a
|
||||
pre-existing alpha channel.)
|
||||
|
||||
The definitions of transitions and placements, which were once in
|
||||
script.rpy, have been moved into common/definitions.rpy.
|
||||
|
||||
There is now a new config variable, config.text_tokenizer. This
|
||||
variable is used to give a function that breaks a line of text up into
|
||||
words, spaces, and other things. Providing a custom function here
|
||||
allows for control of where line breaking is allowed, useful for
|
||||
languages that have different rules about that.
|
||||
|
||||
Fixed the gallery.rpy extra, so that it now works properly with the
|
||||
main menu being in a menu context.
|
||||
|
||||
The demo game was updated to demonstrate the new feature, as well as
|
||||
to have a new, drop-shadowed window frame.
|
||||
|
||||
New in 4.8.4
|
||||
------------
|
||||
|
||||
The format of the compiled script (.rpyc) files has changed. The new
|
||||
format is more efficent, both in terms of space and memory.
|
||||
Unfortunately, the format change is not backwards compatible,
|
||||
and the automatic upgrade function probably won't work. So you'll need
|
||||
to delete the rpyc files from the game directory when you copy it
|
||||
over.
|
||||
|
||||
Removed some unnecessary conversion from pixellate, improving its
|
||||
performance when running on a computer with 32 bits per pixel
|
||||
graphics.
|
||||
|
||||
The xanchor and yanchor arguments can now take numbers between 0 and 1
|
||||
as well as names. Functions returned by Motion now can either return
|
||||
an xpos, ypos tuple or a xpos, ypos, xanchor, yanchor tuple. Move has
|
||||
been upgraded so that it can take similar tuples as the start and
|
||||
end arguments, letting us interpolate the location of the anchors
|
||||
while moving an image around.
|
||||
|
||||
The above modification allowed us to implement a new move
|
||||
transition. This transition attempts to find images that are present
|
||||
in both the old and new scenes, but have changed location between the
|
||||
two scenes. Such images are moved from the old location to the new
|
||||
location over the duration of the move transition. This makes it easy
|
||||
to smoothly move characters from one location to another on the
|
||||
screen.
|
||||
|
||||
The demo script was updated and reorganized, to demonstrate more
|
||||
features. Now, all transitions are demonstrated, as well as things
|
||||
like renpy.input, DynamicCharacter, Move, and Pan.
|
||||
|
||||
A slightly updated version of the script for Moonlight Walks is now
|
||||
in the scripts/ directory. It's there as an example for people to
|
||||
learn from. This script has been updated to work with Ren'Py 4.8, and
|
||||
to fix a tense error.
|
||||
|
||||
Fixed another segfault in windows native midi support.
|
||||
|
||||
|
||||
New in 4.8.3
|
||||
------------
|
||||
|
||||
Ren'Py no longer uses the psyco specializing compiler. The relatively
|
||||
small (about 2%) speed increase it gave wasn't worth the hassle
|
||||
involved in dealing with memory leaks and other semantic changes. This
|
||||
change significantly lowers Ren'Py's memory usage, and fixes a major
|
||||
memory leak that was introduced in the 4.8 series.
|
||||
|
||||
A number of race conditions were fixed in the windows native midi
|
||||
code. The upshot of this is that we can now play midi on windows
|
||||
without there being a small chance of crashing each time we start a
|
||||
new track.
|
||||
|
||||
The config.music_interact variable is gone. It's been replaced by
|
||||
config.interact_callbacks, which is a list of functions to call before
|
||||
each interaction. (This will help us support voice.)
|
||||
|
||||
A new Motion function allows the user to give a function that controls
|
||||
how a displayable moves on the screen, allowing for customizable
|
||||
motions. Pan and Move have been reimplemented in terms of Motion. All
|
||||
three functions now take repeat and bounce parameters. Repeat causes
|
||||
the motion to repeat after a single period is over, while bounce
|
||||
causes the motion to bounce from extreme to extreme.
|
||||
|
||||
Finally, a new voice extra has been added as extras/voice.rpy. This is
|
||||
a simplified version of what will eventually be Ren'Py's voice
|
||||
system. Right now, the only feature missing is that voices for
|
||||
characters cannot be enabled or disabled individually, but may
|
||||
instead only be manipulated in the aggregate.
|
||||
|
||||
|
||||
New in 4.8.2a
|
||||
-------------
|
||||
|
||||
Clicking preferences on the main menu jumpled to an undefined
|
||||
label. This is now fixed.
|
||||
|
||||
A new extra has been added. The commentary.rpy extra adds an optional
|
||||
commentary window, which is shown only when the commentary preference
|
||||
it adds is enabled.
|
||||
|
||||
To support the above, statements are only marked as seen if they have
|
||||
actually been shown to the user (by ui.interact being called during
|
||||
the statement.)
|
||||
|
||||
|
||||
New in 4.8.2
|
||||
------------
|
||||
|
||||
Unicode support in python blocks now works. This allows unicode
|
||||
strings to be used with Ren'Py. This is especially useful when naming
|
||||
characters and translating the various menus into your native
|
||||
language. Please note that the only unicode allowed is inside unicode
|
||||
strings, which begin with a u before the first quote. Unicode outside
|
||||
of unicode strings is an error.
|
||||
|
||||
We apologize to the non-english-speaking world for not noticing this
|
||||
one sooner.
|
||||
|
||||
The main menu was moved out of the context that the main body of the
|
||||
game code executes in, into its own context. The only effect this
|
||||
should have on user code is that some of the menu options changed from
|
||||
simple label names into more complicated jumps. But it does allow us
|
||||
to share the main menu with the game menu, which is something I've
|
||||
had requests for.
|
||||
|
||||
A number of minor changes were made to the menu at this time.
|
||||
|
||||
The README_RENPY.txt file was updated, to make it more current and
|
||||
useful.
|
||||
|
||||
The Character and DynamicCharacter objects were updated to take a
|
||||
condition argument. If this argument does not evaluate to true when a
|
||||
line of dialogue is executed, the line is not displayed to the user.
|
||||
|
||||
A new extra was added (in two_window_say.rpy) that places the name of
|
||||
a speaking character, and what they are saying, in two windows
|
||||
that can be styled separately.
|
||||
|
||||
The biggest change in Ren'Py was the addition of the _renpy
|
||||
module. This is a C module that enhances the functionality of pygame,
|
||||
allowing us to provide transitions and apply effects that simply
|
||||
weren't possible before. A compiled version ships with the windows
|
||||
binaries, installers are available for windows and mac from the Ren'PY
|
||||
website, and source is included in the module/ directory to compile it
|
||||
under Linux and Unix.
|
||||
|
||||
The presence of the _renpy module allows for two new features. The
|
||||
first is that the thumbnails of save images are now smoothly scaled
|
||||
down, which improves their look quite a bit.
|
||||
|
||||
The second new feature is the new pixellate transition, which
|
||||
pixellates the old screen, swaps pixellated screens, and then
|
||||
depixellates the new screen. One can see this feature in action by
|
||||
right clicking to access the game menu... for this release, we've set
|
||||
the game menu transition to be pixellate.
|
||||
|
||||
|
||||
New in 4.8.1
|
||||
------------
|
||||
|
||||
Character objects are no longer limited to displaying through
|
||||
renpy.display_say, but can now be supplied a function argument, which
|
||||
is a function that is called instead. This will make it easier to
|
||||
radically change how dialogue is displayed.
|
||||
|
||||
The ui.grid object now has a new argument, transpose, which allows
|
||||
widgets to be added down the columns first. It also is smarter about
|
||||
allocating space to subwidgets when xfill or yfill are set.
|
||||
|
||||
The file entries in the load and save screens are now arraigned in a
|
||||
grid, which has the style file_picker_grid. This means that we should
|
||||
now be smarter in the presence of games of varying size, and we should
|
||||
wrap text if it gets too long. As part of this,
|
||||
library.file_page_length has now been broken out into two variables,
|
||||
library.file_page_rows and library.file_page_cols.
|
||||
|
||||
renpy.music_stop now supports a fadeout parameter, for orthogonality
|
||||
with the new renpy.music_start.
|
||||
|
||||
Now, python blocks have the correct filename and line numbers
|
||||
annotated onto them. This means that tracebacks generated in python
|
||||
blocks will show a .rpy file and the appropriate line, rather than
|
||||
showing "<none>" and a random line as they did before.
|
||||
|
||||
There are two new style properties, xmaximum and ymaximum. These two
|
||||
properties limit the size of the widgets that they are applied to, as
|
||||
if that widget was placed inside a ui.sizer(). (In fact, ui.sizer() is
|
||||
now implemented in terms of these properties.)
|
||||
|
||||
Now, we only restore or rollback to places where all of the transient
|
||||
layers are empty. This prevents a bug in which additional dialogue
|
||||
will be lost when a restore or rollback occurs.
|
||||
|
||||
The way in which preferences are displayed has been made more
|
||||
configurable. Now, there is a new variable, library.preferences, which
|
||||
is a map from a style to a list of preferences to be displayed in a
|
||||
vbox with that style. There are now two new styles (prefs_left and
|
||||
prefs_right) which control the placement of the default preference
|
||||
columns.
|
||||
|
||||
There is a new spinner preference type, which allows a value to be
|
||||
incremented or decremented. This preference is now used to adjust the
|
||||
speed at which text is shown to the user. So now this is controlled by
|
||||
the user, rather than the config.annoying_text_cps parameter. If you
|
||||
don't want to give the user this control, you can set library.has_cps
|
||||
to False. A number of new styles define the spinner.
|
||||
|
||||
An empty string is now rendered as if it was a string consisting of a
|
||||
single space. This fixes a bug in which an empty string was rendered
|
||||
with zero width and height.
|
||||
|
||||
New in 4.8
|
||||
----------
|
||||
|
||||
A new system was implemented to manage widget focus. This new system
|
||||
allows the focus to be moved from widget to widget using the keyboard,
|
||||
without requiring the keymouse behavior be used. Because of this new
|
||||
system, the keymouse behavior is now a no-op.
|
||||
|
||||
To simplify the code and make every widget compatible with the new
|
||||
focus code, a number of widgets were reimplemented in terms of
|
||||
buttons. This includes the ui.menu widget, which is the widget that is
|
||||
used when presenting the user with choices. Menu buttons are of the
|
||||
new style style.menu_choice_button, while their labels remain of style
|
||||
style.menu_choice. Background images and padding can now be styled
|
||||
onto these menu buttons.
|
||||
|
||||
A number of new image manipulators have been made available in the
|
||||
new im package. These are objects that can load an image (im.Image),
|
||||
crop an image (im.Crop), scale images (im.Scale), rotozoom images
|
||||
(im.Rotozoom) and composite images (im.Composite). The latter is much
|
||||
more flexible than the previous image composition system, as it now
|
||||
supports placing images at arbitrary locations on an arbitrarily big
|
||||
canvas. Image (by itself) is now a function that creates the
|
||||
appropriate image manipulators, retaining compatibility with its
|
||||
behavior in previous releases.
|
||||
|
||||
The image cache is now based on image size, rather than number of
|
||||
images. This prevents a large number of small images from filling up
|
||||
the cache.
|
||||
|
||||
We now load images before starting the timer for a transition. This
|
||||
prevents us from popping into the middle of a transition because we
|
||||
spent some time loading images. This behavior can be controlled by
|
||||
config.load_before_transition.
|
||||
|
||||
The ui.imagemap widget has been rewritten in terms of ui.imagebuttons
|
||||
and im.Crops. Some styles have changed names as a result.
|
||||
|
||||
Ren'Py now keeps track of the images that have been shown to the
|
||||
user. This is the basis of this release's new extra, gallery.rpy,
|
||||
which manages a gallery of unlockable CG.
|
||||
|
||||
The audio system has been rewritten, and a number of low-level audio
|
||||
functions have been exposed in the new audio file. These functions
|
||||
control audio directly, but do not save the state of audio in a save
|
||||
file. The high-level music functions have been rewritten and placed in
|
||||
the music.rpy file, allowing a sufficently adventurous game author to
|
||||
change the way music is played.
|
||||
|
||||
Two changes have been made to the renpy.music_start() function in the
|
||||
high-level sound api. The startpos argument was removed (as it was a
|
||||
pain to support), and a new fadeout argument was added, which lets the
|
||||
fadeout time be controlled for each new song that is played.
|
||||
|
||||
The low-level sound api now lets us do the following things:
|
||||
|
||||
[list][*] Play up to eight sound effects at once.
|
||||
[*] Cancel a playing sound effect.
|
||||
[*] Pause and unpause the playing music.
|
||||
[/list]
|
||||
|
||||
There is now a new, state that a widget can be in. Along with hover,
|
||||
idle, and activate, a widget can now be insensitive. This is the state
|
||||
that all non-focused widgets are in. Having the new insensitive state
|
||||
means that we can eliminate the various disabled_button styles, so we
|
||||
did.
|
||||
|
||||
In general, in this release, many styles have changed names.
|
||||
|
||||
In prior versions of Ren'Py, the bar widget could respond to
|
||||
clicks. This was never used, and won't work well with the
|
||||
keyboard focus control system. So, it's been eliminated.
|
||||
|
||||
Now, if the label "enter_game_menu" exists, it is called when entering
|
||||
the game menu. It's expected that the main purpose of this label will
|
||||
be to play music when in the game menu. Using the default music
|
||||
system, the code to do this would look like:
|
||||
|
||||
[code]
|
||||
label enter_game_menu:
|
||||
$ renpy.music_start("game_menu_music.mid")
|
||||
return
|
||||
[/code]
|
||||
|
||||
A number of bugs were fixed, including bugs with the style system, and
|
||||
with animation refresh.
|
||||
|
||||
Tab characters are now expanded to tab stop at eight spaces, rather
|
||||
than to a fixed spacing of eight spaces. Old code using tabs may need
|
||||
to be reformatted.
|
||||
|
||||
The 'h' key now hides the windows on the screen. This means that all
|
||||
Ren'Py functionality is now available through the keyboard, at least
|
||||
for people with a 1-button mouse. (cough ... Mac ... cough)
|
||||
|
||||
Finally, setting the environment variable RENPY_DISABLE_FULLSCREEN to
|
||||
a non-empty value will disable fullscreen support, for those people
|
||||
(that person?) who had it crash their systems.
|
||||
|
||||
|
||||
New in 4.7.2
|
||||
------------
|
||||
|
||||
This release should be compatiable with the .rpyc and .save files
|
||||
produced by 4.7.1.
|
||||
|
||||
The new readback extra caused save files to reach a size and/or
|
||||
complexity that caused errors in cPickle on Windows, which in turn
|
||||
lead to a repeatable crash of Ren'Py, and left a corrupt save file in
|
||||
the saves directory that prevented further saving and loading of
|
||||
games. This has been addressed in two ways. The first is the use of
|
||||
pickle (as opposed to cPickle) throught Ren'Py, a change that may slow
|
||||
down the engine a little but should ensure correctness. The second is
|
||||
that we now catch the errors produced by corrupt files, and (if
|
||||
config.debug is not set) proceed by ignoring that file.
|
||||
|
||||
There is now a new extra, readback.rpy. This implements readback, in
|
||||
addition to, or instead of, rollback. Readback is limited in a number
|
||||
of ways, insofar as it only shows text without changing pictures or
|
||||
other things. As part of implementing readback, we added a new
|
||||
function, say, that is called when the user gives dialogue consisting
|
||||
of a pair of strings.
|
||||
|
||||
There are a number of changes that improve support of the Mac OS X
|
||||
platform. The foremost among them is that we ship a new file,
|
||||
run_game.pyw. This is identical to run_game.py, but is necessary for
|
||||
the game to run in a graphical environment on the Mac. (It also will
|
||||
run the game without a console window on Windows, if you have the
|
||||
appropriate dependencies.) Ren'Py still requires that PyObjC and
|
||||
pygame are installed on a OS X 10.3 box before it can run.
|
||||
|
||||
The way font line spacing is computed was changed in this release, in
|
||||
the hope of making it consistent on all three platforms. We now ignore
|
||||
font linesize hints, which seem to be computed incorrectly on some
|
||||
platforms, and instead set the line spacing to be equal to the ascent
|
||||
and descent of the font. The user can increase or decrease the line
|
||||
spacing by setting the new line_spacing text property. The old
|
||||
line_height_fudge property is now ignored. The style.rpy common file
|
||||
was updated to take account of this change.
|
||||
|
||||
To aid the user in finding the source of error messages, we now report
|
||||
the name of the file being parsed when an error occurs during the
|
||||
parse phase.
|
||||
|
||||
The tutorial has now been renamed "The Ren'Py Reference Manual", as
|
||||
it's really more of a poorly-organized comprehensive reference then a
|
||||
tutorial. This is in the hope that the Ren'Py user community will one
|
||||
day write a more reasonable tutorial. A number of errors in the
|
||||
reference were corrected.
|
||||
|
||||
The functions used in keymaps can now return values, which are
|
||||
returned to the ui.interact() that called them. This was needed
|
||||
to support readback.
|
||||
|
||||
LICENSE.txt has been updated to include the names and URLs of software
|
||||
that a windows build of Ren'Py depends on, the licenses of which you
|
||||
will be required to comply with when releasing a Ren'Py game.
|
||||
|
||||
New in 4.7.1
|
||||
------------
|
||||
|
||||
Added a new tool, dump_text.py/.exe, that dumps all of the text from a
|
||||
Ren'Py script file. It uses a heuristic approach that is imperfect,
|
||||
but is hopefully accurate enough to be useful for purposes like
|
||||
spell-checking a script. The text is dumped into the file text.txt in
|
||||
the current directory, and on windows that file is then displayed to
|
||||
the user. By default all of the rpy files in the game directory are
|
||||
displayed, but supplying a filename parameter can change that.
|
||||
|
||||
Fixed a bug that was preventing parser error messages from being
|
||||
reported on Windows.
|
||||
|
||||
Fixed a bug in Render.subsurface that lead to weird effects when doing
|
||||
an irisin or irisout on a sufficently complicated scene. See
|
||||
http://lemmasoft.renai.us/forums/viewtopic.php?p=4612#4612 for the
|
||||
discussion.
|
||||
|
||||
New in 4.7
|
||||
----------
|
||||
|
||||
The demo was updated to show the new features in 4.7.
|
||||
|
||||
The big new feature in this release is text tags. Text tags is a
|
||||
markup language that allows text properties to be changed inside a
|
||||
single unit of text. It lets you emphasize individual words by making
|
||||
them bold, bigger, or a different color. Rather than explaining it
|
||||
fully here, let me point you to the new "Text Tags" section of the
|
||||
documentation.
|
||||
|
||||
There were two changes that were made to Ren'Py to support text
|
||||
tags. The first was that interpolation now quotes the interpolated
|
||||
text, making it impossible to introduce a text tag via interpolation.
|
||||
|
||||
The second change is that renpy.input() and friends now take as
|
||||
arguments strings giving allowed and excluded characters. The default
|
||||
is to exclude the '{' and '}' characters, meaning that it's impossible
|
||||
for a user to input a text tag.
|
||||
|
||||
Finally, this release features even more defensive programming
|
||||
involving sound. Now, if the mixer does not initialize, no further
|
||||
sound operations will be performed.
|
||||
|
||||
New in 4.6.2
|
||||
------------
|
||||
|
||||
4.6.2 was never officially released, as it didn't cure all of the
|
||||
sound woes, and didn't have much else in the way of new features.
|
||||
|
||||
Added a speed test, which tests the speed of the dissolve transition
|
||||
on a user's system. This test can be accessed by typing 'S' during the
|
||||
demo (that's shift+the 's' key).
|
||||
|
||||
Fixed a bug with sound on systems with no soundcard in them. (Also,
|
||||
put in a note to remind myself not to re-introduce that bug.)
|
||||
|
||||
Fixed a bug in which imagemaps would not redraw properly.
|
||||
|
||||
New in 4.6.1
|
||||
------------
|
||||
|
||||
Fixed a major memory leak in the rendering code. This leak was
|
||||
rendering 4.6 and 4.5 unusable on smaller systems.
|
||||
|
||||
Executed 250,000 statements (well, 5 statements in a loop, while
|
||||
skipping), and ensured that there was no memory leak during the
|
||||
execution of these statements.
|
||||
|
||||
Added a memory profiler. You can see it by supplying the --leak option
|
||||
to run_game.exe or .py, but don't expect to understand it.
|
||||
|
||||
Changed Dissolve so that it tries to use compositing when it can. This
|
||||
means that it only draws to the screen once... which is a little bit
|
||||
of a speedup on a really expensive operation.
|
||||
|
||||
Fixed a bug that was preventing predictive image loading from
|
||||
working.
|
||||
|
||||
New in 4.6
|
||||
----------
|
||||
|
||||
The demo was updated to show some of the new features listed below.
|
||||
|
||||
There's now a third state that hovered widgets can go into. The
|
||||
activate state is enabled when a button is clicked or a menu choice is
|
||||
selected. The activate state can only be seen when a transition occurs
|
||||
immediately after a button is clicked or a menu choice is selected.
|
||||
|
||||
The margin and padding properties have been broken up, so it's now
|
||||
possible to specify margin and padding for the left, right, top, and
|
||||
bottom of a windows.
|
||||
|
||||
The biggest internal change in this version is the introduction of a
|
||||
layer system in Ren'Py. Transitions can now work on an individual
|
||||
layer (as well as the previous behavior of the whole screen), so it's
|
||||
possible to do things like sliding in and out the dialogue box.
|
||||
|
||||
It's now possible to use transitions when entering and leaving the
|
||||
game menu, by assigning a transition object to
|
||||
library.enter_transition and library.exit_transition, respectively.
|
||||
|
||||
The preferences screen can now be entered from user code, and is
|
||||
now part of the main menu.
|
||||
|
||||
Character objects now support an interact argument when called
|
||||
directly. If it's False, then no interaction occurs. This lets a
|
||||
character window become part of a larger screen.
|
||||
|
||||
Two new extras have been added:
|
||||
|
||||
[list]
|
||||
[*] button_menu.rpy changes the way menus are displayed. Each menu
|
||||
choice is displayed in its own button, roughly centered on the
|
||||
screen. A single menu caption is displayed as narration, if such
|
||||
a caption exists.
|
||||
|
||||
[*] overlay_menu.rpy changes the behavior of right click from
|
||||
showing the game menu to showing a bank of buttons that, when
|
||||
clicked, bring the user to the game menu.
|
||||
[/list]
|
||||
|
||||
The overlay code was overhauled. It's now not possible for an overlay
|
||||
function to return a list of widgets. Instead, the overlay functions
|
||||
are expected to add widgets to the screen using the ui
|
||||
functions. Calling the new function renpy.restart_interaction will,
|
||||
among other things, cause the overlay functions to be called again, if
|
||||
one wants to replace the overlay on the screen.
|
||||
|
||||
A new variable, config.overlay_during_wait, controls if overlays are
|
||||
shown during the execution of wait statements.
|
||||
|
||||
Init blocks of the same priority are now assured to run in the order
|
||||
that they appear in a script file.
|
||||
|
||||
The renpy.interact() function has been eliminated. Use ui.interact()
|
||||
instead, as it performs more error checking.
|
||||
|
||||
Fixed a bug in which some redraws were ignored, and another in which
|
||||
the input widget failed to eat characters. (So typing 'f' in an input
|
||||
widget would be passed through, and eventually cause the game to go to
|
||||
fullscreen mode. No longer.)
|
||||
|
||||
4.5's demo script had a bug that cause it to crash, after playing the
|
||||
MPEG-1 movie. It was a bug in the demo script, and not in Ren'Py
|
||||
proper. It's fixed.
|
||||
|
||||
New in 4.5
|
||||
----------
|
||||
|
||||
We now ship an extras directory, with interesting sample code. It
|
||||
includes:
|
||||
|
||||
[list]
|
||||
[*] fullscreen.rpy, containing code to automatically switch the game
|
||||
into fullscreen on the first run, while preserving the user's
|
||||
preference on later runs.
|
||||
|
||||
[*] 640x480.rpy, which shows how to customize Ren'Py for a game that
|
||||
runs at 640x480.
|
||||
|
||||
[*] kanamode.rpy, contains the code to emulate the interface of the
|
||||
various Digital Object games, which display text one line at a
|
||||
time, but keep a page of text on the screen at once.
|
||||
([url=http://www.bishoujo.us/hosted/kanamode.jpg]screenshot[/url])
|
||||
[/list]
|
||||
|
||||
Added a new CropMove transition. This single class is the root of not
|
||||
one, not two, but fourteen transitions, in the forms of wipes, slides,
|
||||
slideaways, and rectangular irises. You can see some of these
|
||||
transitions in action in the demo, which now has a new section,
|
||||
"What's new with Ren'Py?"
|
||||
|
||||
Added support for playing MPEG-1 movies. Rather than explain it all
|
||||
here, we'l just mention that you can read the new "Movies" subsection
|
||||
of the "Multimedia: Sound, Music, and Movies" section of the Ren'Py
|
||||
manual. We support both hardware-accelerated full-window movies and
|
||||
using a movie as a widget. This is also shown in the what's new
|
||||
section of Ren'Py.
|
||||
|
||||
Added ui.grid(). This is a widget that places its children in a grid.
|
||||
The children must have sizes that do not consider the amount of space
|
||||
available for the grid to work.
|
||||
|
||||
Added ui.pausebehavior(). This separates pausing from the saybehavior
|
||||
paving the way for uninterruptable pauses (a bad idea, IMO) and menus
|
||||
that dismiss themseleves after a certain amount of time.
|
||||
|
||||
A new function, renpy.exists(), can check to see if a given file can
|
||||
be found in the searchpath. This could be used, for example, to make a
|
||||
game that only tries to play music if an add-on music package is
|
||||
downloaded, and the files placed in the appropriate place.
|
||||
|
||||
Added the ability to bind mouse events to mouseup as well as
|
||||
mousedown, and changed some of the default bindings to be on mouseup
|
||||
rather than mousedown. If you make changes to config.keymaps, you'll
|
||||
need to change mouse_1 to mousedown_1, and so on for various other
|
||||
button numbers.
|
||||
|
||||
Moved the place we look for menu sounds from the style of the choices
|
||||
in the menu to the style of the menu itself, as that seems more
|
||||
rational. Also, gave menus their own style. (Can't believe I forgot
|
||||
that.)
|
||||
|
||||
Made a few changes to the library screens. First of all, we
|
||||
rationalized (to some extent, it's still pretty ugly) the names of the
|
||||
styles for library widgets. So some of that may have changed. We
|
||||
placed preferences and the yes/no dialogue inside windows, which can
|
||||
be styled by the user. We then routed all button and label creation
|
||||
through two functions, _button_factory and _label_factory. By
|
||||
overriding these functions, the user can (for example) replace the
|
||||
default textbuttons with imagebuttons.
|
||||
|
||||
Made 'f' toggle fullscreen at the main menu. This will help on virtual
|
||||
windows, where the mouse doesn't work right in fullscreen mode.
|
||||
|
||||
Improved rendering speed. Now, we aggressively cache things to
|
||||
minimize re-renders, and only draw to the screen the parts of the
|
||||
screen that have changed. In common cases, such as the case where only
|
||||
some text has changed, this can greately improve performance. In other
|
||||
cases (transitions), we have to redraw the entire screen anyway, so
|
||||
there's little improvement.
|
||||
|
||||
Fixed a bug in which hiding the UI was not working properly. (This
|
||||
broke the center mouse button behavior.)
|
||||
|
||||
New in 4.4.2
|
||||
------------
|
||||
|
||||
Improved the configurability of the say and menu statements, also
|
||||
changing the way they work. Say statements without a speaker specified
|
||||
are routed through the narrator character, while all menu statements
|
||||
are routed through the menu function. Customizing these functions can
|
||||
customize how Ren'Py interacts with the user, making it a much more
|
||||
flexible engine.
|
||||
|
||||
As part of this, we changed the way character objects work. They are
|
||||
now called directly to display dialogue, rather than having the say
|
||||
method called on them.
|
||||
|
||||
If your game uses variables named "narrator" or "menu", you will need
|
||||
to rename them.
|
||||
|
||||
Changed the way spaces are handled by the text widget. Specifically,
|
||||
they are no longer merged, so it's possible to include spaces in
|
||||
dialogue and thoughts. Please note that the parser still merges
|
||||
adjacent spaces, unless you escape them with a backslash.
|
||||
|
||||
Added a new widget, ui.sizer, that can shrink the amount of space
|
||||
allocated to its child.
|
||||
|
||||
Added two new text properties, first_indent and rest_indent. These
|
||||
properties control how many pixels of indentation to put before each
|
||||
line of text.
|
||||
|
||||
Added a function, color, that can translate a hex triple or quadruple
|
||||
into a Ren'Py color.
|
||||
|
||||
New in 4.4.1
|
||||
------------
|
||||
|
||||
4.4.1 is being released to test some sound fixes, and so it isn't
|
||||
really isn't that complete or well-tested.
|
||||
|
||||
Fixed some longstanding bugs with midi music on Windows. First of all,
|
||||
we now ship with the 1.2.6 version of SDL_mixer, which fixes a bug in
|
||||
1.2.5. That bug caused panning to go to the hard left. We also try to
|
||||
read the windows midi device volumes, and set our volume to match the
|
||||
first one we can read. This should prevent really loud midi music from
|
||||
happening.
|
||||
|
||||
Also, increased the size of the sound buffer to 4096 samples, to
|
||||
prevent skipping.
|
||||
|
||||
Some new features:
|
||||
|
||||
Now, adding interact=False to a Character object will prevent an
|
||||
interaction from occuring.
|
||||
|
||||
Implemented functions to get the amount of time elapsed during the
|
||||
game, renpy.clear_game_runtime() and renpy.get_game_runtime().
|
||||
|
||||
|
||||
New in 4.4
|
||||
----------
|
||||
|
||||
Added a number of features to allow Ren'Py to support statistics-based
|
||||
dating simulations. The first is a new ui.bar() widget, which allows
|
||||
the display of a bar graph. The demo has been enhanced to include a
|
||||
stats and schedule screen, to show how this could be used. You can get
|
||||
to it by asking how to write your own games in the demo, or you can
|
||||
follow the below link to get a screenshot.
|
||||
|
||||
[url]http://www.bishoujo.us/hosted/screenshot.jpg[/url]
|
||||
|
||||
Another feature that has been added were 'jump expression' and 'call
|
||||
expression' statements, allowing computed jumps and calls. Together
|
||||
with an imporoved scheduler and yet-to-be written event dispatcher,
|
||||
this provides the foundation for SBDSes.
|
||||
|
||||
Now, setting a property on a style also sets the hover_ and idle_
|
||||
variants of that property. This makes changing a property of an
|
||||
inherited style a bit saner. In addition, hover now propagates to all
|
||||
children of a button. This means that hover will now work with the
|
||||
styles in the file chooser.
|
||||
|
||||
Added support for layering images. This support (invoked by passing a
|
||||
tuple as an image filename) allows a single image to be constructed
|
||||
from multiple image files, saving disk space while still keeping
|
||||
performance.
|
||||
|
||||
Improved skipping. Now, along with control causing skipping of seen
|
||||
dialogue, TAB toggles skip mode. An indicator displays to let you know
|
||||
that skip mode is enabled. Finally, if a game author wants to disable
|
||||
skipping, he can set config.allow_skipping to false.
|
||||
|
||||
(The following were also in 4.3.2, which was a private release to
|
||||
test the fixes for UTF-8 support.)
|
||||
|
||||
There were two bugs with UTF-8 support. The first was that we didn't
|
||||
understand the byte-order mark, and choked on the syntax error. The
|
||||
second was that the error-reporting code wasn't passing unicode errors
|
||||
through properly, so one couldn't even see the real error. Both are
|
||||
now fixed.
|
||||
|
||||
Improved the reporting of errors that occur during script loading and
|
||||
interpreter initialization by no longer giving a line number when none
|
||||
is appropriate.
|
||||
|
||||
Ren'Py now uses the name of the executable to choose the directory to
|
||||
read the script from. It does this by looking at the name of the
|
||||
executable that was used to run Ren'Py. It strips off the extension,
|
||||
and anything preceding the first underscore in the name, if such a
|
||||
thing exists. It then looks to see if that directory exists, and if it
|
||||
does, uses it. For example, if the program is named "run_en.exe", the
|
||||
"en" directory is used, while if the program is named "homestay.exe",
|
||||
the directory "homestay" is used.
|
||||
|
||||
The config variable config.searchpath is a list of directory that are
|
||||
searched for image files and other media (but not scripts, since all
|
||||
scripts are loaded before the variable can be set). This allows
|
||||
multiple game directories to share images, music, and other data files.
|
||||
|
||||
Once again, I redid the file chooser and default styles. The new file
|
||||
chooser displays 10 entries in two columns, with each entry being
|
||||
shown with an image. The files are now orginized into numbered slots,
|
||||
and it's possible to save in a slot without saving in all previous
|
||||
slots. There were also some changes to the non-user-visible parts of
|
||||
loading and saving.
|
||||
|
||||
Speed Enhancements:
|
||||
|
||||
We now precompile python blocks in Ren'Py scripts, and store the
|
||||
compiled code in the .rpyc files. This makes loading an unmodified
|
||||
script significantly faster. On my system, this more than halved the
|
||||
time it took for the demo script to load. On the dowside, when a
|
||||
script is changed, it now can take somewhat longer for it to begin
|
||||
running, as all the changed code needs to be recompiled. Overall,
|
||||
it's a win, provided you ship the .rpyc files to your users.
|
||||
|
||||
Did another round of profiling, and found that styles were
|
||||
significanly slowing the system down. So I rewrote them to be much
|
||||
faster. This change shouldn't be user-visible, except that your game
|
||||
will feel a bit peppier.
|
||||
|
||||
Added a 1-entry cache for solid fill surfaces. If your game uses them,
|
||||
this might make it go faster.
|
||||
|
||||
New in 4.3.1
|
||||
------------
|
||||
|
||||
New, but undocumented, in 4.3 was the console.exe windows binary. This
|
||||
is a windows executable that opens a windows console. This could be
|
||||
useful if one wants to have a script that prints debugging information
|
||||
to standard output, as in:
|
||||
|
||||
[code]
|
||||
$ a = 1
|
||||
$ print "a =", a
|
||||
[/code]
|
||||
|
||||
There is now a new style property, enable_hover. This must be set to
|
||||
True for a widget to respond to hovering by changing its
|
||||
style. However, if it's set to False, no style change/redraw occurs,
|
||||
making Ren'Py snappier. It defaults to True.
|
||||
|
||||
Ren'Py is now built with psyco, the python specializing compiler. This
|
||||
makes program startup a bit slower, but in return the running time
|
||||
once it's loaded should be faster.
|
||||
|
||||
A new config variable, config.window_icon, allows the user to specify
|
||||
an image file to use for the window icon. Counterintuitively, this
|
||||
should probably be a PNG, as the ICO format is not supported by the
|
||||
SDL_image library.
|
||||
|
||||
Game menu navigations and preference buttons now have their own
|
||||
styles, allowing their look to be customized.
|
||||
|
||||
A bug that prevented the reloading of persistent data on windows has
|
||||
been fixed.
|
||||
|
||||
New in 4.3
|
||||
----------
|
||||
|
||||
Now, when the library first starts up, it tries calling the
|
||||
"splashscreen" label, if it exists. The splashscreen will not be
|
||||
called in the case of a full reset (known to the user as a return to
|
||||
the main menu).
|
||||
|
||||
Added a new text property, antialias, that controls the antialiasing
|
||||
of text. Also made the font property optionally take a comma-separated
|
||||
list of font names, which are searched for in the
|
||||
|
||||
Added a new class, DynamicCharacter, which is now the preferred way of
|
||||
having a character with a changable and/or user-input name.
|
||||
|
||||
A new random number generator has been implemented as
|
||||
renpy.random. This random number generator cooperates with rollback,
|
||||
always producing the same random numbers when a rollback occurs.
|
||||
|
||||
Added a set of UI functions, that allow the user to build up a user
|
||||
interface themselves. These could be useful when implementing more
|
||||
complex games, such as those that require the user to schedule a day
|
||||
in advance. Also new is a new imagebutton, a button consisting of two
|
||||
images.
|
||||
|
||||
Sticky postions have been implemented. Enabling this (using the
|
||||
variable config.sticky_positions) cause the at clause corresponding to
|
||||
a given character to be remembered, allowing the character to change
|
||||
emotion without changing places on the screen (or requiring a second
|
||||
at clause).
|
||||
|
||||
Rewrote the implementation of preferences, to clean up the code. Moved
|
||||
it into its own file, preferences.rpy. Also, made it possible for a
|
||||
user to add his own preferences into the system, although that isn't
|
||||
really recommended.
|
||||
|
||||
The various imagemap functions now have a new parameter, unselected,
|
||||
which gives an image to use when a hotspot is present but not
|
||||
hovered. This lets us distinguish between between hotspots that are
|
||||
present and exist, and hotspots that are totally absent. This could be
|
||||
used to implement something like an image gallery that only displays
|
||||
unlocked images.
|
||||
|
||||
Implemented config.keymap, which allows the changing of keys and mouse
|
||||
buttons that trigger various Ren'Py events.
|
||||
|
||||
There's now a new look for loading and saving games, that allows 10
|
||||
saves to be presented at once. I strongly recommend removing the
|
||||
library.file_page_length line at the start of your game scripts, if
|
||||
it exists.
|
||||
|
||||
Fixed a bug in sound.init that lead to crashes in some cases.
|
||||
|
||||
Fixed a bug in ast.Node.predict that was causing an exception when
|
||||
config.debug was turned on.
|
||||
|
||||
New in 4.2
|
||||
----------
|
||||
@@ -24,6 +996,9 @@ init:
|
||||
$ style.window.background = Frame("frame.png", 125, 25)
|
||||
[/code]
|
||||
|
||||
Fixed several bugs with the archiver. This version just might actually
|
||||
work on Windows.
|
||||
|
||||
Added a new variable, config.hard_rollback_limit, which limits the
|
||||
number of steps the user can rollback the game, interactively. This
|
||||
limit now defaults to 10 steps. (suggested by Grey)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
Copyright 2004-2005 PyTom <pytom@bishoujo.us>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Please note that the above license only applies to Ren'Py
|
||||
proper. A binary distribution of Ren'Py contains a number of other
|
||||
packages, each with their own licenses, which you may be bound to
|
||||
comply with when distributing a Ren'Py game.
|
||||
|
||||
These packages may include:
|
||||
|
||||
Python (Python License)
|
||||
http://www.python.org
|
||||
|
||||
Pygame (GNU LGPL)
|
||||
http://www.pygame.org
|
||||
|
||||
SDL (GNU LGPL)
|
||||
http://www.libsdl.org
|
||||
|
||||
SDL_mixer (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_mixer
|
||||
|
||||
SDL_ttf (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_ttf
|
||||
|
||||
ctypes (MIT)
|
||||
http://starship.python.net/crew/theller/ctypes/
|
||||
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,55 @@
|
||||
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.
|
||||
|
||||
Feel free to use this in your projects.
|
||||
|
||||
|
||||
|
||||
Running the Game
|
||||
================
|
||||
|
||||
(Windows)
|
||||
|
||||
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.
|
||||
|
||||
(Macintosh)
|
||||
|
||||
For information about running Ren'Py games on Mac OS 10.3 or higher,
|
||||
please go to http://www.bishoujo.us/renpy/mac.html .
|
||||
|
||||
(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.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Have a new form of imagemap that uses a color image to distinguish
|
||||
hotspots.
|
||||
|
||||
Keyd compiles.
|
||||
@@ -6,6 +6,7 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
def find_labels(fn, labels):
|
||||
|
||||
@@ -17,6 +18,10 @@ def find_labels(fn, labels):
|
||||
if m:
|
||||
labels[m.group(1)] = True
|
||||
|
||||
m = re.match(r'^\s*call\s+expression.*from\s+(\w+)\s*$', l)
|
||||
if m:
|
||||
labels[m.group(1)] = True
|
||||
|
||||
f.close()
|
||||
|
||||
def replace_labels(fn, labels):
|
||||
@@ -43,25 +48,45 @@ def replace_labels(fn, labels):
|
||||
|
||||
for l in f:
|
||||
l = re.sub(r'^(\s*)call\s+(\w+)(\s*$)', replaceit, l)
|
||||
|
||||
if re.search(r'call\s+expression', l) and not re.search('from', l):
|
||||
|
||||
num = 0
|
||||
|
||||
while True:
|
||||
num += 1
|
||||
label = "_call_expression_%d" % num
|
||||
|
||||
if label not in labels:
|
||||
break
|
||||
|
||||
labels[label] = label
|
||||
|
||||
l = l[:-1] + " from " + label + "\n"
|
||||
|
||||
of.write(l)
|
||||
|
||||
f.close()
|
||||
of.close()
|
||||
|
||||
|
||||
try:
|
||||
os.unlink(fn + ".bak")
|
||||
except:
|
||||
pass
|
||||
|
||||
os.rename(fn, fn + ".bak")
|
||||
os.rename(fn + ".new", fn)
|
||||
|
||||
def main():
|
||||
|
||||
gamedir = "game"
|
||||
pattern = "*/*.rpy"
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
gamedir = sys.argv[1]
|
||||
pattern = sys.argv[1]
|
||||
|
||||
print "Processing files in", gamedir
|
||||
|
||||
files = [ gamedir + "/" + i for i in os.listdir(gamedir)
|
||||
if i.endswith(".rpy") ]
|
||||
files = glob.glob(pattern)
|
||||
files = [ i for i in files if not i.startswith("common/") ]
|
||||
|
||||
labels = { }
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
cd images
|
||||
cd game
|
||||
..\archiver.exe images *.png *.jpg
|
||||
pause
|
||||
|
||||
@@ -8,6 +8,7 @@ import sys
|
||||
import os
|
||||
import encodings.zlib_codec
|
||||
import random
|
||||
import glob
|
||||
|
||||
from cPickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
|
||||
@@ -47,7 +48,13 @@ def main():
|
||||
|
||||
offset = 0
|
||||
|
||||
for fn in sys.argv[2:]:
|
||||
# Needed because windows sucks. It doesn't do globbing on the
|
||||
# command line.
|
||||
files = [ ]
|
||||
for i in sys.argv[2:]:
|
||||
files.extend(glob.glob(i))
|
||||
|
||||
for fn in files:
|
||||
index[fn] = [ ]
|
||||
|
||||
print "Adding %s..." % fn
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
build_exe.py run_game
|
||||
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=programs,
|
||||
console=[ "archiver.py", "add_from.py" ],
|
||||
console=[ "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
|
||||
@@ -20,35 +20,80 @@ init -500:
|
||||
# Used to store library settings.
|
||||
library = object()
|
||||
|
||||
# The number of files to show at once.
|
||||
library.file_page_length = 4
|
||||
# The minimum version of the module we work with. Don't change
|
||||
# this unless you know what you're doing.
|
||||
library.module_version = 4008002
|
||||
|
||||
# A small amount of padding.
|
||||
library.padding = 5
|
||||
|
||||
# The width of a thumbnail.
|
||||
library.thumbnail_width = 100
|
||||
|
||||
# The height of a thumbnail.
|
||||
library.thumbnail_height = 75
|
||||
|
||||
# The contents of the main menu.
|
||||
library.main_menu = [
|
||||
( "Start Game", "start" ),
|
||||
( "Continue Game", "_continue" ),
|
||||
( "Quit Game", "_quit" ),
|
||||
]
|
||||
# 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 = { }
|
||||
|
||||
# True if the skip indicator should be shown.
|
||||
library.skip_indicator = True
|
||||
|
||||
# This is updated to give the user an idea of where a save is
|
||||
# taking place.
|
||||
save_name = ''
|
||||
|
||||
# The function that's used to translate strings in the game menu.
|
||||
init:
|
||||
python:
|
||||
def _button_factory(label,
|
||||
type=None,
|
||||
selected=None,
|
||||
disabled=False,
|
||||
clicked=None,
|
||||
**properties):
|
||||
"""
|
||||
This function is called to create the various buttons used
|
||||
in the game menu. By overriding this function, one can
|
||||
(for example) replace the default textbuttons with image buttons.
|
||||
When it is called, it's expected to add a button to the screen.
|
||||
|
||||
@param label: The label of this button, before translation.
|
||||
|
||||
@param type: The type of the button. One of "mm" (main menu),
|
||||
"gm_nav" (game menu), "file_picker_nav", "yesno", or "prefs".
|
||||
|
||||
@param selected: True if the button is selected, False if not,
|
||||
or None if it doesn't matter.
|
||||
|
||||
@param disabled: True if the button is disabled, False if not.
|
||||
|
||||
@param clicked: A function that should be executed when the
|
||||
button is clicked.
|
||||
|
||||
@param properties: Addtional layout properties.
|
||||
"""
|
||||
|
||||
style = type
|
||||
|
||||
if selected and not disabled:
|
||||
style += "_selected"
|
||||
|
||||
if disabled:
|
||||
clicked = None
|
||||
|
||||
style = style + "_button"
|
||||
text_style = style + "_text"
|
||||
|
||||
ui.textbutton(_(label), style=style, text_style=text_style, clicked=clicked, **properties)
|
||||
|
||||
def _label_factory(label, type, **properties):
|
||||
"""
|
||||
This function is called to create a new label. It can be
|
||||
overridden by the user to change how these labels are created.
|
||||
|
||||
@param label: The label of the box.
|
||||
|
||||
@param type: "prefs" or "yesno".
|
||||
|
||||
@param properties: This may contain position properties.
|
||||
"""
|
||||
|
||||
ui.text(_(label), style=type + "_label", **properties)
|
||||
|
||||
# The function that's used to translate strings in the game menu.
|
||||
def _(s):
|
||||
"""
|
||||
Translates s into another language or something.
|
||||
@@ -59,360 +104,93 @@ init:
|
||||
else:
|
||||
return s
|
||||
|
||||
##############################################################################
|
||||
|
||||
init:
|
||||
|
||||
python:
|
||||
# Called to make a screenshot happen.
|
||||
def _screenshot():
|
||||
renpy.screenshot("screenshot.bmp")
|
||||
|
||||
# Are the windows currently hidden?
|
||||
_windows_hidden = False
|
||||
|
||||
# Hides the windows.
|
||||
def _hide_windows():
|
||||
global _windows_hidden
|
||||
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
try:
|
||||
_windows_hidden = True
|
||||
renpy.interact(renpy.SayBehavior())
|
||||
finally:
|
||||
_windows_hidden = False
|
||||
|
||||
# A keymouse object that we use on the mainmenu and gamemenu
|
||||
# screens.
|
||||
_keymouse = renpy.KeymouseBehavior()
|
||||
|
||||
# Set up the default keymap.
|
||||
python hide:
|
||||
|
||||
# Called to make a screenshot happen.
|
||||
def screenshot():
|
||||
renpy.screenshot("screenshot.bmp")
|
||||
|
||||
def invoke_game_menu():
|
||||
renpy.play(library.enter_sound)
|
||||
renpy.call_in_new_context('_game_menu')
|
||||
|
||||
def toggle_skipping():
|
||||
config.skipping = not config.skipping
|
||||
|
||||
# The default keymap.
|
||||
km = renpy.Keymap(
|
||||
K_PAGEUP = renpy.rollback,
|
||||
mouse_4 = renpy.rollback,
|
||||
s = _screenshot,
|
||||
f = renpy.toggle_fullscreen,
|
||||
m = renpy.toggle_music,
|
||||
K_ESCAPE = renpy.curried_call_in_new_context("_game_menu"),
|
||||
mouse_3 = renpy.curried_call_in_new_context("_game_menu"),
|
||||
mouse_2 = _hide_windows,
|
||||
rollback = renpy.rollback,
|
||||
screenshot = screenshot,
|
||||
toggle_fullscreen = renpy.toggle_fullscreen,
|
||||
toggle_music = renpy.toggle_music,
|
||||
toggle_skip = toggle_skipping,
|
||||
game_menu = invoke_game_menu,
|
||||
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
|
||||
)
|
||||
|
||||
config.underlay = [ km ]
|
||||
|
||||
return
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
label _start:
|
||||
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:
|
||||
|
||||
# The skip indicator.
|
||||
python hide:
|
||||
|
||||
# Show the main menu screen.
|
||||
vbox = renpy.VBox()
|
||||
def skip_indicator():
|
||||
|
||||
for text, label in library.main_menu:
|
||||
vbox.add(renpy.TextButton(text, clicked=_return(label)))
|
||||
if config.allow_skipping and library.skip_indicator:
|
||||
|
||||
menu_window = renpy.Window(vbox, style='mm_menu_window')
|
||||
|
||||
fixed = renpy.Fixed()
|
||||
fixed.add(menu_window)
|
||||
ui.conditional("config.skipping")
|
||||
ui.text(_("Skip Mode"), style='skip_indicator')
|
||||
|
||||
root_window = renpy.Window(fixed, style='mm_root_window')
|
||||
|
||||
store._result = renpy.interact(_keymouse, root_window,
|
||||
suppress_overlay=True,
|
||||
suppress_underlay=True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
$ renpy.jump(_result)
|
||||
config.overlay_functions.append(skip_indicator)
|
||||
|
||||
return
|
||||
|
||||
# Used to call the game menu.
|
||||
label _continue:
|
||||
$ renpy.call_in_new_context("_load_menu")
|
||||
label _hide_windows:
|
||||
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
python:
|
||||
_windows_hidden = True
|
||||
ui.saybehavior()
|
||||
ui.interact(suppress_overlay=True)
|
||||
_windows_hidden = False
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
jump _library_main_menu
|
||||
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Code for the game menu.
|
||||
|
||||
init -500:
|
||||
python:
|
||||
|
||||
# This returns a window containing the game menu navigation
|
||||
# buttons, set up to jump to the appropriate screen sections.
|
||||
def _game_nav(selected):
|
||||
|
||||
buttons = [
|
||||
( "return", _("Return to Game"), "_return"),
|
||||
( "load", _("Load Game"), "_load_screen" ),
|
||||
( "save", _("Save Game"), "_save_screen" ),
|
||||
( "prefs", _("Preferences"), "_prefs_screen" ),
|
||||
( "mainmenu", _("Main Menu"), "_full_restart" ),
|
||||
( "quit", _("Quit Game"), "_confirm_quit" ),
|
||||
]
|
||||
|
||||
vbox = renpy.VBox()
|
||||
win = renpy.Window(vbox, style='gm_nav_window')
|
||||
|
||||
for key, label, target in buttons:
|
||||
style="button"
|
||||
text_style="button_text"
|
||||
|
||||
if key == selected:
|
||||
style = 'selected_button'
|
||||
text_style = 'selected_button_text'
|
||||
|
||||
def clicked(target=target):
|
||||
renpy.jump(target)
|
||||
|
||||
tb = renpy.TextButton(label,
|
||||
style=style,
|
||||
text_style=text_style,
|
||||
clicked=clicked)
|
||||
vbox.add(tb)
|
||||
|
||||
return win
|
||||
|
||||
def _game_interact(selected, *widgets):
|
||||
|
||||
fixed = renpy.Fixed()
|
||||
win = renpy.Window(fixed, style='gm_root_window')
|
||||
fixed.add(_game_nav(selected))
|
||||
|
||||
for w in widgets:
|
||||
fixed.add(w)
|
||||
|
||||
return renpy.interact(_keymouse, win,
|
||||
suppress_underlay=True,
|
||||
suppress_overlay=True
|
||||
)
|
||||
|
||||
_file_picker_index = 0
|
||||
|
||||
def _render_filename(filename, newest_filename):
|
||||
|
||||
if filename is None:
|
||||
return renpy.Text(_("Save in new slot."), style='file_picker_new_slot')
|
||||
|
||||
hbox = renpy.HBox(padding=library.padding)
|
||||
|
||||
if filename == newest_filename:
|
||||
hbox.add(renpy.Text(_("New"), style='file_picker_new'))
|
||||
else:
|
||||
hbox.add(renpy.Text(_("Old"), style='file_picker_old'))
|
||||
|
||||
hbox.add(renpy.load_screenshot(filename))
|
||||
|
||||
hbox.add(renpy.Text(renpy.load_extra_info(filename), style='file_picker_extra_info' ))
|
||||
|
||||
return hbox
|
||||
|
||||
# This displays a file picker that can chose a save file from
|
||||
# the list of save files.
|
||||
def _file_picker(selected, files):
|
||||
|
||||
nsg = renpy.newest_save_game()
|
||||
|
||||
while True:
|
||||
|
||||
if _file_picker_index >= len(files):
|
||||
store._file_picker_index -= library.file_page_length
|
||||
|
||||
if _file_picker_index < 0:
|
||||
store._file_picker_index = 0
|
||||
|
||||
fpi = _file_picker_index
|
||||
|
||||
cur_files = files[fpi:fpi + library.file_page_length]
|
||||
|
||||
vbox = renpy.VBox()
|
||||
|
||||
hbox = renpy.HBox(padding=library.padding * 3)
|
||||
|
||||
def tb(cond, label, clicked):
|
||||
if cond:
|
||||
style = 'button'
|
||||
text_style = 'button_text'
|
||||
else:
|
||||
style = 'disabled_button'
|
||||
text_style = 'disabled_button_text'
|
||||
|
||||
return renpy.TextButton(label, style=style, text_style=text_style, clicked=clicked)
|
||||
|
||||
|
||||
hbox.add(tb(fpi > 0,
|
||||
_('Previous Page'), _return(("fpidelta", -1))))
|
||||
hbox.add(tb(fpi + library.file_page_length < len(files),
|
||||
_('Next Page'), _return(("fpidelta", +1))))
|
||||
vbox.add(hbox)
|
||||
# This code here handles check for the correct version of the Ren'Py module.
|
||||
|
||||
for i in cur_files:
|
||||
child = _render_filename(i, nsg)
|
||||
|
||||
button = renpy.Button(child,
|
||||
style='file_picker_entry',
|
||||
clicked=_return(("return", i)))
|
||||
label _check_module:
|
||||
|
||||
vbox.add(button)
|
||||
|
||||
win = renpy.Window(vbox, style='file_picker_window')
|
||||
|
||||
result = _game_interact(selected, win)
|
||||
|
||||
type, value = result
|
||||
|
||||
if type == "return":
|
||||
return value
|
||||
|
||||
if type == "fpidelta":
|
||||
store._file_picker_index += value * library.file_page_length
|
||||
|
||||
def _yesno_prompt(screen, message):
|
||||
|
||||
prompt = renpy.Text(message, style='yesno_prompt')
|
||||
yes = renpy.TextButton(_("Yes"), style='yesno_yes',
|
||||
clicked=_return(True))
|
||||
no = renpy.TextButton(_("No"), style='yesno_no',
|
||||
clicked=_return(False))
|
||||
|
||||
return _game_interact(screen, prompt, yes, no)
|
||||
|
||||
# Returns a button for a single preference and value.
|
||||
def _prefbutton(label, var, value):
|
||||
|
||||
def clicked():
|
||||
setattr(_preferences, var, value)
|
||||
return True
|
||||
|
||||
style = 'button'
|
||||
text_style = 'button_text'
|
||||
|
||||
if getattr(_preferences, var) == value:
|
||||
style = 'selected_button'
|
||||
text_style = 'selected_button_text'
|
||||
|
||||
return renpy.TextButton(_(label), style=style,
|
||||
text_style=text_style, clicked=clicked)
|
||||
|
||||
# Returns a vbox for a single preference.
|
||||
def _prefvbox(label, var, entries):
|
||||
|
||||
rv = renpy.VBox(style='prefs_pref')
|
||||
rv.add(renpy.Text(_(label), style='prefs_label'))
|
||||
|
||||
for blabel, value in entries:
|
||||
rv.add(_prefbutton(blabel, var, value))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
|
||||
label _load_menu:
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu:
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
|
||||
jump _save_screen
|
||||
|
||||
label _load_screen:
|
||||
|
||||
python:
|
||||
_fn = _file_picker("load", renpy.saved_game_filenames() )
|
||||
|
||||
python:
|
||||
renpy.load(_fn)
|
||||
|
||||
jump _load_screen
|
||||
|
||||
label _save_screen:
|
||||
$ _fn = _file_picker("save", renpy.saved_game_filenames() + [ None ] )
|
||||
|
||||
if not _fn or _yesno_prompt("save", _("Are you sure you want to overwrite your save?")):
|
||||
$ renpy.save(_fn, renpy.time.strftime("%Y-%m-%d %H:%M:%S\n") + save_name)
|
||||
|
||||
jump _save_screen
|
||||
|
||||
# The preferences screen.
|
||||
label _prefs_screen:
|
||||
if not library.module_warning:
|
||||
return
|
||||
|
||||
python hide:
|
||||
prefs_left = [
|
||||
( 'Display', 'fullscreen',
|
||||
[ ('Window', False), ('Fullscreen', True) ] ),
|
||||
( 'Music', 'music',
|
||||
[ ('Enabled', True), ('Disabled', False) ] ),
|
||||
( 'Sound Effects', 'sound',
|
||||
[ ('Enabled', True), ('Disabled', False) ] ),
|
||||
]
|
||||
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/.")
|
||||
|
||||
prefs_right = [
|
||||
('CTRL Skips', 'skip_unseen',
|
||||
[ ('Seen Messages', False), ('All Messages', True) ] ),
|
||||
('Transitions', 'transitions',
|
||||
[ ('All', 2), ('Some', 1), ('None', 0) ]),
|
||||
]
|
||||
|
||||
if config.annoying_text_cps:
|
||||
prefs_right.append(('Text Display', 'fast_text', [ ('Slow', False), ('Fast', True) ]))
|
||||
|
||||
vbox_left = renpy.VBox(padding=library.padding * 3, style='prefs_left')
|
||||
|
||||
for label, var, entries in prefs_left:
|
||||
vbox_left.add(_prefvbox(label, var, entries))
|
||||
|
||||
vbox_right = renpy.VBox(padding=library.padding * 3, style='prefs_right')
|
||||
|
||||
for label, var, entries in prefs_right:
|
||||
vbox_right.add(_prefvbox(label, var, entries))
|
||||
|
||||
_game_interact("prefs", vbox_left, vbox_right)
|
||||
|
||||
jump _prefs_screen
|
||||
|
||||
|
||||
|
||||
|
||||
# Asks the user if he wants to quit.
|
||||
label _confirm_quit:
|
||||
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()
|
||||
|
||||
# Return to the game, after restoring the keymap.
|
||||
label _return:
|
||||
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
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# This file contains the code to implement the Ren'Py preferences
|
||||
# screen.
|
||||
|
||||
init -450:
|
||||
python:
|
||||
|
||||
# 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):
|
||||
"""
|
||||
This is a class that's used to represent a preference that
|
||||
may be shown to the user.
|
||||
"""
|
||||
|
||||
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 base object
|
||||
that will be assigned the selected value. This field
|
||||
must exist.
|
||||
|
||||
@param values: A list of value name, value, condition
|
||||
triples. The value name is the name of this value that
|
||||
will be shown to the user. The value is the literal
|
||||
python value that will be assigned if this value is
|
||||
selected. The condition is a condition that will be
|
||||
evaluated to determine if this is a legal value. If no
|
||||
conditions are true, this preference will not be
|
||||
displayed to the user. A condition of None is always
|
||||
considered to be True.
|
||||
|
||||
@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
|
||||
if cond is None or renpy.eval(cond) ]
|
||||
|
||||
if not values:
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
for name, value in values:
|
||||
|
||||
def clicked(value=value):
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
_button_factory(name, "prefs",
|
||||
selected=cur==value,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
class _PreferenceSpinner(object):
|
||||
"""
|
||||
This is a class that's used to represent a preference
|
||||
spinner, which is a preference that can be incremented
|
||||
and decremented, when shown to the user.
|
||||
"""
|
||||
|
||||
def __init__(self, name, field, minimum, maximum, delta,
|
||||
cond = "True", render = lambda x : str(x),
|
||||
base=_preferences):
|
||||
"""
|
||||
@param name: The name of this preference, that is presented
|
||||
to the user.
|
||||
|
||||
@param field: The name of the field on the base object
|
||||
that is updated by this spinner.
|
||||
|
||||
@param minimum: The minimum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param maximum: The maximum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param delta: The delta by which this spinner is
|
||||
incremented or decremented.
|
||||
|
||||
@param cond: If this condition is not true, this spinner is
|
||||
not shown.
|
||||
|
||||
@param render: This function is called with the value of
|
||||
the field, and is expected to render that value to a
|
||||
string.
|
||||
|
||||
@param base: The base object that this spinner updates
|
||||
the field on. It defaults to _preferences, the preferences
|
||||
object.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
self.delta = delta
|
||||
self.cond = cond
|
||||
self.render = render
|
||||
self.base = base
|
||||
|
||||
def render_preference(self):
|
||||
|
||||
if not renpy.eval(self.cond):
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
|
||||
def minus_clicked():
|
||||
value = cur - self.delta
|
||||
value = max(self.minimum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
def plus_clicked():
|
||||
value = cur + self.delta
|
||||
value = min(self.maximum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
ui.hbox(style='prefs_spinner')
|
||||
_button_factory("-", "prefs_spinner", clicked=minus_clicked)
|
||||
_label_factory(self.render(cur), "prefs_spinner")
|
||||
_button_factory("+", "prefs_spinner", clicked=plus_clicked)
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
|
||||
|
||||
|
||||
python hide:
|
||||
|
||||
# Enablers for some preferences.
|
||||
library.has_music = True
|
||||
library.has_sound = True
|
||||
library.has_transitions = True
|
||||
library.has_cps = True
|
||||
|
||||
|
||||
p1 = _Preference('Display', 'fullscreen', [
|
||||
('Window', False, None),
|
||||
('Fullscreen', True, None),
|
||||
])
|
||||
|
||||
p2 = _Preference('Music', 'music', [
|
||||
('Enabled', True, 'library.has_music'),
|
||||
('Disabled', False, 'library.has_music'),
|
||||
])
|
||||
|
||||
p3 = _Preference('Sound Effects', 'sound', [
|
||||
('Enabled', True, 'library.has_sound'),
|
||||
('Disabled', False, 'library.has_sound'),
|
||||
])
|
||||
|
||||
|
||||
library.preferences['prefs_left'] = [ p1, p2, p3 ]
|
||||
|
||||
p4 = _Preference('TAB and CTRL Skip', 'skip_unseen', [
|
||||
('Seen Messages', False, 'config.allow_skipping'),
|
||||
('All Messages', True, 'config.allow_skipping'),
|
||||
])
|
||||
|
||||
p5 = _Preference('Transitions', 'transitions', [
|
||||
('All', 2, 'library.has_transitions'),
|
||||
('Some', 1, 'library.has_transitions and default_transition'),
|
||||
('None', 0, 'library.has_transitions'),
|
||||
])
|
||||
|
||||
# p6 = _Preference('Text Display', 'fast_text', [
|
||||
# ('Fast', True, 'config.annoying_text_cps'),
|
||||
# ('Slow', False, 'config.annoying_text_cps'),
|
||||
# ])
|
||||
|
||||
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:
|
||||
|
||||
python hide:
|
||||
|
||||
_game_nav("prefs")
|
||||
|
||||
ui.window(style='prefs_window')
|
||||
ui.fixed()
|
||||
|
||||
|
||||
for style, prefs in library.preferences.iteritems():
|
||||
|
||||
ui.vbox(library.padding * 3, style=style)
|
||||
for i in prefs:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
_game_interact()
|
||||
|
||||
jump _prefs_screen
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,27 +14,40 @@
|
||||
# to your script. No need to mess around here, it will just make your
|
||||
# life harder when a new version of Ren'Py is released.
|
||||
|
||||
init -250:
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
style.create('default', None,
|
||||
'The default style that all styles inherit from.')
|
||||
|
||||
dark_cyan = (0, 192, 255, 255)
|
||||
bright_cyan = (0, 255, 255, 255)
|
||||
|
||||
dark_red = (255, 128, 128, 255)
|
||||
bright_red = (255, 64, 64, 255)
|
||||
|
||||
green = (0, 128, 0, 255)
|
||||
|
||||
# Magic.
|
||||
style.default.enable_hover = True
|
||||
|
||||
# Text properties.
|
||||
style.default.font = "Vera.ttf"
|
||||
style.default.antialias = True
|
||||
style.default.size = 22
|
||||
style.default.color = (255, 255, 255, 255)
|
||||
style.default.drop_shadow = (2, 2)
|
||||
style.default.bold = False
|
||||
style.default.italic = False
|
||||
style.default.underline = False
|
||||
style.default.drop_shadow = (1, 1)
|
||||
style.default.drop_shadow_color = (0, 0, 0, 128)
|
||||
style.default.minwidth = 0
|
||||
style.default.textalign = 0
|
||||
style.default.text_y_fudge = 0
|
||||
style.default.first_indent = 0
|
||||
style.default.rest_indent = 0
|
||||
style.default.line_spacing = 0
|
||||
|
||||
# Change this if you're not using Vera 22.
|
||||
if renpy.windows():
|
||||
style.default.line_height_fudge = -4
|
||||
else:
|
||||
style.default.line_height_fudge = 0
|
||||
|
||||
# Window properties.
|
||||
style.default.background = None
|
||||
style.default.xpadding = 0
|
||||
@@ -51,10 +64,11 @@ init -250:
|
||||
style.default.ypos = 0
|
||||
style.default.xanchor = 'left'
|
||||
style.default.yanchor = 'top'
|
||||
style.default.xmaximum = None
|
||||
style.default.ymaximum = None
|
||||
|
||||
# Sound properties.
|
||||
style.default.hover_sound = None
|
||||
style.default.activate_sound = None
|
||||
style.default.sound = None
|
||||
|
||||
# The base style for the large windows.
|
||||
style.create('window', 'default',
|
||||
@@ -92,6 +106,8 @@ init -250:
|
||||
the label of dialogue. The label is used to
|
||||
indicate who is saying something.""")
|
||||
|
||||
style.say_label.bold = True
|
||||
|
||||
style.create('say_dialogue', 'default',
|
||||
"""(text) The style that is used by default for
|
||||
the text of dialogue.""")
|
||||
@@ -107,14 +123,22 @@ init -250:
|
||||
|
||||
# Styles that are used for menus.
|
||||
|
||||
style.create('menu', 'default',
|
||||
"(position) The style that is used for the vbox containing a menu.")
|
||||
|
||||
style.create('menu_caption', 'default',
|
||||
"""(text) The style that is used to render a menu
|
||||
caption.""")
|
||||
"(text) The style that is used to render a menu caption.")
|
||||
|
||||
style.create('menu_choice', 'default',
|
||||
"""(text, hover, sound) The style that is used to render a menu choice.""")
|
||||
"""(text, hover) The style that is used to render
|
||||
the text of a menu choice.""")
|
||||
|
||||
style.create('menu_choice_button', 'default',
|
||||
"""(window, hover, sound) The style that is used
|
||||
to render the button containing a menu choice.""")
|
||||
|
||||
style.menu_choice.hover_color = (255, 255, 0, 255) # yellow
|
||||
style.menu_choice.activate_color = (255, 255, 0, 255) # yellow
|
||||
style.menu_choice.idle_color = (0, 255, 255, 255) # cyan
|
||||
|
||||
style.create('menu_window', 'window',
|
||||
@@ -157,10 +181,20 @@ init -250:
|
||||
|
||||
# Styles that are used by imagemaps
|
||||
style.create('imagemap', 'image_placement',
|
||||
'(sound, position) The style that is used for imagemaps.')
|
||||
'(position) The style that is used for imagemaps.')
|
||||
|
||||
style.create('imagemap_button', 'default',
|
||||
'(window, sound, hover) The style that is used for buttons inside imagemaps.')
|
||||
|
||||
# Styles that are used by imagebutttons.
|
||||
style.create('image_button', 'default',
|
||||
'(window, sound, hover) The default style used for image buttons.')
|
||||
|
||||
style.create('image_button_image', 'default',
|
||||
'The default style used for images inside image buttons.')
|
||||
|
||||
|
||||
# Styles that are used by all Buttons.
|
||||
# Styles that are used by all other Buttons.
|
||||
style.create('button', 'default',
|
||||
'(window, sound, hover) The default style used for buttons in the main and game menus.')
|
||||
|
||||
@@ -173,8 +207,11 @@ init -250:
|
||||
style.button_text.xpos = 0.5
|
||||
style.button_text.xanchor = 'center'
|
||||
style.button_text.size = 24
|
||||
style.button_text.color = (0, 255, 255, 255)
|
||||
style.button_text.hover_color = (128, 255, 255, 255)
|
||||
style.button_text.color = dark_cyan
|
||||
style.button_text.hover_color = bright_cyan
|
||||
style.button_text.activate_color = bright_cyan
|
||||
style.button_text.insensitive_color = (192, 192, 192, 255)
|
||||
style.button_text.drop_shadow = (2, 2)
|
||||
|
||||
# Selected button.
|
||||
style.create('selected_button', 'button',
|
||||
@@ -183,26 +220,24 @@ init -250:
|
||||
style.create('selected_button_text', 'button_text',
|
||||
'(text, hover) The style that is used for the label of a selected button.')
|
||||
|
||||
style.selected_button_text.color = (255, 255, 0, 255)
|
||||
style.selected_button_text.color = dark_red
|
||||
style.selected_button_text.hover_color = bright_red
|
||||
style.selected_button_text.activate_color = bright_red
|
||||
|
||||
# Disabled button.
|
||||
# Bar.
|
||||
style.create('bar', 'default',
|
||||
'(bar) The style that is used by default for bars.')
|
||||
|
||||
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.bar.left_bar = Solid(bright_cyan)
|
||||
style.bar.right_bar = Solid((0, 0, 0, 128))
|
||||
|
||||
# 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.')
|
||||
@@ -224,6 +259,8 @@ init -250:
|
||||
'(window) The style used for the root window of the game menu. This is primarily used to change the background of the game menu.')
|
||||
|
||||
style.gm_root_window.background = Solid((0, 0, 0, 255))
|
||||
style.gm_root_window.xfill = True
|
||||
style.gm_root_window.yfill = True
|
||||
|
||||
style.create('gm_nav_window', 'default',
|
||||
'(window, position) The style used by a window containing buttons that allow the user to navigate through the different screens of the game menu.')
|
||||
@@ -233,101 +270,205 @@ init -250:
|
||||
style.gm_nav_window.ypos = 0.95
|
||||
style.gm_nav_window.yanchor = 'bottom'
|
||||
|
||||
|
||||
style.create('gm_nav_button', 'button',
|
||||
'(window, hover) The style of an unselected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_button_text', 'button_text',
|
||||
'(text, hover) The style of the text of an unselected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_selected_button', 'selected_button',
|
||||
'(window, hover) The style of a selected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected game menu navigation button.')
|
||||
|
||||
style.create('file_picker_window', 'default',
|
||||
'(window, position) A window containing the file picker that is used to choose slots for loading and saving.')
|
||||
|
||||
style.file_picker_window.xpos = 10
|
||||
style.file_picker_window.xpos = 0
|
||||
style.file_picker_window.xanchor = 'left'
|
||||
style.file_picker_window.ypos = 10
|
||||
style.file_picker_window.ypos = 0
|
||||
style.file_picker_window.yanchor = 'top'
|
||||
style.file_picker_window.xpadding = 5
|
||||
|
||||
|
||||
style.create('file_picker_navbox', 'default',
|
||||
'(position) The position of the naviation (next/previous) buttons in the file picker.')
|
||||
|
||||
style.file_picker_navbox.xmargin = 10
|
||||
|
||||
style.create('file_picker_nav_button', 'button',
|
||||
'(window, hover) The style that is used for enabled file picker navigation buttons.')
|
||||
|
||||
style.create('file_picker_nav_button_text', 'button_text',
|
||||
'(text) The style that is used for the label of enabled file picker navigation buttons.')
|
||||
|
||||
|
||||
style.create('file_picker_grid', 'default',
|
||||
'(position) The style of the grid containing the file picker entries.')
|
||||
|
||||
style.file_picker_grid.xfill = True
|
||||
|
||||
style.create('file_picker_entry', 'button',
|
||||
'(window, hover) The style that is used for each of the slots in the file picker.')
|
||||
|
||||
style.file_picker_entry.xpadding = 3
|
||||
style.file_picker_entry.xminimum = 780
|
||||
style.file_picker_entry.ymargin = 5
|
||||
style.file_picker_entry.xpadding = 5
|
||||
style.file_picker_entry.ypadding = 2
|
||||
style.file_picker_entry.xmargin = 5
|
||||
style.file_picker_entry.xfill = True
|
||||
style.file_picker_entry.ymargin = 2
|
||||
|
||||
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
|
||||
style.file_picker_entry.background = Solid((255, 255, 255, 255))
|
||||
style.file_picker_entry.hover_background = Solid((255, 255, 192, 255))
|
||||
style.file_picker_entry.activate_background = Solid((255, 255, 192, 255))
|
||||
|
||||
style.create('file_picker_text', 'default',
|
||||
'(text) A base style for all text that is displayed in the file picker.')
|
||||
|
||||
style.file_picker_text.size = 18
|
||||
style.file_picker_text.color = dark_cyan
|
||||
style.file_picker_text.hover_color = bright_cyan
|
||||
|
||||
style.create('file_picker_new', 'file_picker_text',
|
||||
'(text) The style that is applied to the new indicator in the file picker.')
|
||||
|
||||
style.create('file_picker_old', 'file_picker_text',
|
||||
'(text) The style that is applied to the old indicator in the file pciker.')
|
||||
|
||||
style.file_picker_new.color = (255, 192, 192, 255)
|
||||
style.file_picker_old.color = (192, 192, 255, 255)
|
||||
style.file_picker_new.minwidth = 50
|
||||
style.file_picker_old.minwidth = 50
|
||||
style.file_picker_new.hover_color = bright_red
|
||||
style.file_picker_new.activate_color = bright_red
|
||||
style.file_picker_new.idle_color = dark_red
|
||||
style.file_picker_new.minwidth = 30
|
||||
style.file_picker_old.minwidth = 30
|
||||
|
||||
style.create('file_picker_extra_info', 'file_picker_text',
|
||||
'(text) The style that is applied to extra info in the file picker. The extra info is the save time, and the save_name if one exists.')
|
||||
|
||||
style.file_picker_extra_info.color = (192, 192, 255, 255)
|
||||
style.create('file_picker_empty_slot', 'file_picker_text',
|
||||
'(text) The style that is used for the empty slot indicator in the file picker.')
|
||||
|
||||
style.create('file_picker_new_slot', 'file_picker_text',
|
||||
'(text) The style that is used for the new slot indicator in the file picker.')
|
||||
|
||||
|
||||
style.create('yesno_prompt', 'default',
|
||||
style.create('yesno_label', 'default',
|
||||
'(text, position) The style used for the prompt in a yes/no dialog.')
|
||||
|
||||
style.yesno_prompt.xpos = 0.5
|
||||
style.yesno_prompt.xanchor = 'center'
|
||||
style.yesno_label.color = green
|
||||
style.yesno_label.textalign = 0.5
|
||||
|
||||
style.yesno_prompt.ypos = 0.25
|
||||
style.yesno_prompt.yanchor = 'center'
|
||||
style.create('yesno_button', 'button',
|
||||
'(window, hover) The style of yes/no buttons.')
|
||||
|
||||
style.create('yesno_yes', 'button',
|
||||
'(position) The position of the yes button on the screen.')
|
||||
|
||||
style.yesno_yes.xpos = 0.33
|
||||
style.yesno_yes.xanchor = 'center'
|
||||
style.yesno_yes.ypos = 0.33
|
||||
style.yesno_yes.yanchor = 'center'
|
||||
|
||||
style.create('yesno_no', 'button',
|
||||
'(position) The position of the no button on the screen.')
|
||||
|
||||
style.yesno_no.xpos = 0.66
|
||||
style.yesno_no.xanchor = 'center'
|
||||
style.yesno_no.ypos = 0.33
|
||||
style.yesno_no.yanchor = 'center'
|
||||
style.create('yesno_button_text', 'button_text',
|
||||
'(window, hover) The style of yes/no button text.')
|
||||
|
||||
style.create('yesno_window', 'default',
|
||||
'(window) The style of a window containing a yes/no dialogue.')
|
||||
|
||||
style.yesno_window.xfill = True
|
||||
style.yesno_window.yminimum = 0.5
|
||||
style.yesno_window.xmargin = .1
|
||||
|
||||
# Preferences
|
||||
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(window, position) A window containing an individual preference.')
|
||||
|
||||
style.prefs_pref.xpos = 0.5
|
||||
style.prefs_pref.xanchor = 'center'
|
||||
style.prefs_pref.bottom_margin = 10
|
||||
|
||||
style.create('prefs_label', 'default',
|
||||
'(text, position) The style that is applied to the label of a block of preferences.')
|
||||
|
||||
style.prefs_label.xpos = 0.5
|
||||
style.prefs_label.xanchor = "center"
|
||||
style.prefs_label.color = green
|
||||
|
||||
style.create('prefs_button', 'button',
|
||||
'(window, hover) The style of an unselected preferences button.')
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(position) The position of the box containing an individual preference.')
|
||||
style.prefs_button.xpos = 0.5
|
||||
style.prefs_button.xanchor = 'center'
|
||||
|
||||
style.prefs_pref.xpos = 0.5
|
||||
style.prefs_pref.xanchor = 'center'
|
||||
style.create('prefs_button_text', 'button_text',
|
||||
'(text, hover) The style of the text of an unselected preferences button.')
|
||||
|
||||
style.create('prefs_selected_button', 'selected_button',
|
||||
'(window, hover) The style of a selected preferences button.')
|
||||
|
||||
style.prefs_selected_button.xpos = 0.5
|
||||
style.prefs_selected_button.xanchor = 'center'
|
||||
|
||||
style.create('prefs_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected preferences button.')
|
||||
|
||||
style.create('prefs_window', 'default',
|
||||
'(window, position) A window containing all preferences.')
|
||||
|
||||
style.prefs_window.xfill=True
|
||||
style.prefs_window.ypadding = 0.05
|
||||
|
||||
style.create('prefs_left', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_left.xanchor = 'center'
|
||||
style.prefs_left.xpos = 0.25
|
||||
style.prefs_left.xanchor = "center"
|
||||
style.prefs_left.ypos = 0.05
|
||||
style.prefs_left.yalign = "top"
|
||||
|
||||
style.create('prefs_right', 'default',
|
||||
'(position) The position of the right column of preferences.')
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_right.xanchor = 'center'
|
||||
style.prefs_right.xpos = 0.75
|
||||
style.prefs_right.xanchor = "center"
|
||||
style.prefs_right.ypos = 0.05
|
||||
style.prefs_right.yalign = "top"
|
||||
|
||||
style.create('prefs_spinner', 'default',
|
||||
'(position) The position of the prefs spinner.')
|
||||
|
||||
style.prefs_spinner.xpos = 0.5
|
||||
style.prefs_spinner.xanchor = 'center'
|
||||
|
||||
style.create('prefs_spinner_label', 'prefs_label',
|
||||
'(text) This is the style that displays the value of a preference spinner.')
|
||||
|
||||
style.prefs_spinner_label.minwidth = 100
|
||||
style.prefs_spinner_label.textalign = 0.5
|
||||
|
||||
style.create('prefs_spinner_button', 'prefs_button',
|
||||
'(window, hover) The style of the + or - buttons in a preference spinner.')
|
||||
|
||||
style.create('prefs_spinner_button_text', 'prefs_button_text',
|
||||
'(text, hover) The style of the text of the + and - buttons in a preference spinner.')
|
||||
|
||||
|
||||
|
||||
|
||||
# The skip indicator.
|
||||
|
||||
style.create('skip_indicator', 'default',
|
||||
'(text, position) The style of the text that is used to indicate that skipping is in progress.')
|
||||
|
||||
style.skip_indicator.xpos = 10
|
||||
style.skip_indicator.ypos = 10
|
||||
|
||||
|
||||
# Styles used by internal error messages.
|
||||
|
||||
style.create('error_window', 'default',
|
||||
'(window) The style of the window containing internal error messages.')
|
||||
|
||||
style.error_window.background = Solid((220, 220, 255, 255))
|
||||
style.error_window.xfill = True
|
||||
style.error_window.yfill = True
|
||||
style.error_window.xpadding = 20
|
||||
style.error_window.ypadding = 20
|
||||
|
||||
|
||||
style.create('error_title', 'default',
|
||||
'(text) The style of the text containing the title of an error message.')
|
||||
|
||||
style.error_title.color = (255, 128, 128, 255)
|
||||
|
||||
style.create('error_body', 'default',
|
||||
'(text) The style of the body of an error message.')
|
||||
|
||||
style.error_body.color = (128, 128, 255, 255)
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os.path
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Extra things used for distribution.
|
||||
import encodings.utf_8
|
||||
import encodings.zlib_codec
|
||||
import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
# Add the path to the module.
|
||||
sys.path.append("module")
|
||||
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
|
||||
if name.find("_") != -1:
|
||||
name = name[name.find("_") + 1:]
|
||||
|
||||
if os.path.isdir(name):
|
||||
game = name
|
||||
else:
|
||||
game = "game"
|
||||
|
||||
op = optparse.OptionParser()
|
||||
op.add_option('--game', dest='game', default=game,
|
||||
help='The directory the game is in.')
|
||||
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--lint', dest='lint', default=False, action='store_true',
|
||||
help='Run a number of expensive tests, to try to detect errors in the script.')
|
||||
|
||||
op.add_option('--leak', dest='leak', action='store_true', default=False,
|
||||
help='When the game exits, dumps a profile of memory usage.')
|
||||
|
||||
options, args = op.parse_args()
|
||||
|
||||
if options.python:
|
||||
execfile(options.python)
|
||||
sys.exit(0)
|
||||
|
||||
if not options.lint:
|
||||
import renpy.display.presplash
|
||||
renpy.display.presplash.start(options.game)
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
try:
|
||||
renpy.main.main(options.game, lint=options.lint)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
f = file("traceback.txt", "wU")
|
||||
|
||||
f.write(codecs.BOM_UTF8)
|
||||
|
||||
print >>f, "I'm sorry, but an exception occured while executing your Ren'Py"
|
||||
print >>f, "script."
|
||||
print >>f
|
||||
|
||||
type, value, tb = sys.exc_info()
|
||||
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print >>f
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "-- Full Traceback ------------------------------------------------------------"
|
||||
print >>f
|
||||
|
||||
traceback.print_tb(tb, None, sys.stdout)
|
||||
traceback.print_tb(tb, None, f)
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print type.__name__ + ":",
|
||||
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print unicode(e).encode('utf-8')
|
||||
|
||||
print
|
||||
print >>f
|
||||
|
||||
print renpy.game.exception_info
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "Ren'Py Version:", renpy.version
|
||||
|
||||
f.close()
|
||||
|
||||
try:
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def memory_profile():
|
||||
|
||||
import renpy
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
print
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
objs = gc.get_objects()
|
||||
|
||||
c = { } # count
|
||||
dead_renders = 0
|
||||
|
||||
for i in objs:
|
||||
t = type(i)
|
||||
c[t] = c.get(t, 0) + 1
|
||||
|
||||
if isinstance(i, renpy.display.render.Render):
|
||||
if i.dead:
|
||||
dead_renders += 1
|
||||
|
||||
|
||||
results = [ (count, ty) for ty, count in c.iteritems() ]
|
||||
results.sort()
|
||||
|
||||
for count, ty in results:
|
||||
print count, str(ty)
|
||||
|
||||
if dead_renders:
|
||||
print
|
||||
print "*** found", dead_renders, "dead Renders. ***"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,822 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Perforce Defect Tracking Integration Project
|
||||
# <http://www.ravenbrook.com/project/p4dti/>
|
||||
#
|
||||
# COVERAGE.PY -- COVERAGE TESTING
|
||||
#
|
||||
# Gareth Rees, Ravenbrook Limited, 2001-12-04
|
||||
# Ned Batchelder, 2004-12-12
|
||||
# http://nedbatchelder.com/code/modules/coverage.html
|
||||
#
|
||||
#
|
||||
# 1. INTRODUCTION
|
||||
#
|
||||
# This module provides coverage testing for Python code.
|
||||
#
|
||||
# The intended readership is all Python developers.
|
||||
#
|
||||
# This document is not confidential.
|
||||
#
|
||||
# See [GDR 2001-12-04a] for the command-line interface, programmatic
|
||||
# interface and limitations. See [GDR 2001-12-04b] for requirements and
|
||||
# design.
|
||||
|
||||
"""Usage:
|
||||
|
||||
coverage.py -x MODULE.py [ARG1 ARG2 ...]
|
||||
Execute module, passing the given command-line arguments, collecting
|
||||
coverage data.
|
||||
|
||||
coverage.py -e
|
||||
Erase collected coverage data.
|
||||
|
||||
coverage.py -r [-m] FILE1 FILE2 ...
|
||||
Report on the statement coverage for the given files. With the -m
|
||||
option, show line numbers of the statements that weren't executed.
|
||||
|
||||
coverage.py -a [-d dir] FILE1 FILE2 ...
|
||||
Make annotated copies of the given files, marking statements that
|
||||
are executed with > and statements that are missed with !. With
|
||||
the -d option, make the copies in that directory. Without the -d
|
||||
option, make each copy in the same directory as the original.
|
||||
|
||||
Coverage data is saved in the file .coverage by default. Set the
|
||||
COVERAGE_FILE environment variable to save it somewhere else."""
|
||||
|
||||
__version__ = "2.2.20041231" # see detailed history at the end of this file.
|
||||
|
||||
import compiler
|
||||
import compiler.visitor
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
# 2. IMPLEMENTATION
|
||||
#
|
||||
# This uses the "singleton" pattern.
|
||||
#
|
||||
# The word "morf" means a module object (from which the source file can
|
||||
# be deduced by suitable manipulation of the __file__ attribute) or a
|
||||
# filename.
|
||||
#
|
||||
# When we generate a coverage report we have to canonicalize every
|
||||
# filename in the coverage dictionary just in case it refers to the
|
||||
# module we are reporting on. It seems a shame to throw away this
|
||||
# information so the data in the coverage dictionary is transferred to
|
||||
# the 'cexecuted' dictionary under the canonical filenames.
|
||||
#
|
||||
# The coverage dictionary is called "c" and the trace function "t". The
|
||||
# reason for these short names is that Python looks up variables by name
|
||||
# at runtime and so execution time depends on the length of variables!
|
||||
# In the bottleneck of this application it's appropriate to abbreviate
|
||||
# names to increase speed.
|
||||
|
||||
# A dictionary with an entry for (Python source file name, line number
|
||||
# in that file) if that line has been executed.
|
||||
c = {}
|
||||
|
||||
# t(f, x, y). This method is passed to sys.settrace as a trace
|
||||
# function. See [van Rossum 2001-07-20b, 9.2] for an explanation of
|
||||
# sys.settrace and the arguments and return value of the trace function.
|
||||
# See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
|
||||
# objects.
|
||||
|
||||
def t(f, w, a):
|
||||
if w == 'line':
|
||||
c[(f.f_code.co_filename, f.f_lineno)] = 1
|
||||
return t
|
||||
|
||||
class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
|
||||
def __init__(self, statements, excluded, suite_spots):
|
||||
compiler.visitor.ASTVisitor.__init__(self)
|
||||
self.statements = statements
|
||||
self.excluded = excluded
|
||||
self.suite_spots = suite_spots
|
||||
self.excluding_suite = 0
|
||||
|
||||
def doRecursive(self, node):
|
||||
self.recordNodeLine(node)
|
||||
for n in node.getChildNodes():
|
||||
self.dispatch(n)
|
||||
|
||||
visitStmt = visitModule = doRecursive
|
||||
|
||||
def doCode(self, node):
|
||||
if hasattr(node, 'decorators') and node.decorators:
|
||||
self.dispatch(node.decorators)
|
||||
self.doSuite(node, node.code)
|
||||
|
||||
visitFunction = visitClass = doCode
|
||||
|
||||
def getFirstLine(self, node):
|
||||
# Find the first line in the tree node.
|
||||
lineno = node.lineno
|
||||
for n in node.getChildNodes():
|
||||
f = self.getFirstLine(n)
|
||||
if lineno and f:
|
||||
lineno = min(lineno, f)
|
||||
else:
|
||||
lineno = lineno or f
|
||||
return lineno
|
||||
|
||||
def getLastLine(self, node):
|
||||
# Find the first line in the tree node.
|
||||
lineno = node.lineno
|
||||
for n in node.getChildNodes():
|
||||
lineno = max(lineno, self.getLastLine(n))
|
||||
return lineno
|
||||
|
||||
def doStatement(self, node):
|
||||
self.recordLine(self.getFirstLine(node))
|
||||
|
||||
visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
|
||||
visitPrintnl = visitRaise = visitSubscript = \
|
||||
visitDecorators = \
|
||||
doStatement
|
||||
|
||||
def recordNodeLine(self, node):
|
||||
return self.recordLine(node.lineno)
|
||||
|
||||
def recordLine(self, lineno):
|
||||
# Returns a bool, whether the line is included or excluded.
|
||||
if lineno:
|
||||
# Multi-line tests introducing suites have to get charged to their
|
||||
# keyword.
|
||||
if lineno in self.suite_spots:
|
||||
lineno = self.suite_spots[lineno][0]
|
||||
# If we're inside an exluded suite, record that this line was
|
||||
# excluded.
|
||||
if self.excluding_suite:
|
||||
self.excluded[lineno] = 1
|
||||
return 0
|
||||
# If this line is excluded, or suite_spots maps this line to
|
||||
# another line that is exlcuded, then we're excluded.
|
||||
elif self.excluded.has_key(lineno) or \
|
||||
self.suite_spots.has_key(lineno) and \
|
||||
self.excluded.has_key(self.suite_spots[lineno][1]):
|
||||
return 0
|
||||
# Otherwise, this is an executable line.
|
||||
else:
|
||||
self.statements[lineno] = 1
|
||||
return 1
|
||||
return 0
|
||||
|
||||
default = recordNodeLine
|
||||
|
||||
def recordAndDispatch(self, node):
|
||||
self.recordNodeLine(node)
|
||||
self.dispatch(node)
|
||||
|
||||
def doSuite(self, intro, body, exclude=0):
|
||||
exsuite = self.excluding_suite
|
||||
if exclude or (intro and not self.recordNodeLine(intro)):
|
||||
self.excluding_suite = 1
|
||||
self.recordAndDispatch(body)
|
||||
self.excluding_suite = exsuite
|
||||
|
||||
def doPlainWordSuite(self, prevsuite, suite):
|
||||
# Finding the exclude lines for else's is tricky, because they aren't
|
||||
# present in the compiler parse tree. Look at the previous suite,
|
||||
# and find its last line. If any line between there and the else's
|
||||
# first line are excluded, then we exclude the else.
|
||||
lastprev = self.getLastLine(prevsuite)
|
||||
firstelse = self.getFirstLine(suite)
|
||||
for l in range(lastprev+1, firstelse):
|
||||
if self.suite_spots.has_key(l):
|
||||
self.doSuite(None, suite, exclude=self.excluded.has_key(l))
|
||||
break
|
||||
else:
|
||||
self.doSuite(None, suite)
|
||||
|
||||
def doElse(self, prevsuite, node):
|
||||
if node.else_:
|
||||
self.doPlainWordSuite(prevsuite, node.else_)
|
||||
|
||||
def visitFor(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doElse(node.body, node)
|
||||
|
||||
def visitIf(self, node):
|
||||
# The first test has to be handled separately from the rest.
|
||||
# The first test is credited to the line with the "if", but the others
|
||||
# are credited to the line with the test for the elif.
|
||||
self.doSuite(node, node.tests[0][1])
|
||||
for t, n in node.tests[1:]:
|
||||
self.doSuite(t, n)
|
||||
self.doElse(node.tests[-1][1], node)
|
||||
|
||||
def visitWhile(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doElse(node.body, node)
|
||||
|
||||
def visitTryExcept(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
for i in range(len(node.handlers)):
|
||||
a, b, h = node.handlers[i]
|
||||
if not a:
|
||||
# It's a plain "except:". Find the previous suite.
|
||||
if i > 0:
|
||||
prev = node.handlers[i-1][2]
|
||||
else:
|
||||
prev = node.body
|
||||
self.doPlainWordSuite(prev, h)
|
||||
else:
|
||||
self.doSuite(a, h)
|
||||
self.doElse(node.handlers[-1][2], node)
|
||||
|
||||
def visitTryFinally(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doPlainWordSuite(node.body, node.final)
|
||||
|
||||
def visitGlobal(self, node):
|
||||
# "global" statements don't execute like others (they don't call the
|
||||
# trace function), so don't record their line numbers.
|
||||
pass
|
||||
|
||||
the_coverage = None
|
||||
|
||||
class coverage:
|
||||
error = "coverage error"
|
||||
|
||||
# Name of the cache file (unless environment variable is set).
|
||||
cache_default = ".coverage"
|
||||
|
||||
# Environment variable naming the cache file.
|
||||
cache_env = "COVERAGE_FILE"
|
||||
|
||||
# A map from canonical Python source file name to a dictionary in
|
||||
# which there's an entry for each line number that has been
|
||||
# executed.
|
||||
cexecuted = {}
|
||||
|
||||
# Cache of results of calling the analysis2() method, so that you can
|
||||
# specify both -r and -a without doing double work.
|
||||
analysis_cache = {}
|
||||
|
||||
# Cache of results of calling the canonical_filename() method, to
|
||||
# avoid duplicating work.
|
||||
canonical_filename_cache = {}
|
||||
|
||||
def __init__(self):
|
||||
global the_coverage
|
||||
if the_coverage:
|
||||
raise self.error, "Only one coverage object allowed."
|
||||
self.usecache = 1
|
||||
self.cache = None
|
||||
self.exclude_re = ''
|
||||
|
||||
def help(self, error=None):
|
||||
if error:
|
||||
print error
|
||||
print
|
||||
print __doc__
|
||||
sys.exit(1)
|
||||
|
||||
def command_line(self):
|
||||
import getopt
|
||||
settings = {}
|
||||
optmap = {
|
||||
'-a': 'annotate',
|
||||
'-d:': 'directory=',
|
||||
'-e': 'erase',
|
||||
'-h': 'help',
|
||||
'-i': 'ignore-errors',
|
||||
'-m': 'show-missing',
|
||||
'-r': 'report',
|
||||
'-x': 'execute',
|
||||
}
|
||||
short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
|
||||
long_opts = optmap.values()
|
||||
options, args = getopt.getopt(sys.argv[1:], short_opts,
|
||||
long_opts)
|
||||
for o, a in options:
|
||||
if optmap.has_key(o):
|
||||
settings[optmap[o]] = 1
|
||||
elif optmap.has_key(o + ':'):
|
||||
settings[optmap[o + ':']] = a
|
||||
elif o[2:] in long_opts:
|
||||
settings[o[2:]] = 1
|
||||
elif o[2:] + '=' in long_opts:
|
||||
settings[o[2:]] = a
|
||||
else:
|
||||
self.help("Unknown option: '%s'." % o)
|
||||
if settings.get('help'):
|
||||
self.help()
|
||||
for i in ['erase', 'execute']:
|
||||
for j in ['annotate', 'report']:
|
||||
if settings.get(i) and settings.get(j):
|
||||
self.help("You can't specify the '%s' and '%s' "
|
||||
"options at the same time." % (i, j))
|
||||
args_needed = (settings.get('execute')
|
||||
or settings.get('annotate')
|
||||
or settings.get('report'))
|
||||
action = settings.get('erase') or args_needed
|
||||
if not action:
|
||||
self.help("You must specify at least one of -e, -x, -r, or -a.")
|
||||
if not args_needed and args:
|
||||
self.help("Unexpected arguments %s." % args)
|
||||
|
||||
self.get_ready()
|
||||
self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
|
||||
|
||||
if settings.get('erase'):
|
||||
self.erase()
|
||||
if settings.get('execute'):
|
||||
if not args:
|
||||
self.help("Nothing to do.")
|
||||
sys.argv = args
|
||||
self.start()
|
||||
import __main__
|
||||
sys.path[0] = os.path.dirname(sys.argv[0])
|
||||
execfile(sys.argv[0], __main__.__dict__)
|
||||
if not args:
|
||||
args = self.cexecuted.keys()
|
||||
ignore_errors = settings.get('ignore-errors')
|
||||
show_missing = settings.get('show-missing')
|
||||
directory = settings.get('directory=')
|
||||
if settings.get('report'):
|
||||
self.report(args, show_missing, ignore_errors)
|
||||
if settings.get('annotate'):
|
||||
self.annotate(args, directory, ignore_errors)
|
||||
|
||||
def use_cache(self, usecache):
|
||||
self.usecache = usecache
|
||||
|
||||
def get_ready(self):
|
||||
if self.usecache and not self.cache:
|
||||
self.cache = os.environ.get(self.cache_env, self.cache_default)
|
||||
self.restore()
|
||||
self.analysis_cache = {}
|
||||
|
||||
def start(self):
|
||||
self.get_ready()
|
||||
sys.settrace(t)
|
||||
|
||||
def stop(self):
|
||||
sys.settrace(None)
|
||||
|
||||
def erase(self):
|
||||
global c
|
||||
c = {}
|
||||
self.analysis_cache = {}
|
||||
self.cexecuted = {}
|
||||
if self.cache and os.path.exists(self.cache):
|
||||
os.remove(self.cache)
|
||||
self.exclude_re = ''
|
||||
|
||||
def exclude(self, re):
|
||||
if self.exclude_re:
|
||||
self.exclude_re += "|"
|
||||
self.exclude_re += "(" + re + ")"
|
||||
|
||||
# save(). Save coverage data to the coverage cache.
|
||||
|
||||
def save(self):
|
||||
if self.usecache and self.cache:
|
||||
self.canonicalize_filenames()
|
||||
cache = open(self.cache, 'wb')
|
||||
import marshal
|
||||
marshal.dump(self.cexecuted, cache)
|
||||
cache.close()
|
||||
|
||||
# restore(). Restore coverage data from the coverage cache (if it
|
||||
# exists).
|
||||
|
||||
def restore(self):
|
||||
global c
|
||||
c = {}
|
||||
self.cexecuted = {}
|
||||
assert self.usecache
|
||||
if not os.path.exists(self.cache):
|
||||
return
|
||||
try:
|
||||
cache = open(self.cache, 'rb')
|
||||
import marshal
|
||||
cexecuted = marshal.load(cache)
|
||||
cache.close()
|
||||
if isinstance(cexecuted, types.DictType):
|
||||
self.cexecuted = cexecuted
|
||||
except:
|
||||
pass
|
||||
|
||||
# canonical_filename(filename). Return a canonical filename for the
|
||||
# file (that is, an absolute path with no redundant components and
|
||||
# normalized case). See [GDR 2001-12-04b, 3.3].
|
||||
|
||||
def canonical_filename(self, filename):
|
||||
if not self.canonical_filename_cache.has_key(filename):
|
||||
f = filename
|
||||
if os.path.isabs(f) and not os.path.exists(f):
|
||||
f = os.path.basename(f)
|
||||
if not os.path.isabs(f):
|
||||
for path in [os.curdir] + sys.path:
|
||||
g = os.path.join(path, f)
|
||||
if os.path.exists(g):
|
||||
f = g
|
||||
break
|
||||
cf = os.path.normcase(os.path.abspath(f))
|
||||
self.canonical_filename_cache[filename] = cf
|
||||
return self.canonical_filename_cache[filename]
|
||||
|
||||
# canonicalize_filenames(). Copy results from "executed" to
|
||||
# "cexecuted", canonicalizing filenames on the way. Clear the
|
||||
# "executed" map.
|
||||
|
||||
def canonicalize_filenames(self):
|
||||
global c
|
||||
for filename, lineno in c.keys():
|
||||
f = self.canonical_filename(filename)
|
||||
if not self.cexecuted.has_key(f):
|
||||
self.cexecuted[f] = {}
|
||||
self.cexecuted[f][lineno] = 1
|
||||
c = {}
|
||||
|
||||
# morf_filename(morf). Return the filename for a module or file.
|
||||
|
||||
def morf_filename(self, morf):
|
||||
if isinstance(morf, types.ModuleType):
|
||||
if not hasattr(morf, '__file__'):
|
||||
raise self.error, "Module has no __file__ attribute."
|
||||
file = morf.__file__
|
||||
else:
|
||||
file = morf
|
||||
return self.canonical_filename(file)
|
||||
|
||||
# analyze_morf(morf). Analyze the module or filename passed as
|
||||
# the argument. If the source code can't be found, raise an error.
|
||||
# Otherwise, return a tuple of (1) the canonical filename of the
|
||||
# source code for the module, (2) a list of lines of statements
|
||||
# in the source code, and (3) a list of lines of excluded statements.
|
||||
|
||||
def analyze_morf(self, morf):
|
||||
if self.analysis_cache.has_key(morf):
|
||||
return self.analysis_cache[morf]
|
||||
filename = self.morf_filename(morf)
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext == '.pyc':
|
||||
if not os.path.exists(filename[0:-1]):
|
||||
raise self.error, ("No source for compiled code '%s'."
|
||||
% filename)
|
||||
filename = filename[0:-1]
|
||||
elif ext != '.py':
|
||||
raise self.error, "File '%s' not Python source." % filename
|
||||
source = open(filename, 'r')
|
||||
lines, excluded_lines = self.find_executable_statements(
|
||||
source.read(), exclude=self.exclude_re
|
||||
)
|
||||
source.close()
|
||||
result = filename, lines, excluded_lines
|
||||
self.analysis_cache[morf] = result
|
||||
return result
|
||||
|
||||
def get_suite_spots(self, tree, spots):
|
||||
import symbol, token
|
||||
for i in range(1, len(tree)):
|
||||
if type(tree[i]) == type(()):
|
||||
if tree[i][0] == symbol.suite:
|
||||
# Found a suite, look back for the colon and keyword.
|
||||
lineno_colon = lineno_word = None
|
||||
for j in range(i-1, 0, -1):
|
||||
if tree[j][0] == token.COLON:
|
||||
lineno_colon = tree[j][2]
|
||||
elif tree[j][0] == token.NAME:
|
||||
if tree[j][1] == 'elif':
|
||||
# Find the line number of the first non-terminal
|
||||
# after the keyword.
|
||||
t = tree[j+1]
|
||||
while t and token.ISNONTERMINAL(t[0]):
|
||||
t = t[1]
|
||||
if t:
|
||||
lineno_word = t[2]
|
||||
else:
|
||||
lineno_word = tree[j][2]
|
||||
break
|
||||
elif tree[j][0] == symbol.except_clause:
|
||||
# "except" clauses look like:
|
||||
# ('except_clause', ('NAME', 'except', lineno), ...)
|
||||
if tree[j][1][0] == token.NAME:
|
||||
lineno_word = tree[j][1][2]
|
||||
break
|
||||
if lineno_colon and lineno_word:
|
||||
# Found colon and keyword, mark all the lines
|
||||
# between the two with the two line numbers.
|
||||
for l in range(lineno_word, lineno_colon+1):
|
||||
spots[l] = (lineno_word, lineno_colon)
|
||||
self.get_suite_spots(tree[i], spots)
|
||||
|
||||
def find_executable_statements(self, text, exclude=None):
|
||||
# Find lines which match an exclusion pattern.
|
||||
excluded = {}
|
||||
suite_spots = {}
|
||||
if exclude:
|
||||
reExclude = re.compile(exclude)
|
||||
lines = text.split('\n')
|
||||
for i in range(len(lines)):
|
||||
if reExclude.search(lines[i]):
|
||||
excluded[i+1] = 1
|
||||
|
||||
import parser
|
||||
tree = parser.suite(text+'\n\n').totuple(1)
|
||||
self.get_suite_spots(tree, suite_spots)
|
||||
|
||||
# Use the compiler module to parse the text and find the executable
|
||||
# statements. We add newlines to be impervious to final partial lines.
|
||||
statements = {}
|
||||
ast = compiler.parse(text+'\n\n')
|
||||
visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
|
||||
compiler.walk(ast, visitor, walker=visitor)
|
||||
|
||||
lines = statements.keys()
|
||||
lines.sort()
|
||||
excluded_lines = excluded.keys()
|
||||
excluded_lines.sort()
|
||||
return lines, excluded_lines
|
||||
|
||||
# format_lines(statements, lines). Format a list of line numbers
|
||||
# for printing by coalescing groups of lines as long as the lines
|
||||
# represent consecutive statements. This will coalesce even if
|
||||
# there are gaps between statements, so if statements =
|
||||
# [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
|
||||
# format_lines will return "1-2, 5-11, 13-14".
|
||||
|
||||
def format_lines(self, statements, lines):
|
||||
pairs = []
|
||||
i = 0
|
||||
j = 0
|
||||
start = None
|
||||
pairs = []
|
||||
while i < len(statements) and j < len(lines):
|
||||
if statements[i] == lines[j]:
|
||||
if start == None:
|
||||
start = lines[j]
|
||||
end = lines[j]
|
||||
j = j + 1
|
||||
elif start:
|
||||
pairs.append((start, end))
|
||||
start = None
|
||||
i = i + 1
|
||||
if start:
|
||||
pairs.append((start, end))
|
||||
def stringify(pair):
|
||||
start, end = pair
|
||||
if start == end:
|
||||
return "%d" % start
|
||||
else:
|
||||
return "%d-%d" % (start, end)
|
||||
import string
|
||||
return string.join(map(stringify, pairs), ", ")
|
||||
|
||||
# Backward compatibility with version 1.
|
||||
def analysis(self, morf):
|
||||
f, s, _, m, mf = self.analysis2(morf)
|
||||
return f, s, m, mf
|
||||
|
||||
def analysis2(self, morf):
|
||||
filename, statements, excluded = self.analyze_morf(morf)
|
||||
self.canonicalize_filenames()
|
||||
if not self.cexecuted.has_key(filename):
|
||||
self.cexecuted[filename] = {}
|
||||
missing = []
|
||||
for line in statements:
|
||||
if not self.cexecuted[filename].has_key(line):
|
||||
missing.append(line)
|
||||
return (filename, statements, excluded, missing,
|
||||
self.format_lines(statements, missing))
|
||||
|
||||
def morf_name(self, morf):
|
||||
if isinstance(morf, types.ModuleType):
|
||||
return morf.__name__
|
||||
else:
|
||||
return os.path.splitext(os.path.basename(morf))[0]
|
||||
|
||||
def report(self, morfs, show_missing=1, ignore_errors=0):
|
||||
if not isinstance(morfs, types.ListType):
|
||||
morfs = [morfs]
|
||||
max_name = max([5,] + map(len, map(self.morf_name, morfs)))
|
||||
fmt_name = "%%- %ds " % max_name
|
||||
fmt_err = fmt_name + "%s: %s"
|
||||
header = fmt_name % "Name" + " Stmts Exec Cover"
|
||||
fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
|
||||
if show_missing:
|
||||
header = header + " Missing"
|
||||
fmt_coverage = fmt_coverage + " %s"
|
||||
print header
|
||||
print "-" * len(header)
|
||||
total_statements = 0
|
||||
total_executed = 0
|
||||
for morf in morfs:
|
||||
name = self.morf_name(morf)
|
||||
try:
|
||||
_, statements, _, missing, readable = self.analysis2(morf)
|
||||
n = len(statements)
|
||||
m = n - len(missing)
|
||||
if n > 0:
|
||||
pc = 100.0 * m / n
|
||||
else:
|
||||
pc = 100.0
|
||||
args = (name, n, m, pc)
|
||||
if show_missing:
|
||||
args = args + (readable,)
|
||||
print fmt_coverage % args
|
||||
total_statements = total_statements + n
|
||||
total_executed = total_executed + m
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
if not ignore_errors:
|
||||
type, msg = sys.exc_info()[0:2]
|
||||
print fmt_err % (name, type, msg)
|
||||
if len(morfs) > 1:
|
||||
print "-" * len(header)
|
||||
if total_statements > 0:
|
||||
pc = 100.0 * total_executed / total_statements
|
||||
else:
|
||||
pc = 100.0
|
||||
args = ("TOTAL", total_statements, total_executed, pc)
|
||||
if show_missing:
|
||||
args = args + ("",)
|
||||
print fmt_coverage % args
|
||||
|
||||
# annotate(morfs, ignore_errors).
|
||||
|
||||
blank_re = re.compile("\\s*(#|$)")
|
||||
else_re = re.compile("\\s*else\\s*:\\s*(#|$)")
|
||||
|
||||
def annotate(self, morfs, directory=None, ignore_errors=0):
|
||||
for morf in morfs:
|
||||
try:
|
||||
filename, statements, excluded, missing, _ = self.analysis2(morf)
|
||||
self.annotate_file(filename, statements, excluded, missing, directory)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
if not ignore_errors:
|
||||
raise
|
||||
|
||||
def annotate_file(self, filename, statements, excluded, missing, directory=None):
|
||||
source = open(filename, 'r')
|
||||
if directory:
|
||||
dest_file = os.path.join(directory,
|
||||
os.path.basename(filename)
|
||||
+ ',cover')
|
||||
else:
|
||||
dest_file = filename + ',cover'
|
||||
dest = open(dest_file, 'w')
|
||||
lineno = 0
|
||||
i = 0
|
||||
j = 0
|
||||
covered = 1
|
||||
while 1:
|
||||
line = source.readline()
|
||||
if line == '':
|
||||
break
|
||||
lineno = lineno + 1
|
||||
while i < len(statements) and statements[i] < lineno:
|
||||
i = i + 1
|
||||
while j < len(missing) and missing[j] < lineno:
|
||||
j = j + 1
|
||||
if i < len(statements) and statements[i] == lineno:
|
||||
covered = j >= len(missing) or missing[j] > lineno
|
||||
if self.blank_re.match(line):
|
||||
dest.write(' ')
|
||||
elif self.else_re.match(line):
|
||||
# Special logic for lines containing only
|
||||
# 'else:'. See [GDR 2001-12-04b, 3.2].
|
||||
if i >= len(statements) and j >= len(missing):
|
||||
dest.write('! ')
|
||||
elif i >= len(statements) or j >= len(missing):
|
||||
dest.write('> ')
|
||||
elif statements[i] == missing[j]:
|
||||
dest.write('! ')
|
||||
else:
|
||||
dest.write('> ')
|
||||
elif lineno in excluded:
|
||||
dest.write('- ')
|
||||
elif covered:
|
||||
dest.write('> ')
|
||||
else:
|
||||
dest.write('! ')
|
||||
dest.write(line)
|
||||
source.close()
|
||||
dest.close()
|
||||
|
||||
# Singleton object.
|
||||
the_coverage = coverage()
|
||||
|
||||
# Module functions call methods in the singleton object.
|
||||
def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
|
||||
def start(*args, **kw): return the_coverage.start(*args, **kw)
|
||||
def stop(*args, **kw): return the_coverage.stop(*args, **kw)
|
||||
def erase(*args, **kw): return the_coverage.erase(*args, **kw)
|
||||
def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
|
||||
def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
|
||||
def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
|
||||
def report(*args, **kw): return the_coverage.report(*args, **kw)
|
||||
def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
|
||||
def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
|
||||
|
||||
# Save coverage data when Python exits. (The atexit module wasn't
|
||||
# introduced until Python 2.0, so use sys.exitfunc when it's not
|
||||
# available.)
|
||||
try:
|
||||
import atexit
|
||||
atexit.register(the_coverage.save)
|
||||
except ImportError:
|
||||
sys.exitfunc = the_coverage.save
|
||||
|
||||
# Command-line interface.
|
||||
if __name__ == '__main__':
|
||||
the_coverage.command_line()
|
||||
|
||||
|
||||
# A. REFERENCES
|
||||
#
|
||||
# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
|
||||
# Ravenbrook Limited; 2001-12-04;
|
||||
# <http://www.garethrees.org/2001/12/04/python-coverage/>.
|
||||
#
|
||||
# [GDR 2001-12-04b] "Statement coverage for Python: design and
|
||||
# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
|
||||
# <http://www.garethrees.org/2001/12/04/python-coverage/design.html>.
|
||||
#
|
||||
# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
|
||||
# Guide van Rossum; 2001-07-20;
|
||||
# <http://www.python.org/doc/2.1.1/ref/ref.html>.
|
||||
#
|
||||
# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
|
||||
# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
|
||||
#
|
||||
#
|
||||
# B. DOCUMENT HISTORY
|
||||
#
|
||||
# 2001-12-04 GDR Created.
|
||||
#
|
||||
# 2001-12-06 GDR Added command-line interface and source code
|
||||
# annotation.
|
||||
#
|
||||
# 2001-12-09 GDR Moved design and interface to separate documents.
|
||||
#
|
||||
# 2001-12-10 GDR Open cache file as binary on Windows. Allow
|
||||
# simultaneous -e and -x, or -a and -r.
|
||||
#
|
||||
# 2001-12-12 GDR Added command-line help. Cache analysis so that it
|
||||
# only needs to be done once when you specify -a and -r.
|
||||
#
|
||||
# 2001-12-13 GDR Improved speed while recording. Portable between
|
||||
# Python 1.5.2 and 2.1.1.
|
||||
#
|
||||
# 2002-01-03 GDR Module-level functions work correctly.
|
||||
#
|
||||
# 2002-01-07 GDR Update sys.path when running a file with the -x option,
|
||||
# so that it matches the value the program would get if it were run on
|
||||
# its own.
|
||||
#
|
||||
# 2004-12-12 NMB Significant code changes.
|
||||
# - Finding executable statements has been rewritten so that docstrings and
|
||||
# other quirks of Python execution aren't mistakenly identified as missing
|
||||
# lines.
|
||||
# - Lines can be excluded from consideration, even entire suites of lines.
|
||||
# - The filesystem cache of covered lines can be disabled programmatically.
|
||||
# - Modernized the code.
|
||||
#
|
||||
# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
|
||||
# and add 'analysis2'. Add a global for 'annotate', and factor it, adding
|
||||
# 'annotate_file'.
|
||||
#
|
||||
# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
|
||||
#
|
||||
# C. COPYRIGHT AND LICENCE
|
||||
#
|
||||
# Copyright 2001 Gareth Rees. All rights reserved.
|
||||
# Copyright 2004 Ned Batchelder. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
# DAMAGE.
|
||||
#
|
||||
# $Id: coverage.py 5 2004-12-14 12:08:23Z ned $
|
||||
@@ -1,207 +0,0 @@
|
||||
init:
|
||||
# Set up the size of the screen.
|
||||
$ config.screen_width = 800
|
||||
$ config.screen_height = 600
|
||||
|
||||
# Positions of things on the screen.
|
||||
$ left = Position(xpos=0.0, xanchor='left')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
|
||||
# Backgrounds.
|
||||
image whitehouse = Image("whitehouse.jpg")
|
||||
|
||||
# Character pictures.
|
||||
image eileen happy = Image("9a_happy.png")
|
||||
image eileen vhappy = Image("9a_vhappy.png")
|
||||
image eileen concerned = Image("9a_concerned.png")
|
||||
|
||||
# Character objects.
|
||||
$ e = Character('Eileen', color=(200, 255, 200, 255))
|
||||
|
||||
|
||||
# The actual game starts here.
|
||||
label start:
|
||||
|
||||
$ renpy.music_start('sun-flower-slow-drag.mid')
|
||||
|
||||
scene whitehouse
|
||||
show eileen vhappy
|
||||
|
||||
"Girl" "Welcome to Ren'Py 4!"
|
||||
|
||||
show eileen happy
|
||||
|
||||
"Girl" "And welcome to American Bishoujo's former southern base,
|
||||
just outside of Washington, D.C."
|
||||
|
||||
show eileen happy at left
|
||||
|
||||
"Girl" "This isn't the view from our former base, but it'll do for
|
||||
this demo."
|
||||
|
||||
show eileen happy
|
||||
|
||||
"Girl" "My name is Eileen, and while I plan to star in my own game
|
||||
one day, for now I'm helping to introduce you to Ren'Py."
|
||||
|
||||
e "Ren'Py is an engine that makes it easy to write visual novel
|
||||
games, by taking care of much of the hard work of writing a
|
||||
game."
|
||||
|
||||
e "For example, displaying a line of dialogue is a single
|
||||
statement. So is displaying a thought or narration."
|
||||
|
||||
"I understand."
|
||||
|
||||
e "Dialogue is easy to write. Just put a string on a line by
|
||||
itself, or to the right of an object name or label string."
|
||||
|
||||
e "Since dialogue makes up the bulk of these games, we thought it
|
||||
should be easy to write."
|
||||
|
||||
e "Ren'Py can also display menus that let you alter the flow of
|
||||
the story."
|
||||
|
||||
menu:
|
||||
"Why don't you try a menu out by picking a number?"
|
||||
|
||||
"1":
|
||||
show eileen concerned
|
||||
e "You picked one. I don't like odd numbers."
|
||||
|
||||
"2":
|
||||
show eileen vhappy
|
||||
e "You picked two. Even numbers are lucky!"
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "There are a number of statements that control what's displayed
|
||||
on the screen. The image statement is used to introduce
|
||||
images with names."
|
||||
|
||||
scene
|
||||
|
||||
e "The scene statement can clear the screen..."
|
||||
|
||||
scene whitehouse
|
||||
|
||||
e "... or it can clear the screen and then show a background."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "The show statement is used to show pictures."
|
||||
|
||||
e "When the show statement is used on an image with the same first
|
||||
name (called a tag) as one already shown, it replaces that picture."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "This makes it easy for characters to change emotions."
|
||||
|
||||
hide eileen
|
||||
|
||||
e "The hide statement hides an image."
|
||||
|
||||
show eileen happy at center
|
||||
|
||||
e "A new feature in Ren'Py 4 is the at clause on images, which
|
||||
lets you say where you want to show the image at. I can go
|
||||
from the center of the screen..."
|
||||
|
||||
show eileen happy at left
|
||||
|
||||
e "... to the left of the screen ..."
|
||||
|
||||
show eileen happy at right
|
||||
|
||||
e "... to the right of the screen ..."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "... and back to the center."
|
||||
|
||||
e "Ren'Py supports a variety of control statements, such as jump,
|
||||
call, return, if, and while statements."
|
||||
|
||||
e "Rather than bore you with the details, i'll just tell you to
|
||||
check grab the Ren'Py tutorial from http://www.bishoujo.us/renpy/."
|
||||
|
||||
e "That's just about it for writing scripts. Let me show you some
|
||||
of the new engine features."
|
||||
|
||||
e "The first feature I can show off is the ability to go
|
||||
full-screen. Hit the 'f' key to try it out, and hit 'f'
|
||||
again to go back to a window."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "The next feature, rollback, is really neat."
|
||||
|
||||
show eileen happy
|
||||
|
||||
menu:
|
||||
"Would you like to see it?"
|
||||
|
||||
"Yes.":
|
||||
pass
|
||||
|
||||
"No.":
|
||||
jump after_rollback
|
||||
|
||||
e "Rollback lets you play the game backwards."
|
||||
|
||||
e "It lets you go back and reread a line of dialogue you missed,
|
||||
or even to go back to a menu and make a different choice if you
|
||||
made a mistake."
|
||||
|
||||
e "We do limit the number of steps someone can rollback."
|
||||
|
||||
e "Try it out now, by hitting page up until you get back to the
|
||||
menu, and then choose 'No' instead of 'Yes'."
|
||||
|
||||
show eileen concerned
|
||||
|
||||
e "Well, try it."
|
||||
|
||||
e "You want to hit page up."
|
||||
|
||||
e "Well, whatever, your loss. Moving on."
|
||||
|
||||
|
||||
label after_rollback:
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "Another new feature works only on Windows. If a game crashes,
|
||||
a notepad is brought up showing the crash message, to help
|
||||
you debug what went wrong."
|
||||
|
||||
e "The biggest new feature, though, is reasonable
|
||||
documentation, which you can read at http://www.bishoujo.us/renpy/."
|
||||
|
||||
show eileen concerned
|
||||
|
||||
e "Since this is just a preview release, there are still a few
|
||||
things missing."
|
||||
|
||||
e "For example, there's no support for loading or saving."
|
||||
|
||||
e "We also left out music and animations."
|
||||
|
||||
show eileen happy
|
||||
|
||||
e "Don't worry, though. Those will be coming in the final release,
|
||||
which is due out in a few weeks."
|
||||
|
||||
e "You can begin making your own game by editing game/script.rpy. That's
|
||||
the script for the game you're playing now."
|
||||
|
||||
e "After you make a change, you have to re-run the game to see
|
||||
the change take effect."
|
||||
|
||||
show eileen vhappy
|
||||
|
||||
e "Good luck making your own games!"
|
||||
|
||||
return
|
||||
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -9,9 +9,8 @@ def match_times(source, dest):
|
||||
|
||||
def dosify(s):
|
||||
return s.replace("\n", "\r\n")
|
||||
return s
|
||||
|
||||
def copy_file(source, dest, license=""):
|
||||
def copy_file(source, dest, license="", dos=True):
|
||||
|
||||
print source, "->", dest
|
||||
|
||||
@@ -22,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)
|
||||
|
||||
@@ -44,12 +44,18 @@ def copy_tree(source, dest, should_copy=lambda fn : True, license=""):
|
||||
if "/CVS" in dirpath:
|
||||
continue
|
||||
|
||||
if "/.svn" in dirpath:
|
||||
continue
|
||||
|
||||
reldir = dirpath[len(source):]
|
||||
dstrel = dest + "/" + reldir
|
||||
|
||||
for i in dirnames:
|
||||
if i == "CVS":
|
||||
continue
|
||||
|
||||
if i == ".svn":
|
||||
continue
|
||||
|
||||
os.mkdir(dstrel + "/" + i)
|
||||
|
||||
@@ -92,36 +98,61 @@ def main():
|
||||
|
||||
doc_files = [
|
||||
'example.html',
|
||||
'tutorial.html',
|
||||
'reference.html',
|
||||
'style.css',
|
||||
'RELEASING.txt',
|
||||
]
|
||||
|
||||
# Copy doc
|
||||
copy_tree("doc", target + "/doc",
|
||||
should_copy = lambda fn : fn in doc_files)
|
||||
|
||||
# Copy the game
|
||||
# Copy the game
|
||||
copy_tree(gamedir, target + "/game",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~") and not fn.endswith(".mpg"))
|
||||
|
||||
copy_tree("common", target + "/common",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
def cp(x, license=""):
|
||||
copy_file(x, target + "/" + x)
|
||||
copy_tree("dse", target + "/dse",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
copy_tree("extras", target + "/extras",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
copy_tree("scripts", target + "/scripts",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
def cp(x, license="", dos=True):
|
||||
copy_file(x, target + "/" + x, dos=dos)
|
||||
|
||||
cp("CHANGELOG.txt")
|
||||
cp("LICENSE.txt")
|
||||
cp("README_RENPY.txt")
|
||||
cp("archive_images.bat")
|
||||
cp("lint.bat")
|
||||
cp("run_game.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_game.pyw", license=license)
|
||||
copy_file("run_game.py", target + "/run_dse.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_dse.pyw", license=license)
|
||||
cp("archiver.py", license=license)
|
||||
cp("build_exe.py", license=license)
|
||||
# cp("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,13 @@
|
||||
Steps one needs to take to make a Ren'Py distro.
|
||||
|
||||
0) Update the documentation. (This involves dump_styles, and perhaps
|
||||
copying in a new config.keymap)
|
||||
|
||||
1) Change the version in run_game.py
|
||||
|
||||
2) In cygwin, run "release.sh <version>"
|
||||
|
||||
3) Test run_game.exe
|
||||
4) Test console.exe
|
||||
|
||||
8) Release.
|
||||
@@ -0,0 +1,14 @@
|
||||
Some notes about making sure things are imported in the proper order:
|
||||
|
||||
1) Every module should import 'renpy'.
|
||||
|
||||
2) It's okay for modules to 'import renpy.game as game'.
|
||||
|
||||
3) Those are all the imports that normal modules should do.
|
||||
|
||||
4) renpy/__init__.py should list all the modules in the system, a
|
||||
topologically sorted order, such that if a definition in a uses
|
||||
a definition in b, b comes before a. Please note that this only
|
||||
considers the top level of the file.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
all:: tutorial.html example.html
|
||||
all:: reference.html example.html
|
||||
|
||||
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py tutorial.xml > tutorial.hi.xml
|
||||
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
|
||||
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
|
||||
reference.html: reference.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py reference.xml > reference.hi.xml
|
||||
xsltproc stylesheet.xslt reference.hi.xml > reference.html
|
||||
cp reference.html style.css ~/ab/website/renpy/devel/doc
|
||||
|
||||
|
||||
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml ../demo2/script.rpy
|
||||
python preprocess.py example.xml > example.hi.xml
|
||||
xsltproc stylesheet.xslt example.hi.xml > example.html
|
||||
cp example.html style.css ~/ab/website/renpy/devel/doc
|
||||
@@ -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!
|
||||
@@ -0,0 +1,15 @@
|
||||
# With pygame surfaces and fill.
|
||||
|
||||
Menu mousemove: .1336, .1244, .1437
|
||||
"Save when I tell you to": .055, .066, .0476
|
||||
|
||||
|
||||
# Without fill.
|
||||
|
||||
Menu mousemove: .115, .129, .134
|
||||
"Save when I tell you to": .050, .057, .046
|
||||
|
||||
# With new surface code.
|
||||
|
||||
Menu mousemove: .02something
|
||||
"Save when I tell you to": .038, .040, .038
|
||||
@@ -0,0 +1,44 @@
|
||||
import xml.dom.minidom
|
||||
import sys
|
||||
|
||||
def escape(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace('"', """)
|
||||
|
||||
def transform_children(node):
|
||||
|
||||
rv = [ ]
|
||||
|
||||
for n in node.childNodes:
|
||||
rv.append(transform(n))
|
||||
|
||||
return ''.join(rv)
|
||||
|
||||
|
||||
def transformElement(node):
|
||||
|
||||
attributes = ' '.join(['%s="%s"' % (k, escape(v)) for k,v in node.attributes.items()])
|
||||
|
||||
tag = node.tagName
|
||||
|
||||
|
||||
# Otherwise, the default.
|
||||
return "<%s %s>%s</%s>" % (node.tagName, attributes,
|
||||
transform_children(node), node.tagName)
|
||||
|
||||
|
||||
|
||||
def transform(node):
|
||||
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
return transformElement(node)
|
||||
else:
|
||||
return escape(str(node))
|
||||
|
||||
def main():
|
||||
|
||||
dom = xml.dom.minidom.parse(sys.argv[1])
|
||||
|
||||
print transform(dom.documentElement)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<renpydoc>
|
||||
|
||||
<!-- (define-key xml-mode-map '(control return) 'tompy-xml-ctrlret) -->
|
||||
|
||||
<section title="Say Statement">
|
||||
|
||||
<rule name="statement">
|
||||
<alt>string</alt>
|
||||
<alt>string string</alt>
|
||||
<alt>identifier string</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
These three productions are the various forms of the say
|
||||
statement. In order, they are the 1-string form, the 2-string
|
||||
form, and the name-string form. We'll discuss individually
|
||||
what each one does.
|
||||
</formal><formal>
|
||||
The 1-string form calls the <f>say</f> function (found in the
|
||||
execution context) with the string.
|
||||
</formal><formal>
|
||||
The 2-string form also calls the <f>say</f> function, but this
|
||||
time it passes it the two strings. The strings are passed in the
|
||||
order in which they appear.
|
||||
</formal><formal>
|
||||
When a identifierstring say statement is executed, the first thing
|
||||
that happens is that the identifier is looked up in the execution
|
||||
context. It's an error if the name is not defined. If the name
|
||||
is bound to a string, both strings are passed to the <f>say</f>
|
||||
function, as if the 2-string form was used. Otherwise, the
|
||||
<f>say</f> method is called on the object that the identifier was
|
||||
bound to, with the string as the only argument.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
While the various say statements are very flexible, there are
|
||||
some conventions that we suspect users will use when writing
|
||||
games.
|
||||
</user><user>
|
||||
The 1-string form of the say statment is rendered to the user in
|
||||
a text box without any name associated with it. It's used to
|
||||
indicate the thoughts of the POV character, or to narrate
|
||||
actions that occur in the game.
|
||||
</user><user>
|
||||
The 2-string form is displayed with the first string as a name,
|
||||
and the second string as the line of dialogue said by the
|
||||
character with that name. It's used to indicate spoken dialogue,
|
||||
usually from characters that are minor enough to not have a
|
||||
character object associated with them. The 2-string form is
|
||||
rarely used directly, as normally the name-string or 1-string
|
||||
forms are preferred for dialogue from main characters.
|
||||
</user><user>
|
||||
Generally, when the name-string form is used, the first name
|
||||
refers to a character object. The name field of that character
|
||||
object is looked up, and used to display dialogue as if the
|
||||
2-string form was used. So the name-string form is what is used
|
||||
when we want a main character to say something. This form allows
|
||||
the direct use of the character's name before the dialogue,
|
||||
without requiring the quoting needed by the 2-string form.
|
||||
</user>
|
||||
|
||||
<example>
|
||||
"I was walking down the street one day, when I came across the postman."
|
||||
|
||||
me "Hey, any mail for me today?"
|
||||
|
||||
"Postman" "Yeah, a package came for you. But there's no return address."
|
||||
</example>
|
||||
|
||||
</section>
|
||||
|
||||
<subsection title="Image Display">
|
||||
|
||||
<formal>
|
||||
In Ren'Py, image display is controlled by a pair of image
|
||||
lists. The two image lists are the are called the master image
|
||||
list and the temporary image list. These lists contain python
|
||||
objects that are capable of drawing themselves to the
|
||||
screen. When the time comes to show something to the user, we
|
||||
iterate through the temporary image list, drawing things on
|
||||
the screen in the order in which they appear in the list. So
|
||||
the first thing in the list will be the background, and the
|
||||
thing that's drawn closest to the user will be the last thing
|
||||
in the list.
|
||||
</formal><formal>
|
||||
We can divide the execution of a Ren'Py program into periods
|
||||
where we are showing a screen to the user (for example, during
|
||||
the execution of a say statement) and periods where we are
|
||||
not. After a period of showing the screen to the user, the master image
|
||||
list is copied over the temporary image list.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
We have two image lists for two reasons. The first is the idea
|
||||
that only the temporary image list will include transitions,
|
||||
and once we're done performing the transition, we want it to
|
||||
be remove from the list. The second reason is an idea that
|
||||
(behind the scenes) the temporary list will also include
|
||||
interface elements (like boxes containing character
|
||||
dialog). We, in general, want these boxes to be shown to the
|
||||
user once and then removed from the screen.
|
||||
</user>
|
||||
|
||||
<rule name="image_name">
|
||||
<alt>identifier+</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
An image name consists of one or more identifiers. The first
|
||||
identifier is called the primary identifier of this image
|
||||
name, while the second and later identifier are called
|
||||
secondary identifers. An image name may not include a Ren'Py
|
||||
keyword.
|
||||
</formal><formal>
|
||||
Ren'Py maintains an image dictionary, which maps between image
|
||||
names and objects implementing images that can be show to the
|
||||
user.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
The idea behind primary identifiers is that we will normally
|
||||
want to display only one image of a character at a time. So we
|
||||
use an image's primary identifier to find other images in the
|
||||
master display list that have the same primary identifier.
|
||||
</user>
|
||||
|
||||
|
||||
<rule name="statement">
|
||||
<alt>"image" image_name "=" python_expression</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
This adds a new entry to the image dictionary. Specifically, the
|
||||
python_expression is evaluated to get something that's drawable,
|
||||
and then that object is stored in a tuple in the image
|
||||
dictionary.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
In general, the python expression will be a call to the image
|
||||
constructor, which loads in a new image from disk (or
|
||||
somewhere... read the documentation for the image constructor.)
|
||||
It's also possible to use another constructor, like the one for
|
||||
animation, but that will probably be more rare.
|
||||
</user><user>
|
||||
It's important to note that this doesn't actually load the image
|
||||
into ram, until close to the time when the image is actually
|
||||
needed.
|
||||
</user><user>
|
||||
In general, it makes sense to ensure that all image statements
|
||||
execure during game startup, before the call to
|
||||
startup_complete().
|
||||
</user>
|
||||
|
||||
<example>
|
||||
image woods = image("backgrounds/woods.jpg")
|
||||
image eileen red upset = image("eileen/red_upset.png")
|
||||
image eileen red happy = image("eileen/red_happy.png")
|
||||
</example>
|
||||
|
||||
</subsection>
|
||||
</renpydoc>
|
||||
@@ -0,0 +1,373 @@
|
||||
To write a game using Ren'Py, one must become familar with the Ren'Py
|
||||
script language. This file gives some basic concepts and a list of
|
||||
Ren'Py statements. A second file will give a list of python classes
|
||||
and functions that are intended to be used from Ren'Py code.
|
||||
|
||||
A Ren'Py script can first be seen as a long string of unicode
|
||||
characters. This string is first broken up into logical lines, then
|
||||
the lines are organized into blocks, and finally as a tree of
|
||||
statements. So we'll begin by defining some basic terms, and then
|
||||
we'll go on to give a list of statements Ren'Py understands.
|
||||
|
||||
Logical Lines:
|
||||
|
||||
Each file consists of one or more logical lines. The first logical
|
||||
line begins at the first character file, and subsequent logical lines
|
||||
begin immediately following the end of the previous line. Logical
|
||||
lines are normally ended by the first newline encountered. However,
|
||||
there are several cases that will cause a logical line to extend past
|
||||
a newline character:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
A logical line containing a string must extend at least until the
|
||||
end of that string.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
A logical line parenthesis, brackets, or braces cannot end until
|
||||
each opening character ('(', ']', or '}') is matched with a closing
|
||||
character (')', ']', or '}'). The expression enclosed within is called
|
||||
a parenthetical expression.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
A newline immediately preceded by a backslash will not end a logical
|
||||
line. Instead, both characters will be treated as whitespace, and
|
||||
ignored.
|
||||
<li>
|
||||
</ul>
|
||||
|
||||
Logical lines are numbered by giving the number of the physical line
|
||||
in the file on which the logical line begins. (At most one logical
|
||||
line can exist on each physical line.)
|
||||
|
||||
If a logical line contains a hash mark in it ('#'), all characters
|
||||
from the hash mark to character before the next physical newline are
|
||||
considered to be part of a comment, and are ignored as if they were
|
||||
whitespace. If, after this step, a logical line consists entirely of
|
||||
whitespace, it is ignored.
|
||||
|
||||
Each logical line has an indentaion level. This is computed by first
|
||||
converting tabs to spaces (using 8-space tab stops), and then looking
|
||||
at the number of spaces preceding the first non-whitespace character
|
||||
on the line.
|
||||
|
||||
The following examples all consist of single logical lines with
|
||||
indentation level 0.
|
||||
|
||||
<example>
|
||||
me "How are you doing?"
|
||||
</example>
|
||||
|
||||
<example>
|
||||
"It's a question that I ask myself every day. Today, however, is
|
||||
the first time I've asked it of someone else.
|
||||
</example>
|
||||
|
||||
<example>
|
||||
$ my_list = [ 1, 2, 3, 4,
|
||||
5, 6, 7, 8 ]
|
||||
</example>
|
||||
|
||||
<example>
|
||||
$ a = 1 \
|
||||
+ 2 \
|
||||
+ 3
|
||||
</example>
|
||||
|
||||
Blocks:
|
||||
|
||||
If a logical line is followed by a logical line with greater
|
||||
indentation, the second logical line is considered to be part of the
|
||||
block of the first logical line. This block contains all logical lines
|
||||
with an indentation level equal to that of the second logical line,
|
||||
provided there are no intervening lines with an indentaion level that
|
||||
is less than that of the second logical line. If a line is encountered
|
||||
with an indentation level that is greater than that of the first
|
||||
logical line, but less than that of the second, the indentation is
|
||||
mismatched and an error is reported.
|
||||
|
||||
An example of a block (as part of an if statement) is:
|
||||
|
||||
<example>
|
||||
if happy:
|
||||
e "I'm feeling really happy today."
|
||||
e "It's like I'm the happiest person in the world."
|
||||
</example>
|
||||
|
||||
An example of an indentation mismatch is:
|
||||
|
||||
<example>
|
||||
else:
|
||||
e "I'm having a really bad day."
|
||||
e "I can't even get the indentation right."
|
||||
</example>
|
||||
|
||||
|
||||
One of the things we can parse is a string literal. String literals
|
||||
begin with a quote character (' or "), and end with a matching
|
||||
unescaped quote character. Strings use backslash (\) as an escape
|
||||
character. The following are suppored escape sequences:
|
||||
|
||||
<ul>
|
||||
<li>"\\" - Backslash</li>
|
||||
<li>"\"" - Double Quote</li>
|
||||
<li>"\'" - Single Quote</li>
|
||||
<li>"\n" - Newline</li>
|
||||
<li>"\ " - Space</li>
|
||||
</ul>
|
||||
|
||||
As the string is being parsed, but before escape processing is done,
|
||||
contiguous sequences of whitespace are converted to single space
|
||||
characters. To include more than single spaces, explicitly escape the
|
||||
desired whitespace.
|
||||
|
||||
<example>
|
||||
"I hate having to \"escape\" whitespace."
|
||||
</example>
|
||||
|
||||
Names and Numbers:
|
||||
|
||||
Names begin with a character in the set [a-zA-Z_], and may contain
|
||||
additional characters in the set [a-zA-Z0-9_]. A keyword in Ren'Py may
|
||||
not be used as a name.
|
||||
|
||||
Some example names are:
|
||||
|
||||
<example>
|
||||
eileen
|
||||
lucy
|
||||
molly
|
||||
crm_114
|
||||
_underscore_
|
||||
</example>
|
||||
|
||||
A dotted_name consists of one or more names, separated by dots '.'.
|
||||
|
||||
<example>
|
||||
eileen
|
||||
eileen.name
|
||||
eileen.address.street
|
||||
</example>
|
||||
|
||||
A number matches the characters [0-9.].
|
||||
|
||||
<example>
|
||||
1
|
||||
1234.
|
||||
3.14159
|
||||
.2
|
||||
</example>
|
||||
|
||||
|
||||
Python:
|
||||
|
||||
Ren'Py scripts may contain embedded python. There are three kinds of
|
||||
python things that can be mixed into Ren'Py: simple_expressions,
|
||||
python_expressions, and python_statements.
|
||||
|
||||
A simple_expression consists of either a dotted_name, string, or
|
||||
number optionally followed by a parenthetical expression, or just a
|
||||
single parenthetical expression.
|
||||
|
||||
Examples of simple_expressions are:
|
||||
|
||||
<example>
|
||||
eileen
|
||||
sample.image("foo.jpg", size=(100,200))
|
||||
"test"
|
||||
(1 + 2 + 3)
|
||||
</example>
|
||||
|
||||
Python_expressions occur in statements such as if and while, and
|
||||
extend from the current location on the logical line to just before
|
||||
the next ':' that is not contained within a parenthetical expression
|
||||
on the logical line.
|
||||
|
||||
Examples of expressions that are python_expressions but not
|
||||
simple_expressions are:
|
||||
|
||||
<example>
|
||||
1 + 2 + 3
|
||||
foo.bar().baz()
|
||||
</example>
|
||||
|
||||
Finally, python_statements simply assume that all remaining
|
||||
information on the line should be interpreted as python code.
|
||||
|
||||
|
||||
|
||||
Statements:
|
||||
|
||||
The parsing of each logical line is syntax-directed. When parsing a
|
||||
block, all of the logical lines in that block are parsed as
|
||||
statements. The logical lines at the top level of a file are
|
||||
considered to form a block. Here, we give the rules for parsing each
|
||||
kind of statement, as well as an overview of what each statement
|
||||
does. We'll first give some insight as to what a statement does when
|
||||
used with the standard library, and then we'll write a little about
|
||||
how it goes about accomplishing that task.
|
||||
|
||||
|
||||
|
||||
Say Statements:
|
||||
|
||||
<rule name="statement">
|
||||
<alt>string</alt>
|
||||
<alt>simple_expression string</alt>
|
||||
</rule>
|
||||
|
||||
Probably the most commonly used statement in a Ren'Py script is the
|
||||
say statement. It's so commonly used that we chose not to denote it
|
||||
with a keyword, but instead to simply make all bare strings or
|
||||
simple_expression string pairs into say statements.
|
||||
|
||||
Executing a say statement results in a line of dialogue or thought
|
||||
being displayed to the user. Ren'Py then waits for the user to click
|
||||
or otherwise dismiss the display before proceeding with execution of
|
||||
the program.
|
||||
|
||||
In the single string version of the statement, the string is taken as
|
||||
a thought or narration that should be displayed, unadorned, to the
|
||||
user. If the string is preceded by a simple_expression, the expression
|
||||
is first evaluated to yield a string, and that string is used to
|
||||
indicate to the user who is speaking the line of dialogue.
|
||||
|
||||
Ren'Py executes say statements by first evaluating the
|
||||
simple_expression, if present, and then calling the say function with
|
||||
the values of the two strings, or None if the simple_expression is not
|
||||
defined. Take a look at the documentation for the say function to see
|
||||
what happens from there, as behind the scenes it's a little more
|
||||
complicated that what we describe here.
|
||||
|
||||
Some example say statements are:
|
||||
|
||||
<example>
|
||||
"I was walking down the street one day, when I came across the postman."
|
||||
|
||||
me "Hey, any mail for me today?"
|
||||
|
||||
"Postman" "Yeah, a package came for you. But there's no return address."
|
||||
</example>
|
||||
|
||||
Here, we assume that 'me' is an expression that expands to the name of
|
||||
the main character.
|
||||
|
||||
|
||||
|
||||
Menu Statement:
|
||||
|
||||
<rule name="statement">
|
||||
<alt>"menu" name? ":" menu_block</alt>
|
||||
</rule>
|
||||
|
||||
<rule name="menu_item">
|
||||
<alt>string</alt>
|
||||
<alt>string ( "if" python_expression )? ":" block</alt>
|
||||
<alt>"set" simple_expression</alt>
|
||||
</rule>
|
||||
|
||||
The menu statement displays a menu to the user, waits for the user to
|
||||
provide a response, and then runs a block of code corresponding to the
|
||||
choice that the user made. In a visual novel game, menu statement are
|
||||
the main way in which the user can interact with the story.
|
||||
|
||||
Menu statements begin with the word "menu". This word can be followed
|
||||
by an optional name. If the name is provided, it's as if the menu
|
||||
statement was proceded by a label statement with that name. A menu
|
||||
statement must have a block associated with it, and each of the
|
||||
logical lines in this menu_block are parsed as menu_items.
|
||||
|
||||
There are three kinds of menu_items. The first is a logical line
|
||||
containing only a string. These menu_items are used to provide blocks
|
||||
of text in the menu that are not selectable. For example, they can be
|
||||
use to provide a prompt to the user.
|
||||
|
||||
The second kind of menu_item is a string, followed by an optional if
|
||||
clause, followed by a colon. This menu_item has a block of statements
|
||||
associated with it. It's used to indicate a choice on the menu. If the
|
||||
if clause is supplied, the choice is only presented to the user if the
|
||||
python_expression is true. If a choice is selected by the user, the
|
||||
statements in the block associated with the choice are executed before
|
||||
control continues after the menu statement.
|
||||
|
||||
The final menu_item is the word "set" followed by a
|
||||
simple_expression. The expression is evaluated to get a set of menu
|
||||
items to supress. This set is used to filter the menu choices. If a
|
||||
choice string is in the set, then the corresponding menu choice is not
|
||||
displayed to the user. If a set is defined, then when a choice has
|
||||
been made, the corresponding choice string is added to the set. This
|
||||
provides an easy way of having a menu where the user can select each
|
||||
item at most once.
|
||||
|
||||
Here's an example of a fairly complex menu, that demonstrates all
|
||||
these features.
|
||||
|
||||
<example>
|
||||
menu what_to_do:
|
||||
set what_to_do_set
|
||||
"What should we do today?"
|
||||
|
||||
"Go to the movies.":
|
||||
"We went to the movies."
|
||||
|
||||
"Go shopping.":
|
||||
"We went shopping, and the girls both bought swimsuits."
|
||||
$ have_swimsuits = True
|
||||
|
||||
"Go to the beach." if have_swimsuits:
|
||||
"We went to the beach together."
|
||||
</example>
|
||||
|
||||
The actual task of displaying a menu is implemented internally by a
|
||||
call to the menu python function.
|
||||
|
||||
|
||||
|
||||
Graphics Statements:
|
||||
|
||||
|
||||
Before we can discuss the graphics statements, we must first discuss
|
||||
the graphics model that Ren'Py uses. This will be covered in greater
|
||||
detail in the library reference, but we want to cover the fundamentals
|
||||
here to allow the graphics statements to be put in proper context.
|
||||
|
||||
First, let me define what it means to display a scene. The execution
|
||||
of a Ren'Py script can be roughly divided into periods when the script
|
||||
is executing code, and periods when the script is blocked wating for
|
||||
user input (as is the case when a menu or say statement
|
||||
executes). When Ren'Py is waiting for input, it is displaying a
|
||||
scene. When it gets the input it's waiting for and execution
|
||||
continues, we say it's finished displaying the scene.
|
||||
|
||||
The display in Ren'Py is controlled by three scene lists. These scene
|
||||
lists are called the master scene list, the overlay scene list, and
|
||||
the transient scene list.
|
||||
|
||||
The only scene list that is displayed to the user is the transient
|
||||
scene list. This list is ordered such that items that are at the end
|
||||
of the scene list are closest to the user. All of the items in the
|
||||
scene lists must be displayable--- capable of drawing themselves to
|
||||
the screen. See the library reference for the exact API displayable
|
||||
objects must implement.
|
||||
|
||||
When a scene is displayed, the transient scene list is extended by
|
||||
appending the overlay scene list to it. When display finishes, the
|
||||
transient scene list is replaced with a copy of the master scene
|
||||
list. The scene, show, and hide statements work by modifying both the
|
||||
master and transient scene lists.
|
||||
|
||||
Having three scene lists may seem like needless complexity, but it's
|
||||
useful in implementing transitions and other effects. When we want to
|
||||
fade in an image, we add that image to the master scene list, and that
|
||||
image wrapped in an object that handles the fade effect into the
|
||||
transient scene list. When the scene is then displayed, the image will
|
||||
fade it. The wrapped image on the transient list is then replaced
|
||||
with the image from master list, removing any overhead the fade effect
|
||||
can entail.
|
||||
|
||||
The transient list also helps when displaying UI elements, like
|
||||
dialogue or menus. Finally, the overlay list is used to display things
|
||||
that should always be presented to the user, like load and save
|
||||
buttons.
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -7,6 +8,7 @@ import time
|
||||
import inspect
|
||||
|
||||
sys.path.append('..')
|
||||
sys.path.append('../module')
|
||||
import renpy
|
||||
|
||||
|
||||
@@ -84,6 +86,12 @@ def function(m):
|
||||
renpy.store.renpy = renpy.exports
|
||||
|
||||
func = eval(name, store)
|
||||
|
||||
if isinstance(func, renpy.curry.Curry):
|
||||
if func.callable == renpy.curry.Curry:
|
||||
func = func.args[0]
|
||||
else:
|
||||
func = func.callable
|
||||
|
||||
doc = func.__doc__
|
||||
|
||||
@@ -97,6 +105,9 @@ def function(m):
|
||||
else:
|
||||
args = inspect.formatargspec(*inspect.getargspec(func))
|
||||
|
||||
args = re.sub(r"'\\x00\\x01.*?\\xff'", "im.ramp(0, 255)", args)
|
||||
|
||||
args = re.sub(r'<.*?>', '...', args)
|
||||
|
||||
docparas = []
|
||||
|
||||
@@ -126,6 +137,15 @@ def main():
|
||||
s = f.read()
|
||||
f.close()
|
||||
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
try:
|
||||
renpy.main.main("dump_styles")
|
||||
except "foo":
|
||||
pass
|
||||
|
||||
os.chdir("doc")
|
||||
|
||||
s = re.sub(r"<!-- func (\S+) -->", function, s)
|
||||
s = re.sub(r"<!-- include (\S+) -->", include, s)
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,8 @@
|
||||
init:
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
label main_menu:
|
||||
|
||||
$ renpy.renpy.style.write_docs("doc/styles.xml")
|
||||
$ raise "foo"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This program dumps all the text found in the script to the file text.txt.
|
||||
# If on windows, it also tries to show text.txt to the user.
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
import renpy
|
||||
|
||||
def process_block(block, out):
|
||||
|
||||
for fn, ln, text, child in block:
|
||||
|
||||
if text.startswith("$") or text.startswith("python"):
|
||||
continue
|
||||
|
||||
if text.startswith("init"):
|
||||
continue
|
||||
|
||||
if text.startswith("if") or text.startswith("while"):
|
||||
process_block(child, out)
|
||||
continue
|
||||
|
||||
for m in re.finditer(r'"((?:[^\\"]+|\\.)+)"|' +
|
||||
r"'((?:[^\\']+|\\.)+)'", text):
|
||||
|
||||
s = m.group(1) or m.group(2)
|
||||
|
||||
s = re.sub(r'\s+', ' ', s)
|
||||
s = re.sub(r'\\.', ' ', s)
|
||||
|
||||
s = re.sub(r'\{.*?\}', '', s)
|
||||
|
||||
print >>out, s.encode('utf-8')
|
||||
print >>out
|
||||
|
||||
process_block(child, out)
|
||||
|
||||
|
||||
def process(fn, out):
|
||||
|
||||
block = renpy.parser.group_logical_lines(renpy.parser.list_logical_lines(fn))
|
||||
|
||||
process_block(block, out)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
pattern = "game/*.rpy"
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
pattern = sys.argv[1]
|
||||
|
||||
files = glob.glob(pattern)
|
||||
files = [ i for i in files if not i.startswith("common/") ]
|
||||
|
||||
out = file("text.txt", "w")
|
||||
out.write(codecs.BOM_UTF8)
|
||||
|
||||
for fn in files:
|
||||
process(fn, out)
|
||||
|
||||
out.close()
|
||||
|
||||
try:
|
||||
os.startfile('text.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
../../extras/fullscreen.rpy
|
||||
@@ -0,0 +1 @@
|
||||
../../extras/kanamode.rpy
|
||||
@@ -0,0 +1 @@
|
||||
../script.rpy
|
||||
@@ -0,0 +1,102 @@
|
||||
init -10:
|
||||
|
||||
$ config.searchpath.append('moonlight')
|
||||
|
||||
# Set up the size of the screen, and the window title.
|
||||
$ config.screen_width = 800
|
||||
$ config.screen_height = 600
|
||||
$ config.window_title = "A Ren'Py Extras Game"
|
||||
|
||||
# These are positions that can be used inside at clauses. We set
|
||||
# them up here so that they can be used throughout the program.
|
||||
$ left = Position(xpos=0.0, xanchor='left')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
|
||||
# Likewise, we set up some transitions that we can use in with
|
||||
# clauses and statements.
|
||||
$ fade = Fade(.5, 0, .5) # Fade to black and back.
|
||||
$ dissolve = Dissolve(0.5)
|
||||
|
||||
$ wiperight = CropMove(1.0, "wiperight")
|
||||
$ wipeleft = CropMove(1.0, "wipeleft")
|
||||
$ wipeup = CropMove(1.0, "wipeup")
|
||||
$ wipedown = CropMove(1.0, "wipedown")
|
||||
|
||||
$ slideright = CropMove(1.0, "slideright")
|
||||
$ slideleft = CropMove(1.0, "slideleft")
|
||||
$ slideup = CropMove(1.0, "slideup")
|
||||
$ slidedown = CropMove(1.0, "slidedown")
|
||||
|
||||
$ slideawayright = CropMove(1.0, "slideawayright")
|
||||
$ slideawayleft = CropMove(1.0, "slideawayleft")
|
||||
$ slideawayup = CropMove(1.0, "slideawayup")
|
||||
$ slideawaydown = CropMove(1.0, "slideawaydown")
|
||||
|
||||
$ irisout = CropMove(1.0, "irisout")
|
||||
$ irisin = CropMove(1.0, "irisin")
|
||||
|
||||
# Now, we declare the images that are used in the program.
|
||||
|
||||
# Images.
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
image white = Solid((255, 255, 255, 255))
|
||||
image yellow = Solid((255, 255, 200, 255))
|
||||
|
||||
# Opening Sequence.
|
||||
image bigbeach1 = Image("bigbeach1.jpg")
|
||||
image presents = Image("presents.png")
|
||||
|
||||
# Backgrounds.
|
||||
image beach1 = Image("beach1b.jpg")
|
||||
image beach1 mary = Image("beach1c.jpg")
|
||||
image beach1 title = Image("beach1a.jpg")
|
||||
|
||||
image beach2 = Image("beach2.jpg")
|
||||
image beach3 = Image("beach3.jpg")
|
||||
|
||||
image dawn1 = Image("dawn1.jpg")
|
||||
image dawn2 = Image("dawn2.jpg")
|
||||
|
||||
image library = Image("library.jpg")
|
||||
|
||||
# Ending 1.
|
||||
image transfer = Image("transfer.png")
|
||||
image moonpic = Image("moonpic.jpg")
|
||||
image nogirlpic = Image("nogirlpic.jpg")
|
||||
|
||||
|
||||
# Ending 3.
|
||||
image littlemary = Image("littlemary.jpg")
|
||||
|
||||
# Ending 4.
|
||||
image hospital1 = Image("hospital1.jpg")
|
||||
image hospital2 = Image("hospital2.jpg")
|
||||
image hospital3 = Image("hospital3.jpg")
|
||||
image heaven = Image("heaven.jpg")
|
||||
|
||||
# Endings Common
|
||||
image good_ending = Image("ending.jpg")
|
||||
image bad_ending = Image("badending.jpg")
|
||||
|
||||
# Mary.
|
||||
image mary dark confused smiling = Image("mary_dark_confused_smiling.png")
|
||||
image mary dark confused wistful = Image("mary_dark_confused_wistful.png")
|
||||
image mary dark crying = Image("mary_dark_crying.png")
|
||||
image mary dark laughing = Image("mary_dark_laughing.png")
|
||||
image mary dark sad = Image("mary_dark_sad.png")
|
||||
image mary dark smiling = Image("mary_dark_smiling.png")
|
||||
image mary dark vhappy = Image("mary_dark_vhappy.png")
|
||||
image mary dark wistful = Image("mary_dark_wistful.png")
|
||||
|
||||
image mary dawn confused smiling = Image("mary_dawn_confused_smiling.png")
|
||||
image mary dawn confused wistful = Image("mary_dawn_confused_wistful.png")
|
||||
image mary dawn crying = Image("mary_dawn_crying.png")
|
||||
image mary dawn laughing = Image("mary_dawn_laughing.png")
|
||||
image mary dawn sad = Image("mary_dawn_sad.png")
|
||||
image mary dawn smiling = Image("mary_dawn_smiling.png")
|
||||
image mary dawn vhappy = Image("mary_dawn_vhappy.png")
|
||||
image mary dawn wistful = Image("mary_dawn_wistful.png")
|
||||
|
||||
label start:
|
||||
jump example
|
||||
@@ -0,0 +1,16 @@
|
||||
# This file contains a minimal set of style changes needed to have
|
||||
# Ren'Py work with a game that's 640x480 in size.
|
||||
|
||||
init 1:
|
||||
# Change the screen width.
|
||||
$ config.screen_width = 640
|
||||
$ config.screen_height = 480
|
||||
|
||||
# Font sizes.
|
||||
$ style.default.size = 20
|
||||
$ style.button_text.size = 20
|
||||
$ style.file_picker_text.size = 14
|
||||
|
||||
# Change the size of the thumbnails in the file picker.
|
||||
$ library.thumbnail_width = 60
|
||||
$ library.thumbnail_height = 45
|
||||
@@ -0,0 +1,35 @@
|
||||
# This file adds a number of buttons to the lower-right hand corner of
|
||||
# the screen. Three of these buttons jump to the game menu, which
|
||||
# giving quick access to Load, Save, and Prefs. The fourth button
|
||||
# toggles skipping, to make that more convenient.
|
||||
|
||||
init:
|
||||
|
||||
# Give us some space on the right side of the screen.
|
||||
$ style.window.right_margin = 100
|
||||
|
||||
python:
|
||||
|
||||
def toggle_skipping():
|
||||
config.skipping = not config.skipping
|
||||
|
||||
def button_game_menu():
|
||||
|
||||
# to save typing
|
||||
ccinc = renpy.curried_call_in_new_context
|
||||
|
||||
ui.vbox(xpos=0.98, ypos=0.98, xanchor='right', yanchor='bottom')
|
||||
ui.textbutton("Skip", clicked=toggle_skipping)
|
||||
ui.textbutton("Save", clicked=ccinc("_game_menu_save"))
|
||||
ui.textbutton("Load", clicked=ccinc("_game_menu_load"))
|
||||
ui.textbutton("Prefs", clicked=ccinc("_game_menu_preferences"))
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(button_game_menu)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# In this file, we have an example of replacing the default menu with
|
||||
# a custom one. This new menu consists of a series of centered
|
||||
# buttons, with dialogue appearing in a narration window.
|
||||
|
||||
# You can try this one out by dropping it right into the game
|
||||
# directory.
|
||||
|
||||
# The code that implements button menus.
|
||||
init:
|
||||
python:
|
||||
style.create('menu_button', 'button')
|
||||
style.create('menu_button_text', 'button_text')
|
||||
|
||||
def menu(menuitems):
|
||||
|
||||
narration = None
|
||||
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='menu_window')
|
||||
ui.vbox(xanchor='center', xpos=0.5)
|
||||
|
||||
for label, value in menuitems:
|
||||
if value is None:
|
||||
narration = label
|
||||
continue
|
||||
|
||||
ui.textbutton(label,
|
||||
style="menu_button",
|
||||
text_style="menu_button_text",
|
||||
clicked=ui.returns(value))
|
||||
|
||||
ui.close()
|
||||
|
||||
if narration:
|
||||
narrator(narration, interact=False)
|
||||
|
||||
rv = ui.interact()
|
||||
renpy.checkpoint()
|
||||
return rv
|
||||
|
||||
# Styles to make button menus look good.
|
||||
init 1:
|
||||
python hide:
|
||||
style.menu_window.background = None
|
||||
style.menu_window.yminimum = 0
|
||||
style.menu_window.ypos = 0.40
|
||||
style.menu_window.yanchor = 'center'
|
||||
style.menu_window.xfill = True
|
||||
|
||||
style.menu_button.background = Solid((0, 0, 255, 128))
|
||||
style.menu_button.xfill = True
|
||||
style.menu_button.top_padding = 5
|
||||
style.menu_button.bottom_margin = 5
|
||||
|
||||
style.menu_button_text.xpos = 0.5
|
||||
style.menu_button_text.xanchor = 'center'
|
||||
|
||||
style.menu_button_text.hover_color = (255, 255, 0, 255)
|
||||
style.menu_button_text.idle_color = (255, 255, 255, 255)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# This function implements a commentary track, which is a special kind of
|
||||
# say statement that only displays if commentary is set to on in the
|
||||
# new preference that this mode creates. Making this unlockable is easy,
|
||||
# but is left as an exercise for the reader.
|
||||
#
|
||||
# Use it by putting in lines like:
|
||||
#
|
||||
# commentary "Hey! This is commentary!"
|
||||
|
||||
init -10:
|
||||
|
||||
python:
|
||||
|
||||
# The style for the commentary window.
|
||||
style.create('commentary_window', 'window', 'The commentary window.')
|
||||
|
||||
style.commentary_window.background = Solid((0, 0, 0, 192))
|
||||
style.commentary_window.ypos = 0.0
|
||||
style.commentary_window.yanchor = 'top'
|
||||
|
||||
|
||||
# Ensure that the commentary attribute exists.
|
||||
if persistent.commentary is None:
|
||||
persistent.commentary = False
|
||||
|
||||
|
||||
commentary = Character(None, window_style='commentary_window',
|
||||
condition = 'persistent.commentary')
|
||||
|
||||
python hide:
|
||||
|
||||
cp = _Preference('Commentary', 'commentary', [
|
||||
('Enabled', True, 'True'),
|
||||
('Disabled', False, 'True')
|
||||
], base=persistent)
|
||||
|
||||
library.preferences['prefs_right'].append(cp)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# This file contains code to automatically switch the game into
|
||||
# fullscreen mode.
|
||||
|
||||
# Please note: Fullscreen mode is broken on some platforms (mostly
|
||||
# virtual windows machines like vmware and virtual PC). So having
|
||||
# this code automatically execute could make your game hard to use
|
||||
# on those platforms.
|
||||
#
|
||||
# The way we work around this is to tell users of these platforms
|
||||
# to press 'f' to toggle fullscreen mode. If the game's running in
|
||||
# windowed mode, the problems don't seem to appear.
|
||||
#
|
||||
# You should probably put words to that effect in some sort of
|
||||
# README file.
|
||||
|
||||
# The first time this code runs, the game switches into fullscreen.
|
||||
# After that, it respects the user's preference.
|
||||
|
||||
init:
|
||||
python:
|
||||
if not persistent.set_fullscreen:
|
||||
persistent.set_fullscreen = True
|
||||
_preferences.fullscreen = True
|
||||
@@ -0,0 +1,262 @@
|
||||
# This extra implements an image gallery, complete with automatic
|
||||
# unlocking of the images that have been shown to the user. The images
|
||||
# are divided into pages, with a fixed number of images on each page.
|
||||
|
||||
# Right now, this is configured to used the images found in the demo.
|
||||
# The images are repeated quite a bit, so that we can get four pages
|
||||
# of them. You probably wouldn't do that if you were using this in a
|
||||
# real game.
|
||||
|
||||
# To see this in action, drop it into the game directory of the
|
||||
# demo.
|
||||
|
||||
# Configuration.
|
||||
init:
|
||||
|
||||
python hide:
|
||||
|
||||
|
||||
# The number of columns and rows of images to show in the
|
||||
# gallery.
|
||||
store.gallery_cols = 3
|
||||
store.gallery_rows = 4
|
||||
|
||||
# The size that each image should be scaled to, and that
|
||||
# thumbnails should be.
|
||||
store.gallery_width = 160
|
||||
store.gallery_height = 120
|
||||
|
||||
# The contents of each of the page. Each of these is a list of
|
||||
# tuples with the first element of the tuple being the image
|
||||
# filename and the second element being the name of the image
|
||||
# that will unlock the image with this filename. (That is,
|
||||
# the name that is used in show or scene statements, as a
|
||||
# string.)
|
||||
#
|
||||
# When displaying an image as a thumbnail, this code first
|
||||
# looks for the file thumbnail_<filename>. If that file
|
||||
# exists, it should be a gallery_width x gallery_height
|
||||
# thumbnail. Otherwise, a thumbnail is automatically
|
||||
# generated, but it may screw up the aspect ratio of the
|
||||
# image.
|
||||
#
|
||||
# You probably want to create thumbnails for most images, to
|
||||
# limit memory consumption.
|
||||
page1 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page2 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page3 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
]
|
||||
|
||||
page4 = [
|
||||
( "9a_happy.png", "eileen happy" ),
|
||||
( "9a_vhappy.png", "eileen vhappy" ),
|
||||
( "9a_concerned.png", "eileen concerned" ),
|
||||
]
|
||||
|
||||
# This is the actual list of gallery pages. It's a list
|
||||
# of tuples, with the first element being the name of the
|
||||
# page, the second being the contents of the page (one of
|
||||
# the lists created above), and the third image being the
|
||||
# image used as the background of the page.
|
||||
store.gallery_pages = [
|
||||
("Backgrounds 1", page1, "washington.jpg"),
|
||||
("Backgrounds 2", page2, "whitehouse.jpg"),
|
||||
("Backgrounds 3", page3, "carillon.jpg"),
|
||||
("Character Art", page4, "washington.jpg"),
|
||||
]
|
||||
|
||||
# A window containing the gallery page buttons.
|
||||
style.create('gallery_pages', 'default')
|
||||
style.gallery_pages.xpos = 0.99
|
||||
style.gallery_pages.xanchor='right'
|
||||
style.gallery_pages.ypos = 0.02
|
||||
style.gallery_pages.yanchor = 'top'
|
||||
|
||||
# A button that links to a gallery page.
|
||||
style.create('gallery_page_button', 'button')
|
||||
style.create('gallery_page_button_text', 'button_text')
|
||||
|
||||
# A button that returns us to from whence we came.
|
||||
style.create('gallery_return_button', 'button')
|
||||
style.create('gallery_return_button_text', 'button_text')
|
||||
|
||||
style.gallery_return_button.xpos = 0.99
|
||||
style.gallery_return_button.xanchor='right'
|
||||
style.gallery_return_button.ypos = 0.98
|
||||
style.gallery_return_button.yanchor = 'bottom'
|
||||
|
||||
|
||||
# The grid containing the gallery image buttons.
|
||||
style.create('gallery_grid', 'default')
|
||||
|
||||
# The style of the buttons in the gallery.
|
||||
style.create('gallery_button', 'default')
|
||||
|
||||
# Right now, the backgrounds are all solids, but in a more
|
||||
# professional version, the insensitive background would
|
||||
# probably be a placeholder image that indicates that a
|
||||
# picture has yet to be unlocked.
|
||||
style.gallery_button.insensitive_background = Solid((192, 192, 192, 255))
|
||||
style.gallery_button.idle_background = Solid((255, 255, 255, 255))
|
||||
style.gallery_button.hover_background = Solid((255, 255, 192, 255))
|
||||
|
||||
style.gallery_button.left_margin = 5
|
||||
style.gallery_button.right_margin = 5
|
||||
style.gallery_button.top_margin = 5
|
||||
style.gallery_button.bottom_margin = 5
|
||||
|
||||
style.gallery_button.left_padding = 5
|
||||
style.gallery_button.right_padding = 5
|
||||
style.gallery_button.top_padding = 5
|
||||
style.gallery_button.bottom_padding = 5
|
||||
|
||||
# The style of the images in the buttons in the gallery.
|
||||
style.create('gallery_button_image', 'default')
|
||||
|
||||
# The style of the images that are being shown to the user.
|
||||
style.create('gallery_image', 'image_placement')
|
||||
|
||||
# The transition used when switching gallery pages.
|
||||
store.gallery_transition = Dissolve(0.5)
|
||||
|
||||
python:
|
||||
|
||||
# The function that actually manages the display of the image
|
||||
# gallery.
|
||||
def gallery():
|
||||
|
||||
page = 0
|
||||
|
||||
while True:
|
||||
|
||||
images = gallery_pages[page][1]
|
||||
ui.image(gallery_pages[page][2])
|
||||
|
||||
# Show the names of the various gallery pages.
|
||||
|
||||
ui.window(style='gallery_pages')
|
||||
ui.vbox(focus="gallery_pages")
|
||||
|
||||
for i in range(0, len(gallery_pages)):
|
||||
if i == page:
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(("page", i))
|
||||
|
||||
ui.textbutton(gallery_pages[i][0],
|
||||
style='gallery_page_button',
|
||||
text_style='gallery_page_button_text',
|
||||
clicked=clicked)
|
||||
ui.close()
|
||||
|
||||
# Show the return button.
|
||||
ui.textbutton('Return',
|
||||
style='gallery_return_button',
|
||||
text_style='gallery_return_button_text',
|
||||
clicked=ui.returns(("return", None)))
|
||||
|
||||
# Show the grid for this page.
|
||||
ui.grid(gallery_cols, gallery_rows, style='gallery_grid')
|
||||
|
||||
# For each grid cell.
|
||||
for i in range(0, gallery_cols * gallery_rows):
|
||||
|
||||
# Fill empty space with nulls.
|
||||
if i >= len(images):
|
||||
ui.null()
|
||||
continue
|
||||
|
||||
# Otherwise, get the filename and spec and see if
|
||||
# we've unlocked it.
|
||||
filename, spec = images[i]
|
||||
|
||||
if spec:
|
||||
spec = spec.split()
|
||||
|
||||
if spec and tuple(spec) not in persistent._seen_images:
|
||||
filename = None
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(('show', filename))
|
||||
|
||||
# Create the button, containing the appropriate
|
||||
# image or a null if we haven't unlocked it yet.
|
||||
|
||||
ui.button(style='gallery_button', clicked=clicked)
|
||||
|
||||
if not filename:
|
||||
ui.null(width=gallery_width, height=gallery_height)
|
||||
else:
|
||||
|
||||
if renpy.loadable("thumbnail_" + filename):
|
||||
ui.image("thumbnail_" + filename,
|
||||
style="gallery_button_image")
|
||||
else:
|
||||
ui.add(im.Scale(filename,
|
||||
gallery_width,
|
||||
gallery_height))
|
||||
|
||||
ui.close()
|
||||
|
||||
# Interact with the user.
|
||||
renpy.transition(gallery_transition)
|
||||
cmd, arg = ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
# Process the user's commands.
|
||||
if cmd == "show":
|
||||
ui.add(Solid((0, 0, 0, 255)))
|
||||
ui.image(arg)
|
||||
ui.saybehavior()
|
||||
renpy.transition(gallery_transition)
|
||||
ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
if cmd == "page":
|
||||
page = arg
|
||||
|
||||
if cmd == "return":
|
||||
renpy.transition(gallery_transition)
|
||||
return
|
||||
|
||||
|
||||
library.main_menu.insert(2, ("CG Gallery", ui.jumps("gallery")))
|
||||
|
||||
|
||||
label gallery:
|
||||
|
||||
$ gallery()
|
||||
|
||||
jump _main_menu
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# This is an implementation of Kana mode, a mode in which multiple
|
||||
# lines of dialogue are shown at once, in a fullscreen window. A
|
||||
# single line of dialogue is placed onto the screen for each mouse
|
||||
# click. Calling the clear function clears the screen, ensuring that
|
||||
# the next line will appear at the top of the sceen.
|
||||
#
|
||||
# An example of using Kana mode follows the code that implements it.
|
||||
|
||||
|
||||
# This init block contains all of the code needed to implement
|
||||
# Kana mode. You probably don't want to change this... unless you
|
||||
# do. But all of the customization is found below.
|
||||
init -100:
|
||||
python:
|
||||
|
||||
# This is a list of KanaCharacter, line of dialogue tuples.
|
||||
kana_display_list = [ ]
|
||||
|
||||
# Spacings used.
|
||||
kana_vspacing = 0
|
||||
kana_hspacing = 0
|
||||
|
||||
def clear(arg=None):
|
||||
global kana_display_list
|
||||
kana_display_list = [ ]
|
||||
|
||||
def kana_show():
|
||||
|
||||
ui.window(style='say_window')
|
||||
ui.vbox(kana_vspacing)
|
||||
|
||||
for char, what in kana_display_list:
|
||||
char.show(what)
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.saybehavior()
|
||||
ui.interact()
|
||||
renpy.checkpoint()
|
||||
|
||||
# Characters and the narrator should be instances of this
|
||||
# object.
|
||||
class KanaCharacter(object):
|
||||
|
||||
def __init__(self, who,
|
||||
what_prefix='"',
|
||||
what_suffix='"',
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
**properties):
|
||||
|
||||
self.who = who
|
||||
self.what_prefix = what_prefix
|
||||
self.what_suffix = what_suffix
|
||||
self.who_style = who_style
|
||||
self.what_style = what_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what):
|
||||
kana_display_list.append((self, what))
|
||||
kana_show()
|
||||
|
||||
def show(self, what):
|
||||
ui.hbox(kana_hspacing)
|
||||
ui.text(self.who, style=self.who_style,
|
||||
**self.properties)
|
||||
ui.text(self.what_prefix + what + self.what_suffix,
|
||||
style=self.what_style)
|
||||
ui.close()
|
||||
|
||||
|
||||
# This section updates the styles that are used by the Kana mode stuff
|
||||
# so that they work well with Kana mode. The user may want to change
|
||||
# these so that they look more appropriate.
|
||||
init:
|
||||
|
||||
# The space between lines of dialogue.
|
||||
$ kana_vspacing = 10
|
||||
$ kana_hspacing = 10
|
||||
|
||||
$ style.say_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.say_window.xfill = True
|
||||
$ style.say_window.yfill = True
|
||||
$ style.say_window.xmargin = 0
|
||||
$ style.say_window.ymargin = 0
|
||||
$ style.say_window.xpadding = 20
|
||||
$ style.say_window.ypadding = 20
|
||||
|
||||
$ style.say_label.minwidth = 100
|
||||
$ style.say_label.textalign = 1.0
|
||||
|
||||
# This just makes it look nicer.
|
||||
$ style.say_dialogue.rest_indent = 9
|
||||
|
||||
# Adjust the menu to match.
|
||||
$ style.menu_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.menu_window.xfill = True
|
||||
$ style.menu_window.yfill = True
|
||||
$ style.menu_window.xmargin = 0
|
||||
$ style.menu_window.ymargin = 0
|
||||
$ style.menu_window.xpadding = 20
|
||||
$ style.menu_window.ypadding = 20
|
||||
|
||||
$ style.menu.xpos = 110
|
||||
|
||||
# And now, the example. This example can't be simply run, but instead
|
||||
# should serve as a guide to how to get Kana mode working in your own
|
||||
# game.
|
||||
|
||||
# In the init block, declare the characters as KanaCharacters. We also
|
||||
# want to declare the narrator as a special KanaCharacter.
|
||||
init:
|
||||
$ p = KanaCharacter("")
|
||||
$ g = KanaCharacter("Girl:", color=(255, 128, 128, 255))
|
||||
$ narrator = KanaCharacter("",
|
||||
what_prefix='',
|
||||
what_suffix='',
|
||||
what_style='say_thought')
|
||||
|
||||
|
||||
# Now, the actual script of the example. (An excerpt from Moonlight
|
||||
# Walks.) Notice how we place calls to clear in places where the
|
||||
# we want the screen to be cleared.
|
||||
|
||||
# This text is here to serve as an example, and shouldn't be used
|
||||
# in your game.
|
||||
label example:
|
||||
|
||||
show beach2
|
||||
show mary dark wistful
|
||||
|
||||
$ clear()
|
||||
|
||||
p "What can you tell me about your parents?"
|
||||
|
||||
g "Papa and Mama both came across the ocean as settlers when they
|
||||
were just children."
|
||||
|
||||
g "Papa fought in the war. When it was over, he married Mama, and
|
||||
they used his pension to move here from the mainland, and to
|
||||
build us a house."
|
||||
|
||||
g "Together, they farmed the land, and eventually they had
|
||||
children."
|
||||
|
||||
g "I had an older sister and a younger sister. I was the middle
|
||||
child."
|
||||
|
||||
$ clear()
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She paused for a second to collect her thoughts before
|
||||
continuing. This part was taking a strain on her."
|
||||
|
||||
g "When I was ten, an epidemic hit the island."
|
||||
|
||||
g "We came down with it, and so did all of the other families."
|
||||
|
||||
g "My family was too sick to move, but our neighbors, the Millers,
|
||||
sent some of their boys to get help from the mainland."
|
||||
|
||||
"I remember thinking that Miller was the last name of my aunt and
|
||||
uncle, and wondering if they could be related to me."
|
||||
|
||||
g "I don't know what happened to them, but I never heard from them
|
||||
again."
|
||||
|
||||
show mary dark crying
|
||||
|
||||
g "It lasted a week, and then it was over. My sisters... my
|
||||
parents... they all..."
|
||||
|
||||
g "Now I'm the only one left."
|
||||
|
||||
$ clear()
|
||||
|
||||
p "I'm sorry."
|
||||
|
||||
"I didn't know what else to say to a girl that had lost her
|
||||
family, and was obviously broken up about it."
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She nodded in response, wiped her tears, and we once again
|
||||
started walking in silence."
|
||||
|
||||
menu menu_1:
|
||||
"Show me the example again.":
|
||||
jump example
|
||||
|
||||
"I'm done. Let me go.":
|
||||
return
|
||||
@@ -0,0 +1,87 @@
|
||||
# This file replaces most of the game menu navigation with overlay
|
||||
# buttons that jump directly to various parts of the game menu.
|
||||
# Right clicking hides and shows the buttons, rather than calling
|
||||
# up the game menu directly.
|
||||
|
||||
init:
|
||||
python hide:
|
||||
|
||||
# overlay_menu is an object storing information about the
|
||||
# overlay menu state.
|
||||
store.overlay_menu = object()
|
||||
overlay_menu.shown = False
|
||||
|
||||
# overlay_menu is also a new layer, containing the overlay
|
||||
# menu.
|
||||
config.layers.append("overlay_menu")
|
||||
config.overlay_layers.append("overlay_menu")
|
||||
|
||||
|
||||
# This function actually draws the overlay menu.
|
||||
def overlay_menu_func():
|
||||
if overlay_menu.shown:
|
||||
|
||||
ui.layer("overlay_menu")
|
||||
|
||||
ui.vbox(xpos=1.0, xanchor="right", ypos=0.75, yanchor="bottom")
|
||||
|
||||
def button(label, target):
|
||||
ui.textbutton(label, clicked=renpy.curried_call_in_new_context(target))
|
||||
|
||||
button("Load Game", "_game_menu_load")
|
||||
button("Save Game", "_game_menu_save")
|
||||
button("Preferences", "_game_menu_preferences")
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(overlay_menu_func)
|
||||
|
||||
|
||||
# This function toggles the visibility of the overlay menu.
|
||||
def overlay_menu_toggle():
|
||||
shown = not overlay_menu.shown
|
||||
overlay_menu.shown = shown
|
||||
|
||||
# How long should the transitions take?
|
||||
trans_delay = 0.5
|
||||
|
||||
# These transitions assume that the menu is placed on the
|
||||
# right side of the screen. This indicator gives the
|
||||
# fraction of the screen that participates in transitions.
|
||||
|
||||
trans_frac = 0.75
|
||||
|
||||
if shown:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
startcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
startpos=(1.0, 0.0),
|
||||
endcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
endpos=(trans_frac, 0.0),
|
||||
topnew=True)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
else:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
endcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
endpos=(1.0, 0.0),
|
||||
startcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
startpos=(trans_frac, 0.0),
|
||||
topnew=False)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
# Add a new underlay that handles the overlay menu toggle.
|
||||
config.underlay.append(renpy.Keymap(game_menu = overlay_menu_toggle))
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# This is the annoying readback mode. I implemented this because
|
||||
# people asked for it, not because I like it. I'd prefer you use
|
||||
# rollback, which is better in every way.
|
||||
|
||||
# To use readback, drop this file into your game directory. Readback
|
||||
# will take over when rollback no longer works, so you'll also need to
|
||||
# either reduce config.hard_rollback_limit or set
|
||||
# config.rollback_enabled to false.
|
||||
|
||||
# init:
|
||||
# $ config.hard_rollback_limit = 1
|
||||
|
||||
# Readback works by replacing the default Character and Menu objects
|
||||
# with ones that record what is said in a readback buffer. The user
|
||||
# can then go back, and what has been said will be shown to them
|
||||
# again. Images and the like will not change... if you want that, use
|
||||
# rollback. Heck, use rollback anyway. :-P
|
||||
|
||||
# The number of lines of readback can be changed by assigning an
|
||||
# integer to the readback_limit variable. But leaving it None will
|
||||
# leave the entire game in the buffer. As this is actually fairly
|
||||
# memory-efficent, you should probably leave it as None.
|
||||
|
||||
# Readback uses two styles, readback_dialogue and
|
||||
# readback_thought. Change them to change the color and look of
|
||||
# read-back text.
|
||||
|
||||
# If you want to add your own message, as narration, to the readback
|
||||
# buffer, you can do it by calling the readback function or having
|
||||
# the readback character say it:
|
||||
|
||||
# $ readback('A message for readbackers only.')
|
||||
# readback "A similar message."
|
||||
|
||||
# Note: When using Readback, you can no longer use a string for a
|
||||
# character's name. All dialogue must be routed through Character
|
||||
# objects, if it is to show up in the readback buffer. This limitation
|
||||
# will be fixed in a future version of Ren'Py.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
# The limit of the number of readbacks to keep. None means no limit.
|
||||
readback_limit = None
|
||||
|
||||
# Set this to true to print out the contents of the readback
|
||||
# buffer when it is saved.
|
||||
readback_debug = False
|
||||
|
||||
# Readback styles.
|
||||
style.create('readback_dialogue', 'say_dialogue', '')
|
||||
style.create('readback_thought', 'say_thought', '')
|
||||
|
||||
style.readback_dialogue.color = (255, 128, 128, 255)
|
||||
style.readback_thought.color = (255, 128, 128, 255)
|
||||
|
||||
|
||||
# No user-servicable parts below this line. ######################
|
||||
|
||||
# The readback buffer is a doubly-linked list of readback
|
||||
# objects.
|
||||
class Readback(object):
|
||||
def __init__(self, obj, args):
|
||||
self.older = None
|
||||
self.newer = None
|
||||
self.obj = obj
|
||||
self.args = args
|
||||
|
||||
def show(self):
|
||||
self.obj.readback(*self.args)
|
||||
|
||||
|
||||
readback_oldest = None
|
||||
readback_newest = None
|
||||
readback_count = 0
|
||||
|
||||
|
||||
# This saves a readback entry to the readback buffer.
|
||||
def readback_save(obj, *args):
|
||||
|
||||
store.readback_count += 1
|
||||
|
||||
if readback_limit and readback_count > readback_limit:
|
||||
store.readback_count -= 1
|
||||
readback_oldest.newer.older = None
|
||||
store.readback_oldest = readback_oldest.newer
|
||||
|
||||
rb = Readback(obj, args)
|
||||
|
||||
|
||||
if readback_newest:
|
||||
readback_newest.newer = rb
|
||||
else:
|
||||
store.readback_oldest = rb
|
||||
|
||||
rb.older = readback_newest
|
||||
|
||||
store.readback_newest = rb
|
||||
|
||||
|
||||
if readback_debug:
|
||||
|
||||
print "---- Readback Buffer ----"
|
||||
|
||||
rb = readback_oldest
|
||||
while rb:
|
||||
print rb.obj, rb.args
|
||||
rb = rb.newer
|
||||
|
||||
|
||||
# The rest of this file is replacing the default objects and
|
||||
# functions with versions that save things in the readback
|
||||
# buffer.
|
||||
|
||||
# Save the old character object.
|
||||
readback_OldCharacter = Character
|
||||
readback_oldmenu = menu
|
||||
|
||||
class Character(readback_OldCharacter):
|
||||
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
readback_OldCharacter.__call__(self, what, **kwargs)
|
||||
|
||||
def store_readback(self, who, what):
|
||||
readback_save(self, who, what)
|
||||
|
||||
def readback(self, who, what):
|
||||
self.function(who, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
class Sayer(object):
|
||||
def __call__(self, who, what):
|
||||
renpy.display_say(who, what)
|
||||
readback_save(self, who, what)
|
||||
|
||||
def readback(self, who, what):
|
||||
renpy.display_say(who, what,
|
||||
what_style='readback_dialogue',
|
||||
interact=False)
|
||||
|
||||
|
||||
narrator = Character(None, what_style='say_thought')
|
||||
say = Sayer()
|
||||
|
||||
def readback(what):
|
||||
narrator.store_readback(None, what)
|
||||
|
||||
def menu(menuitems):
|
||||
rv = readback_oldmenu(menuitems)
|
||||
|
||||
text = '\n'.join([ l for l, v in menuitems
|
||||
if v is None or v == rv ])
|
||||
|
||||
readback(text)
|
||||
|
||||
return rv
|
||||
|
||||
# This stuff is involved in entering the readback mode.
|
||||
|
||||
def readback_mode():
|
||||
# Try rollback, first.
|
||||
renpy.rollback()
|
||||
|
||||
# If we made it here, we're into readback mode. So let's
|
||||
# go there now.
|
||||
|
||||
renpy.call_in_new_context("readback")
|
||||
|
||||
# Add in the readback function.
|
||||
config.underlay.append(renpy.Keymap(rollback=readback_mode))
|
||||
|
||||
|
||||
# This label is called in a new context, when the user succesfully
|
||||
# enters Readback mode.
|
||||
label readback:
|
||||
|
||||
# If we have an empty readback buffer, go home.
|
||||
if not readback_newest:
|
||||
return
|
||||
|
||||
python hide:
|
||||
|
||||
rb = readback_newest
|
||||
|
||||
while True:
|
||||
|
||||
rb.show()
|
||||
|
||||
ui.add(renpy.Keymap(rollback=lambda : "older",
|
||||
rollforward=lambda : "newer",
|
||||
dismiss=lambda : "dismiss",
|
||||
))
|
||||
|
||||
res = ui.interact()
|
||||
|
||||
if res == "newer":
|
||||
rb = rb.newer
|
||||
if not rb:
|
||||
break
|
||||
|
||||
elif res == "older":
|
||||
if rb.older:
|
||||
rb = rb.older
|
||||
|
||||
elif res == "dismiss":
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# This extra demonstrates the two window say mode. In this mode, the
|
||||
# character's name is placed in a different window than the line said
|
||||
# by that character.
|
||||
#
|
||||
# You can use this by including this file in your game directory. You
|
||||
# then need to change the definitions of Character and
|
||||
# DynamicCharacter object to set function=two_window_say. For example:
|
||||
#
|
||||
# $ e = Character('Eileen', color=(200, 255, 200, 255),
|
||||
# function=two_window_say)
|
||||
#
|
||||
# Once this is done, everything that is said by that character will be
|
||||
# divided across two windows.
|
||||
#
|
||||
# You'll probably also want to customize the styles given below,
|
||||
# especially by setting style.who_window.background to something
|
||||
# a little more attractive.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
style.create('two_window_say_position', 'default',
|
||||
'(position) Used to get the position of the two windows in two window say mode.')
|
||||
|
||||
style.two_window_say_position.ypos = 1.0
|
||||
style.two_window_say_position.yanchor = 'bottom'
|
||||
|
||||
style.create('who_window', 'default',
|
||||
'(window) The style used for the window containing the who label when a Character uses the two_window_say function.')
|
||||
|
||||
style.who_window.background = Solid((0, 0, 255, 128))
|
||||
style.who_window.xminimum = 150
|
||||
style.who_window.xmargin = 10
|
||||
style.who_window.xpadding = 10
|
||||
style.who_window.ymargin = 5
|
||||
style.who_window.ypadding = 5
|
||||
|
||||
|
||||
def two_window_say(who, what,
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
window_style='say_window',
|
||||
who_window_style='who_window',
|
||||
who_prefix='',
|
||||
who_suffix=': ',
|
||||
what_prefix='',
|
||||
what_suffix='',
|
||||
interact=True,
|
||||
slow=True,
|
||||
**properties):
|
||||
|
||||
if interact:
|
||||
ui.saybehavior()
|
||||
|
||||
if who is not None:
|
||||
who = who_prefix + who + who_suffix
|
||||
|
||||
what = what_prefix + what + what_suffix
|
||||
|
||||
ui.vbox(style='two_window_say_position')
|
||||
|
||||
if who is not None:
|
||||
ui.window(style=who_window_style)
|
||||
ui.text(who, style=who_style, **properties)
|
||||
|
||||
ui.window(style=window_style)
|
||||
ui.text(what, style=what_style, slow=slow)
|
||||
|
||||
ui.close()
|
||||
|
||||
if interact:
|
||||
ui.interact()
|
||||
renpy.checkpoint()
|
||||
@@ -0,0 +1,71 @@
|
||||
# This extra contains a basic implementation of voice support. Right
|
||||
# now, voice is given its own toggle, and can either be turned on or
|
||||
# turned off. In the future, we'll probably provide some way of
|
||||
# toggling it on or off for individual characters.
|
||||
#
|
||||
# To use it, place a voice "<wavfile>" line before each voiced line of
|
||||
# dialogue.
|
||||
#
|
||||
# voice "e_1001.wav"
|
||||
# e "Voice support lets you add the spoken word to your games."
|
||||
#
|
||||
# Normally, a voice is cancelled at the start of the next
|
||||
# interaction. If you want a voice to span interactions, call
|
||||
# voice_sustain.
|
||||
#
|
||||
# voice "e_1002.wav"
|
||||
# e "Voice sustain is a technique that allows the same voice file.."
|
||||
#
|
||||
# $ voice_sustain()
|
||||
# e "...to play for two lines of dialogue."
|
||||
|
||||
init -10:
|
||||
|
||||
python:
|
||||
|
||||
# Ensure the voice preference exists.
|
||||
if persistent.voice is None:
|
||||
persistent.voice = True
|
||||
|
||||
_voice = object()
|
||||
_voice.play = None
|
||||
_voice.sustain = False
|
||||
|
||||
# Call this to specify the voice file that will be played for
|
||||
# the user.
|
||||
def voice(file):
|
||||
_voice.play = file
|
||||
|
||||
# Call this to specify that the currently playing voice file
|
||||
# should be sustained through the current interaction.
|
||||
def voice_sustain(ignored=""):
|
||||
_voice.sustain = True
|
||||
|
||||
python hide:
|
||||
|
||||
vp = _Preference('Voice', 'voice', [
|
||||
('Enabled', True, 'True'),
|
||||
('Disabled', False, 'True')
|
||||
], base=persistent)
|
||||
|
||||
library.preferences['prefs_left'].append(vp)
|
||||
|
||||
# This is called on each interaction, to ensure that the
|
||||
# appropriate voice file is played for the user.
|
||||
def voice_interact():
|
||||
|
||||
if not audio.sound_enabled():
|
||||
return
|
||||
|
||||
if not persistent.voice:
|
||||
return
|
||||
|
||||
if _voice.play and not config.skipping:
|
||||
audio.sound_play(_voice.play, channel=1)
|
||||
elif not _voice.sustain:
|
||||
audio.sound_stop(channel=1)
|
||||
|
||||
_voice.play = None
|
||||
_voice.sustain = False
|
||||
|
||||
config.interact_callbacks.append(voice_interact)
|
||||
@@ -0,0 +1,134 @@
|
||||
;;; Comments beginning with ";;;" are commentary added by PyTom to
|
||||
;;; try to help you understand this file.
|
||||
;;;
|
||||
;;; This installer expects that you will have supplied LICENSE.txt
|
||||
;;; and README.txt files in the root directory of your game.
|
||||
;;;
|
||||
;;; To use this, place it in the root directory of your game,
|
||||
;;; right-click on it, and choose "Compile NSIS script..."
|
||||
|
||||
;;; First up, change the settings below to match your game.
|
||||
|
||||
;;; The exe used to run your game.
|
||||
!define EXE "moonlight.exe"
|
||||
|
||||
;;; The exe containing the installer. This will be created in the directory
|
||||
;;; above the root directory containing your game.
|
||||
!define INSTALLER_EXE "moonlight-1.0ni.exe"
|
||||
|
||||
;;; The name and version.
|
||||
!define PRODUCT_NAME "Moonlight Walks"
|
||||
!define PRODUCT_VERSION "1.0"
|
||||
|
||||
;;; The following settings are only shown to the user in Add/Remove programs
|
||||
;;; but you'll still want to use them.
|
||||
|
||||
!define PRODUCT_WEB_SITE "http://www.bishoujo.us/moonlight/"
|
||||
!define PRODUCT_PUBLISHER "American Bishoujo"
|
||||
|
||||
;;; Ignore this next block of stuff. It's mostly boilerplate.
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
;;; Change this to change the compression scheme.
|
||||
SetCompressor lzma
|
||||
|
||||
;;; You can change these to customize the bitmaps and icons used for
|
||||
;;; your installer and uninstaller. Bitmaps should be 150x57.
|
||||
|
||||
; !define MUI_HEADERIMAGE
|
||||
; !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
|
||||
; !define MUI_HEADERIMAGE_UNBITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
|
||||
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
|
||||
|
||||
|
||||
;;; This is the sequencing of the pages that does the actual installation.
|
||||
;;; you can comment pages out, if you want to.
|
||||
|
||||
; Welcome page
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
; License page
|
||||
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
|
||||
|
||||
; Directory page
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
; Instfiles page
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
; Finish page
|
||||
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstaller pages
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
;;; Okay, that's it for the commentary. You're on your own from here...
|
||||
;;; but you probably won't need to touch anything below this point.
|
||||
|
||||
; Various other defines.
|
||||
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
||||
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
|
||||
|
||||
; Language files
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
; Reserve files
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "..\${INSTALLER_EXE}"
|
||||
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
|
||||
|
||||
Section "!${PRODUCT_NAME}" SEC01
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
File /r /x renpy /x *.py /x *.pyw /x installer.nsi /x persistent *.*
|
||||
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk" "$INSTDIR\README.txt"
|
||||
SectionEnd
|
||||
|
||||
Section -Post
|
||||
WriteUninstaller "$INSTDIR\uninst.exe"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\${EXE}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||
SectionEnd
|
||||
|
||||
|
||||
Function un.onUninstSuccess
|
||||
HideWindow
|
||||
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
|
||||
FunctionEnd
|
||||
|
||||
Function un.onInit
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
|
||||
Abort
|
||||
FunctionEnd
|
||||
|
||||
Section Uninstall
|
||||
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
|
||||
|
||||
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
|
||||
SetAutoClose true
|
||||
SectionEnd
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
This file explains how to build the _renpy module for distribution on
|
||||
the Mac and Windows.
|
||||
|
||||
(This file is not for distribution, as it doesn't make sense to
|
||||
distribute it.)
|
||||
|
||||
Windows:
|
||||
|
||||
Get into msys.
|
||||
|
||||
cd /t/ab/renpy/module
|
||||
/c/python23/python setup_win32 build --compiler mingw32 bdist_wininst
|
||||
|
||||
|
||||
Macintosh:
|
||||
|
||||
python setup_mac bdist_mpkg --zipdist
|
||||
@@ -0,0 +1,14 @@
|
||||
# This makefile should work on Linux, and might work on other unix-like
|
||||
# platforms as well. It probably won't work on Mac OS X, however.
|
||||
|
||||
all: _renpy.so
|
||||
|
||||
_renpy.c: _renpy.pyx
|
||||
pyrexc _renpy.pyx
|
||||
|
||||
_renpy.so: _renpy.c core.c
|
||||
python setup.py build_ext -i
|
||||
|
||||
clean:
|
||||
-rm _renpy.so
|
||||
-rm -Rf build
|
||||
@@ -0,0 +1,45 @@
|
||||
This directory contains the source code for the _renpy module. This
|
||||
module contains a number of image processing functions that aren't
|
||||
present in pygame. If it's not present, then some of Ren'Py's
|
||||
functionality will be missing. Games should still be playable, but
|
||||
some effects may not be present, or may be present in a degraded form.
|
||||
|
||||
How does this affect you? Well, it depends on the platform you're
|
||||
running on. So please read the appropriate selection below.
|
||||
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
If you're running an exe file under Windows, this doesn't affect you
|
||||
one iota. Run it and be happy.
|
||||
|
||||
If you're running a python script under windows, you can download a
|
||||
precompiled version of the module from the Ren'Py homepage,
|
||||
http://www.bishoujo.us/renpy/.
|
||||
|
||||
|
||||
Macintosh
|
||||
---------
|
||||
|
||||
You'll need to download a mpkg containing the precompiled version of
|
||||
the _renpy module. You can grab it from http://www.bishoujo.us/renpy/.
|
||||
|
||||
|
||||
Linux/Unix
|
||||
----------
|
||||
|
||||
You'll need to compile the module yourself. Ensure that you have the
|
||||
SDL development headers and libraries installed, and that you can run
|
||||
the sdl-config program. Then, in this directory, type:
|
||||
|
||||
python setup.py build_ext -i
|
||||
|
||||
It should autodetect SDL, and build the module in the current
|
||||
directory. If successful, a file named _renpy.so will come into
|
||||
existence. You can then run Ren'Py.
|
||||
|
||||
|
||||
If you have question or problems, please contact us via the Ren'Py web
|
||||
page, http://www.bishoujo.us/renpy/.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/* This file exists to adapt the location that pygame looks for SDL
|
||||
* from into the location where SDL tends to exist on most actual
|
||||
* systems.
|
||||
*/
|
||||
|
||||
#include <SDL/SDL.h>
|
||||
@@ -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()
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
pyrexc _renpy.pyx && python setup.py build_ext -i
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
# This is supposed to be run on Windows! In Cygwin!
|
||||
|
||||
if test "x$1" = "x"; then
|
||||
echo Need release name.
|
||||
exit -1
|
||||
fi
|
||||
|
||||
rm -Rf build dist
|
||||
cp run_game.py console.py
|
||||
|
||||
cmd /c build_exe.bat
|
||||
|
||||
python distribute.py ../$1 demo2
|
||||
cd ..
|
||||
zip -9 -r $1.zip $1
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# This file ensures that renpy packages will be imported in the right
|
||||
# order.
|
||||
|
||||
# Some version numbers and things.
|
||||
version = "Ren'Py 4.8.7"
|
||||
script_version = 8007
|
||||
savegame_suffix = "-8.4.save"
|
||||
|
||||
|
||||
# Can be first, because has no dependencies, and may be imported
|
||||
# directly.
|
||||
import renpy.game
|
||||
@@ -13,21 +19,29 @@ import renpy.curry
|
||||
import renpy.execution
|
||||
import renpy.loader
|
||||
import renpy.loadsave
|
||||
import renpy.music
|
||||
import renpy.parser
|
||||
import renpy.python # object
|
||||
import renpy.script
|
||||
import renpy.style
|
||||
import renpy.sound
|
||||
|
||||
import renpy.display
|
||||
import renpy.display
|
||||
import renpy.display.presplash
|
||||
import renpy.display.module
|
||||
import renpy.display.render # Most display stuff depends on this.
|
||||
import renpy.display.core # object
|
||||
import renpy.display.surface
|
||||
import renpy.display.image # core
|
||||
import renpy.display.audio
|
||||
import renpy.display.text # core
|
||||
import renpy.display.layout # core
|
||||
import renpy.display.behavior # layout
|
||||
import renpy.display.transition # core
|
||||
import renpy.display.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.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
# This file contains the AST for the Ren'Py script language. Each class
|
||||
# here corresponds to a statement in the script language.
|
||||
|
||||
@@ -29,6 +30,13 @@ class Node(object):
|
||||
# a node from that file.
|
||||
serials = { }
|
||||
|
||||
__slots__ = [
|
||||
'name',
|
||||
'filename',
|
||||
'linenumber',
|
||||
'next',
|
||||
]
|
||||
|
||||
def __init__(self, loc):
|
||||
"""
|
||||
Initializes this Node object.
|
||||
@@ -93,7 +101,10 @@ class Node(object):
|
||||
probably execute next.
|
||||
"""
|
||||
|
||||
return [ self.next ]
|
||||
if self.next:
|
||||
return [ self.next ]
|
||||
else:
|
||||
return [ ]
|
||||
|
||||
|
||||
def say_menu_with(expression):
|
||||
@@ -114,9 +125,14 @@ def say_menu_with(expression):
|
||||
if renpy.game.preferences.transitions:
|
||||
renpy.game.interface.set_transition(what)
|
||||
|
||||
|
||||
class Say(Node):
|
||||
|
||||
__slots__ = [
|
||||
'who',
|
||||
'what',
|
||||
'with',
|
||||
]
|
||||
|
||||
def __init__(self, loc, who, what, with):
|
||||
|
||||
super(Say, self).__init__(loc)
|
||||
@@ -133,12 +149,17 @@ class Say(Node):
|
||||
who = None
|
||||
|
||||
say_menu_with(self.with)
|
||||
renpy.exports.say(who, self.what % renpy.game.store)
|
||||
renpy.exports.say(who, self.what)
|
||||
|
||||
return self.next
|
||||
|
||||
class Init(Node):
|
||||
|
||||
__slots__ = [
|
||||
'block',
|
||||
'priority',
|
||||
]
|
||||
|
||||
def __init__(self, loc, block, priority):
|
||||
super(Init, self).__init__(loc)
|
||||
|
||||
@@ -166,6 +187,11 @@ class Init(Node):
|
||||
|
||||
class Label(Node):
|
||||
|
||||
__slots__ = [
|
||||
'name',
|
||||
'block',
|
||||
]
|
||||
|
||||
def __init__(self, loc, name, block):
|
||||
"""
|
||||
Constructs a new Label node.
|
||||
@@ -196,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.
|
||||
@@ -206,17 +237,30 @@ class Python(Node):
|
||||
|
||||
super(Python, self).__init__(loc)
|
||||
|
||||
self.python_code = python_code
|
||||
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, filename=filename, lineno=lineno)
|
||||
renpy.game.exception_info = old_ei
|
||||
|
||||
|
||||
def execute(self):
|
||||
renpy.python.py_exec(self.python_code, self.hide)
|
||||
renpy.python.py_exec_bytecode(self.bytecode, self.hide)
|
||||
|
||||
return self.next
|
||||
|
||||
class Image(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imgname',
|
||||
'expr',
|
||||
]
|
||||
|
||||
def __init__(self, loc, name, expr):
|
||||
"""
|
||||
@param name: The name of the image being defined.
|
||||
@@ -227,46 +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, an image, and a with_image, and
|
||||
returns all three to the user.
|
||||
|
||||
@param hide: Reduces error checking, and makes the with_image None
|
||||
if the list of with expressions is empty.
|
||||
"""
|
||||
|
||||
import renpy.display.image
|
||||
|
||||
name, at_list = imspec
|
||||
key = name[0]
|
||||
|
||||
# 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
|
||||
@@ -277,11 +291,16 @@ def predict_imspec(imspec, callback):
|
||||
return
|
||||
|
||||
im = renpy.exports.images[imspec[0]]
|
||||
|
||||
im.predict(callback)
|
||||
|
||||
|
||||
class Show(Node):
|
||||
|
||||
__slots__ = [
|
||||
'imspec',
|
||||
]
|
||||
|
||||
def __init__(self, loc, imspec):
|
||||
"""
|
||||
@param imspec: A triple consisting of an image name (itself a
|
||||
@@ -295,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
|
||||
|
||||
@@ -309,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
|
||||
@@ -323,16 +346,14 @@ class Scene(Node):
|
||||
|
||||
def execute(self):
|
||||
|
||||
import renpy.exports as exports
|
||||
|
||||
sls = renpy.game.context().scene_lists
|
||||
|
||||
sls.clear('master')
|
||||
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
|
||||
|
||||
@@ -345,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
|
||||
@@ -358,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.
|
||||
@@ -381,35 +404,42 @@ class With(Node):
|
||||
def execute(self):
|
||||
trans = renpy.python.py_eval(self.expr)
|
||||
|
||||
# Code copied into exports.with
|
||||
|
||||
if not trans:
|
||||
renpy.game.interface.with_none()
|
||||
else:
|
||||
if renpy.game.preferences.transitions:
|
||||
renpy.game.interface.set_transition(trans)
|
||||
renpy.game.interface.interact(show_mouse=False,
|
||||
trans_pause=True,
|
||||
suppress_overlay=True)
|
||||
renpy.exports.with(trans)
|
||||
|
||||
return self.next
|
||||
|
||||
|
||||
class Call(Node):
|
||||
|
||||
def __init__(self, loc, label):
|
||||
__slots__ = [
|
||||
'label',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, label, expression):
|
||||
|
||||
super(Call, self).__init__(loc)
|
||||
self.label = label
|
||||
self.expression = expression
|
||||
|
||||
def execute(self):
|
||||
return renpy.game.context().call(self.label, return_site=self.next.name)
|
||||
|
||||
label = self.label
|
||||
if self.expression:
|
||||
label = renpy.python.py_eval(label)
|
||||
|
||||
return renpy.game.context().call(label, return_site=self.next.name)
|
||||
|
||||
def predict(self, callback):
|
||||
return [ renpy.game.script.lookup(self.label) ]
|
||||
if self.expression:
|
||||
return [ ]
|
||||
else:
|
||||
return [ renpy.game.script.lookup(self.label) ]
|
||||
|
||||
class Return(Node):
|
||||
|
||||
__slots__ = [ ]
|
||||
|
||||
# No __init__ needed.
|
||||
|
||||
# We don't care what the next node is.
|
||||
@@ -428,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)
|
||||
|
||||
@@ -486,29 +522,51 @@ class Menu(Node):
|
||||
# instead.
|
||||
class Jump(Node):
|
||||
|
||||
def __init__(self, loc, target):
|
||||
__slots__ = [
|
||||
'target',
|
||||
'expression',
|
||||
]
|
||||
|
||||
def __init__(self, loc, target, expression):
|
||||
super(Jump, self).__init__(loc)
|
||||
|
||||
self.target = target
|
||||
self.expression = expression
|
||||
|
||||
# We don't care what our next node is.
|
||||
def chain(self, next):
|
||||
return
|
||||
|
||||
def execute(self):
|
||||
return renpy.game.script.lookup(self.target)
|
||||
|
||||
target = self.target
|
||||
if self.expression:
|
||||
target = renpy.python.py_eval(target)
|
||||
|
||||
return renpy.game.script.lookup(target)
|
||||
|
||||
def predict(self, callback):
|
||||
return [ renpy.game.script.lookup(self.target) ]
|
||||
|
||||
if self.expression:
|
||||
return [ ]
|
||||
else:
|
||||
return [ renpy.game.script.lookup(self.target) ]
|
||||
|
||||
# GNDN
|
||||
class Pass(Node):
|
||||
|
||||
__slots__ = [ ]
|
||||
|
||||
def execute(self):
|
||||
return self.next
|
||||
|
||||
class While(Node):
|
||||
|
||||
__slots__ = [
|
||||
'condition',
|
||||
'block',
|
||||
]
|
||||
|
||||
def __init__(self, loc, condition, block):
|
||||
super(While, self).__init__(loc)
|
||||
|
||||
@@ -535,6 +593,8 @@ class While(Node):
|
||||
|
||||
class If(Node):
|
||||
|
||||
__slots__ = [ 'entries' ]
|
||||
|
||||
def __init__(self, loc, entries):
|
||||
"""
|
||||
@param entries: A list of (condition, block) tuples.
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
# This includes both simple settings (like the screen dimensions) and
|
||||
# methods that perform standard tasks, like the say and menu methods.
|
||||
|
||||
import renpy.display
|
||||
# This will be deleted by the end of this file.
|
||||
import renpy
|
||||
|
||||
# The title of the game window.
|
||||
window_title = "A Ren'Py Game"
|
||||
|
||||
# An image file containing the window icon image.
|
||||
window_icon = None
|
||||
|
||||
# The width and height of the drawable area of the screen.
|
||||
screen_width = 800
|
||||
screen_height = 600
|
||||
@@ -18,6 +22,9 @@ background = None # (0, 0, 0, 255)
|
||||
# about and fix them.
|
||||
debug = False
|
||||
|
||||
# Ditto, but for sound operations
|
||||
debug_sound = False
|
||||
|
||||
# Is rollback enabled? (This only controls if the user-invoked
|
||||
# rollback command does anything)
|
||||
rollback_enabled = True
|
||||
@@ -43,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
|
||||
@@ -57,12 +64,21 @@ predict_statements = 10
|
||||
# it changes.
|
||||
debug_image_cache = False
|
||||
|
||||
# Should we allow skipping at all?
|
||||
allow_skipping = True
|
||||
|
||||
# Are we currently skipping?
|
||||
skipping = False
|
||||
|
||||
# The delay while we are skipping say statements.
|
||||
skip_delay = 100
|
||||
skip_delay = 75
|
||||
|
||||
# Archive files that are searched for images.
|
||||
archives = [ ]
|
||||
|
||||
# Searchpath.
|
||||
searchpath = [ ]
|
||||
|
||||
# If True, we will only try loading from archives.
|
||||
# Only useful for debugging Ren'Py, don't document.
|
||||
force_archives = False
|
||||
@@ -70,19 +86,112 @@ 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
|
||||
|
||||
_globals = globals().copy()
|
||||
# Should the at list be sticky?
|
||||
sticky_positions = False
|
||||
|
||||
# A list of all of the layers that we know about.
|
||||
layers = [ 'master', 'transient', 'overlay' ]
|
||||
|
||||
# A list of layers that should be cleared when we replace
|
||||
# transients.
|
||||
transient_layers = [ 'transient' ]
|
||||
|
||||
# A list of layers that should be cleared when we recompute
|
||||
# overlays.
|
||||
overlay_layers = [ 'overlay' ]
|
||||
|
||||
# A list of layers that are displayed above all other layers.
|
||||
top_layers = [ ]
|
||||
|
||||
# True if we want to show overlays during wait statements, or
|
||||
# false otherwise.
|
||||
overlay_during_wait = True
|
||||
|
||||
# True if we want to allow the fast dissolve.
|
||||
enable_fast_dissolve = True
|
||||
|
||||
# When using the keyboard to navigate, how much we penalize
|
||||
# distance out of the preferred direction.
|
||||
focus_crossrange_penalty = 1024
|
||||
|
||||
# If True, then we force all loading to occur before transitions
|
||||
# start.
|
||||
load_before_transition = True
|
||||
|
||||
# The keymap that is used to change keypresses and mouse events.
|
||||
keymap = dict(
|
||||
|
||||
# Bindings present almost everywhere, unless explicitly
|
||||
# disabled.
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Focus.
|
||||
focus_left = [ 'K_LEFT' ],
|
||||
focus_right = [ 'K_RIGHT' ],
|
||||
focus_up = [ 'K_UP' ],
|
||||
focus_down = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Input.
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
)
|
||||
|
||||
# A 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()
|
||||
|
||||
del _globals["backup"]
|
||||
del _globals["reload"]
|
||||
del _globals["__builtins__"]
|
||||
|
||||
for k, v in _globals.items():
|
||||
|
||||
if k == "text_tokenizer":
|
||||
continue
|
||||
|
||||
_globals[k] = copy.deepcopy(v)
|
||||
|
||||
|
||||
def reload():
|
||||
globals().update(_globals)
|
||||
|
||||
@@ -4,8 +4,8 @@ class Curry(object):
|
||||
callable with the stored arguments and the additional arguments
|
||||
supplied to the call.
|
||||
"""
|
||||
|
||||
__doc__ = property(fget=lambda self : self.callable.__doc__)
|
||||
|
||||
# __doc__ = property(fget=lambda self : self.callable.__doc__)
|
||||
|
||||
def __init__(self, callable, *args, **kwargs):
|
||||
self.callable = callable
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# This file contains support for state-machine controlled animations.
|
||||
|
||||
import renpy
|
||||
import random
|
||||
|
||||
class State(object):
|
||||
"""
|
||||
This creates a state that can be used in a SMAnimation.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, name, image, *atlist):
|
||||
"""
|
||||
@param name: A string giving the name of this state.
|
||||
|
||||
@param image: The displayable that is shown to the user while
|
||||
we are in (entering) this state. For convenience, this can
|
||||
also be a string or tuple, which is interpreted with Image.
|
||||
|
||||
image should be None when this State is used with motion,
|
||||
to indicate that the image will be replaced with the child of
|
||||
the motion.
|
||||
|
||||
@param atlist: A list of functions to call on the image. (In
|
||||
general, if something can be used in an at clause, it can be
|
||||
used here as well.)
|
||||
"""
|
||||
|
||||
if image and not isinstance(image, renpy.display.core.Displayable):
|
||||
image = renpy.display.image.Image(image)
|
||||
|
||||
self.name = name
|
||||
self.image = image
|
||||
self.atlist = atlist
|
||||
|
||||
|
||||
def add(self, sma):
|
||||
sma.states[self.name] = self
|
||||
|
||||
def get_image(self):
|
||||
rv = self.image
|
||||
|
||||
for i in self.atlist:
|
||||
rv = i(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def motion_copy(self, child):
|
||||
|
||||
if self.image is not None:
|
||||
child = self.mage
|
||||
|
||||
return State(self.name, child, *self.atlist)
|
||||
|
||||
|
||||
class Edge(object):
|
||||
"""
|
||||
This creates an edge that can be used with a SMAnimation.
|
||||
"""
|
||||
|
||||
def __init__(self, old, delay, new, trans=None, prob=1):
|
||||
"""
|
||||
@param old: The name (a string) of the state that this transition is from.
|
||||
|
||||
@param delay: The number of seconds that this transition takes.
|
||||
|
||||
@param new: The name (a string) of the state that this transition is to.
|
||||
|
||||
@param trans: The transition that will be used to show the
|
||||
image found in the new state. If None, the image is show
|
||||
immediately.
|
||||
|
||||
When used with an SMMotion, the transition should probably be
|
||||
move.
|
||||
|
||||
@param prob: The number of times this edge is added. This can
|
||||
be used to make a transition more probable then others. For
|
||||
example, if one transition out of a state has prob=5, and the
|
||||
other has prob=1, then the one with prob=5 will execute 5/6 of
|
||||
the time, while the one with prob=1 will only occur 1/6 of the
|
||||
time. (Don't make this too large, as memory use is proportional to
|
||||
this value.)
|
||||
"""
|
||||
|
||||
self.old = old
|
||||
self.delay = delay
|
||||
self.new = new
|
||||
self.trans = trans
|
||||
self.prob = prob
|
||||
|
||||
def add(self, sma):
|
||||
for i in range(0, self.prob):
|
||||
sma.edges.setdefault(self.old, []).append(self)
|
||||
|
||||
|
||||
class SMAnimation(renpy.display.core.Displayable):
|
||||
"""
|
||||
This creates a state-machine animation. Such an animation is
|
||||
created by randomly traversing the edges between states in a
|
||||
defined state machine. Each state corresponds to an image shown to
|
||||
the user, with the edges corresponding to the amount of time an
|
||||
image is shown, and the transition it is shown with.
|
||||
|
||||
Images are shown, perhaps with a transition, when we are
|
||||
transitioning into a state containing that image.
|
||||
"""
|
||||
|
||||
def __init__(self, initial, *args, **properties):
|
||||
"""
|
||||
@param initial: The name (a string) of the initial state we
|
||||
start in.
|
||||
|
||||
This accepts as additional arguments the anim.State and
|
||||
anim.Edge objects that are used to make up this state
|
||||
machine.
|
||||
"""
|
||||
|
||||
if 'delay' in properties:
|
||||
self.delay = properties['delay']
|
||||
del properties['delay']
|
||||
else:
|
||||
self.delay = None
|
||||
|
||||
super(SMAnimation, self).__init__(**properties)
|
||||
|
||||
self.properties = properties
|
||||
|
||||
# The initial state.
|
||||
self.initial = initial
|
||||
|
||||
# A map from state name to State object.
|
||||
self.states = { }
|
||||
|
||||
# A map from state name to list of Edge objects.
|
||||
self.edges = { }
|
||||
|
||||
for i in args:
|
||||
i.add(self)
|
||||
|
||||
# The time at which the current edge started. If None, will be
|
||||
# set to st by render.
|
||||
self.edge_start = None
|
||||
|
||||
# A cache for what the current edge looks like when rendered.
|
||||
self.edge_cache = None
|
||||
|
||||
# The current edge.
|
||||
self.edge = None
|
||||
|
||||
# The state we're in.
|
||||
self.state = None
|
||||
|
||||
def predict(self, callback):
|
||||
for i in self.states.itervalues():
|
||||
i.image.predict(callback)
|
||||
|
||||
def pick_edge(self, state):
|
||||
"""
|
||||
This randomly picks an edge out of the given state, if
|
||||
one exists. It updates self.edge if a transition has
|
||||
been selected, or returns None if none can be found. It also
|
||||
updates self.image to be the new image on the selected edge.
|
||||
"""
|
||||
|
||||
|
||||
if state not in self.edges:
|
||||
self.edge = None
|
||||
return
|
||||
|
||||
edges = self.edges[state]
|
||||
self.edge = random.choice(edges)
|
||||
self.state = self.edge.new
|
||||
|
||||
def update_cache(self):
|
||||
"""
|
||||
Places the correct Displayable into the edge cache, based on
|
||||
what is contained in the given edge. This takes into account
|
||||
the old and new states, and any transition that is present.
|
||||
"""
|
||||
|
||||
im = self.states[self.edge.new].get_image()
|
||||
|
||||
if self.edge.trans:
|
||||
im = self.edge.trans(old_widget=self.states[self.edge.old].get_image(),
|
||||
new_widget=im)
|
||||
|
||||
self.edge_cache = im
|
||||
|
||||
def get_placement(self):
|
||||
|
||||
if self.edge_cache:
|
||||
return self.edge_cache.get_placement()
|
||||
|
||||
if self.state:
|
||||
return self.states[self.state].get_image().get_placement()
|
||||
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.edge_start is None or st < self.edge_start:
|
||||
self.edge_start = st
|
||||
self.edge_cache = None
|
||||
self.pick_edge(self.initial)
|
||||
|
||||
while self.edge and st > self.edge_start + self.edge.delay:
|
||||
self.edge_start += self.edge.delay
|
||||
|
||||
self.edge_cache = None
|
||||
self.pick_edge(self.edge.new)
|
||||
|
||||
# If edge is None, then we have a permanent, static picture. Deal
|
||||
# with that.
|
||||
|
||||
if not self.edge:
|
||||
im = renpy.display.render.render(self.states[self.state].get_image(),
|
||||
width, height,
|
||||
st - self.edge_start)
|
||||
|
||||
|
||||
# Otherwise, we have another edge.
|
||||
|
||||
else:
|
||||
if not self.edge_cache:
|
||||
self.update_cache()
|
||||
|
||||
im = renpy.display.render.render(self.edge_cache, width, height, st - self.edge_start)
|
||||
|
||||
renpy.display.render.redraw(self.edge_cache, self.edge.delay - (st - self.edge_start))
|
||||
|
||||
|
||||
iw, ih = im.get_size()
|
||||
|
||||
rv = renpy.display.render.Render(iw, ih)
|
||||
rv.blit(im, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
def __call__(self, child=None, new_widget=None, old_widget=None):
|
||||
"""
|
||||
Used when this SMAnimation is used as a SMMotion. This creates
|
||||
a duplicate of the animation, with all states containing None
|
||||
as the image having that None replaced with the image that is provided here.
|
||||
"""
|
||||
|
||||
if child is None:
|
||||
child = new_widget
|
||||
|
||||
args = [ ]
|
||||
|
||||
for state in self.states.itervalues():
|
||||
args.append(state.motion_copy(child))
|
||||
|
||||
for edges in self.edges.itervalues():
|
||||
args.extend(edges)
|
||||
|
||||
return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
|
||||
|
||||
def Animation(*args):
|
||||
"""
|
||||
A Displayable that draws an animation, which is a series of images
|
||||
that are displayed with time delays between them.
|
||||
|
||||
Odd (first, third, fifth, etc.) arguments to Animation are
|
||||
interpreted as image filenames, while even arguments are the time
|
||||
to delay between each image. If the number of arguments is odd,
|
||||
the animation will stop with the last image (well, actually delay
|
||||
for a year before looping). Otherwise, the animation will restart
|
||||
after the final delay time.
|
||||
"""
|
||||
|
||||
sm = [ 0 ]
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
|
||||
if i % 2 == 0:
|
||||
sm.append(State(i, arg))
|
||||
|
||||
else:
|
||||
|
||||
if i == len(args) - 1:
|
||||
new = 0
|
||||
else:
|
||||
new = i + 1
|
||||
|
||||
sm.append(Edge(i - 1, arg, new))
|
||||
|
||||
return SMAnimation(*sm)
|
||||
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
# This module contains code that handles the playing of sound and
|
||||
# music files.
|
||||
|
||||
# NOTE TO SELF:
|
||||
#
|
||||
# Remember to code defensively against mikey's computer that
|
||||
# doesn't have the sound card in it.
|
||||
|
||||
import pygame
|
||||
import renpy
|
||||
import sys # to detect windows.
|
||||
import os
|
||||
|
||||
# The Windows Volume Management Strategy (tm).
|
||||
|
||||
# We keep a master music volume, which is the volume we use directly
|
||||
# when playing music as mp3, ogg, etc. When we start up, we compute
|
||||
# a midi music scaling factor. This midi music scaling factor is
|
||||
# computed from the current master music volume such that when we are not
|
||||
# fading, if we pygame.mixer.music.set_volume() to the mmv * mmsv, we get
|
||||
# read the same value from midiOutGetVolume() as we did before we tried
|
||||
# doing that.
|
||||
|
||||
# True if the mixer works, False if it doesn't, None if we have no
|
||||
# idea yet.
|
||||
mixer_works = None
|
||||
|
||||
# Common stuff.
|
||||
mixer_enabled = True
|
||||
playing_midi = True
|
||||
fading = False
|
||||
master_music_volume = 1.0
|
||||
|
||||
# Windows stuff.
|
||||
midi_msf = 0.0
|
||||
last_raw_volume = -1
|
||||
|
||||
def init():
|
||||
|
||||
global mixer_works
|
||||
global read_raw_volume
|
||||
global compute_midi_msf
|
||||
global set_music_volume
|
||||
global playing_midi
|
||||
|
||||
if mixer_works is not None:
|
||||
return
|
||||
|
||||
if 'RENPY_DISABLE_SOUND' in os.environ:
|
||||
mixer_works = False
|
||||
return
|
||||
|
||||
try:
|
||||
bufsize = 4096
|
||||
|
||||
if 'RENPY_SOUND_BUFSIZE' in os.environ:
|
||||
bufsize = int(os.environ['RENPY_SOUND_BUFSIZE'])
|
||||
|
||||
pygame.mixer.init(renpy.config.sound_sample_rate, -16, 2, bufsize)
|
||||
pygame.mixer.music.get_volume()
|
||||
mixer_works = True
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
mixer_works = False
|
||||
|
||||
windows_magic = False
|
||||
|
||||
if hasattr(sys, 'winver') and mixer_works:
|
||||
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
|
||||
def _read_raw_volume():
|
||||
res = c_uint()
|
||||
|
||||
for i in range(0, winmm.midiOutGetNumDevs()):
|
||||
rv = winmm.midiOutGetVolume(i, byref(res))
|
||||
|
||||
if not rv:
|
||||
return res.value
|
||||
else:
|
||||
print "Couldn't read raw midi volume."
|
||||
return -1
|
||||
|
||||
read_raw_volume = _read_raw_volume
|
||||
|
||||
def _compute_midi_msf():
|
||||
"""
|
||||
Computes the Midi MSF. Returns True if successful, False if otherwise.
|
||||
"""
|
||||
|
||||
# Don't update the MSF when fading is going on, or when not
|
||||
# playing a midi. (Except before playing any music whatsoever.)
|
||||
if fading or not playing_midi:
|
||||
return False
|
||||
|
||||
global last_raw_volume
|
||||
|
||||
raw_vol = read_raw_volume()
|
||||
|
||||
if raw_vol < 0:
|
||||
return False
|
||||
|
||||
# The case in which the volume hasn't changed recently.
|
||||
if raw_vol == last_raw_volume:
|
||||
return True
|
||||
|
||||
last_raw_volume = raw_vol
|
||||
|
||||
# print "raw_vol", raw_vol
|
||||
|
||||
# The fraction that the midi mixer is at.
|
||||
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
|
||||
|
||||
global midi_msf
|
||||
midi_msf = mixfrac / master_music_volume
|
||||
|
||||
# print "Midi msf is now:", midi_msf
|
||||
|
||||
return True
|
||||
|
||||
compute_midi_msf = _compute_midi_msf
|
||||
|
||||
# This should get called after the music starts playing.
|
||||
def _set_music_volume(vol):
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
if playing_midi:
|
||||
|
||||
vol *= midi_msf
|
||||
if vol > 1.0:
|
||||
vol = 1.0
|
||||
|
||||
try:
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
global last_raw_volume
|
||||
last_raw_volume = read_raw_volume()
|
||||
|
||||
set_music_volume = _set_music_volume
|
||||
|
||||
# Figure out the default msf, and set it up.
|
||||
windows_magic = compute_midi_msf()
|
||||
playing_midi = False
|
||||
|
||||
except Exception, e:
|
||||
print "Exception when trying to init music:", str(e)
|
||||
print "Falling back to Unix mode."
|
||||
|
||||
if not windows_magic:
|
||||
|
||||
def _compute_midi_msf():
|
||||
return
|
||||
|
||||
compute_midi_msf = _compute_midi_msf
|
||||
|
||||
def _set_music_volume(vol):
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
try:
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
set_music_volume = _set_music_volume
|
||||
|
||||
playing_midi = False
|
||||
|
||||
if mixer_works:
|
||||
pygame.mixer.music.set_endevent(renpy.display.core.MUSICEND)
|
||||
|
||||
|
||||
def disable_mixer():
|
||||
"""
|
||||
This function is called by the video code to disable the
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if mixer_enabled:
|
||||
try:
|
||||
music_stop()
|
||||
pygame.mixer.quit()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
mixer_enabled = False
|
||||
|
||||
def enable_mixer():
|
||||
"""
|
||||
This function is called by the video code to enable the
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if not mixer_enabled:
|
||||
try:
|
||||
pygame.mixer.init()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
mixer_enabled = True
|
||||
|
||||
|
||||
# This detects if the filename is a midi, and sets playing_midi
|
||||
# appropriately.
|
||||
def detect_midi(fn):
|
||||
|
||||
fn = fn.lower()
|
||||
|
||||
global playing_midi
|
||||
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
|
||||
|
||||
|
||||
def music_delay(offset):
|
||||
"""
|
||||
Returns the time left until the current music has been playing for
|
||||
offset seconds. If music is not playing, return None. May return
|
||||
a negative time.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return None
|
||||
|
||||
mo = pygame.mixer.music.get_pos()
|
||||
if mo < 0:
|
||||
return None
|
||||
|
||||
mo /= 1000.0
|
||||
|
||||
return offset - mo
|
||||
|
||||
|
||||
|
||||
# def music_start(filename, loops=-1, startpos=0.0):
|
||||
# """
|
||||
# This starts music playing. If a music track is already playing,
|
||||
# stops that track in favor of this one.
|
||||
|
||||
# @param filename: The file that the music will be played from. This
|
||||
# is relative to the game directory, and must be a real file (so it
|
||||
# cannot be stored in an archive.)
|
||||
|
||||
# @param loops: The number of times the music will loop after it
|
||||
# finishes playing. If negative, the music will loop indefinitely.
|
||||
# Please note that even once the song has finished, rollback or load
|
||||
# may cause it to start playing again. So it may not be safe to have
|
||||
# this set to a non-negative value.
|
||||
|
||||
# @param startpos: The number of seconds into the music to start playing.
|
||||
# """
|
||||
|
||||
# if not mixer_works:
|
||||
# return
|
||||
|
||||
# music_stop()
|
||||
# renpy.game.context().scene_lists.music = (filename, loops, startpos)
|
||||
# restore_music()
|
||||
|
||||
|
||||
# def music_stop():
|
||||
# """
|
||||
# Stops the currently playing music track.
|
||||
# """
|
||||
|
||||
# if not mixer_works:
|
||||
# return
|
||||
|
||||
# renpy.game.context().scene_lists.music = None
|
||||
# restore_music()
|
||||
|
||||
|
||||
# The filename of the currently playing piece of music.
|
||||
playing_filename = None
|
||||
|
||||
# The filename of the currently queued piece of music.
|
||||
queued_filename = None
|
||||
|
||||
# True if the music is in the process of fading out, or
|
||||
# False otherwise.
|
||||
fading = False
|
||||
|
||||
def music_update_volume():
|
||||
"""
|
||||
Sets the volume as appropriate for a midi.
|
||||
"""
|
||||
|
||||
if not playing_filename:
|
||||
return
|
||||
|
||||
detect_midi(playing_filename)
|
||||
set_music_volume(master_music_volume)
|
||||
|
||||
def music_end_event():
|
||||
"""
|
||||
This is called by renpy.display.core when a track of music has
|
||||
endend.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
# shift the filenames.
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = queued_filename
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
music_update_volume()
|
||||
|
||||
# Call the appropriate function.
|
||||
if renpy.config.music_end_event:
|
||||
renpy.config.music_end_event()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_enabled():
|
||||
"""
|
||||
This should be called to check to see if music is enabled. If this
|
||||
returns False, then no music call should be made. Please note that
|
||||
this does not check preferences.music_enabled, so user code should
|
||||
also check that to see if music should be played.
|
||||
|
||||
This will return True if the mixer works and it has not been
|
||||
pre-empted by the video player, and False otherwise.
|
||||
|
||||
If this does not return True, none of the other music functions should
|
||||
be called.
|
||||
"""
|
||||
|
||||
return mixer_works and mixer_enabled
|
||||
|
||||
def music_play(filename):
|
||||
"""
|
||||
This causes the named music filename to be loaded in and played.
|
||||
Music loaded in this way immediately replaces the currently
|
||||
playing music.
|
||||
|
||||
The track is played once, and then stops. It's up to the higher-level
|
||||
music layer to ensure that a track that needs to be looped actually
|
||||
is.
|
||||
|
||||
The filename must refer to a real file, and not a file hidden in an
|
||||
archive.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = filename
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
fn = renpy.loader.transfn(filename)
|
||||
fn = str(fn)
|
||||
|
||||
pygame.mixer.music.load(fn)
|
||||
|
||||
if not pygame.mixer.music.get_busy():
|
||||
pygame.mixer.music.play()
|
||||
|
||||
music_update_volume()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_queue(filename):
|
||||
"""
|
||||
This causes the given filename to be placed into the queue, to be
|
||||
played immediately after the currently playing track finishes.
|
||||
|
||||
The filename must refer to a real file, and not a file hidden in an
|
||||
archive.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
global queued_filename
|
||||
queued_filename = filename
|
||||
|
||||
pygame.mixer.music.queue(renpy.loader.transfn(filename))
|
||||
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_stop():
|
||||
"""
|
||||
This causes the music to be stopped immediately.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = None
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
pygame.mixer.music.stop()
|
||||
|
||||
except:
|
||||
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_fadeout(seconds):
|
||||
"""
|
||||
This causes the music to be faded out over a period of
|
||||
time.
|
||||
"""
|
||||
|
||||
# This works around a race condition in pygame/SDL_mixer.
|
||||
if seconds <= 0.0:
|
||||
music_stop()
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
queued_filename = None
|
||||
fading = True
|
||||
|
||||
pygame.mixer.music.fadeout(int(1000 * seconds))
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_filenames():
|
||||
"""
|
||||
Returns a tuple giving the currently playing music filename and
|
||||
the filename of the track in the queue. It returns None if there
|
||||
is no filename in either slot.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return None, None
|
||||
|
||||
try:
|
||||
|
||||
return playing_filename, queued_filename
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def music_fading():
|
||||
"""
|
||||
Returns True if the music is in the process of fading out, or False
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
return fading
|
||||
|
||||
def music_pause():
|
||||
"""
|
||||
Causes the currently playing music to be paused.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
pygame.mixer.music.pause()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_unpause():
|
||||
"""
|
||||
Causes the currently playing music to be unpaused, if it is
|
||||
currently paused.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
pygame.mixer.music.unpause()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def sound_enabled():
|
||||
"""
|
||||
Returns True if it's possible to play sound, or False if sound
|
||||
should not be played.
|
||||
|
||||
Please note that this does not check preferences.sound. It's up
|
||||
to higher-level code (like renpy.play) to do that.
|
||||
"""
|
||||
|
||||
return mixer_works and mixer_enabled
|
||||
|
||||
def sound_play(filename, loops=0, channel=0):
|
||||
"""
|
||||
This causes the sound contained in the given filename to be played.
|
||||
|
||||
@param loops: The number of extra times that the sound will be
|
||||
played. If -1, the sound is played forever (until stopped).
|
||||
|
||||
@param channel: The channel that the sound will be played on, an
|
||||
integer from 0 to 7. This allows us to support playing up to 8
|
||||
sounds at once.
|
||||
"""
|
||||
|
||||
if not sound_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
chan = pygame.mixer.Channel(channel)
|
||||
chan.play(pygame.mixer.Sound(renpy.loader.load(filename)), loops)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
|
||||
def sound_stop(channel=0):
|
||||
"""
|
||||
This causes the sound currently playing in the specified channel
|
||||
to be stopped.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
chan = pygame.mixer.Channel(channel)
|
||||
chan.stop()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def play(fn, loops=0):
|
||||
"""
|
||||
This plays the given sound. The sound must be in a wav file,
|
||||
and expected to have a sample rate 44100hz (changeable with
|
||||
config.sound_sample_rate), 16 bit, stereo. These expectations may
|
||||
be violated, but that may lead to conversion delays.
|
||||
|
||||
Once a sound has been started, there's no way to stop it.
|
||||
|
||||
@param fn: The name of the file that the sound is read from. This
|
||||
file may be contained in a game directory or an archive.
|
||||
|
||||
@param loops: The number of extra times the sound will be
|
||||
played. (The default, 0, will play the sound once.)
|
||||
|
||||
This plays the sound on channel 0.
|
||||
"""
|
||||
|
||||
if not fn:
|
||||
return
|
||||
|
||||
if not renpy.game.preferences.sound:
|
||||
return
|
||||
|
||||
sound_play(fn, loops=loops)
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
# import renpy.display.core as core
|
||||
# import renpy.display.layout as layout
|
||||
@@ -12,7 +13,80 @@ import renpy
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
class Keymap(renpy.display.layout.Container):
|
||||
def map_event(ev, name):
|
||||
"""
|
||||
This looks up the name in the keymap, and uses it to determine if
|
||||
the given event was caused by one of the keys or mouse buttons
|
||||
mapped to the given name in config.keymap. If so, it returns
|
||||
True, otherwise it returns False.
|
||||
"""
|
||||
|
||||
keys = renpy.config.keymap[name]
|
||||
|
||||
if ev.type == MOUSEBUTTONDOWN:
|
||||
if ( "mousedown_" + str(ev.button) ) in keys:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if ev.type == MOUSEBUTTONUP:
|
||||
if ( "mouseup_" + str(ev.button) ) in keys:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if ev.type == KEYDOWN:
|
||||
for key in keys:
|
||||
if key == ev.unicode or ev.key == getattr(pygame.constants, key, None):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def map_keyup(ev, name):
|
||||
|
||||
keys = renpy.config.keymap[name]
|
||||
|
||||
if ev.type == KEYUP:
|
||||
for key in keys:
|
||||
if ev.key == getattr(pygame.constants, key, None):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_pressed(pressed, name):
|
||||
"""
|
||||
This looks the given name up in the keymap. For each binding of the
|
||||
form K_whatever, it checks to see if the given key is pressed, and if
|
||||
so, returns the keycode of the pressed key. Otherwise, returns False.
|
||||
"""
|
||||
|
||||
keys = renpy.config.keymap[name]
|
||||
|
||||
for key in keys:
|
||||
code = getattr(pygame.constants, key)
|
||||
if pressed[code]:
|
||||
return code
|
||||
|
||||
return False
|
||||
|
||||
def skipping(ev):
|
||||
"""
|
||||
This handles setting skipping in response to the press of one of the
|
||||
CONTROL keys. The library handles skipping in response to TAB.
|
||||
"""
|
||||
|
||||
if map_event(ev, "skip"):
|
||||
renpy.config.skipping = True
|
||||
|
||||
if map_keyup(ev, "skip"):
|
||||
renpy.config.skipping = False
|
||||
|
||||
return
|
||||
|
||||
class Keymap(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a behavior that maps keys to functions that are called when
|
||||
the key is pressed. The keys are specified by giving the appropriate
|
||||
@@ -20,57 +94,42 @@ class Keymap(renpy.display.layout.Container):
|
||||
"""
|
||||
|
||||
def __init__(self, **keymap):
|
||||
super(Keymap, self).__init__(style='default')
|
||||
self.keymap = keymap
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
# Mouse events.
|
||||
if ev.type == MOUSEBUTTONDOWN:
|
||||
key = 'mouse_' + str(ev.button)
|
||||
if key in self.keymap:
|
||||
self.keymap[key]()
|
||||
for name, action in self.keymap.iteritems():
|
||||
if map_event(ev, name):
|
||||
rv = action()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
# def render(self, width, height, st):
|
||||
# return None
|
||||
|
||||
# Keyboard events.
|
||||
if ev.type != KEYDOWN:
|
||||
return
|
||||
|
||||
for key, action in self.keymap.iteritems():
|
||||
if key == ev.unicode or ev.key == getattr(pygame.constants, key, None):
|
||||
action()
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
def render(self, width, height, st):
|
||||
return None
|
||||
|
||||
class KeymouseBehavior(renpy.display.layout.Null):
|
||||
class PauseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class that causes the keyboard to move the mouse. It's
|
||||
useful on the game and key menus, as well as in imagemaps and the
|
||||
like.
|
||||
This is a class implementing the Pause behavior, which is to
|
||||
return a value after a certain amount of time has elapsed.
|
||||
"""
|
||||
|
||||
def __init__(self, delay, result=False):
|
||||
super(PauseBehavior, self).__init__()
|
||||
|
||||
self.delay = delay
|
||||
self.result = result
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
if ev.type == renpy.display.core.DISPLAYTIME:
|
||||
|
||||
pressed = pygame.key.get_pressed()
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
ox, oy = x, y
|
||||
|
||||
if pressed[K_LEFT]:
|
||||
x -= renpy.config.keymouse_distance
|
||||
if pressed[K_RIGHT]:
|
||||
x += renpy.config.keymouse_distance
|
||||
if pressed[K_UP]:
|
||||
y -= renpy.config.keymouse_distance
|
||||
if pressed[K_DOWN]:
|
||||
y += renpy.config.keymouse_distance
|
||||
|
||||
if (x, y) != (ox, oy):
|
||||
pygame.mouse.set_pos((x, y))
|
||||
|
||||
return None
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and ev.duration > self.delay:
|
||||
return self.result
|
||||
|
||||
|
||||
class SayBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
@@ -80,241 +139,102 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
mouse button.
|
||||
"""
|
||||
|
||||
def __init__(self, delay=None):
|
||||
super(SayBehavior, self).__init__()
|
||||
focusable = True
|
||||
|
||||
self.delay = delay
|
||||
def __init__(self, default=True, **properties):
|
||||
super(SayBehavior, self).__init__(default=default, **properties)
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and \
|
||||
ev.duration > self.delay:
|
||||
return False
|
||||
renpy.config.allow_skipping and renpy.config.skipping and \
|
||||
ev.duration > renpy.config.skip_delay / 1000.0:
|
||||
|
||||
if ev.type == MOUSEBUTTONDOWN:
|
||||
if ev.button == 1:
|
||||
if renpy.game.preferences.skip_unseen:
|
||||
return True
|
||||
elif renpy.game.context().seen_current(True):
|
||||
return True
|
||||
|
||||
if ev.button == 5:
|
||||
if renpy.game.context().seen_current(False):
|
||||
return True
|
||||
if map_event(ev, "dismiss") and self.is_focused():
|
||||
return True
|
||||
|
||||
|
||||
if ev.type == KEYDOWN:
|
||||
if ev.key == K_PAGEDOWN:
|
||||
if renpy.game.context().seen_current(False):
|
||||
return True
|
||||
|
||||
if ev.key == K_RETURN:
|
||||
if map_event(ev, "rollforward"):
|
||||
if renpy.game.context().seen_current(False):
|
||||
return True
|
||||
|
||||
if ev.key == K_SPACE:
|
||||
return True
|
||||
|
||||
if ev.key == K_LCTRL or ev.key == K_RCTRL:
|
||||
if renpy.game.preferences.skip_unseen:
|
||||
return True
|
||||
elif renpy.game.context().seen_current(True):
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
class Menu(renpy.display.layout.VBox):
|
||||
|
||||
def __init__(self, menuitems):
|
||||
"""
|
||||
@param menuitems: A list of menuitem tuples. The first element
|
||||
of each tuple is the string that should be displayed to the
|
||||
user. The second item is the value that should be returned if
|
||||
this item is selected, or None to indicate that this item is a
|
||||
caption.
|
||||
"""
|
||||
|
||||
super(Menu, self).__init__(full=False)
|
||||
|
||||
self.selected = None
|
||||
self.results = [ ]
|
||||
|
||||
self.caption_style = renpy.style.Style('menu_caption', { })
|
||||
self.selected_style = renpy.style.Style('menu_choice', { })
|
||||
self.unselected_style = renpy.style.Style('menu_choice', { })
|
||||
|
||||
self.selected_style.set_prefix('hover_')
|
||||
self.unselected_style.set_prefix('idle_')
|
||||
|
||||
for i, (caption, result) in enumerate(menuitems):
|
||||
self.add(renpy.display.text.Text(caption))
|
||||
|
||||
if self.selected is None and result is not None:
|
||||
self.selected = i
|
||||
|
||||
self.results.append(result)
|
||||
|
||||
self.update_styles()
|
||||
|
||||
def update_styles(self):
|
||||
"""
|
||||
This updates the colors of our children to reflect the
|
||||
one that has been selected by the user.
|
||||
"""
|
||||
|
||||
for i, (child, result) in enumerate(zip(self.children, self.results)):
|
||||
|
||||
# Captions should stay the default text color.
|
||||
if result is None:
|
||||
child.set_style(self.caption_style)
|
||||
continue
|
||||
|
||||
# Actual choices change color if they are selected or not.
|
||||
if i == self.selected:
|
||||
child.set_style(self.selected_style)
|
||||
else:
|
||||
child.set_style(self.unselected_style)
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
Processes events.
|
||||
"""
|
||||
|
||||
# print ev
|
||||
# print x, y
|
||||
|
||||
old_selected = self.selected
|
||||
|
||||
# Change selection based on mouse position.
|
||||
if ev.type == MOUSEMOTION:
|
||||
target = self.child_at_point(x, y)
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
if self.results[target] is not None:
|
||||
self.selected = target
|
||||
|
||||
# Make selection based on mouse click position.
|
||||
if ev.type == MOUSEBUTTONDOWN and ev.button == 1:
|
||||
target = self.child_at_point(x, y)
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
if self.results[target] is not None:
|
||||
renpy.sound.play(self.selected_style.activate_sound)
|
||||
return self.results[target]
|
||||
|
||||
# Change selection based on keypress.
|
||||
if ev.type == KEYDOWN and ev.key == K_DOWN:
|
||||
|
||||
selected = self.selected
|
||||
|
||||
while selected < len(self.results) - 1:
|
||||
selected += 1
|
||||
if self.results[selected] is not None:
|
||||
self.selected = selected
|
||||
break
|
||||
|
||||
# Change selection based on keypress.
|
||||
if ev.type == KEYDOWN and ev.key == K_UP:
|
||||
|
||||
selected = self.selected
|
||||
|
||||
while selected > 0:
|
||||
selected -= 1
|
||||
if self.results[selected] is not None:
|
||||
self.selected = selected
|
||||
break
|
||||
|
||||
# Make selection based on keypress.
|
||||
if ev.type == KEYDOWN and ev.key == K_RETURN:
|
||||
renpy.sound.play(self.selected_style.activate_sound)
|
||||
return self.results[self.selected]
|
||||
|
||||
# If the selected item changed, update the display.
|
||||
if self.selected != old_selected:
|
||||
|
||||
self.children[self.selected].set_style(self.selected_style)
|
||||
self.children[old_selected].set_style(self.unselected_style)
|
||||
|
||||
renpy.sound.play(self.selected_style.hover_sound)
|
||||
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
return None
|
||||
|
||||
class Button(renpy.display.layout.Window):
|
||||
|
||||
|
||||
def __init__(self, child, style='button', clicked=None, **properties):
|
||||
def __init__(self, child, style='button', clicked=None,
|
||||
hovered=None, **properties):
|
||||
|
||||
super(Button, self).__init__(child, style=style, **properties)
|
||||
self.style.set_prefix('idle_')
|
||||
|
||||
self.old_hover = False
|
||||
self.activated = False
|
||||
self.clicked = clicked
|
||||
self.hovered = hovered
|
||||
self.focusable = clicked is not None
|
||||
|
||||
def set_hover(self, hover):
|
||||
"""
|
||||
Called when we change from hovered to un-hovered, or
|
||||
vice-versa.
|
||||
"""
|
||||
def render(self, width, height, st):
|
||||
|
||||
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
|
||||
|
||||
if hover:
|
||||
self.style.set_prefix('hover_')
|
||||
else:
|
||||
self.style.set_prefix('idle_')
|
||||
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
inside = False
|
||||
# We deactivate on an event.
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
|
||||
width, height = self.window_size
|
||||
if self.focusable:
|
||||
if self.is_focused():
|
||||
self.set_style_prefix('hover_')
|
||||
else:
|
||||
self.set_style_prefix('idle_')
|
||||
else:
|
||||
self.set_style_prefix('insensitive_')
|
||||
|
||||
if x >= 0 and x < width and y >= 0 and y < height:
|
||||
inside = True
|
||||
# If not focused, ignore all events.
|
||||
if not self.is_focused():
|
||||
return None
|
||||
|
||||
if ev.type == MOUSEMOTION:
|
||||
# If clicked,
|
||||
if map_event(ev, "button_select") and self.clicked:
|
||||
|
||||
if self.old_hover != inside:
|
||||
self.old_hover = inside
|
||||
self.set_hover(inside)
|
||||
self.activated = True
|
||||
|
||||
if inside:
|
||||
renpy.sound.play(self.style.hover_sound)
|
||||
self.set_style_prefix('activate_')
|
||||
renpy.display.audio.play(self.style.sound)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
if (ev.type == MOUSEBUTTONDOWN and ev.button == 1) or \
|
||||
(ev.type == KEYDOWN and ev.key == K_RETURN):
|
||||
if inside:
|
||||
renpy.sound.play(self.style.activate_sound)
|
||||
return self.clicked()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class TextButton(Button):
|
||||
# Reimplementation of the TextButton widget as a Button and a Text
|
||||
# widget.
|
||||
def TextButton(text, style='button', text_style='button_text',
|
||||
clicked=None, **properties):
|
||||
|
||||
def __init__(self, text, style='button', text_style='button_text',
|
||||
clicked=None):
|
||||
text = renpy.display.text.Text(text, style=text_style)
|
||||
return Button(text, style=style, clicked=clicked, **properties)
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -322,30 +242,128 @@ class Input(renpy.display.text.Text):
|
||||
"""
|
||||
|
||||
def __init__(self, default, length=None,
|
||||
style='input_text', **properties):
|
||||
super(Input, self).__init__(default + "_", style=style, **properties)
|
||||
style='input_text',
|
||||
allow=None,
|
||||
exclude=None,
|
||||
**properties):
|
||||
|
||||
super(Input, self).__init__(default.replace("{", "{{") + "_", style=style, **properties)
|
||||
|
||||
self.content = unicode(default)
|
||||
self.length = length
|
||||
|
||||
self.allow = allow
|
||||
self.exclude = exclude
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == KEYDOWN:
|
||||
if ev.key == K_BACKSPACE:
|
||||
if self.content:
|
||||
self.content = self.content[:-1]
|
||||
if map_event(ev, "input_backspace"):
|
||||
if self.content:
|
||||
self.content = self.content[:-1]
|
||||
|
||||
elif ev.key == K_RETURN:
|
||||
return self.content
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
elif ev.unicode:
|
||||
if ord(ev.unicode[0]) < 32:
|
||||
return None
|
||||
elif map_event(ev, "input_enter"):
|
||||
return self.content
|
||||
|
||||
elif ev.type == KEYDOWN and ev.unicode:
|
||||
if ord(ev.unicode[0]) < 32:
|
||||
return None
|
||||
|
||||
if self.length and len(self.content) >= self.length:
|
||||
return None
|
||||
if self.length and len(self.content) >= self.length:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
self.content += ev.unicode
|
||||
if self.allow and ev.unicode not in self.allow:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
self.set_text(self.content + "_")
|
||||
renpy.game.interface.redraw(0)
|
||||
if self.exclude and ev.unicode in self.exclude:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
self.content += ev.unicode
|
||||
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
|
||||
class Bar(renpy.display.core.Displayable):
|
||||
"""
|
||||
Implements a bar that can display an integer value, and respond
|
||||
to clicks on that value.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height, range, value,
|
||||
style='bar', **properties):
|
||||
|
||||
super(Bar, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.range = range
|
||||
self.value = value
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
width = self.width
|
||||
height = self.height
|
||||
|
||||
lgutter = 0
|
||||
rgutter = 0
|
||||
|
||||
barwidth = width - lgutter - rgutter
|
||||
|
||||
left_width = barwidth * self.value // self.range
|
||||
right_width = barwidth - left_width
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
lsurf = render(self.style.left_bar, left_width, height, st)
|
||||
rsurf = render(self.style.right_bar, right_width, height, st)
|
||||
|
||||
rv.blit(lsurf, (lgutter, 0))
|
||||
rv.blit(rsurf, (lgutter + left_width, 0))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Conditional(renpy.display.layout.Container):
|
||||
"""
|
||||
This class renders its child if and only if the condition is
|
||||
true. Otherwise, it renders nothing. (Well, a Null).
|
||||
|
||||
Warning: the condition MUST NOT update the game state in any
|
||||
way, as that would break rollback.
|
||||
"""
|
||||
|
||||
def __init__(self, condition, *args):
|
||||
super(Conditional, self).__init__(*args)
|
||||
|
||||
self.condition = condition
|
||||
self.null = renpy.display.layout.Null()
|
||||
|
||||
self.state = eval(self.condition, vars(renpy.store))
|
||||
|
||||
def render(self, width, height, st):
|
||||
if self.state:
|
||||
return render(self.child, width, height, st)
|
||||
else:
|
||||
return render(self.null, width, height, st)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
state = eval(self.condition, vars(renpy.store))
|
||||
|
||||
if state != self.state:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
self.state = state
|
||||
|
||||
if state:
|
||||
return self.child.event(ev, x, y)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||