Compare commits
55 Commits
6.99
..
renpy-4.8.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ffbaa8fb9 | |||
| ab5d5c0e40 | |||
| c28639ef0b | |||
| a3b3d26816 | |||
| ee6bbf5d9c | |||
| 6e406cdae0 | |||
| 8ef2d97247 | |||
| e9be405aaf | |||
| 23dbab98d2 | |||
| 2865599e34 | |||
| fb5957186a | |||
| 8d2e592589 | |||
| c7714a69fd | |||
| 6d2dce1704 | |||
| 8ab32e8dba | |||
| 00ee770512 | |||
| c5761728b3 | |||
| d33aa31101 | |||
| 34cfc4fa40 | |||
| d603d62680 | |||
| 4991c5761c | |||
| cf3f8afd9f | |||
| 31fc8b213e | |||
| 74be98faba | |||
| b4e6b24a86 | |||
| 7c1980775e | |||
| 90d586968e | |||
| af0778b8bc | |||
| 23bccf06cb | |||
| 7558b20b1e | |||
| 8fbb785674 | |||
| d2210cf08f | |||
| 8d8ba4fb00 | |||
| 915df21775 | |||
| 59c7baf1ff | |||
| dc0d2f8039 | |||
| bcf752d3a6 | |||
| d95cd1f6a0 | |||
| c9f025dab2 | |||
| d484cc2906 | |||
| 18cf867432 | |||
| 149a2904a5 | |||
| ef2e78616f | |||
| 2711f79080 | |||
| fa31e03d06 | |||
| 6a27954677 | |||
| eab1edd5b4 | |||
| b9624fe922 | |||
| 69727f3baf | |||
| 0551eb6135 | |||
| c0b8ca8777 | |||
| 7af2e3d066 | |||
| 0a857f8879 | |||
| 839073ad8c | |||
| eb22c24884 |
@@ -1,54 +0,0 @@
|
||||
*.rpyc
|
||||
*.rpyb
|
||||
*.rpymc
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
*~
|
||||
*.bak
|
||||
|
||||
saves
|
||||
tmp
|
||||
cache
|
||||
|
||||
log.txt
|
||||
/errors.txt
|
||||
/traceback.txt
|
||||
|
||||
styles.txt
|
||||
|
||||
/android
|
||||
/build
|
||||
/dist
|
||||
/dists
|
||||
/renpy.app
|
||||
/jedit
|
||||
/lint.txt
|
||||
/renpy.code
|
||||
/*testing*
|
||||
/screenshot*
|
||||
/renpy.exe
|
||||
/lib
|
||||
/lib.old
|
||||
/doc
|
||||
/.pydevproject
|
||||
/.pydevproject.bak
|
||||
/.project
|
||||
/.settings
|
||||
/LICENSE.txt
|
||||
/templates/english/README.html
|
||||
/the_question/README.html
|
||||
/tutorial/README.html
|
||||
/renpy/angle/*.pyx
|
||||
/renpy/angle/*.pxd
|
||||
/renpy-ppc.zip
|
||||
/module/build
|
||||
/module/gen
|
||||
/editra
|
||||
/launcher/game/theme
|
||||
/launcher/game/script_version.rpy
|
||||
|
||||
dl
|
||||
renpy/vc_version.py
|
||||
.externalToolBuilders
|
||||
rapt
|
||||
@@ -0,0 +1,906 @@
|
||||
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
|
||||
----------
|
||||
|
||||
Ren'Py now ships with a common directory that is used to store the
|
||||
library files, as well as the files needed by a minimal game. (Such as
|
||||
a default font.) The idea here is to allow one to copy a game
|
||||
directory from one version of Ren'Py to another, and have it just
|
||||
work.
|
||||
|
||||
One can upgrade from 4.1 to 4.2 by deleting script.rpy, script.rpyc,
|
||||
library.rpy, and library.rpyc, and then copying in the game
|
||||
directory.
|
||||
|
||||
We've also eliminated the default background images. To go back to the
|
||||
images used with Ren'Py 4.1, add the following lines to your script,
|
||||
and copy the images out of the 4.1 distribution, or your 4.1 based
|
||||
game.
|
||||
|
||||
[code]
|
||||
init:
|
||||
$ style.mm_root_window.background = Image("mainmenu.jpg")
|
||||
$ style.gm_root_window.background = Image("gamemenu.jpg")
|
||||
$ 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)
|
||||
|
||||
Added a second parameter to renpy.pause, that allows us to pause until
|
||||
a particular offset in the background music is reached. (suggested by
|
||||
Alessio)
|
||||
|
||||
Added keyboard mouse controls that can be used to move the mouse
|
||||
around on the game and main menus. This allows us to manipulate the
|
||||
game and main menus without having to touch the mouse. This is mostly
|
||||
for completeness. (suggested by Alessio)
|
||||
|
||||
Changed the way in which overlays work. We now have the variable
|
||||
config.overlay_functions, which contains a list of functions, each of
|
||||
which returns a list of displayables that will be added to the
|
||||
screen. (sorta-kinda suggested by Alessio)
|
||||
|
||||
Removed renpy.set_overlay, since config.overlay_functions is a more
|
||||
flexible way of doing the same thing.
|
||||
|
||||
Now, defining the label main_menu allows you to totally replace the
|
||||
main menu with something of your own devising. Changed the
|
||||
documentation to reflect this, as well as the changes in startup that
|
||||
happened in 4.1.
|
||||
|
||||
Added a program called "add_from", which adds from clauses to all bare
|
||||
call statements found in the program.
|
||||
|
||||
Replaced the menu_selected and menu_unselected styles with a single
|
||||
menu_choice style which participates in the hover protocol. This may
|
||||
change user code, if you change the menu colors. To fix this, replace
|
||||
style.menu_selected.color with style.menu_choice.hover_color, and
|
||||
style.menu_unselected.color with style.menu_choice.idle_color.
|
||||
|
||||
Added the ability to play sound effects, by calling the renpy.play
|
||||
function. Added a preference that allows the user to control if
|
||||
sound effects are played or not.
|
||||
|
||||
Added sound styles, which are used to define the sounds that are
|
||||
played when buttons, imagemaps, and menu choices are hovered and
|
||||
activated. (By default, no such sounds are played.)
|
||||
|
||||
Implemented that annoying thing where text is typed on the screen one
|
||||
character at a time. Also added a preference that allows the user to
|
||||
disable it. Used all the restraint I could muster to avoid defaulting
|
||||
that preference to off.
|
||||
|
||||
Added the ability to fade out music when changing music
|
||||
tracks. This is controlled by the config.fade_music variable.
|
||||
(suggested by Alessio)
|
||||
|
||||
Added parameterized images, and the ability to show text as image
|
||||
using a command like:
|
||||
|
||||
[code]
|
||||
show text "American Bishoujo Presents..."
|
||||
[/code]
|
||||
|
||||
Added a Move function, which can be used in at to allow an image to be
|
||||
moved on the screen. This is best used when the image is smaller than
|
||||
the screen. (suggested by Alessio)
|
||||
|
||||
Changed the format of archive files, to make them somewhat harder to
|
||||
reverse-engineer. This requires the user to regenerate the archive
|
||||
files. To make this easier, we now ship a batch file (called
|
||||
archive_images.bat) that automatically archives the images found in
|
||||
the game directory.
|
||||
|
||||
Added a new function renpy.wait that is equivalent to the wait
|
||||
statement, and made it and renpy.pause return True if they are
|
||||
interrupted, or False if the run to their natural completions. This
|
||||
now makes it possible to jump somewhere else when the user click,
|
||||
rather than blindly going to the next label. For example:
|
||||
|
||||
[code]
|
||||
# Actually, this runs before the library main menu.
|
||||
label main_menu:
|
||||
scene black
|
||||
|
||||
show text "American Bishoujo\nPresents" \
|
||||
at Move((0.5, 0.0), (0.5, 0.5), 3.0,
|
||||
xanchor='center', yanchor='bottom')
|
||||
|
||||
if renpy.pause(6):
|
||||
jump _library_main_menu
|
||||
|
||||
show text "A PyTom Game" at Position(xpos=0.5, ypos=0.5,
|
||||
xanchor="center", yanchor="bottom")
|
||||
|
||||
if renpy.with(fade) or renpy.pause(4):
|
||||
jump _library_main_menu
|
||||
|
||||
$ renpy.transition(fade)
|
||||
jump _library_main_menu
|
||||
[code]
|
||||
|
||||
|
||||
|
||||
Updated renpy-mode.el to add imenu support, so (X)Emacs users can use
|
||||
the speedbar to jump around in a script. If you don't know what this
|
||||
means, you probably don't care.
|
||||
|
||||
Improved the comments in the demo script, to make it a better
|
||||
example. Added a syntax-highlighted version of the demo script to the
|
||||
tutorial, to make it easier on peoples eyes.
|
||||
|
||||
|
||||
|
||||
New in 4.1
|
||||
----------
|
||||
|
||||
Added the ability to customize the main menu by giving a list of
|
||||
buttons on the main menu and the labels that they jump the user to.
|
||||
|
||||
Added the ability to center-click to hide any displayed text or menus.
|
||||
|
||||
Added the ability to have a color mouse that we can move around the
|
||||
screen.
|
||||
|
||||
Added a persistent object, which has fields that are persistent across
|
||||
games. Made the preferences part of this object, so that they are
|
||||
persisted across sessions. For simplicity's sake, made seen_ever part
|
||||
of that persistent object.
|
||||
|
||||
Added a special character called "centered", which causes the text it
|
||||
displays to be centered in the screen without any window. Added two
|
||||
new styles (centered_window and centered_text), which are used for
|
||||
this, and a new property, textalign, which controls the horizontal
|
||||
alignment of text within the Text object itself.
|
||||
|
||||
Keywords are now special in all contexts. So you can no longer include
|
||||
keywords in image names. Sorry.
|
||||
|
||||
Improved parse error messages. They now include the text of the
|
||||
line and a caret indication the location in the line where the parse
|
||||
error occurred.
|
||||
|
||||
Added a with clause to say statements and menus. This lets one display
|
||||
dialogue, thoughts, or menus using a transition. In conjunction with
|
||||
this, changed a bit the semantics of with statements, and with clauses
|
||||
on show, hide, and scene statements. Rewrote the section on
|
||||
transitions in the documentation to reflect the new reality.
|
||||
|
||||
Added the ability to translate the text of the game menu. Together
|
||||
with the ability to change the main menu, this makes Ren'Py fully
|
||||
localizable, except for error messages. Check out the Localization
|
||||
section in the tutorial for more details.
|
||||
|
||||
Added confirmations for quitting and overwriting a save game. This
|
||||
also includes quitting by trying to close the window.
|
||||
|
||||
Added a preferences screen, which lets users change the Ren'Py
|
||||
preferences. Right now, this includes Windowed/Fullscreen, Music
|
||||
Enabled/Disabled, CTRL Skips Unread/All messages, and Transitions
|
||||
controls which transitions are performed.
|
||||
|
||||
Added a Pan function which can be used in an at clause. This allows us
|
||||
to pan over a background image.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
Copyright 2004-2005 PyTom <pytom@bishoujo.us>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Please note that the above license only applies to Ren'Py
|
||||
proper. A binary distribution of Ren'Py contains a number of other
|
||||
packages, each with their own licenses, which you may be bound to
|
||||
comply with when distributing a Ren'Py game.
|
||||
|
||||
These packages may include:
|
||||
|
||||
Python (Python License)
|
||||
http://www.python.org
|
||||
|
||||
Pygame (GNU LGPL)
|
||||
http://www.pygame.org
|
||||
|
||||
SDL (GNU LGPL)
|
||||
http://www.libsdl.org
|
||||
|
||||
SDL_mixer (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_mixer
|
||||
|
||||
SDL_ttf (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_ttf
|
||||
|
||||
ctypes (MIT)
|
||||
http://starship.python.net/crew/theller/ctypes/
|
||||
@@ -1,150 +0,0 @@
|
||||
==============================
|
||||
The Ren'Py Visual Novel Engine
|
||||
==============================
|
||||
|
||||
http://www.renpy.org
|
||||
|
||||
Ren'Py development takes place on the ``master`` branch, and occasionally
|
||||
on feature branches.
|
||||
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
Ren'Py depends on a number of python modules written in Cython and C. For
|
||||
changes to Ren'Py that only involve python modules, you can use the modules
|
||||
found in the latest nightly build. Otherwise, you'll have to compile the
|
||||
modules yourself.
|
||||
|
||||
The development scripts assume a POSIX-like platform. The scripts should run
|
||||
on Linux or Mac OS X, and can be made to run on Windows using an environment
|
||||
like Msys.
|
||||
|
||||
Nightly Build
|
||||
-------------
|
||||
|
||||
Nightly builds can be downloaded from:
|
||||
|
||||
http://nightly.renpy.org
|
||||
|
||||
Note that the latest nightly build is at the bottom of the list. Once you've
|
||||
unpacked the nightly, change into this repository, and run::
|
||||
|
||||
./after_checkout.sh <path-to-nightly>
|
||||
|
||||
Once this script completes, you should be able to run Ren'Py using renpy.sh,
|
||||
renpy.app, or renpy.exe, as appropriate for your platform.
|
||||
|
||||
If the current nightly build doesn't work, please wait 24 hours for a new
|
||||
build to occur. If that build still doesn't work, contact Tom (`pytom at bishoujo.us`,
|
||||
or @renpytom on twitter) to find out what's wrong.
|
||||
|
||||
The ``doc`` symlink will dangle until documentation is built, as described
|
||||
below.
|
||||
|
||||
Compiling the Modules
|
||||
----------------------
|
||||
|
||||
Building the modules requires you have the many dependencies installed on
|
||||
your system. On Ubuntu and Debian, these dependencies can be installed with
|
||||
the command::
|
||||
|
||||
apt-get install python-dev python-pygame libavcodec-dev libavformat-dev \
|
||||
libavresample-dev libfreetype6-dev libglew1.6-dev libsdl1.2-dev \
|
||||
libsdl-image1.2-dev libfribidi-dev libswscale-dev libesd0-dev libpulse-dev
|
||||
|
||||
Other platforms may have an equivalent command. Otherwise, you'll need to
|
||||
build `renpy-deps <https://github.com/renpy/renpy-deps>`_.
|
||||
|
||||
We strongly suggest installing the Ren'Py modules into a Python
|
||||
virtualenv. `This page <http://dabapps.com/blog/introduction-to-pip-and-virtualenv-python/>`_
|
||||
describes how to install pip and the virtualenv tool, and set up a new virtualenv.
|
||||
|
||||
After activating the virtualenv, install cython::
|
||||
|
||||
pip install -U cython
|
||||
|
||||
Next, set RENPY_DEPS_INSTALL To a \::-separated list of paths containing the
|
||||
dependencies, and RENPY_CYTHON to the name of the cython command::
|
||||
|
||||
export RENPY_DEPS_INSTALL=/usr::/usr/lib/x86_64-linux-gnu/
|
||||
export RENPY_CYTHON=cython
|
||||
|
||||
Finally, change into the `modules` directory, and run::
|
||||
|
||||
python setup.py install
|
||||
|
||||
Ren'Py will be installed into the activated virtualenv. It can then be run
|
||||
using the command::
|
||||
|
||||
python -O renpy.py
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Building the documentation requires Ren'Py to work. You'll either need to
|
||||
link in a nightly build, or compile the modules as described above. You'll
|
||||
also need the `Sphinx <http://sphinx-doc.org/>`_ documentation generator.
|
||||
If you have pip working, install Sphinx using::
|
||||
|
||||
pip install -U sphinx
|
||||
|
||||
Once Sphinx is installed, change into the ``sphinx`` directory inside the
|
||||
Ren'Py checkout and run::
|
||||
|
||||
./build.sh
|
||||
|
||||
Format
|
||||
------
|
||||
|
||||
Ren'Py's documentation consists of reStructuredText files found in sphinx/source, and
|
||||
generated documentation found in function docstrings scattered throughout the code. Do
|
||||
not edit the files in sphinx/source/inc directly, as they will be overwritten.
|
||||
|
||||
Docstrings may include tags on the first few lines:
|
||||
|
||||
\:doc: `section` `kind`
|
||||
Indicates that this functions should be documented. `section` gives
|
||||
the name of the include file the function will be documented in, while
|
||||
`kind` indicates the kind of object to be documented (one of ``function``,
|
||||
``method`` or ``class``. If omitted, `kind` will be auto-detected.
|
||||
\:name: `name`
|
||||
The name of the function to be documented. Function names are usually
|
||||
detected, so this is only necessary when a function has multiple aliases.
|
||||
\:args: `args`
|
||||
This overrides the detected argument list. It can be used if some arguments
|
||||
to the function are deprecated.
|
||||
|
||||
For example::
|
||||
|
||||
def warp_speed(factor, transwarp=False):
|
||||
"""
|
||||
:doc: warp
|
||||
:name: renpy.warp_speed
|
||||
:args: (factor)
|
||||
|
||||
Exceeds the speed of light.
|
||||
"""
|
||||
|
||||
renpy.engine.warp_drive.engage(factor)
|
||||
|
||||
|
||||
Translating
|
||||
===========
|
||||
|
||||
For best practices when it comes to translating the launcher and template
|
||||
game, please read:
|
||||
|
||||
http://lemmasoft.renai.us/forums/viewtopic.php?p=321603#p321603
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
For bug fixes, documentation improvements, and simple changes, just
|
||||
make a pull request. For more complex changes, it might make sense
|
||||
to file an issue first so we can discuss the design.
|
||||
@@ -0,0 +1,55 @@
|
||||
This file contains boilerplate information about running Ren'Py that
|
||||
can be included into the README for your game. It may be necessary to
|
||||
change some of the filenames in this document, to make it specific to
|
||||
your game.
|
||||
|
||||
Feel free to use this in your projects.
|
||||
|
||||
|
||||
|
||||
Running the Game
|
||||
================
|
||||
|
||||
(Windows)
|
||||
|
||||
If this game was installed on Windows using an installer, then you can
|
||||
run it by choosing the shortcut left by the installer. If this game
|
||||
was installed on Windows using the cross-platform zip file, then it
|
||||
can be run by executing the run_game.exe file.
|
||||
|
||||
(Macintosh)
|
||||
|
||||
For information about running Ren'Py games on Mac OS 10.3 or higher,
|
||||
please go to http://www.bishoujo.us/renpy/mac.html .
|
||||
|
||||
(Linux/Unix)
|
||||
|
||||
For information about running Ren'Py games under Linux and Unix,
|
||||
please go to http://www.bishoujo.us/renpy/linux.html . At the very
|
||||
least, you will need to compile the _renpy module. Read
|
||||
module/README.txt for details.
|
||||
|
||||
|
||||
Playing the Game
|
||||
================
|
||||
|
||||
By default, the game starts running in full screen mode. On some
|
||||
computers, especially some virtual machines, this can lead to mouse
|
||||
problems. To fix this, press 'f'.
|
||||
|
||||
Some of the more interesting game actions can be performed as follows:
|
||||
|
||||
- The left mouse button is used to advance to the next line of
|
||||
dialogue, or to pick menu options.
|
||||
|
||||
- The right mouse button brings you into a menu screen where you can
|
||||
save the game, load the game, change preferences, return to the main
|
||||
menu, or quit entirely.
|
||||
|
||||
- Scrolling the mouse wheel up or pushing page up returns you to the
|
||||
previous screen.
|
||||
|
||||
- Holding down the CTRL key skips dialogue. By default, it only
|
||||
skips read dialogue, but this can be changed by a preference. Tab
|
||||
toggles skip mode.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Have a new form of imagemap that uses a color image to distinguish
|
||||
hotspots.
|
||||
|
||||
Keyd compiles.
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from renpy import version_tuple #@UnresolvedImport
|
||||
|
||||
version = ".".join(str(i) for i in version_tuple)
|
||||
short_version = ".".join(str(i) for i in version_tuple[:3])
|
||||
print "Version", version
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
|
||||
ap.add_argument("--release", action="store_true")
|
||||
ap.add_argument("--prerelease", action="store_true")
|
||||
ap.add_argument("--experimental", action="store_true")
|
||||
ap.add_argument("--no-tag", "-n", action="store_true")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.release:
|
||||
links = [ "release", "prerelease", "experimental" ]
|
||||
tag = True
|
||||
elif args.prerelease:
|
||||
links = [ "prerelease", "experimental" ]
|
||||
tag = True
|
||||
elif args.experimental:
|
||||
links = [ "experimental" ]
|
||||
tag = False
|
||||
else:
|
||||
links = [ ]
|
||||
tag = False
|
||||
|
||||
|
||||
def check_dirty():
|
||||
if args.no_tag:
|
||||
return
|
||||
|
||||
if subprocess.check_call([ "git", "diff", "--quiet", "HEAD" ]):
|
||||
print "Directory not checked in: {}".format(os.getcwd())
|
||||
sys.exit(1)
|
||||
|
||||
os.chdir("/home/tom/ab/renpy")
|
||||
check_dirty()
|
||||
|
||||
os.chdir("/home/tom/ab/renpy/android")
|
||||
check_dirty()
|
||||
|
||||
if not args.no_tag:
|
||||
subprocess.check_call([ "git", "tag", "-a", "rapt-" + version, "-m", "Tagging RAPT release." ])
|
||||
|
||||
os.chdir("/home/tom/ab/renpy/dl")
|
||||
|
||||
for i in links:
|
||||
if os.path.exists(i):
|
||||
os.unlink(i)
|
||||
os.symlink(short_version, i)
|
||||
|
||||
os.chdir("/home/tom/ab/renpy")
|
||||
|
||||
if tag and not args.no_tag:
|
||||
cmd = [ "git", "tag", "-a", version, "-m", "Ren'Py " + version ]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
os.chdir("/home/tom/ab/website")
|
||||
subprocess.check_call("./upload.sh")
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This program adds froms to every unqualified call found in the game
|
||||
# directory. It's not perfect, but it's better than nothing.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
def find_labels(fn, labels):
|
||||
|
||||
f = file(fn, "r")
|
||||
|
||||
for l in f:
|
||||
|
||||
m = re.match(r'^\s*call\s+\w+\s+from\s+(\w+)\s*$', l)
|
||||
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):
|
||||
|
||||
f = file(fn, "r")
|
||||
of = file(fn + ".new", "w")
|
||||
|
||||
|
||||
def replaceit(m):
|
||||
target = m.group(2)
|
||||
|
||||
num = 0
|
||||
|
||||
while True:
|
||||
num += 1
|
||||
label = "_call_%s_%d" % (target, num)
|
||||
|
||||
if label not in labels:
|
||||
break
|
||||
|
||||
labels[label] = True
|
||||
|
||||
return m.group(1) + "call " + target + " from " + label + m.group(3)
|
||||
|
||||
for l in f:
|
||||
l = re.sub(r'^(\s*)call\s+(\w+)(\s*$)', replaceit, l)
|
||||
|
||||
if re.search(r'call\s+expression', l) and not re.search('from', l):
|
||||
|
||||
num = 0
|
||||
|
||||
while True:
|
||||
num += 1
|
||||
label = "_call_expression_%d" % num
|
||||
|
||||
if label not in labels:
|
||||
break
|
||||
|
||||
labels[label] = label
|
||||
|
||||
l = l[:-1] + " from " + label + "\n"
|
||||
|
||||
of.write(l)
|
||||
|
||||
f.close()
|
||||
of.close()
|
||||
|
||||
|
||||
try:
|
||||
os.unlink(fn + ".bak")
|
||||
except:
|
||||
pass
|
||||
|
||||
os.rename(fn, fn + ".bak")
|
||||
os.rename(fn + ".new", fn)
|
||||
|
||||
def main():
|
||||
|
||||
pattern = "*/*.rpy"
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
pattern = sys.argv[1]
|
||||
|
||||
files = glob.glob(pattern)
|
||||
files = [ i for i in files if not i.startswith("common/") ]
|
||||
|
||||
labels = { }
|
||||
|
||||
for fn in files:
|
||||
print "Finding labels in", fn
|
||||
find_labels(fn, labels)
|
||||
|
||||
for fn in files:
|
||||
print "Replacing labels in", fn
|
||||
replace_labels(fn, labels)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
ROOT="$(dirname $(python -c "import os;print(os.path.realpath('$0'))"))"
|
||||
|
||||
ln -s "$ROOT/help.html" "$ROOT/tutorial/README.html"
|
||||
ln -s "$ROOT/help.html" "$ROOT/the_question/README.html"
|
||||
ln -s "$ROOT/help.html" "$ROOT/templates/english/README.html"
|
||||
|
||||
ln -s "$ROOT/sphinx/source/license.rst" "$ROOT/LICENSE.txt"
|
||||
|
||||
if [ "$1" != "" ]; then
|
||||
ln -s "$1/lib" "$ROOT/lib"
|
||||
ln -s "$1/renpy.app" "$ROOT"
|
||||
ln -s "$1/renpy.exe" "$ROOT"
|
||||
fi
|
||||
@@ -1,10 +0,0 @@
|
||||
# This file sets up the normal python modules. It's used to help eclipse
|
||||
# do type detection, by providing an importable version of the cython code.
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
packages=['renpy', 'renpy.gl', 'renpy.angle', 'renpy.display', 'renpy.audio', 'renpy.text', 'pysdlsound'],
|
||||
package_dir={ 'pysdlsound' : 'module' },
|
||||
)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
cd game
|
||||
..\archiver.exe images *.png *.jpg
|
||||
pause
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# The Ren'Py archiver. This builds a Ren'Py archive file, and the
|
||||
# associated index file. These files are really easy to reverse-engineer,
|
||||
# but are probably better than nothing.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import encodings.zlib_codec
|
||||
import random
|
||||
import glob
|
||||
|
||||
from cPickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
|
||||
# The most we will go without inserting some padding. 10k.
|
||||
padding_every = 10240
|
||||
|
||||
# The amount of padding we will add.
|
||||
padding_max = 32
|
||||
|
||||
def randpadding():
|
||||
|
||||
plen = random.randint(1, padding_max)
|
||||
|
||||
rv = ""
|
||||
|
||||
for i in range(0, plen):
|
||||
rv += chr(random.randint(1, 255))
|
||||
|
||||
return rv
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print "Usage: %s <file-prefix> <files ...>" % sys.argv[0]
|
||||
return
|
||||
|
||||
prefix = sys.argv[1]
|
||||
|
||||
# Archive file.
|
||||
archivef = file(prefix + ".rpa", "wb")
|
||||
|
||||
# Index file.
|
||||
indexf = file(prefix + ".rpi", "wb")
|
||||
|
||||
index = { }
|
||||
|
||||
random.seed()
|
||||
|
||||
offset = 0
|
||||
|
||||
# Needed because windows sucks. It doesn't do globbing on the
|
||||
# command line.
|
||||
files = [ ]
|
||||
for i in sys.argv[2:]:
|
||||
files.extend(glob.glob(i))
|
||||
|
||||
for fn in files:
|
||||
index[fn] = [ ]
|
||||
|
||||
print "Adding %s..." % fn
|
||||
|
||||
datafile = file(fn, "rb")
|
||||
|
||||
while True:
|
||||
|
||||
# Pad with junk.
|
||||
padding = randpadding()
|
||||
archivef.write(padding)
|
||||
offset += len(padding)
|
||||
|
||||
# Pick a random block size.
|
||||
block = random.randint(1, padding_every)
|
||||
|
||||
data = datafile.read(block)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
dlen = len(data)
|
||||
|
||||
archivef.write(data)
|
||||
|
||||
index[fn].append((offset, dlen))
|
||||
offset += dlen
|
||||
|
||||
datafile.close()
|
||||
|
||||
archivef.close()
|
||||
|
||||
indexf.write(dumps(index, HIGHEST_PROTOCOL).encode("zlib"))
|
||||
indexf.close()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
build_exe.py run_game
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print "Usage: build_exe.py <prefix>"
|
||||
print ""
|
||||
print "Builds <prefix>.exe."
|
||||
print "Expects icons in <prefix>.ico."
|
||||
|
||||
sys.exit(-1)
|
||||
|
||||
def program(name):
|
||||
return dict(script="run_game.py",
|
||||
icon_resources=[ (0, name + ".ico"),
|
||||
],
|
||||
dest_base=name)
|
||||
|
||||
programs = [
|
||||
program(sys.argv[1]),
|
||||
]
|
||||
|
||||
sys.argv[1:] = [ 'py2exe' ]
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=programs,
|
||||
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py" ],
|
||||
zipfile='lib/renpy.zip',
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
This package was debianized by Michael Fedrowitz <michaelf@debian.org>
|
||||
on Sun, 23 Feb 2003.
|
||||
|
||||
It was downloaded from
|
||||
'http://ftp.gnome.org/pub/GNOME/sources/ttf-bitstream-vera/'.
|
||||
|
||||
Upstream Author: Bitstream, Inc.
|
||||
|
||||
Copyright:
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
|
||||
Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute
|
||||
the Font Software, including without limitation the rights to use,
|
||||
copy, merge, publish, distribute, and/or sell copies of the Font
|
||||
Software, and to permit persons to whom the Font Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
|
||||
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
|
||||
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font
|
||||
Software without prior written authorization from the Gnome Foundation
|
||||
or Bitstream Inc., respectively. For further information, contact:
|
||||
fonts at gnome dot org.
|
||||
@@ -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
|
||||
|
||||
if library.enter_transition:
|
||||
$ renpy.transition(library.enter_transition)
|
||||
|
||||
if renpy.has_label("enter_game_menu"):
|
||||
call enter_game_menu
|
||||
|
||||
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
|
||||
jump _save_screen
|
||||
|
||||
label _game_menu_load:
|
||||
call _enter_game_menu
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu_preferences:
|
||||
call _enter_game_menu
|
||||
jump _prefs_screen
|
||||
|
||||
label _confirm_quit:
|
||||
call _enter_menu
|
||||
$ _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
|
||||
@@ -0,0 +1,200 @@
|
||||
# This file consists of renpy functions that aren't expected to be
|
||||
# touched by the user too much. We reserve the _ prefix for names
|
||||
# defined in the library.
|
||||
|
||||
# It's strongly reccomended that you don't edit this file, as future
|
||||
# releases of Ren'Py will probably change this file to include more
|
||||
# functionality.
|
||||
|
||||
# It's also strongly recommended that you leave this file in the
|
||||
# game directory, so its functionality is included in your game.
|
||||
|
||||
|
||||
|
||||
init -500:
|
||||
python:
|
||||
|
||||
# These are settings that the user can tweak to control the
|
||||
# look of the main menu and the load/save/escape screens.
|
||||
|
||||
# Used to store library settings.
|
||||
library = object()
|
||||
|
||||
# The minimum version of the module we work with. Don't change
|
||||
# this unless you know what you're doing.
|
||||
library.module_version = 4008002
|
||||
|
||||
# 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 = ''
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
if s in library.translations:
|
||||
return library.translations[s]
|
||||
else:
|
||||
return s
|
||||
|
||||
# Are the windows currently hidden?
|
||||
_windows_hidden = False
|
||||
|
||||
# Set up the default keymap.
|
||||
python hide:
|
||||
|
||||
# Called to make a screenshot happen.
|
||||
def screenshot():
|
||||
renpy.screenshot("screenshot.bmp")
|
||||
|
||||
def invoke_game_menu():
|
||||
renpy.play(library.enter_sound)
|
||||
renpy.call_in_new_context('_game_menu')
|
||||
|
||||
def toggle_skipping():
|
||||
config.skipping = not config.skipping
|
||||
|
||||
# The default keymap.
|
||||
km = renpy.Keymap(
|
||||
rollback = renpy.rollback,
|
||||
screenshot = screenshot,
|
||||
toggle_fullscreen = renpy.toggle_fullscreen,
|
||||
toggle_music = renpy.toggle_music,
|
||||
toggle_skip = toggle_skipping,
|
||||
game_menu = invoke_game_menu,
|
||||
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
|
||||
)
|
||||
|
||||
config.underlay = [ km ]
|
||||
|
||||
|
||||
# The skip indicator.
|
||||
python hide:
|
||||
|
||||
def skip_indicator():
|
||||
|
||||
if config.allow_skipping and library.skip_indicator:
|
||||
|
||||
ui.conditional("config.skipping")
|
||||
ui.text(_("Skip Mode"), style='skip_indicator')
|
||||
|
||||
config.overlay_functions.append(skip_indicator)
|
||||
|
||||
return
|
||||
|
||||
label _hide_windows:
|
||||
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
python:
|
||||
_windows_hidden = True
|
||||
ui.saybehavior()
|
||||
ui.interact(suppress_overlay=True)
|
||||
_windows_hidden = False
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Code for the game menu.
|
||||
|
||||
|
||||
|
||||
# This code here handles check for the correct version of the Ren'Py module.
|
||||
|
||||
label _check_module:
|
||||
|
||||
if not library.module_warning:
|
||||
return
|
||||
|
||||
python hide:
|
||||
module_info = _("While Ren'Py games are playable without the _renpy module, some features may be disabled. For more information, read the module/README.txt file or go to http://www.bishoujo.us/renpy/.")
|
||||
|
||||
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:
|
||||
$ centered = Character(None, what_style="centered_text", window_style="centered_window")
|
||||
image text = renpy.ParameterizedText(style="centered_text")
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# 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("_preferences_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
|
||||
|
||||
if renpy.has_label("splashscreen") and not _restart:
|
||||
call splashscreen
|
||||
|
||||
$ 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
|
||||
|
||||
$ 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 main_menu
|
||||
|
||||
# This is the default main menu, which we get if the user hasn't
|
||||
# defined his own, or if that function calls this explicitly.
|
||||
label _library_main_menu:
|
||||
|
||||
scene
|
||||
|
||||
python hide:
|
||||
|
||||
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='mm_root_window')
|
||||
ui.fixed()
|
||||
|
||||
ui.window(style='mm_menu_window')
|
||||
ui.vbox()
|
||||
|
||||
for text, clicked in library.main_menu:
|
||||
|
||||
if isinstance(clicked, basestring):
|
||||
clicked = ui.jumpsoutofcontext(clicked)
|
||||
|
||||
_button_factory(text, "mm", clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
store._result = ui.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
jump _main_menu
|
||||
@@ -0,0 +1,128 @@
|
||||
# This file contains code to manage the playing of music. It's here,
|
||||
# in an rpy file, because we now optionally let the user override all
|
||||
# of this to implement his or her own music system, using calls to
|
||||
# various functions found in audio.
|
||||
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
config.debug_sound = True
|
||||
|
||||
# This becomes renpy.music_start.
|
||||
def music_start(filename, loops=-1, fadeout=None):
|
||||
"""
|
||||
This starts music playing. If a music track is already
|
||||
playing, this pauses that music track in favor of this
|
||||
one.
|
||||
|
||||
@param filename: The file that the music will be played from.
|
||||
This is relative to the game directory, and must be a real
|
||||
file (so it cannot be stored in an archive).
|
||||
|
||||
@param loops: The number of times the music will loop
|
||||
after it finishes playing for the first time. This is
|
||||
made somewhat less accurate by rollback and loading. If
|
||||
this number is less than zero, the song will loop
|
||||
forever.
|
||||
|
||||
@param fadeout: If this parameter is not None, it is
|
||||
interpreted as a time in seconds, which gives how long
|
||||
it will take to fade out the currently playing music.
|
||||
"""
|
||||
|
||||
if loops >= 0:
|
||||
loops += 1
|
||||
|
||||
ctx = renpy.context()
|
||||
ctx._music_name = filename
|
||||
ctx._music_loops = loops
|
||||
|
||||
if fadeout and audio.music_enabled() and not config.skipping:
|
||||
audio.music_fadeout(fadeout)
|
||||
|
||||
def music_stop(fadeout=None):
|
||||
"""
|
||||
This stops the currently playing music track.
|
||||
|
||||
@param fadeout: If this parameter is not None, it is
|
||||
interpreted as a time in seconds, which gives how long
|
||||
it will take to fade out the currently playing music.
|
||||
"""
|
||||
|
||||
ctx = renpy.context()
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
if fadeout and audio.music_enabled() and not config.skipping:
|
||||
audio.music_fadeout(fadeout)
|
||||
|
||||
renpy.music_start = music_start
|
||||
renpy.music_stop = music_stop
|
||||
|
||||
# This is called once for each interaction, to ensure that the
|
||||
# music is appropriate for that interaction.
|
||||
def music_interact():
|
||||
|
||||
if not audio.music_enabled():
|
||||
return
|
||||
|
||||
ctx = renpy.context()
|
||||
|
||||
if not hasattr(ctx, '_music_name'):
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
playing, queued = audio.music_filenames()
|
||||
|
||||
# If music is disabled, ensure that it is stopped.
|
||||
if not _preferences.music:
|
||||
if playing:
|
||||
audio.music_stop()
|
||||
|
||||
return
|
||||
|
||||
# If we do not match what is playing, stop what's currently
|
||||
# playing and get ready to play something else.
|
||||
if ctx._music_name != playing:
|
||||
|
||||
# If we're not playing anything, immediately start the
|
||||
# new track by calling music_end_event.
|
||||
if not playing:
|
||||
config.music_end_event()
|
||||
|
||||
# Otherwise, we will start a fade to the new music if no
|
||||
# such fade is already in progress, and we're not skipping.
|
||||
elif not audio.music_fading() and not config.skipping:
|
||||
audio.music_fadeout(config.fade_music)
|
||||
|
||||
# This is called whenever a track of music ends, or also from the
|
||||
# above when we want to start a new track when nothing else is
|
||||
# playing.
|
||||
def music_end_event():
|
||||
|
||||
if not _preferences.music:
|
||||
return
|
||||
|
||||
ctx = renpy.context()
|
||||
playing, queued = audio.music_filenames()
|
||||
|
||||
if not hasattr(ctx, '_music_name'):
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
if not ctx._music_name:
|
||||
return
|
||||
|
||||
if not playing and ctx._music_loops:
|
||||
ctx._music_loops -= 1
|
||||
audio.music_play(ctx._music_name)
|
||||
|
||||
if not queued and ctx._music_loops:
|
||||
ctx._music_loops -= 1
|
||||
audio.music_queue(ctx._music_name)
|
||||
|
||||
config.music_interact = music_interact
|
||||
config.music_end_event = music_end_event
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# This file contains the code to implement the Ren'Py preferences
|
||||
# screen.
|
||||
|
||||
init -450:
|
||||
python:
|
||||
|
||||
# This is a map from the name of the style that is applied to
|
||||
# a list of preferences that should be placed into a vbox
|
||||
# with that style.
|
||||
library.preferences = { }
|
||||
|
||||
class _Preference(object):
|
||||
"""
|
||||
This is a class that's used to represent a preference that
|
||||
may be shown to the user.
|
||||
"""
|
||||
|
||||
def __init__(self, name, field, values, base=_preferences):
|
||||
"""
|
||||
@param name: The name of this preference. It will be
|
||||
displayed to the user.
|
||||
|
||||
@param variable: The field on the base object
|
||||
that will be assigned the selected value. This field
|
||||
must exist.
|
||||
|
||||
@param values: A list of value name, value, condition
|
||||
triples. The value name is the name of this value that
|
||||
will be shown to the user. The value is the literal
|
||||
python value that will be assigned if this value is
|
||||
selected. The condition is a condition that will be
|
||||
evaluated to determine if this is a legal value. If no
|
||||
conditions are true, this preference will not be
|
||||
displayed to the user. A condition of None is always
|
||||
considered to be True.
|
||||
|
||||
@param base: The base object on which the variable is
|
||||
read from and set. This defaults to _preferences,
|
||||
the user preferences object.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.values = values
|
||||
self.base = base
|
||||
|
||||
def render_preference(self):
|
||||
values = [ (name, val) for name, val, cond in self.values
|
||||
if cond is None or renpy.eval(cond) ]
|
||||
|
||||
if not values:
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
for name, value in values:
|
||||
|
||||
def clicked(value=value):
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
_button_factory(name, "prefs",
|
||||
selected=cur==value,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
class _PreferenceSpinner(object):
|
||||
"""
|
||||
This is a class that's used to represent a preference
|
||||
spinner, which is a preference that can be incremented
|
||||
and decremented, when shown to the user.
|
||||
"""
|
||||
|
||||
def __init__(self, name, field, minimum, maximum, delta,
|
||||
cond = "True", render = lambda x : str(x),
|
||||
base=_preferences):
|
||||
"""
|
||||
@param name: The name of this preference, that is presented
|
||||
to the user.
|
||||
|
||||
@param field: The name of the field on the base object
|
||||
that is updated by this spinner.
|
||||
|
||||
@param minimum: The minimum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param maximum: The maximum value that this spinner can set
|
||||
the value to.
|
||||
|
||||
@param delta: The delta by which this spinner is
|
||||
incremented or decremented.
|
||||
|
||||
@param cond: If this condition is not true, this spinner is
|
||||
not shown.
|
||||
|
||||
@param render: This function is called with the value of
|
||||
the field, and is expected to render that value to a
|
||||
string.
|
||||
|
||||
@param base: The base object that this spinner updates
|
||||
the field on. It defaults to _preferences, the preferences
|
||||
object.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.field = field
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
self.delta = delta
|
||||
self.cond = cond
|
||||
self.render = render
|
||||
self.base = base
|
||||
|
||||
def render_preference(self):
|
||||
|
||||
if not renpy.eval(self.cond):
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox()
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(self.base, self.field)
|
||||
|
||||
|
||||
def minus_clicked():
|
||||
value = cur - self.delta
|
||||
value = max(self.minimum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
def plus_clicked():
|
||||
value = cur + self.delta
|
||||
value = min(self.maximum, value)
|
||||
setattr(self.base, self.field, value)
|
||||
return True
|
||||
|
||||
ui.hbox(style='prefs_spinner')
|
||||
_button_factory("-", "prefs_spinner", clicked=minus_clicked)
|
||||
_label_factory(self.render(cur), "prefs_spinner")
|
||||
_button_factory("+", "prefs_spinner", clicked=plus_clicked)
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
|
||||
|
||||
|
||||
python hide:
|
||||
|
||||
# Enablers for some preferences.
|
||||
library.has_music = True
|
||||
library.has_sound = True
|
||||
library.has_transitions = True
|
||||
library.has_cps = True
|
||||
|
||||
|
||||
p1 = _Preference('Display', 'fullscreen', [
|
||||
('Window', False, None),
|
||||
('Fullscreen', True, None),
|
||||
])
|
||||
|
||||
p2 = _Preference('Music', 'music', [
|
||||
('Enabled', True, 'library.has_music'),
|
||||
('Disabled', False, 'library.has_music'),
|
||||
])
|
||||
|
||||
p3 = _Preference('Sound Effects', 'sound', [
|
||||
('Enabled', True, 'library.has_sound'),
|
||||
('Disabled', False, 'library.has_sound'),
|
||||
])
|
||||
|
||||
|
||||
library.preferences['prefs_left'] = [ p1, p2, p3 ]
|
||||
|
||||
p4 = _Preference('TAB and CTRL Skip', 'skip_unseen', [
|
||||
('Seen Messages', False, 'config.allow_skipping'),
|
||||
('All Messages', True, 'config.allow_skipping'),
|
||||
])
|
||||
|
||||
p5 = _Preference('Transitions', 'transitions', [
|
||||
('All', 2, 'library.has_transitions'),
|
||||
('Some', 1, 'library.has_transitions and default_transition'),
|
||||
('None', 0, 'library.has_transitions'),
|
||||
])
|
||||
|
||||
# p6 = _Preference('Text Display', 'fast_text', [
|
||||
# ('Fast', True, 'config.annoying_text_cps'),
|
||||
# ('Slow', False, 'config.annoying_text_cps'),
|
||||
# ])
|
||||
|
||||
def cps_render(n):
|
||||
if n == 0:
|
||||
return "Infinite"
|
||||
else:
|
||||
return str(n)
|
||||
|
||||
p6 = _PreferenceSpinner('Text Speed (CPS)', 'text_cps',
|
||||
0, 500, 10, 'library.has_cps',
|
||||
render=cps_render)
|
||||
|
||||
|
||||
library.preferences['prefs_right'] = [ p4, p5, p6 ]
|
||||
|
||||
label _prefs_screen:
|
||||
|
||||
python hide:
|
||||
|
||||
_game_nav("prefs")
|
||||
|
||||
ui.window(style='prefs_window')
|
||||
ui.fixed()
|
||||
|
||||
|
||||
for style, prefs in library.preferences.iteritems():
|
||||
|
||||
ui.vbox(library.padding * 3, style=style)
|
||||
for i in prefs:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
_game_interact()
|
||||
|
||||
jump _prefs_screen
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
# This file is responsible for creating and defining the default styles
|
||||
# used by the system.
|
||||
|
||||
# This file should be considered part of the Ren'Py library, and not
|
||||
# something that needs to be modified by the user. Instead, just update
|
||||
# the appropriate style property in an init: block in your script.
|
||||
#
|
||||
# For example, to change the default window backgrounds to a
|
||||
# transparent dark red, add:
|
||||
#
|
||||
# init:
|
||||
# $ style.window.background = renpy.Solid((128, 0, 0, 128)
|
||||
#
|
||||
# to your script. No need to mess around here, it will just make your
|
||||
# life harder when a new version of Ren'Py is released.
|
||||
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
style.create('default', None,
|
||||
'The default style that all styles inherit from.')
|
||||
|
||||
dark_cyan = (0, 192, 255, 255)
|
||||
bright_cyan = (0, 255, 255, 255)
|
||||
|
||||
dark_red = (255, 128, 128, 255)
|
||||
bright_red = (255, 64, 64, 255)
|
||||
|
||||
green = (0, 128, 0, 255)
|
||||
|
||||
# Magic.
|
||||
style.default.enable_hover = True
|
||||
|
||||
# Text properties.
|
||||
style.default.font = "Vera.ttf"
|
||||
style.default.antialias = True
|
||||
style.default.size = 22
|
||||
style.default.color = (255, 255, 255, 255)
|
||||
style.default.bold = False
|
||||
style.default.italic = False
|
||||
style.default.underline = False
|
||||
style.default.drop_shadow = (1, 1)
|
||||
style.default.drop_shadow_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
|
||||
|
||||
# Window properties.
|
||||
style.default.background = None
|
||||
style.default.xpadding = 0
|
||||
style.default.ypadding = 0
|
||||
style.default.xmargin = 0
|
||||
style.default.ymargin = 0
|
||||
style.default.xfill = False
|
||||
style.default.yfill = False
|
||||
style.default.xminimum = 0 # Includes margins and padding.
|
||||
style.default.yminimum = 0 # Includes margins and padding.
|
||||
|
||||
# Placement properties.
|
||||
style.default.xpos = 0
|
||||
style.default.ypos = 0
|
||||
style.default.xanchor = 'left'
|
||||
style.default.yanchor = 'top'
|
||||
style.default.xmaximum = None
|
||||
style.default.ymaximum = None
|
||||
|
||||
# Sound properties.
|
||||
style.default.sound = None
|
||||
|
||||
# The base style for the large windows.
|
||||
style.create('window', 'default',
|
||||
'(window, placement) The base style for the windows that contain dialogue, thoughts, and menus.')
|
||||
|
||||
style.window.background = Solid((0, 0, 128, 128))
|
||||
style.window.xpadding = 10
|
||||
style.window.ypadding = 5
|
||||
style.window.xmargin = 10
|
||||
style.window.ymargin = 5
|
||||
style.window.xfill = True
|
||||
style.window.yfill = False
|
||||
style.window.xminimum = 0 # Includes margins and padding.
|
||||
style.window.yminimum = 150 # Includes margins and padding.
|
||||
|
||||
style.window.xpos = 0.5
|
||||
style.window.ypos = 1.0
|
||||
style.window.xanchor = 'center'
|
||||
style.window.yanchor = 'bottom'
|
||||
|
||||
# This style controls the default placement of images on the screen.
|
||||
|
||||
style.create('image_placement', 'default',
|
||||
'This style is used to control the default placement of images on the screen.')
|
||||
|
||||
style.image_placement.xpos = 0.5
|
||||
style.image_placement.ypos = 1.0
|
||||
style.image_placement.xanchor = 'center'
|
||||
style.image_placement.yanchor = 'bottom'
|
||||
|
||||
# Styles that are used for dialogue.
|
||||
|
||||
style.create('say_label', 'default',
|
||||
"""(text) The style that is used by default for
|
||||
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.""")
|
||||
|
||||
style.create('say_thought', 'default',
|
||||
"""(text) The label that is used by default for
|
||||
the text of thoughts or narration, when no
|
||||
speaker is given.""")
|
||||
|
||||
style.create('say_window', 'window',
|
||||
'(window, position) The default style for windows containing dialogue and thoughts.')
|
||||
|
||||
|
||||
# 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.")
|
||||
|
||||
style.create('menu_choice', 'default',
|
||||
"""(text, hover) The style that is used to render
|
||||
the text of a menu choice.""")
|
||||
|
||||
style.create('menu_choice_button', 'default',
|
||||
"""(window, hover, sound) The style that is used
|
||||
to render the button containing a menu choice.""")
|
||||
|
||||
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',
|
||||
'(window, position) The default style for windows containing a menu.')
|
||||
|
||||
# Styles that are used by input widgets.
|
||||
style.create('input_text', 'default',
|
||||
'(text) The style used for the text of an input box.')
|
||||
|
||||
style.input_text.color = (255, 255, 0, 255)
|
||||
|
||||
style.create('input_prompt', 'default',
|
||||
'(text) The style used for the prompt of an input box.')
|
||||
|
||||
style.create('input_window', 'window',
|
||||
'(window, position) The style used for the window of an input box.')
|
||||
|
||||
# Styles used by centered.
|
||||
style.create('centered_window', 'default',
|
||||
'(window) The style that is used for a "window" containing centered text.')
|
||||
|
||||
style.create('centered_text', 'default',
|
||||
'(text) The style used for centered text.')
|
||||
|
||||
style.centered_window.xpos = 0.5
|
||||
style.centered_window.xanchor = 'center'
|
||||
style.centered_window.xfill = False
|
||||
|
||||
style.centered_window.ypos = 0.5
|
||||
style.centered_window.yanchor = 'center'
|
||||
style.centered_window.yfill = False
|
||||
|
||||
style.centered_window.xpadding = 10
|
||||
|
||||
style.centered_text.textalign = 0.5
|
||||
style.centered_text.xpos = 0.5
|
||||
style.centered_text.ypos = 0.5
|
||||
style.centered_text.xanchor = 'center'
|
||||
style.centered_text.yanchor = 'center'
|
||||
|
||||
# Styles that are used by imagemaps
|
||||
style.create('imagemap', 'image_placement',
|
||||
'(position) The style that is used for imagemaps.')
|
||||
|
||||
style.create('imagemap_button', 'default',
|
||||
'(window, sound, hover) The style that is used for buttons inside imagemaps.')
|
||||
|
||||
# Styles that are used by imagebutttons.
|
||||
style.create('image_button', 'default',
|
||||
'(window, sound, hover) The default style used for image buttons.')
|
||||
|
||||
style.create('image_button_image', 'default',
|
||||
'The default style used for images inside image buttons.')
|
||||
|
||||
|
||||
# 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.')
|
||||
|
||||
style.button.xpos = 0.5
|
||||
style.button.xanchor = 'center'
|
||||
|
||||
style.create('button_text', 'default',
|
||||
'(text, hover) The default style used for the label of a button.')
|
||||
|
||||
style.button_text.xpos = 0.5
|
||||
style.button_text.xanchor = 'center'
|
||||
style.button_text.size = 24
|
||||
style.button_text.color = dark_cyan
|
||||
style.button_text.hover_color = bright_cyan
|
||||
style.button_text.activate_color = bright_cyan
|
||||
style.button_text.insensitive_color = (192, 192, 192, 255)
|
||||
style.button_text.drop_shadow = (2, 2)
|
||||
|
||||
# Selected button.
|
||||
style.create('selected_button', 'button',
|
||||
'(window, hover) The style that is used for a selected button (for example, the active screen or a chosen preference).')
|
||||
|
||||
style.create('selected_button_text', 'button_text',
|
||||
'(text, hover) The style that is used for the label of a selected button.')
|
||||
|
||||
style.selected_button_text.color = dark_red
|
||||
style.selected_button_text.hover_color = bright_red
|
||||
style.selected_button_text.activate_color = bright_red
|
||||
|
||||
# Bar.
|
||||
style.create('bar', 'default',
|
||||
'(bar) The style that is used by default for bars.')
|
||||
|
||||
style.bar.left_bar = Solid(bright_cyan)
|
||||
style.bar.right_bar = Solid((0, 0, 0, 128))
|
||||
|
||||
# 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.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.')
|
||||
|
||||
style.mm_menu_window.xpos = 0.9
|
||||
style.mm_menu_window.xanchor = 'right'
|
||||
style.mm_menu_window.ypos = 0.9
|
||||
style.mm_menu_window.yanchor = 'bottom'
|
||||
|
||||
style.create('mm_button', 'button',
|
||||
'(window, hover) The style that is used on buttons that are part of the main menu.')
|
||||
|
||||
style.create('mm_button_text', 'button_text',
|
||||
'(text, hover) The style that is used for the labels of buttons that are part of the main menu.')
|
||||
|
||||
# Styles that are used to lay out the game menu.
|
||||
|
||||
style.create('gm_root_window', 'default',
|
||||
'(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.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.')
|
||||
|
||||
style.gm_nav_window.xpos = 0.9
|
||||
style.gm_nav_window.xanchor = 'right'
|
||||
style.gm_nav_window.ypos = 0.95
|
||||
style.gm_nav_window.yanchor = 'bottom'
|
||||
|
||||
|
||||
style.create('gm_nav_button', 'button',
|
||||
'(window, hover) The style of an unselected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_button_text', 'button_text',
|
||||
'(text, hover) The style of the text of an unselected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_selected_button', 'selected_button',
|
||||
'(window, hover) The style of a selected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected game menu navigation button.')
|
||||
|
||||
style.create('file_picker_window', 'default',
|
||||
'(window, position) A window containing the file picker that is used to choose slots for loading and saving.')
|
||||
|
||||
style.file_picker_window.xpos = 0
|
||||
style.file_picker_window.xanchor = 'left'
|
||||
style.file_picker_window.ypos = 0
|
||||
style.file_picker_window.yanchor = 'top'
|
||||
style.file_picker_window.xpadding = 5
|
||||
|
||||
|
||||
style.create('file_picker_navbox', 'default',
|
||||
'(position) The position of the naviation (next/previous) buttons in the file picker.')
|
||||
|
||||
style.file_picker_navbox.xmargin = 10
|
||||
|
||||
style.create('file_picker_nav_button', 'button',
|
||||
'(window, hover) The style that is used for enabled file picker navigation buttons.')
|
||||
|
||||
style.create('file_picker_nav_button_text', 'button_text',
|
||||
'(text) The style that is used for the label of enabled file picker navigation buttons.')
|
||||
|
||||
|
||||
style.create('file_picker_grid', 'default',
|
||||
'(position) The style of the grid containing the file picker entries.')
|
||||
|
||||
style.file_picker_grid.xfill = True
|
||||
|
||||
style.create('file_picker_entry', 'button',
|
||||
'(window, hover) The style that is used for each of the slots in the file picker.')
|
||||
|
||||
style.file_picker_entry.xpadding = 5
|
||||
style.file_picker_entry.ypadding = 2
|
||||
style.file_picker_entry.xmargin = 5
|
||||
style.file_picker_entry.xfill = True
|
||||
style.file_picker_entry.ymargin = 2
|
||||
|
||||
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.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.create('file_picker_empty_slot', 'file_picker_text',
|
||||
'(text) The style that is used for the empty slot indicator in the file picker.')
|
||||
|
||||
style.create('yesno_label', 'default',
|
||||
'(text, position) The style used for the prompt in a yes/no dialog.')
|
||||
|
||||
style.yesno_label.color = green
|
||||
style.yesno_label.textalign = 0.5
|
||||
|
||||
style.create('yesno_button', 'button',
|
||||
'(window, hover) The style of yes/no buttons.')
|
||||
|
||||
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.prefs_button.xpos = 0.5
|
||||
style.prefs_button.xanchor = 'center'
|
||||
|
||||
style.create('prefs_button_text', 'button_text',
|
||||
'(text, hover) The style of the text of an unselected preferences button.')
|
||||
|
||||
style.create('prefs_selected_button', 'selected_button',
|
||||
'(window, hover) The style of a selected preferences button.')
|
||||
|
||||
style.prefs_selected_button.xpos = 0.5
|
||||
style.prefs_selected_button.xanchor = 'center'
|
||||
|
||||
style.create('prefs_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected preferences button.')
|
||||
|
||||
style.create('prefs_window', 'default',
|
||||
'(window, position) A window containing all preferences.')
|
||||
|
||||
style.prefs_window.xfill=True
|
||||
style.prefs_window.ypadding = 0.05
|
||||
|
||||
style.create('prefs_left', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_left.xanchor = 'center'
|
||||
style.prefs_left.xpos = 0.25
|
||||
|
||||
style.create('prefs_right', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_right.xanchor = 'center'
|
||||
style.prefs_right.xpos = 0.75
|
||||
|
||||
style.create('prefs_spinner', 'default',
|
||||
'(position) The position of the prefs spinner.')
|
||||
|
||||
style.prefs_spinner.xpos = 0.5
|
||||
style.prefs_spinner.xanchor = 'center'
|
||||
|
||||
style.create('prefs_spinner_label', 'prefs_label',
|
||||
'(text) This is the style that displays the value of a preference spinner.')
|
||||
|
||||
style.prefs_spinner_label.minwidth = 100
|
||||
style.prefs_spinner_label.textalign = 0.5
|
||||
|
||||
style.create('prefs_spinner_button', 'prefs_button',
|
||||
'(window, hover) The style of the + or - buttons in a preference spinner.')
|
||||
|
||||
style.create('prefs_spinner_button_text', 'prefs_button_text',
|
||||
'(text, hover) The style of the text of the + and - buttons in a preference spinner.')
|
||||
|
||||
|
||||
|
||||
|
||||
# The skip indicator.
|
||||
|
||||
style.create('skip_indicator', 'default',
|
||||
'(text, position) The style of the text that is used to indicate that skipping is in progress.')
|
||||
|
||||
style.skip_indicator.xpos = 10
|
||||
style.skip_indicator.ypos = 10
|
||||
|
||||
|
||||
# Styles used by internal error messages.
|
||||
|
||||
style.create('error_window', 'default',
|
||||
'(window) The style of the window containing internal error messages.')
|
||||
|
||||
style.error_window.background = Solid((220, 220, 255, 255))
|
||||
style.error_window.xfill = True
|
||||
style.error_window.yfill = True
|
||||
style.error_window.xpadding = 20
|
||||
style.error_window.ypadding = 20
|
||||
|
||||
|
||||
style.create('error_title', 'default',
|
||||
'(text) The style of the text containing the title of an error message.')
|
||||
|
||||
style.error_title.color = (255, 128, 128, 255)
|
||||
|
||||
style.create('error_body', 'default',
|
||||
'(text) The style of the body of an error message.')
|
||||
|
||||
style.error_body.color = (128, 128, 255, 255)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
lib\windows-i686\python.exe -OO renpy.py
|
||||
pause
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os.path
|
||||
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import traceback
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Extra things used for distribution.
|
||||
import encodings.utf_8
|
||||
import encodings.zlib_codec
|
||||
import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
sys.path.append("module")
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
|
||||
if name.find("_") != -1:
|
||||
name = name[name.find("_") + 1:]
|
||||
|
||||
if os.path.isdir(name):
|
||||
game = name
|
||||
else:
|
||||
game = "game"
|
||||
|
||||
op = optparse.OptionParser()
|
||||
op.add_option('--game', dest='game', default=game,
|
||||
help='The directory the game is in.')
|
||||
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--leak', dest='leak', action='store_true', default=False,
|
||||
help='When the game exits, dumps a profile of memory usage.')
|
||||
|
||||
options, args = op.parse_args()
|
||||
|
||||
if options.python:
|
||||
execfile(options.python)
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
renpy.main.main(options.game)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
f = file("traceback.txt", "wU")
|
||||
|
||||
f.write(codecs.BOM_UTF8)
|
||||
|
||||
print >>f, "I'm sorry, but an exception occured while executing your Ren'Py"
|
||||
print >>f, "script."
|
||||
print >>f
|
||||
|
||||
type, value, tb = sys.exc_info()
|
||||
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print >>f
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "-- Full Traceback ------------------------------------------------------------"
|
||||
print >>f
|
||||
|
||||
traceback.print_tb(tb, None, sys.stdout)
|
||||
traceback.print_tb(tb, None, f)
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print type.__name__ + ":",
|
||||
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print unicode(e).encode('utf-8')
|
||||
|
||||
print
|
||||
print >>f
|
||||
|
||||
print renpy.game.exception_info
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "Ren'Py Version:", renpy.version
|
||||
|
||||
f.close()
|
||||
|
||||
try:
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def memory_profile():
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
print
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
objs = gc.get_objects()
|
||||
|
||||
c = { } # count
|
||||
dead_renders = 0
|
||||
|
||||
for i in objs:
|
||||
t = type(i)
|
||||
c[t] = c.get(t, 0) + 1
|
||||
|
||||
if isinstance(i, renpy.display.render.Render):
|
||||
if i.dead:
|
||||
dead_renders += 1
|
||||
|
||||
|
||||
results = [ (count, ty) for ty, count in c.iteritems() ]
|
||||
results.sort()
|
||||
|
||||
for count, ty in results:
|
||||
print count, str(ty)
|
||||
|
||||
if dead_renders:
|
||||
print
|
||||
print "*** found", dead_renders, "dead Renders. ***"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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 $
|
||||
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
@@ -1,237 +1,150 @@
|
||||
#!/home/tom/bin/renpython -O
|
||||
|
||||
# Builds a distribution of Ren'Py.
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import os
|
||||
import compileall
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
import glob
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
def match_times(source, dest):
|
||||
|
||||
def copy_tutorial_file(src, dest):
|
||||
"""
|
||||
Copies a file from src to dst. Lines between "# tutorial-only" and
|
||||
"# end-tutorial-only" comments are omitted from the copy.
|
||||
"""
|
||||
stat = os.stat(source)
|
||||
os.utime(dest, (stat.st_atime, stat.st_mtime))
|
||||
|
||||
def dosify(s):
|
||||
return s.replace("\n", "\r\n")
|
||||
|
||||
sf = open(src, "rb")
|
||||
df = open(dest, "wb")
|
||||
def copy_file(source, dest, license="", dos=True):
|
||||
|
||||
# True if we want to copy the line.
|
||||
copy = True
|
||||
print source, "->", dest
|
||||
|
||||
for l in sf:
|
||||
if "# tutorial-only" in l:
|
||||
copy = False
|
||||
elif "# end-tutorial-only" in l:
|
||||
copy = True
|
||||
else:
|
||||
if copy:
|
||||
df.write(l)
|
||||
sf = file(source, "rb")
|
||||
df = file(dest, "wb")
|
||||
|
||||
df.write(license)
|
||||
|
||||
data = sf.read()
|
||||
if dest.endswith(".txt") or dest.endswith(".py") or dest.endswith(".rpy") or dest.endswith(".bat"):
|
||||
if dos:
|
||||
data = dosify(data)
|
||||
|
||||
df.write(data)
|
||||
|
||||
sf.close()
|
||||
df.close()
|
||||
|
||||
match_times(source, dest)
|
||||
|
||||
|
||||
def copy_tree(source, dest, should_copy=lambda fn : True, license=""):
|
||||
|
||||
os.makedirs(dest)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(source):
|
||||
|
||||
if "/saves" in dirpath:
|
||||
continue
|
||||
|
||||
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)
|
||||
|
||||
for i in filenames:
|
||||
if not should_copy(i):
|
||||
continue
|
||||
|
||||
copy_file(dirpath + "/" + i, dstrel + "/" + i, license=license)
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if not sys.flags.optimize:
|
||||
raise Exception("Not running with python optimization.")
|
||||
target = sys.argv[1]
|
||||
gamedir = sys.argv[2]
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("version")
|
||||
ap.add_argument("--fast", action="store_true")
|
||||
# Read license.
|
||||
lf = file("LICENSE.txt")
|
||||
license = "#!/usr/bin/env python\n\n"
|
||||
|
||||
for l in lf:
|
||||
license += "# " + l
|
||||
|
||||
args = ap.parse_args()
|
||||
lf.close()
|
||||
|
||||
# Revision updating is done early, so we can do it even if the rest
|
||||
# of the program fails.
|
||||
license = dosify(license)
|
||||
|
||||
# Determine the version. We grab the current revision, and if any
|
||||
# file has changed, bump it by 1.
|
||||
import renpy
|
||||
if os.path.exists(target):
|
||||
raise Exception("Target exists!")
|
||||
|
||||
match_version = ".".join(str(i) for i in renpy.version_tuple[:2]) #@UndefinedVariable
|
||||
zip_version = ".".join(str(i) for i in renpy.version_tuple[:3]) #@UndefinedVariable
|
||||
# Start off with the target.
|
||||
copy_tree("dist", target,
|
||||
should_copy = lambda fn : fn not in [ 'traceback.txt' ] and not fn.endswith(".log"))
|
||||
|
||||
s = subprocess.check_output([ "git", "describe", "--tags", "--dirty", "--match", match_version ])
|
||||
parts = s.strip().split("-")
|
||||
# Copy renpy modules.
|
||||
copy_tree("renpy", target + "/renpy",
|
||||
should_copy = lambda fn : fn.endswith(".py"),
|
||||
license=license)
|
||||
|
||||
if len(parts) <= 2:
|
||||
vc_version = 0
|
||||
else:
|
||||
vc_version = int(parts[1])
|
||||
doc_files = [
|
||||
'example.html',
|
||||
'reference.html',
|
||||
'style.css',
|
||||
]
|
||||
|
||||
if parts[-1] == "dirty":
|
||||
vc_version += 1
|
||||
# Copy doc
|
||||
copy_tree("doc", target + "/doc",
|
||||
should_copy = lambda fn : fn in doc_files)
|
||||
|
||||
with open("renpy/vc_version.py", "w") as f:
|
||||
f.write("vc_version = {}".format(vc_version))
|
||||
# Copy the game
|
||||
copy_tree(gamedir, target + "/game",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~") and not fn.endswith(".mpg"))
|
||||
|
||||
try:
|
||||
reload(sys.modules['renpy.vc_version']) #@UndefinedVariable
|
||||
except:
|
||||
import renpy.vc_version # @UnusedImport
|
||||
copy_tree("common", target + "/common",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
reload(sys.modules['renpy'])
|
||||
copy_tree("extras", target + "/extras",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
# Check that the versions match.
|
||||
full_version = ".".join(str(i) for i in renpy.version_tuple) #@UndefinedVariable
|
||||
if args.version != "renpy-experimental" \
|
||||
and not args.version.startswith("renpy-nightly-") \
|
||||
and not full_version.startswith(args.version):
|
||||
def cp(x, license="", dos=True):
|
||||
copy_file(x, target + "/" + x, dos=dos)
|
||||
|
||||
raise Exception("The command-line and Ren'Py versions do not match.")
|
||||
cp("CHANGELOG.txt")
|
||||
cp("LICENSE.txt")
|
||||
cp("README_RENPY.txt")
|
||||
cp("archive_images.bat")
|
||||
cp("run_game.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_game.pyw", license=license)
|
||||
cp("archiver.py", license=license)
|
||||
# cp("build_exe.py", license=license)
|
||||
cp("add_from.py", license=license)
|
||||
cp("dump_text.py", license=license)
|
||||
cp("renpy-mode.el")
|
||||
|
||||
os.mkdir(target + "/module")
|
||||
|
||||
# The destination directory.
|
||||
destination = os.path.join("dl", args.version)
|
||||
|
||||
print "Version {} ({})".format(args.version, full_version)
|
||||
|
||||
# Perhaps autobuild.
|
||||
if "RENPY_BUILD_ALL" in os.environ:
|
||||
print("Autobuild...")
|
||||
subprocess.check_call(["scripts/autobuild.sh"])
|
||||
|
||||
# Copy over the screens, to keep them up to date.
|
||||
copy_tutorial_file("tutorial/game/screens.rpy", "templates/english/game/screens.rpy")
|
||||
|
||||
# Compile all the python files.
|
||||
compileall.compile_dir("renpy/", ddir="renpy/", force=1, quiet=1)
|
||||
|
||||
# Generate launcher/game/script_version.rpy
|
||||
with open("launcher/game/script_version.rpy", "w") as f:
|
||||
f.write("init -999 python:\n")
|
||||
f.write(" config.script_version = {!r}\n".format(renpy.version_tuple[:3])) # @UndefinedVariable
|
||||
|
||||
# Compile the various games.
|
||||
if not args.fast:
|
||||
for i in [ 'tutorial', 'launcher', 'the_question' ] + glob.glob("templates/*"):
|
||||
print "Compiling", i
|
||||
subprocess.check_call(["./renpy.sh", i, "quit" ])
|
||||
|
||||
|
||||
# Kick off the rapt build.
|
||||
if not args.fast:
|
||||
out = open("/tmp/rapt_build.txt", "wb")
|
||||
|
||||
print("Building RAPT.")
|
||||
|
||||
android = os.path.abspath("android")
|
||||
|
||||
rapt_build = subprocess.Popen([
|
||||
os.path.join(android, "build_renpy.sh"),
|
||||
"renpy",
|
||||
ROOT,
|
||||
],
|
||||
cwd = android,
|
||||
stdout=out,
|
||||
stderr=out)
|
||||
|
||||
code = rapt_build.wait()
|
||||
|
||||
if code:
|
||||
print "RAPT build failed. The output is in /tmp/rapt_build.txt."
|
||||
sys.exit(1)
|
||||
else:
|
||||
print "RAPT build succeeded."
|
||||
|
||||
if not os.path.exists(destination):
|
||||
os.makedirs(destination)
|
||||
|
||||
if args.fast:
|
||||
|
||||
cmd = [
|
||||
"./renpy.sh",
|
||||
"launcher",
|
||||
"distribute",
|
||||
"launcher",
|
||||
"--package",
|
||||
"sdk",
|
||||
"--destination",
|
||||
destination,
|
||||
"--no-update",
|
||||
]
|
||||
|
||||
else:
|
||||
cmd = [
|
||||
"./renpy.sh",
|
||||
"launcher",
|
||||
"distribute",
|
||||
"launcher",
|
||||
"--destination",
|
||||
destination,
|
||||
]
|
||||
|
||||
print
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
# Sign the update.
|
||||
if not args.fast:
|
||||
subprocess.check_call([
|
||||
"scripts/sign_update.py",
|
||||
"/home/tom/ab/keys/renpy_private.pem",
|
||||
os.path.join(destination, "updates.json"),
|
||||
])
|
||||
|
||||
|
||||
# Write 7z.exe.
|
||||
sdk = "renpy-{}-sdk".format(zip_version)
|
||||
|
||||
if args.version.startswith("renpy-nightly-"):
|
||||
sdk = args.version + "-sdk"
|
||||
|
||||
if not args.fast:
|
||||
|
||||
# shutil.copy("renpy-ppc.zip", os.path.join(destination, "renpy-ppc.zip"))
|
||||
|
||||
with open("7z.sfx", "rb") as f:
|
||||
sfx = f.read()
|
||||
|
||||
os.chdir(destination)
|
||||
|
||||
if os.path.exists(sdk):
|
||||
shutil.rmtree(sdk)
|
||||
|
||||
subprocess.check_call([ "unzip", "-q", sdk + ".zip" ])
|
||||
|
||||
if os.path.exists(sdk + ".7z"):
|
||||
os.unlink(sdk + ".7z")
|
||||
|
||||
sys.stdout.write("Creating -sdk.7z")
|
||||
|
||||
p = subprocess.Popen([ "7z", "a", sdk +".7z", sdk], stdout=subprocess.PIPE)
|
||||
for i, _l in enumerate(p.stdout):
|
||||
if i % 10 != 0:
|
||||
continue
|
||||
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
|
||||
if p.wait() != 0:
|
||||
raise Exception("7z failed")
|
||||
|
||||
with open(sdk + ".7z", "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
with open(sdk + ".7z.exe", "wb") as f:
|
||||
f.write(sfx)
|
||||
f.write(data)
|
||||
|
||||
os.unlink(sdk + ".7z")
|
||||
shutil.rmtree(sdk)
|
||||
|
||||
else:
|
||||
os.chdir(destination)
|
||||
|
||||
if os.path.exists(sdk + ".7z.exe"):
|
||||
os.unlink(sdk + ".7z.exe")
|
||||
|
||||
print
|
||||
cp("module/README.txt")
|
||||
cp("module/_renpy.pyx")
|
||||
cp("module/_renpy.c")
|
||||
cp("module/core.c")
|
||||
cp("module/renpy.h")
|
||||
cp("module/setup.py")
|
||||
cp("module/setup_mac.py")
|
||||
cp("module/setup_win32.py")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
Steps one needs to take to make a Ren'Py distro.
|
||||
|
||||
0) Update the documentation. (This involves dump_styles, and perhaps
|
||||
copying in a new config.keymap)
|
||||
|
||||
1) Change the version in run_game.py
|
||||
|
||||
2) In cygwin, run "release.sh <version>"
|
||||
|
||||
3) Test run_game.exe
|
||||
4) Test console.exe
|
||||
|
||||
8) Release.
|
||||
@@ -0,0 +1,14 @@
|
||||
Some notes about making sure things are imported in the proper order:
|
||||
|
||||
1) Every module should import 'renpy'.
|
||||
|
||||
2) It's okay for modules to 'import renpy.game as game'.
|
||||
|
||||
3) Those are all the imports that normal modules should do.
|
||||
|
||||
4) renpy/__init__.py should list all the modules in the system, a
|
||||
topologically sorted order, such that if a definition in a uses
|
||||
a definition in b, b comes before a. Please note that this only
|
||||
considers the top level of the file.
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
all:: reference.html example.html
|
||||
|
||||
reference.html: reference.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py reference.xml > reference.hi.xml
|
||||
xsltproc stylesheet.xslt reference.hi.xml > reference.html
|
||||
cp reference.html style.css ~/ab/website/renpy/devel/doc
|
||||
|
||||
|
||||
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml ../demo2/script.rpy
|
||||
python preprocess.py example.xml > example.hi.xml
|
||||
xsltproc stylesheet.xslt example.hi.xml > example.html
|
||||
cp example.html style.css ~/ab/website/renpy/devel/doc
|
||||
@@ -0,0 +1,15 @@
|
||||
# With pygame surfaces and fill.
|
||||
|
||||
Menu mousemove: .1336, .1244, .1437
|
||||
"Save when I tell you to": .055, .066, .0476
|
||||
|
||||
|
||||
# Without fill.
|
||||
|
||||
Menu mousemove: .115, .129, .134
|
||||
"Save when I tell you to": .050, .057, .046
|
||||
|
||||
# With new surface code.
|
||||
|
||||
Menu mousemove: .02something
|
||||
"Save when I tell you to": .038, .040, .038
|
||||
@@ -0,0 +1,44 @@
|
||||
import xml.dom.minidom
|
||||
import sys
|
||||
|
||||
def escape(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace('"', """)
|
||||
|
||||
def transform_children(node):
|
||||
|
||||
rv = [ ]
|
||||
|
||||
for n in node.childNodes:
|
||||
rv.append(transform(n))
|
||||
|
||||
return ''.join(rv)
|
||||
|
||||
|
||||
def transformElement(node):
|
||||
|
||||
attributes = ' '.join(['%s="%s"' % (k, escape(v)) for k,v in node.attributes.items()])
|
||||
|
||||
tag = node.tagName
|
||||
|
||||
|
||||
# Otherwise, the default.
|
||||
return "<%s %s>%s</%s>" % (node.tagName, attributes,
|
||||
transform_children(node), node.tagName)
|
||||
|
||||
|
||||
|
||||
def transform(node):
|
||||
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
return transformElement(node)
|
||||
else:
|
||||
return escape(str(node))
|
||||
|
||||
def main():
|
||||
|
||||
dom = xml.dom.minidom.parse(sys.argv[1])
|
||||
|
||||
print transform(dom.documentElement)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<doc>
|
||||
<title>Demo Script Example</title>
|
||||
|
||||
<p><a href="tutorial.html">Return to the tutorial.</a></p>
|
||||
|
||||
<example>
|
||||
<!-- include ../demo2/script.rpy -->
|
||||
</example>
|
||||
|
||||
<p><a href="tutorial.html">Return to the tutorial.</a></p>
|
||||
|
||||
</doc>
|
||||
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<renpydoc>
|
||||
|
||||
<!-- (define-key xml-mode-map '(control return) 'tompy-xml-ctrlret) -->
|
||||
|
||||
<section title="Say Statement">
|
||||
|
||||
<rule name="statement">
|
||||
<alt>string</alt>
|
||||
<alt>string string</alt>
|
||||
<alt>identifier string</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
These three productions are the various forms of the say
|
||||
statement. In order, they are the 1-string form, the 2-string
|
||||
form, and the name-string form. We'll discuss individually
|
||||
what each one does.
|
||||
</formal><formal>
|
||||
The 1-string form calls the <f>say</f> function (found in the
|
||||
execution context) with the string.
|
||||
</formal><formal>
|
||||
The 2-string form also calls the <f>say</f> function, but this
|
||||
time it passes it the two strings. The strings are passed in the
|
||||
order in which they appear.
|
||||
</formal><formal>
|
||||
When a identifierstring say statement is executed, the first thing
|
||||
that happens is that the identifier is looked up in the execution
|
||||
context. It's an error if the name is not defined. If the name
|
||||
is bound to a string, both strings are passed to the <f>say</f>
|
||||
function, as if the 2-string form was used. Otherwise, the
|
||||
<f>say</f> method is called on the object that the identifier was
|
||||
bound to, with the string as the only argument.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
While the various say statements are very flexible, there are
|
||||
some conventions that we suspect users will use when writing
|
||||
games.
|
||||
</user><user>
|
||||
The 1-string form of the say statment is rendered to the user in
|
||||
a text box without any name associated with it. It's used to
|
||||
indicate the thoughts of the POV character, or to narrate
|
||||
actions that occur in the game.
|
||||
</user><user>
|
||||
The 2-string form is displayed with the first string as a name,
|
||||
and the second string as the line of dialogue said by the
|
||||
character with that name. It's used to indicate spoken dialogue,
|
||||
usually from characters that are minor enough to not have a
|
||||
character object associated with them. The 2-string form is
|
||||
rarely used directly, as normally the name-string or 1-string
|
||||
forms are preferred for dialogue from main characters.
|
||||
</user><user>
|
||||
Generally, when the name-string form is used, the first name
|
||||
refers to a character object. The name field of that character
|
||||
object is looked up, and used to display dialogue as if the
|
||||
2-string form was used. So the name-string form is what is used
|
||||
when we want a main character to say something. This form allows
|
||||
the direct use of the character's name before the dialogue,
|
||||
without requiring the quoting needed by the 2-string form.
|
||||
</user>
|
||||
|
||||
<example>
|
||||
"I was walking down the street one day, when I came across the postman."
|
||||
|
||||
me "Hey, any mail for me today?"
|
||||
|
||||
"Postman" "Yeah, a package came for you. But there's no return address."
|
||||
</example>
|
||||
|
||||
</section>
|
||||
|
||||
<subsection title="Image Display">
|
||||
|
||||
<formal>
|
||||
In Ren'Py, image display is controlled by a pair of image
|
||||
lists. The two image lists are the are called the master image
|
||||
list and the temporary image list. These lists contain python
|
||||
objects that are capable of drawing themselves to the
|
||||
screen. When the time comes to show something to the user, we
|
||||
iterate through the temporary image list, drawing things on
|
||||
the screen in the order in which they appear in the list. So
|
||||
the first thing in the list will be the background, and the
|
||||
thing that's drawn closest to the user will be the last thing
|
||||
in the list.
|
||||
</formal><formal>
|
||||
We can divide the execution of a Ren'Py program into periods
|
||||
where we are showing a screen to the user (for example, during
|
||||
the execution of a say statement) and periods where we are
|
||||
not. After a period of showing the screen to the user, the master image
|
||||
list is copied over the temporary image list.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
We have two image lists for two reasons. The first is the idea
|
||||
that only the temporary image list will include transitions,
|
||||
and once we're done performing the transition, we want it to
|
||||
be remove from the list. The second reason is an idea that
|
||||
(behind the scenes) the temporary list will also include
|
||||
interface elements (like boxes containing character
|
||||
dialog). We, in general, want these boxes to be shown to the
|
||||
user once and then removed from the screen.
|
||||
</user>
|
||||
|
||||
<rule name="image_name">
|
||||
<alt>identifier+</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
An image name consists of one or more identifiers. The first
|
||||
identifier is called the primary identifier of this image
|
||||
name, while the second and later identifier are called
|
||||
secondary identifers. An image name may not include a Ren'Py
|
||||
keyword.
|
||||
</formal><formal>
|
||||
Ren'Py maintains an image dictionary, which maps between image
|
||||
names and objects implementing images that can be show to the
|
||||
user.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
The idea behind primary identifiers is that we will normally
|
||||
want to display only one image of a character at a time. So we
|
||||
use an image's primary identifier to find other images in the
|
||||
master display list that have the same primary identifier.
|
||||
</user>
|
||||
|
||||
|
||||
<rule name="statement">
|
||||
<alt>"image" image_name "=" python_expression</alt>
|
||||
</rule>
|
||||
|
||||
<formal>
|
||||
This adds a new entry to the image dictionary. Specifically, the
|
||||
python_expression is evaluated to get something that's drawable,
|
||||
and then that object is stored in a tuple in the image
|
||||
dictionary.
|
||||
</formal>
|
||||
|
||||
<user>
|
||||
In general, the python expression will be a call to the image
|
||||
constructor, which loads in a new image from disk (or
|
||||
somewhere... read the documentation for the image constructor.)
|
||||
It's also possible to use another constructor, like the one for
|
||||
animation, but that will probably be more rare.
|
||||
</user><user>
|
||||
It's important to note that this doesn't actually load the image
|
||||
into ram, until close to the time when the image is actually
|
||||
needed.
|
||||
</user><user>
|
||||
In general, it makes sense to ensure that all image statements
|
||||
execure during game startup, before the call to
|
||||
startup_complete().
|
||||
</user>
|
||||
|
||||
<example>
|
||||
image woods = image("backgrounds/woods.jpg")
|
||||
image eileen red upset = image("eileen/red_upset.png")
|
||||
image eileen red happy = image("eileen/red_happy.png")
|
||||
</example>
|
||||
|
||||
</subsection>
|
||||
</renpydoc>
|
||||
@@ -0,0 +1,373 @@
|
||||
To write a game using Ren'Py, one must become familar with the Ren'Py
|
||||
script language. This file gives some basic concepts and a list of
|
||||
Ren'Py statements. A second file will give a list of python classes
|
||||
and functions that are intended to be used from Ren'Py code.
|
||||
|
||||
A Ren'Py script can first be seen as a long string of unicode
|
||||
characters. This string is first broken up into logical lines, then
|
||||
the lines are organized into blocks, and finally as a tree of
|
||||
statements. So we'll begin by defining some basic terms, and then
|
||||
we'll go on to give a list of statements Ren'Py understands.
|
||||
|
||||
Logical Lines:
|
||||
|
||||
Each file consists of one or more logical lines. The first logical
|
||||
line begins at the first character file, and subsequent logical lines
|
||||
begin immediately following the end of the previous line. Logical
|
||||
lines are normally ended by the first newline encountered. However,
|
||||
there are several cases that will cause a logical line to extend past
|
||||
a newline character:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
A logical line containing a string must extend at least until the
|
||||
end of that string.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
A logical line parenthesis, brackets, or braces cannot end until
|
||||
each opening character ('(', ']', or '}') is matched with a closing
|
||||
character (')', ']', or '}'). The expression enclosed within is called
|
||||
a parenthetical expression.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
A newline immediately preceded by a backslash will not end a logical
|
||||
line. Instead, both characters will be treated as whitespace, and
|
||||
ignored.
|
||||
<li>
|
||||
</ul>
|
||||
|
||||
Logical lines are numbered by giving the number of the physical line
|
||||
in the file on which the logical line begins. (At most one logical
|
||||
line can exist on each physical line.)
|
||||
|
||||
If a logical line contains a hash mark in it ('#'), all characters
|
||||
from the hash mark to character before the next physical newline are
|
||||
considered to be part of a comment, and are ignored as if they were
|
||||
whitespace. If, after this step, a logical line consists entirely of
|
||||
whitespace, it is ignored.
|
||||
|
||||
Each logical line has an indentaion level. This is computed by first
|
||||
converting tabs to spaces (using 8-space tab stops), and then looking
|
||||
at the number of spaces preceding the first non-whitespace character
|
||||
on the line.
|
||||
|
||||
The following examples all consist of single logical lines with
|
||||
indentation level 0.
|
||||
|
||||
<example>
|
||||
me "How are you doing?"
|
||||
</example>
|
||||
|
||||
<example>
|
||||
"It's a question that I ask myself every day. Today, however, is
|
||||
the first time I've asked it of someone else.
|
||||
</example>
|
||||
|
||||
<example>
|
||||
$ my_list = [ 1, 2, 3, 4,
|
||||
5, 6, 7, 8 ]
|
||||
</example>
|
||||
|
||||
<example>
|
||||
$ a = 1 \
|
||||
+ 2 \
|
||||
+ 3
|
||||
</example>
|
||||
|
||||
Blocks:
|
||||
|
||||
If a logical line is followed by a logical line with greater
|
||||
indentation, the second logical line is considered to be part of the
|
||||
block of the first logical line. This block contains all logical lines
|
||||
with an indentation level equal to that of the second logical line,
|
||||
provided there are no intervening lines with an indentaion level that
|
||||
is less than that of the second logical line. If a line is encountered
|
||||
with an indentation level that is greater than that of the first
|
||||
logical line, but less than that of the second, the indentation is
|
||||
mismatched and an error is reported.
|
||||
|
||||
An example of a block (as part of an if statement) is:
|
||||
|
||||
<example>
|
||||
if happy:
|
||||
e "I'm feeling really happy today."
|
||||
e "It's like I'm the happiest person in the world."
|
||||
</example>
|
||||
|
||||
An example of an indentation mismatch is:
|
||||
|
||||
<example>
|
||||
else:
|
||||
e "I'm having a really bad day."
|
||||
e "I can't even get the indentation right."
|
||||
</example>
|
||||
|
||||
|
||||
One of the things we can parse is a string literal. String literals
|
||||
begin with a quote character (' or "), and end with a matching
|
||||
unescaped quote character. Strings use backslash (\) as an escape
|
||||
character. The following are suppored escape sequences:
|
||||
|
||||
<ul>
|
||||
<li>"\\" - Backslash</li>
|
||||
<li>"\"" - Double Quote</li>
|
||||
<li>"\'" - Single Quote</li>
|
||||
<li>"\n" - Newline</li>
|
||||
<li>"\ " - Space</li>
|
||||
</ul>
|
||||
|
||||
As the string is being parsed, but before escape processing is done,
|
||||
contiguous sequences of whitespace are converted to single space
|
||||
characters. To include more than single spaces, explicitly escape the
|
||||
desired whitespace.
|
||||
|
||||
<example>
|
||||
"I hate having to \"escape\" whitespace."
|
||||
</example>
|
||||
|
||||
Names and Numbers:
|
||||
|
||||
Names begin with a character in the set [a-zA-Z_], and may contain
|
||||
additional characters in the set [a-zA-Z0-9_]. A keyword in Ren'Py may
|
||||
not be used as a name.
|
||||
|
||||
Some example names are:
|
||||
|
||||
<example>
|
||||
eileen
|
||||
lucy
|
||||
molly
|
||||
crm_114
|
||||
_underscore_
|
||||
</example>
|
||||
|
||||
A dotted_name consists of one or more names, separated by dots '.'.
|
||||
|
||||
<example>
|
||||
eileen
|
||||
eileen.name
|
||||
eileen.address.street
|
||||
</example>
|
||||
|
||||
A number matches the characters [0-9.].
|
||||
|
||||
<example>
|
||||
1
|
||||
1234.
|
||||
3.14159
|
||||
.2
|
||||
</example>
|
||||
|
||||
|
||||
Python:
|
||||
|
||||
Ren'Py scripts may contain embedded python. There are three kinds of
|
||||
python things that can be mixed into Ren'Py: simple_expressions,
|
||||
python_expressions, and python_statements.
|
||||
|
||||
A simple_expression consists of either a dotted_name, string, or
|
||||
number optionally followed by a parenthetical expression, or just a
|
||||
single parenthetical expression.
|
||||
|
||||
Examples of simple_expressions are:
|
||||
|
||||
<example>
|
||||
eileen
|
||||
sample.image("foo.jpg", size=(100,200))
|
||||
"test"
|
||||
(1 + 2 + 3)
|
||||
</example>
|
||||
|
||||
Python_expressions occur in statements such as if and while, and
|
||||
extend from the current location on the logical line to just before
|
||||
the next ':' that is not contained within a parenthetical expression
|
||||
on the logical line.
|
||||
|
||||
Examples of expressions that are python_expressions but not
|
||||
simple_expressions are:
|
||||
|
||||
<example>
|
||||
1 + 2 + 3
|
||||
foo.bar().baz()
|
||||
</example>
|
||||
|
||||
Finally, python_statements simply assume that all remaining
|
||||
information on the line should be interpreted as python code.
|
||||
|
||||
|
||||
|
||||
Statements:
|
||||
|
||||
The parsing of each logical line is syntax-directed. When parsing a
|
||||
block, all of the logical lines in that block are parsed as
|
||||
statements. The logical lines at the top level of a file are
|
||||
considered to form a block. Here, we give the rules for parsing each
|
||||
kind of statement, as well as an overview of what each statement
|
||||
does. We'll first give some insight as to what a statement does when
|
||||
used with the standard library, and then we'll write a little about
|
||||
how it goes about accomplishing that task.
|
||||
|
||||
|
||||
|
||||
Say Statements:
|
||||
|
||||
<rule name="statement">
|
||||
<alt>string</alt>
|
||||
<alt>simple_expression string</alt>
|
||||
</rule>
|
||||
|
||||
Probably the most commonly used statement in a Ren'Py script is the
|
||||
say statement. It's so commonly used that we chose not to denote it
|
||||
with a keyword, but instead to simply make all bare strings or
|
||||
simple_expression string pairs into say statements.
|
||||
|
||||
Executing a say statement results in a line of dialogue or thought
|
||||
being displayed to the user. Ren'Py then waits for the user to click
|
||||
or otherwise dismiss the display before proceeding with execution of
|
||||
the program.
|
||||
|
||||
In the single string version of the statement, the string is taken as
|
||||
a thought or narration that should be displayed, unadorned, to the
|
||||
user. If the string is preceded by a simple_expression, the expression
|
||||
is first evaluated to yield a string, and that string is used to
|
||||
indicate to the user who is speaking the line of dialogue.
|
||||
|
||||
Ren'Py executes say statements by first evaluating the
|
||||
simple_expression, if present, and then calling the say function with
|
||||
the values of the two strings, or None if the simple_expression is not
|
||||
defined. Take a look at the documentation for the say function to see
|
||||
what happens from there, as behind the scenes it's a little more
|
||||
complicated that what we describe here.
|
||||
|
||||
Some example say statements are:
|
||||
|
||||
<example>
|
||||
"I was walking down the street one day, when I came across the postman."
|
||||
|
||||
me "Hey, any mail for me today?"
|
||||
|
||||
"Postman" "Yeah, a package came for you. But there's no return address."
|
||||
</example>
|
||||
|
||||
Here, we assume that 'me' is an expression that expands to the name of
|
||||
the main character.
|
||||
|
||||
|
||||
|
||||
Menu Statement:
|
||||
|
||||
<rule name="statement">
|
||||
<alt>"menu" name? ":" menu_block</alt>
|
||||
</rule>
|
||||
|
||||
<rule name="menu_item">
|
||||
<alt>string</alt>
|
||||
<alt>string ( "if" python_expression )? ":" block</alt>
|
||||
<alt>"set" simple_expression</alt>
|
||||
</rule>
|
||||
|
||||
The menu statement displays a menu to the user, waits for the user to
|
||||
provide a response, and then runs a block of code corresponding to the
|
||||
choice that the user made. In a visual novel game, menu statement are
|
||||
the main way in which the user can interact with the story.
|
||||
|
||||
Menu statements begin with the word "menu". This word can be followed
|
||||
by an optional name. If the name is provided, it's as if the menu
|
||||
statement was proceded by a label statement with that name. A menu
|
||||
statement must have a block associated with it, and each of the
|
||||
logical lines in this menu_block are parsed as menu_items.
|
||||
|
||||
There are three kinds of menu_items. The first is a logical line
|
||||
containing only a string. These menu_items are used to provide blocks
|
||||
of text in the menu that are not selectable. For example, they can be
|
||||
use to provide a prompt to the user.
|
||||
|
||||
The second kind of menu_item is a string, followed by an optional if
|
||||
clause, followed by a colon. This menu_item has a block of statements
|
||||
associated with it. It's used to indicate a choice on the menu. If the
|
||||
if clause is supplied, the choice is only presented to the user if the
|
||||
python_expression is true. If a choice is selected by the user, the
|
||||
statements in the block associated with the choice are executed before
|
||||
control continues after the menu statement.
|
||||
|
||||
The final menu_item is the word "set" followed by a
|
||||
simple_expression. The expression is evaluated to get a set of menu
|
||||
items to supress. This set is used to filter the menu choices. If a
|
||||
choice string is in the set, then the corresponding menu choice is not
|
||||
displayed to the user. If a set is defined, then when a choice has
|
||||
been made, the corresponding choice string is added to the set. This
|
||||
provides an easy way of having a menu where the user can select each
|
||||
item at most once.
|
||||
|
||||
Here's an example of a fairly complex menu, that demonstrates all
|
||||
these features.
|
||||
|
||||
<example>
|
||||
menu what_to_do:
|
||||
set what_to_do_set
|
||||
"What should we do today?"
|
||||
|
||||
"Go to the movies.":
|
||||
"We went to the movies."
|
||||
|
||||
"Go shopping.":
|
||||
"We went shopping, and the girls both bought swimsuits."
|
||||
$ have_swimsuits = True
|
||||
|
||||
"Go to the beach." if have_swimsuits:
|
||||
"We went to the beach together."
|
||||
</example>
|
||||
|
||||
The actual task of displaying a menu is implemented internally by a
|
||||
call to the menu python function.
|
||||
|
||||
|
||||
|
||||
Graphics Statements:
|
||||
|
||||
|
||||
Before we can discuss the graphics statements, we must first discuss
|
||||
the graphics model that Ren'Py uses. This will be covered in greater
|
||||
detail in the library reference, but we want to cover the fundamentals
|
||||
here to allow the graphics statements to be put in proper context.
|
||||
|
||||
First, let me define what it means to display a scene. The execution
|
||||
of a Ren'Py script can be roughly divided into periods when the script
|
||||
is executing code, and periods when the script is blocked wating for
|
||||
user input (as is the case when a menu or say statement
|
||||
executes). When Ren'Py is waiting for input, it is displaying a
|
||||
scene. When it gets the input it's waiting for and execution
|
||||
continues, we say it's finished displaying the scene.
|
||||
|
||||
The display in Ren'Py is controlled by three scene lists. These scene
|
||||
lists are called the master scene list, the overlay scene list, and
|
||||
the transient scene list.
|
||||
|
||||
The only scene list that is displayed to the user is the transient
|
||||
scene list. This list is ordered such that items that are at the end
|
||||
of the scene list are closest to the user. All of the items in the
|
||||
scene lists must be displayable--- capable of drawing themselves to
|
||||
the screen. See the library reference for the exact API displayable
|
||||
objects must implement.
|
||||
|
||||
When a scene is displayed, the transient scene list is extended by
|
||||
appending the overlay scene list to it. When display finishes, the
|
||||
transient scene list is replaced with a copy of the master scene
|
||||
list. The scene, show, and hide statements work by modifying both the
|
||||
master and transient scene lists.
|
||||
|
||||
Having three scene lists may seem like needless complexity, but it's
|
||||
useful in implementing transitions and other effects. When we want to
|
||||
fade in an image, we add that image to the master scene list, and that
|
||||
image wrapped in an object that handles the fade effect into the
|
||||
transient scene list. When the scene is then displayed, the image will
|
||||
fade it. The wrapped image on the transient list is then replaced
|
||||
with the image from master list, removing any overhead the fade effect
|
||||
can entail.
|
||||
|
||||
The transient list also helps when displaying UI elements, like
|
||||
dialogue or menus. Finally, the overlay list is used to display things
|
||||
that should always be presented to the user, like load and save
|
||||
buttons.
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
import inspect
|
||||
|
||||
sys.path.append('..')
|
||||
sys.path.append('../module')
|
||||
import renpy
|
||||
|
||||
|
||||
keywords = [
|
||||
r'\bimage\b',
|
||||
r'\bscene\b',
|
||||
r'\bshow\b',
|
||||
r'\bhide\b',
|
||||
r'\binit\b',
|
||||
r'\$',
|
||||
r'\blabel\b',
|
||||
r'\bmenu\b',
|
||||
r'\bset\b',
|
||||
r'\bif\b',
|
||||
r'\bwhile\b',
|
||||
r'\bjump\b',
|
||||
r'\blabel\b',
|
||||
r'\bcall\b',
|
||||
r'\breturn\b',
|
||||
r'\bfrom\b',
|
||||
r'\belif\b',
|
||||
r'\belse\b',
|
||||
r'\bpass\b',
|
||||
r'\bwith\b',
|
||||
r'\bat\b',
|
||||
r'\bpython\b',
|
||||
]
|
||||
|
||||
kwre = '|'.join(keywords)
|
||||
|
||||
def example(m):
|
||||
s = m.group(1)
|
||||
rv = ""
|
||||
pos = 0
|
||||
|
||||
while pos < len(s):
|
||||
|
||||
m = re.compile(r'(?s)"(([^"]|\\.)*)"').match(s, pos)
|
||||
if m:
|
||||
rv += '"<span class="string">%s</span>"' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
m = re.compile(r"(?s)'(([^']|\\.)*)'").match(s, pos)
|
||||
if m:
|
||||
rv += '\'<span class="string">%s</span>\'' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
m = re.compile(r"(?s)(#[^\n]+)").match(s, pos)
|
||||
if m:
|
||||
rv += '<span class="comment">%s</span>' % m.group(1)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
|
||||
m = re.compile(kwre).match(s, pos)
|
||||
if m:
|
||||
rv += '<span class="keyword">%s</span>' % m.group(0)
|
||||
pos = m.end()
|
||||
continue
|
||||
|
||||
|
||||
rv += s[pos]
|
||||
pos += 1
|
||||
|
||||
|
||||
return "<example>" + rv + "</example>"
|
||||
|
||||
def function(m):
|
||||
|
||||
name = m.group(1)
|
||||
|
||||
store = vars(renpy.store)
|
||||
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__
|
||||
|
||||
if inspect.isclass(func):
|
||||
func = func.__init__
|
||||
if func.__doc__:
|
||||
doc += "\n" + func.__doc__
|
||||
|
||||
a, b, c, d = inspect.getargspec(func)
|
||||
args = inspect.formatargspec(a[1:], b, c, d)
|
||||
else:
|
||||
args = inspect.formatargspec(*inspect.getargspec(func))
|
||||
|
||||
args = re.sub(r'<.*?>', '...', args)
|
||||
|
||||
docparas = []
|
||||
|
||||
for p in re.split(r'\n\s*\n', doc):
|
||||
p = p.strip()
|
||||
p = re.sub(r"\@param (\w+):", r'<param>\1</param> -', p)
|
||||
p = "<p>" + p + "</p>"
|
||||
|
||||
docparas.append(p)
|
||||
|
||||
doc = '\n'.join(docparas)
|
||||
|
||||
return '<function name="%(name)s" sig="%(args)s">%(doc)s</function>' % locals()
|
||||
|
||||
def include(m):
|
||||
|
||||
f = file(m.group(1))
|
||||
rv = f.read()
|
||||
f.close()
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
f = file(sys.argv[1])
|
||||
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)
|
||||
s = re.sub(r"<!-- date -->", time.strftime("%04Y-%02m-%02d %02H:%02M"), s)
|
||||
s = re.sub(r"(?s)<example>(.*?)</example>", example, s)
|
||||
|
||||
print s
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
BODY {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
H1 {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
H2 {
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
PRE.example {
|
||||
border: 2px black solid;
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
background: #f0f0f0;
|
||||
padding: 1em;
|
||||
padding-bottom: 0em;
|
||||
}
|
||||
|
||||
DIV.rule {
|
||||
border: 2px black solid;
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
margin-bottom: 1em;
|
||||
|
||||
background: #f0f0ff;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
SPAN.comment {
|
||||
color: #800000;
|
||||
}
|
||||
|
||||
SPAN.string {
|
||||
color: #006000;
|
||||
}
|
||||
|
||||
SPAN.keyword {
|
||||
color: #804000;
|
||||
}
|
||||
|
||||
TD.funcname {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
TD.funcsig {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
DIV.funcbody {
|
||||
margin-left: 5%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
SPAN.param {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
P.prop {
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
SPAN.propname {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
DT {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
DT.code {
|
||||
font-family: monospace;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<!-- First, copy the element. -->
|
||||
<xsl:template match="*" priority="-1">
|
||||
<xsl:copy>
|
||||
<xsl:copy-of select="@*" />
|
||||
<xsl:apply-templates />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<!-- Now, handle some elements specially. -->
|
||||
|
||||
<xsl:template match="doc">
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||
<title><xsl:value-of select="title" /></title>
|
||||
</head>
|
||||
<body>
|
||||
<xsl:apply-templates />
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="toc">
|
||||
<ol>
|
||||
<xsl:for-each select="../h3">
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="." /></xsl:attribute>
|
||||
<xsl:value-of select="."/>
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ol>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="funcindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//function">
|
||||
<xsl:sort select="@name" />
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="@name" /></xsl:attribute>
|
||||
<xsl:value-of select="@name"/>
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="varindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//var">
|
||||
<xsl:sort select="@name" />
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="@name" /></xsl:attribute>
|
||||
<xsl:value-of select="@name"/>
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="styleindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//renpy_style">
|
||||
<xsl:sort select="@name" />
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="@name" /></xsl:attribute>
|
||||
<xsl:value-of select="@name"/>
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="propindex">
|
||||
<ul>
|
||||
<xsl:for-each select="//prop">
|
||||
<xsl:sort select="@name" />
|
||||
<li>
|
||||
<a>
|
||||
<xsl:attribute name="href">#<xsl:value-of select="@name" /></xsl:attribute>
|
||||
<xsl:value-of select="@name"/>
|
||||
</a>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="h3">
|
||||
<a>
|
||||
<xsl:attribute name="name"><xsl:value-of select="." /></xsl:attribute>
|
||||
<h3><xsl:apply-templates /></h3>
|
||||
</a>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="title">
|
||||
<h1><xsl:apply-templates /></h1>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="subtitle">
|
||||
<h2><xsl:apply-templates /></h2>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="def">
|
||||
<b><i><xsl:apply-templates /></i></b>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="example">
|
||||
<pre class="example"><xsl:apply-templates /></pre>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rule">
|
||||
<div class="rule"><xsl:apply-templates /></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="function">
|
||||
<a><xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute></a>
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="top" class="funcname"><xsl:value-of select="@name" /></td>
|
||||
<td class="funcsig"><xsl:value-of select="@sig" />:</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="funcbody">
|
||||
<xsl:apply-templates />
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="param">
|
||||
<span class="param"><xsl:apply-templates /></span>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="prop">
|
||||
<a><xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute></a>
|
||||
<p class="prop">
|
||||
<span class="propname"><xsl:value-of select="@name" /></span>
|
||||
--- <xsl:apply-templates />
|
||||
</p>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="renpy_style">
|
||||
<a><xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute></a>
|
||||
<dt><b><xsl:value-of select="@name" /></b>:</dt>
|
||||
<dd><xsl:apply-templates /></dd>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="renpy_style_inherits">
|
||||
(inherits from <b><xsl:apply-templates /></b>) <br />
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="var">
|
||||
<a><xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute></a>
|
||||
<dt class="var"><b><xsl:value-of select="@name" /></b> = <xsl:value-of select="@value" /></dt>
|
||||
<dd>
|
||||
<xsl:apply-templates />
|
||||
</dd>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,8 @@
|
||||
init:
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
label main_menu:
|
||||
|
||||
$ renpy.renpy.style.write_docs("doc/styles.xml")
|
||||
$ raise "foo"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This program dumps all the text found in the script to the file text.txt.
|
||||
# If on windows, it also tries to show text.txt to the user.
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
import renpy
|
||||
|
||||
def process_block(block, out):
|
||||
|
||||
for fn, ln, text, child in block:
|
||||
|
||||
if text.startswith("$") or text.startswith("python"):
|
||||
continue
|
||||
|
||||
if text.startswith("init"):
|
||||
continue
|
||||
|
||||
if text.startswith("if") or text.startswith("while"):
|
||||
process_block(child, out)
|
||||
continue
|
||||
|
||||
for m in re.finditer(r'"((?:[^\\"]+|\\.)+)"|' +
|
||||
r"'((?:[^\\']+|\\.)+)'", text):
|
||||
|
||||
s = m.group(1) or m.group(2)
|
||||
|
||||
s = re.sub(r'\s+', ' ', s)
|
||||
s = re.sub(r'\\.', ' ', s)
|
||||
|
||||
s = re.sub(r'\{.*?\}', '', s)
|
||||
|
||||
print >>out, s.encode('utf-8')
|
||||
print >>out
|
||||
|
||||
process_block(child, out)
|
||||
|
||||
|
||||
def process(fn, out):
|
||||
|
||||
block = renpy.parser.group_logical_lines(renpy.parser.list_logical_lines(fn))
|
||||
|
||||
process_block(block, out)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
pattern = "game/*.rpy"
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
pattern = sys.argv[1]
|
||||
|
||||
files = glob.glob(pattern)
|
||||
files = [ i for i in files if not i.startswith("common/") ]
|
||||
|
||||
out = file("text.txt", "w")
|
||||
out.write(codecs.BOM_UTF8)
|
||||
|
||||
for fn in files:
|
||||
process(fn, out)
|
||||
|
||||
out.close()
|
||||
|
||||
try:
|
||||
os.startfile('text.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
../../extras/fullscreen.rpy
|
||||
@@ -0,0 +1 @@
|
||||
../../extras/kanamode.rpy
|
||||
@@ -0,0 +1 @@
|
||||
../script.rpy
|
||||
@@ -0,0 +1,102 @@
|
||||
init -10:
|
||||
|
||||
$ config.searchpath.append('moonlight')
|
||||
|
||||
# Set up the size of the screen, and the window title.
|
||||
$ config.screen_width = 800
|
||||
$ config.screen_height = 600
|
||||
$ config.window_title = "A Ren'Py Extras Game"
|
||||
|
||||
# These are positions that can be used inside at clauses. We set
|
||||
# them up here so that they can be used throughout the program.
|
||||
$ left = Position(xpos=0.0, xanchor='left')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
|
||||
# Likewise, we set up some transitions that we can use in with
|
||||
# clauses and statements.
|
||||
$ fade = Fade(.5, 0, .5) # Fade to black and back.
|
||||
$ dissolve = Dissolve(0.5)
|
||||
|
||||
$ wiperight = CropMove(1.0, "wiperight")
|
||||
$ wipeleft = CropMove(1.0, "wipeleft")
|
||||
$ wipeup = CropMove(1.0, "wipeup")
|
||||
$ wipedown = CropMove(1.0, "wipedown")
|
||||
|
||||
$ slideright = CropMove(1.0, "slideright")
|
||||
$ slideleft = CropMove(1.0, "slideleft")
|
||||
$ slideup = CropMove(1.0, "slideup")
|
||||
$ slidedown = CropMove(1.0, "slidedown")
|
||||
|
||||
$ slideawayright = CropMove(1.0, "slideawayright")
|
||||
$ slideawayleft = CropMove(1.0, "slideawayleft")
|
||||
$ slideawayup = CropMove(1.0, "slideawayup")
|
||||
$ slideawaydown = CropMove(1.0, "slideawaydown")
|
||||
|
||||
$ irisout = CropMove(1.0, "irisout")
|
||||
$ irisin = CropMove(1.0, "irisin")
|
||||
|
||||
# Now, we declare the images that are used in the program.
|
||||
|
||||
# Images.
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
image white = Solid((255, 255, 255, 255))
|
||||
image yellow = Solid((255, 255, 200, 255))
|
||||
|
||||
# Opening Sequence.
|
||||
image bigbeach1 = Image("bigbeach1.jpg")
|
||||
image presents = Image("presents.png")
|
||||
|
||||
# Backgrounds.
|
||||
image beach1 = Image("beach1b.jpg")
|
||||
image beach1 mary = Image("beach1c.jpg")
|
||||
image beach1 title = Image("beach1a.jpg")
|
||||
|
||||
image beach2 = Image("beach2.jpg")
|
||||
image beach3 = Image("beach3.jpg")
|
||||
|
||||
image dawn1 = Image("dawn1.jpg")
|
||||
image dawn2 = Image("dawn2.jpg")
|
||||
|
||||
image library = Image("library.jpg")
|
||||
|
||||
# Ending 1.
|
||||
image transfer = Image("transfer.png")
|
||||
image moonpic = Image("moonpic.jpg")
|
||||
image nogirlpic = Image("nogirlpic.jpg")
|
||||
|
||||
|
||||
# Ending 3.
|
||||
image littlemary = Image("littlemary.jpg")
|
||||
|
||||
# Ending 4.
|
||||
image hospital1 = Image("hospital1.jpg")
|
||||
image hospital2 = Image("hospital2.jpg")
|
||||
image hospital3 = Image("hospital3.jpg")
|
||||
image heaven = Image("heaven.jpg")
|
||||
|
||||
# Endings Common
|
||||
image good_ending = Image("ending.jpg")
|
||||
image bad_ending = Image("badending.jpg")
|
||||
|
||||
# Mary.
|
||||
image mary dark confused smiling = Image("mary_dark_confused_smiling.png")
|
||||
image mary dark confused wistful = Image("mary_dark_confused_wistful.png")
|
||||
image mary dark crying = Image("mary_dark_crying.png")
|
||||
image mary dark laughing = Image("mary_dark_laughing.png")
|
||||
image mary dark sad = Image("mary_dark_sad.png")
|
||||
image mary dark smiling = Image("mary_dark_smiling.png")
|
||||
image mary dark vhappy = Image("mary_dark_vhappy.png")
|
||||
image mary dark wistful = Image("mary_dark_wistful.png")
|
||||
|
||||
image mary dawn confused smiling = Image("mary_dawn_confused_smiling.png")
|
||||
image mary dawn confused wistful = Image("mary_dawn_confused_wistful.png")
|
||||
image mary dawn crying = Image("mary_dawn_crying.png")
|
||||
image mary dawn laughing = Image("mary_dawn_laughing.png")
|
||||
image mary dawn sad = Image("mary_dawn_sad.png")
|
||||
image mary dawn smiling = Image("mary_dawn_smiling.png")
|
||||
image mary dawn vhappy = Image("mary_dawn_vhappy.png")
|
||||
image mary dawn wistful = Image("mary_dawn_wistful.png")
|
||||
|
||||
label start:
|
||||
jump example
|
||||
@@ -0,0 +1,16 @@
|
||||
# This file contains a minimal set of style changes needed to have
|
||||
# Ren'Py work with a game that's 640x480 in size.
|
||||
|
||||
init 1:
|
||||
# Change the screen width.
|
||||
$ config.screen_width = 640
|
||||
$ config.screen_height = 480
|
||||
|
||||
# Font sizes.
|
||||
$ style.default.size = 20
|
||||
$ style.button_text.size = 20
|
||||
$ style.file_picker_text.size = 14
|
||||
|
||||
# Change the size of the thumbnails in the file picker.
|
||||
$ library.thumbnail_width = 60
|
||||
$ library.thumbnail_height = 45
|
||||
@@ -0,0 +1,35 @@
|
||||
# This file adds a number of buttons to the lower-right hand corner of
|
||||
# the screen. Three of these buttons jump to the game menu, which
|
||||
# giving quick access to Load, Save, and Prefs. The fourth button
|
||||
# toggles skipping, to make that more convenient.
|
||||
|
||||
init:
|
||||
|
||||
# Give us some space on the right side of the screen.
|
||||
$ style.window.right_margin = 100
|
||||
|
||||
python:
|
||||
|
||||
def toggle_skipping():
|
||||
config.skipping = not config.skipping
|
||||
|
||||
def button_game_menu():
|
||||
|
||||
# to save typing
|
||||
ccinc = renpy.curried_call_in_new_context
|
||||
|
||||
ui.vbox(xpos=0.98, ypos=0.98, xanchor='right', yanchor='bottom')
|
||||
ui.textbutton("Skip", clicked=toggle_skipping)
|
||||
ui.textbutton("Save", clicked=ccinc("_game_menu_save"))
|
||||
ui.textbutton("Load", clicked=ccinc("_game_menu_load"))
|
||||
ui.textbutton("Prefs", clicked=ccinc("_game_menu_preferences"))
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(button_game_menu)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# In this file, we have an example of replacing the default menu with
|
||||
# a custom one. This new menu consists of a series of centered
|
||||
# buttons, with dialogue appearing in a narration window.
|
||||
|
||||
# You can try this one out by dropping it right into the game
|
||||
# directory.
|
||||
|
||||
# The code that implements button menus.
|
||||
init:
|
||||
python:
|
||||
style.create('menu_button', 'button')
|
||||
style.create('menu_button_text', 'button_text')
|
||||
|
||||
def menu(menuitems):
|
||||
|
||||
narration = None
|
||||
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='menu_window')
|
||||
ui.vbox(xanchor='center', xpos=0.5)
|
||||
|
||||
for label, value in menuitems:
|
||||
if value is None:
|
||||
narration = label
|
||||
continue
|
||||
|
||||
ui.textbutton(label,
|
||||
style="menu_button",
|
||||
text_style="menu_button_text",
|
||||
clicked=ui.returns(value))
|
||||
|
||||
ui.close()
|
||||
|
||||
if narration:
|
||||
narrator(narration, interact=False)
|
||||
|
||||
rv = ui.interact()
|
||||
renpy.checkpoint()
|
||||
return rv
|
||||
|
||||
# Styles to make button menus look good.
|
||||
init 1:
|
||||
python hide:
|
||||
style.menu_window.background = None
|
||||
style.menu_window.yminimum = 0
|
||||
style.menu_window.ypos = 0.40
|
||||
style.menu_window.yanchor = 'center'
|
||||
style.menu_window.xfill = True
|
||||
|
||||
style.menu_button.background = Solid((0, 0, 255, 128))
|
||||
style.menu_button.xfill = True
|
||||
style.menu_button.top_padding = 5
|
||||
style.menu_button.bottom_margin = 5
|
||||
|
||||
style.menu_button_text.xpos = 0.5
|
||||
style.menu_button_text.xanchor = 'center'
|
||||
|
||||
style.menu_button_text.hover_color = (255, 255, 0, 255)
|
||||
style.menu_button_text.idle_color = (255, 255, 255, 255)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# This file contains code to automatically switch the game into
|
||||
# fullscreen mode.
|
||||
|
||||
# Please note: Fullscreen mode is broken on some platforms (mostly
|
||||
# virtual windows machines like vmware and virtual PC). So having
|
||||
# this code automatically execute could make your game hard to use
|
||||
# on those platforms.
|
||||
#
|
||||
# The way we work around this is to tell users of these platforms
|
||||
# to press 'f' to toggle fullscreen mode. If the game's running in
|
||||
# windowed mode, the problems don't seem to appear.
|
||||
#
|
||||
# You should probably put words to that effect in some sort of
|
||||
# README file.
|
||||
|
||||
# The first time this code runs, the game switches into fullscreen.
|
||||
# After that, it respects the user's preference.
|
||||
|
||||
init:
|
||||
python:
|
||||
if not persistent.set_fullscreen:
|
||||
persistent.set_fullscreen = True
|
||||
_preferences.fullscreen = True
|
||||
@@ -0,0 +1,262 @@
|
||||
# This extra implements an image gallery, complete with automatic
|
||||
# unlocking of the images that have been shown to the user. The images
|
||||
# are divided into pages, with a fixed number of images on each page.
|
||||
|
||||
# Right now, this is configured to used the images found in the demo.
|
||||
# The images are repeated quite a bit, so that we can get four pages
|
||||
# of them. You probably wouldn't do that if you were using this in a
|
||||
# real game.
|
||||
|
||||
# To see this in action, drop it into the game directory of the
|
||||
# demo.
|
||||
|
||||
# Configuration.
|
||||
init:
|
||||
|
||||
python hide:
|
||||
|
||||
|
||||
# The number of columns and rows of images to show in the
|
||||
# gallery.
|
||||
store.gallery_cols = 3
|
||||
store.gallery_rows = 4
|
||||
|
||||
# The size that each image should be scaled to, and that
|
||||
# thumbnails should be.
|
||||
store.gallery_width = 160
|
||||
store.gallery_height = 120
|
||||
|
||||
# The contents of each of the page. Each of these is a list of
|
||||
# tuples with the first element of the tuple being the image
|
||||
# filename and the second element being the name of the image
|
||||
# that will unlock the image with this filename. (That is,
|
||||
# the name that is used in show or scene statements, as a
|
||||
# string.)
|
||||
#
|
||||
# When displaying an image as a thumbnail, this code first
|
||||
# looks for the file thumbnail_<filename>. If that file
|
||||
# exists, it should be a gallery_width x gallery_height
|
||||
# thumbnail. Otherwise, a thumbnail is automatically
|
||||
# generated, but it may screw up the aspect ratio of the
|
||||
# image.
|
||||
#
|
||||
# You probably want to create thumbnails for most images, to
|
||||
# limit memory consumption.
|
||||
page1 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page2 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page3 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
]
|
||||
|
||||
page4 = [
|
||||
( "9a_happy.png", "eileen happy" ),
|
||||
( "9a_vhappy.png", "eileen vhappy" ),
|
||||
( "9a_concerned.png", "eileen concerned" ),
|
||||
]
|
||||
|
||||
# This is the actual list of gallery pages. It's a list
|
||||
# of tuples, with the first element being the name of the
|
||||
# page, the second being the contents of the page (one of
|
||||
# the lists created above), and the third image being the
|
||||
# image used as the background of the page.
|
||||
store.gallery_pages = [
|
||||
("Backgrounds 1", page1, "washington.jpg"),
|
||||
("Backgrounds 2", page2, "whitehouse.jpg"),
|
||||
("Backgrounds 3", page3, "carillon.jpg"),
|
||||
("Character Art", page4, "washington.jpg"),
|
||||
]
|
||||
|
||||
# A window containing the gallery page buttons.
|
||||
style.create('gallery_pages', 'default')
|
||||
style.gallery_pages.xpos = 0.99
|
||||
style.gallery_pages.xanchor='right'
|
||||
style.gallery_pages.ypos = 0.02
|
||||
style.gallery_pages.yanchor = 'top'
|
||||
|
||||
# A button that links to a gallery page.
|
||||
style.create('gallery_page_button', 'button')
|
||||
style.create('gallery_page_button_text', 'button_text')
|
||||
|
||||
# A button that returns us to from whence we came.
|
||||
style.create('gallery_return_button', 'button')
|
||||
style.create('gallery_return_button_text', 'button_text')
|
||||
|
||||
style.gallery_return_button.xpos = 0.99
|
||||
style.gallery_return_button.xanchor='right'
|
||||
style.gallery_return_button.ypos = 0.98
|
||||
style.gallery_return_button.yanchor = 'bottom'
|
||||
|
||||
|
||||
# The grid containing the gallery image buttons.
|
||||
style.create('gallery_grid', 'default')
|
||||
|
||||
# The style of the buttons in the gallery.
|
||||
style.create('gallery_button', 'default')
|
||||
|
||||
# Right now, the backgrounds are all solids, but in a more
|
||||
# professional version, the insensitive background would
|
||||
# probably be a placeholder image that indicates that a
|
||||
# picture has yet to be unlocked.
|
||||
style.gallery_button.insensitive_background = Solid((192, 192, 192, 255))
|
||||
style.gallery_button.idle_background = Solid((255, 255, 255, 255))
|
||||
style.gallery_button.hover_background = Solid((255, 255, 192, 255))
|
||||
|
||||
style.gallery_button.left_margin = 5
|
||||
style.gallery_button.right_margin = 5
|
||||
style.gallery_button.top_margin = 5
|
||||
style.gallery_button.bottom_margin = 5
|
||||
|
||||
style.gallery_button.left_padding = 5
|
||||
style.gallery_button.right_padding = 5
|
||||
style.gallery_button.top_padding = 5
|
||||
style.gallery_button.bottom_padding = 5
|
||||
|
||||
# The style of the images in the buttons in the gallery.
|
||||
style.create('gallery_button_image', 'default')
|
||||
|
||||
# The style of the images that are being shown to the user.
|
||||
style.create('gallery_image', 'image_placement')
|
||||
|
||||
# The transition used when switching gallery pages.
|
||||
store.gallery_transition = Dissolve(0.5)
|
||||
|
||||
python:
|
||||
|
||||
# The function that actually manages the display of the image
|
||||
# gallery.
|
||||
def gallery():
|
||||
|
||||
page = 0
|
||||
|
||||
while True:
|
||||
|
||||
images = gallery_pages[page][1]
|
||||
ui.image(gallery_pages[page][2])
|
||||
|
||||
# Show the names of the various gallery pages.
|
||||
|
||||
ui.window(style='gallery_pages')
|
||||
ui.vbox(focus="gallery_pages")
|
||||
|
||||
for i in range(0, len(gallery_pages)):
|
||||
if i == page:
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(("page", i))
|
||||
|
||||
ui.textbutton(gallery_pages[i][0],
|
||||
style='gallery_page_button',
|
||||
text_style='gallery_page_button_text',
|
||||
clicked=clicked)
|
||||
ui.close()
|
||||
|
||||
# Show the return button.
|
||||
ui.textbutton('Return',
|
||||
style='gallery_return_button',
|
||||
text_style='gallery_return_button_text',
|
||||
clicked=ui.returns(("return", None)))
|
||||
|
||||
# Show the grid for this page.
|
||||
ui.grid(gallery_cols, gallery_rows, style='gallery_grid')
|
||||
|
||||
# For each grid cell.
|
||||
for i in range(0, gallery_cols * gallery_rows):
|
||||
|
||||
# Fill empty space with nulls.
|
||||
if i >= len(images):
|
||||
ui.null()
|
||||
continue
|
||||
|
||||
# Otherwise, get the filename and spec and see if
|
||||
# we've unlocked it.
|
||||
filename, spec = images[i]
|
||||
|
||||
if spec:
|
||||
spec = spec.split()
|
||||
|
||||
if spec and tuple(spec) not in persistent._seen_images:
|
||||
filename = None
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(('show', filename))
|
||||
|
||||
# Create the button, containing the appropriate
|
||||
# image or a null if we haven't unlocked it yet.
|
||||
|
||||
ui.button(style='gallery_button', clicked=clicked)
|
||||
|
||||
if not filename:
|
||||
ui.null(width=gallery_width, height=gallery_height)
|
||||
else:
|
||||
|
||||
if renpy.loadable("thumbnail_" + filename):
|
||||
ui.image("thumbnail_" + filename,
|
||||
style="gallery_button_image")
|
||||
else:
|
||||
ui.add(im.Scale(filename,
|
||||
gallery_width,
|
||||
gallery_height))
|
||||
|
||||
ui.close()
|
||||
|
||||
# Interact with the user.
|
||||
renpy.transition(gallery_transition)
|
||||
cmd, arg = ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
# Process the user's commands.
|
||||
if cmd == "show":
|
||||
ui.add(Solid((0, 0, 0, 255)))
|
||||
ui.image(arg)
|
||||
ui.saybehavior()
|
||||
renpy.transition(gallery_transition)
|
||||
ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
if cmd == "page":
|
||||
page = arg
|
||||
|
||||
if cmd == "return":
|
||||
renpy.transition(gallery_transition)
|
||||
return
|
||||
|
||||
|
||||
library.main_menu.insert(2, ( "CG Gallery", "gallery"))
|
||||
|
||||
|
||||
label gallery:
|
||||
|
||||
$ gallery()
|
||||
|
||||
jump _main_menu
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# This is an implementation of Kana mode, a mode in which multiple
|
||||
# lines of dialogue are shown at once, in a fullscreen window. A
|
||||
# single line of dialogue is placed onto the screen for each mouse
|
||||
# click. Calling the clear function clears the screen, ensuring that
|
||||
# the next line will appear at the top of the sceen.
|
||||
#
|
||||
# An example of using Kana mode follows the code that implements it.
|
||||
|
||||
|
||||
# This init block contains all of the code needed to implement
|
||||
# Kana mode. You probably don't want to change this... unless you
|
||||
# do. But all of the customization is found below.
|
||||
init -100:
|
||||
python:
|
||||
|
||||
# This is a list of KanaCharacter, line of dialogue tuples.
|
||||
kana_display_list = [ ]
|
||||
|
||||
# Spacings used.
|
||||
kana_vspacing = 0
|
||||
kana_hspacing = 0
|
||||
|
||||
def clear(arg=None):
|
||||
global kana_display_list
|
||||
kana_display_list = [ ]
|
||||
|
||||
def kana_show():
|
||||
|
||||
ui.window(style='say_window')
|
||||
ui.vbox(kana_vspacing)
|
||||
|
||||
for char, what in kana_display_list:
|
||||
char.show(what)
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.saybehavior()
|
||||
ui.interact()
|
||||
renpy.checkpoint()
|
||||
|
||||
# Characters and the narrator should be instances of this
|
||||
# object.
|
||||
class KanaCharacter(object):
|
||||
|
||||
def __init__(self, who,
|
||||
what_prefix='"',
|
||||
what_suffix='"',
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
**properties):
|
||||
|
||||
self.who = who
|
||||
self.what_prefix = what_prefix
|
||||
self.what_suffix = what_suffix
|
||||
self.who_style = who_style
|
||||
self.what_style = what_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what):
|
||||
kana_display_list.append((self, what))
|
||||
kana_show()
|
||||
|
||||
def show(self, what):
|
||||
ui.hbox(kana_hspacing)
|
||||
ui.text(self.who, style=self.who_style,
|
||||
**self.properties)
|
||||
ui.text(self.what_prefix + what + self.what_suffix,
|
||||
style=self.what_style)
|
||||
ui.close()
|
||||
|
||||
|
||||
# This section updates the styles that are used by the Kana mode stuff
|
||||
# so that they work well with Kana mode. The user may want to change
|
||||
# these so that they look more appropriate.
|
||||
init:
|
||||
|
||||
# The space between lines of dialogue.
|
||||
$ kana_vspacing = 10
|
||||
$ kana_hspacing = 10
|
||||
|
||||
$ style.say_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.say_window.xfill = True
|
||||
$ style.say_window.yfill = True
|
||||
$ style.say_window.xmargin = 0
|
||||
$ style.say_window.ymargin = 0
|
||||
$ style.say_window.xpadding = 20
|
||||
$ style.say_window.ypadding = 20
|
||||
|
||||
$ style.say_label.minwidth = 100
|
||||
$ style.say_label.textalign = 1.0
|
||||
|
||||
# This just makes it look nicer.
|
||||
$ style.say_dialogue.rest_indent = 9
|
||||
|
||||
# Adjust the menu to match.
|
||||
$ style.menu_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.menu_window.xfill = True
|
||||
$ style.menu_window.yfill = True
|
||||
$ style.menu_window.xmargin = 0
|
||||
$ style.menu_window.ymargin = 0
|
||||
$ style.menu_window.xpadding = 20
|
||||
$ style.menu_window.ypadding = 20
|
||||
|
||||
$ style.menu.xpos = 110
|
||||
|
||||
# And now, the example. This example can't be simply run, but instead
|
||||
# should serve as a guide to how to get Kana mode working in your own
|
||||
# game.
|
||||
|
||||
# In the init block, declare the characters as KanaCharacters. We also
|
||||
# want to declare the narrator as a special KanaCharacter.
|
||||
init:
|
||||
$ p = KanaCharacter("")
|
||||
$ g = KanaCharacter("Girl:", color=(255, 128, 128, 255))
|
||||
$ narrator = KanaCharacter("",
|
||||
what_prefix='',
|
||||
what_suffix='',
|
||||
what_style='say_thought')
|
||||
|
||||
|
||||
# Now, the actual script of the example. (An excerpt from Moonlight
|
||||
# Walks.) Notice how we place calls to clear in places where the
|
||||
# we want the screen to be cleared.
|
||||
|
||||
# This text is here to serve as an example, and shouldn't be used
|
||||
# in your game.
|
||||
label example:
|
||||
|
||||
show beach2
|
||||
show mary dark wistful
|
||||
|
||||
$ clear()
|
||||
|
||||
p "What can you tell me about your parents?"
|
||||
|
||||
g "Papa and Mama both came across the ocean as settlers when they
|
||||
were just children."
|
||||
|
||||
g "Papa fought in the war. When it was over, he married Mama, and
|
||||
they used his pension to move here from the mainland, and to
|
||||
build us a house."
|
||||
|
||||
g "Together, they farmed the land, and eventually they had
|
||||
children."
|
||||
|
||||
g "I had an older sister and a younger sister. I was the middle
|
||||
child."
|
||||
|
||||
$ clear()
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She paused for a second to collect her thoughts before
|
||||
continuing. This part was taking a strain on her."
|
||||
|
||||
g "When I was ten, an epidemic hit the island."
|
||||
|
||||
g "We came down with it, and so did all of the other families."
|
||||
|
||||
g "My family was too sick to move, but our neighbors, the Millers,
|
||||
sent some of their boys to get help from the mainland."
|
||||
|
||||
"I remember thinking that Miller was the last name of my aunt and
|
||||
uncle, and wondering if they could be related to me."
|
||||
|
||||
g "I don't know what happened to them, but I never heard from them
|
||||
again."
|
||||
|
||||
show mary dark crying
|
||||
|
||||
g "It lasted a week, and then it was over. My sisters... my
|
||||
parents... they all..."
|
||||
|
||||
g "Now I'm the only one left."
|
||||
|
||||
$ clear()
|
||||
|
||||
p "I'm sorry."
|
||||
|
||||
"I didn't know what else to say to a girl that had lost her
|
||||
family, and was obviously broken up about it."
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She nodded in response, wiped her tears, and we once again
|
||||
started walking in silence."
|
||||
|
||||
menu menu_1:
|
||||
"Show me the example again.":
|
||||
jump example
|
||||
|
||||
"I'm done. Let me go.":
|
||||
return
|
||||
@@ -0,0 +1,87 @@
|
||||
# This file replaces most of the game menu navigation with overlay
|
||||
# buttons that jump directly to various parts of the game menu.
|
||||
# Right clicking hides and shows the buttons, rather than calling
|
||||
# up the game menu directly.
|
||||
|
||||
init:
|
||||
python hide:
|
||||
|
||||
# overlay_menu is an object storing information about the
|
||||
# overlay menu state.
|
||||
store.overlay_menu = object()
|
||||
overlay_menu.shown = False
|
||||
|
||||
# overlay_menu is also a new layer, containing the overlay
|
||||
# menu.
|
||||
config.layers.append("overlay_menu")
|
||||
config.overlay_layers.append("overlay_menu")
|
||||
|
||||
|
||||
# This function actually draws the overlay menu.
|
||||
def overlay_menu_func():
|
||||
if overlay_menu.shown:
|
||||
|
||||
ui.layer("overlay_menu")
|
||||
|
||||
ui.vbox(xpos=1.0, xanchor="right", ypos=0.75, yanchor="bottom")
|
||||
|
||||
def button(label, target):
|
||||
ui.textbutton(label, clicked=renpy.curried_call_in_new_context(target))
|
||||
|
||||
button("Load Game", "_game_menu_load")
|
||||
button("Save Game", "_game_menu_save")
|
||||
button("Preferences", "_game_menu_preferences")
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(overlay_menu_func)
|
||||
|
||||
|
||||
# This function toggles the visibility of the overlay menu.
|
||||
def overlay_menu_toggle():
|
||||
shown = not overlay_menu.shown
|
||||
overlay_menu.shown = shown
|
||||
|
||||
# How long should the transitions take?
|
||||
trans_delay = 0.5
|
||||
|
||||
# These transitions assume that the menu is placed on the
|
||||
# right side of the screen. This indicator gives the
|
||||
# fraction of the screen that participates in transitions.
|
||||
|
||||
trans_frac = 0.75
|
||||
|
||||
if shown:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
startcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
startpos=(1.0, 0.0),
|
||||
endcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
endpos=(trans_frac, 0.0),
|
||||
topnew=True)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
else:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
endcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
endpos=(1.0, 0.0),
|
||||
startcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
startpos=(trans_frac, 0.0),
|
||||
topnew=False)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
# Add a new underlay that handles the overlay menu toggle.
|
||||
config.underlay.append(renpy.Keymap(game_menu = overlay_menu_toggle))
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
# This is the annoying readback mode. I implemented this because
|
||||
# people asked for it, not because I like it. I'd prefer you use
|
||||
# rollback, which is better in every way.
|
||||
|
||||
# To use readback, drop this file into your game directory. Readback
|
||||
# will take over when rollback no longer works, so you'll also need to
|
||||
# either reduce config.hard_rollback_limit or set
|
||||
# config.rollback_enabled to false.
|
||||
|
||||
# init:
|
||||
# $ config.hard_rollback_limit = 1
|
||||
|
||||
# Readback works by replacing the default Character and Menu objects
|
||||
# with ones that record what is said in a readback buffer. The user
|
||||
# can then go back, and what has been said will be shown to them
|
||||
# again. Images and the like will not change... if you want that, use
|
||||
# rollback. Heck, use rollback anyway. :-P
|
||||
|
||||
# The number of lines of readback can be changed by assigning an
|
||||
# integer to the readback_limit variable. But leaving it None will
|
||||
# leave the entire game in the buffer. As this is actually fairly
|
||||
# memory-efficent, you should probably leave it as None.
|
||||
|
||||
# Readback uses two styles, readback_dialogue and
|
||||
# readback_thought. Change them to change the color and look of
|
||||
# read-back text.
|
||||
|
||||
# If you want to add your own message, as narration, to the readback
|
||||
# buffer, you can do it by calling the readback function or having
|
||||
# the readback character say it:
|
||||
|
||||
# $ readback('A message for readbackers only.')
|
||||
# readback "A similar message."
|
||||
|
||||
# Note: When using Readback, you can no longer use a string for a
|
||||
# character's name. All dialogue must be routed through Character
|
||||
# objects, if it is to show up in the readback buffer. This limitation
|
||||
# will be fixed in a future version of Ren'Py.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
# The limit of the number of readbacks to keep. None means no limit.
|
||||
readback_limit = None
|
||||
|
||||
# Set this to true to print out the contents of the readback
|
||||
# buffer when it is saved.
|
||||
readback_debug = False
|
||||
|
||||
# Readback styles.
|
||||
style.create('readback_dialogue', 'say_dialogue', '')
|
||||
style.create('readback_thought', 'say_thought', '')
|
||||
|
||||
style.readback_dialogue.color = (255, 128, 128, 255)
|
||||
style.readback_thought.color = (255, 128, 128, 255)
|
||||
|
||||
|
||||
# No user-servicable parts below this line. ######################
|
||||
|
||||
# The readback buffer is a doubly-linked list of readback
|
||||
# objects.
|
||||
class Readback(object):
|
||||
def __init__(self, obj, args):
|
||||
self.older = None
|
||||
self.newer = None
|
||||
self.obj = obj
|
||||
self.args = args
|
||||
|
||||
def show(self):
|
||||
self.obj.readback(*self.args)
|
||||
|
||||
|
||||
readback_oldest = None
|
||||
readback_newest = None
|
||||
readback_count = 0
|
||||
|
||||
|
||||
# This saves a readback entry to the readback buffer.
|
||||
def readback_save(obj, *args):
|
||||
|
||||
store.readback_count += 1
|
||||
|
||||
if readback_limit and readback_count > readback_limit:
|
||||
store.readback_count -= 1
|
||||
readback_oldest.newer.older = None
|
||||
store.readback_oldest = readback_oldest.newer
|
||||
|
||||
rb = Readback(obj, args)
|
||||
|
||||
|
||||
if readback_newest:
|
||||
readback_newest.newer = rb
|
||||
else:
|
||||
store.readback_oldest = rb
|
||||
|
||||
rb.older = readback_newest
|
||||
|
||||
store.readback_newest = rb
|
||||
|
||||
|
||||
if readback_debug:
|
||||
|
||||
print "---- Readback Buffer ----"
|
||||
|
||||
rb = readback_oldest
|
||||
while rb:
|
||||
print rb.obj, rb.args
|
||||
rb = rb.newer
|
||||
|
||||
|
||||
# The rest of this file is replacing the default objects and
|
||||
# functions with versions that save things in the readback
|
||||
# buffer.
|
||||
|
||||
# Save the old character object.
|
||||
readback_OldCharacter = Character
|
||||
readback_OldDynamicCharacter = DynamicCharacter
|
||||
readback_oldmenu = menu
|
||||
|
||||
class Character(readback_OldCharacter):
|
||||
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
readback_OldCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, what)
|
||||
|
||||
def readback(self, what):
|
||||
self.function(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
|
||||
|
||||
class DynamicCharacter(readback_OldDynamicCharacter):
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldDynamicCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
if not self.check_condition():
|
||||
return
|
||||
|
||||
name = renpy.renpy.python.py_eval(self.name_expr)
|
||||
readback_OldDynamicCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, name, what)
|
||||
|
||||
def readback(self, name, what):
|
||||
self.function(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
|
||||
class Sayer(object):
|
||||
def __call__(self, who, what):
|
||||
renpy.display_say(who, what)
|
||||
readback_save(self, who, what)
|
||||
|
||||
def readback(self, who, what):
|
||||
renpy.display_say(who, what,
|
||||
what_style='readback_dialogue',
|
||||
interact=False)
|
||||
|
||||
|
||||
narrator = Character(None, what_style='say_thought')
|
||||
say = Sayer()
|
||||
|
||||
def readback(what):
|
||||
readback_save(narrator, what)
|
||||
|
||||
def menu(menuitems):
|
||||
rv = readback_oldmenu(menuitems)
|
||||
|
||||
text = '\n'.join([ l for l, v in menuitems
|
||||
if v is None or v == rv ])
|
||||
|
||||
readback(text)
|
||||
|
||||
return rv
|
||||
|
||||
# This stuff is involved in entering the readback mode.
|
||||
|
||||
def readback_mode():
|
||||
# Try rollback, first.
|
||||
renpy.rollback()
|
||||
|
||||
# If we made it here, we're into readback mode. So let's
|
||||
# go there now.
|
||||
|
||||
renpy.call_in_new_context("readback")
|
||||
|
||||
# Add in the readback function.
|
||||
config.underlay.append(renpy.Keymap(rollback=readback_mode))
|
||||
|
||||
|
||||
# This label is called in a new context, when the user succesfully
|
||||
# enters Readback mode.
|
||||
label readback:
|
||||
|
||||
# If we have an empty readback buffer, go home.
|
||||
if not readback_newest:
|
||||
return
|
||||
|
||||
python hide:
|
||||
|
||||
rb = readback_newest
|
||||
|
||||
while True:
|
||||
|
||||
rb.show()
|
||||
|
||||
ui.add(renpy.Keymap(rollback=lambda : "older",
|
||||
rollforward=lambda : "newer",
|
||||
dismiss=lambda : "dismiss",
|
||||
))
|
||||
|
||||
res = ui.interact()
|
||||
|
||||
if res == "newer":
|
||||
rb = rb.newer
|
||||
if not rb:
|
||||
break
|
||||
|
||||
elif res == "older":
|
||||
if rb.older:
|
||||
rb = rb.older
|
||||
|
||||
elif res == "dismiss":
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -1,132 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>README</title>
|
||||
<style>
|
||||
|
||||
BODY {
|
||||
background: #fff;
|
||||
color: #444;
|
||||
padding-left: 20%;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
padding-right: 20%;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
DT {
|
||||
font-weight: bold;
|
||||
margin-top: .33em;
|
||||
}
|
||||
|
||||
H2 {
|
||||
color: #000;
|
||||
margin-left: -2.5em;
|
||||
}
|
||||
|
||||
H3 {
|
||||
color: #000;
|
||||
margin-left: -1.5em;
|
||||
}
|
||||
|
||||
.editsection {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="renpy_help"></div>
|
||||
|
||||
|
||||
|
||||
|
||||
<p><a name="Basic_Help" id="Basic_Help"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=renpy/Help&action=edit&section=1" title="Edit section: Basic Help">edit</a>]</span> <span class="mw-headline">Basic Help</span></h2>
|
||||
<p>To advance through the game, <tt>left-click</tt> or press the <tt>space</tt> or <tt>enter</tt> keys. When at a menu, <tt>left-click</tt> to make a choice, or use the arrow keys to select a choice and <tt>enter</tt> to activate it.</p>
|
||||
<p><a name="Game_Menu" id="Game_Menu"></a></p>
|
||||
<h3><span class="editsection">[<a href="/w/index.php?title=renpy/Help&action=edit&section=2" title="Edit section: Game Menu">edit</a>]</span> <span class="mw-headline">Game Menu</span></h3>
|
||||
<p>When playing a game, <tt>right-click</tt> or press the <tt>escape</tt> key to enter the game menu. The game menu gives the following choices:</p>
|
||||
<dl>
|
||||
<dt>Return</dt>
|
||||
<dd>Returns to the game.</dd>
|
||||
<dt>Save Game</dt>
|
||||
<dd>Allows you to save a game by clicking on a save slot.</dd>
|
||||
<dt>Load Game</dt>
|
||||
<dd>Allows you to load a game by clicking on a save slot. Clicking on "Auto" accesses the automatic save slots.</dd>
|
||||
<dt>Preferences</dt>
|
||||
<dd>Changes the game preferences (options/configuration):
|
||||
<dl>
|
||||
<dt>Display</dt>
|
||||
<dd>Switches between fullscreen and windowed mode.</dd>
|
||||
<dt>Transitions</dt>
|
||||
<dd>Controls the display of transitions between game screens.</dd>
|
||||
<dt>Text Speed</dt>
|
||||
<dd>Controls the rate at which text displays. The further to the right this slider is, the faster the text will display. All the way to the right causes text to be shown instantly.</dd>
|
||||
<dt>Joystick</dt>
|
||||
<dd>Lets you control the game using a joystick.</dd>
|
||||
<dt>Skip</dt>
|
||||
<dd>Chooses between skipping messages that have been already seen (in any play through the game), and skipping all messages.</dd>
|
||||
<dt>Begin Skipping</dt>
|
||||
<dd>Returns to the game, while skipping.</dd>
|
||||
<dt>After Choices</dt>
|
||||
<dd>Controls if skipping stops upon reaching a menu.</dd>
|
||||
<dt>Auto-Forward Time</dt>
|
||||
<dd>Controls automatic advance. The further to the left this slider is, the shorter the amount of time before the game advances. All the way to the right means text will never auto-forward.</dd>
|
||||
<dt>Music, Sound, and Voice Volume</dt>
|
||||
<dd>Controls the volume of the Music, Sound effect, and Voice channels, respectively. The further to the right these are, the louder the volume.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Main Menu</dt>
|
||||
<dd>Returns to the main menu, ending the current game.</dd>
|
||||
<dt>Help</dt>
|
||||
<dd>Shows this help screen.</dd>
|
||||
<dt>Quit</dt>
|
||||
<dd>Exits the game; the game will be closed and ended.</dd>
|
||||
</dl>
|
||||
<p><a name="Key_and_Mouse_Bindings" id="Key_and_Mouse_Bindings"></a></p>
|
||||
<h3><span class="editsection">[<a href="/w/index.php?title=renpy/Help&action=edit&section=3" title="Edit section: Key and Mouse Bindings">edit</a>]</span> <span class="mw-headline">Key and Mouse Bindings</span></h3>
|
||||
<dl>
|
||||
<dt>Left-click, Enter</dt>
|
||||
<dd>Advances through the game, activates menu choices, buttons, and sliders.</dd>
|
||||
<dt>Space</dt>
|
||||
<dd>Advances through the game, but does not activate choices.</dd>
|
||||
<dt>Arrow Keys</dt>
|
||||
<dd>Selects menu choices, buttons, and sliders.</dd>
|
||||
<dt>Ctrl</dt>
|
||||
<dd>Causes skipping to occur while the ctrl key is held down.</dd>
|
||||
<dt>Tab</dt>
|
||||
<dd>Toggles skipping, causing it to occur until tab is pressed again.</dd>
|
||||
<dt>Mousewheel-Up, PageUp</dt>
|
||||
<dd>Causes rollback to occur. Rollback reverses the game back in time, showing prior text and even allowing menu choices to be changed.</dd>
|
||||
<dt>Mousewheel-Down, PageDown</dt>
|
||||
<dd>Causes rollforward to occur, cancelling out a previous rollback.</dd>
|
||||
<dt>Right-click, Escape</dt>
|
||||
<dd>Enters the game menu. When in the game menu, returns to the game.</dd>
|
||||
<dt>Middle-click, H</dt>
|
||||
<dd>Hides the text window and other transient displays.</dd>
|
||||
<dt>F</dt>
|
||||
<dd>Toggles fullscreen mode</dd>
|
||||
<dt>S</dt>
|
||||
<dd>Takes a screenshot, saving it in a file named screenshotxxxx.png, where xxxx is a serial number.</dd>
|
||||
<dt>Alt-M, Command-H</dt>
|
||||
<dd>Hides (iconifies) the window.</dd>
|
||||
<dt>Alt-F4, Command-Q</dt>
|
||||
<dd>Quits the game.</dd>
|
||||
<dt>Delete</dt>
|
||||
<dd>When a save slot is selected, deletes that save slot.</dd>
|
||||
<dt>v</dt>
|
||||
<dd>Toggles self-voicing mode, which reads text to the user using an os-supplied
|
||||
speech synthesizer. For more information, please read the <a href="http://www.renpy.org/dev-doc/html/self_voicing.html">self-voicing</a>
|
||||
documentation.</dd>
|
||||
</dl>
|
||||
<p><a name="Legal_Notice" id="Legal_Notice"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=renpy/Help&action=edit&section=4" title="Edit section: Legal Notice">edit</a>]</span> <span class="mw-headline">Legal Notice</span></h2>
|
||||
<p>This game uses source code from a number of open source projects. For a list, and a location where the source code can be downloaded from, please view the LICENSE.txt file in the renpy directory, or visit <a href="http://www.renpy.org/wiki/renpy/License" class="external free" title="http://www.renpy.org/wiki/renpy/License" rel="nofollow">http://www.renpy.org/wiki/renpy/License</a> .</p>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import renpy
|
||||
|
||||
# Do nothing when the editor is invoked.
|
||||
Editor = renpy.editor.Editor
|
||||
@@ -1,4 +0,0 @@
|
||||
import renpy
|
||||
|
||||
# Pass the file off to the system editor (as determined by file associations).
|
||||
Editor = renpy.editor.SystemEditor
|
||||
@@ -1,144 +0,0 @@
|
||||
import binascii
|
||||
|
||||
|
||||
def a2b(a):
|
||||
return binascii.a2b_hex(''.join(a.split()))
|
||||
|
||||
|
||||
resources = {
|
||||
260 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 c8 00 c0 80
|
||||
02 00 00 00 00 00 b8 00 29 00 00 00 00 00 00 00
|
||||
08 00 90 01 00 01 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 01 00 01 50 7f 00 14 00
|
||||
32 00 0e 00 01 00 00 00 ff ff 80 00 4f 00 4b 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 04 08 00 50
|
||||
07 00 07 00 aa 00 0c 00 ea 03 00 00 ff ff 81 00
|
||||
00 00 00 00
|
||||
'''),
|
||||
261 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 c8 00 c0 80
|
||||
04 00 00 00 00 00 b8 00 3a 00 00 00 00 00 00 00
|
||||
08 00 90 01 00 01 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 80 00 81 50 07 00 14 00
|
||||
aa 00 0c 00 ec 03 00 00 ff ff 81 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 01 00 01 50 7f 00 25 00
|
||||
32 00 0e 00 01 00 00 00 ff ff 80 00 4f 00 4b 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 50
|
||||
4a 00 25 00 32 00 0e 00 02 00 00 00 ff ff 80 00
|
||||
43 00 61 00 6e 00 63 00 65 00 6c 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 04 08 00 50 07 00 07 00
|
||||
aa 00 0c 00 eb 03 00 00 ff ff 81 00 00 00 00 00
|
||||
'''),
|
||||
262 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 c8 00 c0 80
|
||||
04 00 00 00 00 00 b8 00 29 00 00 00 00 00 00 00
|
||||
08 00 90 01 00 01 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 01 50 7f 00 14 00
|
||||
32 00 0e 00 06 00 00 00 ff ff 80 00 59 00 65 00
|
||||
73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
04 08 00 50 07 00 07 00 aa 00 0c 00 ed 03 00 00
|
||||
ff ff 81 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
00 00 01 50 07 00 14 00 32 00 0e 00 07 00 00 00
|
||||
ff ff 80 00 4e 00 6f 00 00 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 01 50 4b 00 14 00 32 00 0e 00
|
||||
02 00 00 00 ff ff 80 00 43 00 61 00 6e 00 63 00
|
||||
65 00 6c 00 00 00 00 00
|
||||
'''),
|
||||
263 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 80 00 ca 80
|
||||
03 00 00 00 00 00 e2 00 29 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 81 50 07 00 14 00
|
||||
97 00 0e 00 eb 03 00 00 6d 00 73 00 63 00 74 00
|
||||
6c 00 73 00 5f 00 70 00 72 00 6f 00 67 00 72 00
|
||||
65 00 73 00 73 00 33 00 32 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 04 08 00 50 07 00 07 00
|
||||
d4 00 0c 00 ea 03 00 00 ff ff 81 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 50 a9 00 14 00
|
||||
32 00 0e 00 02 00 00 00 ff ff 80 00 43 00 61 00
|
||||
6e 00 63 00 65 00 6c 00 00 00 00 00
|
||||
'''),
|
||||
264 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 c8 00 c0 80
|
||||
04 00 00 00 00 00 b8 00 3a 00 00 00 00 00 00 00
|
||||
08 00 90 01 00 01 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 a0 00 81 50 07 00 14 00
|
||||
aa 00 0c 00 ec 03 00 00 ff ff 81 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 01 00 01 50 7f 00 25 00
|
||||
32 00 0e 00 01 00 00 00 ff ff 80 00 4f 00 4b 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 50
|
||||
4a 00 25 00 32 00 0e 00 02 00 00 00 ff ff 80 00
|
||||
43 00 61 00 6e 00 63 00 65 00 6c 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 04 08 00 50 07 00 07 00
|
||||
aa 00 0c 00 eb 03 00 00 ff ff 81 00 00 00 00 00
|
||||
'''),
|
||||
265 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 c8 00 c8 80
|
||||
12 00 00 00 00 00 d9 00 fc 00 00 00 00 00 00 00
|
||||
08 00 90 01 00 01 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 01 00 01 50 6c 00 ea 00
|
||||
30 00 0e 00 01 00 00 00 ff ff 80 00 4f 00 4b 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 50
|
||||
a2 00 ea 00 30 00 0e 00 02 00 00 00 ff ff 80 00
|
||||
43 00 61 00 6e 00 63 00 65 00 6c 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 07 00 00 50 06 00 06 00
|
||||
cc 00 4e 00 ff ff ff ff ff ff 80 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 03 00 21 50 48 00 12 00
|
||||
84 00 64 00 03 00 00 00 ff ff 85 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 02 50 0c 00 24 00
|
||||
c0 00 08 00 04 00 00 00 ff ff 82 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 80 00 81 50 0c 00 30 00
|
||||
c0 00 0e 00 05 00 00 00 ff ff 81 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 01 50 9c 00 42 00
|
||||
30 00 0e 00 06 00 00 00 ff ff 80 00 41 00 64 00
|
||||
64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
07 00 00 50 06 00 54 00 cb 00 3c 00 ff ff ff ff
|
||||
ff ff 80 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
03 00 21 50 48 00 61 00 84 00 64 00 07 00 00 00
|
||||
ff ff 85 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
00 00 02 50 0c 00 72 00 c0 00 08 00 08 00 00 00
|
||||
ff ff 82 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
00 00 01 50 9c 00 7e 00 30 00 0e 00 09 00 00 00
|
||||
ff ff 80 00 41 00 64 00 64 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 01 50 06 00 96 00
|
||||
42 00 0e 00 0a 00 00 00 ff ff 80 00 41 00 64 00
|
||||
64 00 20 00 66 00 69 00 6c 00 65 00 2e 00 2e 00
|
||||
2e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
00 00 01 50 4e 00 96 00 42 00 0e 00 0b 00 00 00
|
||||
ff ff 80 00 41 00 64 00 64 00 20 00 6e 00 65 00
|
||||
77 00 20 00 66 00 69 00 6c 00 65 00 2e 00 2e 00
|
||||
2e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
00 00 01 50 06 00 a8 00 42 00 0e 00 0c 00 00 00
|
||||
ff ff 80 00 41 00 64 00 64 00 20 00 66 00 6f 00
|
||||
6c 00 64 00 65 00 72 00 2e 00 2e 00 2e 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 07 00 00 50
|
||||
06 00 c6 00 cc 00 1e 00 ff ff ff ff ff ff 80 00
|
||||
43 00 6f 00 6d 00 6d 00 61 00 6e 00 64 00 20 00
|
||||
6c 00 69 00 6e 00 65 00 3a 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 80 00 81 50 0c 00 d2 00
|
||||
c0 00 0e 00 0e 00 00 00 ff ff 81 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 02 50 0c 00 14 00
|
||||
3c 00 08 00 1e 00 00 00 ff ff 82 00 4f 00 70 00
|
||||
74 00 69 00 6f 00 6e 00 3a 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 02 50 0c 00 63 00
|
||||
3c 00 08 00 46 00 00 00 ff ff 82 00 43 00 6f 00
|
||||
6d 00 6d 00 61 00 6e 00 64 00 3a 00 00 00 00 00
|
||||
'''),
|
||||
270 : a2b('''
|
||||
01 00 ff ff 00 00 00 00 00 00 00 00 48 04 00 44
|
||||
02 00 00 00 00 00 23 01 1a 00 00 00 00 00 00 00
|
||||
08 00 00 00 00 00 4d 00 53 00 20 00 53 00 68 00
|
||||
65 00 6c 00 6c 00 20 00 44 00 6c 00 67 00 00 00
|
||||
00 00 00 00 00 00 00 00 01 00 02 50 00 00 07 00
|
||||
23 01 08 00 ff ff ff ff ff ff 82 00 53 00 74 00
|
||||
61 00 74 00 69 00 63 00 00 00 00 00 00 00 00 00
|
||||
00 00 00 00 00 00 02 40 00 00 12 00 23 01 08 00
|
||||
5f 04 00 00 ff ff 82 00 73 00 74 00 63 00 33 00
|
||||
32 00 00 00 00 00
|
||||
'''),
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
# Checks for various abilities that might be taken away from us by
|
||||
# redistributors.
|
||||
|
||||
init 1 python in ability:
|
||||
|
||||
from store import config
|
||||
import store
|
||||
import store.updater as updater
|
||||
|
||||
import os
|
||||
|
||||
EXECUTABLES = [ "renpy.exe", "renpy.app", "renpy.sh" ]
|
||||
|
||||
# can_distribute - True if we can distribute
|
||||
for i in EXECUTABLES:
|
||||
if not os.path.exists(os.path.join(config.renpy_base, i)):
|
||||
can_distribute = False
|
||||
else:
|
||||
can_distribute = True
|
||||
|
||||
|
||||
# can_update - True if we can update.
|
||||
can_update = updater.can_update() or (store.UPDATE_SIMULATE is not None)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
screen about:
|
||||
|
||||
$ version = renpy.version()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
xfill True
|
||||
|
||||
has vbox xfill True
|
||||
|
||||
add "images/logo.png" xalign 0.5 yoffset -5
|
||||
|
||||
null height 15
|
||||
|
||||
text _("[version!q]") xalign 0.5 bold True
|
||||
|
||||
null height 20
|
||||
|
||||
textbutton _("View license") action interface.OpenLicense() xalign 0.5
|
||||
|
||||
textbutton _("Back") action Jump("front_page") style "l_left_button"
|
||||
|
||||
label about:
|
||||
call screen about
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
label add_file:
|
||||
|
||||
python hide:
|
||||
import os
|
||||
import codecs
|
||||
|
||||
filename = interface.input(_("FILENAME"), _("Enter the name of the script file to create."), filename="withslash", cancel=Jump("navigation"))
|
||||
|
||||
if "." in filename and not filename.endswith(".rpy"):
|
||||
interface.error(_("The filename must have the .rpy extension."), label="navigation")
|
||||
elif "." not in filename:
|
||||
filename += ".rpy"
|
||||
|
||||
path = os.path.join(project.current.gamedir, filename)
|
||||
dir = os.path.dirname(path)
|
||||
|
||||
if os.path.exists(path):
|
||||
interface.error(_("The file already exists."), label="navigation")
|
||||
|
||||
contents = u"\uFEFF"
|
||||
contents += _("# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n")
|
||||
contents += "\n"
|
||||
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
contents = u"\uFEFF"
|
||||
contents += _("# Ren'Py automatically loads all script files ending with .rpy. To use this\n# file, define a label and jump to it from another file.\n")
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(contents.encode("utf-8"))
|
||||
|
||||
jump navigation_refresh
|
||||
|
||||
@@ -1,674 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
init python:
|
||||
ANDROID_NO_RAPT = 0
|
||||
ANDROID_NO_JDK = 1
|
||||
ANDROID_NO_SDK = 2
|
||||
ANDROID_NO_KEY = 3
|
||||
ANDROID_NO_CONFIG = 4
|
||||
ANDROID_OK = 5
|
||||
|
||||
NO_RAPT_TEXT = _("To build Android packages, please download RAPT, unzip it, and place it into the Ren'Py directory. Then restart the Ren'Py launcher.")
|
||||
NO_JDK_TEXT = _("A 32-bit Java Development Kit is required to build Android packages on Windows. The JDK is different from the JRE, so it's possible you have Java without having the JDK.\n\nPlease {a=http://www.oracle.com/technetwork/java/javase/downloads/index.html}download and install the JDK{/a}, then restart the Ren'Py launcher.")
|
||||
NO_SDK_TEXT = _("RAPT has been installed, but you'll need to install the Android SDK before you can build Android packages. Choose Install SDK to do this.")
|
||||
NO_KEY_TEXT = _("RAPT has been installed, but a key hasn't been configured. Please create a new key, or restore android.keystore.")
|
||||
NO_CONFIG_TEXT = _("The current project has not been configured. Use \"Configure\" to configure it before building.")
|
||||
OK_TEXT = _("Choose \"Build\" to build the current project, or attach an Android device and choose \"Build & Install\" to build and install it on the device.")
|
||||
|
||||
PHONE_TEXT = _("Attempts to emulate an Android phone.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
TABLET_TEXT = _("Attempts to emulate an Android tablet.\n\nTouch input is emulated through the mouse, but only when the button is held down. Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
OUYA_TEXT = _("Attempts to emulate a televison-based Android console, like the OUYA or Fire TV.\n\nController input is mapped to the arrow keys, Enter is mapped to the select button, Escape is mapped to the menu button, and PageUp is mapped to the back button.")
|
||||
|
||||
INSTALL_SDK_TEXT = _("Downloads and installs the Android SDK and supporting packages. Optionally, generates the keys required to sign the package.")
|
||||
CONFIGURE_TEXT = _("Configures the package name, version, and other information about this project.")
|
||||
PLAY_KEYS_TEXT = _("Opens the file containing the Google Play keys in the editor.\n\nThis is only needed if the application is using an expansion APK. Read the documentation for more details.")
|
||||
BUILD_TEXT = _("Builds the Android package.")
|
||||
BUILD_AND_INSTALL_TEXT = _("Builds the Android package, and installs it on an Android device connected to your computer.")
|
||||
|
||||
CONNECT_TEXT = _("Connects to an Android device running ADB in TCP/IP mode.")
|
||||
DISCONNECT_TEXT = _("Disconnects from an Android device running ADB in TCP/IP mode.")
|
||||
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
|
||||
def find_rapt():
|
||||
|
||||
global RAPT_PATH
|
||||
|
||||
candidates = [ ]
|
||||
|
||||
RAPT_PATH = os.path.join(config.renpy_base, "rapt")
|
||||
|
||||
if os.path.isdir(RAPT_PATH):
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(RAPT_PATH, "buildlib"))
|
||||
else:
|
||||
RAPT_PATH = None
|
||||
RAPT_PATH is None
|
||||
|
||||
find_rapt()
|
||||
|
||||
import threading
|
||||
|
||||
if RAPT_PATH:
|
||||
import rapt
|
||||
import rapt.build
|
||||
import rapt.configure
|
||||
import rapt.install_sdk
|
||||
import rapt.plat
|
||||
import rapt.interface
|
||||
else:
|
||||
rapt = None
|
||||
|
||||
def AndroidState():
|
||||
"""
|
||||
Determines the state of the android install, and returns it.
|
||||
"""
|
||||
|
||||
if RAPT_PATH is None:
|
||||
return ANDROID_NO_RAPT
|
||||
if renpy.windows and not "JAVA_HOME" in os.environ:
|
||||
return ANDROID_NO_JDK
|
||||
if not os.path.exists(rapt.plat.path("android-sdk/extras/google/play_licensing")):
|
||||
return ANDROID_NO_SDK
|
||||
if not os.path.exists(rapt.plat.path("android.keystore")):
|
||||
return ANDROID_NO_KEY
|
||||
if not os.path.exists(rapt.plat.path("local.properties")):
|
||||
return ANDROID_NO_KEY
|
||||
if not os.path.exists(os.path.join(project.current.path, ".android.json")):
|
||||
return ANDROID_NO_CONFIG
|
||||
return ANDROID_OK
|
||||
|
||||
|
||||
def AndroidStateText(state):
|
||||
"""
|
||||
Returns text corresponding to the state.
|
||||
"""
|
||||
|
||||
if state == ANDROID_NO_RAPT:
|
||||
return NO_RAPT_TEXT
|
||||
if state == ANDROID_NO_JDK:
|
||||
return NO_JDK_TEXT
|
||||
if state == ANDROID_NO_SDK:
|
||||
return NO_SDK_TEXT
|
||||
if state == ANDROID_NO_KEY:
|
||||
return NO_KEY_TEXT
|
||||
if state == ANDROID_NO_CONFIG:
|
||||
return NO_CONFIG_TEXT
|
||||
if state == ANDROID_OK:
|
||||
return OK_TEXT
|
||||
|
||||
def AndroidIfState(state, needed, action):
|
||||
"""
|
||||
If `state` is `needed` or better, `action` is returned. Otherwise,
|
||||
returns None, disabling the button.
|
||||
"""
|
||||
|
||||
if state >= needed:
|
||||
return action
|
||||
else:
|
||||
return None
|
||||
|
||||
class AndroidInterface(object):
|
||||
|
||||
def __init__(self):
|
||||
self.process = None
|
||||
self.filename = project.current.temp_filename("android.txt")
|
||||
|
||||
self.info_msg = ""
|
||||
|
||||
with open(self.filename, "w"):
|
||||
pass
|
||||
|
||||
def log(self, msg):
|
||||
with open(self.filename, "a") as f:
|
||||
f.write("\n")
|
||||
f.write(msg)
|
||||
f.write("\n")
|
||||
|
||||
def info(self, prompt):
|
||||
self.info_msg = prompt
|
||||
interface.processing(prompt, pause=False)
|
||||
self.log(prompt)
|
||||
|
||||
def yesno(self, prompt, submessage=None):
|
||||
return interface.yesno(prompt, submessage=submessage)
|
||||
|
||||
def yesno_choice(self, prompt, default=None):
|
||||
choices = [ (True, "Yes"), (False, "No") ]
|
||||
return interface.choice(prompt, choices, default)
|
||||
|
||||
def terms(self, url, prompt):
|
||||
submessage = _("{a=%s}%s{/a}") % (url, url)
|
||||
|
||||
if not interface.yesno(prompt, submessage=submessage):
|
||||
self.fail("You must accept the terms and conditions to proceed.", edit=False)
|
||||
|
||||
|
||||
def input(self, prompt, empty=None):
|
||||
|
||||
if empty is None:
|
||||
empty = ''
|
||||
|
||||
while True:
|
||||
rv = interface.input(_("QUESTION"), prompt, default=empty, cancel=Jump("android"))
|
||||
|
||||
rv = rv.strip()
|
||||
|
||||
if rv:
|
||||
return rv
|
||||
|
||||
def choice(self, prompt, choices, default):
|
||||
return interface.choice(prompt, choices, default, cancel=Jump("android"))
|
||||
|
||||
def fail(self, prompt, edit=True):
|
||||
self.log(prompt)
|
||||
prompt = re.sub(r'(http://\S+)', r'{a=\1}\1{/a}', prompt)
|
||||
|
||||
# Open android.txt in the editor.
|
||||
if edit:
|
||||
editor.EditAbsolute(self.filename)()
|
||||
|
||||
interface.error(prompt, label="android")
|
||||
|
||||
def success(self, prompt):
|
||||
self.log(prompt)
|
||||
interface.info(prompt, pause=False)
|
||||
|
||||
def final_success(self, prompt):
|
||||
self.log(prompt)
|
||||
interface.info(prompt, label="android")
|
||||
|
||||
def run_yes_thread(self):
|
||||
import time
|
||||
|
||||
try:
|
||||
while self.run_yes:
|
||||
self.process.stdin.write('y\n')
|
||||
self.process.stdin.flush()
|
||||
time.sleep(.2)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def call(self, cmd, cancel=False, use_path=False, yes=False):
|
||||
|
||||
print
|
||||
print " ".join(cmd)
|
||||
|
||||
self.cmd = cmd
|
||||
|
||||
f = open(self.filename, "a")
|
||||
|
||||
f.write("\n\n\n")
|
||||
|
||||
if cancel:
|
||||
cancel_action = self.cancel
|
||||
else:
|
||||
cancel_action = None
|
||||
|
||||
startupinfo = None
|
||||
if renpy.windows:
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
self.yes_thread = None
|
||||
|
||||
try:
|
||||
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action)
|
||||
|
||||
kwargs = { }
|
||||
if yes:
|
||||
kwargs["stdin"] = subprocess.PIPE
|
||||
|
||||
try:
|
||||
self.process = subprocess.Popen(cmd, cwd=RAPT_PATH, stdout=f, stderr=f, startupinfo=startupinfo, **kwargs)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc(file=f)
|
||||
raise
|
||||
|
||||
if yes:
|
||||
import threading
|
||||
self.run_yes = True
|
||||
self.yes_thread = threading.Thread(target=self.run_yes_thread)
|
||||
self.yes_thread.daemon = True
|
||||
self.yes_thread.start()
|
||||
|
||||
renpy.call_screen("android_process", interface=self)
|
||||
finally:
|
||||
f.close()
|
||||
interface.hide_screen()
|
||||
|
||||
if yes and self.yes_thread:
|
||||
self.run_yes = False
|
||||
self.yes_thread.join()
|
||||
|
||||
self.process = None
|
||||
self.yes_thread = None
|
||||
|
||||
def check_process(self):
|
||||
rv = self.process.poll()
|
||||
|
||||
if rv is not None:
|
||||
if rv:
|
||||
raise subprocess.CalledProcessError(rv, self.cmd)
|
||||
else:
|
||||
return True
|
||||
|
||||
def download(self, url, dest):
|
||||
try:
|
||||
d = Downloader(url, dest)
|
||||
cancel_action = [ d.cancel, Jump("android") ]
|
||||
interface.processing(self.info_msg, show_screen=True, cancel=cancel_action, bar_value=DownloaderValue(d))
|
||||
ui.timer(.1, action=d.check, repeat=True)
|
||||
ui.interact()
|
||||
finally:
|
||||
interface.hide_screen()
|
||||
|
||||
def background(self, f):
|
||||
try:
|
||||
t = threading.Thread(target=f)
|
||||
t.start()
|
||||
|
||||
interface.processing(self.info_msg, show_screen=True)
|
||||
|
||||
while t.is_alive():
|
||||
renpy.pause(0)
|
||||
t.join(0.25)
|
||||
|
||||
finally:
|
||||
interface.hide_screen()
|
||||
|
||||
|
||||
def cancel(self):
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
|
||||
renpy.jump("android")
|
||||
|
||||
|
||||
class AndroidBuild(Action):
|
||||
"""
|
||||
Activates an Android build process.
|
||||
"""
|
||||
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
|
||||
def __call__(self):
|
||||
renpy.jump(self.label)
|
||||
|
||||
class LaunchEmulator(Action):
|
||||
|
||||
def __init__(self, emulator, variants):
|
||||
self.emulator = emulator
|
||||
self.variants = variants
|
||||
|
||||
def __call__(self):
|
||||
|
||||
env = {
|
||||
"RENPY_EMULATOR" : self.emulator,
|
||||
"RENPY_VARIANT" : self.variants,
|
||||
}
|
||||
|
||||
p = project.current
|
||||
p.launch(env=env)
|
||||
|
||||
def update_android_json(p, gui):
|
||||
"""
|
||||
Updates .android.json to include the google play information.
|
||||
|
||||
`p`
|
||||
The project to update json for.
|
||||
"""
|
||||
|
||||
p.update_dump(True, gui=gui)
|
||||
|
||||
build = p.dump["build"]
|
||||
|
||||
filename = os.path.join(p.path, ".android.json")
|
||||
|
||||
with open(filename, "r") as f:
|
||||
android_json = json.load(f)
|
||||
|
||||
if "google_play_key" in build:
|
||||
android_json["google_play_key"] = build["google_play_key"]
|
||||
else:
|
||||
android_json.pop("google_play_key", None)
|
||||
|
||||
if "google_play_salt" in build:
|
||||
|
||||
if len(build["google_play_salt"]) != 20:
|
||||
raise Exception("build.google_play_salt must be exactly 20 bytes long.")
|
||||
|
||||
android_json["google_play_salt"] = ", ".join(str(i) for i in build["google_play_salt"])
|
||||
else:
|
||||
android_json.pop("google_play_salt", None)
|
||||
|
||||
|
||||
with open(filename, "w") as f:
|
||||
json.dump(android_json, f)
|
||||
|
||||
def android_build(command, p=None, gui=True):
|
||||
"""
|
||||
This actually builds the package.
|
||||
"""
|
||||
|
||||
if p is None:
|
||||
p = project.current
|
||||
|
||||
update_android_json(p, gui)
|
||||
|
||||
dist = p.temp_filename("android.dist")
|
||||
|
||||
if os.path.exists(dist):
|
||||
shutil.rmtree(dist)
|
||||
|
||||
if gui:
|
||||
reporter = distribute.GuiReporter()
|
||||
rapt_interface = AndroidInterface()
|
||||
else:
|
||||
reporter = distribute.TextReporter()
|
||||
rapt_interface = rapt.interface.Interface()
|
||||
|
||||
distribute.Distributor(p,
|
||||
reporter=reporter,
|
||||
packages=[ 'android' ],
|
||||
build_update=False,
|
||||
noarchive=True,
|
||||
packagedest=dist,
|
||||
report_success=False,
|
||||
)
|
||||
|
||||
with interface.nolinks():
|
||||
rapt.build.build(rapt_interface, dist, command)
|
||||
|
||||
# The android support can stick unicode into os.environ. Fix that.
|
||||
init 100 python:
|
||||
for k, v in list(os.environ.items()):
|
||||
if not isinstance(v, str):
|
||||
os.environ[k] = renpy.fsencode(v)
|
||||
|
||||
screen android_process(interface):
|
||||
|
||||
zorder 100
|
||||
|
||||
default ft = FileTail(interface.filename)
|
||||
|
||||
text "[ft.text!q]":
|
||||
size 14
|
||||
color TEXT
|
||||
font "Roboto-Light.ttf"
|
||||
xpos 75
|
||||
ypos 350
|
||||
|
||||
timer .1 action interface.check_process repeat True
|
||||
timer .2 action ft.update repeat True
|
||||
|
||||
|
||||
screen android:
|
||||
|
||||
default tt = Tooltip(None)
|
||||
$ state = AndroidState()
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Android: [project.current.name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Emulation:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Phone"):
|
||||
action LaunchEmulator("touch", "small phone touch android")
|
||||
hovered tt.Action(PHONE_TEXT)
|
||||
|
||||
textbutton _("Tablet"):
|
||||
action LaunchEmulator("touch", "medium tablet touch android")
|
||||
hovered tt.Action(TABLET_TEXT)
|
||||
|
||||
textbutton _("Television / OUYA"):
|
||||
action LaunchEmulator("tv", "small tv ouya android")
|
||||
hovered tt.Action(OUYA_TEXT)
|
||||
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Install SDK & Create Keys"):
|
||||
action AndroidIfState(state, ANDROID_NO_SDK, Jump("android_installsdk"))
|
||||
hovered tt.Action(INSTALL_SDK_TEXT)
|
||||
|
||||
textbutton _("Configure"):
|
||||
action AndroidIfState(state, ANDROID_NO_CONFIG, Jump("android_configure"))
|
||||
hovered tt.Action(CONFIGURE_TEXT)
|
||||
|
||||
textbutton _("Build Package"):
|
||||
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build"))
|
||||
hovered tt.Action(BUILD_TEXT)
|
||||
|
||||
textbutton _("Build & Install"):
|
||||
action AndroidIfState(state, ANDROID_OK, AndroidBuild("android_build_and_install"))
|
||||
hovered tt.Action(BUILD_AND_INSTALL_TEXT)
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Other:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Remote ADB Connect"):
|
||||
action AndroidIfState(state, ANDROID_OK, Jump("android_connect"))
|
||||
hovered tt.Action(CONNECT_TEXT)
|
||||
|
||||
textbutton _("Remote ADB Disconnect"):
|
||||
action AndroidIfState(state, ANDROID_OK, Jump("android_disconnect"))
|
||||
hovered tt.Action(DISCONNECT_TEXT)
|
||||
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
add SPACER
|
||||
|
||||
if tt.value:
|
||||
text tt.value
|
||||
else:
|
||||
text AndroidStateText(state)
|
||||
|
||||
|
||||
textbutton _("Back") action Jump("front_page") style "l_left_button"
|
||||
|
||||
|
||||
label android:
|
||||
|
||||
if RAPT_PATH is None:
|
||||
$ interface.yesno(_("Before packaging Android apps, you'll need to download RAPT, the Ren'Py Android Packaging Tool. Would you like to download RAPT now?"), no=Jump("front_page"))
|
||||
$ add_dlc("rapt", restart=True)
|
||||
|
||||
call screen android
|
||||
|
||||
|
||||
label android_installsdk:
|
||||
|
||||
python:
|
||||
with interface.nolinks():
|
||||
rapt.install_sdk.install_sdk(AndroidInterface())
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_configure:
|
||||
|
||||
python:
|
||||
rapt.configure.configure(AndroidInterface(), project.current.path)
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_build:
|
||||
|
||||
$ android_build([ 'release' ])
|
||||
|
||||
jump android
|
||||
|
||||
|
||||
label android_build_and_install:
|
||||
|
||||
$ android_build([ 'release', 'install' ])
|
||||
|
||||
jump android
|
||||
|
||||
label android_connect:
|
||||
|
||||
python hide:
|
||||
|
||||
if persistent.connect_address is not None:
|
||||
address = persistent.connect_address
|
||||
else:
|
||||
address = ""
|
||||
|
||||
while True:
|
||||
address = interface.input(
|
||||
_("Remote ADB Address"),
|
||||
_("Please enter the IP address and port number to connect to, in the form \"192.168.1.143:5555\". Consult your device's documentation to determine if it supports remote ADB, and if so, the address and port to use."),
|
||||
default=address,
|
||||
cancel=Jump("android"),
|
||||
)
|
||||
|
||||
address = address.strip()
|
||||
|
||||
try:
|
||||
host, port = address.split(":")
|
||||
except:
|
||||
interface.error(_("Invalid remote ADB address"), _("The address must contain one exactly one ':'."), label=None)
|
||||
continue
|
||||
|
||||
if " " in host:
|
||||
interface.error(_("Invalid remote ADB address"), _("The host may not contain whitespace."), label=None)
|
||||
continue
|
||||
|
||||
try:
|
||||
int(port)
|
||||
except:
|
||||
interface.error(_("Invalid remote ADB address"), _("The port must be a number."), label=None)
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
persistent.connect_address = address
|
||||
|
||||
rapt_interface = AndroidInterface()
|
||||
rapt.build.connect(rapt_interface, address)
|
||||
|
||||
jump android
|
||||
|
||||
label android_disconnect:
|
||||
|
||||
python hide:
|
||||
|
||||
rapt_interface = AndroidInterface()
|
||||
rapt.build.disconnect(rapt_interface)
|
||||
|
||||
jump android
|
||||
|
||||
init python:
|
||||
|
||||
def android_build_command():
|
||||
ap = renpy.arguments.ArgumentParser()
|
||||
ap.add_argument("project", help="The path to the project directory.")
|
||||
ap.add_argument("command", help="Commands to pass to ant. (Try 'release' 'install'.)", nargs='+')
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
p = project.Project(args.project)
|
||||
|
||||
android_build(args.command, p=p, gui=False)
|
||||
|
||||
return False
|
||||
|
||||
renpy.arguments.register_command("android_build", android_build_command)
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
# Ren'Py archiver. This builds a Ren'Py archive file, and the
|
||||
# associated index file. These files are really easy to
|
||||
# reverse-engineer, but are probably better than nothing.
|
||||
|
||||
init python in archiver:
|
||||
|
||||
import sys
|
||||
import random
|
||||
import glob
|
||||
|
||||
from cPickle import dumps, HIGHEST_PROTOCOL
|
||||
|
||||
|
||||
class Archive(object):
|
||||
"""
|
||||
Adds files from disk to a rpa archive.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
|
||||
# The archive file.
|
||||
self.f = open(filename, "wb")
|
||||
|
||||
# The index to the file.
|
||||
self.index = _dict()
|
||||
|
||||
# A fixed key minimizes difference between archive versions.
|
||||
self.key = 0x42424242
|
||||
|
||||
padding = "RPA-3.0 XXXXXXXXXXXXXXXX XXXXXXXX\n"
|
||||
self.f.write(padding)
|
||||
|
||||
def add(self, name, path):
|
||||
"""
|
||||
Adds a file to the archive.
|
||||
"""
|
||||
|
||||
self.index[name] = _list()
|
||||
|
||||
with open(path, "rb") as df:
|
||||
data = df.read()
|
||||
dlen = len(data)
|
||||
|
||||
# Pad.
|
||||
padding = "Made with Ren'Py."
|
||||
self.f.write(padding)
|
||||
|
||||
offset = self.f.tell()
|
||||
|
||||
self.f.write(data)
|
||||
|
||||
self.index[name].append((offset ^ self.key, dlen ^ self.key, ""))
|
||||
|
||||
def close(self):
|
||||
|
||||
indexoff = self.f.tell()
|
||||
|
||||
self.f.write(dumps(self.index, HIGHEST_PROTOCOL).encode("zlib"))
|
||||
|
||||
self.f.seek(0)
|
||||
self.f.write("RPA-3.0 %016x %08x\n" % (indexoff, self.key))
|
||||
|
||||
self.f.close()
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
|
||||
# http://www.csn.ul.ie/~caolan/publink/winresdump/winresdump/doc/pefile.html
|
||||
# Contains a reasonable description of the format.
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import array
|
||||
import pefile # @UnresolvedImport
|
||||
|
||||
# This class performs various operations on memory-loaded binary files,
|
||||
# including modifications.
|
||||
class BinFile(object):
|
||||
|
||||
def set_u32(self, addr, value):
|
||||
self.a[addr+0] = (value >> 0) & 0xff
|
||||
self.a[addr+1] = (value >> 8) & 0xff
|
||||
self.a[addr+2] = (value >> 16) & 0xff
|
||||
self.a[addr+3] = (value >> 24) & 0xff
|
||||
|
||||
def u32(self):
|
||||
addr = self.addr
|
||||
rv = self.a[addr]
|
||||
rv |= self.a[addr+1] << 8
|
||||
rv |= self.a[addr+2] << 16
|
||||
rv |= self.a[addr+3] << 24
|
||||
self.addr += 4
|
||||
return rv
|
||||
|
||||
def u16(self):
|
||||
addr = self.addr
|
||||
rv = self.a[addr]
|
||||
rv |= self.a[addr+1] << 8
|
||||
self.addr += 2
|
||||
return rv
|
||||
|
||||
def u8(self):
|
||||
rv = self.a[self.addr]
|
||||
self.addr += 1
|
||||
return rv
|
||||
|
||||
def name(self):
|
||||
c = self.u16()
|
||||
|
||||
rv = u""
|
||||
for _i in range(c):
|
||||
rv += unichr(self.u16())
|
||||
|
||||
return rv
|
||||
|
||||
def seek(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def tostring(self):
|
||||
return self.a.tostring()
|
||||
|
||||
def substring(self, start, len): #@ReservedAssignment
|
||||
return self.a[start:start+len].tostring()
|
||||
|
||||
def __init__(self, data):
|
||||
self.a = array.array('B')
|
||||
self.a.fromstring(data)
|
||||
|
||||
##############################################################################
|
||||
# These functions parse data out of the file. In these functions, offset is
|
||||
# relative to the start of the file.
|
||||
|
||||
# The virtual address of the resource segment.
|
||||
resource_virtual = 0
|
||||
|
||||
# This parses a data block out of the resources.
|
||||
def parse_data(bf, offset):
|
||||
bf.seek(offset)
|
||||
data_offset = bf.u32()
|
||||
data_len = bf.u32()
|
||||
code_page = bf.u32()
|
||||
bf.u32()
|
||||
|
||||
l = [ ]
|
||||
|
||||
bf.seek(data_offset - resource_virtual)
|
||||
for _i in range(data_len):
|
||||
l.append(chr(bf.u8()))
|
||||
|
||||
return (code_page, "".join(l))
|
||||
|
||||
# This parses a resource directory.
|
||||
def parse_directory(bf, offset):
|
||||
|
||||
bf.seek(offset)
|
||||
char = bf.u32() #@UnusedVariable
|
||||
timedate = bf.u32() #@UnusedVariable
|
||||
major = bf.u16() #@UnusedVariable
|
||||
minor = bf.u16() #@UnusedVariable
|
||||
n_named = bf.u16()
|
||||
n_id = bf.u16()
|
||||
|
||||
entries = [ ]
|
||||
|
||||
for _i in range(n_named + n_id):
|
||||
entries.append((bf.u32(), bf.u32()))
|
||||
|
||||
rv = { }
|
||||
|
||||
for name, value in entries:
|
||||
|
||||
if name & 0x80000000:
|
||||
bf.seek((name & 0x7fffffff))
|
||||
name = bf.name()
|
||||
|
||||
if value & 0x80000000:
|
||||
value = parse_directory(bf, value & 0x7fffffff)
|
||||
else:
|
||||
value = parse_data(bf, value)
|
||||
|
||||
rv[name] = value
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
##############################################################################
|
||||
# This utility function displays the tree of resources that have been loaded.
|
||||
def show_resources(d, prefix):
|
||||
|
||||
if not isinstance(d, dict):
|
||||
print prefix, "Codepage", d[0], "length", len(d[1])
|
||||
return
|
||||
|
||||
for k in d:
|
||||
print prefix, k
|
||||
show_resources(d[k], prefix + " ")
|
||||
|
||||
##############################################################################
|
||||
# These functions repack the resources into a new resource segment. Here,
|
||||
# the offset is relative to the start of the resource segment.
|
||||
|
||||
class Packer(object):
|
||||
|
||||
def pack(self, d):
|
||||
self.data = ""
|
||||
self.data_offset = 0
|
||||
|
||||
self.entries = ""
|
||||
self.entries_offset = 0
|
||||
|
||||
head = self.pack_dict(d, 0)
|
||||
|
||||
self.data = ""
|
||||
self.data_offset = len(head) + len(self.entries)
|
||||
|
||||
self.entries = ""
|
||||
self.entries_offset = len(head)
|
||||
|
||||
return self.pack_dict(d, 0) + self.entries + self.data
|
||||
|
||||
def pack_name(self, s):
|
||||
rv = self.data_offset + len(self.data)
|
||||
|
||||
l = len(s)
|
||||
s = s.encode("utf-16le")
|
||||
self.data += struct.pack("<H", l) + s + "\0\0"
|
||||
|
||||
return rv
|
||||
|
||||
def pack_tuple(self, t):
|
||||
codepage, data = t
|
||||
|
||||
rv = len(self.entries) + self.entries_offset
|
||||
|
||||
if len(self.data) % 2:
|
||||
self.data += "P"
|
||||
|
||||
daddr = len(self.data) + self.data_offset
|
||||
|
||||
self.entries += struct.pack("<IIII", daddr + resource_virtual, len(data), codepage, 0)
|
||||
self.data += data
|
||||
|
||||
# if len(self.data) % 1 == 1:
|
||||
# self.data += 'P'
|
||||
|
||||
return rv
|
||||
|
||||
def pack_dict(self, d, offset):
|
||||
name_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, unicode))
|
||||
id_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, int))
|
||||
|
||||
rv = struct.pack("<IIHHHH", 0, 0, 4, 0, len(name_entries), len(id_entries))
|
||||
|
||||
offset += len(rv) + (len(name_entries) + len(id_entries)) * 8
|
||||
|
||||
rest = ""
|
||||
|
||||
for (name, value) in name_entries + id_entries:
|
||||
if isinstance(name, unicode):
|
||||
name = 0x80000000 | self.pack_name(name)
|
||||
|
||||
if isinstance(value, dict):
|
||||
addr = offset | 0x80000000
|
||||
packed = self.pack_dict(value, offset)
|
||||
offset += len(packed)
|
||||
rest += packed
|
||||
else:
|
||||
addr = self.pack_tuple(value)
|
||||
|
||||
rv += struct.pack("<II", name, addr)
|
||||
|
||||
return rv + rest
|
||||
|
||||
##############################################################################
|
||||
# This loads in an icon file, and returns a dictionary that is suitable for
|
||||
# use in the resources of an exe file.
|
||||
def load_icon(fn):
|
||||
f = BinFile(file(fn, "rb").read())
|
||||
|
||||
f.seek(0)
|
||||
f.u16()
|
||||
f.u16()
|
||||
count = f.u16()
|
||||
|
||||
rv = { }
|
||||
rv[3] = { }
|
||||
|
||||
group = struct.pack("HHH", 0, 1, count)
|
||||
|
||||
for i in range(count):
|
||||
width = f.u8()
|
||||
height = f.u8()
|
||||
colors = f.u8()
|
||||
reserved = f.u8()
|
||||
planes = f.u16()
|
||||
bpp = f.u16()
|
||||
size = f.u32()
|
||||
offset = f.u32()
|
||||
|
||||
addr = f.addr
|
||||
f.seek(offset + 16)
|
||||
if not f.u32():
|
||||
f.set_u32(offset + 20, 0)
|
||||
|
||||
rv[3][i + 1] = { 0 : (1252, f.substring(offset, size)) }
|
||||
|
||||
|
||||
group += struct.pack("BBBBHHIH", width, height, colors, reserved,
|
||||
planes, bpp, size, i + 1)
|
||||
|
||||
f.seek(addr)
|
||||
|
||||
rv[14] = { 1 : { 0 : (1252, group) } }
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
##############################################################################
|
||||
# This is the main function that should be called externally, that copies over
|
||||
# the icons.
|
||||
def change_icons(oldexe, icofn):
|
||||
global resource_virtual
|
||||
|
||||
pe = pefile.PE(oldexe)
|
||||
|
||||
for s in pe.sections:
|
||||
if s.Name == ".rsrc\0\0\0":
|
||||
rsrc_section = s
|
||||
break
|
||||
else:
|
||||
raise Exception("Couldn't find resource section.")
|
||||
|
||||
base = rsrc_section.PointerToRawData
|
||||
resource_virtual = rsrc_section.VirtualAddress
|
||||
|
||||
physize = rsrc_section.SizeOfRawData
|
||||
virsize = rsrc_section.Misc_VirtualSize
|
||||
|
||||
f = file(oldexe, "rb")
|
||||
f.seek(base)
|
||||
data = f.read(physize)
|
||||
f.close()
|
||||
|
||||
bf = BinFile(data)
|
||||
|
||||
resources = parse_directory(bf, 0)
|
||||
# show_resources(resources, "")
|
||||
resources.update(load_icon(icofn))
|
||||
# show_resources(resources, "")
|
||||
|
||||
rsrc = Packer().pack(resources)
|
||||
|
||||
alignment = pe.OPTIONAL_HEADER.SectionAlignment
|
||||
|
||||
# print "Alignment is", alignment
|
||||
|
||||
if len(rsrc) % alignment:
|
||||
pad = alignment - (len(rsrc) % alignment)
|
||||
padding = "RENPYVNE" * (pad / 8 + 1)
|
||||
padding = padding[:pad]
|
||||
rsrc += padding
|
||||
|
||||
newsize = len(rsrc)
|
||||
|
||||
rsrc_section.Misc_VirtualSize += newsize - virsize
|
||||
rsrc_section.Misc_PhysicalAddress += newsize - virsize
|
||||
rsrc_section.Misc += newsize - virsize
|
||||
rsrc_section.SizeOfRawData += newsize - physize
|
||||
|
||||
pe.OPTIONAL_HEADER.SizeOfInitializedData += newsize - physize
|
||||
|
||||
# Resource size.
|
||||
pe.OPTIONAL_HEADER.DATA_DIRECTORY[2].Size += newsize - virsize
|
||||
|
||||
# Compute the total size of the image.
|
||||
total_size = 0
|
||||
|
||||
for i in pe.sections:
|
||||
|
||||
sec_size = i.Misc_VirtualSize
|
||||
sec_size = sec_size - (sec_size % alignment) + alignment
|
||||
|
||||
total_size += sec_size
|
||||
|
||||
pe.OPTIONAL_HEADER.SizeOfImage = total_size
|
||||
|
||||
return pe.write()[:base] + rsrc
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
f = file(sys.argv[3], "wb")
|
||||
f.write(change_icons(sys.argv[1], sys.argv[2]))
|
||||
f.close()
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
init python:
|
||||
import random
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
|
||||
def theme_names():
|
||||
"""
|
||||
Gets a list of all of the theme names we know about.
|
||||
"""
|
||||
|
||||
names = list(theme_data.THEME.keys())
|
||||
names.sort(key=lambda a : a.lower())
|
||||
|
||||
return names
|
||||
|
||||
def scheme_names(theme):
|
||||
"""
|
||||
Gets a list of the color scheme names corresponding to the given
|
||||
theme.
|
||||
"""
|
||||
|
||||
names = list(theme_data.THEME[theme].keys())
|
||||
names.sort(key=lambda a : a.lower())
|
||||
|
||||
return names
|
||||
|
||||
def theme_yinitial():
|
||||
names = theme_names()
|
||||
|
||||
if len(names) < 2:
|
||||
return 0
|
||||
|
||||
return 1.0 * names.index(current_theme) / (len(names) - 1)
|
||||
|
||||
def scheme_yinitial():
|
||||
names = scheme_names(current_theme)
|
||||
|
||||
if len(names) < 2:
|
||||
return 0
|
||||
|
||||
return 1.0 * names.index(current_scheme) / (len(names) - 1)
|
||||
|
||||
def pick_theme(theme, scheme):
|
||||
"""
|
||||
Returns a theme and scheme that are similar to `theme` and `scheme`.
|
||||
|
||||
If the theme is known, picks it, otherwise picks a random theme. If
|
||||
the scheme is known for that theme, picks it, otherwise picks a
|
||||
random scheme that is known for the current theme.
|
||||
"""
|
||||
|
||||
if theme not in theme_data.THEME:
|
||||
theme = random.choice(list(theme_data.THEME))
|
||||
|
||||
schemes = theme_data.THEME[theme]
|
||||
|
||||
if scheme not in schemes:
|
||||
if theme in schemes:
|
||||
scheme = theme
|
||||
else:
|
||||
scheme = random.choice(list(schemes))
|
||||
|
||||
return theme, scheme
|
||||
|
||||
def implement_theme(theme, scheme):
|
||||
"""
|
||||
Implement the current theme.
|
||||
|
||||
This function uses non-public APIs.
|
||||
"""
|
||||
|
||||
global showing_theme, showing_scheme
|
||||
|
||||
if theme == showing_theme and scheme == showing_scheme:
|
||||
return
|
||||
|
||||
renpy.style.restore(style_backup)
|
||||
exec theme_data.THEME[theme][scheme] in globals()
|
||||
|
||||
# Rebuild the style cache.
|
||||
renpy.style.rebuild(False)
|
||||
|
||||
# Bust the render cache, so we re-evaluate the styles.
|
||||
renpy.display.interface.kill_textures()
|
||||
|
||||
showing_theme = theme
|
||||
showing_scheme = scheme
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
showing_theme = None
|
||||
showing_scheme = None
|
||||
|
||||
class SetTheme(Action):
|
||||
def __init__(self, theme):
|
||||
self.theme = theme
|
||||
|
||||
def __call__(self):
|
||||
global current_theme
|
||||
global current_scheme
|
||||
|
||||
current_theme, current_scheme = pick_theme(self.theme, current_scheme)
|
||||
|
||||
implement_theme(current_theme, current_scheme)
|
||||
renpy.restart_interaction()
|
||||
|
||||
def get_selected(self):
|
||||
return current_theme == self.theme
|
||||
|
||||
class SetScheme(Action):
|
||||
def __init__(self, scheme):
|
||||
self.scheme = scheme
|
||||
|
||||
def __call__(self):
|
||||
global current_theme
|
||||
global current_scheme
|
||||
|
||||
current_theme, current_scheme = pick_theme(current_theme, self.scheme)
|
||||
|
||||
implement_theme(current_theme, current_scheme)
|
||||
renpy.restart_interaction()
|
||||
|
||||
def get_selected(self):
|
||||
return current_scheme == self.scheme
|
||||
|
||||
class PreviewTheme(Action):
|
||||
|
||||
def __init__(self, theme, scheme):
|
||||
self.theme = theme
|
||||
self.scheme = scheme
|
||||
|
||||
def __call__(self):
|
||||
theme, scheme = pick_theme(self.theme, self.scheme)
|
||||
implement_theme(theme, scheme)
|
||||
|
||||
def unhovered(self):
|
||||
if (showing_theme == self.theme and showing_scheme == self.scheme):
|
||||
implement_theme(current_theme, current_scheme)
|
||||
|
||||
def value_changed(value):
|
||||
return None
|
||||
|
||||
##########################################################################
|
||||
# Code to update options.rpy
|
||||
|
||||
def list_logical_lines(filename):
|
||||
"""
|
||||
This reads in filename, and turns it into a list of logical
|
||||
lines.
|
||||
"""
|
||||
|
||||
f = codecs.open(filename, "rb", "utf-8")
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
# The result.
|
||||
rv = [ ]
|
||||
|
||||
# The current position we're looking at in the buffer.
|
||||
pos = 0
|
||||
|
||||
# Looping over the lines in the file.
|
||||
while pos < len(data):
|
||||
|
||||
# The line that we're building up.
|
||||
line = ""
|
||||
|
||||
# The number of open parenthesis there are right now.
|
||||
parendepth = 0
|
||||
|
||||
# Looping over the characters in a single logical line.
|
||||
while pos < len(data):
|
||||
|
||||
c = data[pos]
|
||||
|
||||
if c == '\n' and not parendepth:
|
||||
rv.append(line)
|
||||
|
||||
pos += 1
|
||||
# This helps out error checking.
|
||||
line = ""
|
||||
break
|
||||
|
||||
# Backslash/newline.
|
||||
if c == "\\" and data[pos+1] == "\n":
|
||||
pos += 2
|
||||
line += "\\\n"
|
||||
continue
|
||||
|
||||
# Parenthesis.
|
||||
if c in ('(', '[', '{'):
|
||||
parendepth += 1
|
||||
|
||||
if c in ('}', ']', ')') and parendepth:
|
||||
parendepth -= 1
|
||||
|
||||
# Comments.
|
||||
if c == '#':
|
||||
while data[pos] != '\n':
|
||||
line += data[pos]
|
||||
pos += 1
|
||||
|
||||
continue
|
||||
|
||||
# Strings.
|
||||
if c in ('"', "'", "`"):
|
||||
delim = c
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
escape = False
|
||||
|
||||
while pos < len(data):
|
||||
|
||||
c = data[pos]
|
||||
|
||||
if escape:
|
||||
escape = False
|
||||
pos += 1
|
||||
line += c
|
||||
continue
|
||||
|
||||
if c == delim:
|
||||
pos += 1
|
||||
line += c
|
||||
break
|
||||
|
||||
if c == '\\':
|
||||
escape = True
|
||||
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
line += c
|
||||
pos += 1
|
||||
|
||||
if line:
|
||||
rv.append(line)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def switch_theme():
|
||||
"""
|
||||
Switches the theme of the current project to the current theme
|
||||
and color scheme. (As set in current_theme and current_scheme.)
|
||||
"""
|
||||
|
||||
theme_code = theme_data.THEME[current_theme][current_scheme]
|
||||
|
||||
# Did we change the file at all?
|
||||
changed = False
|
||||
|
||||
filename = os.path.join(project.current.path, "game/options.rpy")
|
||||
|
||||
with codecs.open(filename + ".new", "wb", "utf-8") as out:
|
||||
|
||||
for l in list_logical_lines(filename):
|
||||
|
||||
m = re.match(r' theme.(\w+)\(', l)
|
||||
if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS):
|
||||
l = " " + theme_code
|
||||
changed = True
|
||||
|
||||
out.write(l + "\n")
|
||||
|
||||
if changed:
|
||||
try:
|
||||
os.unlink(filename + ".bak")
|
||||
except:
|
||||
pass
|
||||
|
||||
os.rename(filename, filename + ".bak")
|
||||
os.rename(filename + ".new", filename)
|
||||
else:
|
||||
os.unlink(filename + ".new")
|
||||
interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much."))
|
||||
|
||||
|
||||
# Now give the theme's screen-ops function a chance to make any
|
||||
# necessary changes to the screens.rpy file
|
||||
filename = os.path.join(project.current.path, "game/screens.rpy")
|
||||
changed = False
|
||||
|
||||
try:
|
||||
with codecs.open(filename + ".new", "wb", "utf-8") as out:
|
||||
lines = list_logical_lines(filename)
|
||||
|
||||
lines = theme_data.THEME_SCREEN_OPERATIONS[current_theme](lines)
|
||||
|
||||
if lines != None:
|
||||
for l in lines:
|
||||
out.write(l + "\n")
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
try:
|
||||
os.unlink(filename + ".bak")
|
||||
except:
|
||||
pass
|
||||
|
||||
os.rename(filename, filename + ".bak")
|
||||
os.rename(filename + ".new", filename)
|
||||
|
||||
except Exception as inst:
|
||||
try:
|
||||
# just in case
|
||||
os.unlink(filename + ".new")
|
||||
except:
|
||||
pass
|
||||
pass
|
||||
|
||||
def make_style_backup():
|
||||
"""
|
||||
Call this to back up the styles. This should be called in a
|
||||
translate python block in each translation.
|
||||
"""
|
||||
|
||||
global style_backup
|
||||
style_backup = renpy.style.backup()
|
||||
|
||||
translate None python:
|
||||
make_style_backup()
|
||||
|
||||
screen theme_demo:
|
||||
|
||||
window:
|
||||
style "gm_root"
|
||||
xpadding 5
|
||||
ypadding 5
|
||||
|
||||
grid 1 1:
|
||||
xfill True
|
||||
style_group "prefs"
|
||||
|
||||
vbox:
|
||||
|
||||
frame:
|
||||
style_group "pref"
|
||||
has vbox
|
||||
|
||||
label _("Display")
|
||||
textbutton _("Window") action SelectedIf(True)
|
||||
textbutton _("Fullscreen") action ui.returns(None)
|
||||
textbutton _("Planetarium") action None
|
||||
|
||||
|
||||
frame:
|
||||
style_group "pref"
|
||||
has vbox
|
||||
|
||||
label _("Sound Volume")
|
||||
bar style "slider" value .75 range 1.0 changed value_changed
|
||||
|
||||
textbutton "Test":
|
||||
action ui.returns(None)
|
||||
style "soundtest_button"
|
||||
|
||||
|
||||
init -2 python:
|
||||
style.pref_frame.xfill = True
|
||||
style.pref_frame.xmargin = 5
|
||||
style.pref_frame.top_margin = 5
|
||||
|
||||
style.pref_vbox.xfill = True
|
||||
|
||||
style.pref_button.size_group = "pref"
|
||||
style.pref_button.xalign = 1.0
|
||||
|
||||
style.pref_slider.xmaximum = 192
|
||||
style.pref_slider.xalign = 1.0
|
||||
|
||||
style.soundtest_button.xalign = 1.0
|
||||
|
||||
|
||||
screen choose_theme:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Choose Theme")
|
||||
|
||||
hbox:
|
||||
yfill True
|
||||
|
||||
# Theme selector.
|
||||
frame:
|
||||
style "l_indent"
|
||||
bottom_margin HALF_SPACER_HEIGHT
|
||||
xmaximum 225
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Theme") style "l_label_small"
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
yinitial theme_yinitial()
|
||||
mousewheel True
|
||||
|
||||
has vbox
|
||||
|
||||
for i in theme_names():
|
||||
textbutton "[i]":
|
||||
action SetTheme(i)
|
||||
hovered PreviewTheme(i, current_scheme)
|
||||
style "l_list2"
|
||||
|
||||
|
||||
# Color scheme selector.
|
||||
frame:
|
||||
style "l_indent"
|
||||
bottom_margin HALF_SPACER_HEIGHT
|
||||
xmaximum 225
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Color Scheme") style "l_label_small"
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
yinitial scheme_yinitial()
|
||||
|
||||
has vbox
|
||||
|
||||
for i in scheme_names(current_theme):
|
||||
textbutton "[i]":
|
||||
action SetScheme(i)
|
||||
hovered PreviewTheme(current_theme, i)
|
||||
style "l_list2"
|
||||
|
||||
|
||||
# Preview
|
||||
frame:
|
||||
style "l_default"
|
||||
background Frame(PATTERN, 0, 0, tile=True)
|
||||
xpadding 5
|
||||
ypadding 5
|
||||
|
||||
xfill True
|
||||
yfill True
|
||||
xmargin 20
|
||||
bottom_margin 6
|
||||
|
||||
use theme_demo
|
||||
|
||||
textbutton _("Back") action Jump("front_page") style "l_left_button"
|
||||
textbutton _("Continue") action Return(True) style "l_right_button"
|
||||
|
||||
|
||||
label choose_theme_callable:
|
||||
|
||||
python:
|
||||
current_theme, current_scheme = pick_theme(None, None)
|
||||
implement_theme(current_theme, current_scheme)
|
||||
|
||||
call screen choose_theme
|
||||
|
||||
python hide:
|
||||
with interface.error_handling("changing the theme"):
|
||||
switch_theme()
|
||||
|
||||
return
|
||||
|
||||
label choose_theme:
|
||||
call choose_theme_callable
|
||||
jump front_page
|
||||
@@ -1,260 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
init python:
|
||||
class PackageToggle(Action):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def get_selected(self):
|
||||
return self.name in project.current.data['packages']
|
||||
|
||||
def __call__(self):
|
||||
packages = project.current.data['packages']
|
||||
|
||||
if self.name in packages:
|
||||
packages.remove(self.name)
|
||||
else:
|
||||
packages.append(self.name)
|
||||
|
||||
project.current.save_data()
|
||||
renpy.restart_interaction()
|
||||
|
||||
class DataToggle(Action):
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
|
||||
def get_selected(self):
|
||||
return project.current.data[self.field]
|
||||
|
||||
def __call__(self):
|
||||
project.current.data[self.field] = not project.current.data[self.field]
|
||||
|
||||
project.current.save_data()
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
DEFAULT_BUILD_INFO = """
|
||||
|
||||
## This section contains information about how to build your project into
|
||||
## distribution files.
|
||||
init python:
|
||||
|
||||
## The name that's used for directories and archive files. For example, if
|
||||
## this is 'mygame-1.0', the windows distribution will be in the
|
||||
## directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
|
||||
build.directory_name = "PROJECTNAME-1.0"
|
||||
|
||||
## The name that's uses for executables - the program that users will run
|
||||
## to start the game. For example, if this is 'mygame', then on Windows,
|
||||
## users can click 'mygame.exe' to start the game.
|
||||
build.executable_name = "PROJECTNAME"
|
||||
|
||||
## If True, Ren'Py will include update information into packages. This
|
||||
## allows the updater to run.
|
||||
build.include_update = False
|
||||
|
||||
## File patterns:
|
||||
##
|
||||
## The following functions take file patterns. File patterns are case-
|
||||
## insensitive, and matched against the path relative to the base
|
||||
## directory, with and without a leading /. If multiple patterns match,
|
||||
## the first is used.
|
||||
##
|
||||
##
|
||||
## In a pattern:
|
||||
##
|
||||
## /
|
||||
## Is the directory separator.
|
||||
## *
|
||||
## Matches all characters, except the directory separator.
|
||||
## **
|
||||
## Matches all characters, including the directory separator.
|
||||
##
|
||||
## For example:
|
||||
##
|
||||
## *.txt
|
||||
## Matches txt files in the base directory.
|
||||
## game/**.ogg
|
||||
## Matches ogg files in the game directory or any of its subdirectories.
|
||||
## **.psd
|
||||
## Matches psd files anywhere in the project.
|
||||
|
||||
## Classify files as None to exclude them from the built distributions.
|
||||
|
||||
build.classify('**~', None)
|
||||
build.classify('**.bak', None)
|
||||
build.classify('**/.**', None)
|
||||
build.classify('**/#**', None)
|
||||
build.classify('**/thumbs.db', None)
|
||||
|
||||
## To archive files, classify them as 'archive'.
|
||||
|
||||
# build.classify('game/**.png', 'archive')
|
||||
# build.classify('game/**.jpg', 'archive')
|
||||
|
||||
## Files matching documentation patterns are duplicated in a mac app
|
||||
## build, so they appear in both the app and the zip file.
|
||||
|
||||
build.documentation('*.html')
|
||||
build.documentation('*.txt')
|
||||
"""
|
||||
|
||||
# A screen that displays a file or directory name, and
|
||||
# lets the user change it,
|
||||
#
|
||||
# title
|
||||
# The title of the link.
|
||||
# value
|
||||
# The value of the field.
|
||||
screen distribute_name:
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text title
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
text "[value!q]"
|
||||
|
||||
add SPACER
|
||||
|
||||
|
||||
screen build_distributions:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Build Distributions: [project.current.name!q]")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
|
||||
# Left side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
use distribute_name(
|
||||
title=_("Directory Name:"),
|
||||
value=project.current.dump["build"]["directory_name"])
|
||||
|
||||
use distribute_name(
|
||||
title=_("Executable Name:"),
|
||||
value=project.current.dump["build"]["executable_name"])
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Actions:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
|
||||
has vbox
|
||||
|
||||
textbutton _("Edit options.rpy") action editor.Edit("game/options.rpy", check=True)
|
||||
textbutton _("Refresh") action Jump("build_distributions")
|
||||
|
||||
|
||||
# Right side.
|
||||
frame:
|
||||
style "l_indent"
|
||||
xmaximum ONEHALF
|
||||
xfill True
|
||||
|
||||
has vbox
|
||||
|
||||
add SEPARATOR2
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
text _("Build Packages:")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
$ packages = project.current.dump["build"]["packages"]
|
||||
|
||||
for pkg in packages:
|
||||
if not pkg["hidden"]:
|
||||
$ description = pkg["description"]
|
||||
textbutton "[description!q]" action PackageToggle(pkg["name"]) style "l_checkbox"
|
||||
|
||||
add SPACER
|
||||
|
||||
if project.current.dump["build"]["include_update"]:
|
||||
textbutton _("Build Updates") action DataToggle("build_update") style "l_checkbox"
|
||||
|
||||
|
||||
textbutton _("Back") action Jump("front_page") style "l_left_button"
|
||||
textbutton _("Build") action Jump("distribute") style "l_right_button"
|
||||
|
||||
label build_update_dump:
|
||||
python:
|
||||
project.current.update_dump(True)
|
||||
|
||||
if project.current.dump.get("error", False):
|
||||
interface.error(_("Errors were detected when running the project. Please ensure the project runs without errors before building distributions."))
|
||||
|
||||
return
|
||||
|
||||
label build_distributions:
|
||||
|
||||
call build_update_dump
|
||||
|
||||
if not project.current.dump["build"]["directory_name"]:
|
||||
jump build_missing
|
||||
|
||||
call screen build_distributions
|
||||
|
||||
label build_missing:
|
||||
|
||||
python hide:
|
||||
|
||||
interface.yesno(_("Your project does not contain build information. Would you like to add build information to the end of options.rpy?"), yes=Return(True), no=Jump("front_page"))
|
||||
|
||||
build_info = DEFAULT_BUILD_INFO.replace("PROJECTNAME", project.current.name)
|
||||
|
||||
with open(os.path.join(project.current.path, "game", "options.rpy"), "a") as f:
|
||||
f.write(build_info)
|
||||
|
||||
jump build_distributions
|
||||
@@ -1,150 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
init python:
|
||||
|
||||
import urllib2
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
class Downloader(object):
|
||||
|
||||
def __init__(self, url, dest):
|
||||
"""
|
||||
Downloads `url` to `dest`, providing progress reports
|
||||
as necessary.
|
||||
"""
|
||||
|
||||
self.url = url
|
||||
|
||||
# The destination file, and the destination temp file.
|
||||
self.dest = dest
|
||||
self.tmp = dest + ".tmp"
|
||||
|
||||
# Open the tmpfile.
|
||||
self.safe_unlink(self.tmp)
|
||||
self.tmpfile = open(self.tmp, "wb")
|
||||
|
||||
# Set by the thread to indicate progress (ranges from 0.0 to 1.0).
|
||||
self.progress = 0.0
|
||||
|
||||
# This is set to true by cancel() to indicate the download should be cancelled.
|
||||
self.cancelled = False
|
||||
|
||||
# Set on succes or failure.
|
||||
self.success = False
|
||||
self.failure = None
|
||||
|
||||
try:
|
||||
# Open the URL.
|
||||
self.urlfile = urllib2.urlopen(url)
|
||||
|
||||
t = threading.Thread(target=self.thread)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
except Exception as e:
|
||||
self.failure = str(e)
|
||||
|
||||
def thread(self):
|
||||
|
||||
try:
|
||||
count = 0
|
||||
|
||||
if "content-length" in self.urlfile.headers:
|
||||
length = int(self.urlfile.headers["content-length"])
|
||||
else:
|
||||
length = 0
|
||||
|
||||
while not self.cancelled:
|
||||
|
||||
data = self.urlfile.read(65536)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
count += len(data)
|
||||
self.tmpfile.write(data)
|
||||
|
||||
if length > 0:
|
||||
self.progress = 1.0 * count / length
|
||||
|
||||
self.tmpfile.close()
|
||||
|
||||
if self.cancelled:
|
||||
return
|
||||
|
||||
if length and count != length:
|
||||
self.failure = "Download length does not match content length."
|
||||
return
|
||||
|
||||
self.safe_unlink(self.dest)
|
||||
os.rename(self.tmp, self.dest)
|
||||
|
||||
self.success = True
|
||||
|
||||
except Exception as e:
|
||||
self.failure = str(e)
|
||||
|
||||
|
||||
def safe_unlink(self, fn):
|
||||
if os.path.exists(fn):
|
||||
os.unlink(fn)
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Cancels the download.
|
||||
"""
|
||||
|
||||
self.cancelled = True
|
||||
|
||||
def check(self):
|
||||
"""
|
||||
Returns True if the download is finished, False if it was cancelled,
|
||||
None if it's ongoing, and raises an Exception if the download has failed.
|
||||
"""
|
||||
|
||||
if self.success:
|
||||
return True
|
||||
if self.cancelled:
|
||||
return False
|
||||
if self.failure:
|
||||
raise Exception("Downloading {} to {} failed: {}".format(self.url, self.dest, self.failure))
|
||||
|
||||
return None
|
||||
|
||||
class DownloaderValue(BarValue):
|
||||
"""
|
||||
A BarValue that reports the progress of a background download.
|
||||
"""
|
||||
|
||||
def __init__(self, d):
|
||||
self.downloader = d
|
||||
|
||||
def get_adjustment(self):
|
||||
self.adjustment = ui.adjustment(value=0.0, range=1.0, adjustable=False)
|
||||
return self.adjustment
|
||||
|
||||
def periodic(self, st):
|
||||
self.adjustment.change(self.downloader.progress)
|
||||
return .25
|
||||
|
||||
@@ -1,498 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
# Editor Support.
|
||||
#
|
||||
# This contains code for scanning for editors, and for allowing the user to
|
||||
# select an editor.
|
||||
|
||||
init 1 python in editor:
|
||||
|
||||
from store import Action, renpy, config, persistent
|
||||
import store.project as project
|
||||
import store.updater as updater
|
||||
import store.interface as interface
|
||||
import store.util as util
|
||||
import store
|
||||
|
||||
import glob
|
||||
import re
|
||||
import traceback
|
||||
import os
|
||||
import os.path
|
||||
|
||||
# Should we set up the editor?
|
||||
set_editor = "RENPY_EDIT_PY" not in os.environ
|
||||
|
||||
# A map from editor name to EditorInfo object.
|
||||
editors = { }
|
||||
|
||||
class EditorInfo(object):
|
||||
def __init__(self, filename):
|
||||
# The path to the editor info file.
|
||||
self.filename = filename
|
||||
|
||||
# The name of the editor.
|
||||
self.name = os.path.basename(filename)[:-len(".edit.py")]
|
||||
|
||||
# The time the editor file was last modified. We use this
|
||||
# to decide if we should update the editors mat when we
|
||||
# have multiple versions of an editor in contention.
|
||||
self.mtime = os.path.getmtime(filename)
|
||||
|
||||
def scan_editor(filename):
|
||||
"""
|
||||
Inserts an editor into editors if there isn't a newer
|
||||
editor there already.
|
||||
"""
|
||||
|
||||
ei = EditorInfo(filename)
|
||||
|
||||
if ei.name in editors:
|
||||
if editors[ei.name].mtime >= ei.mtime:
|
||||
return
|
||||
|
||||
editors[ei.name] = ei
|
||||
|
||||
def scan_all():
|
||||
"""
|
||||
Finds all *.edit.py files, and uses them to populate the list
|
||||
of editors.
|
||||
"""
|
||||
|
||||
editors.clear()
|
||||
|
||||
for d in [ config.renpy_base, persistent.projects_directory ]:
|
||||
if d is None:
|
||||
continue
|
||||
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
|
||||
for i in util.listdir(d):
|
||||
i = os.path.join(d, i)
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
|
||||
for j in util.listdir(i):
|
||||
j = os.path.join(i, j)
|
||||
|
||||
if j.endswith(".edit.py"):
|
||||
scan_editor(j)
|
||||
|
||||
########################################################################
|
||||
|
||||
# A list of fancy_editor_info objects.
|
||||
fancy_editors = [ ]
|
||||
|
||||
# The error message to display if an editor failed to start.
|
||||
error_message = None
|
||||
|
||||
class FancyEditorInfo(object):
|
||||
"""
|
||||
Represents an editor in the selection screen. A FEI knows if the
|
||||
editor is installed or not.
|
||||
"""
|
||||
|
||||
def __init__(self, priority, name, description=None, dlc=None, dldescription=None, error_message=None):
|
||||
# The priority of the editor. Lower priorities will come later
|
||||
# in the list.
|
||||
self.priority = priority
|
||||
|
||||
# The name of the editor.
|
||||
self.name = name
|
||||
|
||||
# Is the editor installed?
|
||||
self.installed = name in editors
|
||||
|
||||
# The dlc needed to install the editor.
|
||||
self.dlc = dlc
|
||||
|
||||
# A description of the editor.
|
||||
self.description = description
|
||||
|
||||
# A description of the download.
|
||||
self.dldescription = dldescription
|
||||
|
||||
# An error message to display if the editor failed to start.
|
||||
self.error_message = error_message
|
||||
|
||||
def fancy_scan_editors():
|
||||
"""
|
||||
Creates the list of FancyEditorInfo objects.
|
||||
"""
|
||||
|
||||
global fancy_editors
|
||||
|
||||
scan_all()
|
||||
|
||||
fei = fancy_editors = [ ]
|
||||
|
||||
# Editra.
|
||||
ED = _("{b}Recommended.{/b} A beta editor with an easy to use interface and features that aid in development, such as spell-checking. Editra currently lacks the IME support required for Chinese, Japanese, and Korean text input.")
|
||||
EDL = _("{b}Recommended.{/b} A beta editor with an easy to use interface and features that aid in development, such as spell-checking. Editra currently lacks the IME support required for Chinese, Japanese, and Korean text input. On Linux, Editra requires wxPython.")
|
||||
|
||||
if renpy.windows:
|
||||
dlc = "editra-windows"
|
||||
installed = os.path.exists(os.path.join(config.basedir, "editra/Editra-win32"))
|
||||
description = ED
|
||||
error_message = None
|
||||
elif renpy.macintosh:
|
||||
dlc = "editra-mac"
|
||||
installed = os.path.exists(os.path.join(config.basedir, "editra/Editra-mac.app"))
|
||||
description = ED
|
||||
error_message = None
|
||||
else:
|
||||
dlc = "editra-linux"
|
||||
installed = os.path.exists(os.path.join(config.basedir, "editra/Editra"))
|
||||
description = EDL
|
||||
error_message = _("This may have occured because wxPython is not installed on this system.")
|
||||
|
||||
e = FancyEditorInfo(
|
||||
1,
|
||||
"Editra",
|
||||
description,
|
||||
dlc,
|
||||
_("Up to 22 MB download required."),
|
||||
error_message)
|
||||
|
||||
e.installed = e.installed or installed
|
||||
|
||||
fei.append(e)
|
||||
|
||||
# jEdit
|
||||
fei.append(FancyEditorInfo(
|
||||
2,
|
||||
"jEdit",
|
||||
_("A mature editor that requires Java."),
|
||||
"jedit",
|
||||
_("1.8 MB download required."),
|
||||
_("This may have occured because Java is not installed on this system."),
|
||||
))
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
3,
|
||||
"System Editor",
|
||||
_("Invokes the editor your operating system has associated with .rpy files."),
|
||||
None))
|
||||
|
||||
for k in editors:
|
||||
if k in [ "Editra", "jEdit", "System Editor", "None" ]:
|
||||
continue
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
4,
|
||||
k,
|
||||
None,
|
||||
None))
|
||||
|
||||
fei.append(FancyEditorInfo(
|
||||
5,
|
||||
"None",
|
||||
_("Prevents Ren'Py from opening a text editor."),
|
||||
None))
|
||||
|
||||
fei.sort(key=lambda e : (e.priority, e.name.lower()))
|
||||
|
||||
# If we're in a linux distro or something, assume all editors work.
|
||||
if not updater.can_update():
|
||||
for i in fei:
|
||||
i.installed = True
|
||||
|
||||
def fancy_activate_editor(default=False):
|
||||
"""
|
||||
Activates the editor in persistent.editor, if it's installed.
|
||||
|
||||
`default`
|
||||
|
||||
"""
|
||||
|
||||
global error_message
|
||||
|
||||
fancy_scan_editors()
|
||||
|
||||
if default and not set_editor:
|
||||
renpy.editor.init()
|
||||
return
|
||||
|
||||
for i in fancy_editors:
|
||||
|
||||
if i.name == persistent.editor:
|
||||
if i.installed and i.name in editors:
|
||||
ei = editors[i.name]
|
||||
os.environ["RENPY_EDIT_PY"] = renpy.fsencode(os.path.abspath(ei.filename))
|
||||
error_message = i.error_message
|
||||
break
|
||||
|
||||
else:
|
||||
persistent.editor = None
|
||||
os.environ.pop("RENPY_EDIT_PY", None)
|
||||
|
||||
renpy.editor.init()
|
||||
|
||||
def fancy_select_editor(name):
|
||||
"""
|
||||
Selects the editor with the given name, installing it if it
|
||||
doesn't already exist.
|
||||
"""
|
||||
|
||||
for fe in fancy_editors:
|
||||
if fe.name == name:
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
if not fe.installed:
|
||||
|
||||
# We don't check the status of this because fancy_activate_editor
|
||||
# will fail if the editor is not installed.
|
||||
store.add_dlc(fe.dlc)
|
||||
|
||||
persistent.editor = fe.name
|
||||
fancy_activate_editor()
|
||||
|
||||
return persistent.editor is not None
|
||||
|
||||
# Call fancy_activate_editor on startup.
|
||||
fancy_activate_editor(True)
|
||||
|
||||
class SelectEditor(Action):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def get_selected(self):
|
||||
return persistent.editor == self.name
|
||||
|
||||
def __call__(self):
|
||||
return fancy_select_editor(self.name)
|
||||
|
||||
|
||||
def check_editor():
|
||||
"""
|
||||
Checks to see if an editor is set. If one isn't asks the user to
|
||||
select one.
|
||||
|
||||
Returns True if the editor is set and editing can proceed, and
|
||||
False otherwise.
|
||||
"""
|
||||
|
||||
if not set_editor:
|
||||
return True
|
||||
|
||||
if persistent.editor:
|
||||
return True
|
||||
|
||||
return renpy.invoke_in_new_context(renpy.call_screen, "editor")
|
||||
|
||||
##########################################################################
|
||||
# Editing actions.
|
||||
|
||||
|
||||
class Edit(Action):
|
||||
alt = "Edit [text]."
|
||||
|
||||
def __init__(self, filename, line=None, check=False):
|
||||
"""
|
||||
An action that opens the given line of the given file in a
|
||||
text editor.
|
||||
|
||||
`filename`
|
||||
The filename to open.
|
||||
|
||||
`line`
|
||||
The line in the file to jump to.
|
||||
|
||||
`check`
|
||||
If true, we will check to see if the file exists, and gray
|
||||
out the box if it does not.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
self.line = line
|
||||
self.check = check
|
||||
|
||||
def get_sensitive(self):
|
||||
if not self.check:
|
||||
return True
|
||||
|
||||
fn = project.current.unelide_filename(self.filename)
|
||||
return os.path.exists(fn)
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not self.get_sensitive():
|
||||
return
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
fn = project.current.unelide_filename(self.filename)
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
|
||||
e.begin()
|
||||
e.open(fn, line=self.line)
|
||||
e.end()
|
||||
|
||||
except Exception, e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
class EditAbsolute(Action):
|
||||
def __init__(self, filename, line=None, check=False):
|
||||
"""
|
||||
An action that lets us edit an absolutely-specified filename.
|
||||
|
||||
`filename`
|
||||
The filename to open.
|
||||
|
||||
`line`
|
||||
The line in the file to jump to.
|
||||
|
||||
`check`
|
||||
If true, we will check to see if the file exists, and gray
|
||||
out the box if it does not.
|
||||
"""
|
||||
|
||||
self.filename = filename
|
||||
self.line = line
|
||||
self.check = check
|
||||
|
||||
def get_sensitive(self):
|
||||
if not self.check:
|
||||
return True
|
||||
|
||||
return os.path.exists(self.filename)
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not self.get_sensitive():
|
||||
return
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
|
||||
e.begin()
|
||||
e.open(self.filename, line=self.line)
|
||||
e.end()
|
||||
|
||||
except Exception, e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
|
||||
class EditAll(Action):
|
||||
"""
|
||||
Opens all scripts that are part of the current project in a web browser.
|
||||
"""
|
||||
|
||||
alt = "Edit [text]."
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def __call__(self):
|
||||
|
||||
if not check_editor():
|
||||
return
|
||||
|
||||
scripts = project.current.script_files()
|
||||
scripts = [ i for i in scripts if not i.startswith("game/tl/") ]
|
||||
scripts.sort(key=lambda fn : fn.lower())
|
||||
|
||||
for fn in [ "game/screens.rpy", "game/options.rpy", "game/script.rpy" ]:
|
||||
if fn in scripts:
|
||||
scripts.remove(fn)
|
||||
scripts.insert(0, fn)
|
||||
|
||||
try:
|
||||
|
||||
e = renpy.editor.editor
|
||||
e.begin()
|
||||
|
||||
for fn in scripts:
|
||||
fn = project.current.unelide_filename(fn)
|
||||
e.open(fn)
|
||||
|
||||
e.end()
|
||||
|
||||
except Exception, e:
|
||||
exception = traceback.format_exception_only(type(e), e)[-1][:-1]
|
||||
renpy.invoke_in_new_context(interface.error, _("An exception occured while launching the text editor:\n[exception!q]"), error_message, exception=exception)
|
||||
|
||||
screen editor:
|
||||
|
||||
frame:
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
label _("Select Editor")
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
frame:
|
||||
style "l_indent"
|
||||
xfill True
|
||||
|
||||
viewport:
|
||||
scrollbars "vertical"
|
||||
mousewheel True
|
||||
|
||||
has vbox
|
||||
|
||||
text _("A text editor is the program you'll use to edit Ren'Py script files. Here, you can select the editor Ren'Py will use. If not already present, the editor will be automatically downloaded and installed.") style "l_small_text"
|
||||
|
||||
for fe in editor.fancy_editors:
|
||||
|
||||
add SPACER
|
||||
|
||||
textbutton fe.name action editor.SelectEditor(fe.name)
|
||||
|
||||
add HALF_SPACER
|
||||
|
||||
frame:
|
||||
style "l_indent"
|
||||
has vbox
|
||||
|
||||
if fe.description:
|
||||
text fe.description style "l_small_text"
|
||||
|
||||
if not fe.installed:
|
||||
add HALF_SPACER
|
||||
text fe.dldescription style "l_small_text"
|
||||
|
||||
|
||||
textbutton _("Cancel") action Return(False) style "l_left_button"
|
||||
|
||||
label editor_preference:
|
||||
call screen editor
|
||||
jump preferences
|
||||
@@ -1,288 +0,0 @@
|
||||
# Copyright 2004-2014 Tom Rothamel <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.
|
||||
|
||||
define PROJECT_ADJUSTMENT = ui.adjustment()
|
||||
|
||||
init python:
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
class OpenDirectory(Action):
|
||||
"""
|
||||
Opens `directory` in a file browser. `directory` is relative to
|
||||
the project root.
|
||||
"""
|
||||
|
||||
alt = _("Open [text] directory.")
|
||||
|
||||
def __init__(self, directory, absolute=False):
|
||||
if absolute:
|
||||
self.directory = directory
|
||||
else:
|
||||
self.directory = os.path.join(project.current.path, directory)
|
||||
|
||||
def get_sensitive(self):
|
||||
return os.path.exists(self.directory)
|
||||
|
||||
def __call__(self):
|
||||
|
||||
try:
|
||||
directory = renpy.fsencode(self.directory)
|
||||
|
||||
if renpy.windows:
|
||||
os.startfile(directory)
|
||||
elif renpy.macintosh:
|
||||
subprocess.Popen([ "open", directory ])
|
||||
else:
|
||||
subprocess.Popen([ "xdg-open", directory ])
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
# Used for testing.
|
||||
def Relaunch():
|
||||
renpy.quit(relaunch=True)
|
||||
|
||||
screen front_page:
|
||||
frame:
|
||||
alt ""
|
||||
|
||||
style_group "l"
|
||||
style "l_root"
|
||||
|
||||
has hbox
|
||||
|
||||
# Projects list section - on left.
|
||||
|
||||
frame:
|
||||
style "l_projects"
|
||||
xmaximum 300
|
||||
right_margin 2
|
||||
|
||||
top_padding 20
|
||||
bottom_padding 26
|
||||
|
||||
side "t c b":
|
||||
|
||||
window style "l_label":
|
||||
|
||||
has hbox:
|
||||
xfill True
|
||||
|
||||
text "PROJECTS:" style "l_label_text" size 36 yoffset 10
|
||||
|
||||
textbutton _("refresh"):
|
||||
xalign 1.0
|
||||
yalign 1.0
|
||||
yoffset 5
|
||||
style "l_small_button"
|
||||
action project.Rescan()
|
||||
right_margin HALF_INDENT
|
||||
|
||||
side "c r":
|
||||
|
||||
viewport:
|
||||
yadjustment PROJECT_ADJUSTMENT
|
||||
mousewheel True
|
||||
use front_page_project_list
|
||||
|
||||
vbar:
|
||||
style "l_vscrollbar"
|
||||
adjustment PROJECT_ADJUSTMENT
|
||||
|
||||
vbox:
|
||||
add HALF_SPACER
|
||||
add SEPARATOR
|
||||
add HALF_SPACER
|
||||
|
||||
hbox:
|
||||
xfill True
|
||||
|
||||
textbutton _("+ Create New Project"):
|
||||
left_margin (HALF_INDENT)
|
||||
action Jump("new_project")
|
||||
|
||||
|
||||
|
||||
# Project section - on right.
|
||||
|
||||
if project.current is not None:
|
||||
use front_page_project
|
||||
|
||||
if project.current is not None:
|
||||
textbutton _("Launch Project") action project.Launch() style "l_right_button"
|
||||
|
||||
|
||||
|
||||
# This is used by front_page to display the list of known projects on the screen.
|
||||
screen front_page_project_list:
|
||||
|
||||
$ projects = project.manager.projects
|
||||
$ templates = project.manager.templates
|
||||
|
||||
vbox:
|
||||
|
||||
if templates and persistent.show_templates:
|
||||
|
||||
for p in templates:
|
||||
|
||||
textbutton _("[p.name!q] (template)"):
|
||||
action project.Select(p)
|
||||
alt _("Select project [text].")
|
||||
style "l_list"
|
||||
|
||||
null height 12
|
||||
|
||||
if projects:
|
||||
|
||||
for p in projects:
|
||||
|
||||
textbutton "[p.name!q]":
|
||||
action project.Select(p)
|
||||
alt _("Select project [text].")
|
||||
style "l_list"
|
||||
|
||||
null height 12
|
||||
|
||||
textbutton _("Tutorial") action project.Select("tutorial") style "l_list" alt _("Select project [text].")
|
||||
textbutton _("The Question") action project.Select("the_question") style "l_list" alt _("Select project [text].")
|
||||
|
||||
|
||||
# This is used for the right side of the screen, which is where the project-specific
|
||||
# buttons are.
|
||||
screen front_page_project:
|
||||
|
||||
$ p = project.current
|
||||
|
||||
window:
|
||||
|
||||
has vbox
|
||||
|
||||
frame style "l_label":
|
||||
has hbox xfill True
|
||||
text "[p.name!q]" style "l_label_text"
|
||||
label _("Active Project") style "l_alternate"
|
||||
|
||||
grid 2 1:
|
||||
xfill True
|
||||
spacing HALF_INDENT
|
||||
|
||||
vbox:
|
||||
|
||||
label _("Open Directory") style "l_label_small"
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton _("game") action OpenDirectory("game")
|
||||
textbutton _("base") action OpenDirectory(".")
|
||||
# textbutton _("images") action OpenDirectory("game/images") style "l_list"
|
||||
# textbutton _("save") action None style "l_list"
|
||||
|
||||
vbox:
|
||||
|
||||
label _("Edit File") style "l_label_small"
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton "script.rpy" action editor.Edit("game/script.rpy", check=True)
|
||||
textbutton "options.rpy" action editor.Edit("game/options.rpy", check=True)
|
||||
textbutton "screens.rpy" action editor.Edit("game/screens.rpy", check=True)
|
||||
textbutton _("All script files") action editor.EditAll()
|
||||
|
||||
add SPACER
|
||||
add SEPARATOR
|
||||
add SPACER
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton _("Navigate Script") text_size 30 action Jump("navigation")
|
||||
|
||||
add SPACER
|
||||
|
||||
grid 2 1:
|
||||
xfill True
|
||||
spacing HALF_INDENT
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
textbutton _("Check Script (Lint)") action Jump("lint")
|
||||
textbutton _("Change Theme") action Jump("choose_theme")
|
||||
textbutton _("Delete Persistent") action Jump("rmpersistent")
|
||||
textbutton _("Force Recompile") action Jump("force_recompile")
|
||||
|
||||
# textbutton "Relaunch" action Relaunch
|
||||
|
||||
frame style "l_indent":
|
||||
has vbox
|
||||
|
||||
if ability.can_distribute:
|
||||
textbutton _("Build Distributions") action Jump("build_distributions")
|
||||
|
||||
textbutton _("Android") action Jump("android")
|
||||
textbutton _("Generate Translations") action Jump("translate")
|
||||
textbutton _("Extract Dialogue") action Jump("extract_dialogue")
|
||||
|
||||
label main_menu:
|
||||
return
|
||||
|
||||
label start:
|
||||
show screen bottom_info
|
||||
|
||||
label front_page:
|
||||
call screen front_page
|
||||
jump front_page
|
||||
|
||||
|
||||
label lint:
|
||||
python hide:
|
||||
|
||||
interface.processing(_("Checking script for potential problems..."))
|
||||
lint_fn = project.current.temp_filename("lint.txt")
|
||||
|
||||
project.current.launch([ 'lint', lint_fn ], wait=True)
|
||||
|
||||
e = renpy.editor.editor
|
||||
e.begin(True)
|
||||
e.open(lint_fn)
|
||||
e.end()
|
||||
|
||||
jump front_page
|
||||
|
||||
label rmpersistent:
|
||||
|
||||
python hide:
|
||||
interface.processing(_("Deleting persistent data..."))
|
||||
project.current.launch([ 'rmpersistent' ], wait=True)
|
||||
|
||||
jump front_page
|
||||
|
||||
label force_recompile:
|
||||
|
||||
python hide:
|
||||
interface.processing(_("Recompiling all rpy files into rpyc files..."))
|
||||
project.current.launch([ 'compile' ], wait=True)
|
||||
|
||||
jump front_page
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 687 B |
|
Before Width: | Height: | Size: 96 B |