diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index 5bd7c45..0164e08 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -7,6 +7,8 @@ const CHUNK_TYPE_CURVED_TRACK: int = 2 const CHUNK_GENERATION_STEPS_PER_FRAME: int = 2 const CHUNK_CLEANUP_STEPS_PER_FRAME: int = 4 const LAMPPOST_WIRE_STEPS_PER_FRAME: int = 1 + +const MAX_CHUNK_UNIQUENESS: int = 5 const RAILWAY_SCENE_DIRECTORY: String = "res://tgcc/chunk/railway/scene" const RIVER_SIDE_ORDER: Array[String] = ["north", "est", "south", "west"] const RIVER_DIRECTIONS: Dictionary = { @@ -48,7 +50,6 @@ var pending_generation_cells: Array[Vector2i] = [] var pending_cleanup_cells: Array[Vector2i] = [] var pending_wire_cells: Array[Vector2i] = [] var pending_wire_lookup: Dictionary = {} -var pending_radar_update: bool = false var rail_chunk_catalogue: Dictionary = {} var manual_biome: Biome = null @@ -125,16 +126,12 @@ func _destroy_and_regenrate_world() -> void: var train_pos = rail_path.train_instance.global_position var current_pos = Vector2i(roundi(train_pos.x / chunk_size), roundi(train_pos.z / chunk_size)) _refresh_world(current_pos) - pending_radar_update = true func _process(_delta: float) -> void: _drain_cleanup_queue() _drain_generation_queue() _drain_wire_queue() - if pending_radar_update: - pending_radar_update = false - func _physics_process(_delta: float) -> void: if rail_path == null or rail_path.train_instance == null: return @@ -146,7 +143,6 @@ func _physics_process(_delta: float) -> void: if current_pos != last_pos_train: last_pos_train = current_pos _refresh_world(current_pos) - pending_radar_update = true func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void: if root == null: return @@ -239,6 +235,7 @@ func _get_chunk_scene_metadata(scene: PackedScene) -> Dictionary: "info_path": info_path, "rotations": rotations, "has_lamppost": "have_lamppost" in info_node and info_node.have_lamppost, + "uniqueness": _get_chunk_uniqueness_from_info(info_node), } preview_chunk.queue_free() chunk_candidate_cache[key] = metadata @@ -273,7 +270,6 @@ func _clear_pending_world_work() -> void: pending_cleanup_cells.clear() pending_wire_cells.clear() pending_wire_lookup.clear() - pending_radar_update = false func _collect_replaceable_rail_chunks(root: Node, result: Array[Node3D]) -> void: if root == null: @@ -470,6 +466,54 @@ func _get_procedural_biome_name(value: float) -> String: var index = clamp(int(normalized_value * biome_list.size()), 0, biome_list.size() - 1) return biome_list[index].name +func _get_chunk_uniqueness_from_info(info_node: Node) -> int: + if info_node != null and "uniqueness" in info_node: + return clampi(info_node.uniqueness, 0, MAX_CHUNK_UNIQUENESS) + return 0 + +func _has_nearby_unique_chunk(grid_pos: Vector2i, uniqueness: int) -> bool: + if uniqueness <= 0: + return false + + for nearby_pos in board.keys(): + var nearby_uniqueness = int(board[nearby_pos].get("uniqueness", 0)) + if nearby_uniqueness <= 0: + continue + + var min_distance = maxi(uniqueness, nearby_uniqueness) + var dist_x = abs(nearby_pos.x - grid_pos.x) + var dist_z = abs(nearby_pos.y - grid_pos.y) + if dist_x <= min_distance and dist_z <= min_distance: + return true + return false + +func _get_uniqueness_pick_weight(uniqueness: int) -> float: + return 1.0 / pow(float(uniqueness + 1), 2.0) + +func _pick_weighted_candidate(candidates: Array) -> Dictionary: + var total_weight = 0.0 + for candidate in candidates: + total_weight += candidate.weight + + if total_weight <= 0.0: + return candidates.pick_random() + + var target_weight = randf() * total_weight + var current_weight = 0.0 + for candidate in candidates: + current_weight += candidate.weight + if current_weight >= target_weight: + return candidate + return candidates.back() + +func _pick_backup_scene(zone_catalogue: Array[PackedScene], grid_pos: Vector2i) -> PackedScene: + for scene in zone_catalogue: + var metadata = _get_chunk_scene_metadata(scene) + var uniqueness = int(metadata.get("uniqueness", 0)) + if not _has_nearby_unique_chunk(grid_pos, uniqueness): + return scene + return zone_catalogue[0] + func _register_cell_with_ray(grid_pos: Vector2i) -> bool: if board.has(grid_pos): return true @@ -517,6 +561,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: var river_exit_found = {"north": false, "est": false, "south": false, "west": false} var height_found = {"north": 0, "est": 0, "south": 0, "west": 0} var have_lamppost = false + var uniqueness = 0 #Get node info if right_info_node != null: @@ -530,6 +575,8 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: if "have_lamppost" in right_info_node: have_lamppost = right_info_node.have_lamppost + uniqueness = _get_chunk_uniqueness_from_info(right_info_node) + #Add piece to the grid board[grid_pos] = { "type": "obstacle", @@ -538,7 +585,8 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: "heights": height_found, "node": root_chunk, "info": right_info_node, - "have_lamppost": have_lamppost + "have_lamppost": have_lamppost, + "uniqueness": uniqueness } if have_lamppost: @@ -579,6 +627,9 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: var u_conn = data["connections"] var u_river_conn = data["river_connections"] var u_height = data["heights"] + var uniqueness = int(metadata.get("uniqueness", 0)) + if _has_nearby_unique_chunk(grid_pos, uniqueness): + continue var match_conn_n = (req_conn_north == -1) or ((req_conn_north == 1) == u_conn["north"]) var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"]) @@ -607,7 +658,14 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: if req_height_south == -1 and u_height["south"] == height_target: score += 1 if req_height_west == -1 and u_height["west"] == height_target: score += 1 - valid_candidates.append({"scene": scene, "rotation": rot, "data": data, "score": score}) + valid_candidates.append({ + "scene": scene, + "rotation": rot, + "data": data, + "score": score, + "weight": _get_uniqueness_pick_weight(uniqueness), + "uniqueness": uniqueness + }) if valid_candidates.size() > 0: var max_score = -1 @@ -618,7 +676,7 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: for c in valid_candidates: if c.score == max_score: best_candidate.append(c) - var choise = best_candidate.pick_random() + var choise = _pick_weighted_candidate(best_candidate) var new_chunk = choise.scene.instantiate() new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) new_chunk.rotation.y = choise.rotation * (-PI / 2.0) @@ -641,18 +699,21 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "heights": choise.data["heights"], "node": new_chunk, "info": info_new, - "have_lamppost": have_lamppost + "have_lamppost": have_lamppost, + "uniqueness": choise.uniqueness } if have_lamppost: _queue_lamppost_wire_connection(grid_pos) else: - var backup = zone_catalogue[0].instantiate() + var backup_scene = _pick_backup_scene(zone_catalogue, grid_pos) + var backup = backup_scene.instantiate() backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) add_child(backup) - var info_backup = _get_cached_chunk_info(backup, zone_catalogue[0]) + var info_backup = _get_cached_chunk_info(backup, backup_scene) + var backup_uniqueness = _get_chunk_uniqueness_from_info(info_backup) var safe_heights = { "north": req_height_north if req_height_north != -1 else height_target, @@ -668,7 +729,8 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "heights": safe_heights, "node": backup, "info": info_backup, - "have_lamppost": false + "have_lamppost": false, + "uniqueness": backup_uniqueness } func _calculate_river_flow_direction(grid_pos: Vector2i, river_connections: Dictionary) -> Vector2: diff --git a/core/biome_generator/chunk_info.gd b/core/biome_generator/chunk_info.gd index 74b6629..5f9bad6 100644 --- a/core/biome_generator/chunk_info.gd +++ b/core/biome_generator/chunk_info.gd @@ -5,6 +5,7 @@ class_name ChunkInfo @export_group("Set Piece Rules (Unique pieces)") @export var exclusive_biome: String = "" # Es: "Forest" +@export_range(0, 5, 1) var uniqueness: int = 0 #0=common; 5=unique @export_group("Base exit") @export var north: bool = false