File: FindLocation.c

package info (click to toggle)
openclonk 8.1-4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 169,520 kB
  • sloc: cpp: 180,479; ansic: 108,988; xml: 31,371; python: 1,223; php: 767; makefile: 145; sh: 101; javascript: 34
file content (332 lines) | stat: -rw-r--r-- 8,080 bytes parent folder | download | duplicates (5)
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
	FindLocation.c
	This script contains the function FindLocation which uses a parameter-system similar to FindObject.
	This function should mainly be used for placement of objects at the start of a scenario.
	FindLocation is not guaranteed to always return a spot if a fitting spot exists, it's just best effort.
	
	Examples:
	finds a tunnel spot:
	FindLocation([Loc_Tunnel()]);
	finds a floor spot but not in front of tunnel:
	FindLocation([Loc_Not(Find_Tunnel()), Loc_Wall(CNAT_Bottom)]);
	
	@author Zapper
*/

static const LOC_INVALID = 0;
static const LOC_SOLID = 1;
static const LOC_INAREA = 2;
static const LOC_MATERIAL = 3;
static const LOC_MATERIALVAL = 4;
static const LOC_FUNC = 5;
static const LOC_WALL = 6;
static const LOC_SPACE = 7;
static const LOC_NOT = 8;
static const LOC_SKY = 9;
static const LOC_LIQUID = 10;
static const LOC_OR = 11;
static const LOC_AND = 12;
static const LOC_MAXTRIES = 13;

/*
	Returns a spot where a custom function returned "true".
	For example: Loc_Condition(LargeCaveMushroom.GoodSpot)
	with "func GoodSpot(int x, int y)"
*/
global func Loc_Func(function)
{
	return [LOC_FUNC, function];
}

global func Loc_Not(cond)
{
	return [LOC_NOT, cond];
}

global func Loc_Or(...)
{
	var conds = [LOC_OR];
	for (var i = 0; i < 10; ++i)
	{
		if (Par(i) != nil)
			PushBack(conds, Par(i));
	}
	return conds;
}

global func Loc_And(...)
{
	var conds = [LOC_AND];
	for (var i = 0; i < 10; ++i)
	{
		if (Par(i) != nil)
			PushBack(conds, Par(i));
	}
	return conds;
}
/*
	only returns results in a rectangle defined by either the coordinates and size x, y, w, h or by passing a rectangle-proplist
	as "x" to allow Loc_InRect(Rectangle(x, y, w, h))
*/	
global func Loc_InRect(x, int y, int w, int h)
{
	if (x == nil) return [LOC_INVALID];
	if (GetType(x) == C4V_PropList) return [LOC_INAREA, x];
	return [LOC_INAREA, Rectangle(x, y, w, h)];
}

/*
only returns results in area defined by a proplist
*/
global func Loc_InArea(a)
{
	if (a == nil) return [LOC_INVALID];
	return[LOC_INAREA, a];
}

/*
	Returns a point in a given material or material-texture-combination.
	The texture-parameter is optional.
*/
global func Loc_Material(string material, string texture)
{
	if (texture)
		return [LOC_MATERIAL, Material(material), texture];
	else
		return [LOC_MATERIAL, Material(material)];
}

global func Loc_MaterialVal(string entry, string section, int entry_nr, compare)
{
	return [LOC_MATERIALVAL, entry, section, entry_nr, compare];
}

global func Loc_Tunnel()
{
	return Loc_Material("Tunnel");
}

/*
	Sets the maximum number of trying to find a position with a random point.
	Should probably be larger than 1000 unless you are very sure that such a spot is common.
*/
global func Loc_MaxTries(int number_of_tries)
{
	return [LOC_MAXTRIES, number_of_tries];
}

global func Loc_Sky() {	return [LOC_SKY]; }
global func Loc_Solid() { return [LOC_SOLID]; }
global func Loc_Liquid() { return [LOC_LIQUID]; }

/*
	returns a point on a wall. /direction/ can be either CNAT_Bottom, CNAT_Top, CNAT_Left, CNAT_Right.
	Note that this implies that you are looking for a non-solid spot.
*/
global func Loc_Wall(int direction, wall_condition1, ...)
{
	var x = 0, y = 0;
	if(direction & CNAT_Left) x = -1;
	else
	if(direction & CNAT_Right) x = 1;
	
	if(direction & CNAT_Top) y = -1;
	else
	if(direction & CNAT_Bottom) y = 1;
	
	var both_left_right = !!((direction & CNAT_Left) && (direction & CNAT_Right));
	var both_top_bottom = !!((direction & CNAT_Top) && (direction & CNAT_Bottom));
	
	var wall_conditions = [];
	for (var i = 1; i < 10; ++i)
	{
		var condition = Par(i);
		if (!condition) 
			continue;
		PushBack(wall_conditions, condition);
	}
	
	return [LOC_WALL, x, y, both_left_right, both_top_bottom, wall_conditions];
}

/*
	returns a spot with enough space either vertically or horizontally.
	For example Loc_Space(20, CNAT_Top) would only return points where a Clonk could stand.
*/
global func Loc_Space(int space, int direction)
{
	return [LOC_SPACE, space, direction ?? 0x0f];
}

