Compare commits

..

55 Commits

Author SHA1 Message Date
tom b51312b21d Tagging 4.8.7
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/tags/4.8.7@171 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-06-09 03:38:44 +00:00
tom ab5d5c0e40 git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@65 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-04-02 22:57:20 +00:00
tom c28639ef0b 4.7.2 final
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@63 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-04-02 22:13:26 +00:00
tom a3b3d26816 git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@62 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-04-02 21:36:42 +00:00
tom ee6bbf5d9c git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@61 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-04-02 21:24:41 +00:00
tom 6e406cdae0 git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@60 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-04-02 20:44:26 +00:00
tom 8ef2d97247 git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@59 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-04-02 20:24:34 +00:00
tom e9be405aaf Installer.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@58 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-03-19 23:20:46 +00:00
tom 23dbab98d2 Readback.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@57 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-03-19 19:52:29 +00:00
tom 2865599e34 Added button_game_menu.rpy
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@56 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-03-11 03:39:31 +00:00
tom fb5957186a Changed back library transition.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@54 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-27 04:18:44 +00:00
tom 8d2e592589 Added dump_text
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@53 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-27 04:00:50 +00:00
tom c7714a69fd Added unicode_escape encoding.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@52 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-27 02:51:58 +00:00
tom 6d2dce1704 Fixed bug in subsurface.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@51 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-16 23:35:15 +00:00
tom 8ab32e8dba git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@45 ef037c1b-3fee-0310-a5a7-a1260de54e99 2005-02-14 21:05:59 +00:00
tom 00ee770512 Updated changelog for 4.7.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@44 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 20:15:00 +00:00
tom c5761728b3 Updated changelog for 4.7.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@43 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 19:33:00 +00:00
tom d33aa31101 Version to 4.7
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@42 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 15:38:59 +00:00
tom 34cfc4fa40 Made mixer access much more conservative.
Fixed performance problems with fonts on windows.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@41 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 04:31:05 +00:00
tom d603d62680 Documentation for text tags, and enhancements reccomended by it.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@40 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-14 02:17:54 +00:00
tom 4991c5761c Reimplemented Text, to support text tags, which allow the size, color, boldness, italics,
and underline of text to be changed.

Added text properties for bold, italic, and underline.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@39 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-13 23:50:51 +00:00
tom cf3f8afd9f 4.6.2
Fixed bug with no sound card.

Added speedtest.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@37 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-12 22:08:36 +00:00
tom 31fc8b213e Fixed bug in imagemap.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@36 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-11 05:08:28 +00:00
tom 74be98faba Changed console.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@34 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-11 00:31:51 +00:00
tom b4e6b24a86 Fixed bugs with predictive loading and surface reuse.
Updated changelog and version for 4.6.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@33 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-10 20:24:03 +00:00
tom 7c1980775e Fixed memory leak in / rewrote render.py
Added memory leak profiler.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@32 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-10 07:29:10 +00:00
tom 90d586968e Added a framecounter.
Changed dissolve to use compositing rather than drawing twice. Minor speedup. 



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@31 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-09 16:05:18 +00:00
tom af0778b8bc Fixed text of demo game.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@29 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 19:02:05 +00:00
tom 23bccf06cb config.overlay_during_wait added.
overlay_menu extra added.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@28 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 18:49:37 +00:00
tom 7558b20b1e Bug fixes.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@27 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 17:18:04 +00:00
tom 8fbb785674 Updated license.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@26 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 05:52:03 +00:00
tom d2210cf08f Added interact parameters to various says.
Added button_menu extra.

Fixed Lemmasoft url.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@25 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 05:51:30 +00:00
tom 8d8ba4fb00 Added the preferences screen to the main menu.
Readied the changelog and demo script for release.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@24 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 05:05:22 +00:00
tom 915df21775 Broke up the margin and padding properties.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@23 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 03:47:17 +00:00
tom 59c7baf1ff Added support for activating widgets.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@22 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 02:58:33 +00:00
tom dc0d2f8039 Minor fixes.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@21 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 02:05:11 +00:00
tom bcf752d3a6 Updated documentation to reflect previous checkin.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@20 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 01:48:45 +00:00
tom d95cd1f6a0 Added layer support.
Added interaction restart support.

Added support for game menu transitions.

Eliminated renpy.interact()





