diff --git a/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg b/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg
new file mode 100644
index 0000000..fe4dbf5
--- /dev/null
+++ b/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg
@@ -0,0 +1 @@
+
diff --git a/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg.import b/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg.import
new file mode 100644
index 0000000..9584d3b
--- /dev/null
+++ b/addons/anthonyec.camera_preview/GuiResizerTopLeft.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btc01wc11tiid"
+path="res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg"
+dest_files=["res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/anthonyec.camera_preview/GuiResizerTopRight.svg b/addons/anthonyec.camera_preview/GuiResizerTopRight.svg
new file mode 100644
index 0000000..dd00953
--- /dev/null
+++ b/addons/anthonyec.camera_preview/GuiResizerTopRight.svg
@@ -0,0 +1 @@
+
diff --git a/addons/anthonyec.camera_preview/GuiResizerTopRight.svg.import b/addons/anthonyec.camera_preview/GuiResizerTopRight.svg.import
new file mode 100644
index 0000000..4a1fa5d
--- /dev/null
+++ b/addons/anthonyec.camera_preview/GuiResizerTopRight.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://04l05jxuyt7k"
+path="res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg"
+dest_files=["res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/anthonyec.camera_preview/Pin.svg b/addons/anthonyec.camera_preview/Pin.svg
new file mode 100644
index 0000000..8e5935c
--- /dev/null
+++ b/addons/anthonyec.camera_preview/Pin.svg
@@ -0,0 +1 @@
+
diff --git a/addons/anthonyec.camera_preview/Pin.svg.import b/addons/anthonyec.camera_preview/Pin.svg.import
new file mode 100644
index 0000000..27d274f
--- /dev/null
+++ b/addons/anthonyec.camera_preview/Pin.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://do6d60od41vmg"
+path="res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/anthonyec.camera_preview/Pin.svg"
+dest_files=["res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/anthonyec.camera_preview/plugin.cfg b/addons/anthonyec.camera_preview/plugin.cfg
new file mode 100644
index 0000000..4ad0d4c
--- /dev/null
+++ b/addons/anthonyec.camera_preview/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Little Camera Preview"
+description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
+author="Anthony Cossins"
+version="1.3"
+script="plugin.gd"
diff --git a/addons/anthonyec.camera_preview/plugin.gd b/addons/anthonyec.camera_preview/plugin.gd
new file mode 100644
index 0000000..4e74dd8
--- /dev/null
+++ b/addons/anthonyec.camera_preview/plugin.gd
@@ -0,0 +1,87 @@
+@tool
+extends EditorPlugin
+
+const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
+
+var preview: CameraPreview
+var current_main_screen_name: String
+
+func _enter_tree() -> void:
+ main_screen_changed.connect(_on_main_screen_changed)
+ EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
+
+ # Initialise preview panel and add to main screen.
+ preview = preview_scene.instantiate() as CameraPreview
+ preview.request_hide()
+
+ var main_screen = EditorInterface.get_editor_main_screen()
+ main_screen.add_child(preview)
+
+func _exit_tree() -> void:
+ if preview:
+ preview.queue_free()
+
+func _ready() -> void:
+ # TODO: Currently there is no API to get the main screen name without
+ # listening to the `EditorPlugin.main_screen_changed` signal:
+ # https://github.com/godotengine/godot-proposals/issues/2081
+ EditorInterface.set_main_screen_editor("Script")
+ EditorInterface.set_main_screen_editor("3D")
+
+func _on_main_screen_changed(screen_name: String) -> void:
+ current_main_screen_name = screen_name
+
+ # TODO: Bit of a hack to prevent pinned staying between view changes on the same scene.
+ preview.unlink_camera()
+ _on_editor_selection_changed()
+
+func _on_editor_selection_changed() -> void:
+ if not is_main_screen_viewport():
+ # This hides the preview "container" and not the preview itself, allowing
+ # any locked previews to remain visible once switching back to 3D tab.
+ preview.visible = false
+ return
+
+ preview.visible = true
+
+ var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
+
+ var selected_camera_3d: Camera3D = find_camera_3d_or_null(selected_nodes)
+ var selected_camera_2d: Camera2D = find_camera_2d_or_null(selected_nodes)
+
+ if selected_camera_3d and current_main_screen_name == "3D":
+ preview.link_with_camera_3d(selected_camera_3d)
+ preview.request_show()
+
+ elif selected_camera_2d and current_main_screen_name == "2D":
+ preview.link_with_camera_2d(selected_camera_2d)
+ preview.request_show()
+
+ else:
+ preview.request_hide()
+
+func is_main_screen_viewport() -> bool:
+ return current_main_screen_name == "3D" or current_main_screen_name == "2D"
+
+func find_camera_3d_or_null(nodes: Array[Node]) -> Camera3D:
+ var camera: Camera3D
+
+ for node in nodes:
+ if node is Camera3D:
+ camera = node as Camera3D
+ break
+
+ return camera
+
+func find_camera_2d_or_null(nodes: Array[Node]) -> Camera2D:
+ var camera: Camera2D
+
+ for node in nodes:
+ if node is Camera2D:
+ camera = node as Camera2D
+ break
+
+ return camera
+
+func _on_selected_camera_3d_tree_exiting() -> void:
+ preview.unlink_camera()
diff --git a/addons/anthonyec.camera_preview/preview.gd b/addons/anthonyec.camera_preview/preview.gd
new file mode 100644
index 0000000..3c07d04
--- /dev/null
+++ b/addons/anthonyec.camera_preview/preview.gd
@@ -0,0 +1,404 @@
+@tool
+
+class_name CameraPreview
+extends Control
+
+enum CameraType {
+ CAMERA_2D,
+ CAMERA_3D
+}
+
+enum PinnedPosition {
+ LEFT,
+ RIGHT,
+}
+
+enum InteractionState {
+ NONE,
+ RESIZE,
+ DRAG,
+
+ # Animation is split into 2 seperate states so that the tween is only
+ # invoked once in the "start" state.
+ START_ANIMATE_INTO_PLACE,
+ ANIMATE_INTO_PLACE,
+}
+
+const margin_3d: Vector2 = Vector2(10, 10)
+const margin_2d: Vector2 = Vector2(20, 15)
+const panel_margin: float = 2
+const min_panel_size: float = 250
+
+@onready var panel: Panel = %Panel
+@onready var placeholder: Panel = %Placeholder
+@onready var preview_camera_3d: Camera3D = %Camera3D
+@onready var preview_camera_2d: Camera2D = %Camera2D
+@onready var sub_viewport: SubViewport = %SubViewport
+@onready var sub_viewport_text_rect: TextureRect = %TextureRect
+@onready var resize_left_handle: Button = %ResizeLeftHandle
+@onready var resize_right_handle: Button = %ResizeRightHandle
+@onready var lock_button: Button = %LockButton
+@onready var gradient: TextureRect = %Gradient
+@onready var viewport_margin_container: MarginContainer = %ViewportMarginContainer
+@onready var overlay_margin_container: MarginContainer = %OverlayMarginContainer
+@onready var overlay_container: Control = %OverlayContainer
+
+var camera_type: CameraType = CameraType.CAMERA_3D
+var pinned_position: PinnedPosition = PinnedPosition.RIGHT
+var viewport_ratio: float = 1
+var editor_scale: float = EditorInterface.get_editor_scale()
+var is_locked: bool
+var show_controls: bool
+var selected_camera_3d: Camera3D
+var selected_camera_2d: Camera2D
+
+var state: InteractionState = InteractionState.NONE
+var initial_mouse_position: Vector2
+var initial_panel_size: Vector2
+var initial_panel_position: Vector2
+
+func _ready() -> void:
+ # Set initial width.
+ panel.size.x = min_panel_size * editor_scale
+
+ # Setting texture to viewport in code instead of directly in the editor
+ # because otherwise an error "Path to node is invalid: Panel/SubViewport"
+ # on first load. This is harmless but doesn't look great.
+ #
+ # This is a known issue:
+ # https://github.com/godotengine/godot/issues/27790#issuecomment-499740220
+ sub_viewport_text_rect.texture = sub_viewport.get_texture()
+
+ # From what I can tell there's something wrong with how an editor theme
+ # scales when used within a plugin. It seems to ignore the screen scale.
+ # For instance, a 30x30px button will appear tiny on a retina display.
+ #
+ # Someone else had the issue with no luck:
+ # https://forum.godotengine.org/t/how-to-scale-plugin-controls-to-look-the-same-in-4k-as-1080p/36151
+ #
+ # And seems Dialogic also scales buttons manually:
+ # https://github.com/dialogic-godot/dialogic/blob/master/addons/dialogic/Editor/Common/sidebar.gd#L25C6-L38
+ #
+ # Maybe I don't know the correct way to do it, so for now the workaround is
+ # to set the correct size in code using screen scale.
+ var button_size = Vector2(30, 30) * editor_scale
+ var margin_size: float = panel_margin * editor_scale
+
+ resize_left_handle.size = button_size
+ resize_left_handle.pivot_offset = Vector2(0, 0) * editor_scale
+
+ resize_right_handle.size = button_size
+ resize_right_handle.pivot_offset = Vector2(30, 30) * editor_scale
+
+ lock_button.size = button_size
+ lock_button.pivot_offset = Vector2(0, 30) * editor_scale
+
+ viewport_margin_container.add_theme_constant_override("margin_left", margin_size)
+ viewport_margin_container.add_theme_constant_override("margin_top", margin_size)
+ viewport_margin_container.add_theme_constant_override("margin_right", margin_size)
+ viewport_margin_container.add_theme_constant_override("margin_bottom", margin_size)
+
+ overlay_margin_container.add_theme_constant_override("margin_left", margin_size)
+ overlay_margin_container.add_theme_constant_override("margin_top", margin_size)
+ overlay_margin_container.add_theme_constant_override("margin_right", margin_size)
+ overlay_margin_container.add_theme_constant_override("margin_bottom", margin_size)
+
+ # Parent node overlay size is not available on first ready, need to wait a
+ # frame for it to be drawn.
+ await get_tree().process_frame
+
+ # Anchors are set in code because setting them in the editor UI doesn't take
+ # editor scale into account.
+ resize_left_handle.position = Vector2(0, 0)
+ resize_right_handle.set_anchors_preset(Control.PRESET_TOP_LEFT)
+
+ resize_right_handle.position = Vector2(overlay_container.size.x - button_size.x, 0)
+ resize_right_handle.set_anchors_preset(Control.PRESET_TOP_RIGHT)
+
+ lock_button.position = Vector2(0, overlay_container.size.y - button_size.y)
+ lock_button.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
+
+func _process(_delta: float) -> void:
+ if not visible: return
+
+ match state:
+ InteractionState.NONE:
+ panel.size = get_clamped_size(panel.size)
+ panel.position = get_pinned_position(pinned_position)
+
+ InteractionState.RESIZE:
+ var delta_mouse_position = initial_mouse_position - get_global_mouse_position()
+ var resized_size = panel.size
+
+ if pinned_position == PinnedPosition.LEFT:
+ resized_size = initial_panel_size - delta_mouse_position
+
+ if pinned_position == PinnedPosition.RIGHT:
+ resized_size = initial_panel_size + delta_mouse_position
+
+ panel.size = get_clamped_size(resized_size)
+ panel.position = get_pinned_position(pinned_position)
+
+ InteractionState.DRAG:
+ placeholder.size = panel.size
+
+ var global_mouse_position = get_global_mouse_position()
+ var offset = initial_mouse_position - initial_panel_position
+
+ panel.global_position = global_mouse_position - offset
+
+ if global_mouse_position.x < global_position.x + size.x / 2:
+ pinned_position = PinnedPosition.LEFT
+ else:
+ pinned_position = PinnedPosition.RIGHT
+
+ placeholder.position = get_pinned_position(pinned_position)
+
+ InteractionState.START_ANIMATE_INTO_PLACE:
+ var final_position: Vector2 = get_pinned_position(pinned_position)
+ var tween = get_tree().create_tween()
+
+ tween.set_ease(Tween.EASE_OUT)
+ tween.set_trans(Tween.TRANS_CUBIC)
+ tween.tween_property(panel, "position", final_position, 0.3)
+
+ tween.finished.connect(func():
+ panel.position = final_position
+ state = InteractionState.NONE
+ )
+
+ state = InteractionState.ANIMATE_INTO_PLACE
+
+ # I couldn't get `mouse_entered` and `mouse_exited` events to work
+ # nicely, so I use rect method instead. Plus using this method it's easy to
+ # grow the hit area size.
+ var panel_hover_rect = Rect2(panel.global_position, panel.size)
+ panel_hover_rect = panel_hover_rect.grow(40)
+
+ var mouse_position = get_global_mouse_position()
+
+ show_controls = state != InteractionState.NONE or panel_hover_rect.has_point(mouse_position)
+
+ # UI visibility.
+ resize_left_handle.visible = show_controls and pinned_position == PinnedPosition.RIGHT
+ resize_right_handle.visible = show_controls and pinned_position == PinnedPosition.LEFT
+ lock_button.visible = show_controls or is_locked
+ placeholder.visible = state == InteractionState.DRAG or state == InteractionState.ANIMATE_INTO_PLACE
+ gradient.visible = show_controls
+
+ # Sync camera settings.
+ if camera_type == CameraType.CAMERA_3D and selected_camera_3d:
+ sub_viewport.size = panel.size
+
+ # Sync position and rotation without using a `RemoteTransform` node
+ # because if you save a camera as a scene, the remote transform node will
+ # be stored within the scene. Also it's harder to keep the remote
+ # transform `remote_path` up-to-date with scene changes, which causes
+ # many errors.
+ preview_camera_3d.global_position = selected_camera_3d.global_position
+ preview_camera_3d.global_rotation = selected_camera_3d.global_rotation
+
+ preview_camera_3d.fov = selected_camera_3d.fov
+ preview_camera_3d.projection = selected_camera_3d.projection
+ preview_camera_3d.size = selected_camera_3d.size
+ preview_camera_3d.cull_mask = selected_camera_3d.cull_mask
+ preview_camera_3d.keep_aspect = selected_camera_3d.keep_aspect
+ preview_camera_3d.near = selected_camera_3d.near
+ preview_camera_3d.far = selected_camera_3d.far
+ preview_camera_3d.h_offset = selected_camera_3d.h_offset
+ preview_camera_3d.v_offset = selected_camera_3d.v_offset
+ preview_camera_3d.attributes = selected_camera_3d.attributes
+ preview_camera_3d.environment = selected_camera_3d.environment
+
+ if camera_type == CameraType.CAMERA_2D and selected_camera_2d:
+ var project_window_size = get_project_window_size()
+ var ratio = project_window_size.x / panel.size.x
+
+ # TODO: Is there a better way to fix this?
+ # The camera border is visible sometimes due to pixel rounding.
+ # Subtract 1px from right and bottom to hide this.
+ var hide_camera_border_fix = Vector2(1, 1)
+
+ sub_viewport.size = panel.size
+ sub_viewport.size_2d_override = (panel.size - hide_camera_border_fix) * ratio
+ sub_viewport.size_2d_override_stretch = true
+
+ preview_camera_2d.global_position = selected_camera_2d.global_position
+ preview_camera_2d.global_rotation = selected_camera_2d.global_rotation
+
+ preview_camera_2d.offset = selected_camera_2d.offset
+ preview_camera_2d.zoom = selected_camera_2d.zoom
+ preview_camera_2d.ignore_rotation = selected_camera_2d.ignore_rotation
+ preview_camera_2d.anchor_mode = selected_camera_2d.anchor_mode
+ preview_camera_2d.limit_left = selected_camera_2d.limit_left
+ preview_camera_2d.limit_right = selected_camera_2d.limit_right
+ preview_camera_2d.limit_top = selected_camera_2d.limit_top
+ preview_camera_2d.limit_bottom = selected_camera_2d.limit_bottom
+
+func link_with_camera_3d(camera_3d: Camera3D) -> void:
+ # TODO: Camera may not be ready since this method is called in `_enter_tree`
+ # in the plugin because of a workaround for:
+ # https://github.com/godotengine/godot-proposals/issues/2081
+ if not preview_camera_3d:
+ return request_hide()
+
+ var is_different_camera = camera_3d != preview_camera_3d
+
+ # TODO: A bit messy.
+ if is_different_camera:
+ if preview_camera_3d.tree_exiting.is_connected(unlink_camera):
+ preview_camera_3d.tree_exiting.disconnect(unlink_camera)
+
+ if not camera_3d.tree_exiting.is_connected(unlink_camera):
+ camera_3d.tree_exiting.connect(unlink_camera)
+
+ sub_viewport.disable_3d = false
+ sub_viewport.world_3d = camera_3d.get_world_3d()
+
+ selected_camera_3d = camera_3d
+ camera_type = CameraType.CAMERA_3D
+
+func link_with_camera_2d(camera_2d: Camera2D) -> void:
+ if not preview_camera_2d:
+ return request_hide()
+
+ var is_different_camera = camera_2d != preview_camera_2d
+
+ # TODO: A bit messy.
+ if is_different_camera:
+ if preview_camera_2d.tree_exiting.is_connected(unlink_camera):
+ preview_camera_2d.tree_exiting.disconnect(unlink_camera)
+
+ if not camera_2d.tree_exiting.is_connected(unlink_camera):
+ camera_2d.tree_exiting.connect(unlink_camera)
+
+ sub_viewport.disable_3d = true
+ sub_viewport.world_2d = camera_2d.get_world_2d()
+
+ selected_camera_2d = camera_2d
+ camera_type = CameraType.CAMERA_2D
+
+func unlink_camera() -> void:
+ if selected_camera_3d:
+ selected_camera_3d = null
+
+ if selected_camera_2d:
+ selected_camera_2d = null
+
+ is_locked = false
+ lock_button.button_pressed = false
+
+func request_hide() -> void:
+ if is_locked: return
+ visible = false
+
+func request_show() -> void:
+ visible = true
+
+func get_pinned_position(pinned_position: PinnedPosition) -> Vector2:
+ var margin: Vector2 = margin_3d * editor_scale
+
+ if camera_type == CameraType.CAMERA_2D:
+ margin = margin_2d * editor_scale
+
+ match pinned_position:
+ PinnedPosition.LEFT:
+ return Vector2.ZERO - Vector2(0, panel.size.y) - Vector2(-margin.x, margin.y)
+ PinnedPosition.RIGHT:
+ return size - panel.size - margin
+ _:
+ assert(false, "Unknown pinned position %s" % str(pinned_position))
+
+ return Vector2.ZERO
+
+func get_clamped_size(desired_size: Vector2) -> Vector2:
+ var viewport_ratio = get_project_window_ratio()
+ var editor_viewport_size = get_editor_viewport_size()
+
+ var max_bounds = Vector2(
+ editor_viewport_size.x * 0.6,
+ editor_viewport_size.y * 0.8
+ )
+
+ var clamped_size = desired_size
+
+ # Apply aspect ratio.
+ clamped_size = Vector2(clamped_size.x, clamped_size.x * viewport_ratio)
+
+ # Clamp the max size while respecting the aspect ratio.
+ if clamped_size.y >= max_bounds.y:
+ clamped_size.x = max_bounds.y / viewport_ratio
+ clamped_size.y = max_bounds.y
+
+ if clamped_size.x >= max_bounds.x:
+ clamped_size.x = max_bounds.x
+ clamped_size.y = max_bounds.x * viewport_ratio
+
+ # Clamp the min size based on if it's portrait or landscape. Portrait min
+ # size should be based on it's height. Landscape min size is based on it's
+ # width instead. Applying min width to a portrait size would make it too big.
+ var is_portrait = viewport_ratio > 1
+
+ if is_portrait and clamped_size.y <= min_panel_size * editor_scale:
+ clamped_size.x = min_panel_size / viewport_ratio
+ clamped_size.y = min_panel_size
+ clamped_size = clamped_size * editor_scale
+
+ if not is_portrait and clamped_size.x <= min_panel_size * editor_scale:
+ clamped_size.x = min_panel_size
+ clamped_size.y = min_panel_size * viewport_ratio
+ clamped_size = clamped_size * editor_scale
+
+ # Round down to avoid sub-pixel artifacts, mainly seen around the margins.
+ return clamped_size.floor()
+
+func get_project_window_size() -> Vector2:
+ var window_width = float(ProjectSettings.get_setting("display/window/size/viewport_width"))
+ var window_height = float(ProjectSettings.get_setting("display/window/size/viewport_height"))
+
+ return Vector2(window_width, window_height)
+
+func get_project_window_ratio() -> float:
+ var project_window_size = get_project_window_size()
+
+ return project_window_size.y / project_window_size.x
+
+func get_editor_viewport_size() -> Vector2:
+ var fallback_size = EditorInterface.get_editor_main_screen().size
+
+ # There isn't an API for getting the viewport node. Instead it has to be
+ # found by checking the parent's parent of the subviewport and find
+ # the correct node based on name and class.
+ var editor_sub_viewport_3d = EditorInterface.get_editor_viewport_3d(0)
+ var editor_viewport_container = editor_sub_viewport_3d.get_parent().get_parent().get_parent()
+
+ # Early return incase editor tree structure has changed.
+ if editor_viewport_container.get_class() != "Node3DEditorViewportContainer":
+ return fallback_size
+
+ return editor_viewport_container.size
+
+func _on_resize_handle_button_down() -> void:
+ if state != InteractionState.NONE: return
+
+ state = InteractionState.RESIZE
+ initial_mouse_position = get_global_mouse_position()
+ initial_panel_size = panel.size
+
+func _on_resize_handle_button_up() -> void:
+ state = InteractionState.NONE
+
+func _on_drag_handle_button_down() -> void:
+ if state != InteractionState.NONE: return
+
+ state = InteractionState.DRAG
+ initial_mouse_position = get_global_mouse_position()
+ initial_panel_position = panel.global_position
+
+func _on_drag_handle_button_up() -> void:
+ if state != InteractionState.DRAG: return
+
+ state = InteractionState.START_ANIMATE_INTO_PLACE
+
+func _on_lock_button_pressed() -> void:
+ is_locked = !is_locked
diff --git a/addons/anthonyec.camera_preview/preview.tscn b/addons/anthonyec.camera_preview/preview.tscn
new file mode 100644
index 0000000..77b3ced
--- /dev/null
+++ b/addons/anthonyec.camera_preview/preview.tscn
@@ -0,0 +1,200 @@
+[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
+
+[ext_resource type="Script" path="res://addons/anthonyec.camera_preview/preview.gd" id="1_6b32r"]
+[ext_resource type="Texture2D" uid="uid://do6d60od41vmg" path="res://addons/anthonyec.camera_preview/Pin.svg" id="2_p0pa8"]
+[ext_resource type="Texture2D" uid="uid://btc01wc11tiid" path="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg" id="2_t64ej"]
+[ext_resource type="Texture2D" uid="uid://04l05jxuyt7k" path="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg" id="3_6yuab"]
+
+[sub_resource type="ViewportTexture" id="ViewportTexture_hchdq"]
+viewport_path = NodePath("Panel/SubViewport")
+
+[sub_resource type="Gradient" id="Gradient_11p6r"]
+offsets = PackedFloat32Array(0, 0.3, 0.6, 1)
+colors = PackedColorArray(0, 0, 0, 0.235294, 0, 0, 0, 0.0784314, 0, 0, 0, 0.0784314, 0, 0, 0, 0.235294)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_4dkve"]
+gradient = SubResource("Gradient_11p6r")
+width = 256
+height = 256
+fill_to = Vector2(2.08165e-12, 1)
+
+[node name="Preview" type="Control"]
+z_index = 999
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_6b32r")
+
+[node name="Placeholder" type="Panel" parent="."]
+unique_name_in_owner = true
+visible = false
+modulate = Color(1, 1, 1, 0.705882)
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -40.0
+offset_top = -40.0
+offset_right = 410.0
+offset_bottom = 410.0
+grow_horizontal = 0
+grow_vertical = 0
+
+[node name="Panel" type="Panel" parent="."]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -520.0
+offset_top = -908.889
+offset_right = -20.0
+offset_bottom = -20.0
+grow_horizontal = 0
+grow_vertical = 0
+pivot_offset = Vector2(450, 300)
+
+[node name="SubViewport" type="SubViewport" parent="Panel"]
+unique_name_in_owner = true
+handle_input_locally = false
+gui_disable_input = true
+size_2d_override_stretch = true
+
+[node name="Camera3D" type="Camera3D" parent="Panel/SubViewport"]
+unique_name_in_owner = true
+current = true
+
+[node name="Camera2D" type="Camera2D" parent="Panel/SubViewport"]
+unique_name_in_owner = true
+ignore_rotation = false
+
+[node name="ViewportMarginContainer" type="MarginContainer" parent="Panel"]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+
+[node name="TextureRect" type="TextureRect" parent="Panel/ViewportMarginContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+texture = SubResource("ViewportTexture_hchdq")
+expand_mode = 1
+
+[node name="Gradient" type="TextureRect" parent="Panel"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+texture = SubResource("GradientTexture2D_4dkve")
+
+[node name="OverlayMarginContainer" type="MarginContainer" parent="Panel"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+
+[node name="OverlayContainer" type="Control" parent="Panel/OverlayMarginContainer"]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 2
+mouse_filter = 2
+
+[node name="DragHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+focus_mode = 0
+flat = true
+
+[node name="ResizeLeftHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+offset_right = 60.0
+offset_bottom = 60.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+mouse_default_cursor_shape = 12
+icon = ExtResource("2_t64ej")
+flat = true
+icon_alignment = 1
+expand_icon = true
+
+[node name="ResizeRightHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -60.0
+offset_bottom = 60.0
+pivot_offset = Vector2(60, 60)
+size_flags_horizontal = 8
+size_flags_vertical = 0
+mouse_default_cursor_shape = 11
+icon = ExtResource("3_6yuab")
+flat = true
+icon_alignment = 1
+expand_icon = true
+
+[node name="LockButton" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_top = -60.0
+offset_right = 60.0
+pivot_offset = Vector2(0, 60)
+size_flags_horizontal = 0
+size_flags_vertical = 8
+tooltip_text = "Always Show Preview"
+toggle_mode = true
+icon = ExtResource("2_p0pa8")
+flat = true
+icon_alignment = 1
+expand_icon = true
+
+[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_down"]
+[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_up"]
+[connection signal="renamed" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_renamed"]
+[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_down"]
+[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_up"]
+[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_down"]
+[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_up"]
+[connection signal="pressed" from="Panel/OverlayMarginContainer/OverlayContainer/LockButton" to="." method="_on_lock_button_pressed"]
diff --git a/addons/godot_debug_environments/LICENSE b/addons/godot_debug_environments/LICENSE
new file mode 100644
index 0000000..7fa92cd
--- /dev/null
+++ b/addons/godot_debug_environments/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Vladi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/godot_debug_environments/README.md b/addons/godot_debug_environments/README.md
new file mode 100644
index 0000000..d27b3a0
--- /dev/null
+++ b/addons/godot_debug_environments/README.md
@@ -0,0 +1,2 @@
+# Debug_environments
+A simple Godot addon that allows to add context (extra scene) to scenes in "Run Current Scene" mode.
diff --git a/addons/godot_debug_environments/debug_environment_editor_inspector.gd b/addons/godot_debug_environments/debug_environment_editor_inspector.gd
new file mode 100644
index 0000000..359c75b
--- /dev/null
+++ b/addons/godot_debug_environments/debug_environment_editor_inspector.gd
@@ -0,0 +1,37 @@
+@tool
+extends EditorInspectorPlugin
+
+func _can_handle(object):
+ return object is Node and object == EditorInterface.get_edited_scene_root()
+
+func _parse_begin(object):
+ add_custom_control(create_ui(object))
+
+func create_ui(node: Node) -> Control:
+ var path = DebugEnvironmentLib.get_debug_path(node)
+ var button = Button.new()
+ button.icon = load("res://addons/godot_debug_environments/flask.svg")
+ if(FileAccess.file_exists(path)):
+ button.text = "Edit testing environment"
+ button.pressed.connect(func(): edit_env(path), CONNECT_DEFERRED)
+ else:
+ button.text = "Create testing environment"
+ button.pressed.connect(func(): create_env(path), CONNECT_DEFERRED)
+ return button
+
+func edit_env(path: String):
+ EditorInterface.open_scene_from_path(path)
+
+func create_env(path: String):
+ if !DirAccess.dir_exists_absolute(path.get_base_dir()):
+ DirAccess.make_dir_recursive_absolute(path.get_base_dir())
+ var env = Node.new()
+ env.name = path.get_file().replace(".tscn", "")
+ var target = Node.new()
+ target.name = "$DEBUG_TARGET$"
+ env.add_child(target)
+ target.owner = env
+ var scene = PackedScene.new()
+ scene.pack(env)
+ ResourceSaver.save(scene, path)
+ edit_env(path)
diff --git a/addons/godot_debug_environments/debug_environment_lib.gd b/addons/godot_debug_environments/debug_environment_lib.gd
new file mode 100644
index 0000000..4b3b2c4
--- /dev/null
+++ b/addons/godot_debug_environments/debug_environment_lib.gd
@@ -0,0 +1,6 @@
+class_name DebugEnvironmentLib extends Object
+
+const debug_environment_path = "res://editor/debug_environments/debug_env_$SCENE$"
+
+static func get_debug_path(node:Node) -> String:
+ return debug_environment_path.replace("$SCENE$", node.scene_file_path.get_file())
diff --git a/addons/godot_debug_environments/debug_environment_watcher.gd b/addons/godot_debug_environments/debug_environment_watcher.gd
new file mode 100644
index 0000000..a3fcf23
--- /dev/null
+++ b/addons/godot_debug_environments/debug_environment_watcher.gd
@@ -0,0 +1,10 @@
+extends Node
+
+func _ready() -> void:
+ # Get loaded scene
+ var node = get_tree().current_scene
+ # Check if debug environment is stored
+ var path = DebugEnvironmentLib.get_debug_path(node)
+ if not FileAccess.file_exists(path): return
+ # Replace instance with debug environment
+ get_tree().call_deferred("change_scene_to_file",path)
diff --git a/addons/godot_debug_environments/flask.svg b/addons/godot_debug_environments/flask.svg
new file mode 100644
index 0000000..1c11b3d
--- /dev/null
+++ b/addons/godot_debug_environments/flask.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/godot_debug_environments/flask.svg.import b/addons/godot_debug_environments/flask.svg.import
new file mode 100644
index 0000000..8e8ae87
--- /dev/null
+++ b/addons/godot_debug_environments/flask.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://clfknatufch1h"
+path="res://.godot/imported/flask.svg-2fc53ef5b1742277dd2e351a1ac292cf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/godot_debug_environments/flask.svg"
+dest_files=["res://.godot/imported/flask.svg-2fc53ef5b1742277dd2e351a1ac292cf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/godot_debug_environments/launchSettingsBase.json b/addons/godot_debug_environments/launchSettingsBase.json
new file mode 100644
index 0000000..fef48a0
--- /dev/null
+++ b/addons/godot_debug_environments/launchSettingsBase.json
@@ -0,0 +1,19 @@
+
+{
+ "profiles": {
+ "Godot Debug": {
+ "commandName": "Executable",
+ "executablePath": "$EDITOR$",
+ "commandLineArgs": "--path . --verbose",
+ "workingDirectory": ".",
+ "nativeDebugging": true
+ },
+ "Godot Debug $NAME$": {
+ "commandName": "Executable",
+ "executablePath": "$EDITOR$",
+ "commandLineArgs": "--upwards $PATH$ --verbose",
+ "workingDirectory": ".",
+ "nativeDebugging": true
+ }
+ }
+}
diff --git a/addons/godot_debug_environments/plugin.cfg b/addons/godot_debug_environments/plugin.cfg
new file mode 100644
index 0000000..8aba179
--- /dev/null
+++ b/addons/godot_debug_environments/plugin.cfg
@@ -0,0 +1,10 @@
+[plugin]
+
+name="Debug environments"
+description="
+ A simple script that loads a given scene in a specified environment.
+ Good to test scenes in a certain context without having to manually select the right test scene.
+"
+author="Douwe Ravers"
+version="1.0"
+script="plugin.gd"
diff --git a/addons/godot_debug_environments/plugin.gd b/addons/godot_debug_environments/plugin.gd
new file mode 100644
index 0000000..20d128c
--- /dev/null
+++ b/addons/godot_debug_environments/plugin.gd
@@ -0,0 +1,13 @@
+@tool
+extends EditorPlugin
+
+var plugin
+
+func _enter_tree():
+ plugin = preload("res://addons/godot_debug_environments/debug_environment_editor_inspector.gd").new()
+ add_autoload_singleton("DebugEnvironmentWatcher", "res://addons/godot_debug_environments/debug_environment_watcher.gd")
+ add_inspector_plugin(plugin)
+
+func _exit_tree():
+ remove_autoload_singleton("DebugEnvironmentWatcher")
+ remove_inspector_plugin(plugin)
diff --git a/addons/scene-library/icons/thumb_placeholder.svg b/addons/scene-library/icons/thumb_placeholder.svg
new file mode 100644
index 0000000..d02722b
--- /dev/null
+++ b/addons/scene-library/icons/thumb_placeholder.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/addons/scene-library/icons/thumb_placeholder.svg.import b/addons/scene-library/icons/thumb_placeholder.svg.import
new file mode 100644
index 0000000..c1f4e58
--- /dev/null
+++ b/addons/scene-library/icons/thumb_placeholder.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cqln7j6oifypf"
+path="res://.godot/imported/thumb_placeholder.svg-81a80a6d7c59edb9d7ce28b01f213c5c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene-library/icons/thumb_placeholder.svg"
+dest_files=["res://.godot/imported/thumb_placeholder.svg-81a80a6d7c59edb9d7ce28b01f213c5c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/scene-library/plugin.cfg b/addons/scene-library/plugin.cfg
new file mode 100644
index 0000000..137aa95
--- /dev/null
+++ b/addons/scene-library/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Scene Library"
+description="A tool for easy work with your scenes"
+author="Mansur Isaev and contributors"
+version="0.7.0-dev"
+script="plugin.gd"
diff --git a/addons/scene-library/plugin.gd b/addons/scene-library/plugin.gd
new file mode 100644
index 0000000..3dacdb5
--- /dev/null
+++ b/addons/scene-library/plugin.gd
@@ -0,0 +1,65 @@
+# Copyright (c) 2023-2024 Mansur Isaev and contributors - MIT License
+# See `LICENSE.md` included in the source distribution for details.
+
+@tool
+extends EditorPlugin
+
+
+const SceneLibrary = preload("res://addons/scene-library/scripts/scene_library.gd")
+
+const BUTTON_NAME: String = "Scene Library"
+
+
+var _editor: SceneLibrary = null
+var _button: Button = null
+
+
+func _enter_tree() -> void:
+ _editor = SceneLibrary.new()
+ _button = add_control_to_bottom_panel(_editor, BUTTON_NAME)
+ _editor.open_asset_request.connect(_on_open_asset_request)
+ _editor.show_in_file_system_request.connect(_on_show_in_file_system_request)
+ _editor.show_in_file_manager_request.connect(_on_show_in_file_manager_request)
+ _editor.library_saved.connect(_on_library_saved)
+ _editor.library_unsaved.connect(_on_library_unsaved)
+
+ get_parent().connect(&"scene_saved", _editor.handle_scene_saved)
+ EditorInterface.get_file_system_dock().files_moved.connect(_editor.handle_file_moved)
+ EditorInterface.get_file_system_dock().file_removed.connect(_editor.handle_file_removed)
+
+
+func _exit_tree() -> void:
+ remove_control_from_bottom_panel(_editor)
+ _editor.queue_free()
+
+
+func _save_external_data() -> void:
+ if _editor.is_saved():
+ return
+
+ var library_path: String = _editor.get_current_library_path()
+ if library_path.is_empty():
+ return
+
+ _editor.save_library(library_path)
+
+
+func _on_library_saved() -> void:
+ _button.set_text(BUTTON_NAME)
+
+func _on_library_unsaved() -> void:
+ _button.set_text(BUTTON_NAME + "(*)")
+
+
+func _on_open_asset_request(path: String) -> void:
+ EditorInterface.open_scene_from_path(path)
+
+
+func _on_show_in_file_system_request(path: String) -> void:
+ EditorInterface.get_file_system_dock().navigate_to_path(path)
+
+
+func _on_show_in_file_manager_request(path: String) -> void:
+ var error := OS.shell_show_in_file_manager(ProjectSettings.globalize_path(path), true)
+ if error:
+ push_warning(error_string(error))
diff --git a/addons/scene-library/scene_library.cfg b/addons/scene-library/scene_library.cfg
new file mode 100644
index 0000000..80dbda1
--- /dev/null
+++ b/addons/scene-library/scene_library.cfg
@@ -0,0 +1,4 @@
+library=Array[Dictionary]([{
+"assets": Array[Dictionary]([]),
+"name": "Props"
+}])
diff --git a/addons/scene-library/scripts/scene_library.gd b/addons/scene-library/scripts/scene_library.gd
new file mode 100644
index 0000000..1b61730
--- /dev/null
+++ b/addons/scene-library/scripts/scene_library.gd
@@ -0,0 +1,1469 @@
+# Copyright (c) 2023-2024 Mansur Isaev and contributors - MIT License
+# See `LICENSE.md` included in the source distribution for details.
+
+extends MarginContainer
+
+
+signal library_changed
+
+signal library_unsaved
+signal library_saved
+
+signal collection_changed
+
+signal open_asset_request(path: String)
+signal show_in_file_system_request(path: String)
+signal show_in_file_manager_request(path: String)
+signal asset_display_mode_changed(display_mode: DisplayMode)
+
+
+enum CollectionTabMenu {
+ NEW,
+ RENAME,
+ DELETE,
+}
+enum LibraryMenu {
+ NEW,
+ OPEN,
+ SAVE,
+ SAVE_AS,
+}
+enum DisplayMode{
+ THUMBNAILS,
+ LIST,
+}
+enum SortMode {
+ NAME,
+ NAME_REVERSE,
+}
+enum AssetContextMenu {
+ OPEN_ASSET,
+ COPY_PATH,
+ COPY_UID,
+ DELETE_ASSET,
+ SHOW_IN_FILE_SYSTEM,
+ SHOW_IN_FILE_MANAGER,
+ REFRESH,
+ MAX,
+}
+
+
+const NULL_LIBRARY: Array[Dictionary] = []
+const NULL_COLLECTION: Dictionary = {}
+
+const THUMB_GRID_SIZE: int = 192
+const THUMB_LIST_SIZE: int = 48
+
+
+var _main_vbox: VBoxContainer = null
+
+var _collec_hbox: HBoxContainer = null
+var _collec_tab_bar: TabBar = null
+var _collec_tab_add: Button = null
+var _all_tabs_list: MenuButton = null
+var _collec_option: MenuButton = null
+
+var _main_container: PanelContainer = null
+var _content_vbox: VBoxContainer = null
+
+var _top_hbox: HBoxContainer = null
+var _asset_filter_line: LineEdit = null
+var _asset_sort_mode_btn: Button = null
+
+var _mode_thumb_btn: Button = null
+var _mode_list_btn: Button = null
+
+var _item_list: ItemList = null
+
+var _open_dialog: ConfirmationDialog = null
+var _save_dialog: ConfirmationDialog = null
+
+var _save_timer: Timer = null
+
+var _thumb_grid_icon_size: int = 64
+var _thumb_list_icon_size: int = 16
+
+# INFO: May be required for debugging.
+var _cache_enabled: bool = true
+var _cache_path: String = "res://.godot/thumb_cache"
+
+# Create thumbnail scene:
+var _viewport: SubViewport = null
+
+var _camera_2d: Camera2D = null
+
+var _camera_3d: Camera3D = null
+var _light_3d: DirectionalLight3D = null
+
+var _asset_display_mode: DisplayMode = DisplayMode.THUMBNAILS
+var _sort_mode: SortMode = SortMode.NAME
+
+var _thumbnails: Dictionary = {}
+
+var _mutex: Mutex = null
+var _thread: Thread = null
+var _thread_queue: Array[Dictionary] = []
+var _thread_sem: Semaphore = null
+var _thread_work: bool = true
+
+var _saved: bool = true
+# INFO: Use key-value pairs to store collections.
+var _curr_lib: Array[Dictionary] = NULL_LIBRARY # Array[Dictionary[StringName, ImageTexture]]
+var _curr_lib_path: String = ""
+
+var _curr_collec: Dictionary = NULL_COLLECTION
+
+
+func _update_position_new_collection_btn() -> void:
+ var tab_bar_total_width := float(_collec_tab_bar.get_theme_constant(&"h_separation"))
+ for i: int in _collec_tab_bar.get_tab_count():
+ tab_bar_total_width += _collec_tab_bar.get_tab_rect(i).size.x
+
+ _collec_tab_bar.size = Vector2(minf(_collec_tab_bar.size.x, tab_bar_total_width), 0.0)
+ _collec_tab_add.position.x = _collec_tab_bar.size.x
+
+ _all_tabs_list.set_visible(_collec_tab_bar.get_offset_buttons_visible())
+
+
+static func _def_setting(name: String, value: Variant) -> Variant:
+ if not ProjectSettings.has_setting(name):
+ ProjectSettings.set_setting(name, value)
+
+ ProjectSettings.set_initial_value(name, value)
+ return ProjectSettings.get_setting_with_override(name)
+
+@warning_ignore("narrowing_conversion", "unsafe_method_access")
+func _enter_tree() -> void:
+ _cache_enabled = _def_setting("addons/scene_library/cache/enabled", true)
+ _cache_path = _def_setting("addons/scene_library/cache/path", "res://.godot/thumb_cache")
+
+ _thumb_grid_icon_size = _def_setting("addons/scene_library/thumbnail/grid_size", 64)
+ _thumb_list_icon_size = _def_setting("addons/scene_library/thumbnail/list_size", 16)
+
+ self.add_theme_constant_override(&"margin_left", -get_theme_stylebox(&"BottomPanel", &"EditorStyles").get_margin(SIDE_LEFT))
+ self.add_theme_constant_override(&"margin_right", -get_theme_stylebox(&"BottomPanel", &"EditorStyles").get_margin(SIDE_RIGHT))
+ self.add_theme_constant_override(&"margin_top", -get_theme_stylebox(&"BottomPanel", &"EditorStyles").get_margin(SIDE_TOP))
+
+ self.set_custom_minimum_size(Vector2(0.0, 180.0))
+
+ # INFO: Required to create a tab pseudo-container background.
+ var tabbar_background := Panel.new()
+ tabbar_background.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"tabbar_background", &"TabContainer"))
+ self.add_child(tabbar_background)
+
+ _main_vbox = VBoxContainer.new()
+ _main_vbox.add_theme_constant_override(&"separation", 0)
+ self.add_child(_main_vbox)
+
+ _collec_hbox = HBoxContainer.new()
+ _collec_hbox.add_theme_constant_override(&"separation", 0)
+ # INFO: Required to calculate the position of the "new" button.
+ _collec_hbox.sort_children.connect(_update_position_new_collection_btn)
+ _main_vbox.add_child(_collec_hbox)
+
+ _collec_tab_bar = TabBar.new()
+ _collec_tab_bar.set_auto_translate(false)
+ _collec_tab_bar.set_drag_to_rearrange_enabled(true)
+ _collec_tab_bar.set_h_size_flags(Control.SIZE_EXPAND_FILL)
+ _collec_tab_bar.set_max_tab_width(256) # TODO: Make this parameter receive global editor settings.
+ _collec_tab_bar.set_theme_type_variation(&"TabContainer")
+ _collec_tab_bar.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"DebuggerPanel", &"EditorStyles"))
+ _collec_tab_bar.set_select_with_rmb(true)
+ _collec_tab_bar.add_tab("[null]")
+ _collec_tab_bar.set_tab_disabled(0, true)
+ _collec_tab_bar.set_tab_close_display_policy(TabBar.CLOSE_BUTTON_SHOW_NEVER)
+ _collec_tab_bar.tab_selected.connect(_on_collection_tab_changed)
+ _collec_tab_bar.tab_close_pressed.connect(_on_collection_tab_close_pressed)
+ _collec_tab_bar.tab_rmb_clicked.connect(_on_collection_tab_rmb_clicked)
+ _collec_tab_bar.active_tab_rearranged.connect(_on_collection_tab_rearranged)
+ _collec_hbox.add_child(_collec_tab_bar)
+
+ _collec_tab_add = Button.new()
+ _collec_tab_add.set_flat(true)
+ _collec_tab_add.set_disabled(true)
+ _collec_tab_add.set_tooltip_text("Add a new Collection.")
+ _collec_tab_add.set_button_icon(get_theme_icon(&"Add", &"EditorIcons"))
+ _collec_tab_add.add_theme_color_override(&"icon_normal_color", Color(0.6, 0.6, 0.6, 0.8))
+ _collec_tab_add.set_h_size_flags(Control.SIZE_SHRINK_END)
+ _collec_tab_add.pressed.connect(show_create_collection_dialog)
+ _collec_hbox.add_child(_collec_tab_add)
+
+ _all_tabs_list = MenuButton.new()
+ _all_tabs_list.hide()
+ _all_tabs_list.set_tooltip_text("List all tabs.")
+ _all_tabs_list.set_button_icon(get_theme_icon(&"GuiOptionArrow", &"EditorIcons"))
+ _all_tabs_list.add_theme_color_override(&"icon_normal_color", Color(0.6, 0.6, 0.6, 0.8))
+ _collec_hbox.add_child(_all_tabs_list)
+
+ var popup: PopupMenu = _all_tabs_list.get_popup()
+ popup.index_pressed.connect(_collec_tab_bar.set_current_tab)
+
+ _collec_option = MenuButton.new()
+ _collec_option.set_flat(true)
+ _collec_option.set_button_icon(get_theme_icon(&"GuiTabMenuHl", &"EditorIcons"))
+ _collec_option.add_theme_color_override(&"icon_normal_color", Color(0.6, 0.6, 0.6, 0.8))
+ _collec_hbox.add_child(_collec_option)
+
+ popup = _collec_option.get_popup()
+ popup.add_item("New Library", LibraryMenu.NEW)
+ popup.add_item("Open Library", LibraryMenu.OPEN)
+ popup.add_separator()
+ popup.add_item("Save Library", LibraryMenu.SAVE)
+ popup.add_item("Save Library As...", LibraryMenu.SAVE_AS)
+ popup.id_pressed.connect(_on_collection_option_id_pressed)
+
+ _main_container = PanelContainer.new()
+ _main_container.set_mouse_filter(Control.MOUSE_FILTER_IGNORE)
+ _main_container.set_v_size_flags(Control.SIZE_EXPAND_FILL)
+ _main_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"DebuggerPanel", &"EditorStyles"))
+ _main_vbox.add_child(_main_container)
+
+ _content_vbox = VBoxContainer.new()
+ _main_container.add_child(_content_vbox)
+
+ _top_hbox = HBoxContainer.new()
+ _content_vbox.add_child(_top_hbox)
+
+ _asset_filter_line = LineEdit.new()
+ _asset_filter_line.set_placeholder("Filter assets")
+ _asset_filter_line.set_clear_button_enabled(true)
+ _asset_filter_line.set_right_icon(get_theme_icon(&"Search", &"EditorIcons"))
+ _asset_filter_line.set_editable(false) # The value will be changed when the collection is changed.
+ _asset_filter_line.set_h_size_flags(Control.SIZE_EXPAND_FILL)
+ _asset_filter_line.text_changed.connect(_on_filter_assets_text_changed)
+ _top_hbox.add_child(_asset_filter_line)
+
+ _asset_sort_mode_btn = Button.new()
+ _asset_sort_mode_btn.set_disabled(true)
+ _asset_sort_mode_btn.set_tooltip_text("Toggle alphabetical sorting of assets")
+ _asset_sort_mode_btn.set_flat(true)
+ _asset_sort_mode_btn.set_toggle_mode(true)
+ _asset_sort_mode_btn.set_button_icon(get_theme_icon(&"Sort", &"EditorIcons"))
+ _asset_sort_mode_btn.toggled.connect(_sort_assets_button_toggled)
+ _top_hbox.add_child(_asset_sort_mode_btn)
+
+ _top_hbox.add_child(VSeparator.new())
+
+ var button_group := ButtonGroup.new()
+
+ _mode_thumb_btn = Button.new()
+ _mode_thumb_btn.set_flat(true)
+ _mode_thumb_btn.set_disabled(true)
+ _mode_thumb_btn.set_tooltip_text("View items as a grid of thumbnails.")
+ _mode_thumb_btn.set_toggle_mode(true)
+ _mode_thumb_btn.set_button_icon(get_theme_icon(&"FileThumbnail", &"EditorIcons"))
+ _mode_thumb_btn.set_button_group(button_group)
+ _mode_thumb_btn.pressed.connect(set_asset_display_mode.bind(DisplayMode.THUMBNAILS))
+ _top_hbox.add_child(_mode_thumb_btn)
+
+ _mode_list_btn = Button.new()
+ _mode_list_btn.set_flat(true)
+ _mode_list_btn.set_disabled(true)
+ _mode_list_btn.set_tooltip_text("View items as a list.")
+ _mode_list_btn.set_toggle_mode(true)
+ _mode_list_btn.set_button_icon(get_theme_icon(&"FileList", &"EditorIcons"))
+ _mode_list_btn.set_button_group(button_group)
+ _mode_list_btn.pressed.connect(set_asset_display_mode.bind(DisplayMode.LIST))
+ _top_hbox.add_child(_mode_list_btn)
+
+ _item_list = AssetItemList.new()
+ _item_list.set_focus_mode(Control.FOCUS_CLICK)
+ _item_list.set_max_columns(0)
+ _item_list.set_mouse_filter(Control.MOUSE_FILTER_PASS)
+ _item_list.set_same_column_width(true)
+ _item_list.set_select_mode(ItemList.SELECT_MULTI)
+ _item_list.set_texture_filter(CanvasItem.TEXTURE_FILTER_LINEAR)
+ _item_list.set_v_size_flags(Control.SIZE_EXPAND_FILL)
+ _item_list.gui_input.connect(_on_item_list_gui_input)
+ _item_list.item_clicked.connect(_on_item_list_item_clicked)
+ _item_list.item_activated.connect(_on_item_list_item_activated)
+ _content_vbox.add_child(_item_list)
+
+ _asset_display_mode = _def_setting("addons/scene_library/thumbnail/mode", DisplayMode.THUMBNAILS)
+ _update_asset_display_mode(_asset_display_mode)
+
+ _open_dialog = _create_file_dialog(true)
+ _open_dialog.set_title("Open Asset Library")
+ _open_dialog.connect(&"file_selected", load_library)
+ self.add_child(_open_dialog)
+
+ _save_dialog = _create_file_dialog(false)
+ _save_dialog.set_title("Save Asset Library As...")
+ _save_dialog.connect(&"file_selected", save_library)
+ self.add_child(_save_dialog)
+
+ _save_timer = Timer.new()
+ _save_timer.set_one_shot(true)
+ _save_timer.set_wait_time(10.0) # Save unsaved data every 10 seconds.
+ _save_timer.timeout.connect(_on_save_timer_timeout)
+ library_unsaved.connect(_save_timer.start)
+ self.add_child(_save_timer)
+
+ var world_2d := World2D.new()
+
+ var world_3d := World3D.new()
+ # TODO: Add a feature to change Environment.
+ world_3d.set_environment(get_viewport().get_world_3d().get_environment())
+
+ _viewport = SubViewport.new()
+ _viewport.set_world_2d(world_2d)
+ _viewport.set_world_3d(world_3d)
+ _viewport.set_update_mode(SubViewport.UPDATE_DISABLED) # We'll update the frame manually.
+ _viewport.set_debug_draw(Viewport.DEBUG_DRAW_DISABLE_LOD) # This is necessary to avoid visual glitches.
+ _viewport.set_process_mode(Node.PROCESS_MODE_DISABLED) # Needs to disable animations.
+ _viewport.set_size(Vector2i(THUMB_GRID_SIZE, THUMB_GRID_SIZE))
+ _viewport.set_disable_input(true)
+ _viewport.set_transparent_background(true)
+ _viewport.set_physics_object_picking(false)
+ _viewport.set_default_canvas_item_texture_filter(ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter"))
+ _viewport.set_default_canvas_item_texture_repeat(ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_repeat"))
+ _viewport.set_fsr_sharpness(ProjectSettings.get_setting("rendering/scaling_3d/fsr_sharpness"))
+ _viewport.set_msaa_2d(ProjectSettings.get_setting("rendering/anti_aliasing/quality/msaa_2d"))
+ _viewport.set_msaa_3d(ProjectSettings.get_setting("rendering/anti_aliasing/quality/msaa_3d"))
+ _viewport.set_positional_shadow_atlas_16_bits(ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_16_bits"))
+ _viewport.set_positional_shadow_atlas_quadrant_subdiv(0, ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_0_subdiv"))
+ _viewport.set_positional_shadow_atlas_quadrant_subdiv(1, ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_1_subdiv"))
+ _viewport.set_positional_shadow_atlas_quadrant_subdiv(2, ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv"))
+ _viewport.set_positional_shadow_atlas_quadrant_subdiv(3, ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_3_subdiv"))
+ _viewport.set_positional_shadow_atlas_size(ProjectSettings.get_setting("rendering/lights_and_shadows/positional_shadow/atlas_size"))
+ _viewport.set_scaling_3d_mode(ProjectSettings.get_setting("rendering/scaling_3d/mode"))
+ _viewport.set_scaling_3d_scale(ProjectSettings.get_setting("rendering/scaling_3d/scale"))
+ _viewport.set_screen_space_aa(ProjectSettings.get_setting("rendering/anti_aliasing/quality/screen_space_aa"))
+ _viewport.set_texture_mipmap_bias(ProjectSettings.get_setting("rendering/textures/default_filters/texture_mipmap_bias"))
+ self.add_child(_viewport)
+
+ _camera_2d = Camera2D.new()
+ _camera_2d.set_enabled(false)
+ _viewport.add_child(_camera_2d)
+
+ # TODO: Add a feature to set lighting.
+ _light_3d = DirectionalLight3D.new()
+ _light_3d.set_shadow_mode(DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS)
+ _light_3d.set_bake_mode(Light3D.BAKE_STATIC)
+ _light_3d.set_shadow(true)
+ _light_3d.basis *= Basis(Vector3.UP, deg_to_rad(45.0))
+ _light_3d.basis *= Basis(Vector3.LEFT, deg_to_rad(65.0))
+ _viewport.add_child(_light_3d)
+
+ _camera_3d = Camera3D.new()
+ _camera_3d.set_current(false)
+ _camera_3d.set_fov(22.5)
+ _viewport.add_child(_camera_3d)
+
+ # Multithreading starts here.
+ _mutex = Mutex.new()
+ _thread_sem = Semaphore.new()
+ _thread = Thread.new()
+ _thread.start(_thread_process)
+
+ library_changed.connect(update_tabs)
+
+ collection_changed.connect(update_item_list)
+ asset_display_mode_changed.connect(_update_asset_display_mode)
+
+ _curr_lib_path = _def_setting("addons/scene_library/library/current_library_path", "res://addons/scene-library/scene_library.cfg")
+ load_library(_curr_lib_path)
+
+ collection_changed.connect(_collec_tab_bar.size_flags_changed.emit)
+
+
+func _exit_tree() -> void:
+ _mutex.lock()
+ _thread_work = false
+ _mutex.unlock()
+
+ _thread_sem.post()
+ if _thread.is_started():
+ _thread.wait_to_finish()
+
+@warning_ignore("unsafe_method_access")
+func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
+ if not _item_list.get_rect().has_point(at_position):
+ return false
+
+ if not data is Dictionary or data.get("type") != "files":
+ return false
+
+ if _curr_lib.is_read_only() or _curr_collec.is_read_only():
+ return false
+
+ var files: PackedStringArray = data["files"]
+ var rec_ext: PackedStringArray = ResourceLoader.get_recognized_extensions_for_type("PackedScene")
+
+ for file: String in files:
+ var extension: String = file.get_extension().to_lower()
+ if not rec_ext.has(extension):
+ return false
+
+ if has_asset_path(file) or not is_valid_scene_file(file):
+ return false
+
+ return true
+
+
+func _drop_data(_at_position: Vector2, data: Variant) -> void:
+ if data is Dictionary:
+ var files: PackedStringArray = data["files"]
+
+ for path: String in files:
+ create_asset(path)
+
+
+func mark_saved() -> void:
+ library_saved.emit()
+ _saved = true
+
+func mark_unsaved() -> void:
+ library_unsaved.emit()
+ _saved = false
+
+func is_saved() -> bool:
+ return _saved
+
+
+func set_current_library(library: Array[Dictionary]) -> void:
+ if is_same(_curr_lib, library):
+ return
+
+ _curr_lib = library
+ library_changed.emit()
+ # Switch to the first tab.
+ _collec_tab_bar.set_current_tab(0)
+
+func get_current_library() -> Array[Dictionary]:
+ return _curr_lib
+
+
+func set_current_library_path(path: String) -> void:
+ if is_same(_curr_lib_path, path):
+ return
+
+ ProjectSettings.set_setting("addons/scene_library/library/current_library_path", path)
+ _curr_lib_path = path
+
+func get_current_library_path() -> String:
+ return _curr_lib_path
+
+
+func has_collection(collection_name: String) -> bool:
+ for collection: Dictionary in get_current_library():
+ if collection.name == collection_name:
+ return true
+
+ return false
+
+func create_collection(collection_name: String) -> void:
+ assert(not has_collection(collection_name), "Collection with this name already exists.")
+
+ var assets: Array[Dictionary] = []
+ var new_collection: Dictionary = {
+ &"name": collection_name,
+ &"assets": assets,
+ }
+
+ _curr_lib.push_back(new_collection)
+
+ library_changed.emit()
+ mark_unsaved()
+
+ # Switch to the last tab.
+ _collec_tab_bar.set_current_tab(_collec_tab_bar.get_tab_count() - 1)
+
+
+func remove_collection(index: int) -> void:
+ _curr_lib.remove_at(index)
+
+ library_changed.emit()
+ mark_unsaved()
+
+ # Swith to the prev tab.
+ _collec_tab_bar.set_current_tab(_collec_tab_bar.get_current_tab())
+
+func show_remove_collection_dialog(index: int) -> void:
+ var assets: Array[Dictionary] = _curr_lib[index].assets
+ if assets.is_empty():
+ return remove_collection(index)
+
+ var window := ConfirmationDialog.new()
+ window.set_size(Vector2i.ZERO)
+ window.set_flag(Window.FLAG_RESIZE_DISABLED, true)
+ window.focus_exited.connect(window.queue_free)
+ window.confirmed.connect(remove_collection.bind(index))
+
+ window.set_ok_button_text("Remove")
+
+ var label := Label.new()
+ label.set_text("Are you sure you want to delete this collection? (Cannot be undone.)")
+ window.add_child(label)
+
+ self.add_child(window)
+ window.popup_centered(Vector2i(300, 0))
+
+
+func _queue_has_id(id: int) -> bool:
+ _mutex.lock()
+
+ for item: Dictionary in _thread_queue:
+ if item.id == id:
+ _mutex.unlock()
+ return true
+
+ _mutex.unlock()
+ return false
+
+func _queue_update_thumbnail(id: int) -> void:
+ if not _thumbnails.has(id) or _queue_has_id(id):
+ return
+
+ _mutex.lock()
+ var queue_item: Dictionary = {&"id": id, &"thumb": _thumbnails[id]}
+ _thread_queue.push_back(queue_item)
+ _mutex.unlock()
+
+ _thread_sem.post()
+
+func _get_or_create_thumbnail(id: int, path: String) -> ImageTexture:
+ var thumb: ImageTexture = _thumbnails.get(id, null)
+ if is_instance_valid(thumb):
+ return thumb
+
+ var cache_path: String = _get_thumb_cache_path(path)
+ if _cache_enabled and FileAccess.file_exists(cache_path):
+ thumb = ImageTexture.create_from_image(Image.load_from_file(cache_path))
+ _thumbnails[id] = thumb
+ else:
+ thumb = ImageTexture.create_from_image(Image.load_from_file(ProjectSettings.globalize_path("res://addons/scene-library/icons/thumb_placeholder.svg")))
+ _thumbnails[id] = thumb
+
+ _queue_update_thumbnail(id)
+
+ return thumb
+
+func _create_asset(id: int, uid: String, path: String) -> Dictionary:
+ var asset: Dictionary = {
+ &"id": id,
+ &"uid": uid,
+ &"path": path,
+ &"thumb": _get_or_create_thumbnail(id, path),
+ }
+ return asset
+
+
+static func is_valid_scene_file(path: String) -> bool:
+ return ResourceLoader.exists(path, "PackedScene") and ResourceLoader.get_recognized_extensions_for_type("PackedScene").has(path.get_extension().to_lower())
+
+
+static func get_or_create_valid_uid(path: String) -> int:
+ var id: int = ResourceLoader.get_resource_uid(path)
+ if id == ResourceUID.INVALID_ID:
+ id = ResourceUID.create_id()
+ ResourceUID.add_id(id, path)
+
+ return id
+
+
+func create_asset(path: String) -> void:
+ assert(is_valid_scene_file(path), "PackedScene file was not found or has an invalid extension.")
+
+ var id: int = get_or_create_valid_uid(path)
+ var new_asset: Dictionary = _create_asset(id, ResourceUID.id_to_text(id), path)
+
+ var assets: Array[Dictionary] = _curr_collec.assets
+ assets.push_back(new_asset)
+
+ collection_changed.emit()
+ mark_unsaved()
+
+
+func remove_asset(id: int) -> bool:
+ var assets: Array[Dictionary] = _curr_collec.assets
+
+ for i: int in assets.size():
+ if assets[i].id != id:
+ continue
+
+ assets.remove_at(i)
+
+ collection_changed.emit()
+ mark_unsaved()
+
+ return true
+
+ return false
+
+
+func set_current_collection(collection: Dictionary) -> void:
+ if is_same(_curr_collec, collection):
+ return
+
+ _curr_collec = collection
+
+ _item_list.deselect_all()
+ collection_changed.emit()
+
+func get_current_collection() -> Dictionary:
+ return _curr_collec
+
+
+func has_asset_path(path: String) -> bool:
+ for asset: Dictionary in _curr_collec.assets:
+ if asset.path == path:
+ return true
+
+ return false
+
+
+func update_tabs() -> void:
+ var is_valid: bool = not _curr_lib.is_read_only() and not _curr_lib.is_empty()
+
+ _asset_filter_line.set_editable(is_valid)
+ _collec_tab_add.set_disabled(_curr_lib.is_read_only())
+ _asset_sort_mode_btn.set_disabled(not is_valid)
+ _mode_thumb_btn.set_disabled(not is_valid)
+ _mode_list_btn.set_disabled(not is_valid)
+
+ if _curr_lib.size():
+ _collec_tab_bar.set_tab_count(_curr_lib.size())
+ _collec_tab_bar.set_tab_close_display_policy(TabBar.CLOSE_BUTTON_SHOW_ACTIVE_ONLY)
+
+ var popup: PopupMenu = _all_tabs_list.get_popup()
+ popup.set_item_count(_curr_lib.size())
+
+ for i: int in _curr_lib.size():
+ _collec_tab_bar.set_tab_title(i, _curr_lib[i].name)
+ _collec_tab_bar.set_tab_disabled(i, false)
+ _collec_tab_bar.set_tab_metadata(i, _curr_lib[i])
+
+ popup.set_item_text(i, _curr_lib[i].name)
+ else:
+ _collec_tab_bar.set_tab_count(1)
+ _collec_tab_bar.set_tab_close_display_policy(TabBar.CLOSE_BUTTON_SHOW_NEVER)
+ _collec_tab_bar.set_tab_title(0, "[null]")
+ _collec_tab_bar.set_tab_disabled(0, true)
+ _collec_tab_bar.set_tab_metadata(0, NULL_COLLECTION)
+
+ # INFO: Required to recalculate position of the "new collection" button.
+ _collec_tab_bar.size_flags_changed.emit()
+
+@warning_ignore("unsafe_call_argument")
+func update_item_list() -> void:
+ var assets: Array[Dictionary] = _curr_collec.assets
+ _item_list.set_item_count(assets.size())
+
+ var is_list_mode: bool = _asset_display_mode == DisplayMode.LIST
+ var filter: String = _asset_filter_line.get_text()
+
+ var index: int = 0
+ for asset: Dictionary in assets:
+ var path: String = asset.path
+ if not filter.is_subsequence_ofn(path.get_file()):
+ continue
+
+ _item_list.set_item_text(index, path.get_file().get_basename())
+ _item_list.set_item_icon(index, asset.thumb)
+ # NOTE: This tooltip will be hidden because used the custom tooltip.
+ _item_list.set_item_tooltip(index, path)
+ _item_list.set_item_metadata(index, asset)
+
+ index += 1
+
+ _item_list.set_item_count(index)
+
+
+func set_asset_display_mode(display_mode: DisplayMode) -> void:
+ if is_same(_asset_display_mode, display_mode):
+ return
+
+ ProjectSettings.set_setting("addons/scene_library/thumbnail/mode", display_mode)
+ _asset_display_mode = display_mode
+
+ asset_display_mode_changed.emit(display_mode)
+
+func get_asset_display_mode() -> DisplayMode:
+ return _asset_display_mode
+
+static func sort_asset_ascending(a: Dictionary, b: Dictionary) -> bool:
+ @warning_ignore("unsafe_method_access")
+ return a.path.get_file() < b.path.get_file()
+static func sort_asset_descending(a: Dictionary, b: Dictionary) -> bool:
+ @warning_ignore("unsafe_method_access")
+ return a.path.get_file() > b.path.get_file()
+static func sort_assets(assets: Array[Dictionary], sort_mode: SortMode) -> void:
+ if sort_mode == SortMode.NAME:
+ assets.sort_custom(sort_asset_ascending)
+ else:
+ assets.sort_custom(sort_asset_descending)
+
+func set_sort_mode(sort_mode: SortMode) -> void:
+ if is_same(_sort_mode, sort_mode):
+ return
+
+ _sort_mode = sort_mode
+ sort_assets(_curr_collec.assets, sort_mode)
+
+ collection_changed.emit()
+
+func get_sort_mode() -> SortMode:
+ return _sort_mode
+
+
+func show_create_collection_dialog() -> AcceptDialog:
+ var window := AcceptDialog.new()
+ window.set_size(Vector2i.ZERO)
+ window.set_title("Create New Collection")
+ window.add_cancel_button("Cancel")
+ window.set_flag(Window.FLAG_RESIZE_DISABLED, true)
+ window.focus_exited.connect(window.queue_free)
+ self.add_child(window)
+
+ var ok_button: Button = window.get_ok_button()
+ ok_button.set_text("Create")
+ ok_button.set_disabled(true)
+
+ var vbox := VBoxContainer.new()
+ window.add_child(vbox)
+
+ var label := Label.new()
+ label.set_text("New Collection Name:")
+ vbox.add_child(label)
+
+ var line_edit := LineEdit.new()
+ window.register_text_enter(line_edit)
+ line_edit.set_text("new_collection")
+ line_edit.select_all()
+
+ # INFO: Disables the ability to create a collection and set a tooltip.
+ line_edit.text_changed.connect(func(c_name: String) -> void:
+ if c_name.is_empty():
+ line_edit.set_tooltip_text("Collection name is empty.")
+ elif has_collection(c_name):
+ line_edit.set_tooltip_text("Collection with this name already exists.")
+ else:
+ line_edit.set_tooltip_text("")
+
+ ok_button.set_disabled(c_name.is_empty() or has_collection(c_name))
+ line_edit.set_right_icon(get_theme_icon(&"StatusError", &"EditorIcons") if ok_button.is_disabled() else null)
+ )
+ line_edit.text_changed.emit(line_edit.get_text()) # Required for status updates.
+ vbox.add_child(line_edit)
+
+ window.confirmed.connect(func() -> void:
+ var new_collec_name: String = line_edit.get_text()
+ create_collection(new_collec_name)
+ )
+ window.popup_centered(Vector2i(300, 0))
+ line_edit.grab_focus()
+
+ return window
+
+
+
+func _serialize_asset(asset: Dictionary) -> Dictionary:
+ return {"uid": asset.uid, "path": asset.path}
+
+func _serialize_assets(assets: Array[Dictionary]) -> Array[Dictionary]:
+ var serialized: Array[Dictionary] = []
+ serialized.resize(assets.size())
+
+ for i: int in assets.size():
+ serialized[i] = _serialize_asset(assets[i])
+
+ return serialized
+
+func _serialize_collection(collection: Dictionary) -> Dictionary:
+ return {
+ "name": collection.name,
+ "assets": _serialize_assets(collection.assets),
+ }
+
+func _serialize_library(library: Array[Dictionary]) -> Array[Dictionary]:
+ var serialized: Array[Dictionary] = []
+ serialized.resize(library.size())
+
+ for i: int in library.size():
+ serialized[i] = _serialize_collection(library[i])
+
+ return serialized
+
+
+func _cfg_save_library(library: Array[Dictionary], path: String) -> void:
+ var serialized: Array[Dictionary] = _serialize_library(library)
+
+ var config := ConfigFile.new()
+ config.set_value("", "library", serialized)
+
+ var error := config.save(path)
+ assert(error == OK, error_string(error))
+
+func _json_save_library(library: Array[Dictionary], path: String) -> void:
+ var serialized: Array[Dictionary] = _serialize_library(library)
+
+ var file := FileAccess.open(path, FileAccess.WRITE)
+ assert(FileAccess.get_open_error() == OK, error_string(FileAccess.get_open_error()))
+
+ file.store_string(JSON.stringify(serialized, "\t"))
+ file.close()
+
+func save_library(path: String) -> void:
+ var extension: String = path.get_extension()
+ assert(extension == "cfg" or extension == "json", "Invalid extension.")
+
+ if extension == "cfg":
+ _cfg_save_library(_curr_lib, path)
+ elif extension == "json":
+ _json_save_library(_curr_lib, path)
+ else:
+ return
+
+ mark_saved()
+
+
+func _deserialize_asset(asset: Dictionary) -> Dictionary:
+ var uid: String = asset.get("uid", "")
+ var path: String = asset.get("path", "")
+
+ var id: int = ResourceUID.text_to_id(uid)
+
+ # TODO: Add error handling.
+ if id != ResourceUID.INVALID_ID and ResourceUID.has_id(id): # If the UID is valid.
+ path = ResourceUID.get_id_path(id)
+ # If the UID is wrong, try to load the asset by the path.
+ # It also checks whether the file extension is valid.
+ elif is_valid_scene_file(path):
+ id = ResourceLoader.get_resource_uid(path)
+ uid = ResourceUID.id_to_text(id)
+
+ if not ResourceUID.has_id(id):
+ ResourceUID.add_id(id, path)
+
+ # Invalid assset.
+ else:
+ return {}
+
+ return _create_asset(id, uid, path)
+
+func _deserialize_assets(assets: Array) -> Array[Dictionary]:
+ var deserialized: Array[Dictionary] = []
+
+ for asset: Dictionary in assets:
+ asset = _deserialize_asset(asset)
+ if asset.is_empty():
+ continue
+
+ deserialized.push_back(asset)
+
+ return deserialized
+
+func _deserialize_collection(collection: Dictionary) -> Dictionary:
+ var deserialized: Dictionary = {
+ &"name": collection[&"name"],
+ &"assets": _deserialize_assets(collection["assets"])
+ }
+
+ return deserialized
+
+func _deserialize_library(library: Array) -> Array[Dictionary]:
+ var deserialized: Array[Dictionary] = []
+ deserialized.resize(library.size())
+
+ for i: int in library.size():
+ deserialized[i] = _deserialize_collection(library[i])
+
+ return deserialized
+
+
+func _load_cfg(path: String) -> Array[Dictionary]:
+ var config := ConfigFile.new()
+
+ var error := config.load(path)
+ assert(error == OK, error_string(error))
+
+ var data: Variant = config.get_value("", "library")
+ if data is Array:
+ return _deserialize_library(data)
+
+ return NULL_LIBRARY
+
+func _load_json(path: String) -> Array[Dictionary]:
+ var json := JSON.new()
+
+ var error := json.parse(FileAccess.get_file_as_string(path))
+ assert(error == OK, error_string(error))
+
+ var data: Variant = json.get_data()
+ if data is Array:
+ return _deserialize_library(data)
+
+ return NULL_LIBRARY
+
+func load_library(path: String) -> void:
+ var library: Array[Dictionary] = []
+
+ if FileAccess.file_exists(path):
+ var extension: String = path.get_extension()
+ assert(extension == "cfg" or extension == "json", "Invalid extension.")
+
+ if extension == "cfg":
+ library = _load_cfg(path)
+ elif extension == "json":
+ library = _load_json(path)
+
+ # Check for “null” value.
+ if library.is_read_only():
+ return
+
+ set_current_library(library)
+ set_current_library_path(path)
+
+
+@warning_ignore("unsafe_method_access")
+func _calculate_node_rect(node: Node) -> Rect2:
+ var rect := Rect2()
+ if node is Node2D and node.is_visible():
+ # HACK: This works only in editor.
+ rect = node.get_global_transform() * node.call(&"_edit_get_rect")
+
+ for i: int in node.get_child_count():
+ rect = rect.merge(_calculate_node_rect(node.get_child(i)))
+
+ return rect
+
+@warning_ignore("unsafe_method_access")
+func _calculate_node_aabb(node: Node) -> AABB:
+ var aabb := AABB()
+
+ if node is Node3D and not node.is_visible():
+ return aabb
+ # NOTE: If the node is not MeshInstance3D, the AABB is not calculated correctly.
+ # The camera may have incorrect distances to objects in the scene.
+ elif node is MeshInstance3D:
+ aabb = node.get_global_transform() * node.get_aabb()
+
+ for i: int in node.get_child_count():
+ aabb = aabb.merge(_calculate_node_aabb(node.get_child(i)))
+
+ return aabb
+
+
+func _focus_camera_on_node_2d(node: Node) -> void:
+ var rect: Rect2 = _calculate_node_rect(node)
+ _camera_2d.set_position(rect.get_center())
+
+ var zoom_ratio: float = THUMB_GRID_SIZE / maxf(rect.size.x, rect.size.y)
+ _camera_2d.set_zoom(Vector2(zoom_ratio, zoom_ratio))
+
+func _focus_camera_on_node_3d(node: Node) -> void:
+ var transform := Transform3D.IDENTITY
+ # TODO: Add a feature to configure the rotation of the camera.
+ transform.basis *= Basis(Vector3.UP, deg_to_rad(40.0))
+ transform.basis *= Basis(Vector3.LEFT, deg_to_rad(22.5))
+
+ var aabb: AABB = _calculate_node_aabb(node)
+ var distance: float = aabb.get_longest_axis_size() / tan(deg_to_rad(_camera_3d.get_fov()) * 0.5)
+
+ transform.origin = transform * (Vector3.BACK * distance) + aabb.get_center()
+
+ _camera_3d.set_global_transform(transform.orthonormalized())
+
+
+func _get_thumb_cache_dir() -> String:
+ return ProjectSettings.globalize_path(_cache_path)
+
+func _get_thumb_cache_path(path: String) -> String:
+ return _get_thumb_cache_dir().path_join(path.md5_text()) + ".png"
+
+func _save_thumb_to_disk(id: int, image: Image) -> void:
+ if not DirAccess.dir_exists_absolute(_get_thumb_cache_dir()):
+ var error := DirAccess.make_dir_absolute(_get_thumb_cache_dir())
+ assert(error == OK, error_string(error))
+
+ var error := image.save_png(_get_thumb_cache_path(ResourceUID.get_id_path(id)))
+ assert(error == OK, error_string(error))
+
+func _create_thumb(item: Dictionary, callback: Callable) -> void:
+ var path: String = ResourceUID.get_id_path(item.id)
+ if not is_valid_scene_file(path):
+ return callback.call()
+
+ var packed_scene := ResourceLoader.load(path, "PackedScene") as PackedScene
+ # INFO: Could be null if, for example, the dependencies are broken.
+ if not is_instance_valid(packed_scene) or not packed_scene.can_instantiate():
+ return callback.call()
+
+ var instance: Node = packed_scene.instantiate()
+
+ _viewport.call_deferred(&"add_child", instance)
+ await instance.ready
+
+ if instance is Node2D:
+ _camera_3d.set_current(false)
+ _camera_2d.set_enabled(true)
+ _focus_camera_on_node_2d(instance)
+ else:
+ _camera_2d.set_enabled(false)
+ _camera_3d.set_current(true)
+ _focus_camera_on_node_3d(instance)
+
+ await RenderingServer.frame_pre_draw
+ _viewport.set_update_mode(SubViewport.UPDATE_ONCE)
+
+ await RenderingServer.frame_post_draw
+
+ var image: Image = _viewport.get_texture().get_image()
+ image.resize(THUMB_GRID_SIZE, THUMB_GRID_SIZE, Image.INTERPOLATE_LANCZOS)
+
+ var thumb: ImageTexture = item.thumb
+ thumb.update(image)
+
+ if _cache_enabled:
+ _save_thumb_to_disk(item.id, image)
+
+ instance.call_deferred(&"free")
+ await instance.tree_exited
+
+ callback.call()
+
+func _thread_process() -> void:
+ var semaphore := Semaphore.new()
+
+ while _thread_work:
+ if _thread_queue.is_empty():
+ _thread_sem.wait()
+ else:
+ _mutex.lock()
+ var item: Dictionary = _thread_queue.pop_front()
+ _mutex.unlock()
+
+ # This ensures that this method will be executed in the main thread.
+ call_deferred_thread_group(&"_create_thumb", item, semaphore.post)
+ semaphore.wait()
+
+
+
+
+func handle_scene_saved(path: String) -> void:
+ # INFO: When we save a scene, we try to update the asset thumbnail.
+ # The "_queue_update_thumbnail" method will not create new thumbnails if they have not been previously created.
+ _queue_update_thumbnail(ResourceLoader.get_resource_uid(path))
+
+
+func handle_file_moved(old_file: String, new_file: String) -> void:
+ if not _thumbnails.has(ResourceLoader.get_resource_uid(new_file)):
+ return
+
+ for collection: Dictionary in _curr_lib:
+ for asset: Dictionary in collection.assets:
+ if asset.path == old_file:
+ asset.path = new_file
+ break
+
+ collection_changed.emit()
+
+
+func handle_file_removed(file: String) -> void:
+ # TODO: Need to add Dictionary for asset path.
+ # Because we can't use UID for deleted files.
+ # And we have to go through all collections and assets.
+ var removed: int = 0
+ for collection: Dictionary in _curr_lib:
+ var assets: Array[Dictionary] = collection.assets
+
+ for i: int in assets.size():
+ if assets[i].path != file:
+ continue
+
+ assets.remove_at(i)
+ removed += 1
+ break
+
+ if removed:
+ collection_changed.emit()
+
+
+
+
+func _on_collection_tab_changed(tab: int) -> void:
+ set_current_collection(_collec_tab_bar.get_tab_metadata(tab))
+
+
+func _on_collection_tab_close_pressed(tab: int) -> void:
+ show_remove_collection_dialog(tab)
+
+
+func _on_collection_tab_rmb_clicked(tab: int) -> void:
+ var collection: Dictionary = _collec_tab_bar.get_tab_metadata(tab)
+
+ var popup := PopupMenu.new()
+ popup.id_pressed.connect(func(option: CollectionTabMenu) -> void:
+ match option:
+ CollectionTabMenu.NEW:
+ show_create_collection_dialog()
+
+ CollectionTabMenu.RENAME:
+ var old_name: String = collection.name
+
+ var rename_collec_window := AcceptDialog.new()
+ rename_collec_window.set_size(Vector2i.ZERO)
+ rename_collec_window.set_title("Rename Collection")
+ rename_collec_window.add_cancel_button("Cancel")
+ rename_collec_window.set_flag(Window.FLAG_RESIZE_DISABLED, true)
+ rename_collec_window.focus_exited.connect(rename_collec_window.queue_free)
+
+ var ok_button: Button = rename_collec_window.get_ok_button()
+ ok_button.set_text("OK")
+ ok_button.set_disabled(true)
+
+ var vbox := VBoxContainer.new()
+ rename_collec_window.add_child(vbox)
+
+ var label := Label.new()
+ label.set_text("Change Collection Name:")
+ vbox.add_child(label)
+
+ var line_edit := LineEdit.new()
+ line_edit.set_select_all_on_focus(true)
+ line_edit.set_text(old_name)
+ rename_collec_window.register_text_enter(line_edit)
+
+ # INFO: Disables the ability to create a collection and set a tooltip.
+ line_edit.text_changed.connect(func(new_name: String) -> void:
+ var is_valid := false
+
+ if new_name.is_empty():
+ line_edit.set_tooltip_text("Collection name is empty.")
+ elif has_collection(new_name):
+ line_edit.set_tooltip_text("Collection with this name already exists.")
+ else:
+ line_edit.set_tooltip_text("")
+ is_valid = true
+
+ ok_button.set_disabled(not is_valid)
+ line_edit.set_right_icon(null if is_valid else get_theme_icon(&"StatusError", &"EditorIcons"))
+ )
+
+ line_edit.text_changed.emit(line_edit.get_text()) # Required for update status.
+ vbox.add_child(line_edit)
+
+ rename_collec_window.confirmed.connect(func() -> void:
+ collection.name = line_edit.get_text()
+ _collec_tab_bar.set_tab_title(tab, line_edit.get_text())
+ mark_unsaved()
+ )
+
+ self.add_child(rename_collec_window)
+ rename_collec_window.popup_centered(Vector2i(300, 0))
+ line_edit.grab_focus()
+
+ CollectionTabMenu.DELETE:
+ show_remove_collection_dialog(tab)
+ )
+ popup.focus_exited.connect(popup.queue_free)
+ self.add_child(popup)
+
+ if collection.is_read_only(): # If "null" collection.
+ # BUG: You can't see it because the tab is disabled.
+ popup.add_item("New Collection", CollectionTabMenu.NEW)
+ else:
+ popup.add_item("New Collection", CollectionTabMenu.NEW)
+ popup.add_separator()
+ popup.add_item("Rename Collection", CollectionTabMenu.RENAME)
+ popup.add_item("Delete Collection", CollectionTabMenu.DELETE)
+
+ popup.popup(Rect2i(get_screen_position() + get_local_mouse_position(), Vector2i.ZERO))
+
+
+func _on_collection_tab_rearranged(_to_idx: int) -> void:
+ for i: int in _collec_tab_bar.get_tab_count():
+ _curr_lib[i] = _collec_tab_bar.get_tab_metadata(i)
+
+
+func _create_file_dialog(open: bool) -> ConfirmationDialog:
+ var dialog: ConfirmationDialog = null
+
+ if Engine.is_editor_hint(): # Works only in the editor.
+ var editor_file_dialog: EditorFileDialog = ClassDB.instantiate(&"EditorFileDialog")
+ editor_file_dialog.set_access(EditorFileDialog.ACCESS_FILESYSTEM)
+ editor_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_OPEN_FILE if open else EditorFileDialog.FILE_MODE_SAVE_FILE)
+ editor_file_dialog.add_filter("*.cfg", "Config File")
+ editor_file_dialog.add_filter("*.json", "JSON File")
+ dialog = editor_file_dialog
+ else:
+ var file_dialog := FileDialog.new()
+ file_dialog.set_access(FileDialog.ACCESS_FILESYSTEM)
+ file_dialog.set_file_mode(FileDialog.FILE_MODE_OPEN_FILE if open else FileDialog.FILE_MODE_SAVE_FILE)
+ file_dialog.add_filter("*.cfg", "Config File")
+ file_dialog.add_filter("*.json", "JSON File")
+ dialog = file_dialog
+
+ dialog.set_exclusive(true)
+
+ return dialog
+
+func _popup_file_dialog(window: Window) -> void:
+ window.popup_centered_clamped(Vector2(1050, 700) * DisplayServer.screen_get_scale(), 0.8)
+
+func _on_collection_option_id_pressed(option: LibraryMenu) -> void:
+ match option:
+ # TODO: Add a feature to check if the current library is saved.
+ LibraryMenu.NEW:
+ var new_library: Array[Dictionary] = []
+ set_current_library(new_library)
+
+ _curr_lib_path = ""
+
+ LibraryMenu.OPEN:
+ _popup_file_dialog(_open_dialog)
+
+ LibraryMenu.SAVE when not _curr_lib_path.is_empty():
+ save_library(_curr_lib_path)
+
+ LibraryMenu.SAVE, LibraryMenu.SAVE_AS:
+ _popup_file_dialog(_save_dialog)
+
+
+func _on_filter_assets_text_changed(_filter: String) -> void:
+ update_item_list()
+
+
+func _sort_assets_button_toggled(reverse: bool) -> void:
+ set_sort_mode(SortMode.NAME_REVERSE if reverse else SortMode.NAME)
+
+
+func _update_thumb_icon_size(display_mode: DisplayMode) -> void:
+ if display_mode == DisplayMode.THUMBNAILS:
+ _item_list.set_fixed_column_width(_thumb_grid_icon_size * 1.5)
+ _item_list.set_fixed_icon_size(Vector2i(_thumb_grid_icon_size, _thumb_grid_icon_size))
+ else:
+ _item_list.set_fixed_column_width(0)
+ _item_list.set_fixed_icon_size(Vector2i(_thumb_list_icon_size, _thumb_list_icon_size))
+
+func _update_asset_display_mode(display_mode: DisplayMode) -> void:
+ if display_mode == DisplayMode.THUMBNAILS:
+ _item_list.set_max_columns(0)
+ _item_list.set_icon_mode(ItemList.ICON_MODE_TOP)
+ _item_list.set_max_text_lines(2)
+
+ _mode_thumb_btn.set_pressed_no_signal(true)
+ else:
+ _item_list.set_max_columns(0)
+ _item_list.set_icon_mode(ItemList.ICON_MODE_LEFT)
+ _item_list.set_max_text_lines(1)
+
+ _mode_list_btn.set_pressed_no_signal(true)
+
+ for i: int in _item_list.get_item_count():
+ var asset: Dictionary = _item_list.get_item_metadata(i)
+ _item_list.set_item_icon(i, asset.thumb)
+
+ _update_thumb_icon_size(display_mode)
+
+
+func _set_thumb_grid_icon_size(icon_size: int) -> void:
+ icon_size = clampi(icon_size, THUMB_LIST_SIZE, THUMB_GRID_SIZE)
+ if _thumb_grid_icon_size == icon_size:
+ return
+
+ ProjectSettings.set_setting("addons/scene_library/thumbnail/grid_size", icon_size)
+ _thumb_grid_icon_size = icon_size
+
+ _update_thumb_icon_size(_asset_display_mode)
+
+func _set_thumb_list_icon_size(icon_size: int) -> void:
+ const ICON_MIN_SIZE = 16
+
+ icon_size = clampi(icon_size, ICON_MIN_SIZE, THUMB_LIST_SIZE)
+ if _thumb_list_icon_size == icon_size:
+ return
+
+ ProjectSettings.set_setting("addons/scene_library/thumbnail/list_size", icon_size)
+ _thumb_list_icon_size = icon_size
+
+ _update_thumb_icon_size(_asset_display_mode)
+
+
+func _on_item_list_gui_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton and event.is_pressed() and event.is_command_or_control_pressed():
+ const ICON_GRID_STEP = 8
+ const ICON_LIST_STEP = 4
+
+ match event.get_button_index():
+ MOUSE_BUTTON_WHEEL_UP:
+ if _asset_display_mode == DisplayMode.THUMBNAILS:
+ _set_thumb_grid_icon_size(_thumb_grid_icon_size + ICON_GRID_STEP)
+ else:
+ _set_thumb_list_icon_size(_thumb_list_icon_size + ICON_LIST_STEP)
+
+ MOUSE_BUTTON_WHEEL_DOWN:
+ if _asset_display_mode == DisplayMode.THUMBNAILS:
+ _set_thumb_grid_icon_size(_thumb_grid_icon_size - ICON_GRID_STEP)
+ else:
+ _set_thumb_list_icon_size(_thumb_list_icon_size - ICON_LIST_STEP)
+
+ _:
+ return
+
+ accept_event()
+
+func _on_item_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
+ if mouse_button_index != MOUSE_BUTTON_RIGHT:
+ return
+
+ _item_list.select(index, false)
+ var selected_assets: PackedInt32Array = _item_list.get_selected_items()
+
+ var popup := PopupMenu.new()
+ popup.connect(&"focus_exited", popup.queue_free)
+ popup.connect(&"id_pressed", func(option: AssetContextMenu) -> void:
+ var asset: Dictionary = _item_list.get_item_metadata(selected_assets[0])
+
+ match option:
+ AssetContextMenu.OPEN_ASSET:
+ open_asset_request.emit(asset.path)
+
+ AssetContextMenu.COPY_PATH:
+ DisplayServer.clipboard_set(asset.path)
+
+ AssetContextMenu.COPY_UID:
+ DisplayServer.clipboard_set(asset.uid)
+
+ AssetContextMenu.DELETE_ASSET:
+ var assets: Array[Dictionary] = _curr_collec.assets
+
+ if selected_assets.size() == 1:
+ assets.remove_at(selected_assets[0])
+ else:
+ selected_assets.reverse()
+
+ for i: int in selected_assets:
+ assets.remove_at(i)
+
+ collection_changed.emit()
+ mark_unsaved()
+
+ AssetContextMenu.SHOW_IN_FILE_SYSTEM:
+ show_in_file_system_request.emit(asset.path)
+
+ AssetContextMenu.SHOW_IN_FILE_MANAGER:
+ show_in_file_manager_request.emit(asset.path)
+
+ AssetContextMenu.REFRESH:
+ for i: int in selected_assets:
+ asset = _item_list.get_item_metadata(i)
+ _queue_update_thumbnail(asset.id)
+ )
+ self.add_child(popup)
+
+ if selected_assets.size() == 1: # If only one asset is selected.
+ popup.add_item("Open", AssetContextMenu.OPEN_ASSET)
+ popup.set_item_icon(-1, get_theme_icon(&"Load", &"EditorIcons"))
+ popup.add_separator()
+ popup.add_item("Copy Path", AssetContextMenu.COPY_PATH)
+ popup.set_item_icon(-1, get_theme_icon(&"ActionCopy", &"EditorIcons"))
+ popup.add_item("Copy UID", AssetContextMenu.COPY_UID)
+ popup.set_item_icon(-1, get_theme_icon(&"Instance", &"EditorIcons"))
+ popup.add_item("Delete", AssetContextMenu.DELETE_ASSET)
+ popup.set_item_icon(-1, get_theme_icon(&"Remove", &"EditorIcons"))
+ popup.add_separator()
+ popup.add_item("Show in FileSystem", AssetContextMenu.SHOW_IN_FILE_SYSTEM)
+ popup.set_item_icon(-1, get_theme_icon(&"Filesystem", &"EditorIcons"))
+ popup.add_item("Show in File Manager", AssetContextMenu.SHOW_IN_FILE_MANAGER)
+ popup.set_item_icon(-1, get_theme_icon(&"Folder", &"EditorIcons"))
+ popup.add_separator()
+ popup.add_item("Refresh", AssetContextMenu.REFRESH)
+ popup.set_item_icon(-1, get_theme_icon(&"Reload", &"EditorIcons"))
+ else: # If many assets are selected.
+ popup.add_item("Delete", AssetContextMenu.DELETE_ASSET)
+ popup.set_item_icon(popup.get_item_index(AssetContextMenu.DELETE_ASSET), get_theme_icon(&"Remove", &"EditorIcons"))
+ popup.add_item("Refresh", AssetContextMenu.REFRESH)
+ popup.set_item_icon(-1, get_theme_icon(&"Reload", &"EditorIcons"))
+
+ popup.popup(Rect2i(_item_list.get_screen_position() + at_position, Vector2i.ZERO))
+
+
+func _on_item_list_item_activated(index: int) -> void:
+ var asset: Dictionary = _item_list.get_item_metadata(index)
+ open_asset_request.emit(asset.path)
+
+
+func _on_save_timer_timeout() -> void:
+ if _curr_lib_path.is_empty():
+ return
+
+ save_library(_curr_lib_path)
+
+
+
+
+class AssetItemList extends ItemList:
+ func _gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed(&"ui_text_select_all"):
+ for i: int in get_item_count():
+ select(i, false)
+
+ accept_event()
+
+ func _create_drag_preview(files: PackedStringArray) -> Control:
+ const MAX_ROWS = 6
+
+ var vbox := VBoxContainer.new()
+ var num_rows := mini(files.size(), MAX_ROWS)
+
+ for i: int in num_rows:
+ var hbox := HBoxContainer.new()
+ vbox.add_child(hbox)
+
+ var icon := TextureRect.new()
+ icon.set_texture(get_theme_icon(&"File", &"EditorIcons"))
+ icon.set_stretch_mode(TextureRect.STRETCH_KEEP_CENTERED)
+ icon.set_size(Vector2(16.0, 16.0))
+ hbox.add_child(icon)
+
+ var label := Label.new()
+ label.set_text(files[i].get_file().get_basename())
+ hbox.add_child(label)
+
+ if files.size() > num_rows:
+ var label := Label.new()
+ label.set_text("%d more files" % int(files.size() - num_rows))
+ vbox.add_child(label)
+
+ return vbox
+
+ func _get_drag_data(at_position: Vector2) -> Variant:
+ var item: int = get_item_at_position(at_position)
+ if item < 0:
+ return null
+
+ var files := PackedStringArray()
+ for i: int in get_selected_items():
+ var asset: Dictionary = get_item_metadata(i)
+ files.push_back(asset.path)
+
+ set_drag_preview(_create_drag_preview(files))
+
+ return {"type": "files", "files": files}
+
+ func _make_custom_tooltip(_for_text: String) -> Object:
+ var item: int = get_item_at_position(get_local_mouse_position())
+ if item < 0:
+ return null
+
+ var asset: Dictionary = get_item_metadata(item)
+ if asset.is_empty():
+ return null
+
+ var vbox := VBoxContainer.new()
+
+ var thumb_rect := TextureRect.new()
+ thumb_rect.set_expand_mode(TextureRect.EXPAND_IGNORE_SIZE)
+ thumb_rect.set_h_size_flags(Control.SIZE_SHRINK_CENTER)
+ thumb_rect.set_v_size_flags(Control.SIZE_SHRINK_CENTER)
+ thumb_rect.set_custom_minimum_size(Vector2(THUMB_GRID_SIZE, THUMB_GRID_SIZE))
+ thumb_rect.set_texture(asset.thumb)
+ vbox.add_child(thumb_rect)
+
+ var label := Label.new()
+ label.set_text(asset.path)
+ vbox.add_child(label)
+
+ return vbox
diff --git a/editor/debug_environments/debug_env_player.tscn b/editor/debug_environments/debug_env_player.tscn
new file mode 100644
index 0000000..71e1f66
--- /dev/null
+++ b/editor/debug_environments/debug_env_player.tscn
@@ -0,0 +1,39 @@
+[gd_scene load_steps=8 format=3 uid="uid://y6j3eke6bhws"]
+
+[ext_resource type="Script" path="res://scenes/game.gd" id="1_xginw"]
+[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_qkysy"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tx3qi"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_mmhs2"]
+sky_material = SubResource("ProceduralSkyMaterial_tx3qi")
+
+[sub_resource type="Environment" id="Environment_r8f2r"]
+background_mode = 2
+sky = SubResource("Sky_mmhs2")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ob58c"]
+albedo_color = Color(0, 0.46, 0.145667, 1)
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_alxwk"]
+material = SubResource("StandardMaterial3D_ob58c")
+size = Vector2(20, 20)
+
+[node name="debug_env_player" type="Node3D"]
+script = ExtResource("1_xginw")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
+shadow_enabled = true
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_r8f2r")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+mesh = SubResource("PlaneMesh_alxwk")
+
+[node name="Player" parent="." instance=ExtResource("2_qkysy")]
diff --git a/entities/faction/enemy/enemy.gd b/entities/faction/enemy/enemy.gd
new file mode 100644
index 0000000..3cfaa07
--- /dev/null
+++ b/entities/faction/enemy/enemy.gd
@@ -0,0 +1,11 @@
+extends Faction
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta: float) -> void:
+ pass
diff --git a/entities/faction/faction.gd b/entities/faction/faction.gd
new file mode 100644
index 0000000..b168206
--- /dev/null
+++ b/entities/faction/faction.gd
@@ -0,0 +1,13 @@
+class_name Faction extends Node
+
+var buildings:Array[Node3D]:
+ get:
+ var buildings:Array[Node3D]
+ buildings.assign($Buildings.get_children())
+ return buildings
+
+var units:Array[Node3D]:
+ get:
+ var buildings:Array[Node3D]
+ buildings.assign($Units.get_children())
+ return buildings
diff --git a/entities/faction/fog_of_war.gd b/entities/faction/fog_of_war.gd
new file mode 100644
index 0000000..fe4fe6d
--- /dev/null
+++ b/entities/faction/fog_of_war.gd
@@ -0,0 +1,47 @@
+class_name FogOfWar extends Node
+
+@export var visual:bool = true
+@export var width:int = 20
+@export var height:int = 20
+
+var size:int:
+ get: return width*height
+
+var faction:Faction:
+ get: return get_parent() as Faction
+
+var grid:Array[bool] = []
+
+func _ready() -> void:
+ if not visual: $FogOfWarVisual.queue_free()
+ generate_new_grid()
+ visualize_grid()
+
+func _on_tick() -> void:
+ update_fog_of_war()
+ visualize_grid()
+
+func generate_new_grid()->void:
+ grid.resize(size)
+ for i in range(size):
+ grid[i] = true
+
+func visualize_grid()->void:
+ if not has_node("FogOfWarVisual"): return
+ var fog_of_war_visual := $FogOfWarVisual as GridMap
+ for i in range(size):
+ var item := 0 if grid[i] else -1
+ fog_of_war_visual.set_cell_item(c2p(i2c(i)), item)
+
+func update_fog_of_war()->void:
+ var entities := faction.buildings + faction.units
+ for i in range(size):
+ grid[i] = grid[i] and not entities.map(
+ func(entity:Node3D)->bool:
+ return entity.position.distance_squared_to(c2p(i2c(i))) < 10
+ ).has(true)
+
+# Converters from coordinates to index values
+func c2i(coor:Vector2i)->int: return coor.x+coor.y*width
+func i2c(index:int)->Vector2i: return Vector2i(index%width, index/width)
+func c2p(coor:Vector2i)->Vector3i: return Vector3i(coor.x-width/2, 0, coor.y-height/2)
diff --git a/entities/faction/fog_of_war.tscn b/entities/faction/fog_of_war.tscn
new file mode 100644
index 0000000..8169b03
--- /dev/null
+++ b/entities/faction/fog_of_war.tscn
@@ -0,0 +1,18 @@
+[gd_scene load_steps=3 format=3 uid="uid://ch7ug2prgwyyn"]
+
+[ext_resource type="Script" path="res://entities/faction/fog_of_war.gd" id="1_bm7vn"]
+[ext_resource type="MeshLibrary" uid="uid://cesuajklj4ds4" path="res://resources/meshlib.tres" id="2_5docv"]
+
+[node name="FogOfWar" type="Node"]
+script = ExtResource("1_bm7vn")
+
+[node name="TickTimer" type="Timer" parent="."]
+wait_time = 0.5
+autostart = true
+
+[node name="FogOfWarVisual" type="GridMap" parent="."]
+mesh_library = ExtResource("2_5docv")
+cell_size = Vector3(1, 1, 1)
+metadata/_editor_floor_ = Vector3(0, 0, 0)
+
+[connection signal="timeout" from="TickTimer" to="." method="_on_tick"]
diff --git a/entities/faction/player/player.gd b/entities/faction/player/player.gd
new file mode 100644
index 0000000..3cfaa07
--- /dev/null
+++ b/entities/faction/player/player.gd
@@ -0,0 +1,11 @@
+extends Faction
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta: float) -> void:
+ pass
diff --git a/entities/faction/player/player_camera.gd b/entities/faction/player/player_camera.gd
new file mode 100644
index 0000000..84ff26d
--- /dev/null
+++ b/entities/faction/player/player_camera.gd
@@ -0,0 +1,33 @@
+extends Camera3D
+
+var move_speed : float = 5
+var tilt_speed : float = 2
+
+func _process(delta: float) -> void:
+ process_movement_speed()
+ process_movement(delta)
+ process_camera_tilt(delta)
+
+func process_movement(delta:float) -> void:
+ var input_vector := Vector3.ZERO;
+ input_vector += Vector3.UP * Input.get_axis("move_down", "move_up")
+ input_vector += Vector3.FORWARD * Input.get_axis("move_back", "move_forward")
+ input_vector += Vector3.RIGHT * Input.get_axis("move_left", "move_right")
+ translate(input_vector * move_speed * delta)
+ position.y = clampf(position.y, 2, 20)
+
+func process_movement_speed() -> void:
+ if Input.is_action_just_released("move_increase"):
+ move_speed = clampf(move_speed + 1, 1, 10)
+ if Input.is_action_just_released("move_decrease"):
+ move_speed = clampf(move_speed-1, 1, 10)
+
+func process_camera_tilt(delta: float) -> void:
+ if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ var mouse_move := -Input.get_last_mouse_velocity().normalized()
+ mouse_move *= tilt_speed * delta
+ rotation.y = rotation.y + mouse_move.x
+ rotation.x =clampf(rotation.x + mouse_move.y, -PI/2, 0)
+ else:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
diff --git a/project.godot b/project.godot
index 9c9e575..7c7916f 100644
--- a/project.godot
+++ b/project.godot
@@ -11,5 +11,72 @@ config_version=5
[application]
config/name="RomAnts"
+run/main_scene="res://scenes/game.tscn"
config/features=PackedStringArray("4.3")
config/icon="res://icon.svg"
+
+[autoload]
+
+DebugEnvironmentWatcher="*res://addons/godot_debug_environments/debug_environment_watcher.gd"
+
+[debug]
+
+gdscript/warnings/untyped_declaration=2
+gdscript/warnings/unsafe_property_access=1
+gdscript/warnings/unsafe_method_access=1
+gdscript/warnings/unsafe_cast=1
+gdscript/warnings/unsafe_call_argument=1
+gdscript/warnings/return_value_discarded=1
+
+[editor]
+
+movie_writer/movie_file="D:/Projects/movie.avi"
+
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/anthonyec.camera_preview/plugin.cfg", "res://addons/godot_debug_environments/plugin.cfg", "res://addons/scene-library/plugin.cfg")
+
+[input]
+
+move_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
+]
+}
+move_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
+]
+}
+move_forward={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
+]
+}
+move_back={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
+]
+}
+move_left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
+]
+}
+move_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
+]
+}
+move_increase={
+"deadzone": 0.5,
+"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
+]
+}
+move_decrease={
+"deadzone": 0.5,
+"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
+]
+}
diff --git a/resources/meshlib.tres b/resources/meshlib.tres
new file mode 100644
index 0000000..38e5441
--- /dev/null
+++ b/resources/meshlib.tres
@@ -0,0 +1,28 @@
+[gd_resource type="MeshLibrary" load_steps=5 format=3 uid="uid://cesuajklj4ds4"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1o0cn"]
+albedo_color = Color(0.22, 0.22, 0.22, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_solw3"]
+material = SubResource("StandardMaterial3D_1o0cn")
+
+[sub_resource type="Image" id="Image_lmm1c"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 67, 67, 67, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 66, 66, 66, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 65, 65, 65, 255, 65, 65, 65, 255, 65, 65, 65, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 33, 33, 33, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 61, 61, 61, 255, 62, 62, 62, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 62, 62, 62, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 34, 34, 34, 255, 62, 62, 62, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 255, 34, 34, 34, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 64,
+"mipmaps": false,
+"width": 64
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_aro7y"]
+image = SubResource("Image_lmm1c")
+
+[resource]
+item/0/name = "MeshInstance3D"
+item/0/mesh = SubResource("BoxMesh_solw3")
+item/0/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+item/0/shapes = []
+item/0/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+item/0/navigation_layers = 1
+item/0/preview = SubResource("ImageTexture_aro7y")
diff --git a/resources/meshlib.tscn b/resources/meshlib.tscn
new file mode 100644
index 0000000..87d1e8a
--- /dev/null
+++ b/resources/meshlib.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=3 format=3 uid="uid://bucaq3d3kowwa"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1o0cn"]
+albedo_color = Color(0.22, 0.22, 0.22, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_dj03r"]
+material = SubResource("StandardMaterial3D_1o0cn")
+
+[node name="Node3D" type="Node3D"]
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+mesh = SubResource("BoxMesh_dj03r")
diff --git a/scenes/gam5CAE.tmp b/scenes/gam5CAE.tmp
new file mode 100644
index 0000000..6f3fefc
--- /dev/null
+++ b/scenes/gam5CAE.tmp
@@ -0,0 +1,159 @@
+[gd_scene load_steps=17 format=3 uid="uid://rnmcx0o0hdgb"]
+
+[ext_resource type="Script" path="res://scenes/game.gd" id="1_3n5gp"]
+[ext_resource type="Script" path="res://entities/faction/player/player_camera.gd" id="2_1etxd"]
+[ext_resource type="Script" path="res://entities/faction/player/player.gd" id="2_ba5k2"]
+[ext_resource type="MeshLibrary" uid="uid://cesuajklj4ds4" path="res://resources/meshlib.tres" id="2_wyom5"]
+[ext_resource type="Script" path="res://entities/faction/fog_of_war.gd" id="4_8hagq"]
+[ext_resource type="Script" path="res://entities/faction/enemy/enemy.gd" id="6_y7ter"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tx3qi"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_mmhs2"]
+sky_material = SubResource("ProceduralSkyMaterial_tx3qi")
+
+[sub_resource type="Environment" id="Environment_r8f2r"]
+background_mode = 2
+sky = SubResource("Sky_mmhs2")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_e001c"]
+width = 16
+height = 16
+
+[sub_resource type="PrismMesh" id="PrismMesh_dw2jx"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nljwt"]
+albedo_color = Color(1, 0, 0, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xv1v4"]
+albedo_color = Color(0, 0.766667, 1, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ob58c"]
+albedo_color = Color(0, 0.46, 0.145667, 1)
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_alxwk"]
+material = SubResource("StandardMaterial3D_ob58c")
+size = Vector2(20, 20)
+
+[sub_resource type="SphereMesh" id="SphereMesh_3yo4a"]
+
+[node name="Game" type="Node3D"]
+script = ExtResource("1_3n5gp")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
+shadow_enabled = true
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_r8f2r")
+
+[node name="Player" type="Node" parent="."]
+script = ExtResource("2_ba5k2")
+
+[node name="PlayerCamera" type="Camera3D" parent="Player"]
+transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 2.38419e-07, 1.98981, 3.94427)
+script = ExtResource("2_1etxd")
+
+[node name="Sprite3D" type="Sprite3D" parent="Player/PlayerCamera"]
+transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 1, 0)
+cast_shadow = 0
+axis = 1
+billboard = 2
+texture = SubResource("NoiseTexture2D_e001c")
+
+[node name="PlayerUI" type="Control" parent="Player"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="SubViewportContainer" type="SubViewportContainer" parent="Player/PlayerUI"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -206.0
+offset_top = -206.0
+grow_horizontal = 0
+grow_vertical = 0
+stretch = true
+
+[node name="SubViewport" type="SubViewport" parent="Player/PlayerUI/SubViewportContainer"]
+handle_input_locally = false
+size = Vector2i(206, 206)
+render_target_update_mode = 4
+
+[node name="Camera3D" type="Camera3D" parent="Player/PlayerUI/SubViewportContainer/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 20, 0)
+projection = 1
+size = 20.0
+
+[node name="FogOfWar" type="Node" parent="Player"]
+script = ExtResource("4_8hagq")
+
+[node name="FogOfWarVisual" type="GridMap" parent="Player/FogOfWar"]
+mesh_library = ExtResource("2_wyom5")
+cell_size = Vector3(1, 1, 1)
+metadata/_editor_floor_ = Vector3(0, 0, 0)
+
+[node name="Buildings" type="Node" parent="Player"]
+
+[node name="PlayerCamp" type="MeshInstance3D" parent="Player/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.40872, 7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_nljwt")
+
+[node name="Units" type="Node" parent="Player"]
+
+[node name="Enemy" type="Node" parent="."]
+script = ExtResource("6_y7ter")
+
+[node name="FogOfWar" type="Node" parent="Enemy"]
+script = ExtResource("4_8hagq")
+
+[node name="Buildings" type="Node" parent="Enemy"]
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Enemy/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0.40872, -7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_xv1v4")
+
+[node name="Units" type="Node" parent="Enemy"]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Terrain" type="MeshInstance3D" parent="Environment"]
+mesh = SubResource("PlaneMesh_alxwk")
+skeleton = NodePath("../..")
+
+[node name="Food" type="Node" parent="Environment"]
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.41598, 0.40872, -3.16507)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57951, 0.40872, 2.67711)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D5" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.39705, 0.408719, -3.50521)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D6" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0892634, 0.408719, 5.57122)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
diff --git a/scenes/gam77C8.tmp b/scenes/gam77C8.tmp
new file mode 100644
index 0000000..6f3fefc
--- /dev/null
+++ b/scenes/gam77C8.tmp
@@ -0,0 +1,159 @@
+[gd_scene load_steps=17 format=3 uid="uid://rnmcx0o0hdgb"]
+
+[ext_resource type="Script" path="res://scenes/game.gd" id="1_3n5gp"]
+[ext_resource type="Script" path="res://entities/faction/player/player_camera.gd" id="2_1etxd"]
+[ext_resource type="Script" path="res://entities/faction/player/player.gd" id="2_ba5k2"]
+[ext_resource type="MeshLibrary" uid="uid://cesuajklj4ds4" path="res://resources/meshlib.tres" id="2_wyom5"]
+[ext_resource type="Script" path="res://entities/faction/fog_of_war.gd" id="4_8hagq"]
+[ext_resource type="Script" path="res://entities/faction/enemy/enemy.gd" id="6_y7ter"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tx3qi"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_mmhs2"]
+sky_material = SubResource("ProceduralSkyMaterial_tx3qi")
+
+[sub_resource type="Environment" id="Environment_r8f2r"]
+background_mode = 2
+sky = SubResource("Sky_mmhs2")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_e001c"]
+width = 16
+height = 16
+
+[sub_resource type="PrismMesh" id="PrismMesh_dw2jx"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nljwt"]
+albedo_color = Color(1, 0, 0, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xv1v4"]
+albedo_color = Color(0, 0.766667, 1, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ob58c"]
+albedo_color = Color(0, 0.46, 0.145667, 1)
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_alxwk"]
+material = SubResource("StandardMaterial3D_ob58c")
+size = Vector2(20, 20)
+
+[sub_resource type="SphereMesh" id="SphereMesh_3yo4a"]
+
+[node name="Game" type="Node3D"]
+script = ExtResource("1_3n5gp")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
+shadow_enabled = true
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_r8f2r")
+
+[node name="Player" type="Node" parent="."]
+script = ExtResource("2_ba5k2")
+
+[node name="PlayerCamera" type="Camera3D" parent="Player"]
+transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 2.38419e-07, 1.98981, 3.94427)
+script = ExtResource("2_1etxd")
+
+[node name="Sprite3D" type="Sprite3D" parent="Player/PlayerCamera"]
+transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 1, 0)
+cast_shadow = 0
+axis = 1
+billboard = 2
+texture = SubResource("NoiseTexture2D_e001c")
+
+[node name="PlayerUI" type="Control" parent="Player"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="SubViewportContainer" type="SubViewportContainer" parent="Player/PlayerUI"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -206.0
+offset_top = -206.0
+grow_horizontal = 0
+grow_vertical = 0
+stretch = true
+
+[node name="SubViewport" type="SubViewport" parent="Player/PlayerUI/SubViewportContainer"]
+handle_input_locally = false
+size = Vector2i(206, 206)
+render_target_update_mode = 4
+
+[node name="Camera3D" type="Camera3D" parent="Player/PlayerUI/SubViewportContainer/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 20, 0)
+projection = 1
+size = 20.0
+
+[node name="FogOfWar" type="Node" parent="Player"]
+script = ExtResource("4_8hagq")
+
+[node name="FogOfWarVisual" type="GridMap" parent="Player/FogOfWar"]
+mesh_library = ExtResource("2_wyom5")
+cell_size = Vector3(1, 1, 1)
+metadata/_editor_floor_ = Vector3(0, 0, 0)
+
+[node name="Buildings" type="Node" parent="Player"]
+
+[node name="PlayerCamp" type="MeshInstance3D" parent="Player/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.40872, 7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_nljwt")
+
+[node name="Units" type="Node" parent="Player"]
+
+[node name="Enemy" type="Node" parent="."]
+script = ExtResource("6_y7ter")
+
+[node name="FogOfWar" type="Node" parent="Enemy"]
+script = ExtResource("4_8hagq")
+
+[node name="Buildings" type="Node" parent="Enemy"]
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Enemy/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0.40872, -7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_xv1v4")
+
+[node name="Units" type="Node" parent="Enemy"]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Terrain" type="MeshInstance3D" parent="Environment"]
+mesh = SubResource("PlaneMesh_alxwk")
+skeleton = NodePath("../..")
+
+[node name="Food" type="Node" parent="Environment"]
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.41598, 0.40872, -3.16507)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57951, 0.40872, 2.67711)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D5" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.39705, 0.408719, -3.50521)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D6" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0892634, 0.408719, 5.57122)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
diff --git a/scenes/game.gd b/scenes/game.gd
new file mode 100644
index 0000000..4dbb900
--- /dev/null
+++ b/scenes/game.gd
@@ -0,0 +1,9 @@
+class_name Game extends Node3D
+
+
+func _ready() -> void:
+ pass # Replace with function body.
+
+
+func _process(delta: float) -> void:
+ pass
diff --git a/scenes/game.tscn b/scenes/game.tscn
new file mode 100644
index 0000000..8524aec
--- /dev/null
+++ b/scenes/game.tscn
@@ -0,0 +1,234 @@
+[gd_scene load_steps=21 format=3 uid="uid://rnmcx0o0hdgb"]
+
+[ext_resource type="Script" path="res://scenes/game.gd" id="1_3n5gp"]
+[ext_resource type="Script" path="res://entities/faction/player/player_camera.gd" id="2_1etxd"]
+[ext_resource type="Script" path="res://entities/faction/player/player.gd" id="2_ba5k2"]
+[ext_resource type="PackedScene" uid="uid://ch7ug2prgwyyn" path="res://entities/faction/fog_of_war.tscn" id="5_8a5ny"]
+[ext_resource type="Script" path="res://entities/faction/enemy/enemy.gd" id="6_y7ter"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tx3qi"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_mmhs2"]
+sky_material = SubResource("ProceduralSkyMaterial_tx3qi")
+
+[sub_resource type="Environment" id="Environment_r8f2r"]
+background_mode = 2
+sky = SubResource("Sky_mmhs2")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_e001c"]
+width = 16
+height = 16
+
+[sub_resource type="PrismMesh" id="PrismMesh_dw2jx"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nljwt"]
+albedo_color = Color(1, 0, 0, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_api38"]
+albedo_color = Color(0.53, 0, 0, 1)
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_cpeim"]
+lightmap_size_hint = Vector2i(17, 14)
+material = SubResource("StandardMaterial3D_api38")
+radius = 0.25
+height = 1.0
+
+[sub_resource type="Animation" id="Animation_qp8oi"]
+resource_name = "db_anim"
+length = 12.0
+step = 0.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("MeshInstance3D:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 2, 2.5, 4, 4.5, 6, 7, 9, 9.5, 12),
+"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
+"update": 0,
+"values": [Vector3(-5.16842, 0.25, 6.861), Vector3(-5.16842, 0.25, -0.139), Vector3(-4.16842, 0.25, -0.139), Vector3(4.83158, 0.249999, -0.139), Vector3(5.83158, 0.249999, -1.139), Vector3(5.83158, 0.249999, -7.139), Vector3(4.83158, 0.249999, -7.139), Vector3(-5.16842, 0.249999, -7.139), Vector3(-6.16842, 0.249999, -6.139), Vector3(-6.16841, 0.249998, 4.861)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("MeshInstance3D:rotation")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 2, 2.5, 4, 4.5, 6, 7, 9, 9.5, 12),
+"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
+"update": 0,
+"values": [Vector3(-1.5708, 0, 0), Vector3(-1.5708, 0, 0), Vector3(-1.5708, -1.5708, 0), Vector3(-1.5708, -1.5708, 0), Vector3(-1.5708, -7.17753e-23, 0), Vector3(-1.5708, -7.17753e-23, 0), Vector3(-1.5708, 1.5708, 0), Vector3(-1.5708, 1.5708, 0), Vector3(-1.5708, -3.14159, 0), Vector3(-1.5708, -3.14159, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_27gua"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("MeshInstance3D:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(-5.16842, 0.25, 6.861)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("MeshInstance3D:rotation")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(-1.5708, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_ae667"]
+_data = {
+"RESET": SubResource("Animation_27gua"),
+"db_anim": SubResource("Animation_qp8oi")
+}
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xv1v4"]
+albedo_color = Color(0, 0.766667, 1, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ob58c"]
+albedo_color = Color(0, 0.46, 0.145667, 1)
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_alxwk"]
+material = SubResource("StandardMaterial3D_ob58c")
+size = Vector2(20, 20)
+
+[sub_resource type="SphereMesh" id="SphereMesh_3yo4a"]
+
+[node name="Game" type="Node3D"]
+script = ExtResource("1_3n5gp")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
+shadow_enabled = true
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_r8f2r")
+
+[node name="Player" type="Node" parent="."]
+script = ExtResource("2_ba5k2")
+
+[node name="PlayerCamera" type="Camera3D" parent="Player"]
+transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 2.38419e-07, 1.98981, 3.94427)
+script = ExtResource("2_1etxd")
+
+[node name="Sprite3D" type="Sprite3D" parent="Player/PlayerCamera"]
+transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 1, 0)
+cast_shadow = 0
+axis = 1
+billboard = 2
+texture = SubResource("NoiseTexture2D_e001c")
+
+[node name="PlayerUI" type="Control" parent="Player"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="SubViewportContainer" type="SubViewportContainer" parent="Player/PlayerUI"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -206.0
+offset_top = -206.0
+grow_horizontal = 0
+grow_vertical = 0
+stretch = true
+
+[node name="SubViewport" type="SubViewport" parent="Player/PlayerUI/SubViewportContainer"]
+handle_input_locally = false
+size = Vector2i(206, 206)
+render_target_update_mode = 4
+
+[node name="Camera3D" type="Camera3D" parent="Player/PlayerUI/SubViewportContainer/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 20, 0)
+projection = 1
+size = 20.0
+
+[node name="FogOfWar" parent="Player" instance=ExtResource("5_8a5ny")]
+
+[node name="Buildings" type="Node" parent="Player"]
+
+[node name="PlayerCamp" type="MeshInstance3D" parent="Player/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.40872, 7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_nljwt")
+
+[node name="Units" type="Node" parent="Player"]
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Player/Units"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -5.16842, 0.25, 6.861)
+mesh = SubResource("CapsuleMesh_cpeim")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="Player"]
+root_node = NodePath("../Units")
+libraries = {
+"": SubResource("AnimationLibrary_ae667")
+}
+autoplay = "db_anim"
+
+[node name="Enemy" type="Node" parent="."]
+script = ExtResource("6_y7ter")
+
+[node name="FogOfWar" parent="Enemy" instance=ExtResource("5_8a5ny")]
+visual = false
+
+[node name="Buildings" type="Node" parent="Enemy"]
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Enemy/Buildings"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0.40872, -7)
+mesh = SubResource("PrismMesh_dw2jx")
+skeleton = NodePath("../../../Environment/Terrain")
+surface_material_override/0 = SubResource("StandardMaterial3D_xv1v4")
+
+[node name="Units" type="Node" parent="Enemy"]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Terrain" type="MeshInstance3D" parent="Environment"]
+mesh = SubResource("PlaneMesh_alxwk")
+skeleton = NodePath("../..")
+
+[node name="Food" type="Node" parent="Environment"]
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.41598, 0.40872, -3.16507)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D4" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57951, 0.40872, 2.67711)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D5" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.39705, 0.408719, -3.50521)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")
+
+[node name="MeshInstance3D6" type="MeshInstance3D" parent="Environment/Food"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0892634, 0.408719, 5.57122)
+mesh = SubResource("SphereMesh_3yo4a")
+skeleton = NodePath("../../Terrain")