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
22# This file contains code that creates a few new statements.
23
24init -1200 python in audio:
25    pass
26
27init -1200 python:
28
29    config.default_sound_loop = None
30
31    def _audio_eval(expr):
32        return eval(expr, locals=store.audio.__dict__)
33
34    def _try_eval(e, what):
35        try:
36            return _audio_eval(e)
37        except:
38            renpy.error('unable to evaluate %s %r' % (what, e))
39
40python early hide:
41
42    def warp_audio(p):
43        """
44        Determines if we should play this statement while warping.
45        """
46
47        if p.get("channel", None) is not None:
48            channel = eval(p["channel"])
49        else:
50            channel = "music"
51
52        return renpy.music.is_music(channel)
53
54    def parse_play_music(l):
55
56        file = l.simple_expression()
57        if not file:
58            renpy.error("play requires a file")
59
60        fadeout = "None"
61        fadein = "0"
62        channel = None
63        loop = None
64        if_changed = False
65        volume = "1.0"
66
67        while not l.eol():
68
69            if l.keyword('fadeout'):
70                fadeout = l.simple_expression()
71                if fadeout is None:
72                    renpy.error('expected simple expression')
73
74                continue
75
76            if l.keyword('fadein'):
77                fadein = l.simple_expression()
78                if fadein is None:
79                    renpy.error('expected simple expression')
80
81                continue
82
83            if l.keyword('channel'):
84                channel = l.simple_expression()
85                if channel is None:
86                    renpy.error('expected simple expression')
87
88                continue
89
90            if l.keyword('loop'):
91                loop = True
92                continue
93
94            if l.keyword('noloop'):
95                loop = False
96                continue
97
98            if l.keyword('if_changed'):
99                if_changed = True
100                continue
101
102            if l.keyword('volume'):
103                volume = l.simple_expression()
104                continue
105
106            renpy.error('expected end of line')
107
108        return dict(file=file,
109                    fadeout=fadeout,
110                    fadein=fadein,
111                    channel=channel,
112                    loop=loop,
113                    if_changed=if_changed,
114                    volume=volume)
115
116    def execute_play_music(p):
117
118        if p["channel"] is not None:
119            channel = eval(p["channel"])
120        else:
121            channel = "music"
122
123        renpy.music.play(_audio_eval(p["file"]),
124                         fadeout=eval(p["fadeout"]),
125                         fadein=eval(p["fadein"]),
126                         channel=channel,
127                         loop=p.get("loop", None),
128                         if_changed=p.get("if_changed", False),
129                         relative_volume=eval(p.get("volume", "1.0")))
130
131    def predict_play_music(p):
132        if renpy.emscripten or os.environ.get('RENPY_SIMULATE_DOWNLOAD', False):
133            fn = _audio_eval(p["file"])
134            try:
135                with renpy.loader.load(fn) as f:
136                    pass
137            except renpy.webloader.DownloadNeeded as exception:
138                renpy.webloader.enqueue(exception.relpath, 'music', None)
139        return [ ]
140
141    def lint_play_music(p, channel="music"):
142
143        file = _try_eval(p["file"], 'filename')
144
145        if p["channel"] is not None:
146            channel = _try_eval(p["channel"], 'channel')
147
148        if not isinstance(file, list):
149            file = [ file ]
150
151        for fn in file:
152            if isinstance(fn, basestring):
153                try:
154                    if not renpy.music.playable(fn, channel):
155                        renpy.error("%r is not loadable" % fn)
156                except:
157                    pass
158
159    renpy.register_statement('play music',
160                              parse=parse_play_music,
161                              execute=execute_play_music,
162                              predict=predict_play_music,
163                              lint=lint_play_music,
164                              warp=warp_audio)
165
166    # From here on, we'll steal bits of other statements when defining other
167    # statements.
168
169    def parse_queue_music(l):
170
171        file = l.simple_expression()
172        if not file:
173            renpy.error("queue requires a file")
174
175        channel = None
176        loop = None
177        volume = "1.0"
178        fadein = "0"
179
180        while not l.eol():
181
182            if l.keyword('channel'):
183                channel = l.simple_expression()
184                if channel is None:
185                    renpy.error('expected simple expression')
186
187                continue
188
189            if l.keyword('loop'):
190                loop = True
191                continue
192
193            if l.keyword('noloop'):
194                loop = False
195                continue
196
197            if l.keyword('volume'):
198                volume = l.simple_expression()
199                continue
200
201            if l.keyword('fadein'):
202                fadein = l.simple_expression()
203                if fadein is None:
204                    renpy.error('expected simple expression')
205
206                continue
207
208            renpy.error('expected end of line')
209
210        return dict(file=file, channel=channel, loop=loop, volume=volume, fadein=fadein)
211
212    def execute_queue_music(p):
213        if p["channel"] is not None:
214            channel = eval(p["channel"])
215        else:
216            channel = "music"
217
218        renpy.music.queue(
219            _audio_eval(p["file"]),
220            channel=channel,
221            loop=p.get("loop", None),
222            relative_volume=eval(p.get("volume", "1.0")),
223            fadein=eval(p.get("fadein", "0")),
224            )
225
226
227    renpy.register_statement('queue music',
228                              parse=parse_queue_music,
229                              execute=execute_queue_music,
230                              lint=lint_play_music,
231                              warp=warp_audio)
232
233    def parse_stop_music(l):
234        channel = None
235        fadeout = "None"
236
237        while not l.eol():
238
239            if l.keyword("fadeout"):
240                fadeout = l.simple_expression()
241                if fadeout is None:
242                    renpy.error('expected simple expression')
243
244                continue
245
246            if l.keyword('channel'):
247                channel = l.simple_expression()
248                if channel is None:
249                    renpy.error('expected simple expression')
250
251                continue
252
253            renpy.error('expected end of line')
254
255        return dict(fadeout=fadeout, channel=channel)
256
257    def execute_stop_music(p):
258        if p["channel"] is not None:
259            channel = eval(p["channel"])
260        else:
261            channel = "music"
262
263        renpy.music.stop(fadeout=eval(p["fadeout"]), channel=channel)
264
265    renpy.register_statement('stop music',
266                              parse=parse_stop_music,
267                              execute=execute_stop_music,
268                              warp=warp_audio)
269
270
271    # Sound statements. They share alot with the equivalent music
272    # statements.
273
274    def warp_sound(p):
275        """
276        Determines if we should play this statement while warping.
277        """
278
279        if p.get("channel", None) is not None:
280            channel = eval(p["channel"])
281        else:
282            channel = "sound"
283
284        return renpy.music.is_music(channel)
285
286    def execute_play_sound(p):
287
288        if p["channel"] is not None:
289            channel = eval(p["channel"])
290        else:
291            channel = "sound"
292
293        fadeout = eval(p["fadeout"]) or 0
294
295        loop = p.get("loop", False)
296
297        if loop is None:
298            loop = config.default_sound_loop
299
300        renpy.sound.play(_audio_eval(p["file"]),
301                         fadeout=fadeout,
302                         fadein=eval(p["fadein"]),
303                         loop=loop,
304                         channel=channel,
305                         relative_volume=eval(p.get("volume", "1.0")))
306
307    def lint_play_sound(p, lint_play_music=lint_play_music):
308        return lint_play_music(p, channel="sound")
309
310    renpy.register_statement('play sound',
311                              parse=parse_play_music,
312                              execute=execute_play_sound,
313                              lint=lint_play_sound,
314                              warp=warp_sound)
315
316    def execute_queue_sound(p):
317        if p["channel"] is not None:
318            channel = eval(p["channel"])
319        else:
320            channel = "sound"
321
322        loop = p.get("loop", False)
323
324        if loop is None:
325            loop = config.default_sound_loop
326
327        renpy.sound.queue(_audio_eval(p["file"]), channel=channel, loop=loop, relative_volume=eval(p.get("volume", "1.0")))
328
329
330    renpy.register_statement('queue sound',
331                              parse=parse_queue_music,
332                              execute=execute_queue_sound,
333                              lint=lint_play_sound,
334                              warp=warp_sound)
335
336    def execute_stop_sound(p):
337        if p["channel"] is not None:
338            channel = eval(p["channel"])
339        else:
340            channel = "sound"
341
342        fadeout = eval(p["fadeout"]) or 0
343
344        renpy.sound.stop(fadeout=fadeout, channel=channel)
345
346    renpy.register_statement('stop sound',
347                              parse=parse_stop_music,
348                              execute=execute_stop_sound,
349                              warp=warp_sound)
350
351
352    # Generic play/queue/stop statements. These take a channel name as
353    # the second thing.
354
355    def parse_play_generic(l, parse_play_music=parse_play_music):
356        channel = l.name()
357
358        if channel is None:
359            renpy.error('play requires a channel')
360
361        rv = parse_play_music(l)
362        if rv["channel"] is None:
363            rv["channel"] = repr(channel)
364
365        return rv
366
367    def parse_queue_generic(l, parse_queue_music=parse_queue_music):
368        channel = l.name()
369
370        if channel is None:
371            renpy.error('queue requires a channel')
372
373        rv = parse_queue_music(l)
374        if rv["channel"] is None:
375            rv["channel"] = repr(channel)
376
377        return rv
378
379    def parse_stop_generic(l, parse_stop_music=parse_stop_music):
380        channel = l.name()
381
382        if channel is None:
383            renpy.error('stop requires a channel')
384
385        rv = parse_stop_music(l)
386        if rv["channel"] is None:
387            rv["channel"] = repr(channel)
388
389        return rv
390
391    def lint_play_generic(p, lint_play_music=lint_play_music):
392        channel = eval(p["channel"])
393
394        if not renpy.music.channel_defined(channel):
395            renpy.error("channel %r is not defined" % channel)
396
397        lint_play_music(p, channel)
398
399    def lint_stop_generic(p):
400        channel = eval(p["channel"])
401
402        if not renpy.music.channel_defined(channel):
403            renpy.error("channel %r is not defined" % channel)
404
405    renpy.register_statement('play',
406                              parse=parse_play_generic,
407                              execute=execute_play_music,
408                              predict=predict_play_music,
409                              lint=lint_play_generic,
410                              warp=warp_audio)
411
412    renpy.register_statement('queue',
413                              parse=parse_queue_generic,
414                              execute=execute_queue_music,
415                              lint=lint_play_generic,
416                              warp=warp_audio)
417
418    renpy.register_statement('stop',
419                              parse=parse_stop_generic,
420                              execute=execute_stop_music,
421                              lint=lint_stop_generic,
422                              warp=warp_audio)
423
424
425
426    ##########################################################################
427    # Pause statement.
428
429    # Should the pause statement use the pause Transition?
430    config.pause_with_transition = False
431
432    def parse_pause(l):
433
434        delay = l.simple_expression()
435
436        if not l.eol():
437            renpy.error("expected end of line.")
438
439        return { "delay" : delay }
440
441    def lint_pause(p):
442
443        if p["delay"]:
444            _try_eval(p["delay"], 'pause statement')
445
446    def execute_pause(p):
447
448        if p["delay"]:
449            delay = eval(p["delay"])
450            if config.pause_with_transition:
451                renpy.with_statement(Pause(delay))
452            else:
453                renpy.pause(delay)
454        else:
455            renpy.pause()
456
457    renpy.register_statement('pause',
458                              parse=parse_pause,
459                              lint=lint_pause,
460                              execute=execute_pause)
461
462
463##############################################################################
464# Screen-related statements.
465
466python early hide:
467
468    # Should we predict screens?
469    config.predict_screen_statements = True
470
471    def warp_true(p):
472        return True
473
474    def parse_show_call_screen(l):
475
476        # Parse a name.
477        name = l.require(l.name)
478
479        # Parse the list of arguments.
480        arguments = renpy.parser.parse_arguments(l)
481
482        predict = True
483        transition_expr = None
484
485        while True:
486
487            if l.keyword('nopredict'):
488                predict = False
489
490            elif l.keyword('with'):
491                transition_expr = l.require(l.simple_expression)
492
493            else:
494                break
495
496        l.expect_eol()
497
498        return dict(name=name, arguments=arguments, predict=predict, transition_expr=transition_expr)
499
500    def parse_hide_screen(l):
501        name = l.require(l.name)
502
503        transition_expr = None
504
505        if l.keyword('with'):
506            transition_expr = l.require(l.simple_expression)
507
508        l.expect_eol()
509
510        return dict(name=name, transition_expr=transition_expr)
511
512    def predict_screen(p):
513
514        if not config.predict_screen_statements:
515            return
516
517        predict = p.get("predict", False)
518
519        if not predict:
520            return
521
522        name = p["name"]
523        a = p["arguments"]
524
525        if a is not None:
526            args, kwargs = a.evaluate()
527        else:
528            args = [ ]
529            kwargs = { }
530
531        renpy.predict_screen(name, *args, **kwargs)
532
533    def execute_show_screen(p):
534
535        name = p["name"]
536        a = p["arguments"]
537
538        if a is not None:
539            args, kwargs = a.evaluate()
540        else:
541            args = [ ]
542            kwargs = { }
543
544        transition_expr = p.get("transition_expr", None)
545        if transition_expr is not None:
546            renpy.with_statement(None)
547
548        renpy.show_screen(name, *args, **kwargs)
549
550        if transition_expr is not None:
551            renpy.with_statement(eval(transition_expr))
552
553    def execute_call_screen(p):
554
555        name = p["name"]
556        a = p["arguments"]
557
558        transition_expr = p.get("transition_expr", None)
559
560        if transition_expr is not None:
561            renpy.transition(eval(transition_expr))
562
563        if a is not None:
564            args, kwargs = a.evaluate()
565        else:
566            args = [ ]
567            kwargs = { }
568
569        store._return = renpy.call_screen(name, *args, **kwargs)
570
571    def execute_hide_screen(p):
572        name = p["name"]
573
574        transition_expr = p.get("transition_expr", None)
575        if transition_expr is not None:
576            renpy.with_statement(None)
577
578        renpy.hide_screen(name)
579
580        if transition_expr is not None:
581            renpy.with_statement(eval(transition_expr))
582
583
584    def lint_screen(p):
585        name = p["name"]
586        if not renpy.has_screen(name):
587            renpy.error("Screen %s does not exist." % name)
588
589
590    renpy.register_statement("show screen",
591                              parse=parse_show_call_screen,
592                              execute=execute_show_screen,
593                              predict=predict_screen,
594                              lint=lint_screen,
595                              warp=warp_true)
596
597    renpy.register_statement("call screen",
598                              parse=parse_show_call_screen,
599                              execute=execute_call_screen,
600                              predict=predict_screen,
601                              lint=lint_screen,
602                              force_begin_rollback=True)
603
604    renpy.register_statement("hide screen",
605                              parse=parse_hide_screen,
606                              execute=execute_hide_screen,
607                              warp=warp_true)
608