1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# 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 <https://www.gnu.org/licenses/>.
#
class_name Map
extends Node3D
class TileInfo:
func _init(position_, name_, tile_, neighbours_) -> void:
position = position_; name = name_; tile = tile_; neighbours = neighbours_
var position: Vector2i
var name: String
var tile: Tile
var neighbours: Array
const NEIGHBOR_OFFSETS: Array[Vector2i] = [Vector2i.UP, Vector2i.LEFT, Vector2i.DOWN, Vector2i.RIGHT]
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_tile_name(pos: Vector2i): # -> String?
var e = tile_by_pos.get(pos)
if e != null: return e.name
else: return null
func get_tile_instance(pos: Vector2i) -> Tile:
var e = tile_by_pos.get(pos)
if e != null: return e.tile
else: return null
func set_all_tiles(changes: Dictionary[Vector2i, Array]):
for pos: Vector2i in changes:
set_tiles(Vector2i(pos.x, pos.y), changes[pos], changes)
func set_tiles(pos: Vector2i, tiles: Array = [], pending_changes: Dictionary[Vector2i, Array] = {}): # tiles: Array[String]
var inst = get_tile_instance(pos)
if inst != null and not tiles.is_empty():
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)
if autoflush: flush()
func _add_tiles(pos: Vector2i, tiles: Array, pending_changes: Dictionary[Vector2i, Array] = {}) -> void:
# 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([])
for tile_name: String in tiles:
var tile := tile_factory.produce(tile_name, pos, neighbors)
add_child(tile)
tile.position = Vector3(pos.x, 0, pos.y)
tile_by_pos[pos] = TileInfo.new(pos, tile_name, tile, neighbors)
func _remove_tile(pos: Vector2i):
var tile := get_tile_instance(pos)
if tile == null: return
if tile.item != null: tile.item.queue_free()
if tile is FloorLike:
var floor_mesher = tile_factory.floor_meshers.get(tile.fm_id())
if floor_mesher != null:
floor_mesher.remove_tile(pos)
tile.queue_free()
tile_by_pos.erase(pos)
tile.name += "_queued_free"
@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.)]
|