CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/common/00layeredimage.rpy
2025-03-03 23:00:33 +01:00

1136 lines
31 KiB
Text

init offset = -100
python early in layeredimage:
from store import Transform, ConditionSwitch, Fixed, Null, config, Text, eval
from collections import OrderedDict
ATL_PROPERTIES = [ i for i in renpy.atl.PROPERTIES ]
# The properties for attribute layers.
LAYER_PROPERTIES = [ "if_all", "if_any", "if_not", "at" ] + ATL_PROPERTIES
# This is the default value for predict_all given to conditions.
predict_all = False
def format_function(what, name, group, variant, attribute, image, image_format, **kwargs):
"""
:doc: li_ff
This is called to format the information about an attribute
or condition into a displayable. This can be replaced by a
creator, but the new function should ignore unknown kwargs.
`what`
A string giving a description of the thing being formatted,
which is used to create better error messages.
`name`
The name of the attribute image.
`group`
The group of an attribute, None if not supplied or if it's
part of a condition.
`variant`
The variant argument to the group, or None if it is not
supplied.
`attribute`
The attribute itself.
`image`
Either a displayable or string.
`image_format`
The image_format argument of the LayeredImage.
If `image` is None, then `name`, `group` (if not None), `variant` (if not None),
and `attribute` are combined with underscores to create `image`, which
will then be a string.
If `images` is a string, and `image_format` is not None, `image` is formatted
into the string to get the final displayable.
So if `name` is "eileen", `group` is "expression", and
`attribute` is "happy", `image` would be set to "eileen_expression_happy".
If `image_format` is "images/{image}.png",
the final image Ren'Py finds is "images/eileen_expression_happy.png".
But note that it would have found the same image without the format
argument.
"""
if image is None:
if name is None:
raise Exception("Can't find an image name to format {}.".format(what))
if attribute is None:
raise Exception("Can't find an attribute name to format {}.".format(what))
parts = [ name ]
if group is not None:
parts.append(group)
if variant is not None:
parts.append(variant)
parts.append(attribute)
image = "_".join(parts)
if isinstance(image, basestring) and (image_format is not None):
image = image_format.format(name=name, image=image)
return image
class Layer(object):
"""
Base class for our layers.
"""
def __init__(self, if_all=[ ], if_any=[ ], if_not=[ ], at=[ ], **kwargs):
if not isinstance(at, list):
at = [ at ]
self.at = at
if not isinstance(if_all, list):
if_all = [ if_all ]
self.if_all = if_all
if not isinstance(if_any, list):
if_any = [ if_any ]
self.if_any = if_any
if not isinstance(if_not, list):
if_not = [ if_not ]
self.if_not = if_not
self.transform_args = kwargs
def check(self, attributes):
for i in self.if_all:
if i not in attributes:
return False
if self.if_any:
for i in self.if_any:
if i in attributes:
break
else:
return False
for i in self.if_not:
if i in attributes:
return False
return True
def wrap(self, d):
"""
Wraps a displayable in the at list and transform arguments.
"""
d = renpy.displayable(d)
for i in self.at:
d = i(d)
if self.transform_args:
d = Transform(d, **self.transform_args)
return d
class Attribute(Layer):
"""
:doc: li
:name: Attribute
This is used to represent a layer of an LayeredImage that is
controlled by an attribute. A single attribute can control
multiple layers, in which case all layers corresponding to
that attribute will be displayed.
`group`
A string giving the group the attribute is part of. This
may be None, in which case a group with the same name as
the attribute is created.
`attribute`
A string giving the name of the attribute.
`image`
If not None, this should be a displayable that is displayed when
this attribute is shown.
`default`
If True, and no other attribute for the group is selected,
this attribute is.
The following keyword arguments are also known:
`at`
A transform or list of transforms that are applied to the
image.
`if_all`
An attribute or list of attributes. The displayable is only shown
if all of these are showing.
`if_any`
An attribute or list of attributes. if not empty, the displayable is only shown
if any of these are showing.
`if_not`
An attribute or list of attributes. The displayable is only shown
if none of these are showing.
Other keyword arguments are interpreted as transform properties. If
any are present, a transform is created that wraps the image. (For
example, pos=(100, 200) can be used to offset the image by 100 pixels
horizontally and 200 vertically.)
If the `image` parameter is omitted or None, and the LayeredImage
has been given the `image_format` parameter, the image_format is used
to generate an image filename.
"""
def __init__(self, group, attribute, image=None, default=False, **kwargs):
prefix = kwargs.pop("prefix", None)
variant = kwargs.pop("variant", None)
super(Attribute, self).__init__(**kwargs)
self.group = group
self.raw_attribute = attribute
if prefix is not None:
attribute = prefix + "_" + attribute
self.attribute = attribute
self.image = image
self.default = default
self.variant = variant
def apply_format(self, ai):
self.image = self.wrap(ai.format(
"Attribute ({!r}, {!r})".format(self.group, self.attribute),
group=self.group,
variant=self.variant,
attribute=self.raw_attribute,
image=self.image,
))
def get_displayable(self, attributes):
if self.attribute not in attributes:
return None
if not self.check(attributes):
return None
return self.image
class RawAttribute(object):
def __init__(self, name):
self.name = name
self.image = None
self.properties = OrderedDict()
def execute(self, group=None, properties=None):
if properties is not None:
properties = dict(properties)
else:
properties = dict()
if self.image:
image = eval(self.image)
else:
image = None
properties.update({ k : eval(v) for k, v in self.properties.items() })
return [ Attribute(group, self.name, image, **properties) ]
class RawAttributeGroup(object):
def __init__(self, image_name, group):
self.image_name = image_name
self.group = group
self.properties = OrderedDict()
self.children = [ ]
def execute(self):
properties = { k : eval(v) for k, v in self.properties.items() }
auto = properties.pop("auto", False)
variant = properties.get("variant", None)
multiple = properties.pop("multiple", False)
rv = [ ]
if multiple:
group = None
else:
group = self.group
for i in self.children:
rv.extend(i.execute(group=group, properties=properties))
if auto:
seen = set(i.raw_attribute for i in rv)
pattern = self.image_name.replace(" ", "_") + "_" + self.group + "_"
if variant:
pattern += variant + "_"
for i in renpy.list_images():
if i.startswith(pattern):
rest = i[len(pattern):]
attrs = rest.split()
if len(attrs) == 1:
if attrs[0] not in seen:
rv.append(Attribute(group, attrs[0], renpy.displayable(i), **properties))
return rv
class Condition(Layer):
"""
:doc: li
:name: Condition
This is used to represent a layer of an LayeredImage that
is controlled by a condition. When the condition is true,
the layer is displayed. Otherwise, nothing is displayed.
`condition`
This should be a string giving a Python condition that determines
if the layer is displayed.
`image`
If not None, this should be a displayable that is displayed when
the condition is true.
`if_all`
An attribute or list of attributes. The condition is only evaluated
if all of these are showing.
`if_any`
An attribute or list of attributes. If not empty, the condition is only evaluated
if any of these are showing.
`if_not`
An attribute or list of attributes. The condition is only evaluated
if none of these are showing.
`at`
A transform or list of transforms that are applied to the
image.
Other keyword arguments are interpreted as transform properties. If
any are present, a transform is created that wraps the image. (For
example, pos=(100, 200) can be used to offset the image by 100 pixels
horizontally and 200 vertically.)
"""
at = [ ]
def __init__(self, condition, image, **kwargs):
self.condition = condition
self.image = image
super(Condition, self).__init__(**kwargs)
def apply_format(self, ai):
self.image = self.wrap(ai.format(
"Condition ({})".format(self.condition),
group=None,
attribute=None,
image=self.image,
))
def get_displayable(self, attributes):
if not self.check(attributes):
return None
return ConditionSwitch(
self.condition, self.image,
None, Null(),
predict_all=predict_all,
)
class RawCondition(object):
def __init__(self, condition):
self.condition = condition
self.image = None
self.properties = OrderedDict()
def execute(self):
properties = { k : eval(v) for k, v in self.properties.items() }
return [ Condition(self.condition, eval(self.image), **properties) ]
class ConditionGroup(Layer):
"""
Combines a list of conditions into a single ConditionSwitch.
"""
def __init__(self, conditions):
self.conditions = conditions
def apply_format(self, ai):
for i in self.conditions:
i.apply_format(ai)
def get_displayable(self, attributes):
args = [ ]
for i in self.conditions:
if not i.check(attributes):
continue
args.append(i.condition)
args.append(i.image)
args.append(None)
args.append(Null())
return ConditionSwitch(predict_all=predict_all, *args)
class RawConditionGroup(object):
def __init__(self):
self.conditions = [ ]
def execute(self):
l = [ ]
for i in self.conditions:
l.extend(i.execute())
return [ ConditionGroup(l) ]
class Always(Layer):
"""
:doc: li
:name: Always
This is used for a displayable that is always shown.
`image`
The displayable to show.
`default`
If True, and no other attribute for the group is selected,
this attribute is.
`at`
A transform or list of transforms that are applied to the
image.
`if_all`
An attribute or list of attributes. The displayable is only shown
if all of these are showing.
`if_any`
An attribute or list of attributes. If not empty, the displayable
is only shown if any of these are showing.
`if_not`
An attribute or list of attributes. The displayable is only shown
if none of these are showing.
"""
def __init__(self, image, **kwargs):
self.image = image
super(Always, self).__init__(**kwargs)
def apply_format(self, ai):
self.image = self.wrap(ai.format(
"Always",
group=None,
attribute=None,
image=self.image,
))
def get_displayable(self, attributes):
if not self.check(attributes):
return None
return self.image
class RawAlways(object):
def __init__(self):
self.image = None
self.properties = OrderedDict()
def execute(self):
if self.image:
image = eval(self.image)
else:
image = None
properties = { k : eval(v) for k, v in self.properties.items() }
return [ Always(image, **properties) ]
class LayeredImage(object):
"""
:doc: li
:name: LayeredImage
This is an image-like object that, when shown with the proper set of
attributes, shows a displayable created by compositing together the
displayables associated with those attribute.
`attributes`
This must be a list of Attribute objects. Each Attribute object
reflects a displayable that may or may not be displayed as part
of the image. The items in this list are in back-to-front order,
with the first item further from the viewer and the last
closest.
`at`
A transform or list of transforms that are applied to the displayable
after it is parameterized.
`name`
The name of the attribute image. This is used as part of the names
of image components.
`image_format`
When a given image is a string, and this is supplied, the image name
is interpolated into `image_format` to make an image file. For example,
"sprites/eileen/{image}.png" will look for the image in a subdirectory
of sprites. (This is not used by auto groups, which look for images and
not image files.)
`format_function`
A function that is used instead of `layeredimage.format_function` to format
the image information into a displayable.
`attribute_function`
If not None, a function that's called with a set of attributes supplied to
the image, and returns the set of attributes used to select layers. This is
called when determining the layers to display, after the attribute themselves
have been chosen. It can be used to express complex dependencies between attributes
or select attributes at random.
Additional keyword arguments are passed to a Fixed that is created to hold
the layer. Unless explicitly overridden, xfit and yfit are set to true on
the Fixed, which means it will shrink to the smallest size that fits all
of the layer images it is showing.
A LayeredImage is not a displayable, and can't be used in all the
places a displayable can be used. This is because it requires an image
name (generally including image attributes) to be provided. As such,
it should either be displayed through a scene or show statement, or by
an image name string used as a displayable.
"""
attribute_function = None
def __init__(self, attributes, at=[], name=None, image_format=None, format_function=None, attribute_function=None, **kwargs):
self.name = name
self.image_format = image_format
self.format_function = format_function
self.attribute_function = attribute_function
self.attributes = [ ]
self.layers = [ ]
import collections
self.attribute_to_groups = collections.defaultdict(set)
self.group_to_attributes = collections.defaultdict(set)
for i in attributes:
self.add(i)
if not isinstance(at, list):
at = [ at ]
self.at = at
kwargs.setdefault("xfit", True)
kwargs.setdefault("yfit", True)
self.fixed_args = kwargs
def format(self, what, attribute=None, group=None, variant=None, image=None):
ff = format_function
if self.format_function is not None:
ff = self.format_function
return ff(
what=what,
name=self.name,
group=group,
variant=variant,
attribute=attribute,
image=image,
image_format=self.image_format)
def add(self, a):
if not isinstance(a, Layer):
a = Always(a)
a.apply_format(self)
self.layers.append(a)
if isinstance(a, Attribute):
self.attributes.append(a)
if a.group is not None:
self.attribute_to_groups[a.attribute].add(a.group)
self.group_to_attributes[a.group].add(a.attribute)
def get_banned(self, attributes):
"""
Get the set of attributes that are incompatible with those
in attributes.
"""
rv = set()
for i in attributes:
for g in self.attribute_to_groups[i]:
for j in self.group_to_attributes[g]:
if j != i:
rv.add(j)
return rv
def _duplicate(self, args):
name = " ".join(args.name + tuple(args.args))
attributes = set(args.args)
unknown = set(args.args)
banned = self.get_banned(attributes)
for a in self.attributes:
unknown.discard(a.attribute)
if a.default and (a.attribute not in banned):
attributes.add(a.attribute)
if self.attribute_function:
attributes = set(self.attribute_function(attributes))
rv = Fixed(**self.fixed_args)
for i in self.layers:
d = i.get_displayable(attributes)
if d is not None:
if d._duplicatable:
d = d._duplicate(None)
rv.add(d)
if unknown and args.lint:
args = args.copy()
args.args = tuple(unknown)
args.extraneous()
if unknown and config.developer:
message = [" ".join(args.name), "unknown attributes:", " ".join(sorted(unknown))]
text = Text(
"\n".join(message),
size=16,
xalign=0.5,
yalign=0.5,
text_align=0.5,
color="#fff",
outlines=[ (1, "#0008", 0, 0) ],
)
rv = Fixed(rv, text, fit_first=True)
for i in self.at:
rv = i(rv)
return rv
def _list_attributes(self, tag, attributes):
banned = self.get_banned(attributes)
group_attr = [ ]
seen = set()
group_count = 0
old_group = None
for a in self.attributes:
if a.group != old_group:
old_group = a.group
group_count += 1
if a.attribute in banned:
continue
if a.attribute in seen:
continue
seen.add(a.attribute)
group_attr.append((group_count, a.attribute))
group_attr.sort()
return [ i[1] for i in group_attr ]
def _choose_attributes(self, tag, attributes, optional):
unknown = list(attributes)
attributes = set(attributes)
banned = self.get_banned(attributes)
both = attributes & banned
if both:
raise Exception("The attributes for {} conflict: {}".format(tag, " ".join(both)))
if optional is not None:
attributes |= (set(optional) - banned)
rv = [ ]
for a in self.attributes:
if a.attribute in attributes:
if a.attribute not in rv:
rv.append(a.attribute)
if a.attribute in unknown:
unknown.remove(a.attribute)
if unknown:
return None
return tuple(rv)
class RawLayeredImage(object):
def __init__(self, name):
self.name = name
self.children = [ ]
self.properties = OrderedDict()
def execute(self):
properties = { k : eval(v) for k, v in self.properties.items() }
l = [ ]
for i in self.children:
l.extend(i.execute())
renpy.image(
self.name,
LayeredImage(l, name=self.name.replace(" ", "_"), **properties),
)
def execute_layeredimage(rai):
rai.execute()
def parse_property(l, o, names):
"""
Parses a property, returns True if one is found.
"""
checkpoint = l.checkpoint()
name = l.word()
if name is None:
return False
if name not in names:
l.revert(checkpoint)
return False
if name in o.properties:
l.error("Duplicate property " + name)
if name == "auto" or name == "default" or name == "multiple":
expr = "True"
else:
expr = l.require(l.simple_expression)
o.properties[name] = expr
return True
def parse_attribute(l, parent):
name = l.require(l.image_name_component)
a = RawAttribute(name)
parent.children.append(a)
def line(l):
while True:
if parse_property(l, a, [ "default" ] + LAYER_PROPERTIES):
continue
if l.match('null'):
image = "Null()"
else:
image = l.simple_expression()
if image is not None:
if a.image is not None:
l.error('An attribute can only have zero or one displayables, two found.')
a.image = image
continue
break
line(l)
if not l.match(':'):
l.expect_eol()
l.expect_noblock('attribute')
return
l.expect_block('attribute')
l.expect_eol()
ll = l.subblock_lexer()
while ll.advance():
line(ll)
ll.expect_eol()
ll.expect_noblock('attribute')
return
def parse_always(l, parent):
a = RawAlways()
parent.children.append(a)
def line(l):
while True:
if parse_property(l, a, LAYER_PROPERTIES):
continue
image = l.simple_expression()
if image is not None:
if a.image is not None:
l.error('The always statement can only have one displayable, two found.')
a.image = image
continue
break
line(l)
if not l.match(':'):
l.expect_eol()
l.expect_noblock('attribute')
return
l.expect_block('attribute')
l.expect_eol()
ll = l.subblock_lexer()
while ll.advance():
line(ll)
ll.expect_eol()
ll.expect_noblock('attribute')
if a.image is None:
l.error("The always statement must have a displayable.")
return
def parse_group(l, parent, image_name):
group = l.require(l.image_name_component)
rv = RawAttributeGroup(image_name, group)
parent.children.append(rv)
while parse_property(l, rv, [ "auto", "prefix", "variant", "multiple" ] + LAYER_PROPERTIES):
pass
if l.match(':'):
l.expect_eol()
l.expect_block("group")
ll = l.subblock_lexer()
while ll.advance():
if ll.keyword("attribute"):
parse_attribute(ll, rv)
continue
while parse_property(ll, rv, [ "auto" ] + LAYER_PROPERTIES):
pass
ll.expect_eol()
ll.expect_noblock('group property')
else:
l.expect_eol()
l.expect_noblock("group")
def parse_condition(l, need_expr):
l.skip_whitespace()
if need_expr:
condition = l.delimited_python(':')
else:
condition = None
l.require(':')
l.expect_block('condition')
l.expect_eol()
ll = l.subblock_lexer()
rv = RawCondition(condition)
while ll.advance():
while True:
if parse_property(ll, rv, LAYER_PROPERTIES):
continue
image = ll.simple_expression()
if image is not None:
if rv.image is not None:
ll.error('A condition can only have one displayable, two found.')
rv.image = image
continue
break
ll.expect_noblock("condition properties")
ll.expect_eol()
if rv.image is None:
l.error("A condition must have a displayable.")
return rv
def parse_conditions(l, parent):
cg = RawConditionGroup()
cg.conditions.append(parse_condition(l, True))
l.advance()
while l.keyword('elif'):
cg.conditions.append(parse_condition(l, True))
l.advance()
if l.keyword('else'):
cg.conditions.append(parse_condition(l, False))
l.advance()
parent.children.append(cg)
def parse_layeredimage(l):
name = [ l.require(l.image_name_component) ]
while True:
part = l.image_name_component()
if part is None:
break
name.append(part)
l.require(':')
l.expect_block("layeredimage")
ll = l.subblock_lexer()
ll.advance()
name = " ".join(name)
rv = RawLayeredImage(name)
while not ll.eob:
if ll.keyword('attribute'):
parse_attribute(ll, rv)
ll.advance()
elif ll.keyword('group'):
parse_group(ll, rv, name)
ll.advance()
elif ll.keyword('if'):
parse_conditions(ll, rv)
# Advances for us.
elif ll.keyword('always'):
parse_always(ll, rv)
ll.advance()
else:
while parse_property(ll, rv, [ "image_format", "format_function", "attribute_function", "at" ] +
renpy.sl2.slproperties.position_property_names +
renpy.sl2.slproperties.box_property_names
):
pass
ll.expect_noblock('statement')
ll.expect_eol()
ll.advance()
return rv
renpy.register_statement("layeredimage", parse=parse_layeredimage, execute=execute_layeredimage, init=True, block=True)
class LayeredImageProxy(object):
"""
:doc: li_proxy
:name: LayeredImageProxy
This is an image-like object that proxies attributes passed to it to
another layered image.
`name`
A string giving the name of the layered image to proxy to.
`transform`
If given, a transform or list of transforms that are applied to the
image after it has been proxied.
"""
def __init__(self, name, transform=None):
self.name = name
if "[" not in self.name:
if renpy.get_registered_image(name) is None:
raise Exception("{!r} is not a registered image name.".format(self.name))
if transform is None:
self.transform = [ ]
elif isinstance(transform, list):
self.transform = transform
else:
self.transform = [ transform ]
@property
def image(self):
name = self.name
if "[" in name:
name = renpy.substitute(name, translate=False)
image = renpy.get_registered_image(name)
if image is None:
raise Exception("{!r} is not a registered image name, in LayeredImageProxy.".format(name))
return image
def _duplicate(self, args):
rv = self.image._duplicate(args)
for i in self.transform:
rv = i(rv)
return rv
def _choose_attributes(self, tag, attributes, optional):
return self.image._choose_attributes(tag, attributes, optional)
def _list_attributes(self, tag, attributes):
return self.image._list_attributes(tag, attributes)
renpy.store.Attribute = Attribute
renpy.store.LayeredImage = LayeredImage
renpy.store.LayeredImageProxy = LayeredImageProxy
renpy.store.Condition = Condition
renpy.store.ConditionGroup = ConditionGroup