# Hurry Curry! - a game about cooking # Copyright (C) 2025 Hurry Curry! contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, version 3 of the License only. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # class_name Map extends Node3D class TileInfo: func _init(position_, tiles_, tile_instances_, tile_parent_) -> void: position = position_; tiles = tiles_; tile_instances = tile_instances_; tile_parent = tile_parent_ var position: Vector2i var tiles: Array # Array[String] var tile_instances: Array[Tile] var tile_parent: Node3D var interact_tile: Tile const NEIGHBOR_OFFSETS: Array[Vector2i] = [Vector2i.UP, Vector2i.LEFT, Vector2i.DOWN, Vector2i.RIGHT] const TILE_COMBINATOR: Dictionary[Array, Array] = { ["counter", "sink"]: ["sink"] } # : Dictionary[Array[String], Array[String]] var tile_by_pos: Dictionary[Vector2i, TileInfo] = {} var autoflush = false var currently_baked = false var floor_node := MeshInstance3D.new() var tile_factory := TileFactory.new() func get_tiles_at(pos: Vector2i): # -> Array[String]? var e = tile_by_pos.get(pos) if e == null: return null return e.tiles func get_topmost_instance(pos: Vector2i): # -> Tile? var e = tile_by_pos.get(pos) if e == null: return null return e.tile_instances[-1] func get_tile_item(pos: Vector2i): # -> Item? var e = get_topmost_instance(pos) if e == null: return null return e.item func set_all_tiles(changes: Dictionary[Vector2i, Array], srv: Game.ServerContext = null): for pos: Vector2i in changes: set_tiles(Vector2i(pos.x, pos.y), changes[pos], changes, srv) func set_tiles(pos: Vector2i, tiles: Array = [], pending_changes: Dictionary[Vector2i, Array] = {}, srv: Game.ServerContext = null): # tiles: Array[String] var tile_info = tile_by_pos.get(pos) if tile_info != null: for inst: Tile in tile_info.tile_instances: for tile: String in tiles: # TODO: Don't return, but handle changes which weren't handled by the instance below. if inst.change(tile): return # Instance handled change itself! _remove_tile(pos) if not tiles.is_empty(): _add_tiles(pos, tiles, pending_changes, srv) if autoflush: flush() func _add_tiles(pos: Vector2i, tiles: Array, pending_changes: Dictionary[Vector2i, Array], srv: Game.ServerContext) -> void: # Combinate tiles for k in TILE_COMBINATOR: if G.has_all(tiles, k): for item: String in k: tiles.erase(item) tiles.append_array(TILE_COMBINATOR[k]) # Find neighbor tile names var neighbors: Array[Array] = [] # Array[Array[String]] for offset: Vector2i in NEIGHBOR_OFFSETS: var neighbor_pos: Vector2i = pos + offset if pending_changes.has(neighbor_pos): neighbors.append(pending_changes[neighbor_pos]) elif tile_by_pos.has(neighbor_pos): neighbors.append(tile_by_pos[neighbor_pos]) else: neighbors.append([]) var tiles_parent = Node3D.new() tiles_parent.name = str(pos) add_child(tiles_parent) var tile_instances: Array[Tile] = [] for tile_name: String in tiles: var tile := tile_factory.produce(tile_name, pos, neighbors, tile_instances, srv) tile_instances.append(tile) tile.position = Vector3(pos.x, 0, pos.y) tiles_parent.add_child(tile) tile_by_pos[pos] = TileInfo.new(pos, tiles, tile_instances, tiles_parent) func _remove_tile(pos: Vector2i): var tile_info = tile_by_pos.get(pos) if tile_info == null: return var topmost_instance = get_topmost_instance(pos) if topmost_instance.item != null: topmost_instance.item.queue_free() for instance: Tile in tile_info.tile_instances: if instance is FloorLike: var floor_mesher = tile_factory.floor_meshers.get(instance.fm_id()) if floor_mesher != null: floor_mesher.remove_tile(pos) instance.queue_free() tile_by_pos.erase(pos) @onready var voxelgi: VoxelGI = $VoxelGI func _ready(): Settings.hook_changed("graphics.gi", self, apply_gi_setting) floor_node.material_override = preload("res://map/tiles/floor_material.tres") for fm in tile_factory.floor_meshers.values(): add_child(fm.mesh_instance) add_child(floor_node) func flush() -> void: for fm in tile_factory.floor_meshers.values(): fm.flush() gi_bake() func apply_gi_setting(state): if state == "voxelgi" and not currently_baked: gi_bake() else: currently_baked = false voxelgi.data = null func gi_bake(): if Settings.read("graphics.gi") != "voxelgi": return print("Map: Rebaking VoxelGI") currently_baked = true gi_bake_blocking() func gi_bake_blocking(): var map_extents = extents() var extent_min = map_extents[0] var extent_max = map_extents[1] var center = (extent_max + extent_min) / 2 var size = extent_max - extent_min voxelgi.position = Vector3(center.x, 3., center.y) voxelgi.size = Vector3(size.x, 8., size.y) print("Baking now!") var start = Time.get_ticks_msec() voxelgi.bake() voxelgi.visible = true print("Bake done. elapsed=", Time.get_ticks_msec() - start) func extents() -> Array[Vector2]: var extent_min = Vector2(0,0) var extent_max = Vector2(0,0) for e: TileInfo in tile_by_pos.values(): extent_min.x = min(extent_min.x, e.position.x) extent_min.y = min(extent_min.y, e.position.y) extent_max.x = max(extent_max.x, e.position.x) extent_max.y = max(extent_max.y, e.position.y) return [extent_min, extent_max + Vector2(1., 1.)]