git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@19 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-08 01:21:45 +00:00
tom c9f025dab2 Fixed bug in demo script. :-(
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@18 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-07 05:00:26 +00:00
tom d484cc2906 Fixed bug in distribute.py
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@16 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-05 05:00:45 +00:00
tom 18cf867432 Ready for the release of 4.5.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@15 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-05 04:55:45 +00:00
tom 149a2904a5 Added renpy.exists.
Made demo play video, if it exists.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@14 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-04 05:05:20 +00:00
tom ef2e78616f Bug fix in library.
Updated documentation and changelog.

Improved error reporting by printing error message at the top of 
traceback.txt.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@13 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-03 15:30:34 +00:00
tom 2711f79080 Implemented grid widget.
Changed the library to use constructors and grids.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@12 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-03 06:45:58 +00:00
tom fa31e03d06 Fixed bug where screen would not redraw after changing from fullscreen to
windowed, or vice-versa.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@11 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-02 18:31:10 +00:00
tom 6a27954677 New transition: CropMove
Extras directory for interesting sample code. It now includes kanamode
and fullscreen examples. An extragame tree exists to test out some of 
this code.

Now create Solids with an alpha of 255 as a surface w/o an alpha
channel.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@10 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-02 18:24:05 +00:00
tom eab1edd5b4 All new rendering code. Replaces renpy.surface.Surface with
renpy.render.Render, which handles caching and clipping properly. 



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@9 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-01 19:10:42 +00:00
tom b9624fe922 Redid the display routines so that everything is a widget inside an
interaction. It's one less thing to think of when doing transitions.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@8 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-02-01 05:31:39 +00:00
tom 69727f3baf Speed improvements to the library (we clear the unseed screne list before
showing the main or game menus).

Speed improvements with graphical mice, and also make sure that it works
with movies.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@7 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-31 15:30:10 +00:00
tom 0551eb6135 Rewrote movie support. Now, we also support overlaying things onto movies.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@6 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-31 06:34:59 +00:00
tom c0b8ca8777 Various minor speed improvements.
Turned renpy.sound and renpy.music into renpy.display.audio.

Added renpy.display.video, and the ability to play MPEG-1 video.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@5 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-29 16:05:58 +00:00
tom 7af2e3d066 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.



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@4 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-28 03:29:22 +00:00
tom 0a857f8879 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. 

Fixed a bug in which hiding the UI was not working properly. (This 
broke the center mouse button behavior.)



git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@3 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-28 01:30:56 +00:00
tom 839073ad8c Deleted release.txt.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@2 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-22 07:46:04 +00:00
tom eb22c24884 Initial import.
git-svn-id: file:///home/tom/magnetic/ab/cvs-and-svn/svn-repos/renpy/trunk@1 ef037c1b-3fee-0310-a5a7-a1260de54e99
2005-01-22 07:43:23 +00:00
179 changed files with 21655 additions and 2911 deletions
+975
View File
@@ -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)
+52
View File
@@ -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.
+55
View File
@@ -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.
+4
View File
@@ -0,0 +1,4 @@
Have a new form of imagemap that uses a color image to distinguish
hotspots.
Keyd compiles.
+31 -6
View File
@@ -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 = { }
+2 -1
View File
@@ -1,2 +1,3 @@
cd images
cd game
..\archiver.exe images *.png *.jpg
pause
+8 -1
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
build_exe.py run_game
+1 -1
View File
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
setup(name="RenPy",
windows=programs,
console=[ "archiver.py", "add_from.py" ],
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py", "run_dse.py" ],
zipfile='lib/renpy.zip',
)
+60
View File
@@ -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))
+437
View File
@@ -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
+117 -339
View File
@@ -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:
+79
View File
@@ -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
+128
View File
@@ -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
+241
View File
@@ -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
+216 -75
View File
@@ -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)
+160
View File
@@ -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()
+822
View File
@@ -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 $
-207
View File
@@ -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
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

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

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
View File
Binary file not shown.
+891 -75
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

