Example #1
0
 def apply_animated_material_forcefield(self) -> None:
     """Renders a GIF loosely based on the behavior of the AnimatedMaterialForcefield part."""
     # tile rotates every 500 ms (_1 to _4), color rotates every 250 ms
     obj, tile = self.qud_object, self.qud_tile
     tile_prefix = tile.filename.rsplit('_', 1)[0][:-1]
     tile_postfix = '_' + tile.filename.rsplit('_', 1)[1]
     tile_numerals = ['1', '1', '2', '2', '3', '3', '4', '4']
     tile_colors = ['&C', '&c', '&C', '&c']
     tile_details = [None, 'K', 'c', 'C']
     forcefield_palette = obj.part_AnimatedMaterialForcefield_Color
     if forcefield_palette is not None:
         if forcefield_palette == 'Red':
             tile_colors = ['&R', '&r', '&R', '&r']
             tile_details = [None, 'r', 'r', 'r']
         elif forcefield_palette == 'Blue':
             tile_colors = ['&B', '&b', '&B', '&b']
             tile_details = [None, 'K', 'b', 'B']
     tile_colors = tile_colors + tile_colors
     tile_details = tile_details + tile_details
     frames = []
     durations = []
     for numeral, color, detail in zip(tile_numerals, tile_colors,
                                       tile_details):
         f = tile_prefix + numeral + tile_postfix  # construct filename
         frames.append(
             QudTile(f, color, color, detail, tile.qudname,
                     tile.raw_transparent))
         durations.append(250)
     self._make_gif(frames, durations)
Example #2
0
 def apply_phase_sticky(self) -> None:
     """Renders a GIF loosely based on the behavior of the PhaseSticky part."""
     tile = self.qud_tile
     frame1 = QudTile(tile.filename, '&k', '&k', tile.raw_detailcolor,
                      tile.qudname, tile.raw_transparent)
     frame2 = QudTile(tile.filename, '&K', '&K', tile.raw_detailcolor,
                      tile.qudname, tile.raw_transparent)
     frame3 = QudTile(tile.filename, '&c', '&c', tile.raw_detailcolor,
                      tile.qudname, tile.raw_transparent)
     frame4 = tile
     frame5 = QudTile(tile.filename, '&y', '&y', tile.raw_detailcolor,
                      tile.qudname, tile.raw_transparent)
     frame6 = tile
     frames = [frame1, frame2, frame3, frame4, frame5, frame6]
     durations = [40, 40, 40, 40, 40, 40]
     self._make_gif(frames, durations)
Example #3
0
 def apply_animated_material_mainframe_tape_drive(self) -> None:
     """Renders a GIF loosely based on the behavior of the AnimatedMaterialMainframeTapeDrive."""
     t = self.qud_tile
     pre = t.filename.split('-')[0][:-1]
     post = '-' + t.filename.split('-')[1]
     file1, file2, file3, file4 = f'{pre}1{post}', f'{pre}2{post}',\
                                  f'{pre}3{post}', f'{pre}4{post}'
     frames: List[QudTile] = [
         QudTile(file1, t.colorstring, t.raw_tilecolor, t.raw_detailcolor,
                 t.qudname, t.raw_transparent),
         QudTile(file2, t.colorstring, t.raw_tilecolor, t.raw_detailcolor,
                 t.qudname, t.raw_transparent),
         QudTile(file3, t.colorstring, t.raw_tilecolor, t.raw_detailcolor,
                 t.qudname, t.raw_transparent),
         QudTile(file4, t.colorstring, t.raw_tilecolor, t.raw_detailcolor,
                 t.qudname, t.raw_transparent)
     ]
     # tape drive rotates forward once every 500ms. But it also has a 1/64 chance (each) to enter
     # RushingForward or RushingBackward mode for a random duration between 15-120 frames. We
     # have to restrain ourselves a bit to keep the GIF at a manageable size. The GIF generated
     # below is 80 frames and 926KB.
     sequence = []
     durations = []
     for cycle in range(2):  # Normal forward
         for rotation in range(4):
             sequence.append(frames[rotation])
             durations.append(500)
     for cycle in range(12):  # RushingBackward
         for rotation in [2, 1, 0, 3]:
             sequence.append(frames[rotation])
             durations.append(20)
     for cycle in range(1):  # Normal forward
         for rotation in range(4):
             sequence.append(frames[rotation])
             durations.append(500)
     for cycle in range(4):  # RushingForward
         for rotation in range(4):
             sequence.append(frames[rotation])
             durations.append(20)
     for cycle in range(1):  # Normal forward
         for rotation in range(4):
             sequence.append(frames[rotation])
             durations.append(500)
     self._make_gif(sequence, durations)
