1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22init python:
23    class PackageToggle(Action):
24        def __init__(self, name):
25            self.name = name
26
27        def get_selected(self):
28            return self.name in project.current.data['packages']
29
30        def __call__(self):
31            packages = project.current.data['packages']
32
33            if self.name in packages:
34                packages.remove(self.name)
35            else:
36                packages.append(self.name)
37
38            project.current.save_data()
39            renpy.restart_interaction()
40
41    class DataToggle(Action):
42        def __init__(self, field):
43            self.field = field
44
45        def get_selected(self):
46            return project.current.data[self.field]
47
48        def __call__(self):
49            project.current.data[self.field] = not project.current.data[self.field]
50
51            project.current.save_data()
52            renpy.restart_interaction()
53
54
55    DEFAULT_BUILD_INFO = """
56
57## This section contains information about how to build your project into
58## distribution files.
59init python:
60
61    ## The name that's used for directories and archive files. For example, if
62    ## this is 'mygame-1.0', the windows distribution will be in the
63    ## directory 'mygame-1.0-win', in the 'mygame-1.0-win.zip' file.
64    build.directory_name = "PROJECTNAME-1.0"
65
66    ## The name that's uses for executables - the program that users will run
67    ## to start the game. For example, if this is 'mygame', then on Windows,
68    ## users can click 'mygame.exe' to start the game.
69    build.executable_name = "PROJECTNAME"
70
71    ## If True, Ren'Py will include update information into packages. This
72    ## allows the updater to run.
73    build.include_update = False
74
75    ## File patterns:
76    ##
77    ## The following functions take file patterns. File patterns are case-
78    ## insensitive, and matched against the path relative to the base
79    ## directory, with and without a leading /. If multiple patterns match,
80    ## the first is used.
81    ##
82    ##
83    ## In a pattern:
84    ##
85    ## /
86    ##     Is the directory separator.
87    ## *
88    ##     Matches all characters, except the directory separator.
89    ## **
90    ##     Matches all characters, including the directory separator.
91    ##
92    ## For example:
93    ##
94    ## *.txt
95    ##     Matches txt files in the base directory.
96    ## game/**.ogg
97    ##     Matches ogg files in the game directory or any of its subdirectories.
98    ## **.psd
99    ##    Matches psd files anywhere in the project.
100
101    ## Classify files as None to exclude them from the built distributions.
102
103    build.classify('**~', None)
104    build.classify('**.bak', None)
105    build.classify('**/.**', None)
106    build.classify('**/#**', None)
107    build.classify('**/thumbs.db', None)
108
109    ## To archive files, classify them as 'archive'.
110
111    # build.classify('game/**.png', 'archive')
112    # build.classify('game/**.jpg', 'archive')
113
114    ## Files matching documentation patterns are duplicated in a mac app
115    ## build, so they appear in both the app and the zip file.
116
117    build.documentation('*.html')
118    build.documentation('*.txt')
119    """
120
121# A screen that displays a file or directory name, and
122# lets the user change it,
123#
124# title
125#     The title of the link.
126# value
127#     The value of the field.
128screen distribute_name:
129
130    add SEPARATOR2
131
132    frame:
133        style "l_indent"
134        has vbox
135
136        text title
137
138        add HALF_SPACER
139
140        frame:
141            style "l_indent"
142            text "[value!q]"
143
144    add SPACER
145
146
147screen build_distributions:
148
149    frame:
150        style_group "l"
151        style "l_root"
152
153        window:
154
155            has vbox
156
157            label _("Build Distributions: [project.current.display_name!q]")
158
159            add HALF_SPACER
160
161            hbox:
162
163                # Left side.
164                frame:
165                    style "l_indent"
166                    xmaximum ONEHALF
167                    xfill True
168
169                    has vbox
170
171                    use distribute_name(
172                        title=_("Directory Name:"),
173                        value=project.current.dump["build"]["directory_name"])
174
175                    use distribute_name(
176                        title=_("Executable Name:"),
177                        value=project.current.dump["build"]["executable_name"])
178
179                    add SEPARATOR2
180
181                    frame:
182                        style "l_indent"
183                        has vbox
184
185                        text _("Actions:")
186
187                        add HALF_SPACER
188
189                        frame style "l_indent":
190
191                            has vbox
192
193                            textbutton _("Edit options.rpy") action editor.Edit("game/options.rpy", check=True)
194                            textbutton _("Add from clauses to calls, once") action Jump("add_from")
195                            textbutton _("Refresh") action Jump("build_distributions")
196
197                            add HALF_SPACER
198
199                            textbutton _("Upload to itch.io") action Jump("itch")
200
201                # Right side.
202                frame:
203                    style "l_indent"
204                    xmaximum ONEHALF
205                    xfill True
206
207                    has vbox
208
209                    add SEPARATOR2
210
211                    frame:
212                        style "l_indent"
213                        has vbox
214
215                        text _("Build Packages:")
216
217                        add HALF_SPACER
218
219                        $ packages = project.current.dump["build"]["packages"]
220
221                        for pkg in packages:
222                            if not pkg["hidden"]:
223                                $ description = pkg["description"]
224                                textbutton "[description!q]" action PackageToggle(pkg["name"]) style "l_checkbox"
225
226                    add SPACER
227                    add HALF_SPACER
228                    add SEPARATOR2
229
230                    frame:
231                        style "l_indent"
232                        has vbox
233
234                        text _("Options:")
235
236                        add HALF_SPACER
237
238                        if project.current.dump["build"]["include_update"]:
239                            textbutton _("Build Updates") action DataToggle("build_update") style "l_checkbox"
240
241                        textbutton _("Add from clauses to calls") action DataToggle("add_from") style "l_checkbox"
242                        textbutton _("Force Recompile") action DataToggle("force_recompile") style "l_checkbox"
243
244
245    textbutton _("Return") action Jump("front_page") style "l_left_button"
246    textbutton _("Build") action Jump("start_distribute") style "l_right_button"
247
248label add_from_common:
249    python:
250        interface.processing(_("Adding from clauses to call statements that do not have them."))
251        project.current.launch([ "add_from" ], wait=True)
252
253    return
254
255label add_from:
256    call add_from_common
257    jump build_distributions
258
259
260label start_distribute:
261    if project.current.data["add_from"]:
262        call add_from_common
263
264    jump distribute
265
266label build_update_dump:
267    python:
268        project.current.update_dump(True)
269
270        if project.current.dump.get("error", False):
271            interface.error(_("Errors were detected when running the project. Please ensure the project runs without errors before building distributions."))
272
273    return
274
275label build_distributions:
276
277    call build_update_dump
278
279label post_build:
280
281    if not project.current.dump["build"]["directory_name"]:
282        jump build_missing
283
284    call screen build_distributions
285
286label build_missing:
287
288    python hide:
289
290        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"))
291
292        project_name = project.current.name
293        project_name = project_name.replace(" ", "_")
294        project_name = project_name.replace(":", "")
295        project_name = project_name.replace(";", "")
296
297        build_info = DEFAULT_BUILD_INFO.replace("PROJECTNAME", project_name)
298
299        with open(os.path.join(project.current.path, "game", "options.rpy"), "a") as f:
300            f.write(build_info)
301
302    jump build_distributions
303