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