1 /**
2 	FindLocation.c
3 	This script contains the function FindLocation which uses a parameter-system similar to FindObject.
4 	This function should mainly be used for placement of objects at the start of a scenario.
5 	FindLocation is not guaranteed to always return a spot if a fitting spot exists, it's just best effort.
6 
7 	Examples:
8 	finds a tunnel spot:
9 	FindLocation([Loc_Tunnel()]);
10 	finds a floor spot but not in front of tunnel:
11 	FindLocation([Loc_Not(Find_Tunnel()), Loc_Wall(CNAT_Bottom)]);
12 
13 	@author Zapper
14 */
15 
16 static const LOC_INVALID = 0;
17 static const LOC_SOLID = 1;
18 static const LOC_INAREA = 2;
19 static const LOC_MATERIAL = 3;
20 static const LOC_MATERIALVAL = 4;
21 static const LOC_FUNC = 5;
22 static const LOC_WALL = 6;
23 static const LOC_SPACE = 7;
24 static const LOC_NOT = 8;
25 static const LOC_SKY = 9;
26 static const LOC_LIQUID = 10;
27 static const LOC_OR = 11;
28 static const LOC_AND = 12;
29 static const LOC_MAXTRIES = 13;
30 
31 /*
32 	Returns a spot where a custom function returned "true".
33 	For example: Loc_Condition(LargeCaveMushroom.GoodSpot)
34 	with "func GoodSpot(int x, int y)"
35 */
Loc_Func(function)36 global func Loc_Func(function)
37 {
38 	return [LOC_FUNC, function];
39 }
40 
Loc_Not(cond)41 global func Loc_Not(cond)
42 {
43 	return [LOC_NOT, cond];
44 }
45 
Loc_Or(...)46 global func Loc_Or(...)
47 {
48 	var conds = [LOC_OR];
49 	for (var i = 0; i < 10; ++i)
50 	{
51 		if (Par(i) != nil)
52 			PushBack(conds, Par(i));
53 	}
54 	return conds;
55 }
56 
Loc_And(...)57 global func Loc_And(...)
58 {
59 	var conds = [LOC_AND];
60 	for (var i = 0; i < 10; ++i)
61 	{
62 		if (Par(i) != nil)
63 			PushBack(conds, Par(i));
64 	}
65 	return conds;
66 }
67 /*
68 	only returns results in a rectangle defined by either the coordinates and size x, y, w, h or by passing a rectangle-proplist
69 	as "x" to allow Loc_InRect(Rectangle(x, y, w, h))
70 */
Loc_InRect(x,int y,int w,int h)71 global func Loc_InRect(x, int y, int w, int h)
72 {
73 	if (x == nil) return [LOC_INVALID];
74 	if (GetType(x) == C4V_PropList) return [LOC_INAREA, x];
75 	return [LOC_INAREA, Rectangle(x, y, w, h)];
76 }
77 
78 /*
79 only returns results in area defined by a proplist
80 */
Loc_InArea(a)81 global func Loc_InArea(a)
82 {
83 	if (a == nil) return [LOC_INVALID];
84 	return[LOC_INAREA, a];
85 }
86 
87 /*
88 	Returns a point in a given material or material-texture-combination.
89 	The texture-parameter is optional.
90 */
Loc_Material(string material,string texture)91 global func Loc_Material(string material, string texture)
92 {
93 	if (texture)
94 		return [LOC_MATERIAL, Material(material), texture];
95 	else
96 		return [LOC_MATERIAL, Material(material)];
97 }
98 
Loc_MaterialVal(string entry,string section,int entry_nr,compare)99 global func Loc_MaterialVal(string entry, string section, int entry_nr, compare)
100 {
101 	return [LOC_MATERIALVAL, entry, section, entry_nr, compare];
102 }
103 
Loc_Tunnel()104 global func Loc_Tunnel()
105 {
106 	return Loc_Material("Tunnel");
107 }
108 
109 /*
110 	Sets the maximum number of trying to find a position with a random point.
111 	Should probably be larger than 1000 unless you are very sure that such a spot is common.
112 */
Loc_MaxTries(int number_of_tries)113 global func Loc_MaxTries(int number_of_tries)
114 {
115 	return [LOC_MAXTRIES, number_of_tries];
116 }
117 
Loc_Sky()118 global func Loc_Sky() {	return [LOC_SKY]; }
Loc_Solid()119 global func Loc_Solid() { return [LOC_SOLID]; }
Loc_Liquid()120 global func Loc_Liquid() { return [LOC_LIQUID]; }
121 
122 /*
123 	returns a point on a wall. /direction/ can be either CNAT_Bottom, CNAT_Top, CNAT_Left, CNAT_Right.
124 	Note that this implies that you are looking for a non-solid spot.
125 */
Loc_Wall(int direction,wall_condition1,...)126 global func Loc_Wall(int direction, wall_condition1, ...)
127 {
128 	var x = 0, y = 0;
129 	if(direction & CNAT_Left) x = -1;
130 	else
131 	if(direction & CNAT_Right) x = 1;
132 
133 	if(direction & CNAT_Top) y = -1;
134 	else
135 	if(direction & CNAT_Bottom) y = 1;
136 
137 	var both_left_right = !!((direction & CNAT_Left) && (direction & CNAT_Right));
138 	var both_top_bottom = !!((direction & CNAT_Top) && (direction & CNAT_Bottom));
139 
140 	var wall_conditions = [];
141 	for (var i = 1; i < 10; ++i)
142 	{
143 		var condition = Par(i);
144 		if (!condition)
145 			continue;
146 		PushBack(wall_conditions, condition);
147 	}
148 
149 	return [LOC_WALL, x, y, both_left_right, both_top_bottom, wall_conditions];
150 }
151 
152 /*
153 	returns a spot with enough space either vertically or horizontally.
154 	For example Loc_Space(20, CNAT_Top) would only return points where a Clonk could stand.
155 */
Loc_Space(int space,int direction)156 global func Loc_Space(int space, int direction)
157 {
158 	return [LOC_SPACE, space, direction ?? 0x0f];
159 }
160 
FindLocation(condition1,...)161 global func FindLocation(condition1, ...)
162 {
163 	var area = nil;
164 	var xdir = 0, ydir = 0, xmod = nil, ymod = nil, wall_conditions = [];
165 	var flags = [];
166 	// maximum number of tries
167 	var repeat = 5000;
168 
169 	//  put parameters in array and filter out special parameters
170 	for (var i = 0; i < 10; ++i)
171 	{
172 		var f = Par(i);
173 		if (!f) continue;
174 
175 		if (f[0] == LOC_INAREA)
176 		{
177 			area = f[1];
178 			PushBack(flags, f); // re-check area afterwards to account for wall attachments, etc. pushing position out of range
179 		}
180 		else if (f[0] == LOC_WALL)
181 		{
182 			xdir = f[1];
183 			ydir = f[2];
184 			xmod = f[3];
185 			ymod = f[4];
186 			wall_conditions = f[5];
187 		}
188 		else if (f[0] == LOC_MAXTRIES)
189 		{
190 			repeat = f[1];
191 		}
192 		else
193 		{
194 			PushBack(flags, f);
195 		}
196 	}
197 	area = area ?? Shape->LandscapeRectangle();
198 
199 	// repeat until a spot is found or max. number of tries exceeded
200 	var outpos = {};
201 	while (repeat-- > 0)
202 	{
203 		if (!area->GetRandomPoint(outpos)) return nil; // invalid shape or nothing found
204 		var x = outpos.x, y = outpos.y;
205 		var valid = true;
206 
207 		// find wall-spot
208 		// this part just moves the random point to left/right/up/down until a wall is hit
209 		if (xdir || ydir)
210 		{
211 			if (GBackSolid(x, y)) continue;
212 			var lx = xdir;
213 			var ly = ydir;
214 			if (xmod) if (Random(2)) lx *= -1;
215 			if (ymod) if (Random(2)) ly *= -1;
216 
217 			valid = false;
218 			var failsafe = 0;
219 			do
220 			{
221 				if (GBackSolid(x + lx, y + ly))
222 				{
223 					valid = true;
224 					for (var flag in wall_conditions)
225 					{
226 						if (Global->FindLocationConditionCheckIsValid(flag, x + lx, y + ly)) continue;
227 						valid = false;
228 						break;
229 					}
230 					break;
231 				}
232 				x += lx;
233 				y += ly;
234 			} while (++failsafe < 100);
235 			if (!valid) continue;
236 		}
237 
238 		// check every flag
239 		for (var flag in flags)
240 		{
241 			if (Global->FindLocationConditionCheckIsValid(flag, x, y)) continue;
242 			valid = false;
243 			break;
244 		}
245 		if (valid)
246 		{
247 			// Store back position as LOC_WALL etc. may have modified it.
248 			outpos.x = x;
249 			outpos.y = y;
250 			return outpos;
251 		}
252 	}
253 
254 	// no spot found
255 	return nil;
256 }
257 
FindLocationConditionCheckIsValid(flag,x,y)258 global func FindLocationConditionCheckIsValid(flag, x, y)
259 {
260 	if (flag[0] == LOC_NOT) return !FindLocationConditionCheckIsValid(flag[1], x, y);
261 
262 	if (flag[0] == LOC_OR)
263 	{
264 		var max = GetLength(flag);
265 		for (var i = 1; i < max; ++i)
266 			if (FindLocationConditionCheckIsValid(flag[i], x, y)) return true;
267 		return false;
268 	}
269 
270 	if (flag[0] == LOC_AND)
271 	{
272 		var max = GetLength(flag);
273 		for (var i = 1; i < max; ++i)
274 			if (!FindLocationConditionCheckIsValid(flag[i], x, y)) return false;
275 		return true;
276 	}
277 
278 	if (flag[0] == LOC_INAREA)
279 	{
280 		var area = flag[1];
281 		area = area ?? Shape->LandscapeRectangle();
282 		return area->IsPointContained(x, y);
283 	}
284 
285 	if (flag[0] == LOC_SOLID)
286 		return GBackSolid(x, y);
287 
288 	if (flag[0] == LOC_SKY)
289 		return GBackSky(x, y);
290 
291 	if (flag[0] == LOC_LIQUID)
292 		return GBackLiquid(x, y);
293 
294 	if (flag[0] == LOC_MATERIAL)
295 	{
296 		if ((GetMaterial(x, y) != flag[1])) return false;
297 		if (GetLength(flag) > 2)
298 			if (GetTexture(x, y) != flag[2]) return false;
299 		return true;
300 	}
301 
302 	if (flag[0] == LOC_MATERIALVAL)
303 	{
304 		var mat = GetMaterial(x, y);
305 		var mat_val = GetMaterialVal(flag[1], flag[2], mat, flag[3]);
306 		return mat_val == flag[4];
307 	}
308 
309 	// custom condition call
310 	if (flag[0] == LOC_FUNC)
311 	{
312 		return Global->Call(flag[1], x, y);
313 	}
314 
315 	// has some space?
316 	if (flag[0] == LOC_SPACE)
317 	{
318 		var dist = flag[1], dirs = flag[2];
319 		// if only one direction is given in one dimension, the other dimension is tested from a center point halfway off in that dimension
320 		var cy = y + dist * ((dirs&CNAT_Bottom)/CNAT_Bottom - (dirs&CNAT_Top)/CNAT_Top) / 2;
321 		var cx = x + dist * ((dirs&CNAT_Right)/CNAT_Right - (dirs&CNAT_Left)/CNAT_Left) / 2;
322 		// check all desired directions
323 		if (dirs & CNAT_Top) if (!PathFree(cx,y,cx,y-dist)) return false;
324 		if (dirs & CNAT_Bottom) if (!PathFree(cx,y,cx,y+dist)) return false;
325 		if (dirs & CNAT_Left) if (!PathFree(x,cy,x-dist,cy)) return false;
326 		if (dirs & CNAT_Right) if (!PathFree(x,cy,x+dist,cy)) return false;
327 		return true;
328 	}
329 
330 	// invalid flag? always fulfilled
331 	return true;
332 }
333