def __init__( self, draw_area: Gtk.Widget, markers: List[MapMarkerPlacement], cb_dungeon_name: Callable[[int], str], scale: int ): self.draw_area = draw_area self.markers: List[MapMarkerPlacement] = markers self.markers_at_pos: Dict[Tuple[int, int], List[MapMarkerPlacement]] = {} self.map_bg = None self.level_id = None self.draw_tile_grid = True self._cb_dungeon_name = cb_dungeon_name # Interaction self.mouse_x = 99999 self.mouse_y = 99999 self._selected: Optional[MapMarkerPlacement] = None self._editing = None self._editing_pos = None self._hide = None self.tile_grid_plugin = GridDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, color=(0.2, 0.2, 0.2, 0.1) ) self.scale = scale self.drawing_is_active = False
def __init__( self, draw_area: Widget, bma: Union[BmaProtocol, None], bpa_durations: int, pal_ani_durations: int, # chunks_surfaces[layer_number][chunk_idx][palette_animation_frame][frame] chunks_surfaces: Iterable[Iterable[Iterable[Iterable[cairo.Surface]]]] ): """ Initialize a drawer... :param draw_area: Widget to draw on. :param bma: Either a BMA with tile indexes or None, has to be set manually then for drawing :param bpa_durations: How many frames to hold a BPA animation tile :param chunks_surfaces: Bg controller format chunk surfaces """ self.draw_area = draw_area self.reset(bma, bpa_durations, pal_ani_durations, chunks_surfaces) self.draw_chunk_grid = False self.draw_tile_grid = False self.use_pink_bg = False # Interaction self.interaction_mode = DrawerInteraction.NONE self.interaction_chunks_selected_id = 0 self.interaction_col_solid = False self.interaction_dat_value = 0 self.mouse_x = 99999 self.mouse_y = 99999 self.edited_layer = -1 self.edited_collision = -1 self.show_only_edited_layer = False self.dim_layers = False self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = False self.selection_plugin = SelectionDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(BPC_TILE_DIM, BPC_TILE_DIM) self.chunk_grid_plugin = GridDrawerPlugin( BPC_TILE_DIM * self.tiling_width, BPC_TILE_DIM * self.tiling_height, color=(0.15, 0.15, 0.15, 0.25)) self.scale = 1 self.drawing_is_active = False
def __init__( self, draw_area: Widget, dbg: Union[Dbg, None], pal_ani_durations: int, # chunks_surfaces[chunk_idx][palette_animation_frame][frame] chunks_surfaces: Iterable[Iterable[List[cairo.Surface]]]): """ Initialize a drawer... :param draw_area: Widget to draw on. :param dbg: Either a DBG with chunk indexes or None, has to be set manually then for drawing :param chunks_surfaces: Bg controller format chunk surfaces """ self.draw_area = draw_area self.reset(dbg, pal_ani_durations, chunks_surfaces) self.draw_chunk_grid = True self.draw_tile_grid = True self.use_pink_bg = False # Interaction self.interaction_chunks_selected_id = 0 self.mouse_x = 99999 self.mouse_y = 99999 self.tiling_width = DBG_TILING_DIM self.tiling_height = DBG_TILING_DIM self.width_in_chunks = DBG_WIDTH_AND_HEIGHT self.height_in_chunks = DBG_WIDTH_AND_HEIGHT self.width_in_tiles = DBG_WIDTH_AND_HEIGHT * 3 self.height_in_tiles = DBG_WIDTH_AND_HEIGHT * 3 self.selection_plugin = SelectionDrawerPlugin( DPCI_TILE_DIM, DPCI_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(DPCI_TILE_DIM, DPCI_TILE_DIM) self.chunk_grid_plugin = GridDrawerPlugin( DPCI_TILE_DIM * self.tiling_width, DPCI_TILE_DIM * self.tiling_height, color=(0.15, 0.15, 0.15, 0.25)) self.scale = 1 self.drawing_is_active = False
def set_tileset_renderer(self, renderer: AbstractTilesetRenderer): self.tileset_renderer = renderer self.selection_plugin = SelectionDrawerPlugin( self.tileset_renderer.chunk_dim(), self.tileset_renderer.chunk_dim(), self.selection_draw_callback ) self.tile_grid_plugin = GridDrawerPlugin( self.tileset_renderer.chunk_dim(), self.tileset_renderer.chunk_dim(), offset_x=self.tileset_renderer.chunk_dim() * OFFSET_BASE, offset_y=self.tileset_renderer.chunk_dim() * OFFSET_BASE )
def __init__(self, draw_area: Gtk.Widget, ssa: Ssa, cb_trigger_label: Callable[[int], str], sprite_provider: SpriteProvider): self.draw_area = draw_area self.ssa = ssa self.map_bg = None self.position_marks: List[SourceMapPositionMark] = [] self.draw_tile_grid = False # Interaction self.interaction_mode = InteractionMode.SELECT self.mouse_x = 99999 self.mouse_y = 99999 self.sprite_provider = sprite_provider self._cb_trigger_label = cb_trigger_label self._sectors_visible = [ True for _ in range(0, len(self.ssa.layer_list)) ] self._sectors_solo = [ False for _ in range(0, len(self.ssa.layer_list)) ] self._sector_highlighted = None self._selected: Optional[Union[SsaActor, SsaObject, SsaPerformer, SsaEvent, SourceMapPositionMark]] = None # If not None, drag is active and value is coordinate self._selected__drag: Optional[Tuple[int, int]] = None self._edit_pos_marks = False self.selection_plugin = SelectionDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(BPC_TILE_DIM, BPC_TILE_DIM, offset_x=-BPC_TILE_DIM / 2, offset_y=-BPC_TILE_DIM / 2) self.scale = 1 self.drawing_is_active = False
def __init__( self, draw_area: Gtk.Widget, fixed_floor: FixedFloor, sprite_provider: SpriteProvider, entity_rule_container: EntityRuleContainer, string_provider: StringProvider ): self.draw_area = draw_area self.fixed_floor = fixed_floor self.tileset_renderer: Optional[AbstractTilesetRenderer] = None self.draw_tile_grid = False self.info_layer_active = None self.entity_rule_container = entity_rule_container # Interaction self.interaction_mode = InteractionMode.SELECT self.mouse_x = 99999 self.mouse_y = 99999 self.sprite_provider = sprite_provider self.string_provider = string_provider # Depending on the mode this is either a coordinate tuple or a FixedFloorActionRule to place. self._selected: Optional[Union[Tuple[int, int], FixedFloorActionRule]] = None self._selected__drag = None self.selection_plugin = SelectionDrawerPlugin( DPCI_TILE_DIM * DPC_TILING_DIM, DPCI_TILE_DIM * DPC_TILING_DIM, self.selection_draw_callback ) self.tile_grid_plugin = GridDrawerPlugin( DPCI_TILE_DIM * DPC_TILING_DIM, DPCI_TILE_DIM * DPC_TILING_DIM, offset_x=OFFSET, offset_y=OFFSET ) self.scale = 1 self.drawing_is_active = False
class Drawer: def __init__(self, draw_area: Gtk.Widget, ssa: Ssa, cb_trigger_label: Callable[[int], str], sprite_provider: SpriteProvider): self.draw_area = draw_area self.ssa = ssa self.map_bg = None self.position_marks: List[SourceMapPositionMark] = [] self.draw_tile_grid = False # Interaction self.interaction_mode = InteractionMode.SELECT self.mouse_x = 99999 self.mouse_y = 99999 self.sprite_provider = sprite_provider self._cb_trigger_label = cb_trigger_label self._sectors_visible = [ True for _ in range(0, len(self.ssa.layer_list)) ] self._sectors_solo = [ False for _ in range(0, len(self.ssa.layer_list)) ] self._sector_highlighted = None self._selected = None # If not None, drag is active and value is coordinate self._selected__drag: Optional[Tuple[int, int]] = None self._edit_pos_marks = False self.selection_plugin = SelectionDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(BPC_TILE_DIM, BPC_TILE_DIM, offset_x=-BPC_TILE_DIM / 2, offset_y=-BPC_TILE_DIM / 2) self.scale = 1 self.drawing_is_active = False def start(self): """Start drawing on the DrawingArea""" self.drawing_is_active = True if isinstance(self.draw_area, Gtk.DrawingArea): self.draw_area.connect('draw', self.draw) self.draw_area.queue_draw() def draw(self, wdg, ctx: cairo.Context): ctx.set_antialias(cairo.Antialias.NONE) ctx.scale(self.scale, self.scale) # Background if self.map_bg is not None: ctx.set_source_surface(self.map_bg, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() size_w, size_h = self.draw_area.get_size_request() size_w /= self.scale size_h /= self.scale # Black out bg a bit if not self._edit_pos_marks: ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, size_h) ctx.fill() # Tile Grid if self.draw_tile_grid: self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # RENDER ENTITIES for layer_i, layer in enumerate(self.ssa.layer_list): if not self._is_layer_visible(layer_i): continue for actor in layer.actors: if not self._is_dragged(actor): bb = self.get_bb_actor(actor) if actor != self._selected: self._handle_layer_highlight(ctx, layer_i, *bb) self._draw_actor(ctx, actor, *bb) self._draw_hitbox_actor(ctx, actor) for obj in layer.objects: if not self._is_dragged(obj): bb = self.get_bb_object(obj) if obj != self._selected: self._handle_layer_highlight(ctx, layer_i, *bb) self._draw_object(ctx, obj, *bb) self._draw_hitbox_object(ctx, obj) for trigger in layer.events: if not self._is_dragged(trigger): bb = self.get_bb_trigger(trigger) if trigger != self._selected: self._handle_layer_highlight(ctx, layer_i, *bb) self._draw_trigger(ctx, trigger, *bb) for performer in layer.performers: if not self._is_dragged(performer): bb = self.get_bb_performer(performer) if performer != self._selected: self._handle_layer_highlight(ctx, layer_i, *bb) self._draw_hitbox_performer(ctx, performer) self._draw_performer(ctx, performer, *bb) # Black out bg a bit if self._edit_pos_marks: ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, size_h) ctx.fill() # RENDER POSITION MARKS for pos_mark in self.position_marks: bb = self.get_bb_pos_mark(pos_mark) self._draw_pos_mark(ctx, pos_mark, *bb) # Cursor / Active selected / Place mode self._handle_selection(ctx) x, y, w, h = self._handle_drag_and_place_modes() self.selection_plugin.set_size(w, h) self.selection_plugin.draw(ctx, size_w, size_h, x, y, ignore_obb=True) # Position ctx.scale(1 / self.scale, 1 / self.scale) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_source_rgb(*COLOR_WHITE) ctx.set_font_size(18) try: sw: Gtk.ScrolledWindow = self.draw_area.get_parent().get_parent( ).get_parent() s = sw.get_allocated_size() ctx.move_to(sw.get_hadjustment().get_value() + 30, s[0].height + sw.get_vadjustment().get_value() - 30) if self._selected__drag is not None: sx, sy = self.get_current_drag_entity_pos() else: sx, sy = self._snap_pos(self.mouse_x, self.mouse_y) sx /= BPC_TILE_DIM sy /= BPC_TILE_DIM ctx.text_path(f"X: {sx}, Y: {sy}") ctx.set_source_rgb(*COLOR_BLACK) ctx.set_line_width(0.3) ctx.set_source_rgb(*COLOR_WHITE) ctx.fill_preserve() ctx.stroke() except BaseException: pass return True def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int): if self.interaction_mode == InteractionMode.SELECT: if self._selected is not None and self._selected__drag is not None: # Draw dragged: x, y = self.get_current_drag_entity_pos() if isinstance(self._selected, SsaActor): x, y, w, h = self.get_bb_actor(self._selected, x=x, y=y) self._draw_actor(ctx, self._selected, x, y, w, h) elif isinstance(self._selected, SsaObject): x, y, w, h = self.get_bb_object(self._selected, x=x, y=y) self._draw_object(ctx, self._selected, x, y, w, h) elif isinstance(self._selected, SsaPerformer): x, y, w, h = self.get_bb_performer(self._selected, x=x, y=y) self._draw_performer(ctx, self._selected, x, y, w, h) elif isinstance(self._selected, SsaEvent): x, y, w, h = self.get_bb_trigger(self._selected, x=x, y=y) self._draw_trigger(ctx, self._selected, x, y, w, h) elif isinstance(self._selected, SourceMapPositionMark): x, y, w, h = self.get_bb_pos_mark(self._selected, x=x, y=y) self._draw_pos_mark(ctx, self._selected, x, y, w, h) return # Tool modes elif self.interaction_mode == InteractionMode.PLACE_ACTOR: self._surface_place_actor(ctx, x, y, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3) elif self.interaction_mode == InteractionMode.PLACE_OBJECT: self._surface_place_object(ctx, x, y, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3) elif self.interaction_mode == InteractionMode.PLACE_PERFORMER: self._surface_place_performer(ctx, x, y, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3) elif self.interaction_mode == InteractionMode.PLACE_TRIGGER: self._surface_place_trigger(ctx, x, y, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3) def set_mouse_position(self, x, y): self.mouse_x = x self.mouse_y = y def get_under_mouse( self ) -> Tuple[Optional[int], Optional[Union[SsaActor, SsaObject, SsaPerformer, SsaEvent]]]: """ Returns the first entity under the mouse position, if any, and it's layer number. Not visible layers are not searched. Elements are searched in reversed drawing order (so what's drawn on top is also taken). Does not return positon marks under the mouse. """ for layer_i, layer in enumerate(reversed(self.ssa.layer_list)): layer_i = len(self.ssa.layer_list) - layer_i - 1 if not self._is_layer_visible(layer_i): continue for performer in reversed(layer.performers): bb = self.get_bb_performer(performer) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return layer_i, performer for trigger in reversed(layer.events): bb = self.get_bb_trigger(trigger) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return layer_i, trigger for obj in reversed(layer.objects): bb = self.get_bb_object(obj) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return layer_i, obj for actor in reversed(layer.actors): bb = self.get_bb_actor(actor) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return layer_i, actor return None, None def get_pos_mark_under_mouse(self) -> Optional[SourceMapPositionMark]: """ Returns the first position mark under the mouse position, if any. Elements are searched in reversed drawing order (so what's drawn on top is also taken). """ for pos_mark in reversed(self.position_marks): bb = self.get_bb_pos_mark(pos_mark) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return pos_mark return None def set_draw_tile_grid(self, v): self.draw_tile_grid = v def set_scale(self, v): self.scale = v def get_bb_actor(self, actor: SsaActor, x=None, y=None) -> Tuple[int, int, int, int]: if x is None: x = actor.pos.x_absolute if y is None: y = actor.pos.y_absolute if actor.actor.entid <= 0: _, cx, cy, w, h = self.sprite_provider.get_actor_placeholder( actor.actor.id, actor.pos.direction.id, lambda: GLib.idle_add(self._redraw)) else: _, cx, cy, w, h = self.sprite_provider.get_monster( actor.actor.entid, actor.pos.direction.id, lambda: GLib.idle_add(self._redraw)) return x - cx, y - cy, w, h def _draw_hitbox_actor(self, ctx: cairo.Context, actor: SsaActor): coords_hitbox = self._get_pmd_bounding_box(actor.pos.x_absolute, actor.pos.y_absolute, ACTOR_DEFAULT_HITBOX_W, ACTOR_DEFAULT_HITBOX_H) self._draw_hitbox(ctx, COLOR_ACTORS, *coords_hitbox) def _draw_actor(self, ctx: cairo.Context, actor: SsaActor, *sprite_coords): self._draw_actor_sprite(ctx, actor, sprite_coords[0], sprite_coords[1]) self._draw_name(ctx, COLOR_ACTORS, actor.actor.name, sprite_coords[0], sprite_coords[1]) def get_bb_object(self, object: SsaObject, x=None, y=None) -> Tuple[int, int, int, int]: if x is None: x = object.pos.x_absolute if y is None: y = object.pos.y_absolute if object.object.name != 'NULL': # Load sprite to get dims. _, cx, cy, w, h = self.sprite_provider.get_for_object( object.object.name, lambda: GLib.idle_add(self._redraw)) return x - cx, y - cy, w, h return self._get_pmd_bounding_box(x, y, object.hitbox_w * BPC_TILE_DIM, object.hitbox_h * BPC_TILE_DIM) def _draw_hitbox_object(self, ctx: cairo.Context, object: SsaObject): coords_hitbox = self._get_pmd_bounding_box( object.pos.x_absolute, object.pos.y_absolute, object.hitbox_w * BPC_TILE_DIM, object.hitbox_h * BPC_TILE_DIM) self._draw_hitbox(ctx, COLOR_OBJECTS, *coords_hitbox) def _draw_object(self, ctx: cairo.Context, object: SsaObject, *sprite_coords): # Draw sprite representation if object.object.name != 'NULL': self._draw_object_sprite(ctx, object, sprite_coords[0], sprite_coords[1]) self._draw_name(ctx, COLOR_OBJECTS, object.object.name, sprite_coords[0], sprite_coords[1]) return self._draw_generic_placeholder(ctx, COLOR_OBJECTS, object.object.unique_name, *sprite_coords, object.pos.direction) def get_bb_performer(self, performer: SsaPerformer, x=None, y=None) -> Tuple[int, int, int, int]: if x is None: x = performer.pos.x_absolute if y is None: y = performer.pos.y_absolute return self._get_pmd_bounding_box(x, y, performer.hitbox_w * BPC_TILE_DIM, performer.hitbox_h * BPC_TILE_DIM) def _draw_hitbox_performer(self, ctx: cairo.Context, performer: SsaPerformer): self._draw_hitbox(ctx, COLOR_PERFORMER, *self.get_bb_performer(performer)) def _draw_performer(self, ctx: cairo.Context, performer: SsaPerformer, x, y, w, h): # Label ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_source_rgb(*COLOR_PERFORMER) ctx.set_font_size(12) ctx.move_to(x - 4, y - 8) ctx.show_text(f'{performer.type}') # Direction arrow self._triangle(ctx, x, y, BPC_TILE_DIM, COLOR_PERFORMER, performer.pos.direction.id) def get_bb_trigger(self, trigger: SsaEvent, x=None, y=None) -> Tuple[int, int, int, int]: if x is None: x = trigger.pos.x_absolute if y is None: y = trigger.pos.y_absolute return (x, y, trigger.trigger_width * BPC_TILE_DIM, trigger.trigger_height * BPC_TILE_DIM) def _draw_trigger(self, ctx: cairo.Context, trigger: SsaEvent, *coords_hitbox): # Draw hitbox self._draw_hitbox(ctx, COLOR_PERFORMER, *coords_hitbox) # Label ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_source_rgb(1, 1, 1) ctx.set_font_size(12) ctx.move_to(coords_hitbox[0] + 4, coords_hitbox[1] + 14) ctx.show_text(f'{self._cb_trigger_label(trigger.trigger_id)}') return coords_hitbox def get_bb_pos_mark(self, pos_mark: SourceMapPositionMark, x=None, y=None) -> Tuple[int, int, int, int]: if x is None: x = pos_mark.x_with_offset * BPC_TILE_DIM if y is None: y = pos_mark.y_with_offset * BPC_TILE_DIM return x - BPC_TILE_DIM, y - BPC_TILE_DIM, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3 def _draw_pos_mark(self, ctx: cairo.Context, pos_mark: SourceMapPositionMark, *bb_cords): # Outline ctx.set_source_rgba(*COLOR_POS_MARKS, 0.8) ctx.rectangle(*bb_cords) ctx.set_line_width(4.0) ctx.set_dash([1.0]) ctx.stroke() ctx.set_dash([]) # Inner ctx.rectangle(bb_cords[0] + BPC_TILE_DIM, bb_cords[1] + BPC_TILE_DIM, BPC_TILE_DIM, BPC_TILE_DIM) ctx.fill() # Label self._draw_name(ctx, COLOR_POS_MARKS, pos_mark.name, bb_cords[0], bb_cords[1], scale=2) def _is_layer_visible(self, layer_i: int) -> bool: return self._sectors_solo[layer_i] or (not any(self._sectors_solo) and self._sectors_visible[layer_i]) def _is_dragged(self, entity: Union[SsaActor, SsaObject, SsaPerformer, SsaEvent, SourceMapPositionMark]): return entity == self._selected and self._selected__drag is not None def _handle_layer_highlight(self, ctx: cairo.Context, layer: int, x: int, y: int, w: int, h: int): if layer == self._sector_highlighted: padding = 2 x -= padding y -= padding w += padding * 2 h += padding * 2 ctx.set_source_rgba(*COLOR_LAYER_HIGHLIGHT) ctx.set_line_width(1.5) ctx.rectangle(x, y, w, h) ctx.set_dash([1.0]) ctx.stroke() ctx.set_dash([]) def _handle_selection(self, ctx: cairo.Context): if self._selected is None: return if isinstance(self._selected, SsaActor): x, y, w, h = self.get_bb_actor(self._selected) elif isinstance(self._selected, SsaObject): x, y, w, h = self.get_bb_object(self._selected) elif isinstance(self._selected, SsaPerformer): x, y, w, h = self.get_bb_performer(self._selected) elif isinstance(self._selected, SsaEvent): x, y, w, h = self.get_bb_trigger(self._selected) elif isinstance(self._selected, SourceMapPositionMark): x, y, w, h = self.get_bb_pos_mark(self._selected) else: return padding = 2 x -= padding y -= padding w += padding * 2 h += padding * 2 ctx.set_source_rgba(*COLOR_LAYER_HIGHLIGHT) ctx.set_line_width(3) ctx.rectangle(x, y, w, h) ctx.set_dash([1.0]) ctx.stroke() ctx.set_dash([]) def _handle_drag_and_place_modes(self): if self.interaction_mode == InteractionMode.SELECT: # IF DRAGGED if self._selected is not None and self._selected__drag is not None: # Draw dragged: x, y = self.get_current_drag_entity_pos() if isinstance(self._selected, SsaActor): x, y, w, h = self.get_bb_actor(self._selected, x=x, y=y) elif isinstance(self._selected, SsaObject): x, y, w, h = self.get_bb_object(self._selected, x=x, y=y) elif isinstance(self._selected, SsaPerformer): x, y, w, h = self.get_bb_performer(self._selected, x=x, y=y) elif isinstance(self._selected, SsaEvent): x, y, w, h = self.get_bb_trigger(self._selected, x=x, y=y) elif isinstance(self._selected, SourceMapPositionMark): x, y, w, h = self.get_bb_pos_mark(self._selected, x=x, y=y) return x, y, w, h # DEFAULT return self.mouse_x, self.mouse_y, BPC_TILE_DIM, BPC_TILE_DIM # Tool modes x = self.mouse_x - self.mouse_x % (BPC_TILE_DIM / 2) y = self.mouse_y - self.mouse_y % (BPC_TILE_DIM / 2) return x - BPC_TILE_DIM * 1.5, y - BPC_TILE_DIM * 1.5, BPC_TILE_DIM * 3, BPC_TILE_DIM * 3 def _get_pmd_bounding_box(self, x_center: int, y_center: int, w: int, h: int, y_offset=0.0) -> Tuple[int, int, int, int]: left = x_center - int(w / 2) top = y_center - int(h / 2) - int(y_offset) return left, top, w, h def _draw_hitbox(self, ctx: cairo.Context, color: Color, x: int, y: int, w: int, h: int): ctx.set_source_rgba(*color, ALPHA_T) ctx.rectangle(x, y, w, h) ctx.fill() def _draw_generic_placeholder(self, ctx: cairo.Context, color: Color, label: str, x: int, y: int, w: int, h: int, direction: Pmd2ScriptDirection): # Rectangle ctx.set_source_rgb(*color) ctx.set_line_width(1) ctx.rectangle(x, y, w, h) ctx.stroke() # Label self._draw_name(ctx, color, label, x, y) # Direction arrow a_sz = int(BPC_TILE_DIM / 2) self._triangle(ctx, x + int(w / 2) - int(a_sz / 2), y + h - a_sz - int(a_sz / 2), a_sz, (1, 1, 1), direction.id) def _draw_name(self, ctx: cairo.Context, color: Color, label: str, x: int, y: int, scale=1): ctx.set_source_rgb(*color) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(6 * scale) ctx.move_to(x, y - 4 * scale) ctx.show_text(label) def _triangle(self, ctx: cairo.Context, x: int, y: int, a_sz: int, color: Color, direction_id: int): if direction_id == 1 or direction_id == 0: # Down self._polygon(ctx, [(x, y), (x + a_sz, y), (x + int(a_sz / 2), y + a_sz)], color=color) elif direction_id == 2: # DownRight self._polygon(ctx, [(x + a_sz, y), (x + a_sz, y + a_sz), (x, y + a_sz)], color=color) elif direction_id == 3: # Right self._polygon(ctx, [(x, y), (x + a_sz, y + int(a_sz / 2)), (x, y + a_sz)], color=color) elif direction_id == 4: # UpRight self._polygon(ctx, [(x, y), (x + a_sz, y), (x + a_sz, y + a_sz)], color=color) elif direction_id == 5: # Up self._polygon(ctx, [(x, y + a_sz), (x + a_sz, y + a_sz), (x + int(a_sz / 2), y)], color=color) elif direction_id == 6: # UpLeft self._polygon(ctx, [(x, y + a_sz), (x, y), (x + a_sz, y)], color=color) elif direction_id == 7: # Left self._polygon(ctx, [(x + a_sz, y), (x, y + int(a_sz / 2)), (x + a_sz, y + a_sz)], color=color) elif direction_id == 8: # DownLeft self._polygon(ctx, [(x, y), (x, y + a_sz), (x + a_sz, y + a_sz)], color=color) def _polygon(self, ctx: cairo.Context, points, color, outline=None): ctx.new_path() for point in points: ctx.line_to(*point) ctx.close_path() ctx.set_source_rgba(*color) if outline is not None: ctx.fill_preserve() ctx.set_source_rgba(*outline) ctx.set_line_width(1) ctx.stroke() else: ctx.fill() def _draw_actor_sprite(self, ctx: cairo.Context, actor: SsaActor, x, y): """Draws the sprite for an actor""" if actor.actor.entid == 0: sprite = self.sprite_provider.get_actor_placeholder( actor.actor.id, actor.pos.direction.id, self._redraw)[0] else: sprite = self.sprite_provider.get_monster( actor.actor.entid, actor.pos.direction.id, lambda: GLib.idle_add(self._redraw))[0] ctx.translate(x, y) ctx.set_source_surface(sprite) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() ctx.translate(-x, -y) def _draw_object_sprite(self, ctx: cairo.Context, obj: SsaObject, x, y): """Draws the sprite for an object""" sprite = self.sprite_provider.get_for_object( obj.object.name, lambda: GLib.idle_add(self._redraw))[0] ctx.translate(x, y) ctx.set_source_surface(sprite) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() ctx.translate(-x, -y) def _surface_place_actor(self, ctx: cairo.Context, x, y, w, h): ctx.set_line_width(1) sprite_surface = self.sprite_provider.get_monster_outline(1, 1)[0] ctx.translate(x, y) ctx.set_source_surface(sprite_surface) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() self._draw_plus(ctx) ctx.translate(-x, -y) def get_pos_place_actor(self) -> Tuple[int, int]: """Get the X and Y position on the grid to place the actor on, in PLACE_ACTOR mode.""" return self._snap_pos(self.mouse_x, self.mouse_y + BPC_TILE_DIM) def _surface_place_object(self, ctx: cairo.Context, x, y, w, h): ctx.set_line_width(1) cx, cy = x + w / 2, y + h / 2 rect_dim = BPC_TILE_DIM * 2 ctx.set_source_rgb(1, 1, 1) ctx.rectangle(cx - rect_dim / 2, cy - rect_dim / 2, rect_dim, rect_dim) ctx.stroke() ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(6) ctx.move_to(cx - 5, cy - 2) ctx.show_text(f'OBJ') ctx.translate(x, y) self._draw_plus(ctx) ctx.translate(-x, -y) def get_pos_place_object(self) -> Tuple[int, int]: """Get the X and Y position on the grid to place the object on, in PLACE_OBJECT mode.""" return self._snap_pos(self.mouse_x, self.mouse_y) def _surface_place_performer(self, ctx: cairo.Context, x, y, w, h): ctx.set_line_width(1) cx, cy = x + w / 2, y + h / 2 self._triangle(ctx, cx - BPC_TILE_DIM / 2, cy - BPC_TILE_DIM / 2, BPC_TILE_DIM, (255, 255, 255), 1) ctx.translate(x, y) self._draw_plus(ctx) ctx.translate(-x, -y) def get_pos_place_performer(self) -> Tuple[int, int]: """Get the X and Y position on the grid to place the performer on, in PLACE_PERFORMER mode.""" return self._snap_pos(self.mouse_x, self.mouse_y) def _surface_place_trigger(self, ctx: cairo.Context, x, y, w, h): ctx.set_line_width(1) cx, cy = x + w / 2, y + h / 2 rect_dim = BPC_TILE_DIM * 2 ctx.set_source_rgb(1, 1, 1) ctx.rectangle(cx - rect_dim / 2, cy - rect_dim / 2, rect_dim, rect_dim) ctx.stroke() ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(6) ctx.move_to(cx - 5, cy - 2) ctx.show_text(f'TRG') ctx.translate(x, y) self._draw_plus(ctx) ctx.translate(-x, -y) def get_pos_place_trigger(self) -> Tuple[int, int]: """Get the X and Y position on the grid to place the trigger on, in PLACE_TRIGGER mode.""" return self._snap_pos(self.mouse_x - BPC_TILE_DIM, self.mouse_y - BPC_TILE_DIM) def _draw_plus(self, ctx: cairo.Context): arrow_len = BPC_TILE_DIM / 4 ctx.set_source_rgb(1, 1, 1) ctx.translate(-arrow_len, 0) ctx.move_to(0, 0) ctx.rel_line_to(arrow_len * 2, 0) ctx.stroke() ctx.translate(arrow_len, -arrow_len) ctx.move_to(0, 0) ctx.rel_line_to(0, arrow_len * 2) ctx.stroke() ctx.translate(0, arrow_len) def set_sector_visible(self, sector_id, value): self._sectors_visible[sector_id] = value self.draw_area.queue_draw() def set_sector_solo(self, sector_id, value): self._sectors_solo[sector_id] = value self.draw_area.queue_draw() def set_sector_highlighted(self, sector_id): self._sector_highlighted = sector_id self.draw_area.queue_draw() def get_sector_highlighted(self): return self._sector_highlighted def set_selected(self, entity: Optional[Union[SsaActor, SsaObject, SsaPerformer, SsaEvent, SourceMapPositionMark]]): self._selected = entity self.draw_area.queue_draw() def add_position_marks(self, pos_marks): self.position_marks += pos_marks def set_drag_position(self, x: int, y: int): """Start dragging. x/y is the offset on the entity, where the dragging was started.""" self._selected__drag = (x, y) def end_drag(self): self._selected__drag = None def sector_added(self): self._sectors_solo.append(False) self._sectors_visible.append(True) def sector_removed(self, id): del self._sectors_solo[id] del self._sectors_visible[id] if self._sector_highlighted == id: self._sector_highlighted = None elif self._sector_highlighted > id: self._sector_highlighted -= 1 def get_current_drag_entity_pos(self) -> Tuple[int, int]: return self._snap_pos(self.mouse_x - self._selected__drag[0], self.mouse_y - self._selected__drag[1]) def _redraw(self): if self.draw_area is None or self.draw_area.get_parent() is None: return self.draw_area.queue_draw() def edit_position_marks(self): self._edit_pos_marks = True @staticmethod def _is_in_bb(bb_x, bb_y, bb_w, bb_h, mouse_x, mouse_y): return bb_x <= mouse_x < bb_x + bb_w and bb_y <= mouse_y < bb_y + bb_h @staticmethod def _snap_pos(x, y): x = x - x % (BPC_TILE_DIM / 2) y = y - y % (BPC_TILE_DIM / 2) return x, y
class Drawer: def __init__( self, draw_area: Widget, dbg: Union[Dbg, None], pal_ani_durations: int, # chunks_surfaces[chunk_idx][palette_animation_frame][frame] chunks_surfaces: Iterable[Iterable[List[cairo.Surface]]]): """ Initialize a drawer... :param draw_area: Widget to draw on. :param dbg: Either a DBG with chunk indexes or None, has to be set manually then for drawing :param chunks_surfaces: Bg controller format chunk surfaces """ self.draw_area = draw_area self.reset(dbg, pal_ani_durations, chunks_surfaces) self.draw_chunk_grid = True self.draw_tile_grid = True self.use_pink_bg = False # Interaction self.interaction_chunks_selected_id = 0 self.mouse_x = 99999 self.mouse_y = 99999 self.tiling_width = DBG_TILING_DIM self.tiling_height = DBG_TILING_DIM self.width_in_chunks = DBG_WIDTH_AND_HEIGHT self.height_in_chunks = DBG_WIDTH_AND_HEIGHT self.width_in_tiles = DBG_WIDTH_AND_HEIGHT * 3 self.height_in_tiles = DBG_WIDTH_AND_HEIGHT * 3 self.selection_plugin = SelectionDrawerPlugin( DPCI_TILE_DIM, DPCI_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(DPCI_TILE_DIM, DPCI_TILE_DIM) self.chunk_grid_plugin = GridDrawerPlugin( DPCI_TILE_DIM * self.tiling_width, DPCI_TILE_DIM * self.tiling_height, color=(0.15, 0.15, 0.15, 0.25)) self.scale = 1 self.drawing_is_active = False # noinspection PyAttributeOutsideInit def reset(self, dbg, pal_ani_durations, chunks_surfaces): if isinstance(dbg, Dbg): self.mappings = dbg.mappings else: self.mappings = [] self.animation_context = AnimationContext([chunks_surfaces], 0, pal_ani_durations) def start(self): """Start drawing on the DrawingArea""" self.drawing_is_active = True if isinstance(self.draw_area, Gtk.DrawingArea): self.draw_area.connect('draw', self.draw) self.draw_area.queue_draw() GLib.timeout_add(int(1000 / FPS), self._tick) def stop(self): self.drawing_is_active = False def _tick(self): if self.draw_area is None: return False if self.draw_area is not None and self.draw_area.get_parent() is None: # XXX: Gtk doesn't remove the widget on switch sometimes... self.draw_area.destroy() return False self.animation_context.advance() if EventManager.instance().get_if_main_window_has_fous(): self.draw_area.queue_draw() return self.drawing_is_active def draw(self, wdg, ctx: cairo.Context, do_translates=True): ctx.set_antialias(cairo.Antialias.NONE) ctx.scale(self.scale, self.scale) chunk_width = self.tiling_width * DPCI_TILE_DIM chunk_height = self.tiling_height * DPCI_TILE_DIM # Background if not self.use_pink_bg: ctx.set_source_rgb(0, 0, 0) else: ctx.set_source_rgb(1.0, 0, 1.0) ctx.rectangle( 0, 0, self.width_in_chunks * self.tiling_width * DPCI_TILE_DIM, self.height_in_chunks * self.tiling_height * DPCI_TILE_DIM) ctx.fill() # Layers for chunks_at_frame in self.animation_context.current(): for i, chunk_at_pos in enumerate(self.mappings): if 0 < chunk_at_pos < len(chunks_at_frame): chunk = chunks_at_frame[chunk_at_pos] ctx.set_source_surface(chunk, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() if (i + 1) % self.width_in_chunks == 0: # Move to beginning of next line if do_translates: ctx.translate( -chunk_width * (self.width_in_chunks - 1), chunk_height) else: # Move to next tile in line if do_translates: ctx.translate(chunk_width, 0) # Move back to beginning if do_translates: ctx.translate(0, -chunk_height * self.height_in_chunks) break size_w, size_h = self.draw_area.get_size_request() size_w /= self.scale size_h /= self.scale # Selection self.selection_plugin.set_size(self.tiling_width * DPCI_TILE_DIM, self.tiling_height * DPCI_TILE_DIM) self.selection_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # Tile Grid if self.draw_tile_grid: self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # Chunk Grid if self.draw_chunk_grid: self.chunk_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) return True def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int): # Draw a chunk chunks_at_frame = self.animation_context.current()[0] ctx.set_source_surface( chunks_at_frame[self.interaction_chunks_selected_id], x, y) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() def set_mouse_position(self, x, y): self.mouse_x = x self.mouse_y = y def set_selected_chunk(self, chunk_id): self.interaction_chunks_selected_id = chunk_id def get_selected_chunk_id(self): return self.interaction_chunks_selected_id def set_draw_chunk_grid(self, v): self.draw_chunk_grid = v def set_draw_tile_grid(self, v): self.draw_tile_grid = v def set_pink_bg(self, v): self.use_pink_bg = v def set_scale(self, v): self.scale = v
class WorldMapDrawer: def __init__( self, draw_area: Gtk.Widget, markers: List[MapMarkerPlacement], cb_dungeon_name: Callable[[int], str], scale: int ): self.draw_area = draw_area self.markers: List[MapMarkerPlacement] = markers self.markers_at_pos: Dict[Tuple[int, int], List[MapMarkerPlacement]] = {} self.map_bg = None self.level_id = None self.draw_tile_grid = True self._cb_dungeon_name = cb_dungeon_name # Interaction self.mouse_x = 99999 self.mouse_y = 99999 self._selected: Optional[MapMarkerPlacement] = None self._editing = None self._editing_pos = None self._hide = None self.tile_grid_plugin = GridDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, color=(0.2, 0.2, 0.2, 0.1) ) self.scale = scale self.drawing_is_active = False def start(self): """Start drawing on the DrawingArea""" self.drawing_is_active = True if isinstance(self.draw_area, Gtk.DrawingArea): self.draw_area.connect('draw', self.draw) self.draw_area.queue_draw() def draw(self, wdg, ctx: cairo.Context): ctx.set_antialias(cairo.Antialias.NONE) ctx.scale(self.scale, self.scale) # Background if self.map_bg is not None: if self.level_id == WORLD_MAP_DEFAULT_ID: # We display the bottom right of the map. ctx.set_source_surface(self.map_bg, -504, -1008) else: ctx.set_source_surface(self.map_bg, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() size_w, size_h = self.draw_area.get_size_request() size_w /= self.scale size_h /= self.scale # Tile Grid if self.draw_tile_grid: self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # Selection self._handle_selection(ctx) # RENDER MARKERS self.markers_at_pos = {} for i, marker in enumerate(self.markers): if marker != self._editing and marker != self._hide and marker.level_id == self.level_id and marker.reference_id <= -1: self._draw_marker(ctx, marker) if self._editing: # Black out ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, size_h) ctx.fill() # Render editing marker self._draw_marker(ctx, self._editing) # nah, too crowded. #for (x, y), list_of_markers in self.markers_at_pos.items(): # ms = [self.markers.index(m) for m in list_of_markers] # self._draw_name(ctx, ms, x, y) return True def get_under_mouse(self) -> Optional[MapMarkerPlacement]: """ Returns the first marker under the mouse position, if any. """ for i, marker in enumerate(self.markers): if marker.level_id == self.level_id and marker.reference_id <= -1: bb = (marker.x - RAD * 2, marker.y - RAD * 2, RAD * 4, RAD * 4) if self._is_in_bb(*bb, self.mouse_x, self.mouse_y): return marker return None def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int): if self._selected is not None and self._selected__drag is not None: # Draw dragged: x, y = self.get_current_drag_entity_pos() self._draw_marker(ctx, self._selected, x, y) def set_mouse_position(self, x, y): self.mouse_x = x self.mouse_y = y def _draw_marker(self, ctx: cairo.Context, marker: MapMarkerPlacement, x=None, y=None): x, y = self._get_marker_xy(marker, x, y) if (x, y) not in self.markers_at_pos: self.markers_at_pos[(x, y)] = [] self.markers_at_pos[(x, y)].append(marker) # Outline + Fill bb_cords = (x - RAD, y - RAD, RAD * 2, RAD * 2) ctx.rectangle(*bb_cords) ctx.set_line_width(1.0) ctx.set_source_rgb(0, 0, 0) ctx.stroke_preserve() ctx.set_source_rgba(*COLOR_MARKERS, 0.8) ctx.fill() def _get_marker_xy(self, marker: MapMarkerPlacement, x=None, y=None): if marker == self._editing: return self._editing_pos ref_marker = marker if marker.reference_id > -1: ref_marker = self.markers[marker.reference_id] if x is None: x = ref_marker.x if y is None: y = ref_marker.y return x, y def _handle_selection(self, ctx: cairo.Context): if self._selected is None: return if self._selected.level_id != self.level_id: return x, y = self._get_marker_xy(self._selected) x, y, w, h = (x - RAD * 2, y - RAD * 2, RAD * 4, RAD * 4) ctx.set_source_rgba(0, 0, 1, 0.6) ctx.rectangle(x, y, w, h) ctx.fill() def _draw_name(self, ctx: cairo.Context, marker_ids: List[int], x: int, y: int): ctx.set_source_rgb(*COLOR_MARKERS) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(4 * self.scale) ctx.move_to(x, y - 4 * self.scale) label = "" for mid in marker_ids: if self.markers[mid].reference_id > -1: dlabel = self._cb_dungeon_name(mid) if dlabel == '': dlabel = f'{mid}' label += f'{dlabel}, ' ctx.show_text(label.strip(', ')) def set_selected(self, entity: Optional[MapMarkerPlacement]): self._selected = entity self.draw_area.queue_draw() def set_editing(self, entity: Optional[MapMarkerPlacement], editing_pos: Tuple[int, int] = None, hide: Optional[MapMarkerPlacement] = None): self._editing = entity if editing_pos is None: editing_pos = (entity.x, entity.y) self._editing_pos = editing_pos self._selected = entity self._hide = hide self.draw_area.queue_draw() def _redraw(self): if self.draw_area is None or self.draw_area.get_parent() is None: return self.draw_area.queue_draw() @staticmethod def _is_in_bb(bb_x, bb_y, bb_w, bb_h, mouse_x, mouse_y): return bb_x <= mouse_x < bb_x + bb_w and bb_y <= mouse_y < bb_y + bb_h
class Drawer: def __init__( self, draw_area: Widget, bma: Union[BmaProtocol, None], bpa_durations: int, pal_ani_durations: int, # chunks_surfaces[layer_number][chunk_idx][palette_animation_frame][frame] chunks_surfaces: Iterable[Iterable[Iterable[Iterable[cairo.Surface]]]] ): """ Initialize a drawer... :param draw_area: Widget to draw on. :param bma: Either a BMA with tile indexes or None, has to be set manually then for drawing :param bpa_durations: How many frames to hold a BPA animation tile :param chunks_surfaces: Bg controller format chunk surfaces """ self.draw_area = draw_area self.reset(bma, bpa_durations, pal_ani_durations, chunks_surfaces) self.draw_chunk_grid = False self.draw_tile_grid = False self.use_pink_bg = False # Interaction self.interaction_mode = DrawerInteraction.NONE self.interaction_chunks_selected_id = 0 self.interaction_col_solid = False self.interaction_dat_value = 0 self.mouse_x = 99999 self.mouse_y = 99999 self.edited_layer = -1 self.edited_collision = -1 self.show_only_edited_layer = False self.dim_layers = False self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = False self.selection_plugin = SelectionDrawerPlugin( BPC_TILE_DIM, BPC_TILE_DIM, self.selection_draw_callback) self.tile_grid_plugin = GridDrawerPlugin(BPC_TILE_DIM, BPC_TILE_DIM) self.chunk_grid_plugin = GridDrawerPlugin( BPC_TILE_DIM * self.tiling_width, BPC_TILE_DIM * self.tiling_height, color=(0.15, 0.15, 0.15, 0.25)) self.scale = 1 self.drawing_is_active = False def reset_bma(self, bma): if isinstance(bma, BmaProtocol): self.tiling_width = bma.tiling_width self.tiling_height = bma.tiling_height self.mappings: List[Sequence[int]] = [bma.layer0, bma.layer1] # type: ignore self.width_in_chunks = bma.map_width_chunks self.height_in_chunks = bma.map_height_chunks self.width_in_tiles: Optional[u8] = bma.map_width_camera self.height_in_tiles: Optional[u8] = bma.map_height_camera self.collision1 = bma.collision self.collision2 = bma.collision2 self.data_layer = bma.unknown_data_block else: self.tiling_width = u8(3) self.tiling_height = u8(3) self.mappings = [[], []] self.width_in_chunks = u8(1) self.height_in_chunks = u8(1) self.width_in_tiles = None self.height_in_tiles = None self.collision1 = None self.collision2 = None self.data_layer = None # noinspection PyAttributeOutsideInit def reset(self, bma, bpa_durations, pal_ani_durations, chunks_surfaces): self.reset_bma(bma) self.animation_context = AnimationContext(chunks_surfaces, bpa_durations, pal_ani_durations) self._tileset_drawer_overlay: Optional[MapTilesetOverlay] = None def start(self): """Start drawing on the DrawingArea""" self.drawing_is_active = True if isinstance(self.draw_area, Gtk.DrawingArea): self.draw_area.connect('draw', self.draw) self.draw_area.queue_draw() GLib.timeout_add(int(1000 / FPS), self._tick) def stop(self): self.drawing_is_active = False def _tick(self): if self.draw_area is None: return False if self.draw_area is not None and self.draw_area.get_parent() is None: # XXX: Gtk doesn't remove the widget on switch sometimes... self.draw_area.destroy() return False self.animation_context.advance() if EventManager.instance().get_if_main_window_has_fous(): self.draw_area.queue_draw() return self.drawing_is_active def draw(self, wdg, ctx: cairo.Context, do_translates=True): ctx.set_antialias(cairo.Antialias.NONE) ctx.scale(self.scale, self.scale) chunk_width = self.tiling_width * BPC_TILE_DIM chunk_height = self.tiling_height * BPC_TILE_DIM # Background if not self.use_pink_bg: ctx.set_source_rgb(0, 0, 0) else: ctx.set_source_rgb(1.0, 0, 1.0) ctx.rectangle( 0, 0, self.width_in_chunks * self.tiling_width * BPC_TILE_DIM, self.height_in_chunks * self.tiling_height * BPC_TILE_DIM) ctx.fill() if self._tileset_drawer_overlay is not None and self._tileset_drawer_overlay.enabled: self._tileset_drawer_overlay.draw_full(ctx, self.mappings[0], self.width_in_chunks, self.height_in_chunks) else: # Layers for layer_idx, chunks_at_frame in enumerate( self.animation_context.current()): if self.show_only_edited_layer and layer_idx != self.edited_layer: continue current_layer_mappings = self.mappings[layer_idx] for i, chunk_at_pos in enumerate(current_layer_mappings): if 0 < chunk_at_pos < len(chunks_at_frame): chunk = chunks_at_frame[chunk_at_pos] ctx.set_source_surface(chunk, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) if self.edited_layer != -1 and layer_idx > 0 and layer_idx != self.edited_layer: # For Layer 1 if not the current edited: Set an alpha mask ctx.paint_with_alpha(0.7) else: ctx.paint() if (i + 1) % self.width_in_chunks == 0: # Move to beginning of next line if do_translates: ctx.translate( -chunk_width * (self.width_in_chunks - 1), chunk_height) else: # Move to next tile in line if do_translates: ctx.translate(chunk_width, 0) # Move back to beginning if do_translates: ctx.translate(0, -chunk_height * self.height_in_chunks) if (self.edited_layer != -1 and layer_idx < 1 and layer_idx != self.edited_layer) \ or (layer_idx == 1 and self.dim_layers) \ or (layer_idx == 0 and self.animation_context.num_layers < 2 and self.dim_layers): # For Layer 0 if not the current edited: Draw dark rectangle # or for layer 1 if dim layers # ...or for layer 0 if dim layers and no second layer ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle( 0, 0, self.width_in_chunks * self.tiling_width * BPC_TILE_DIM, self.height_in_chunks * self.tiling_height * BPC_TILE_DIM) ctx.fill() # Col 1 and 2 for col_index, should_draw in enumerate( [self.draw_collision1, self.draw_collision2]): if should_draw: if col_index == 0: ctx.set_source_rgba(1, 0, 0, 0.4) col: Sequence[bool] = self.collision1 # type: ignore else: ctx.set_source_rgba(0, 1, 0, 0.4) col = self.collision2 # type: ignore for i, c in enumerate(col): if c: ctx.rectangle(0, 0, BPC_TILE_DIM, BPC_TILE_DIM) ctx.fill() if (i + 1) % self.width_in_tiles == 0: # type: ignore # Move to beginning of next line if do_translates: ctx.translate(-BPC_TILE_DIM * (self.width_in_tiles - 1), BPC_TILE_DIM) # type: ignore else: # Move to next tile in line if do_translates: ctx.translate(BPC_TILE_DIM, 0) # Move back to beginning if do_translates: ctx.translate(0, -BPC_TILE_DIM * self.height_in_tiles) # type: ignore # Data if self.draw_data_layer: ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(6) ctx.set_source_rgb(0, 0, 1) assert self.data_layer is not None for i, dat in enumerate(self.data_layer): if dat > 0: ctx.move_to(0, BPC_TILE_DIM - 2) ctx.show_text(f"{dat:02x}") if (i + 1) % self.width_in_tiles == 0: # type: ignore # Move to beginning of next line if do_translates: ctx.translate(-BPC_TILE_DIM * (self.width_in_tiles - 1), BPC_TILE_DIM) # type: ignore else: # Move to next tile in line if do_translates: ctx.translate(BPC_TILE_DIM, 0) # Move back to beginning if do_translates: ctx.translate(0, -BPC_TILE_DIM * self.height_in_tiles) # type: ignore size_w, size_h = self.draw_area.get_size_request() size_w /= self.scale size_h /= self.scale # Selection if self.interaction_mode == DrawerInteraction.CHUNKS: self.selection_plugin.set_size(self.tiling_width * BPC_TILE_DIM, self.tiling_height * BPC_TILE_DIM) else: self.selection_plugin.set_size(BPC_TILE_DIM, BPC_TILE_DIM) self.selection_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # Tile Grid if self.draw_tile_grid: self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) # Chunk Grid if self.draw_chunk_grid: self.chunk_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x, self.mouse_y) return True def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int): if self.interaction_mode == DrawerInteraction.CHUNKS: # Draw a chunk chunks_at_frame = self.animation_context.current()[ self.edited_layer] ctx.set_source_surface( chunks_at_frame[self.interaction_chunks_selected_id], x, y) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() elif self.interaction_mode == DrawerInteraction.COL: # Draw collision if self.interaction_col_solid: ctx.set_source_rgba(1, 0, 0, 1) ctx.rectangle(x, y, BPC_TILE_DIM, BPC_TILE_DIM) ctx.fill() elif self.interaction_mode == DrawerInteraction.DAT: # Draw data if self.interaction_dat_value > 0: ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(6) ctx.set_source_rgb(1, 1, 1) ctx.move_to(x, y + BPC_TILE_DIM - 2) ctx.show_text(f"{self.interaction_dat_value:02x}") def set_mouse_position(self, x, y): self.mouse_x = x self.mouse_y = y def set_selected_chunk(self, chunk_id): self.interaction_chunks_selected_id = chunk_id def get_selected_chunk_id(self): return self.interaction_chunks_selected_id def set_interaction_col_solid(self, v): self.interaction_col_solid = v def get_interaction_col_solid(self): return self.interaction_col_solid def set_interaction_dat_value(self, v): self.interaction_dat_value = v def get_interaction_dat_value(self): return self.interaction_dat_value def set_edited_layer(self, layer_id): # The layer that is not edited will be drawn with a bit of transparency or darker # Default is -1, which shows all at full opacity self.dim_layers = False self.edited_layer = layer_id self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = False self.edited_collision = -1 self.interaction_mode = DrawerInteraction.CHUNKS def set_show_only_edited_layer(self, v): self.show_only_edited_layer = v def set_edited_collision(self, collision_id): self.dim_layers = True self.edited_layer = -1 self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = False if collision_id == 0: self.draw_collision1 = True elif collision_id == 1: self.draw_collision2 = True self.edited_collision = collision_id self.interaction_mode = DrawerInteraction.COL def get_edited_collision(self): return self.edited_collision def set_edit_data_layer(self): self.dim_layers = True self.edited_layer = -1 self.edited_collision = -1 self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = True self.interaction_mode = DrawerInteraction.DAT def get_interaction_mode(self): return self.interaction_mode def set_draw_chunk_grid(self, v): self.draw_chunk_grid = v def set_draw_tile_grid(self, v): self.draw_tile_grid = v def set_pink_bg(self, v): self.use_pink_bg = v def set_scale(self, v): self.scale = v def add_overlay(self, tileset_drawer_overlay): self._tileset_drawer_overlay = tileset_drawer_overlay @typing.no_type_check def unload(self): self.draw_area = None self.reset(None, None, None, None) self.draw_chunk_grid = False self.draw_tile_grid = False self.use_pink_bg = False self.interaction_mode = DrawerInteraction.NONE self.interaction_chunks_selected_id = 0 self.interaction_col_solid = False self.interaction_dat_value = 0 self.mouse_x = 99999 self.mouse_y = 99999 self.edited_layer = -1 self.edited_collision = -1 self.show_only_edited_layer = False self.dim_layers = False self.draw_collision1 = False self.draw_collision2 = False self.draw_data_layer = False self.selection_plugin = None self.tile_grid_plugin = None self.chunk_grid_plugin = None self.scale = 1 self.drawing_is_active = False
class FixedRoomDrawer: def __init__( self, draw_area: Gtk.Widget, fixed_floor: FixedFloor, sprite_provider: SpriteProvider, entity_rule_container: EntityRuleContainer, string_provider: StringProvider ): self.draw_area = draw_area self.fixed_floor = fixed_floor self.tileset_renderer: Optional[AbstractTilesetRenderer] = None self.draw_tile_grid = False self.info_layer_active = None self.entity_rule_container = entity_rule_container # Interaction self.interaction_mode = InteractionMode.SELECT self.mouse_x = 99999 self.mouse_y = 99999 self.sprite_provider = sprite_provider self.string_provider = string_provider # Depending on the mode this is either a coordinate tuple or a FixedFloorActionRule to place. self._selected: Optional[Union[Tuple[int, int], FixedFloorActionRule]] = None self._selected__drag = None self.selection_plugin = SelectionDrawerPlugin( DPCI_TILE_DIM * DPC_TILING_DIM, DPCI_TILE_DIM * DPC_TILING_DIM, self.selection_draw_callback ) self.tile_grid_plugin = GridDrawerPlugin( DPCI_TILE_DIM * DPC_TILING_DIM, DPCI_TILE_DIM * DPC_TILING_DIM, offset_x=OFFSET, offset_y=OFFSET ) self.scale = 1 self.drawing_is_active = False def start(self): """Start drawing on the DrawingArea""" self.drawing_is_active = True if isinstance(self.draw_area, Gtk.DrawingArea): self.draw_area.connect('draw', self.draw) self.draw_area.queue_draw() def draw(self, wdg, ctx: cairo.Context): ctx.set_antialias(cairo.Antialias.NONE) ctx.scale(self.scale, self.scale) # Background if self.tileset_renderer is not None: bg = self.tileset_renderer.get_background() if bg is not None: ctx.set_source_surface(bg, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() size_w, size_h = self.draw_area.get_size_request() size_w /= self.scale size_h /= self.scale # Black out bg a bit ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, size_h) ctx.fill() # Iterate over floor and render it draw_outside_as_second_terrain = any(action.tr_type == TileRuleType.SECONDARY_HALLWAY_VOID_ALL for action in self.fixed_floor.actions if isinstance(action, TileRule)) outside = DmaType.WATER if draw_outside_as_second_terrain else DmaType.WALL rules = [] rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) ridx = 0 for y in range(0, self.fixed_floor.height): row = [outside, outside, outside, outside, outside] rules.append(row) for x in range(0, self.fixed_floor.width): action = self.fixed_floor.actions[ridx] if isinstance(action, TileRule): if action.tr_type.floor_type == FloorType.FLOOR: row.append(DmaType.FLOOR) elif action.tr_type.floor_type == FloorType.WALL: row.append(DmaType.WALL) elif action.tr_type.floor_type == FloorType.SECONDARY: row.append(DmaType.WATER) elif action.tr_type.floor_type == FloorType.FLOOR_OR_WALL: row.append(DmaType.WALL) else: item, monster, tile, stats = self.entity_rule_container.get(action.entity_rule_id) if tile.is_secondary_terrain(): row.append(DmaType.WATER) else: row.append(DmaType.FLOOR) ridx += 1 row += [outside, outside, outside, outside, outside] rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) rules.append([outside] * (self.fixed_floor.width + 10)) dungeon = self.tileset_renderer.get_dungeon(rules) ctx.set_source_surface(dungeon, 0, 0) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() # Tile Grid if self.draw_tile_grid: self.tile_grid_plugin.draw(ctx, size_w - OFFSET, size_h - OFFSET, self.mouse_x, self.mouse_y) # Black out non-editable area ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, DPCI_TILE_DIM * DPC_TILING_DIM * 5) ctx.fill() ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, DPCI_TILE_DIM * DPC_TILING_DIM * (self.fixed_floor.height + 5), size_w, DPCI_TILE_DIM * DPC_TILING_DIM * 5) ctx.fill() ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, DPCI_TILE_DIM * DPC_TILING_DIM * 5, DPCI_TILE_DIM * DPC_TILING_DIM * 5, DPCI_TILE_DIM * DPC_TILING_DIM * self.fixed_floor.height) ctx.fill() ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(DPCI_TILE_DIM * DPC_TILING_DIM * (self.fixed_floor.width + 5), DPCI_TILE_DIM * DPC_TILING_DIM * 5, DPCI_TILE_DIM * DPC_TILING_DIM * 5, DPCI_TILE_DIM * DPC_TILING_DIM * self.fixed_floor.height) ctx.fill() # Draw Pokémon, items, traps, etc. ridx = 0 for y in range(0, self.fixed_floor.height): y += 5 for x in range(0, self.fixed_floor.width): x += 5 action = self.fixed_floor.actions[ridx] sx = DPCI_TILE_DIM * DPC_TILING_DIM * x sy = DPCI_TILE_DIM * DPC_TILING_DIM * y self._draw_action(ctx, action, sx, sy) ridx += 1 # Draw info layer if self.info_layer_active: # Black out bg a bit ctx.set_source_rgba(0, 0, 0, 0.5) ctx.rectangle(0, 0, size_w, size_h) ctx.fill() ridx = 0 for y in range(0, self.fixed_floor.height): y += 5 for x in range(0, self.fixed_floor.width): x += 5 action = self.fixed_floor.actions[ridx] sx = DPCI_TILE_DIM * DPC_TILING_DIM * x sy = DPCI_TILE_DIM * DPC_TILING_DIM * y if isinstance(action, EntityRule): item, monster, tile, stats = self.entity_rule_container.get(action.entity_rule_id) # Has trap? if tile.trap_id < 25 and self.info_layer_active == InfoLayer.TRAP: self._draw_info_trap(sx, sy, ctx, self._trap_name(tile.trap_id), tile.can_be_broken(), tile.trap_is_visible()) # Has item? if item.item_id > 0 and self.info_layer_active == InfoLayer.ITEM: self._draw_info_item(sx, sy, ctx, self._item_name(item.item_id)) # Has Pokémon? if monster.md_idx > 0 and self.info_layer_active == InfoLayer.MONSTER: self._draw_info_monster(sx, sy, ctx, monster.enemy_settings) if self.info_layer_active == InfoLayer.TILE: self._draw_info_tile(sx, sy, ctx, tile.room_id, False, False) elif self.info_layer_active == InfoLayer.TILE: self._draw_info_tile(sx, sy, ctx, 0 if action.tr_type.room_type == RoomType.ROOM else -1, action.tr_type.impassable, action.tr_type.absolute_mover) ridx += 1 # Cursor / Active selected / Place mode x, y, w, h = self.mouse_x, self.mouse_y, DPCI_TILE_DIM * DPC_TILING_DIM, DPCI_TILE_DIM * DPC_TILING_DIM self.selection_plugin.set_size(w, h) xg, yg = self.get_cursor_pos_in_grid() xg *= DPCI_TILE_DIM * DPC_TILING_DIM yg *= DPCI_TILE_DIM * DPC_TILING_DIM self.selection_plugin.draw(ctx, size_w, size_h, xg, yg, ignore_obb=True) return True def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int): sx = DPCI_TILE_DIM * DPC_TILING_DIM * x sy = DPCI_TILE_DIM * DPC_TILING_DIM * y if self.interaction_mode == InteractionMode.SELECT: if self._selected is not None and self._selected__drag is not None: # Draw dragged: selected_x, selected_y = self._selected selected = self.fixed_floor.actions[self.fixed_floor.width * selected_y + selected_x] self._draw_single_tile(ctx, selected, x, y) self._draw_action(ctx, selected, x, y) # Tool modes elif self.interaction_mode == InteractionMode.PLACE_TILE or self.interaction_mode == InteractionMode.PLACE_ENTITY: self._draw_single_tile(ctx, self._selected, x, y) self._draw_action(ctx, self._selected, x, y) def set_mouse_position(self, x, y): self.mouse_x = x self.mouse_y = y def end_drag(self): self._selected__drag = None def set_draw_tile_grid(self, v): self.draw_tile_grid = v self._redraw() def set_info_layer(self, v: Optional[InfoLayer]): self.info_layer_active = v self.draw_area.queue_draw() def set_scale(self, v): self.scale = v def set_tileset_renderer(self, renderer: AbstractTilesetRenderer): self.tileset_renderer = renderer def set_selected(self, selected): self._selected = selected self._redraw() def get_selected(self): return self._selected def set_drag_position(self, x: int, y: int): """Start dragging. x/y is the offset on the entity, where the dragging was started.""" self._selected__drag = (x, y) def get_cursor_is_in_bounds(self, w, h, real_offset=False): return self.get_pos_is_in_bounds(self.mouse_x, self.mouse_y, w, h, real_offset) def get_cursor_pos_in_grid(self, real_offset=False): return self.get_pos_in_grid(self.mouse_x, self.mouse_y, real_offset) def get_pos_is_in_bounds(self, x, y, w, h, real_offset=False): x, y = self.get_pos_in_grid(x, y, real_offset) return x > -1 and y > - 1 and x < w and y < h def get_pos_in_grid(self, x, y, real_offset=False): x = int(x / (DPC_TILING_DIM * DPCI_TILE_DIM)) y = int(y / (DPC_TILING_DIM * DPCI_TILE_DIM)) if real_offset: x -= 5 y -= 5 return x, y def _redraw(self): if self.draw_area is None or self.draw_area.get_parent() is None: return self.draw_area.queue_draw() def _draw_placeholder(self, actor_id, sx, sy, direction, ctx): sprite, cx, cy, w, h = self.sprite_provider.get_actor_placeholder( actor_id, direction.ssa_id if direction is not None else 0, lambda: GLib.idle_add(self._redraw) ) ctx.translate(sx, sy) ctx.set_source_surface( sprite, -cx + DPCI_TILE_DIM * DPC_TILING_DIM / 2, -cy + DPCI_TILE_DIM * DPC_TILING_DIM * 0.75 ) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() ctx.translate(-sx, -sy) def _draw_info_trap(self, sx, sy, ctx, name, can_be_broken, visible): self._draw_name(ctx, sx, sy, name, COLOR_WHITE) if can_be_broken: self._draw_bottom_left(ctx, sx, sy, COLOR_RED, 'B') if visible: self._draw_bottom_right(ctx, sx, sy, COLOR_YELLOW, 'V') def _draw_info_item(self, sx, sy, ctx, name): self._draw_name(ctx, sx, sy, name, COLOR_WHITE) def _draw_info_monster(self, sx, sy, ctx, enemy_settings): if enemy_settings == MonsterSpawnType.ALLY_HELP: self._draw_bottom_left(ctx, sx, sy, COLOR_GREEN, 'A') elif enemy_settings == MonsterSpawnType.ENEMY_STRONG: self._draw_bottom_left(ctx, sx, sy, COLOR_RED, 'E') else: self._draw_bottom_right(ctx, sx, sy, COLOR_YELLOW, 'O') def _draw_info_tile(self, sx, sy, ctx, room_id, impassable, absolute_mover): if room_id > -1: self._draw_top_right(ctx, sx, sy, COLOR_RED, str(room_id)) if impassable: self._draw_bottom_left(ctx, sx, sy, COLOR_YELLOW, 'I') if absolute_mover: self._draw_bottom_right(ctx, sx, sy, COLOR_GREEN, 'A') def _trap_name(self, trap_id): return MappaTrapType(trap_id).name def _item_name(self, item_id): return self.string_provider.get_value(StringType.ITEM_NAMES, item_id) if item_id < MAX_ITEMS else _("(Special?)") def _draw_name(self, ctx, sx, sy, name, color): ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_source_rgba(*color) ctx.set_font_size(12) ctx.move_to(sx, sy - 10) ctx.show_text(name) def _draw_top_right(self, ctx, sx, sy, color, text): self._draw_little_text(ctx, sx + 20, sy + 8, color, text) def _draw_bottom_left(self, ctx, sx, sy, color, text): self._draw_little_text(ctx, sx + 2, sy + 22, color, text) def _draw_bottom_right(self, ctx, sx, sy, color, text): self._draw_little_text(ctx, sx + 20, sy + 22, color, text) def _draw_little_text(self, ctx, sx, sy, color, text): ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_source_rgba(*color) ctx.set_font_size(8) ctx.move_to(sx, sy) ctx.show_text(text) def _draw_action(self, ctx, action, sx, sy): if isinstance(action, EntityRule): item, monster, tile, stats = self.entity_rule_container.get(action.entity_rule_id) # Has trap? if tile.trap_id < 25: ctx.rectangle(sx + 5, sy + 5, DPCI_TILE_DIM * DPC_TILING_DIM - 10, DPCI_TILE_DIM * DPC_TILING_DIM - 10) ctx.set_source_rgba(*COLOR_TRAP) ctx.fill_preserve() ctx.set_source_rgba(*COLOR_OUTLINE) ctx.set_line_width(1) ctx.stroke() # Has item? if item.item_id > 0: ctx.arc(sx + DPCI_TILE_DIM * DPC_TILING_DIM / 2, sy + DPCI_TILE_DIM * DPC_TILING_DIM / 2, DPCI_TILE_DIM * DPC_TILING_DIM / 2, 0, 2 * math.pi) ctx.set_source_rgba(*COLOR_ITEM) ctx.fill_preserve() ctx.set_source_rgba(*COLOR_OUTLINE) ctx.set_line_width(1) ctx.stroke() # Has Pokémon? if monster.md_idx > 0: sprite, cx, cy, w, h = self.sprite_provider.get_monster( monster.md_idx, action.direction.ssa_id if action.direction is not None else 0, lambda: GLib.idle_add(self._redraw) ) ctx.translate(sx, sy) ctx.set_source_surface( sprite, -cx + DPCI_TILE_DIM * DPC_TILING_DIM / 2, -cy + DPCI_TILE_DIM * DPC_TILING_DIM * 0.75 ) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() ctx.translate(-sx, -sy) else: # Leader spawn tile if action.tr_type == TileRuleType.LEADER_SPAWN: self._draw_placeholder(0, sx, sy, action.direction, ctx) # Attendant1 spawn tile if action.tr_type == TileRuleType.ATTENDANT1_SPAWN: self._draw_placeholder(10, sx, sy, action.direction, ctx) # Attendant2 spawn tile if action.tr_type == TileRuleType.ATTENDANT2_SPAWN: self._draw_placeholder(11, sx, sy, action.direction, ctx) # Attendant3 spawn tile if action.tr_type == TileRuleType.ATTENDANT3_SPAWN: self._draw_placeholder(15, sx, sy, action.direction, ctx) # Key walls if action.tr_type == TileRuleType.FL_WA_ROOM_FLAG_0C or action.tr_type == TileRuleType.FL_WA_ROOM_FLAG_0D: ctx.rectangle(sx + 5, sy + 5, DPCI_TILE_DIM * DPC_TILING_DIM - 10, DPCI_TILE_DIM * DPC_TILING_DIM - 10) ctx.set_source_rgba(*COLOR_KEY_WALL) ctx.fill_preserve() ctx.set_source_rgba(*COLOR_OUTLINE) ctx.set_line_width(1) ctx.stroke() # Warp zone if action.tr_type == TileRuleType.WARP_ZONE or action.tr_type == TileRuleType.WARP_ZONE_2: ctx.rectangle(sx + 5, sy + 5, DPCI_TILE_DIM * DPC_TILING_DIM - 10, DPCI_TILE_DIM * DPC_TILING_DIM - 10) ctx.set_source_rgba(*COLOR_WARP_ZONE) ctx.fill_preserve() ctx.set_source_rgba(*COLOR_OUTLINE) ctx.set_line_width(1) ctx.stroke() def _draw_single_tile(self, ctx, action, x, y): type = DmaType.FLOOR if isinstance(action, TileRule): if action.tr_type.floor_type == FloorType.WALL: type = DmaType.WALL elif action.tr_type.floor_type == FloorType.SECONDARY: type = DmaType.WATER elif action.tr_type.floor_type == FloorType.FLOOR_OR_WALL: type = DmaType.WALL else: item, monster, tile, stats = self.entity_rule_container.get(action.entity_rule_id) if tile.is_secondary_terrain(): type = DmaType.WATER surf = self.tileset_renderer.get_single_tile(type) ctx.translate(x, y) ctx.set_source_surface( surf, 0, 0 ) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() ctx.translate(-x, -y)