extends Node2D @onready var tile_layer: TileMapLayer = $main_game_background_tiles @onready var camera: Camera2D = $main_game_camera @export var RENDER_RADIUS: int = 20 @export_range(0.0, 1.0) var producer_spawn_chance: float = 0.015 # 1.5% chance per tile # preload scenes that we can place: const EXTRACTOR_SCENE = preload("res://extractor.tscn") const BELT_SCENE = preload("res://belt.tscn") const ADDER_SCENE = preload("res://adder.tscn") const DELETE_SCENE = preload("res://delete.tscn") enum Mode { NONE, PLACE_EXTRACTOR, PLACE_BELT, PLACE_ADDER, DELETE } var current_mode = Mode.NONE var preview_item: Node2D = null # Holds the visual for the mouse # These should likely be changed to be the same format as above @export var producer_scene: PackedScene @export var consumer_scene: PackedScene var render_radius_x = RENDER_RADIUS var render_radius_y = RENDER_RADIUS # This dictionary tracks what physical entity occupies a grid coordinate. # Format: Vector2i(x, y) : { "type": "consumer" } or { "type": "producer", "value": 10 } var grid_data: Dictionary = {} # This dictionary keeps track of where the floor tiles have already been visually placed var rendered_tiles: Dictionary = {} func _ready() -> void: # Explicitly pre-seed the center of the world data structure initialize_world_center() func _process(_delta: float) -> void: #print(camera.position) #print(get_global_mouse_position()) #print(get_grid_snapped_mouse_position()) if camera: render_radius_x = RENDER_RADIUS / camera.zoom[0] render_radius_y = RENDER_RADIUS / camera.zoom[1] generate_and_render_around_camera() # Update the preview item's position and snap to the grid if preview_item and current_mode != Mode.NONE: var grid_pos = get_grid_snapped_mouse_position() preview_item.global_position = tile_layer.map_to_local(grid_pos) # var scaled_pos = (get_grid_snapped_mouse_position() * 32) # scaled_pos[0] += 32 # scaled_pos[1] += 32 # preview_item.global_position = scaled_pos func _input(event): # Detect left mouse click if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: match current_mode: Mode.PLACE_EXTRACTOR: place_item(EXTRACTOR_SCENE) Mode.PLACE_BELT: place_item(BELT_SCENE) Mode.DELETE: delete_item(get_grid_snapped_mouse_position()) # Right-click cancels the current action elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: clear_preview() current_mode = Mode.NONE # Returns the grid position of the mouse func get_grid_snapped_mouse_position() -> Vector2: return tile_layer.local_to_map(get_global_mouse_position()) # Define the absolute center data rule func initialize_world_center() -> void: # Loop from -1 to 1 on both X and Y to cover a 3x3 area for x in range(-1, 2): for y in range(-1, 2): var current_coord = Vector2i(x, y) grid_data[current_coord] = [{ "type": "consumer", "is_center": (x == 0 and y == 0) # Only (0,0) is marked as the visual center }] print("World Core Initialized: 3x3 Consumer registered from (-1,-1) to (1,1)") if producer_scene: print("Placing consumer at (0, 0)") # 1. Create a live instance of the saved scene file var consumer = consumer_scene.instantiate() # 2. Add it as a child of the world so it exists in the active game tree add_child(consumer) # 3. Convert grid coordinates back to exact engine pixel coordinates # map_to_local gives us the exact center point of that grid tile var pixel_position = tile_layer.map_to_local(Vector2i(0, 0)) consumer.global_position = pixel_position # Combined loop to manage both world data rules and visual rendering func generate_and_render_around_camera() -> void: var camera_grid_pos = tile_layer.local_to_map(camera.global_position) for x in range(camera_grid_pos.x - render_radius_x, camera_grid_pos.x + render_radius_x): for y in range(camera_grid_pos.y - render_radius_y, camera_grid_pos.y + render_radius_y): var tile_coords = Vector2i(x, y) # 1. VISUAL RENDERING (Always draw the background floor) if not rendered_tiles.has(tile_coords): # Draw your background tile asset (Source ID: 0, Atlas Coords: 0,0) tile_layer.set_cell(tile_coords, 0, Vector2i(0, 0)) rendered_tiles[tile_coords] = true # 2. DATA PROCESSING PLACEHOLDER # This is where Phase 2 will plug in to evaluate if a producer should spawn process_grid_data_at(tile_coords) # PHASE 2: Distance Math & Random Generation func process_grid_data_at(coords: Vector2i) -> void: # If this coordinate is already occupied (like by our 3x3 Consumer), skip it! # This ensures that there is a 3x3 space to spawn item in. This is required for the producers for x in range(-1, 2): for y in range(-1, 2): if grid_data.has(coords + Vector2i(x, y)): return # Roll the dice to see if a Producer should spawn here if randf() < producer_spawn_chance: # Calculate Manhattan Distance from the absolute center (0,0) var distance = abs(coords.x) + abs(coords.y) # Formula: Start at 1, add 1 extra value for every 10 tiles away var producer_value = 1 + floor(distance / 10.0) # Register the producer in our global data registry # disabled: for x in range(-1, 2): # disabled: for y in range(-1, 2): # disabled: grid_data[coords + Vector2i(x, y)] = { # disabled: "type": "producer", # disabled: "value": producer_value # disabled: } grid_data[coords] = [{ "type": "producer", "value": producer_value }] if producer_scene: print("Placing producer at " + str(coords.x) + ", " + str(coords.y)) # 1. Create a live instance of the saved scene file var new_producer = producer_scene.instantiate() var pixel_position = tile_layer.map_to_local(coords) new_producer.global_position = pixel_position new_producer.setup(int(producer_value)) # 2. Add it as a child of the world so it exists in the active game tree add_child(new_producer) # Spawns the preview item following the cursor func set_preview_mode(mode_type: Mode, scene_to_preview: PackedScene): clear_preview() current_mode = mode_type if scene_to_preview: preview_item = scene_to_preview.instantiate() # Optional: Make the preview slightly transparent preview_item.modulate.a = 0.5 add_child(preview_item) func clear_preview(): if preview_item: preview_item.queue_free() preview_item = null # Places an item onto the game board func place_item(item: PackedScene): var spawn_pos = get_grid_snapped_mouse_position() print("attempting placement at %s, %s" % [spawn_pos[0], spawn_pos[1]]) # Check if there is something under us var object_at_coords = get_object_at_coordinate(spawn_pos) print(object_at_coords) if object_at_coords != null: if item == EXTRACTOR_SCENE && object_at_coords['type'] == 'producer': print("You are placing an extractor and it is on top of a producer") pass # Do not return if we are placing an extractor and we are on top of a producer else: print("Failed to place due to %s in the way" % object_at_coords['type']) return else: # If we are not over something if item == EXTRACTOR_SCENE: # And we are placing an extractor print("You must place an extractor on top of a producer") return # Do not place print("Placing object") var new_item = item.instantiate() new_item.global_position = tile_layer.map_to_local(spawn_pos) new_item.add_to_group("Placed_objects") add_child(new_item) register_object_at_coordinate( spawn_pos, { "type": new_item.name, }) # Custom setup needed if item == EXTRACTOR_SCENE: new_item.setup(object_at_coords['value']) else: print("No custom setup for %s" % item) func delete_item(target_pos: Vector2i): var targets = get_tree().get_nodes_in_group("Placed_objects") for item in targets: print("Trying to match delete at: " + str(tile_layer.local_to_map(item.global_position)[0]) + ", " + str(tile_layer.local_to_map(item.global_position)[1])) if tile_layer.local_to_map(item.global_position) == target_pos: print("deleting item") item.queue_free() delete_object_at_coordinate(target_pos) break # Prevents deleting multiple items. # Helper function: Check if a coordinate is occupied by a building func get_object_at_coordinate(coords: Vector2i): if grid_data.has(coords): print(grid_data) return grid_data[coords][-1] # Return the last item there return null # Returns null if it's just an empty background tile # Helper function: Force place an object into the grid data registry func register_object_at_coordinate(coords: Vector2i, object_data: Dictionary) -> void: if grid_data.has(coords): grid_data[coords].append(object_data) else: grid_data[coords] = [object_data] func delete_object_at_coordinate(coords: Vector2i): grid_data[coords].pop_back() # --- UI Button Signals --- func _on_extractor_button_pressed(): set_preview_mode(Mode.PLACE_EXTRACTOR, EXTRACTOR_SCENE) func _on_belt_button_pressed() -> void: set_preview_mode(Mode.PLACE_BELT, BELT_SCENE) func _on_adder_button_pressed() -> void: set_preview_mode(Mode.PLACE_ADDER, ADDER_SCENE) func _on_delete_button_pressed() -> void: set_preview_mode(Mode.DELETE, DELETE_SCENE)