Interior Maps #13
							
								
								
									
										
											BIN
										
									
								
								local_player.scn
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								local_player.scn
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								map_editor.scn
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								map_editor.scn
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										18
									
								
								map_editor/map_editor_menu.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								map_editor/map_editor_menu.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| class_name MapEditorMenu extends VBoxContainer | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal edit_activated(edit_action: Callable) | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| func activate_editor(edit_action: Callable) -> void: | ||||
| 	edit_activated.emit(edit_action) | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| func reset() -> void: | ||||
| 	pass | ||||
							
								
								
									
										
											BIN
										
									
								
								map_editor/paint_selector_button_group.res
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								map_editor/paint_selector_button_group.res
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										122
									
								
								mesh_grid.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								mesh_grid.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| class_name MeshGrid extends Node3D | ||||
| 
 | ||||
| const _CELL_COUNT := 8 | ||||
| 
 | ||||
| class _Chunk: | ||||
| 	var multimesh_instances: Array[_MultimeshInstance] = [] | ||||
| 
 | ||||
| 	var meshes: Array[Mesh] = [] | ||||
| 
 | ||||
| 	func _init() -> void: | ||||
| 		self.meshes.resize(_CELL_COUNT * _CELL_COUNT) | ||||
| 
 | ||||
| class _MultimeshInstance: | ||||
| 	var _instance_rid := RID() | ||||
| 
 | ||||
| 	var _multimesh_rid := RID() | ||||
| 
 | ||||
| 	func _init(scenario_rid: RID, mesh: Mesh, transforms: Array) -> void: | ||||
| 		_multimesh_rid = RenderingServer.multimesh_create() | ||||
| 		_instance_rid = RenderingServer.instance_create2(_multimesh_rid, scenario_rid) | ||||
| 
 | ||||
| 		RenderingServer.multimesh_set_mesh(_multimesh_rid, mesh.get_rid()) | ||||
| 
 | ||||
| 		var transform_count := transforms.size() | ||||
| 
 | ||||
| 		RenderingServer.multimesh_allocate_data(_multimesh_rid, | ||||
| 			transform_count, RenderingServer.MULTIMESH_TRANSFORM_3D, true) | ||||
| 
 | ||||
| 		for i in transform_count: | ||||
| 			RenderingServer.multimesh_instance_set_transform(_multimesh_rid, i, transforms[i]) | ||||
| 
 | ||||
| var _chunks: Array[_Chunk] = [] | ||||
| 
 | ||||
| var _grid_origin := Vector2(0.5, 0.5) | ||||
| 
 | ||||
| ## | ||||
| ## The number of horizontal and vertical cells in the current grid. | ||||
| ## | ||||
| ## Setting this value will result in the current grid data being completely overwritten to return | ||||
| ## it to a sensible default. | ||||
| ## | ||||
| @export | ||||
| var size: Vector2i: | ||||
| 	set(value): | ||||
| 		self._chunks.resize(int(ceil((value.x * value.y) / float(_CELL_COUNT)))) | ||||
| 
 | ||||
| 		for i in self._chunks.size(): | ||||
| 			self._chunks[i] = _Chunk.new() | ||||
| 
 | ||||
| 		size = value | ||||
| 
 | ||||
| func _notification(what: int) -> void: | ||||
| 	match what: | ||||
| 		NOTIFICATION_TRANSFORM_CHANGED: | ||||
| 			for chunk in _chunks: | ||||
| 				for multimesh_instance in chunk.multimesh_instances: | ||||
| 					RenderingServer.instance_set_transform( | ||||
| 						multimesh_instance._instance_rid, multimesh_instance.global_transform) | ||||
| 
 | ||||
| ## | ||||
| ## Sets the cell at [code]coordinate[/code] to display [code]mesh[/code]. | ||||
| ## | ||||
| ## Note that this function assumes [code]coordinate[/code] is within the bounds of the grid | ||||
| ## coordinate space. | ||||
| ## | ||||
| ## Note that changes to a cell result in the chunk it resides in being completely regenerated as | ||||
| ## part of its modification, and therefore has an implicitly higher overhead to other setter | ||||
| ## operations defined by [MeshGrid]. | ||||
| ## | ||||
| func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void: | ||||
| 	assert(Rect2i(Vector2i.ZERO, size).has_point(coordinate), "coordinate must be within grid") | ||||
| 
 | ||||
| 	# TODO: Once this is all lowered into native code, look for ways to parallelize the loops. | ||||
| 	var chunk_coordinate := coordinate / _CELL_COUNT | ||||
| 	var cell_coordinate := coordinate % _CELL_COUNT | ||||
| 	var chunk := _chunks[(size.x * chunk_coordinate.y) + chunk_coordinate.x] | ||||
| 	var chunk_meshes := chunk.meshes | ||||
| 
 | ||||