global func FindLocation(condition1, ...)
{
	var area = nil;
	var xdir = 0, ydir = 0, xmod = nil, ymod = nil, wall_conditions = [];
	var flags = [];
	// maximum number of tries
	var repeat = 5000;
	
	//  put parameters in array and filter out special parameters
	for (var i = 0; i < 10; ++i)
	{
		var f = Par(i);
		if (!f) continue;
		
		if (f[0] == LOC_INAREA)
		{
			area = f[1];
			PushBack(flags, f); // re-check area afterwards to account for wall attachments, etc. pushing position out of range
		}
		else if (f[0] == LOC_WALL)
		{
			xdir = f[1];
			ydir = f[2];
			xmod = f[3];
			ymod = f[4];
			wall_conditions = f[5];
		}
		else if (f[0] == LOC_MAXTRIES)
		{
			repeat = f[1];
		}
		else
		{
			PushBack(flags, f);
		}
	}
	area = area ?? Shape->LandscapeRectangle();
	
	// repeat until a spot is found or max. number of tries exceeded
	var outpos = {};
	while (repeat-- > 0)
	{
		if (!area->GetRandomPoint(outpos)) return nil; // invalid shape or nothing found
		var x = outpos.x, y = outpos.y;
		var valid = true;
		
		// find wall-spot
		// this part just moves the random point to left/right/up/down until a wall is hit
		if (xdir || ydir)
		{
			if (GBackSolid(x, y)) continue;
			var lx = xdir;
			var ly = ydir;
			if (xmod) if (Random(2)) lx *= -1;
			if (ymod) if (Random(2)) ly *= -1;

			valid = false;
			var failsafe = 0;
			do
			{
				if (GBackSolid(x + lx, y + ly)) 
				{
					valid = true;
					for (var flag in wall_conditions)
					{
						if (Global->FindLocationConditionCheckIsValid(flag, x + lx, y + ly)) continue;
						valid = false;
						break;
					}
					break;
				}
				x += lx;
				y += ly;
			} while (++failsafe < 100);
			if (!valid) continue;
		}
		
		// check every flag
		for (var flag in flags)
		{
			if (Global->FindLocationConditionCheckIsValid(flag, x, y)) continue;
			valid = false;
			break;
		}
		if (valid)
		{
			// Store back position as LOC_WALL etc. may have modified it.
			outpos.x = x;
			outpos.y = y;
			return outpos;
		}
	}
	
	// no spot found
	return nil;
}

global func FindLocationConditionCheckIsValid(flag, x, y)
{
	if (flag[0] == LOC_NOT) return !FindLocationConditionCheckIsValid(flag[1], x, y);
	
	if (flag[0] == LOC_OR)
	{
		var max = GetLength(flag);
		for (var i = 1; i < max; ++i)
			if (FindLocationConditionCheckIsValid(flag[i], x, y)) return true;
		return false;
	}
	
	if (flag[0] == LOC_AND)
	{
		var max = GetLength(flag);
		for (var i = 1; i < max; ++i)
			if (!FindLocationConditionCheckIsValid(flag[i], x, y)) return false;
		return true;
	}
	
	if (flag[0] == LOC_INAREA)
	{
		var area = flag[1];
		area = area ?? Shape->LandscapeRectangle();
		return area->IsPointContained(x, y);
	}
	
	if (flag[0] == LOC_SOLID)
		return GBackSolid(x, y);
	
	if (flag[0] == LOC_SKY)
		return GBackSky(x, y);
	
	if (flag[0] == LOC_LIQUID)
		return GBackLiquid(x, y);
	
	if (flag[0] == LOC_MATERIAL)
	{
		if ((GetMaterial(x, y) != flag[1])) return false;
		if (GetLength(flag) > 2)
			if (GetTexture(x, y) != flag[2]) return false;
		return true;
	}
	
	if (flag[0] == LOC_MATERIALVAL)
	{
		var mat = GetMaterial(x, y);
		var mat_val = GetMaterialVal(flag[1], flag[2], mat, flag[3]);
		return mat_val == flag[4];
	}
		
	// custom condition call
	if (flag[0] == LOC_FUNC)
	{
		return Global->Call(flag[1], x, y);
	}
	
	// has some space?
	if (flag[0] == LOC_SPACE)
	{
		var dist = flag[1], dirs = flag[2];
		// if only one direction is given in one dimension, the other dimension is tested from a center point halfway off in that dimension
		var cy = y + dist * ((dirs&CNAT_Bottom)/CNAT_Bottom - (dirs&CNAT_Top)/CNAT_Top) / 2;
		var cx = x + dist * ((dirs&CNAT_Right)/CNAT_Right - (dirs&CNAT_Left)/CNAT_Left) / 2;
		// check all desired directions
		if (dirs & CNAT_Top) if (!PathFree(cx,y,cx,y-dist)) return false;
		if (dirs & CNAT_Bottom) if (!PathFree(cx,y,cx,y+dist)) return false;
		if (dirs & CNAT_Left) if (!PathFree(x,cy,x-dist,cy)) return false;
		if (dirs & CNAT_Right) if (!PathFree(x,cy,x+dist,cy)) return false;
		return true;
	}
	
	// invalid flag? always fulfilled
	return true;
}