summaryrefslogtreecommitdiff
path: root/ast2json/renpy/atl.py
diff options
context:
space:
mode:
Diffstat (limited to 'ast2json/renpy/atl.py')
-rw-r--r--ast2json/renpy/atl.py1544
1 files changed, 1544 insertions, 0 deletions
diff --git a/ast2json/renpy/atl.py b/ast2json/renpy/atl.py
new file mode 100644
index 0000000..a321e6b
--- /dev/null
+++ b/ast2json/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
+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)