+41 -10
View File
@@ -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()
+13
View File
@@ -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.
+14
View File
@@ -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.
+6 -6
View 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
+107
View File
@@ -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!
+15
View File
@@ -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
+44
View File
@@ -0,0 +1,44 @@
import xml.dom.minidom
import sys
def escape(s):
return s.replace("&", "&amp;").replace("<", "&lt;").replace('"', "&quot;")
def transform_children(node):
rv = [ ]
for n in node.childNodes:
rv.append(transform(n))
return ''.join(rv)
def transformElement(node):
attributes = ' '.join(['%s="%s"' % (k, escape(v)) for k,v in node.attributes.items()])
tag = node.tagName
# Otherwise, the default.
return "<%s %s>%s</%s>" % (node.tagName, attributes,
transform_children(node), node.tagName)
def transform(node):
if node.nodeType == node.ELEMENT_NODE:
return transformElement(node)
else:
return escape(str(node))
def main():
dom = xml.dom.minidom.parse(sys.argv[1])
print transform(dom.documentElement)
if __name__ == "__main__":
main()
+163
View File
@@ -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>
+373
View File
@@ -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.
+20
View File
@@ -1,5 +1,6 @@
#!/usr/bin/python
import os
import re
import sys
import time
@@ -7,6 +8,7 @@ import time
import inspect
sys.path.append('..')
sys.path.append('../module')
import renpy
@@ -84,6 +86,12 @@ def function(m):
renpy.store.renpy = renpy.exports
func = eval(name, store)
if isinstance(func, renpy.curry.Curry):
if func.callable == renpy.curry.Curry:
func = func.args[0]
else:
func = func.callable
doc = func.__doc__
@@ -97,6 +105,9 @@ def function(m):
else:
args = inspect.formatargspec(*inspect.getargspec(func))
args = re.sub(r"'\\x00\\x01.*?\\xff'", "im.ramp(0, 255)", args)
args = re.sub(r'<.*?>', '...', args)
docparas = []
@@ -126,6 +137,15 @@ def main():
s = f.read()
f.close()
os.chdir("..")
try:
renpy.main.main("dump_styles")
except "foo":
pass
os.chdir("doc")
s = re.sub(r"<!-- func (\S+) -->", function, s)
s = re.sub(r"<!-- include (\S+) -->", include, s)
File diff suppressed because it is too large Load Diff
+24
View File
@@ -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>
+132
View File
@@ -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
+258
View File
@@ -0,0 +1,258 @@
# The Ren'Py/DSE event dispatcher. This file contains the code that
# actually supports the running of events. Specifically, it contains
# code that determines which events are available, which events can
# run, and to actually run the events that should be run during a
# given period.
# This isn't really intended to be user-changable.
init -100:
python:
# A list of all of the events that the system knowns about,
# it's filtered to determine which events should run when.
all_events = [ ]
# The base class for events. When constructed, an event
# automatically adds itself to all_events.
#
# The first parameter for this is a unique name for the event,
# which is also used as the label that is called when the
# event executes.
#
# All other parameters are expressions. These expressions can
# be either strings, or objects having an eval method. Many
# interesting objects are given below.
#
# Keyword arguments are also kept on the object. Currently,
# there is one useful keyword argument, priority. This
# controls the order in which events are in the event list.
# (Events with lower priority number are evaluated first. If a
# priority is not specified, it's 100.)
class event(object):
def __repr__(self):
return '<event ' + self.name + '>'
def __init__(self, name, *args, **kwargs):
self.name = name
exprs = [ ]
for i in args:
if isinstance(i, basestring):
exprs.append(event.evaluate(i))
else:
exprs.append(i)
self.exprs = exprs
self.priority = kwargs.get('priority', 100)
all_events.append(self)
# Checks to see if this event is valid. It's calles with
# a list of events that have already checked out to be
# True, and returns True if this event checks out.
def check(self, valid):
for i in self.exprs:
if not i.eval(self.name, valid):
return False
return True
# The base class for all of the event checks given below.
class event_check(object):
def __invert__(self):
return event.false(self)
def __and__(self, other):
return event.and_op(self, other)
def __or__(self, other):
return event.or_op(self, other)
# This evaluates the expression given as an argument, and the
# returns true if it evaluates to true.
class evaluate(event_check):
def __init__(self, expr):
self.expr = expr
def eval(self, name, valid):
return eval(self.expr)
# If present as a condition to an event, an object of this
# type ensures that the event will only execute once.
class once(event_check):
def eval(self, name, valid):
return name not in events_executed
# Returns True if no event of higher priority can execute,
# and false otherwise. In general, solo events should be
# the lowest priority, and are run if nothing else is.
class solo(event_check):
def eval(self, name, valid):
# True if valid is empty.
return not valid
# Returns True if the given events have happend already,
# at any time in the game. False if at least one hasn't
# happened yet.
class happened(event_check):
def __init__(self, *events):
self.events = events
def eval(self, name, valid):
for i in self.events:
if i not in events_executed:
return False
return True
# Evaluates its argument, and returns true if it is false
# and vice-versa.
class false(event_check):
def __init__(self, cond):
self.cond = cond
def eval(self, name, valid):
return not self.cond.eval(name, valid)
# Handles the and operator.
class and_op(event_check):
def __init__(self, *args):
self.args = args
def eval(self, name, valid):
for i in self.args:
if not i.eval(name, valid):
return False
return True
# Handles the or operator.
class or_op(event_check):
def __init__(self, *args):
self.args = args
def eval(self, name, valid):
for i in self.args:
if i.eval(name, valid):
return True
return False
# Returns True if all of the events given as arguments have
# happened, or False otherwise.
class depends(event_check):
def __init__(self, *events):
self.events = events
def eval(self, name, valid):
for i in self.events:
if i not in events_executed_yesterday:
return False
return True
# The number of periods to skip.
skip_periods = 0
# This returns True if the current period should be skipped,
# or False if the current period should execute. If it returns
# True, it decrements skip_periods.
def check_skip_period():
global skip_periods
if skip_periods:
skip_periods -= 1
return True
else:
return False
# This should be called when the game starts. It inits the various
# data structures used by the events.
label events_init:
# Stores the events that have been executed.
$ events_executed = { }
# Stores the events that have been executed before today.
# (Where today is ended by a call to events_end_day.)
$ events_executed_yesterday = { }
return
# This should called at the end of a (game) day, to let things
# like depends_yesterday to work.
label events_end_day:
$ skip_periods = 0
python hide:
# We can't skip between days.
skip_periods = 0
for k in events_executed:
events_executed_yesterday[k] = True
return
# This is called once per period, to determine, and then execute, the
# events that should be run for that period.
label events_run_period:
$ events = [ ]
python hide:
for i in all_events:
if i.check(events):
events.append(i.name)
while not check_skip_period() and events:
$ _event = events.pop(0)
$ events_executed[_event] = True
call expression _event from call_expression_event_1
return
# If this is jumped to, it will end the current period immediately,
# and return control to the main program.
label events_end_period:
$ skip_period = 1
return
# If this is jumped to, it will end the current period and skip
# the next period. Use this if, say, the user goes on a date that
# takes up two time slots.
label events_skip_period:
$ skip_period = 2
return
init 100:
python:
# Sort all events on priority. Schwartzian transform time.
all_events = [ (i.priority, i) for i in all_events ]
all_events.sort()
all_events = [ b for a, b in all_events ]
+747
View File
@@ -0,0 +1,747 @@
# This file contains the events that will be part of the game. It's
# expected that the user will add and remove events as appropriate
# for this game.
# Some characters that are used in events in the game.
init:
$ t = Character('Teacher')
$ gg = Character('Glasses Girl', color=(192, 255, 192, 255))
$ sg = Character('Sporty Girl', color=(255, 255, 192, 255))
$ bg = Character('Both Girls')
# First up, we define some simple events for the various actions, that
# are run only if no higher-priority event is about to occur.
init:
$ event("class", "act == 'class'", event.solo(), priority=200)
$ event("cut", "act == 'cut'", event.solo(), priority=200)
$ event("study", "act == 'study'", event.solo(), priority=200)
$ event("hang", "act == 'hang'", event.solo(), priority=200)
$ event("exercise", "act == 'exercise'", event.solo(), priority=200)
$ event("play", "act == 'play'", event.solo(), priority=200)
label class:
"I make it to class just in time, and proceed to listen to the
teacher droning on about a wide range of topics, none of which
are remotely interesting."
return
label cut:
"I cut class, and spend the morning goofing off instead."
$ intelligence -= 10
return
label study:
"I head on down to the library, and start reading about the topics
I should have been reading about in class."
$ intelligence += 10
return
label hang:
"I spend the afternoon hanging out with my friends, killing
some time."
return
label exercise:
"I decide to go out for a run through the town, to keep myself in
shape."
$ strength += 10
return
label play:
"I pop a DVD into my video game console, and spend the evening
rolling small cities up into balls."
$ strength -= 10
return
# Below here are special events that are triggered when certain
# conditions are true.
# This is an introduction event, that runs once when we first go
# to class.
init:
$ event("introduction", "act == 'class'", event.once())
label introduction:
"I run to school, and make it to my seat just as the bell
signalling the start of class rings."
t "Before we start, I have an announcement to make."
t "We will have two new students joining us. Girls, come on in."
"Two girls walk in, and stand in front of the class."
"They're twins."
"Identical twins."
"Identical black hair, and the same pretty face."
"Despite that, it's still fairly easy to tell them apart."
"The one on the left is wearing glasses."
"Not too thick, but enough to let me know she probably reads alot
of books."
"If I look a little closely, I can find another difference."
"The one on the right probably exercises a bit more."
"I can tell by the muscle tone in her legs."
"I realize that I'm staring at her legs, and quickly look up."
"Suddenly, I realize that she's been talking for all this town."
sg "... to this town. And we hope to be friends with all of you."
sg "Well, that's about it. Sis, do you have anything to say?"
"The girl with glasses pauses for a second, and then quickly says:"
gg "{size=-4}It's good to meet you all.{/size}"
"She stops, and goes back to not saying anything."
t "Well, if that's all, you can take your seats and we can start
the class."
"They do, and our teacher begins his lecture."
"I don't think anyone pays much attention to it, however."
return
# These are the events with glasses girl.
init:
# The glasses girl is studying in the library, but we do not
# talk to her.
#
#
$ event("gg_studying",
# This takes place when the action is 'study'.
"act == 'study'",
# This will only take place if no higher-priority
# event will occur.
event.solo(),
# This takes place at least one day after seeing the
# introduction event.
event.depends("introduction"),
# This takes priority over the study event.
priority=190)
# She asks to borrow our pen.
$ event("borrow_pen",
# This takes place when we go to study, and we have an int
# >= 50.
"act == 'study' and intelligence >= 50",
# It runs only once.
event.once(),
# It requires the introduction event to have run at least
# one day before.
event.depends("introduction"))
# After the pen, she smiles when she sees us.
$ event("gg_smiling", "act == 'study'",
event.solo(), event.depends("borrow_pen"),
priority = 180)
# The bookslide.
$ event("bookslide", "act == 'study' and intelligence == 100",
event.once(), event.depends("borrow_pen"))
# She makes us cookies.
$ event("cookies", "act == 'study'",
event.once(), event.depends("bookslide"))
# Her solo ending.
$ event("gg_confess", "act == 'class'",
event.once(), event.depends("cookies"))
label gg_studying:
"I head to the library, to get some studying done."
"The glasses girl is there, but she's busy reading a book, taking
notes as she does so."
"I decide not to disturb her, and instead start reading my own
book."
$ intelligence += 10
return
label borrow_pen:
"I head to the library, to get some studying done."
"The glasses girl is there, but she's busy reading a book."
"I decide not to disturb her, and instead start reading my own
book."
"Suddenly, I feel a tap on my shoulder."
"I look up, and see the glasses girl standing right next to me."
gg "Excuse me, but can I borrow your pen?"
gg "Mine ran out of ink."
"I dig through my bag, to find the pen I had stashed there."
"While I'm looking, I point out that she seems to come to the
library alot."
gg "Hm... I guess you're right."
gg "There's so much stuff here, and I want to know about it all."
gg "Surely, you must feel the same way, as you're here almost as
much as I am."
"I don't have the heart to tell her that I'm only here to study so
that I don't fail out."
"My hand brushes the pen, and I quickly pull it out and give it to
her."
gg "Thank you."
"She says, and she goes back to studying."
return
label gg_smiling:
"I head to the library, to get some studying done."
"The glasses girl is there, and smiles at me for a second before
turning back to her book."
"I decide not to disturb her, and instead start reading my own
book."
$ intelligence += 10
return
label bookslide:
"I head to the library, to get some studying done."
"The glasses girl is standing right by the entrance, putting a
book back into a bookcase containing science books."
"It looks quite old, and quite weak, as if it could break at any
time."
"Suddenly, I hear a loud crack come from the bookcase."
"Without thinking, I throw myself between the girl and the
bookcase, pushing her out of the way in the process."
"As the shelves fail one by one, I'm hit with large textbooks on
topics ranging from Astronomy to Zoology."
"She's safe, but I'm knocked off my feet by the falling books."
"Before the dust even settled, the girl with glasses realized what
happened and asked:"
gg "Are you alright?"
"I tell her that I am, all the while rubbing a bruise left by a
particularly large Physics book."
gg "You saved me."
"She points out. I shrug... I guess I did, but it's not like I'm a
hero or anything."
"She extends out her hand, I take it, and she helps me to get up."
gg "Wow... Um..."
"She doesn't know what to say."
"I suggest that we help clean up the mess, mostly to take her off
the spot."
"She agrees, and together we begin piling the books up into neat
piles."
return
label cookies:
"I head to the library, to get some studying done."
"The glasses girl is there, apparently waiting for me."
"She's holding a package in her hands."
gg "Here."
"She says, and she hands me the package."
"I take it from her, and open it."
"It contains fresh homemade cookies. Ginger snaps, I think."
gg "It's to thank you."
"She points out... She's probably not used to this. Especially
with guys."
"I take one of them, and stick it in my mouth."
"The taste is exquisite."
"It's perhaps one of the best cookies I've ever tasted."
"Of course it is. It's the only cookie I'd ever tasted that was
made for me by a beutiful girl."
"I tell her this... that it's delicious, not the girl part."
"But I do let slip that if I could eat these every day, I'd be the
happiest guy in the world."
"At this, she can only blush."
return
label gg_confess:
"I once again barely make it to class on time."
"I sit down, at my desk, and put some of my books into it."
"My hand brushes a folded sheet of paper, one I didn't remember
putting in there."
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
"That's all it says... no signature or anything."
"Lunch is a few hours away, but the time passes like a blur."
"It's all I can do to avoid racing up to the roof... but I give it
some time anyway."
"It wouldn't make sense for me to get there first."
"I let two minutes elapse before leaving the classroom, and then
slowly walk the flights of stairs up to the roof."
"Standing there, I find the glasses girl."
"She's holding what looks like a homemade lunch... big enough for
two."
"I look at it, then her, then remember what I had said after she
made me the cookies."
"Finally, I ask her... \"Does this mean?\""
"She nods. It's all the confirmation I need."
"I sit down next to my new girlfriend... and together we start
eating her lunch."
"I'm probably the happiest guy in the world."
"But I still have to find out her name."
".:. Ending 1."
$ renpy.full_restart()
init:
$ event("catchme", "act == 'exercise'",
event.depends('introduction'), event.once())
$ event("cantcatchme", "act == 'exercise'",
event.depends('catchme'), event.solo(), priority=190)
$ event("caughtme", "act == 'exercise' and strength >= 50",
event.depends('catchme'), event.once())
$ event("together", "act == 'exercise' and strength >= 50",
event.depends('caughtme'), event.solo(), priority=180)
$ event("apart", "act == 'exercise' and strength < 50",
event.depends('caughtme'), event.solo(), priority=180)
$ event("pothole", "act == 'exercise' and strength >= 100",
event.depends('caughtme'), event.once())
$ event("dontsee", "act == 'exercise'",
event.depends('pothole'), event.solo(), priority=170)
$ event("sg_confess", "act == 'class'",
event.depends('dontsee'), event.once())
label catchme:
"I decide to go out for a run, to keep myself in shape."
"As I'm running through the town, I see a girl."
"She's one of the twins who transferred into my class."
"She waves, and comes over to me."
sg "I didn't know you were a runner."
"I point out that I'm not really a runner... I just run a little
bit at a time."
"I ask her if she wants to run with me for a while."
sg "As if! You couldn't keep up with me."
"I point out that I probably can."
sg "Well, let's see."
"We set off running, but she quickly pulls past me."
sg "See? Well, maybe we can try it again when you're a bit
faster."
sg "Until then, later."
"Even though I'm jogging, she pulls away as if it is nothing."
return
label cantcatchme:
"I'm out running again, when the sporty girl catches up to me."
sg "Still at it?"
sg "Well, keep up the good work. One day you'll be as fast as me!"
sg "Well, maybe."
"She pulls out past me, and disappears into the distance. One day
I'll catch up to her."
$ strength += 10
return
label caughtme:
"I'm out running again, when the sporty girl catches up to me."
sg "Still at it?"
sg "Well, keep up the good work. One day you'll be as fast as me!"
sg "Well, maybe."
"Today, however, I'm not about to let this stand unchallenged."
"I break out into a run, and for the first time ever, I keep up
with her."
"We both run, neck and neck, me keeping up with her."
"Finally, she starts slowing down, and we come to a stop
together."
sg "Not bad."
"She pauses to catch her breath."
sg "You've been practicing, and it really shows."
sg "You've finally become fast enough to run with me."
"I nod, accepting her praise."
sg "We should do this more often... it's better to run with
someone else, to keep the challenge up."
"I nod again."
sg "Well, shall we go?"
"I nod a third time, and we take off, running side by side."
$ strength += 10
return
label together:
"I start running, and meet up with the sporty girl as she passes
the street in front of my house."
"She's still better than me... she's been running for over a mile
before reaching this point."
"Still, I can keep up with her for the rest of the run. And that's
not bad."
$ strength += 10
return
label apart:
"I start running, and meet up with the sporty girl as she passes
the street in front of my house."
"I try to keep up with her, but she pulls away from me."
"When she's a block away, she slows down and lets me catch up."
sg "It's your own fault... this is what you get for not
practicing."
"She's right, of course, and I redouble my efforts to try to keep
up with her."
$ strength += 10
return
label pothole:
"I start running, and meet up with the sporty girl as she passes
the street in front of my house."
"We run together for several miles."
"I think about how much I've improved in our time together."
"And, although she probably won't admit it, I think she's improved
as well."
"I guess a little friendly competition is usually for the best."
"A small yelp pulls me out of my thought."
sg "Ow!"
"The sporty girl sits down, grabbing her ankle."
"I ask her what happened."
sg "I... hit a... pothole. Twisted my... ankle."
"I wince in sympathy."
"We wait a bit. I'm not sure what to do."
"Finally, I ask her if she can walk on it."
"She tries for a bit, but then winces in pain."
sg "No, I don't think so."
"I realize that we can't stay here."
"And so, I crouch down and motion for her to climb up onto my
back."
sg "What are you doing?"
"I explain that she can't stay out in the middle of the street
forever, and she won't get any help until I can get her home."
"And the only way to do that is for me to carry her."
"She accepts this, and climbs up onto my back."
"She wraps her arms around my neck, and I place my hands
underneath her to make a seat."
"I stand up, and start carrying her home."
return
label dontsee:
"I go running again."
"But this time, I don't see the sporty girl."
"I finish the course that we usually take, but it's not the same
without her."
return
label sg_confess:
"I once again barely make it to class on time."
"I sit down, at my desk, and put some of my books into it."
"My hand brushes a folded sheet of paper, one I didn't remember
putting in there."
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
"That's all it says... no signature or anything."
"Lunch is a few hours away, but the time passes like a blur."
"It's all I can do to avoid racing up to the roof... but I give it
some time anyway."
"It wouldn't make sense for me to get there first."
"I let two minutes elapse before leaving the classroom, and then
slowly walk the flights of stairs up to the roof."
"Standing there, I find the sporty girl."
"She's leaning on a crutch."
"I look at it for a second, and she notices that."
sg "I went to the doctor, and he gave me this."
sg "Looks like we won't be running together for a while."
"I nod."
sg "And that's why I asked you here."
sg "I couldn't stand the though of not seeing you for a few
weeks."
"I search my feelings, and realize I feel the same way."
sg "So I thought..."
"She doesn't say it... she doesn't need to."
"We both know how we feel about each other."
"And with that, we went from being running partners to partners in
a deeper sense."
"Now if I only knew her name..."
".:. Ending 2."
$ renpy.full_restart()
init:
# This needs to be higher-priority than either girl's ending.
$ event('both_confess', 'act == "class"',
event.depends("dontsee"), event.depends("cookies"),
event.once(), priority = 50)
label both_confess:
"I once again barely make it to class on time."
"I sit down, at my desk, and put some of my books into it."
"My hand brushes a folded sheet of paper, then another."
"I take the first one out, and read it."
"It's a girl's handwriting... \"Meet me on the roof at lunch.\""
"That's all it says... no signature or anything."
"I take a look at the second one, and it says the same thing."
"Sure, the handwriting is a little different, but..."
"Lunch is a few hours away, but the time passes like a blur."
"It's all I can do to avoid racing up to the roof... but I give it
some time anyway."
"I let two minutes elapse before leaving the classroom, and then
slowly walk the flights of stairs up to the roof."
"Standing there are the twins."
"Both of them."
"As in, the two notes came from the two twins."
"I ask them what they are doing there, feigning ignorance."
sg "Well, I invited you up here to confess to you..."
sg "... and then I found out that my sister here was about to do
the same thing."
gg "{size=-4}...I was...{/size}"
sg "When we found out, we were quite shocked, but after comparing
notes, we decide what we're going to do..."
"I quickly run through the possibilities in my head."
"The best cases involve them never talking to me again."
"The worst cases involve me being thrown off the roof."
bg "We're going to share you!"
"Eh?"
"That wasn't something I considered."
"Each of the girls grabs onto one of my arms."
"I don't know what the future holds for us..."
"... and I don't know if this will work out."
"But I do know that one day I will work up the courage to find out
their names."
".:. Ending 3."
$ renpy.full_restart()
+185
View File
@@ -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
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+8
View File
@@ -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"
Executable
+76
View File
@@ -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()
+1
View File
@@ -0,0 +1 @@
../../extras/fullscreen.rpy
+1
View File
@@ -0,0 +1 @@
../../extras/kanamode.rpy
+1
View File
@@ -0,0 +1 @@
../script.rpy
+102
View File
@@ -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
+16
View File
@@ -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
+35
View File
@@ -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)
+63
View File
@@ -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)
+40
View File
@@ -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)
+23
View File
@@ -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
+262
View File
@@ -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
+193
View File
@@ -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
+87
View File
@@ -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))
+227
View File
@@ -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
+74
View File
@@ -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()
+71
View File
@@ -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)
Symlink
+1
View File
@@ -0,0 +1 @@
demo2/
BIN
View File
Binary file not shown.
+134
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
console.exe --lint > lint.txt
start lint.txt
+17
View File
@@ -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
+14
View File
@@ -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
+45
View File
@@ -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/.
+6
View File
@@ -0,0 +1,6 @@
/* This file exists to adapt the location that pygame looks for SDL
* from into the location where SDL tends to exist on most actual
* systems.
*/
#include <SDL/SDL.h>
+568
View File
@@ -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);
}
+159
View File
@@ -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()
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
pyrexc _renpy.pyx && python setup.py build_ext -i
+608
View File
@@ -0,0 +1,608 @@
#include "renpy.h"
#include <pygame/pygame.h>
#include <stdio.h>
// Shows how to do this.
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#endif
/* Initializes the stuff found in this file.
*/
void core_init() {
import_pygame_base();
import_pygame_surface();
}
/* This pixellates a 32-bit RGBA pygame surface to a destination
* surface of a given size.
*
* pysrc - The source pygame surface, which must be 32-bit RGBA.
* pydst - The destination pygame surface, which should be 32-bit
* RGBA, and locked.
* avgwidth - The width of the pixels that will be averaged together.
* avgheight - The height of the pixels that will be averaged
* together.
* outwidth - The width of pixels that will be written to the output.
* outheight - The height of pixels that will be written to the
* output.
*
* We assume that pysrc and pydst have been locked before we are called.
*/
void pixellate32_core(PyObject *pysrc,
PyObject *pydst,
int avgwidth,
int avgheight,
int outwidth,
int outheight
) {
SDL_Surface *src;
SDL_Surface *dst;
int x, y, i, j;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
int vw, vh;
unsigned char *srcpixels;
unsigned char *dstpixels;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (unsigned char *) src->pixels;
dstpixels = (unsigned char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
/* Compute the virtual width and height. */
vw = ( srcw + avgwidth - 1) / avgwidth;
vh = ( srch + avgheight - 1) / avgheight;
/* Iterate through each of the virtual pixels. */
for (y = 0; y < vh; y++) {
int srcy = avgheight * y;
int dsty = outheight * y;
int srcylimit = srcy + avgheight;
int dstylimit = dsty + outheight;
if (srcylimit > srch) {
srcylimit = srch;
}
if (dstylimit > dsth) {
dstylimit = dsth;
}
for (x = 0; x < vw; x++) {
int srcx = avgwidth * x;
int dstx = outwidth * x;
int srcxlimit = srcx + avgwidth;
int dstxlimit = dstx + outheight;
if (srcxlimit > srcw) {
srcxlimit = srcw;
}
if (dstxlimit > dstw) {
dstxlimit = dstw;
}
// Please note that these names are just
// suggestions... It's possible that alpha will be
// in r, for example.
int r = 0;
int g = 0;
int b = 0;
int a = 0;
int number = 0;
// pos always points to the start of the current line.
unsigned char *pos = &srcpixels[srcy * srcpitch + srcx * 4];
/* Sum up the pixel values. */
for (j = srcy; j < srcylimit; j++) {
// po points to the current pixel.
unsigned char *po = pos;
for (i = srcx; i < srcxlimit; i++) {
r += *po++;
g += *po++;
b += *po++;
a += *po++;
number += 1;
}
pos += srcpitch;
}
/* Compute the average pixel values. */
r /= number;
g /= number;
b /= number;
a /= number;
/* Write out the average pixel values. */
pos = &dstpixels[dsty * dstpitch + dstx * 4];
for (j = dsty; j < dstylimit; j++) {
unsigned char *po = pos;
for (i = dstx; i < dstxlimit; i++) {
*po++ = r;
*po++ = g;
*po++ = b;
*po++ = a;
}
pos += dstpitch;
}
}
}
}
/* This pixellates a 32-bit RGBA pygame surface to a destination
* surface of a given size.
*
* pysrc - The source pygame surface, which must be 32-bit RGBA.
* pydst - The destination pygame surface, which should be 32-bit
* RGBA, and locked.
* avgwidth - The width of the pixels that will be averaged together.
* avgheight - The height of the pixels that will be averaged
* together.
* outwidth - The width of pixels that will be written to the output.
* outheight - The height of pixels that will be written to the
* output.
*
* We assume that pysrc and pydst have been locked before we are called.
*/
void pixellate24_core(PyObject *pysrc,
PyObject *pydst,
int avgwidth,
int avgheight,
int outwidth,
int outheight
) {
SDL_Surface *src;
SDL_Surface *dst;
int x, y, i, j;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
int vw, vh;
unsigned char *srcpixels;
unsigned char *dstpixels;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (unsigned char *) src->pixels;
dstpixels = (unsigned char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
/* Compute the virtual width and height. */
vw = ( srcw + avgwidth - 1) / avgwidth;
vh = ( srch + avgheight - 1) / avgheight;
/* Iterate through each of the virtual pixels. */
for (y = 0; y < vh; y++) {
int srcy = avgheight * y;
int dsty = outheight * y;
int srcylimit = srcy + avgheight;
int dstylimit = dsty + outheight;
if (srcylimit > srch) {
srcylimit = srch;
}
if (dstylimit > dsth) {
dstylimit = dsth;
}
for (x = 0; x < vw; x++) {
int srcx = avgwidth * x;
int dstx = outwidth * x;
int srcxlimit = srcx + avgwidth;
int dstxlimit = dstx + outheight;
if (srcxlimit > srcw) {
srcxlimit = srcw;
}
if (dstxlimit > dstw) {
dstxlimit = dstw;
}
// Please note that these names are just
// suggestions... It's possible that blue will be
// in r, for example.
int r = 0;
int g = 0;
int b = 0;
int number = 0;
// pos always points to the start of the current line.
unsigned char *pos = &srcpixels[srcy * srcpitch + srcx * 3];
/* Sum up the pixel values. */
for (j = srcy; j < srcylimit; j++) {
// po points to the current pixel.
unsigned char *po = pos;
for (i = srcx; i < srcxlimit; i++) {
r += *po++;
g += *po++;
b += *po++;
number += 1;
}
pos += srcpitch;
}
/* Compute the average pixel values. */
r /= number;
g /= number;
b /= number;
/* Write out the average pixel values. */
pos = &dstpixels[dsty * dstpitch + dstx * 3];
for (j = dsty; j < dstylimit; j++) {
unsigned char *po = pos;
for (i = dstx; i < dstxlimit; i++) {
*po++ = r;
*po++ = g;
*po++ = b;
}
pos += dstpitch;
}
}
}
}
/*
* This expects pysrc and pydst to be surfaces of the same size. It
* the source surface to the destination surface, using the r, g, b,
* and a maps. These maps are expected to be 256 bytes long, with each
* byte corresponding to a possible value of a channel in pysrc,
* giving what that value is mapped to in pydst.
*/
void map32_core(PyObject *pysrc,
PyObject *pydst,
char *rmap,
char *gmap,
char *bmap,
char *amap) {
SDL_Surface *src;
SDL_Surface *dst;
int x, y;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
char *srcpixels;
char *dstpixels;
char *srcrow;
char *dstrow;
char *srcp;
char *dstp;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (char *) src->pixels;
dstpixels = (char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
srcrow = srcpixels;
dstrow = dstpixels;
for (y = 0; y < srch; y++) {
srcp = srcrow;
dstp = dstrow;
for (x = 0; x < srcw; x++) {
*dstp++ = rmap[(unsigned char) *srcp++];
*dstp++ = gmap[(unsigned char) *srcp++];
*dstp++ = bmap[(unsigned char) *srcp++];
*dstp++ = amap[(unsigned char) *srcp++];
}
srcrow += srcpitch;
dstrow += dstpitch;
}
}
void map24_core(PyObject *pysrc,
PyObject *pydst,
char *rmap,
char *gmap,
char *bmap) {
SDL_Surface *src;
SDL_Surface *dst;
int x, y;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
char *srcpixels;
char *dstpixels;
char *srcrow;
char *dstrow;
char *srcp;
char *dstp;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (char *) src->pixels;
dstpixels = (char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
srcrow = srcpixels;
dstrow = dstpixels;
for (y = 0; y < srch; y++) {
srcp = srcrow;
dstp = dstrow;
for (x = 0; x < srcw; x++) {
*dstp++ = rmap[(unsigned char) *srcp++];
*dstp++ = gmap[(unsigned char) *srcp++];
*dstp++ = bmap[(unsigned char) *srcp++];
}
srcrow += srcpitch;
dstrow += dstpitch;
}
}
#if 0
void xblur32_core(PyObject *pysrc,
PyObject *pydst,
int radius) {
int i, x, y;
SDL_Surface *src;
SDL_Surface *dst;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
unsigned char *srcpixels;
unsigned char *dstpixels;
int count;
unsigned char *srcp;
unsigned char *dstp;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (unsigned char *) src->pixels;
dstpixels = (unsigned char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
int divisor = radius * 2 + 1;
for (y = 0; y < dsth; y++) {
// The values of the pixels on the left and right ends of the
// line.
unsigned char lr, lg, lb, la;
unsigned char rr, rg, rb, ra;
unsigned char *leader = srcpixels + y * srcpitch;
unsigned char *trailer = leader;
dstp = dstpixels + y * dstpitch;
lr = *leader;
lg = *(leader + 1);
lb = *(leader + 2);
la = *(leader + 3);
int sumr = lr * radius;
int sumg = lg * radius;
int sumb = lb * radius;
int suma = la * radius;
for (x = 0; x < radius + 0; x++) {
sumr += *leader++;
sumg += *leader++;
sumb += *leader++;
suma += *leader++;
}
// left side of the kernel is off of the screen.
for (x = 0; x < radius; x++) {
sumr += *leader++;
sumg += *leader++;
sumb += *leader++;
suma += *leader++;
*dstp++ = sumr / divisor;
*dstp++ = sumg / divisor;
*dstp++ = sumb / divisor;
*dstp++ = suma / divisor;
sumr -= lr;
sumg -= lg;
sumb -= lb;
suma -= la;
}
int end = srcw - radius - 1;
// The kernel is fully on the screen.
for (; x < end; x++) {
sumr += *leader++;
sumg += *leader++;
sumb += *leader++;
suma += *leader++;
*dstp++ = sumr / divisor;
*dstp++ = sumg / divisor;
*dstp++ = sumb / divisor;
*dstp++ = suma / divisor;
sumr -= *trailer++;
sumg -= *trailer++;
sumb -= *trailer++;
suma -= *trailer++;
}
rr = *leader++;
rg = *leader++;
rb = *leader++;
ra = *leader++;
// The kernel is off the right side of the screen.
for (; x < srcw; x++) {
sumr += rr;
sumg += rg;
sumb += rb;
suma += ra;
*dstp++ = sumr / divisor;
*dstp++ = sumg / divisor;
*dstp++ = sumb / divisor;
*dstp++ = suma / divisor;
sumr -= *trailer++;
sumg -= *trailer++;
sumb -= *trailer++;
suma -= *trailer++;
}
}
}
#endif
// Alpha Munge takes a channel from the source pixel, maps it, and
// sticks it into the alpha channel of the destination, overwriting
// the destination's alpha channel.
//
// It's used to implement SmartDissolve.
void alphamunge_core(PyObject *pysrc,
PyObject *pydst,
int src_bypp, // bytes per pixel.
int src_aoff, // alpha offset.
int dst_aoff, // alpha offset.
char *amap) {
int x, y;
SDL_Surface *src;
SDL_Surface *dst;
Uint32 srcpitch, dstpitch;
Uint32 srcw, srch;
Uint32 dstw, dsth;
unsigned char *srcpixels;
unsigned char *dstpixels;
unsigned char *srcline;
unsigned char *dstline;
unsigned char *srcp;
unsigned char *dstp;
src = PySurface_AsSurface(pysrc);
dst = PySurface_AsSurface(pydst);
srcpixels = (unsigned char *) src->pixels;
dstpixels = (unsigned char *) dst->pixels;
srcpitch = src->pitch;
dstpitch = dst->pitch;
srcw = src->w;
dstw = dst->w;
srch = src->h;
dsth = dst->h;
// We assume that src is bigger than dst, and so use dst
// to handle everything.
srcline = srcpixels;
dstline = dstpixels;
for (y = 0; y < dsth; y++) {
srcp = srcline + src_aoff;
dstp = dstline + dst_aoff;
for (x = 0; x < dstw; x++) {
*dstp = amap[*srcp];
srcp += src_bypp;
dstp += 4; // Need an alpha channel.
}
srcline += srcpitch;
dstline += dstpitch;
}
}
Binary file not shown.
Binary file not shown.
+53
View File
@@ -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
+55
View File
@@ -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()
+12
View File
@@ -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()
+11
View File
@@ -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()
+34
View File
@@ -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
Executable
+16
View File
@@ -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
+19 -5
View File
@@ -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.
+135 -75
View File
@@ -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.
+121 -12
View File
@@ -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)
+2 -2
View File
@@ -4,8 +4,8 @@ class Curry(object):
callable with the stored arguments and the additional arguments
supplied to the call.
"""
__doc__ = property(fget=lambda self : self.callable.__doc__)
# __doc__ = property(fget=lambda self : self.callable.__doc__)
def __init__(self, callable, *args, **kwargs):
self.callable = callable
+290
View File
@@ -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)
+613
View File
@@ -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)
+275 -257
View File
@@ -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)
+536 -292
View File
File diff suppressed because it is too large Load Diff
+355
View File
@@ -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)

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