| 	chunk_meshes[(_CELL_COUNT * cell_coordinate.y) + cell_coordinate.x] = mesh | ||||
| 
 | ||||
| 	# Invalide any existing baked multimeshes in the chunk. | ||||
| 	for multimesh_instance in chunk.multimesh_instances: | ||||
| 		RenderingServer.free_rid(multimesh_instance._instance_rid) | ||||
| 		RenderingServer.free_rid(multimesh_instance._multimesh_rid) | ||||
| 
 | ||||
| 	chunk.multimesh_instances.clear() | ||||
| 
 | ||||
| 	# Normalize mesh instance data for the chunk. | ||||
| 	var mesh_transform_sets := {} | ||||
| 
 | ||||
| 	for i in chunk_meshes.size(): | ||||
| 		var chunk_mesh := chunk_meshes[i] | ||||
| 
 | ||||
| 		if chunk_mesh != null: | ||||
| 			if not(chunk_mesh in mesh_transform_sets): | ||||
| 				mesh_transform_sets[chunk_mesh] = [] | ||||
| 
 | ||||
| 			mesh_transform_sets[chunk_mesh].push_back(Transform3D(Basis.IDENTITY, Vector3( | ||||
| 				(float(chunk_coordinate.x * _CELL_COUNT) + (i % _CELL_COUNT)) - | ||||
| 					(float(size.x) * _grid_origin.x), 0.0, | ||||
| 						(float(chunk_coordinate.y * _CELL_COUNT) + (i / _CELL_COUNT)) -  | ||||
| 							(float(size.y) * _grid_origin.y)))) | ||||
| 
 | ||||
| 	# Bake into multimesh instances for the chunk. | ||||
| 	var scenario_rid := get_world_3d().scenario | ||||
| 
 | ||||
| 	for chunk_mesh in mesh_transform_sets: | ||||
| 		var multimesh_instance :=\ | ||||
| 			_MultimeshInstance.new(scenario_rid, chunk_mesh, mesh_transform_sets[chunk_mesh]) | ||||
| 
 | ||||
| 		RenderingServer.instance_set_transform(multimesh_instance._instance_rid, global_transform) | ||||
| 
 | ||||
| 		chunk.multimesh_instances.push_back(multimesh_instance) | ||||
| 
 | ||||
| ## | ||||
| ## Returns [code]world_position[/code] converted into a coordinate aligned with the [MeshGrid]. | ||||
| ## | ||||
| ## Note that [code]world_position[/code] values not within the [MeshGrid] will produce grid | ||||
| ## coordinates outside of the [MeshGrid] bounds as well. | ||||
| ## | ||||
| func world_to_grid(world_position: Vector2) -> Vector2i: | ||||
| 	return Vector2i((world_position + (Vector2(size) * _grid_origin)).floor()) | ||||
| @ -192,7 +192,7 @@ func get_cursor_point() -> Vector2: | ||||
| ## Successful point intersection will return the plane coordinates in a [class Vector2], otherwise | ||||
| ## [code]null[/code] is returned.  | ||||
| ## | ||||
| func screen_to_plane(screen_point: Vector2): | ||||
| func screen_to_plane(screen_point: Vector2) -> Variant: | ||||
| 	if self._camera != null: | ||||
| 		var plane_target = Plane(Vector3.UP, 0.0).intersects_ray( | ||||
| 			self._camera.global_position, self._camera.project_ray_normal(screen_point)) | ||||
|  | ||||
| @ -9,21 +9,36 @@ | ||||
| config_version=5 | ||||
| 
 | ||||
