File size: 9,063 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
local function GetPrefabItems(self)
	local items = {}
	local PrefabMarkers = PrefabMarkers
	for _, prefab in ipairs(self:FilterPrefabs()) do
		items[#items + 1] = PrefabMarkers[prefab]
	end
	table.sort(items)
	table.insert(items, 1, "")
	return items
end

DefineClass.PlacePrefabLogic = {
	__parents = { "PropertyObject" },
	properties = {
		{ category = "Prefab", id = "FixedPrefab",     name = "Fixed Prefabs",          editor = "string_list", default = false, items = GetPrefabItems, no_validate = true, buttons = {{name = "Test", func = "TestPlacePrefab"}}},
		{ category = "Prefab", id = "PrefabPOIType",   name = "Prefab POI Type",        editor = "preset_id",   default = "", preset_class = "PrefabPOI" },
		{ category = "Prefab", id = "PrefabType",      name = "Prefab Type",            editor = "preset_id",   default = "", preset_class = "PrefabType" },
		{ category = "Prefab", id = "PrefabTagsAny",   name = "Prefab Tags Any",        editor = "set",         default = empty_table, items = function() return PrefabTagsCombo() end, three_state = true },
		{ category = "Prefab", id = "PrefabTagsAll",   name = "Prefab Tags All",        editor = "set",         default = empty_table, items = function() return PrefabTagsCombo() end, three_state = true },
		{ category = "Prefab", id = "MaxPrefabRadius", name = "Max Allowed Radius",     editor = "number",      default = 0, scale = "m" },
		{ category = "Prefab", id = "FixAtCenter",     name = "Fix At Center",          editor = "bool",        default = true, help = "Allow the prefab to be spawned anywhere inside the max radius" },
		{ category = "Prefab", id = "RandAngle",       name = "Rand Angle",             editor = "number",      default = 0, scale = "deg" },
		{ category = "Prefab", id = "PlacedName",      name = "Placed Name",            editor = "text",        default = "", read_only = true, dont_save = true,  buttons = {{name = "Goto", func = "GotoPrefabAction"}} },
		{ category = "Prefab", id = "PlaceError",      name = "Place Error",            editor = "text",        default = "", read_only = true, dont_save = true },
		{ category = "Prefab", id = "PrefabCount",     name = "Prefab Count",           editor = "number",      default = 0, read_only = true, dont_save = true },
	},
	reserved_locations = false,
}

function PlacePrefabLogic:SetFixedPrefab(prefab)
	if type(prefab) == "string" then
		prefab = { prefab }
	end
	self.FixedPrefab = prefab
end

function PlacePrefabLogic:FilterPrefabs(params, all_prefabs)
	all_prefabs = all_prefabs or PrefabMarkers
	local prefabs = {}
	local poi_type = params and params.poi_type or self.PrefabPOIType or ""
	local ptype = params and params.prefab_type or self.PrefabType or ""
	local max_radius = params and params.max_radius or self.MaxPrefabRadius or 0
	local tags_any = params and params.tags_any or self.PrefabTagsAny
	local tags_all = params and params.tags_all or self.PrefabTagsAll
	local type_tile = const.TypeTileSize
	for _, prefab in ipairs(all_prefabs) do
		if (poi_type == "" or prefab.poi_type == poi_type)
		and (ptype == "" or prefab.type == "" or prefab.type == ptype)
		and (max_radius == 0 or prefab.max_radius * type_tile <= max_radius)
		and MatchThreeStateSet(prefab.tags, tags_any, tags_all)
		then
			prefabs[#prefabs + 1] = prefab
		end
	end
	return prefabs
end

function PlacePrefabLogic:GetPrefabs(params)
	local fixed_prefabs = params and params.name or self.FixedPrefab
	if fixed_prefabs then
		local prefabs = {}
		if type(fixed_prefabs) == "string" then
			fixed_prefabs = { fixed_prefabs } 
		end
		for _, name in ipairs(fixed_prefabs) do
			local prefab = PrefabMarkers[name]
			if not prefab then
				StoreErrorSource(self, "No such prefab:", name)
			else
				prefabs[#prefabs + 1] = prefab
			end
		end
		if params and params.filter_fixed then
			local tags_any = params and params.tags_any or self.PrefabTagsAny
			local tags_all = params and params.tags_all or self.PrefabTagsAll
			for i = #prefabs,1,-1 do
				if not MatchThreeStateSet(prefabs[i].tags, tags_any, tags_all) then
					table.remove_rotate(prefabs, i)
				end
			end
		end
		if #prefabs > 0 then
			return prefabs
		end
	end
	return self:FilterPrefabs(params)
end

function PlacePrefabLogic:GetError()
	if mapdata.IsPrefabMap and self:GetPrefabCount() == 0 then
		return "No matching prefabs found!"
	end
end

function PlacePrefabLogic:GetPrefabCount(params)
	return #self:GetPrefabs(params)
end

function PlacePrefabLogic:ReserveLocation(pos, radius)
	self.reserved_locations = table.create_add(self.reserved_locations, {pos, radius})
end

function PlacePrefabLogic:GetReservedRatio()
	local max_radius = self.MaxPrefabRadius
	if max_radius <= 0 then
		return 100
	end
	local radius_sum2 = 0
	for _, info in ipairs(self.reserved_locations) do
		local radius = info[2]
		radius_sum2 = radius * radius
	end
	return 100 * sqrt(radius_sum2) / max_radius
end

function PlacePrefabLogic:CheckReservedLocations(pos, radius)
	--DbgClear(true) DbgAddCircle(self, self.MaxPrefabRadius, yellow) DbgAddCircle(pos, radius, blue)
	for _, info in ipairs(self.reserved_locations) do
		if IsCloser2D(pos, info[1], radius + info[2]) then
			--DbgAddCircle(info[1], info[2], red)
			return
		end
	end
	--DbgAddVector(pos)
	return true
end

function PlacePrefabLogic:GetPrefabLoc(seed, params)
	seed = seed or InteractionRand(nil, "PlacePrefab")
	local name, pos, angle, prefab, idx
	local prefabs = self:GetPrefabs(params)
	local retry
	while true do
		local idx
		if #prefabs > 1 then
			prefab, idx, seed = table.weighted_rand(prefabs, "weight", seed)
		else
			prefab = prefabs[1]
		end
		assert(prefab)
		if not prefab then
			return
		end
		pos = params and params.pos
		if not pos then
			pos = self:GetVisualPos()
			if not self.FixAtCenter then
				local reserved_radius
				if params and params.avoid_reserved_locations and self.reserved_locations then
					reserved_radius = (prefab.min_radius + prefab.max_radius) * const.TypeTileSize / 2
				end
				local radius = prefab.max_radius * const.TypeTileSize
				local free_dist = self.MaxPrefabRadius - radius
				if free_dist > 0 then
					local center = pos
					pos = false
					local retries = params and params.avoid_reserved_retries or 16
					for i=1,retries do
						local ra, rr
						ra, seed = BraidRandom(seed, 360*60)
						rr, seed = BraidRandom(seed, free_dist)
						local pos_i = RotateRadius(rr, ra, center)
						if not reserved_radius or self:CheckReservedLocations(pos_i, reserved_radius) then
							pos = pos_i
							break
						end
					end
				elseif reserved_radius and not self:CheckReservedLocations(pos, reserved_radius) then
					pos = false
				end
			end
		end
		if pos then
			name = PrefabMarkers[prefab]
			angle = params and params.angle
			if not angle then
				angle = self:GetAngle()
				local rand_angle = self.RandAngle
				if rand_angle > 0 then
					local desired_angle = params and params.desired_angle
					if desired_angle then
						local angle_diff = AngleDiff(desired_angle, angle)
						if abs(angle_diff) <= rand_angle then
							angle = desired_angle
						else
							local min_angle, max_angle = angle - rand_angle, angle + rand_angle
							if abs(AngleDiff(desired_angle, min_angle)) < abs(AngleDiff(desired_angle, max_angle)) then
								angle = min_angle
							else
								angle = max_angle
							end
						end
					else
						local da
						da, seed = BraidRandom(seed, -rand_angle, rand_angle)
						angle = angle + da
					end
				end
			end
			return name, pos, angle, prefab, seed
		end
		if #prefabs == 1 then
			return
		end
		table.remove_rotate(prefabs, idx)
	end
end

function PlacePrefabLogic:PlacePrefab(seed, params)
	local success, err, objs, inv_bbox
	local name, pos, angle, prefab, seed = self:GetPrefabLoc(seed, params)
	if not name then
		err = "No matching prefabs found!"
	else
		success, err, objs, inv_bbox = procall(PlacePrefab, name, pos, angle, seed, params)
	end
	self.PlaceError = err
	self.PlacedName = name
	ObjModified(self)
	return err, objs, pos, prefab, name, inv_bbox
end

function PlacePrefabLogic:EditorCallbackGenerate(generator, object_source, placed_objects, prefab_list)
	local mark = placed_objects[self]
	local info = mark and prefab_list[mark]
	local ptype = info and info[4]
	if ptype then
		self.PrefabType = ptype
	end
end

----

DefineClass.PlacePrefabMarker = {
	__parents = { "RadiusMarker", "PlacePrefabLogic", "PrefabSourceInfo" },
	editor_text_color = RGB(50, 50, 100),
	editor_color = RGB(150, 150, 0),
}

function PlacePrefabMarker:GetMeshRadius()
	local max_radius = self.MaxPrefabRadius
	for _, prefab in ipairs(self:GetPrefabs()) do
		max_radius = Max(max_radius, prefab.max_radius)
	end
	return max_radius
end

function PlacePrefabMarker:OnEditorSetProperty(prop_id, old_value, ged)
	local meta = self:GetPropertyMetadata(prop_id)
	if meta and meta.category == "Prefab" then
		self:UpdateMeshRadius()
	end
end

function PlacePrefabMarker:TestPlacePrefab()
	local err, objs = self:PlacePrefab(AsyncRand(), {
		create_undo = true,
	})
	if err then
		print(err)
	end
end