Example #4
0
 def apply_gas_animation(self) -> None:
     """Renders a GIF that replicates the behavior of the Gas part."""
     t = self.qud_tile
     glyph1 = TileProvider(StandInTiles.gas_glyph1)
     glyph2 = TileProvider(StandInTiles.gas_glyph2)
     glyph3 = TileProvider(StandInTiles.gas_glyph3)
     glyph4 = TileProvider(StandInTiles.gas_glyph4)
     frame1 = QudTile(None, t.colorstring, t.raw_tilecolor,
                      t.raw_detailcolor, t.qudname, t.raw_transparent,
                      glyph1)
     frame2 = QudTile(None, t.colorstring, t.raw_tilecolor,
                      t.raw_detailcolor, t.qudname, t.raw_transparent,
                      glyph2)
     frame3 = QudTile(None, t.colorstring, t.raw_tilecolor,
                      t.raw_detailcolor, t.qudname, t.raw_transparent,
                      glyph3)
     frame4 = QudTile(None, t.colorstring, t.raw_tilecolor,
                      t.raw_detailcolor, t.qudname, t.raw_transparent,
                      glyph4)
     self._make_gif([frame1, frame2, frame3, frame4], [250, 250, 250, 250])
Example #5
0
 def apply_animated_material_techlight(self) -> None:
     obj, tile = self.qud_object, self.qud_tile
     base_color = obj.part_AnimatedMaterialTechlight_baseColor
     base_color = '&c' if base_color is None else base_color
     frame1 = QudTile(tile.filename, None, base_color, 'Y', tile.qudname,
                      tile.raw_transparent)
     frame2 = QudTile(tile.filename, None, base_color, 'C', tile.qudname,
                      tile.raw_transparent)
     frame3 = QudTile(tile.filename, None, base_color, 'B', tile.qudname,
                      tile.raw_transparent)
     frame4 = QudTile(tile.filename, None, base_color, 'b', tile.qudname,
                      tile.raw_transparent)
     frame5 = QudTile(tile.filename, None, base_color, 'c', tile.qudname,
                      tile.raw_transparent)
     frames = [
         (frame1, 650),
         (frame2, 20),
         (frame1, 900),
         (frame4, 20),
         (frame1, 750),
         (frame3, 20),
         (frame1, 800),
         (frame2, 20),
         (frame1, 40),
         (frame4, 20),
         (frame1, 1150),
         (frame2, 20),
         (frame1, 350),
         (frame5, 20),
         (frame1, 1000),
         (frame2, 20),
         (frame1, 20),
         (frame3, 20),
         (frame1, 200),
         (frame2, 20),
         (frame1, 2500),
         (frame5, 20),
     ]
     self._make_gif([f[0] for f in frames], [d[1] for d in frames])