| _global_script_classes=[{ | ||||
| "base": "Control", | ||||
| "class": &"ActionPrompt", | ||||
| "language": &"GDScript", | ||||
| "path": "res://user_interface/action_prompt.gd" | ||||
| }, { | ||||
| "base": "HFlowContainer", | ||||
| "class": &"ItemSelection", | ||||
| "language": &"GDScript", | ||||
| "path": "res://user_interface/button_selection.gd" | ||||
| }, { | ||||
| "base": "Node", | ||||
| "class": &"MapEditorTerrainCanvas", | ||||
| "base": "VBoxContainer", | ||||
| "class": &"MapEditorMenu", | ||||
| "language": &"GDScript", | ||||
| "path": "res://map_editor/map_editor_terrain_canvas.gd" | ||||
| "path": "res://map_editor/map_editor_menu.gd" | ||||
| }, { | ||||
| "base": "Node3D", | ||||
| "class": &"MeshGrid", | ||||
| "language": &"GDScript", | ||||
| "path": "res://mesh_grid.gd" | ||||
| }, { | ||||
| "base": "Node3D", | ||||
| "class": &"PlayerController", | ||||
| "language": &"GDScript", | ||||
| "path": "res://player_controller.gd" | ||||
| }, { | ||||
| "base": "Control", | ||||
| "class": &"SelectionPrompt", | ||||
| "language": &"GDScript", | ||||
| "path": "res://user_interface/selection_prompt.gd" | ||||
| }, { | ||||
| "base": "Node", | ||||
| "class": &"Settings", | ||||
| "language": &"GDScript", | ||||
| @ -34,18 +49,33 @@ _global_script_classes=[{ | ||||
| "language": &"GDScript", | ||||
| "path": "res://terrain/terrain_instance_3d.gd" | ||||
| }, { | ||||
| "base": "Node", | ||||
| "class": &"TerrainMapCanvas", | ||||
| "language": &"GDScript", | ||||
| "path": "res://terrain/terrain_map_canvas.gd" | ||||
| }, { | ||||
| "base": "Resource", | ||||
| "class": &"TerrainPaint", | ||||
| "language": &"GDScript", | ||||
| "path": "res://terrain/paints/terrain_paint.gd" | ||||
| }, { | ||||
| "base": "Control", | ||||
| "class": &"WorkerPrompt", | ||||
| "language": &"GDScript", | ||||
| "path": "res://user_interface/worker_prompt.gd" | ||||
| }] | ||||
| _global_script_class_icons={ | ||||
| "ActionPrompt": "", | ||||
| "ItemSelection": "", | ||||
| "MapEditorTerrainCanvas": "", | ||||
| "MapEditorMenu": "", | ||||
| "MeshGrid": "", | ||||
| "PlayerController": "", | ||||
| "SelectionPrompt": "", | ||||
| "Settings": "", | ||||
| "TerrainInstance3D": "", | ||||
| "TerrainPaint": "" | ||||
| "TerrainMapCanvas": "", | ||||
| "TerrainPaint": "", | ||||
| "WorkerPrompt": "" | ||||
| } | ||||
| 
 | ||||
| [application] | ||||
| @ -59,6 +89,7 @@ config/icon="res://icon.png" | ||||
| [autoload] | ||||
| 
 | ||||
| GameSettings="*res://settings.gd" | ||||
| LocalPlayer="*res://local_player.scn" | ||||
| 
 | ||||
| [debug] | ||||
| 
 | ||||
|  | ||||
| @ -10,9 +10,9 @@ const _SHADER := preload("res://terrain/terrain.gdshader") | ||||
| ## | ||||
| enum PaintSlot { | ||||
| 	ERASE, | ||||
| 	RED, | ||||
| 	GREEN, | ||||
| 	BLUE, | ||||
| 	PAINT_1, | ||||
| 	PAINT_2, | ||||
| 	PAINT_3, | ||||
| } | ||||
| 
 | ||||
| var _albedo_maps: Array[Texture2D] = [null, null, null, null] | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| class_name MapEditorTerrainCanvas extends Node | ||||
| class_name TerrainMapCanvas extends Node | ||||
| 
 | ||||
| ## | ||||
| ## Blank sample value. | ||||
| @ -37,7 +37,11 @@ func clear(clear_elevation: float) -> void: | ||||
| 	_editable_texture.update(_editable_image) | ||||
| 
 | ||||
| ## | ||||
| ## Returns the canvas data as a [Texture2D]. | ||||
| ## Returns the canvas data as a [Texture2D] where RGB channels are encoded in the red, green, and | ||||
| ## blue, component of each pixel and the height is the alpha component. | ||||
| ## | ||||
| ## The returned value is designed for use with a [TerrainInstance3D] or other class compatible with | ||||
| ## the encoded format specified above. | ||||
| ## | ||||
| func get_texture() -> Texture2D: | ||||
| 	return _editable_texture | ||||
							
								
								
									
										43
									
								
								user_interface/action_prompt.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								user_interface/action_prompt.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| @tool | ||||
| class_name ActionPrompt extends Control | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal prompt_accepted() | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal prompted() | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 
 | ||||
| @export | ||||
| var _accept_button: BaseButton = null | ||||
| 
 | ||||
| @export | ||||
| var initial_focus: Control = null | ||||
| 
 | ||||
| func _get_configuration_warnings() -> PackedStringArray: | ||||
| 	var warnings := PackedStringArray() | ||||
| 
 | ||||
| 	if _accept_button == null: | ||||
| 		warnings.append("`Accept Button` must point to a valid BaseButton instance") | ||||
| 
 | ||||
| 	return warnings | ||||
| 
 | ||||
| func _ready() -> void: | ||||
| 	assert(_accept_button != null, "accept button cannot be null") | ||||
| 
 | ||||
| 	_accept_button.pressed.connect(func () -> void: | ||||
| 		prompt_accepted.emit() | ||||
| 		hide()) | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| func prompt() -> void: | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 	show() | ||||
| 	prompted.emit() | ||||
| 
 | ||||
| 	if initial_focus != null: | ||||
| 		initial_focus.grab_focus() | ||||
							
								
								
									
										68
									
								
								user_interface/selection_prompt.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								user_interface/selection_prompt.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| @tool | ||||
| class_name SelectionPrompt extends Control | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal prompted() | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal prompt_selected(selected_item: int) | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 
 | ||||
| @export | ||||
| var _button_group: ButtonGroup = null | ||||
| 
 | ||||
| ## | ||||
| ## If set to [code]true[/code], the selection prompt dismisses itself after a selection is made. | ||||
| ## Otherwise, if set to [code]false[/code], the prompt remains present after a selection is made. | ||||
| ## | ||||
| @export | ||||
| var dismiss_on_selection := false | ||||
| 
 | ||||
| func _get_configuration_warnings() -> PackedStringArray: | ||||
| 	var warnings := PackedStringArray() | ||||
| 
 | ||||
| 	if _button_group == null: | ||||
| 		warnings.append("`Button Group` must point to a valid BaseButton instance") | ||||
| 
 | ||||
| 	return warnings | ||||
| 
 | ||||
| func _ready() -> void: | ||||
| 	assert(_button_group != null, "button group cannot be null") | ||||
| 
 | ||||
| 	_button_group.pressed.connect(func (button: BaseButton) -> void: | ||||
| 		var selected_item = _button_group.get_buttons().find(button) | ||||
| 
 | ||||
| 		assert(selected_item > -1, "selected item cannot be less than 0") | ||||
| 		prompt_selected.emit(selected_item) | ||||
| 
 | ||||
| 		if dismiss_on_selection: | ||||
| 			hide()) | ||||
| 
 | ||||
| ## | ||||
| ## Returns the [ButtonGroup] used by the selection prompt to handle selections. | ||||
| ## | ||||
| func get_button_group() -> ButtonGroup: | ||||
| 	return _button_group | ||||
| 
 | ||||
| ## | ||||
| ## Displays the selection prompt, with [code]initial_selection[/code] as the initial value selected. | ||||
| ## | ||||
| ## Once a value has been manually selected in the prompt, [signal prompt_selected] is emitted. | ||||
| ## | ||||
| ## *Note* that [code]initial_selection[/code] *must* be a valid index in the range of the button | ||||
| ## group used by the selection prompt, which may be accessed by the calling logic via | ||||
| ## [member get_button_group] | ||||
| ## | ||||
| func prompt(initial_selection: int) -> void: | ||||
| 	assert(_button_group != null, "button group cannot be null") | ||||
| 
 | ||||
| 	var selected_button := _button_group.get_buttons()[initial_selection] | ||||
| 
 | ||||
| 	selected_button.button_pressed = true | ||||
| 
 | ||||
| 	show() | ||||
| 	selected_button.grab_focus() | ||||
| 	prompted.emit() | ||||
							
								
								
									
										46
									
								
								user_interface/worker_prompt.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								user_interface/worker_prompt.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| class_name WorkerPrompt extends Control | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| signal prompted() | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 
 | ||||
| var _worker_thread := Thread.new() | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| ## | ||||
| @export | ||||
| var label: Label = null | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| @export | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| var progress: Range = null | ||||
| 
 | ||||
| ## | ||||
| ## | ||||
| ## | ||||
| func prompt(display_message: String, steps: Array) -> void: | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 	show() | ||||
| 	prompted.emit() | ||||
| 
 | ||||
| 	if label != null: | ||||
| 		label.text = display_message | ||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing doc comment. Missing doc comment. | ||||
| 
 | ||||
| 	_worker_thread.start(func () -> void: | ||||
| 		var count := steps.size() | ||||
| 		var total := float(count) | ||||
| 
 | ||||
| 		for i in count: | ||||
| 			steps[i].call() | ||||
| 
 | ||||
| 			if progress != null: | ||||
| 				progress.value = i / total) | ||||
| 
 | ||||
| 	while _worker_thread.is_alive(): | ||||
| 		await get_tree().process_frame | ||||
| 
 | ||||
| 	_worker_thread.wait_to_finish() | ||||
| 	hide() | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
Missing doc comment.