diff options
Diffstat (limited to 'unrpyc/renpy/atl.py')
-rw-r--r-- | unrpyc/renpy/atl.py | 1544 |
1 files changed, 1544 insertions, 0 deletions
diff --git a/unrpyc/renpy/atl.py b/unrpyc/renpy/atl.py new file mode 100644 index 0000000..f314e3e --- /dev/null +++ b/unrpyc/renpy/atl.py @@ -0,0 +1,1544 @@ +# Copyright 2004-2013 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. + +import renpy.display +import random + +def compiling(loc): + file, number = loc #@ReservedAssignment + + renpy.game.exception_info = "Compiling ATL code at %s:%d" % (file, number) + +def executing(loc): + file, number = loc #@ReservedAssignment + + renpy.game.exception_info = "Executing ATL code at %s:%d" % (file, number) + + +# A map from the name of a time warp function to the function itself. +warpers = { } + +def atl_warper(f): + name = f.__name__ + warpers[name] = f + return f + +# The pause warper is used internally when no other warper is +# specified. +@atl_warper +def pause(t): + if t < 1.0: + return 0.0 + else: + return 1.0 + +position = object() + +# A dictionary giving property names and the corresponding default +# values. +PROPERTIES = { + "pos" : (position, position), + "xpos" : position, + "ypos" : position, + "anchor" : (position, position), + "xanchor" : position, + "yanchor" : position, + "xaround" : position, + "yaround" : position, + "xanchoraround" : float, + "yanchoraround" : float, + "align" : (float, float), + "xalign" : float, + "yalign" : float, + "rotate" : float, + "rotate_pad" : bool, + "transform_anchor" : bool, + "xzoom" : float, + "yzoom" : float, + "zoom" : float, + "alpha" : float, + "around" : (position, position), + "alignaround" : (float, float), + "angle" : float, + "radius" : float, + "crop" : (float, float, float, float), + "size" : (int, int), + "corner1" : (float, float), + "corner2" : (float, float), + "subpixel" : bool, + "delay" : float, + "xoffset" : float, + "yoffset" : float, + "offset" : (int, int), + "xcenter" : position, + "ycenter" : position, + } + + +def correct_type(v, b, ty): + """ + Corrects the type of v to match ty. b is used to inform the match. + """ + + if ty is position: + if v is None: + return None + else: + return type(b)(v) + else: + return ty(v) + + +def interpolate(t, a, b, type): #@ReservedAssignment + """ + Linearly interpolate the arguments. + """ + + if t >= 1.0: + return b + + # Recurse into tuples. + if isinstance(b, tuple): + if a is None: + a = [ None ] * len(b) + + return tuple(interpolate(t, i, j, ty) for i, j, ty in zip(a, b, type)) + + # Deal with booleans, nones, etc. + elif b is None or isinstance(b, bool): + if t >= 1.0: + return b + else: + return a + + # Interpolate everything else. + else: + if a is None: + a = 0 + + return correct_type(a + t * (b - a), b, type) + +# Interpolate the value of a spline. This code is based on Aenakume's code, +# from 00splines.rpy. +def interpolate_spline(t, spline): + + if isinstance(spline[-1], tuple): + return tuple(interpolate_spline(t, i) for i in zip(*spline)) + + if spline[0] is None: + return spline[-1] + + if len(spline) == 2: + t_p = 1.0 - t + + rv = t_p * spline[0] + t * spline[-1] + + elif len(spline) == 3: + t_pp = (1.0 - t)**2 + t_p = 2 * t * (1.0 - t) + t2 = t**2 + + rv = t_pp * spline[0] + t_p * spline[1] + t2 * spline[2] + + elif len(spline) == 4: + + t_ppp = (1.0 - t)**3 + t_pp = 3 * t * (1.0 - t)**2 + t_p = 3 * t**2 * (1.0 - t) + t3 = t**3 + + rv = t_ppp * spline[0] + t_pp * spline[1] + t_p * spline[2] + t3 * spline[3] + + else: + raise Exception("ATL can't interpolate splines of length %d." % len(spline)) + + return correct_type(rv, spline[-1], position) + + +# This is the context used when compiling an ATL statement. It stores the +# scopes that are used to evaluate the various expressions in the statement, +# and has a method to do the evaluation and return a result. +class Context(object): + def __init__(self, context): + self.context = context + + def eval(self, expr): #@ReservedAssignment + expr = renpy.python.escape_unicode(expr) + return eval(expr, renpy.store.__dict__, self.context) #@UndefinedVariable + +# This is intended to be subclassed by ATLTransform. It takes care of +# managing ATL execution, which allows ATLTransform itself to not care +# much about the contents of this file. +class ATLTransformBase(renpy.object.Object): + + # Compatibility with older saves. + parameters = renpy.ast.ParameterInfo([ ], [ ], None, None) + + def __init__(self, atl, context, parameters): + + # The constructor will be called by atltransform. + + if parameters is None: + parameters = ATLTransformBase.parameters + + # The parameters that we take. + self.parameters = parameters + + # The raw code that makes up this ATL statement. + self.atl = atl + + # The context in which execution occurs. + self.context = Context(context) + + # The code after it has been compiled into a block. + self.block = None + + # The properties of the block, if it contains only an + # Interpolation. + self.properties = None + + # The state of the statement we are executing. As this can be + # shared between more than one object (in the case of a hide), + # the data must not be altered. + self.atl_state = None + + # Are we done? + self.done = False + + # The transform event we are going to process. + self.transform_event = None + + # The transform event we last processed. + self.last_transform_event = None + + # The child transform event we last processed. + self.last_child_transform_event = None + + def take_execution_state(self, t): + """ + Updates self to begin executing from the same point as t. This + requires that t.atl is self.atl. + """ + + super(ATLTransformBase, self).take_execution_state(t) + + if t.atl is not self.atl: + return + + self.done = t.done + self.block = t.block + self.atl_state = t.atl_state + self.transform_event = t.transform_event + self.last_transform_event = t.last_transform_event + self.last_child_transform_event = t.last_child_transform_event + + self.st = t.st + self.at = t.at + self.st_offset = t.st_offset + self.at_offset = t.at_offset + + if self.child is renpy.display.motion.null: + self.child = t.child + + + def __call__(self, *args, **kwargs): + + context = self.context.context.copy() + + for k, v in self.parameters.parameters: + if v is not None: + context[k] = renpy.python.py_eval(v) + + positional = list(self.parameters.positional) + args = list(args) + + child = None + + if not positional and args: + child = args.pop(0) + + # Handle positional arguments. + while positional and args: + name = positional.pop(0) + value = args.pop(0) + + if name in kwargs: + raise Exception('Parameter %r is used as both a positional and keyword argument to a transition.' % name) + + context[name] = value + + if args: + raise Exception("Too many arguments passed to ATL transform.") + + # Handle keyword arguments. + for k, v in kwargs.items(): + + if k in positional: + positional.remove(k) + context[k] = v + elif k in context: + context[k] = v + elif k == 'child': + child = v + else: + raise Exception('Parameter %r is not known by ATL Transform.' % k) + + if child is None: + child = self.child + + if child is None: + child = renpy.display.motion.get_null() + + # Create a new ATL Transform. + parameters = renpy.ast.ParameterInfo({}, positional, None, None) + + rv = renpy.display.motion.ATLTransform( + atl=self.atl, + child=child, + style=self.style_arg, + context=context, + parameters=parameters) + + rv.take_state(self) + + return rv + + + def compile(self): #@ReservedAssignment + """ + Compiles the ATL code into a block. As necessary, updates the + properties. + """ + + if self.parameters.positional and self.parameters.positional[0][1] is None: + raise Exception("Cannot compile ATL Transform, as it's missing positional parameter %s." % self.parameters.positional[0]) + + old_exception_info = renpy.game.exception_info + + self.block = self.atl.compile(self.context) + + if len(self.block.statements) == 1 \ + and isinstance(self.block.statements[0], Interpolation): + + interp = self.block.statements[0] + + if interp.duration == 0 and interp.properties: + self.properties = interp.properties[:] + + renpy.game.exception_info = old_exception_info + + + def execute(self, trans, st, at): + + if self.done: + return None + + if not self.block: + self.compile() + + # Propagate transform_events from children. + if self.child: + if self.child.transform_event != self.last_child_transform_event: + self.last_child_transform_event = self.child.transform_event + self.transform_event = self.child.transform_event + + # Hide request. + if trans.hide_request: + self.transform_event = "hide" + + if trans.replaced_request: + self.transform_event = "replaced" + + # Notice transform events. + if self.transform_event != self.last_transform_event: + event = self.transform_event + self.last_transform_event = self.transform_event + else: + event = None + + old_exception_info = renpy.game.exception_info + + if self.atl.animation: + timebase = at + else: + timebase = st + + action, arg, pause = self.block.execute(trans, timebase, self.atl_state, event) + + renpy.game.exception_info = old_exception_info + + # print "Executing", self, self.state, self.xpos, self.ypos + + if action == "continue": + self.atl_state = arg + else: + self.done = True + + return pause + + def predict_one(self): + self.atl.predict(self.context) + + def visit(self): + if not self.block: + self.compile() + + return self.children + self.block.visit() + + +# The base class for raw ATL statements. +class RawStatement(renpy.object.Object): + + def __init__(self, loc): + super(RawStatement, self).__init__() + self.loc = loc + + # Compiles this RawStatement into a Statement, by using ctx to + # evaluate expressions as necessary. + def compile(self, ctx): #@ReservedAssignment + raise Exception("Compile not implemented.") + + # Predicts the images used by this statement. + def predict(self, ctx): + return + +# The base class for compiled ATL Statements. +class Statement(renpy.object.Object): + + def __init__(self, loc): + super(Statement, self).__init__() + self.loc = loc + + # trans is the transform we're working on. + # st is the time since this statement started executing. + # state is the state stored by this statement, or None if + # we've just started executing this statement. + # event is an event we're triggering. + # + # "continue", state, pause - Causes this statement to execute + # again, with the given state passed in the second time around. + # + # + # "next", timeleft, pause - Causes the next statement to execute, + # with timeleft being the amount of time left after this statement + # finished. + # + # "event", (name, timeleft), pause - Causes an event to be reported, + # and control to head up to the event handler. + # + # "repeat", (count, timeleft), pause - Causes the repeat behavior + # to occur. + # + # As the Repeat statement can only appear in a block, only Block + # needs to deal with the repeat behavior. + # + # Pause is the amount of time until execute should be called again, + # or None if there's no need to call execute ever again. + def execute(self, trans, st, state, event): + raise Exception("Not implemented.") + + # Return a list of displayable children. + def visit(self): + return [ ] + +# This represents a Raw ATL block. +class RawBlock(RawStatement): + + # Should we use the animation timebase or the showing timebase? + animation = False + + def __init__(self, loc, statements, animation): + + super(RawBlock, self).__init__(loc) + + # A list of RawStatements in this block. + self.statements = statements + + self.animation = animation + + def compile(self, ctx): #@ReservedAssignment + compiling(self.loc) + + statements = [ i.compile(ctx) for i in self.statements ] + + return Block(self.loc, statements) + + def predict(self, ctx): + for i in self.statements: + i.predict(ctx) + + +# A compiled ATL block. +class Block(Statement): + def __init__(self, loc, statements): + + super(Block, self).__init__(loc) + + # A list of statements in the block. + self.statements = statements + + # The start times of various statements. + self.times = [ ] + + for i, s in enumerate(statements): + if isinstance(s, Time): + self.times.append((s.time, i + 1)) + + self.times.sort() + + def execute(self, trans, st, state, event): + + executing(self.loc) + + # Unpack the state. + if state is not None: + index, start, loop_start, repeats, times, child_state = state + else: + index, start, loop_start, repeats, times, child_state = 0, 0, 0, 0, self.times[:], None + + # What we might be returning. + action = "continue" + arg = None + pause = None + + while action == "continue": + + # Target is the time we're willing to execute to. + # Max_pause is how long we'll wait before executing again. + + # If we have times queued up, then use them to inform target + # and time. + if times: + time, tindex = times[0] + target = min(time, st) + max_pause = time - target + + # Otherwise, take the defaults. + else: + target = st + max_pause = 15 + + while True: + + # If we've hit the last statement, it's the end of + # this block. + if index >= len(self.statements): + return "next", target - start, None + + + # Find the statement and try to run it. + stmt = self.statements[index] + action, arg, pause = stmt.execute(trans, target - start, child_state, event) + + # On continue, persist our state. + if action == "continue": + if pause is None: + pause = max_pause + + action, arg, pause = "continue", (index, start, loop_start, repeats, times, arg), min(max_pause, pause) + break + + elif action == "event": + return action, arg, pause + + # On next, advance to the next statement in the block. + elif action == "next": + index += 1 + start = target - arg + child_state = None + + # On repeat, either terminate the block, or go to + # the first statement. + elif action == "repeat": + + count, arg = arg + loop_end = target - arg + duration = loop_end - loop_start + + if duration <= 0: + raise Exception("ATL appears to be in an infinite loop.") + + # Figure how many durations can occur between the + # start of the loop and now. + new_repeats = int((target - loop_start) / duration) + + if count is not None: + if repeats + new_repeats >= count: + new_repeats = count - repeats + loop_start += new_repeats * duration + return "next", target - loop_start, None + + repeats += new_repeats + loop_start = loop_start + new_repeats * duration + start = loop_start + index = 0 + child_state = None + + if times: + time, tindex = times[0] + if time <= target: + times.pop(0) + + index = tindex + start = time + child_state = None + + continue + + return action, arg, pause + + def visit(self): + return [ j for i in self.statements for j in i.visit() ] + +# This can become one of four things: +# +# - A pause. +# - An interpolation (which optionally can also reference other +# blocks, as long as they're not time-dependent, and have the same +# arity as the interpolation). +# - A call to another block. +# - A command to change the image, perhaps with a transition. +# +# We won't decide which it is until runtime, as we need the +# values of the variables here. +class RawMultipurpose(RawStatement): + + warp_function = None + + def __init__(self, loc): + + super(RawMultipurpose, self).__init__(loc) + + self.warper = None + self.duration = None + self.properties = [ ] + self.expressions = [ ] + self.splines = [ ] + self.revolution = None + self.circles = "0" + + def add_warper(self, name, duration, warp_function): + self.warper = name + self.duration = duration + self.warp_function = warp_function + + def add_property(self, name, exprs): + self.properties.append((name, exprs)) + + def add_expression(self, expr, with_clause): + self.expressions.append((expr, with_clause)) + + def add_revolution(self, revolution): + self.revolution = revolution + + def add_circles(self, circles): + self.circles = circles + + def add_spline(self, name, exprs): + self.splines.append((name, exprs)) + + def compile(self, ctx): #@ReservedAssignment + + compiling(self.loc) + + # Figure out what kind of statement we have. If there's no + # interpolator, and no properties, than we have either a + # call, or a child statement. + if (self.warper is None and + self.warp_function is None and + not self.properties and + not self.splines and + len(self.expressions) == 1): + + expr, withexpr = self.expressions[0] + + child = ctx.eval(expr) + if withexpr: + transition = ctx.eval(withexpr) + else: + transition = None + + if isinstance(child, (int, float)): + return Interpolation(self.loc, "pause", child, [ ], None, 0, [ ]) + + if isinstance(child, ATLTransformBase): + child.compile() + return child.block + + else: + return Child(self.loc, child, transition) + + compiling(self.loc) + + # Otherwise, we probably have an interpolation statement. + + if self.warp_function: + warper = ctx.eval(self.warp_function) + else: + warper = self.warper or "pause" + + if warper not in warpers: + raise Exception("ATL Warper %s is unknown at runtime." % warper) + + properties = [ ] + + for name, expr in self.properties: + if name not in PROPERTIES: + raise Exception("ATL Property %s is unknown at runtime." % property) + + value = ctx.eval(expr) + properties.append((name, value)) + + splines = [ ] + + for name, exprs in self.splines: + if name not in PROPERTIES: + raise Exception("ATL Property %s is unknown at runtime." % property) + + values = [ ctx.eval(i) for i in exprs ] + + splines.append((name, values)) + + for expr, _with in self.expressions: + try: + value = ctx.eval(expr) + except: + raise Exception("Could not evaluate expression %r when compiling ATL." % expr) + + if not isinstance(value, ATLTransformBase): + raise Exception("Expression %r is not an ATL transform, and so cannot be included in an ATL interpolation." % expr) + + value.compile() + + if value.properties is None: + raise Exception("ATL transform %r is too complicated to be included in interpolation." % expr) + + + properties.extend(value.properties) + + duration = ctx.eval(self.duration) + circles = ctx.eval(self.circles) + + return Interpolation(self.loc, warper, duration, properties, self.revolution, circles, splines) + + def predict(self, ctx): + + for i, _j in self.expressions: + + try: + i = ctx.eval(i) + except: + continue + + if isinstance(i, ATLTransformBase): + i.atl.predict(ctx) + return + + try: + renpy.easy.predict(i) + except: + continue + +# This lets us have an ATL transform as our child. +class RawContainsExpr(RawStatement): + + def __init__(self, loc, expr): + + super(RawContainsExpr, self).__init__(loc) + + self.expression = expr + + def compile(self, ctx): #@ReservedAssignment + compiling(self.loc) + child = ctx.eval(self.expression) + return Child(self.loc, child, None) + + +# This allows us to have multiple children, inside a Fixed. +class RawChild(RawStatement): + + def __init__(self, loc, child): + + super(RawChild, self).__init__(loc) + + self.children = [ child ] + + def compile(self, ctx): #@ReservedAssignment + box = renpy.display.layout.MultiBox(layout='fixed') + + for i in self.children: + box.add(renpy.display.motion.ATLTransform(i, context=ctx.context)) + + return Child(self.loc, box, None) + + +# This changes the child of this statement, optionally with a transition. +class Child(Statement): + + def __init__(self, loc, child, transition): + + super(Child, self).__init__(loc) + + self.child = renpy.easy.displayable(child) + self.transition = transition + + def execute(self, trans, st, state, event): + + executing(self.loc) + + old_child = trans.raw_child + + if (old_child is not None) and (old_child is not renpy.display.motion.null) and (self.transition is not None): + child = self.transition(old_widget=old_child, + new_widget=self.child) + else: + child = self.child + + trans.set_child(child) + trans.raw_child = self.child + + return "next", st, None + + def visit(self): + return [ self.child ] + + +# This causes interpolation to occur. +class Interpolation(Statement): + + def __init__(self, loc, warper, duration, properties, revolution, circles, splines): + + super(Interpolation, self).__init__(loc) + + self.warper = warper + self.duration = duration + self.properties = properties + self.splines = splines + + # The direction we revolve in: cw, ccw, or None. + self.revolution = revolution + + # The number of complete circles we make. + self.circles = circles + + def execute(self, trans, st, state, event): + + executing(self.loc) + + warper = warpers.get(self.warper, self.warper) + + if self.duration: + complete = min(1.0, st / self.duration) + else: + complete = 1.0 + + complete = warper(complete) + + if state is None: + + # Create a new transform state, and apply the property + # changes to it. + newts = renpy.display.motion.TransformState() + newts.take_state(trans.state) + + for k, v in self.properties: + setattr(newts, k, v) + + # Now, the things we change linearly are in the difference + # between the new and old states. + linear = trans.state.diff(newts) + + revolution = None + splines = [ ] + + # Clockwise revolution. + if self.revolution is not None: + + # Remove various irrelevant motions. + for i in [ 'xpos', 'ypos', + 'xanchor', 'yanchor', + 'xaround', 'yaround', + 'xanchoraround', 'yanchoraround', + ]: + + linear.pop(i, None) + + if newts.xaround is not None: + + # Ensure we rotate around the new point. + trans.state.xaround = newts.xaround + trans.state.yaround = newts.yaround + trans.state.xanchoraround = newts.xanchoraround + trans.state.yanchoraround = newts.yanchoraround + + # Get the start and end angles and radii. + startangle = trans.state.angle + endangle = newts.angle + startradius = trans.state.radius + endradius = newts.radius + + # Make sure the revolution is in the appropriate direction, + # and contains an appropriate number of circles. + + if self.revolution == "clockwise": + if endangle < startangle: + startangle -= 360 + + startangle -= self.circles * 360 + + elif self.revolution == "counterclockwise": + if endangle > startangle: + startangle += 360 + + startangle += self.circles * 360 + + # Store the revolution. + revolution = (startangle, endangle, startradius, endradius) + + # Figure out the splines. + for name, values in self.splines: + splines.append((name, [ getattr(trans.state, name) ] + values)) + + state = (linear, revolution, splines) + + # Ensure that we set things, even if they don't actually + # change from the old state. + for k, v in self.properties: + if k not in linear: + setattr(trans.state, k, v) + + else: + linear, revolution, splines = state + + # Linearly interpolate between the things in linear. + for k, (old, new) in linear.items(): + value = interpolate(complete, old, new, PROPERTIES[k]) + + setattr(trans.state, k, value) + + # Handle the revolution. + if revolution is not None: + startangle, endangle, startradius, endradius = revolution + trans.state.angle = interpolate(complete, startangle, endangle, float) + trans.state.radius = interpolate(complete, startradius, endradius, float) + + + # Handle any splines we might have. + for name, values in splines: + value = interpolate_spline(complete, values) + setattr(trans.state, name, value) + + if st >= self.duration: + return "next", st - self.duration, None + else: + if not self.properties and not self.revolution and not self.splines: + return "continue", state, self.duration - st + else: + return "continue", state, 0 + + +# Implementation of the repeat statement. +class RawRepeat(RawStatement): + + def __init__(self, loc, repeats): + + super(RawRepeat, self).__init__(loc) + + self.repeats = repeats + + def compile(self, ctx): #@ReservedAssignment + + compiling(self.loc) + + repeats = self.repeats + + if repeats is not None: + repeats = ctx.eval(repeats) + + return Repeat(self.loc, repeats) + +class Repeat(Statement): + + def __init__(self, loc, repeats): + + super(Repeat, self).__init__(loc) + + self.repeats = repeats + + def execute(self, trans, st, state, event): + return "repeat", (self.repeats, st), 0 + + +# Parallel statement. + +class RawParallel(RawStatement): + + def __init__(self, loc, block): + + super(RawParallel, self).__init__(loc) + self.blocks = [ block ] + + def compile(self, ctx): #@ReservedAssignment + return Parallel(self.loc, [i.compile(ctx) for i in self.blocks]) + + def predict(self, ctx): + for i in self.blocks: + i.predict(ctx) + + +class Parallel(Statement): + + def __init__(self, loc, blocks): + super(Parallel, self).__init__(loc) + self.blocks = blocks + + def execute(self, trans, st, state, event): + + executing(self.loc) + + if state is None: + state = [ (i, None) for i in self.blocks ] + + # The amount of time left after finishing this block. + left = [ ] + + # The duration of the pause. + pauses = [ ] + + # The new state structure. + newstate = [ ] + + for i, istate in state: + + action, arg, pause = i.execute(trans, st, istate, event) + + if pause is not None: + pauses.append(pause) + + if action == "continue": + newstate.append((i, arg)) + elif action == "next": + left.append(arg) + elif action == "event": + return action, arg, pause + + if newstate: + return "continue", newstate, min(pauses) + else: + return "next", min(left), None + + def visit(self): + return [ j for i in self.blocks for j in i.visit() ] + + +# The choice statement. + +class RawChoice(RawStatement): + + def __init__(self, loc, chance, block): + super(RawChoice, self).__init__(loc) + + self.choices = [ (chance, block) ] + + def compile(self, ctx): #@ReservedAssignment + compiling(self.loc) + return Choice(self.loc, [ (ctx.eval(chance), block.compile(ctx)) for chance, block in self.choices]) + + def predict(self, ctx): + for _i, j in self.choices: + j.predict(ctx) + +class Choice(Statement): + + def __init__(self, loc, choices): + + super(Choice, self).__init__(loc) + + self.choices = choices + + def execute(self, trans, st, state, event): + + executing(self.loc) + + if state is None: + + total = 0 + for chance, choice in self.choices: + total += chance + + n = random.uniform(0, total) + + for chance, choice in self.choices: + if n < chance: + break + n -= chance + + cstate = None + + else: + choice, cstate = state + + action, arg, pause = choice.execute(trans, st, cstate, event) + + if action == "continue": + return "continue", (choice, arg), pause + else: + return action, arg, None + + def visit(self): + return [ j for i in self.choices for j in i[1].visit() ] + + +# The Time statement. + +class RawTime(RawStatement): + + def __init__(self, loc, time): + + super(RawTime, self).__init__(loc) + self.time = time + + def compile(self, ctx): #@ReservedAssignment + compiling(self.loc) + return Time(self.loc, ctx.eval(self.time)) + +class Time(Statement): + + def __init__(self, loc, time): + super(Time, self).__init__(loc) + + self.time = time + + def execute(self, trans, st, state, event): + return "continue", None, None + + +# The On statement. + +class RawOn(RawStatement): + + def __init__(self, loc, name, block): + super(RawOn, self).__init__(loc) + + self.handlers = { name : block } + + def compile(self, ctx): #@ReservedAssignment + + compiling(self.loc) + + handlers = { } + + for k, v in self.handlers.items(): + handlers[k] = v.compile(ctx) + + return On(self.loc, handlers) + + def predict(self, ctx): + for i in self.handlers.values(): + i.predict(ctx) + +class On(Statement): + + def __init__(self, loc, handlers): + super(On, self).__init__(loc) + + self.handlers = handlers + + def execute(self, trans, st, state, event): + + executing(self.loc) + + # If it's our first time through, start in the start state. + if state is None: + name, start, cstate = ("start", st, None) + else: + name, start, cstate = state + + # If we have an external event, and we have a handler for it, + # handle it. + if event in self.handlers: + + # Do not allow people to abort the hide handler with another + # event. + if name != "hide": + name = event + start = st + cstate = None + + + while True: + + # If we don't have a handler, return until we change event. + if name not in self.handlers: + return "continue", (name, start, cstate), None + + action, arg, pause = self.handlers[name].execute(trans, st - start, cstate, event) + + # If we get a continue, save our state. + if action == "continue": + + # If it comes from a hide block, indicate that. + if name == "hide" or name == "replaced": + trans.hide_response = False + trans.replaced_response = False + + return "continue", (name, start, arg), pause + + # If we get a next, then try going to the default + # event, unless we're already in default, in which case we + # go to None. + elif action == "next": + if name == "default" or name == "hide" or name == "replaced": + name = None + else: + name = "default" + + start = st - arg + cstate = None + + continue + + # If we get an event, then either handle it if we can, or + # pass it up the stack if we can't. + elif action == "event": + + name, arg = arg + + if name in self.handlers: + start = max(st - arg, st - 30) + cstate = None + continue + + return "event", (name, arg), None + + def visit(self): + return [ j for i in self.handlers.values() for j in i.visit() ] + + +# Event statement. + +class RawEvent(RawStatement): + + def __init__(self, loc, name): + super(RawEvent, self).__init__(loc) + + self.name = name + + def compile(self, ctx): #@ReservedAssignment + return Event(self.loc, self.name) + + +class Event(Statement): + + def __init__(self, loc, name): + super(Event, self).__init__(loc) + + self.name = name + + def execute(self, trans, st, state, event): + return "event", (self.name, st), None + + +class RawFunction(RawStatement): + + def __init__(self, loc, expr): + super(RawFunction, self).__init__(loc) + + self.expr = expr + + def compile(self, ctx): #@ReservedAssignment + compiling(self.loc) + return Function(self.loc, ctx.eval(self.expr)) + + +class Function(Statement): + + def __init__(self, loc, function): + super(Function, self).__init__(loc) + + self.function = function + + def execute(self, trans, st, state, event): + fr = self.function(trans, st, trans.at) + + if fr is not None: + return "continue", None, fr + else: + return "next", 0, None + + +# This parses an ATL block. +def parse_atl(l): + + l.advance() + block_loc = l.get_location() + + statements = [ ] + + animation = False + + while not l.eob: + + loc = l.get_location() + + if l.keyword('repeat'): + + repeats = l.simple_expression() + statements.append(RawRepeat(loc, repeats)) + + elif l.keyword('block'): + l.require(':') + l.expect_eol() + l.expect_block('block') + + block = parse_atl(l.subblock_lexer()) + statements.append(block) + + elif l.keyword('contains'): + + expr = l.simple_expression() + + if expr: + + l.expect_noblock('contains expression') + statements.append(RawContainsExpr(loc, expr)) + + else: + + l.require(':') + l.expect_eol() + l.expect_block('contains') + + block = parse_atl(l.subblock_lexer()) + statements.append(RawChild(loc, block)) + + elif l.keyword('parallel'): + l.require(':') + l.expect_eol() + l.expect_block('parallel') + + block = parse_atl(l.subblock_lexer()) + statements.append(RawParallel(loc, block)) + + elif l.keyword('choice'): + + chance = l.simple_expression() + if not chance: + chance = "1.0" + + l.require(':') + l.expect_eol() + l.expect_block('choice') + + block = parse_atl(l.subblock_lexer()) + statements.append(RawChoice(loc, chance, block)) + + elif l.keyword('on'): + + name = l.require(l.word) + + l.require(':') + l.expect_eol() + l.expect_block('on') + + block = parse_atl(l.subblock_lexer()) + statements.append(RawOn(loc, name, block)) + + elif l.keyword('time'): + time = l.require(l.simple_expression) + l.expect_noblock('time') + + statements.append(RawTime(loc, time)) + + elif l.keyword('function'): + expr = l.require(l.simple_expression) + l.expect_noblock('function') + + statements.append(RawFunction(loc, expr)) + + elif l.keyword('event'): + name = l.require(l.word) + l.expect_noblock('event') + + statements.append(RawEvent(loc, name)) + + elif l.keyword('pass'): + l.expect_noblock('pass') + statements.append(None) + + elif l.keyword('animation'): + l.expect_noblock('animation') + animation = True + + else: + + # If we can't assign it it a statement more specifically, + # we try to parse it into a RawMultipurpose. That will + # then be turned into another statement, as appropriate. + + # The RawMultipurpose we add things to. + rm = renpy.atl.RawMultipurpose(loc) + + # Is the last clause an expression? + last_expression = False + + # Is this clause an expression? + this_expression = False + + # First, look for a warper. + cp = l.checkpoint() + warper = l.name() + + + if warper in warpers: + duration = l.require(l.simple_expression) + warp_function = None + + elif warper == "warp": + + warper = None + warp_function = l.require(l.simple_expression) + duration = l.require(l.simple_expression) + + else: + l.revert(cp) + + warper = None + warp_function = None + duration = "0" + + rm.add_warper(warper, duration, warp_function) + + # Now, look for properties and simple_expressions. + while True: + + # Update expression status. + last_expression = this_expression + this_expression = False + + if l.keyword('pass'): + continue + + # Parse revolution keywords. + if l.keyword('clockwise'): + rm.add_revolution('clockwise') + continue + + if l.keyword('counterclockwise'): + rm.add_revolution('counterclockwise') + continue + + if l.keyword('circles'): + expr = l.require(l.simple_expression) + rm.add_circles(expr) + + # Try to parse a property. + cp = l.checkpoint() + + prop = l.name() + + if prop in PROPERTIES: + + expr = l.require(l.simple_expression) + + # We either have a property or a spline. It's the + # presence of knots that determine which one it is. + + knots = [ ] + + while l.keyword('knot'): + knots.append(l.require(l.simple_expression)) + + if knots: + knots.append(expr) + rm.add_spline(prop, knots) + else: + rm.add_property(prop, expr) + + continue + + # Otherwise, try to parse it as a simple expressoon, + # with an optional with clause. + + l.revert(cp) + + expr = l.simple_expression() + + if not expr: + break + + if last_expression: + l.error('ATL statement contains two expressions in a row; is one of them a misspelled property? If not, separate them with pass.') + + this_expression = True + + if l.keyword("with"): + with_expr = l.require(l.simple_expression) + else: + with_expr = None + + rm.add_expression(expr, with_expr) + + l.expect_noblock('ATL') + + statements.append(rm) + + + if l.eol(): + l.advance() + continue + + l.require(",", "comma or end of line") + + + # Merge together statements that need to be merged together. + + merged = [ ] + old = None + + for new in statements: + + if isinstance(old, RawParallel) and isinstance(new, RawParallel): + old.blocks.extend(new.blocks) + continue + + elif isinstance(old, RawChoice) and isinstance(new, RawChoice): + old.choices.extend(new.choices) + continue + + elif isinstance(old, RawChild) and isinstance(new, RawChild): + old.children.extend(new.children) + continue + + elif isinstance(old, RawOn) and isinstance(new, RawOn): + old.handlers.update(new.handlers) + continue + + # None is a pause statement, which gets skipped, but also + # prevents things from combining. + elif new is None: + old = new + continue + + merged.append(new) + old = new + + return RawBlock(block_loc, merged, animation) |