Example #6
0
 def tile(self, tile_index: int = 0) -> QudTile | None:
     """Retrieves the painted QudTile for this object. If an index is supplied for an object that
     has multiple tiles, returns the alternate tile at the specified index."""
     if tile_index >= len(self._tiles):
         return None
     if self._tiles[tile_index] is not None:
         return self._tiles[tile_index]
     if self.file is None or self.file == '':
         if not self.standin:
             return None
         self._stylize_tile_variant(tile_index)
         self._tiles[tile_index] = QudTile(None, self.color, self.tilecolor,
                                           self.detail, self.obj.name,
                                           self.trans, self.standin,
                                           self.prefab_imitator)
     else:
         self._stylize_tile_variant(tile_index)
         self._tiles[tile_index] = QudTile(self.file, self.color,
                                           self.tilecolor, self.detail,
                                           self.obj.name, self.trans, None,
                                           self.prefab_imitator)
     return self._tiles[tile_index]
Example #7
0
    def apply_power_transmission(self) -> None:
        """Renders a GIF loosely based on the behavior of IPowerTransmission parts that have
        TileEffects enabled.

        To simplify things, we assume that the object is powered and unbroken, ignoring other
        potential variations."""
        obj, t = self.qud_object, self.qud_tile
        part, tile_path_start = None, None
        for partname in POWER_TRANSMISSION_PARTS:
            part = getattr(obj, f'part_{partname}')
            if part is not None and 'TileBaseFromTag' in part:
                if 'TileEffects' in part and part['TileEffects'].lower(
                ) == 'true':
                    tile_path_start = getattr(
                        obj, f"tag_{part['TileBaseFromTag']}_Value")
                    break
        if tile_path_start is None or part is None:
            logging.error(f'Unexpectedly failed to generate .gif for '
                          f'"{obj.name}" - missing Tile rendering tag?')
            return
        directory = t.filename.split(tile_path_start)[0]

        # calculate tile path postfixes
        first_postfix = ''
        numeral_postfixes = []
        if 'TileAppendWhenUnbrokenAndPowered' in part:
            first_postfix += part['TileAppendWhenUnbrokenAndPowered']
        else:
            if 'TileAppendWhenPowered' in part:
                first_postfix += part['TileAppendWhenPowered']
            if 'TileAppendWhenUnbroken' in part:
                first_postfix += part['TileAppendWhenUnbroken']
        if 'TileAnimatePoweredFrames' in part:
            for num in range(1, int(part['TileAnimatePoweredFrames']) + 1):
                numeral_postfixes.append(f'_{num}')
        final_postfix = '_nsew' if TilePainter.is_painted_fence(obj) else ''
        ext = obj.tag_PaintedFenceExtension_Value
        ext = ext if ext else '.bmp'

        # divide frames evenly across 1000 milliseconds
        frame_duration = (100 // len(numeral_postfixes)) * 10
        frame_duration = 20 if frame_duration < 20 else frame_duration  # minimum render duration
        frames = []
        durations = []
        for numeral_postfix in numeral_postfixes:
            f = directory + tile_path_start + first_postfix + numeral_postfix + final_postfix + ext
            frames.append(
                QudTile(f, t.colorstring, t.raw_tilecolor, t.raw_detailcolor,
                        t.qudname, t.raw_transparent))
            durations.append(frame_duration)
        self._make_gif(frames, durations)
Example #8
0
 def apply_vortex_animation(self) -> None:
     """Renders a GIF loosely based on the behavior of the SpaceTimeVortex part."""
     frame_count = 50
     random.seed(self.qud_object.name)
     glyphs = ['§', 'φ', '☼', '○']  # '•'
     colors = ['B', 'R', 'C', 'W', 'K']
     frames: List[QudTile] = []
     for _ in range(frame_count):
         color = random.choice(colors)
         glyph = random.choice(glyphs) if random.randint(0,
                                                         20) != 0 else '•'
         generator = TileProvider(
             lambda: (StandInTiles.make_font_glyph(glyph, color), False))
         frames.append(
             QudTile.from_image_provider(generator, self.qud_object.name))
     self._make_gif(frames, [50] * frame_count)
Example #9
0
 def apply_walltrap_animation(self) -> None:
     """Renders a GIF loosely based on the behavior of the Walltrap part."""
     tile = self.qud_tile
     frame1 = tile  # WarmColor
     readycolor = self.qud_object.part_Walltrap_ReadyColor
     turninterval = self.qud_object.part_Walltrap_TurnInterval
     turninterval = 3 if turninterval is None else int(turninterval)
     fore = extract_foreground_char(readycolor, 'R')
     back = extract_background_char(readycolor, 'g')
     color_string = '&' + fore + '^' + back
     tile_color = color_string
     trans_color = back
     detail_color = 'transparent'
     frame2 = QudTile(tile.filename, color_string, tile_color, detail_color,
                      tile.qudname, trans_color)  # ReadyColor
     final_frame_duration = turninterval * 1200 - 1600
     final_frame_duration = 20 if final_frame_duration < 20 else final_frame_duration
     self._make_gif([frame1, frame2, frame1],
                    [1600, 1200, final_frame_duration])
Example #10
0
 def apply_animated_material_reality_stabilization_field(self) -> None:
     tile = self.qud_tile
     t_pre = tile.filename.rsplit('_', 1)[0][:-1]
     t_post = '_' + tile.filename.rsplit('_', 1)[1]
     tile_paths = [
         f'{t_pre}1{t_post}', f'{t_pre}2{t_post}', f'{t_pre}3{t_post}',
         f'{t_pre}4{t_post}'
     ]
     # tile_colors = ['&y^k', '&K^k', '&Y^y', '&Y^K', '&y^k']
     # tile_details = ['k', 'k', 'y', 'K', 'k']
     tile_colors = ['&y', '&K', '&Y^y', '&Y^K', '&y']
     tile_details = [None, None, 'y', 'K', None]
     color_tick_idxs = [0, 2000, 7000, 12000, 16000]
     frames = []
     durations = []
     path_idx = 0
     color_idx = 0
     prev_tick = None
     for tick in range(0, 36000, 100):
         update_frame = False
         if tick % 2400 == 0:
             path_idx = (tick // 2400) % 4
             update_frame = True
         if (tick % 18000) in color_tick_idxs:
             color_idx = color_tick_idxs.index((tick % 18000))
             update_frame = True
         if update_frame is True:
             path = tile_paths[path_idx]
             color = tile_colors[color_idx]
             detail = tile_details[color_idx]
             frames.append(
                 QudTile(path, color, color, detail, tile.qudname,
                         tile.raw_transparent))
             if prev_tick is not None:
                 duration = tick - prev_tick
                 duration = 20 if duration < 20 else duration  # minimum render duration
                 durations[-1] = duration
             durations.append(0)
             prev_tick = tick
     durations[-1] = 36000 - prev_tick
     durations[-1] = 20 if durations[-1] < 20 else durations[
         -1]  # minimum render duration
     self._make_gif(frames, durations)
Example #11
0
    def apply_astral(self, random_sequence: bool = False) -> None:
        """Renders a GIF loosely based on the behavior of the Astral tag combined with a phasing
        effect (like the Astral mutation). The only object this currently affects is Astral Tabby.

        This is very similar to the holographic animiation but it uses different colors and is
        designed to animate at half the speed, more or less."""
        tile = self.qud_tile
        glyph1 = TileProvider(StandInTiles.hologram_material_glyph1)
        glyph2 = TileProvider(StandInTiles.hologram_material_glyph2)
        glyph3 = TileProvider(StandInTiles.hologram_material_glyph3)
        # base most of the time
        base = QudTile(tile.filename, '&K', '&K', 'y', tile.qudname,
                       tile.raw_transparent)
        # 2-4 somewhat common
        frame2 = QudTile(tile.filename, '&Y', '&Y', 'k', tile.qudname,
                         tile.raw_transparent)
        frame3 = QudTile(tile.filename, '&y', '&y', 'K', tile.qudname,
                         tile.raw_transparent)
        frame4 = QudTile(tile.filename, '&k', '&k', 'y', tile.qudname,
                         tile.raw_transparent)
        # 5-9 less common
        frame5 = QudTile(None, '&K', '&K', 'y', tile.qudname,
                         tile.raw_transparent, glyph1)
        frame6 = QudTile(None, '&K', '&K', 'y', tile.qudname,
                         tile.raw_transparent, glyph2)
        frame7 = QudTile(None, '&K', '&K', 'y', tile.qudname,
                         tile.raw_transparent, glyph3)
        frame8 = QudTile(tile.filename, '&K', '&K', 'k', tile.qudname,
                         tile.raw_transparent)
        frame9 = QudTile(tile.filename, '&K', '&K', 'y', tile.qudname,
                         tile.raw_transparent)
        frames = []
        durations = []
        if random_sequence:
            altframes_common = [frame2, frame3, frame4]
            altframes_rare = [frame8, frame9]
            baseframe = True
            fullflicker_chance = 12
            for i in range(40):  # frame limit
                if baseframe:
                    frames.append(base)
                    durations.append(
                        random.randint(600, 1400) + random.randint(-200, 600))
                    baseframe = not baseframe
                else:
                    if random.randint(1, fullflicker_chance) == 1:
                        fullflicker_chance = 200  # significantly reduce chance
                        frames.extend([frame6, frame5, frame7])
                        durations.extend([20, 20, 20])
                    else:
                        if random.randint(1, 18) > 1:
                            frames.append(random.choice(altframes_common))
                        else:
                            frames.append(random.choice(altframes_rare))
                        if random.randint(1, 8) > 1:
                            durations.append(40)
                        else:
                            durations.append(
                                50 if random.randint(1, 10) > 7 else 30)
                    if random.randint(1, 5) > 1:
                        baseframe = not baseframe
        else:
            frames.extend([
                base, frame2, base, frame3, base, frame9, frame6, frame5,
                frame7, base, frame4, base
            ])
            durations.extend(
                [1100, 40, 400, 40, 2200, 30, 20, 20, 20, 1560, 40, 960])
            frames.extend([
                frame2, frame3, base, frame4, base, frame4, base, frame2, base,
                frame3, base, frame8, base
            ])
            durations.extend([
                30, 40, 1800, 50, 2200, 40, 1700, 40, 1000, 50, 2600, 40, 1400
            ])
            frames.extend([
                frame4, base, frame3, base, frame2, base, frame3, base, frame4,
                base
            ])
            durations.extend([40, 2500, 40, 1300, 40, 1000, 30, 1800, 40, 700])
        self._make_gif(frames, durations)
Example #12
0
    def apply_hologram_material(self,
                                is_concealed: bool = False,
                                random_sequence: bool = False) -> None:
        """Renders a GIF loosely based on the behavior of the HologramMaterial part.

        This particular method uses a preset algorithm for the default case, which (1) ensures we'll
        know when the existing wiki image matches, because it'll always be the same (2) is
        predictable and we know it'll look halfway decent.

        Cryptogull invokes this with the random_sequence parameter, which instead creates a random
        holographic animation sequence."""
        tile = self.qud_tile
        glyph1 = TileProvider(StandInTiles.hologram_material_glyph1)
        glyph2 = TileProvider(StandInTiles.hologram_material_glyph2)
        glyph3 = TileProvider(StandInTiles.hologram_material_glyph3)
        # base most of the time
        if is_concealed:
            base = tile
        else:
            base = QudTile(tile.filename, '&B', '&B', 'b', tile.qudname,
                           tile.raw_transparent)
        # 2-4 somewhat common
        frame2 = QudTile(tile.filename, '&C', '&C', 'c', tile.qudname,
                         tile.raw_transparent)
        frame3 = QudTile(tile.filename, '&c', '&c', 'b', tile.qudname,
                         tile.raw_transparent)
        frame4 = QudTile(tile.filename, '&b', '&b', 'C', tile.qudname,
                         tile.raw_transparent)
        # 5-9 less common
        frame5 = QudTile(None, '&c', '&c', 'b', tile.qudname,
                         tile.raw_transparent, glyph1)
        frame6 = QudTile(None, '&C', '&C', 'b', tile.qudname,
                         tile.raw_transparent, glyph2)
        frame7 = QudTile(None, '&Y', '&Y', 'b', tile.qudname,
                         tile.raw_transparent, glyph3)
        frame8 = QudTile(tile.filename, '&Y', '&Y', 'b', tile.qudname,
                         tile.raw_transparent)
        frame9 = QudTile(tile.filename, '&Y', '&Y', 'c', tile.qudname,
                         tile.raw_transparent)
        frames = []
        durations = []
        if random_sequence:
            altframes_common = [frame2, frame3, frame4]
            altframes_rare = [frame8, frame9]
            baseframe = True
            fullflicker_chance = 12
            for i in range(40):  # frame limit
                if baseframe:
                    frames.append(base)
                    durations.append(
                        random.randint(300, 700) + random.randint(-100, 300))
                    baseframe = not baseframe
                else:
                    if random.randint(1, fullflicker_chance) == 1:
                        fullflicker_chance = 200  # significantly reduce chance
                        frames.extend([frame6, frame5, frame7])
                        durations.extend([20, 20, 20])
                    else:
                        if random.randint(1, 18) > 1:
                            frames.append(random.choice(altframes_common))
                        else:
                            frames.append(random.choice(altframes_rare))
                        if random.randint(1, 8) > 1:
                            durations.append(40)
                        else:
                            durations.append(
                                50 if random.randint(1, 10) > 7 else 30)
                    if random.randint(1, 5) > 1:
                        baseframe = not baseframe
        else:
            frames.extend([
                base, frame2, base, frame3, base, frame9, frame6, frame5,
                frame7, base, frame4, base
            ])
            durations.extend(
                [550, 40, 200, 40, 1100, 30, 20, 20, 20, 780, 40, 480])
            frames.extend([
                frame2, frame3, base, frame4, base, frame4, base, frame2, base,
                frame3, base, frame8, base
            ])
            durations.extend(
                [30, 40, 900, 50, 1100, 40, 850, 40, 500, 50, 1300, 40, 700])
            frames.extend([
                frame4, base, frame3, base, frame2, base, frame3, base, frame4,
                base
            ])
            durations.extend([40, 1250, 40, 650, 40, 500, 30, 900, 40, 350])
        self._make_gif(frames, durations)
Example #13
0
    def apply_animated_material_generic(self) -> None:
        """Renders a GIF loosely based on the behavior of the AnimatedMaterialGeneric and
        AnimatedMaterialGenericAlternate parts."""
        source_tile = self.qud_tile
        max_frames = 40  # set an upper limit to animation length to limit .gif size
        anim_parts = []
        if self.qud_object.part_AnimatedMaterialGeneric is not None:
            anim_parts.append(self.qud_object.part_AnimatedMaterialGeneric)
        if self.qud_object.part_AnimatedMaterialGenericAlternate is not None:
            anim_parts.append(
                self.qud_object.part_AnimatedMaterialGenericAlternate)

        # adjustments for particular objects
        # AnimatedMaterialGenericAlternate is often inherited from HighTechInstallation and used as
        # the 'unpowered' animation, so we don't always want to show it if we're animating an object
        # that also has a 'powered' animation
        remove_alternate = [
            'Force Projector', 'GritGateChromeBeacon', 'Rodanis Y',
            'Industrial Fan', 'Hydraulic Press', 'Fusion Pumping Station',
            'Solar Power Station', 'Solar Pumping Station'
        ]
        if self.qud_object.name in remove_alternate:
            anim_parts.pop()  # ignore AnimatedMaterialGenericAlternate

        # get greatest animation length and move that animation to front of array
        max_animation_length = 60
        len1, len2 = 60, 60
        if 'AnimationLength' in anim_parts[0]:
            len1 = max_animation_length = int(anim_parts[0]['AnimationLength'])
        if len(anim_parts) == 2 and 'AnimationLength' in anim_parts[1]:
            len2 = int(anim_parts[1]['AnimationLength'])
            if len2 > max_animation_length:
                max_animation_length = int(anim_parts[1]['AnimationLength'])
                anim_parts[0], anim_parts[1] = anim_parts[1], anim_parts[0]
                len1, len2 = len2, len1

        # if necessary, calculate lowest common multiple of out-of-sync animation lengths
        total_duration = max_animation_length
        if len(anim_parts) > 1:
            total_duration = lowest_common_multiple(len1, len2)

        # extract frames from each animation
        tileframes: List[dict] = [{}, {}]
        colorframes: List[dict] = [{}, {}]
        detailframes: List[dict] = [{}, {}]
        for part, tframes, cframes, dframes in \
                zip(anim_parts, tileframes, colorframes, detailframes):
            # set first frame to 'default' to absorb object's base render properties
            # using special logic below
            tframes[0] = 'default'
            cframes[0] = 'default'
            dframes[0] = 'default'
            # add additional frames based on AnimatedMaterial* part
            if 'TileAnimationFrames' in part:
                parse_comma_equals_str_into_dict(part['TileAnimationFrames'],
                                                 tframes)
            if 'ColorStringAnimationFrames' in part:
                parse_comma_equals_str_into_dict(
                    part['ColorStringAnimationFrames'], cframes)
            if 'DetailColorAnimationFrames' in part:
                parse_comma_equals_str_into_dict(
                    part['DetailColorAnimationFrames'], dframes)

        # merge animation frames until we reach total_duration or we hit the max_frames limit
        sequenced_animation = []
        tick, max_tick = -1, max_frames * 300
        frame_index, last_frame_tick, idx1, idx2 = 0, 0, 0, 0
        while tick < max_tick and tick < (total_duration -
                                          1) and frame_index <= max_frames:
            tick += 1
            tile, color, detail = None, None, None
            idx1 = tick % len1
            idx2 = tick % len2
            if idx1 in tileframes[0]:
                tile = tileframes[0][idx1]
            if idx1 in colorframes[0]:
                color = colorframes[0][idx1]
            if idx1 in detailframes[0]:
                detail = detailframes[0][idx1]
            if idx2 in tileframes[1]:
                tile = tileframes[1][idx2]
            if idx2 in colorframes[1]:
                color = colorframes[1][idx2]
            if idx2 in detailframes[1]:
                detail = detailframes[1][idx2]
            if tile is None and color is None and detail is None:
                continue
            sequenced_animation.append((-1, tile, color, detail))
            if frame_index > 0:
                # insert duration into previous frame
                duration = tick - last_frame_tick
                prev_frame = sequenced_animation[frame_index - 1]
                sequenced_animation[frame_index - 1] = \
                    (duration, prev_frame[1], prev_frame[2], prev_frame[3])
            frame_index += 1
            last_frame_tick = tick
        # insert duration into final frame (or remove it)
        if tick == (total_duration - 1) or tick == max_tick:
            duration = tick - last_frame_tick
            last_frame = sequenced_animation[-1]
            sequenced_animation[-1] = (duration, last_frame[1], last_frame[2],
                                       last_frame[3])
        elif frame_index > max_frames:
            sequenced_animation.pop()

        finalized_tiles = []
        finalized_durations = []
        for frame in sequenced_animation:
            duration, tile, color, detail = frame
            # calculate GIF duration
            duration = (
                duration *
                (100 / 60)) // 1  # convert game's 60fps to gif format's 100fps
            duration = duration * 10  # convert to 1000ms rate used by PIL Image
            if duration < 20:
                duration = 20  # minimum render duration
            finalized_durations.append(duration)
            # determine tile and colors for this animation frame
            tile = source_tile.filename if (tile is None
                                            or tile == 'default') else tile
            detail = source_tile.raw_detailcolor if (detail is None or detail == 'default') \
                else detail
            # Some complexity follows: if ColorStringAnimationFrames is not specified or is
            # 'default', the game uses the Render part ColorString and does NOT touch TileColor.
            # However, if ColorStringAnimationFrames IS specified, the game will (effectively) set
            # the specified color to BOTH ColorString and TileColor:
            animation_color_is_unspecified = (color is None
                                              or color == 'default')
            color = source_tile.colorstring if animation_color_is_unspecified else color
            tile_color = source_tile.raw_tilecolor if animation_color_is_unspecified else color
            # create tile
            qud_tile = QudTile(tile, color, tile_color, detail,
                               source_tile.qudname,
                               source_tile.raw_transparent)
            finalized_tiles.append(qud_tile)
        # generate GIF
        self._make_gif(finalized_tiles, finalized_durations)
Example #14
0
    def tile(self) -> QudTile:
        """Return a QudTile colored to match the in-game representation.

        Created on-demand to speed load times; cached in self._tile after first call."""

        if self._tile is not None:
            return self._tile
        tile = None  # not all objects have tiles
        if self.part_Render_Tile and not self.tag_BaseObject:
            holo_parts = [
                'part_HologramMaterial', 'part_HologramWallMaterial',
                'part_HologramMaterialPrimary'
            ]
            if (any(self.is_specified(part) for part in holo_parts)
                    or self.name == "Wraith-Knight Templar"):
                # special handling for holograms
                color = '&B'
                tilecolor = '&B^b'
                detail = 'b'
                trans = 'transparent'
            elif self.is_specified('part_AnimatedMaterialStasisfield'):
                color = '&C^M'
                tilecolor = '&C^M'
                detail = 'M'
                trans = 'M'
            else:
                color = self.part_Render_ColorString
                tilecolor = self.part_Render_TileColor
                # default detail color when unspecified is black (0, 0, 0)
                # which matches the overlay UI inventory rendering
                # ------------------------------------
                detail = self.part_Render_DetailColor
                # below uses logic similar to non-overlay UI where default ('k') is
                # essentially invisible/transparent against the default background color ('k')
                # ------------------------------------
                # _ = self.part_Render_DetailColor
                # detail = _ if _ else 'transparent'
                trans = 'transparent'

            if self.is_specified('tag_PaintedWall'
                                 ) and self.tag_PaintedWall_Value != "*delete":
                # special handling for painted walls
                if detail is None and '^' in color:
                    trans = color.split('^', 1)[1]
                _ = self.tag_PaintedWallAtlas_Value
                tileloc = _ if _ else 'Tiles/'
                _ = self.tag_PaintedWallExtension_Value
                tileext = _ if _ and self.name != 'Dirt' else '.bmp'
                tile = QudTile(tileloc + self.tag_PaintedWall_Value +
                               '-00000000' + tileext,
                               color,
                               tilecolor,
                               detail,
                               self.name,
                               raw_transparent=trans)
            elif self.is_specified(
                    'tag_PaintedFence'
            ) and self.tag_PaintedFence_Value != "*delete":
                # special handling for painted fences
                if detail is None and '^' in color:
                    trans = color.split('^', 1)[1]
                _ = self.tag_PaintedFenceAtlas_Value
                tileloc = _ if _ else 'Tiles/'
                _ = self.tag_PaintedFenceExtension_Value
                tileext = _ if _ else '.bmp'
                tile = QudTile(tileloc + self.tag_PaintedFence_Value + "_" +
                               tileext,
                               color,
                               tilecolor,
                               detail,
                               self.name,
                               raw_transparent=trans)
            else:
                # normal rendering
                tile = QudTile(self.part_Render_Tile, color, tilecolor, detail,
                               self.name)
        self._tile = tile
        return tile