File: Script.c

package info (click to toggle)
openclonk 8.1-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 169,516 kB
  • sloc: cpp: 180,479; ansic: 108,988; xml: 31,371; python: 1,223; php: 767; makefile: 145; sh: 101; javascript: 34
file content (409 lines) | stat: -rw-r--r-- 13,186 bytes parent folder | download | duplicates (6)
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
/**
	Storm
*/

local Name = "$Name$";
local Description = "$Description$";

local storm_debug = false;
local streams, n_streams;
local execution_index = 0, n_exec_per_loop = 1;
local max_stream_len = 1000; // maximum number of intervals to be calculated for each stream of this storm
local exec_interval = 10; // exec every n frames
local find_mask; // Find_-condition for objects to be flung by wind
local strength; // storm strength
local stream_density = 20;
local stream_border_dist = 20;
local map, map_res1, map_res2, map_size1, map_size2, map_off1, map_off2;
//local debug_map;
local storm_particles; // storm particle definition, for pretty visuals

local StormStream;

static g_storm;

func Initialize()
{
	// singleton
	g_storm = this;
	SetPosition();
	// defaults
	storm_particles = 
	{
		Size = 1,
		Stretch = PV_Speed(PV_Linear(4000, 0), 0),
		Alpha = PV_KeyFrames(0, 0, 0, 100, 255, 1000, 255),
		Rotation = PV_Direction(),
		CollisionVertex = 1000,
		OnCollision = PC_Die()
	};
	StormStream = {
		max_segment_stretch = 100, // maximum number of pixels per segment that can be deviated from dir in either direction
		max_segment_stretch_want = 5, // maximum movement back into original position that is preferred (i.e.: speed at which gaps behind sky islands close)
		search_steps = 10,        // steps in pixels in which to search for holes to blow through
		search_steps_mult = 200,  // multiplyer, in percent, by which search steps get larger with each iteration
	};
	find_mask = Find_And(Find_Category(C4D_Vehicle | C4D_Living | C4D_Object), Find_Not(Find_Func("IsEnvironment")));
	SetStorm(20,0, 2000);
}

func Clear()
{
	// timer
	RemoveEffect("IntExecute", this);
	// helper objects
	for (var i=0; i<n_streams; ++i)
		if (streams[i].debug) streams[i].debug->RemoveObject();
	streams = nil;
	n_streams = 0;
	map = nil;
}

private func InitMap()
{
	// init empty wind map according to parameters
	// determine coordinate borders
	var wdt=LandscapeWidth()-1, hgt = LandscapeHeight()-1;
	var w1_min = Min(Min(MapXYToW1(0,0),MapXYToW1(0,hgt)),Min(MapXYToW1(wdt,0),MapXYToW1(wdt,hgt)));
	var w1_max = Max(Max(MapXYToW1(0,0),MapXYToW1(0,hgt)),Max(MapXYToW1(wdt,0),MapXYToW1(wdt,hgt)));
	var w2_min = Min(Min(MapXYToW2(0,0),MapXYToW2(0,hgt)),Min(MapXYToW2(wdt,0),MapXYToW2(wdt,hgt)));
	var w2_max = Max(Max(MapXYToW2(0,0),MapXYToW2(0,hgt)),Max(MapXYToW2(wdt,0),MapXYToW2(wdt,hgt)));
	// implement to cover complete border range
	map_res1 = StormStream.dir_len;
	map_res2 = stream_density;
	map_off1 = w1_min - map_res1/2;
	map_off2 = w2_min - map_res2/2;
	map_size1 = (w1_max - map_off1) / map_res1 + 1;
	map_size2 = (w2_max - map_off2) / map_res2 + 1;
	// allocate map
	map = CreateArray(map_size1 * map_size2);
	//debug_map = CreateArray(map_size1 * map_size2);
	return true;
}

private func MapXYToIdx(int x, int y)
{
	if (x<0 || x>=LandscapeWidth() || y<0 || y>=LandscapeHeight()) return -1;
	return (MapXYToW1(x, y)-map_off1)/map_res1 + ((MapXYToW2(x, y)-map_off2)/map_res2) * map_size1;
}

private func MapXYToW1(int x, int y)
{
	// coordinate transform from x/y space to in-wind-direction coordinate
	return (x*StormStream.dir_x + y*StormStream.dir_y) / StormStream.dir_len;
}

private func MapXYToW2(int x, int y)
{
	// coordinate transform from x/y space to perpendicular-to-wind-direction coordinate
	return (x*StormStream.dir_y - y*StormStream.dir_x) / StormStream.dir_len;
}

// dir_*: vector pointing in storm direction. vector length is equal to segment intervals.
// strength: how much to fling objects
func SetStorm(int dir_x, int dir_y, int astrength)
{
	// clear old
	Clear();
	// add new
	var d = Distance(dir_x, dir_y);
	if (!astrength || !d) return;
	strength = astrength;
	StormStream.dir_x = dir_x;
	StormStream.dir_y = dir_y;
	StormStream.dir_len = d;
	// init map
	InitMap();
	// create streams
	n_streams = ((Abs(LandscapeWidth()*dir_y) + Abs(LandscapeHeight()*dir_x))/d - 2*stream_border_dist) / stream_density;
	streams = CreateArray(n_streams);
	var i_stream = 0, s, x0, y0, sgn_x=1, sgn_y=1;
	var wdt = LandscapeWidth()-1, hgt = LandscapeHeight()-1;
	if (dir_y<0) { y0 = hgt; sgn_y = -1; }
	if (dir_x<0) { x0 = wdt; sgn_x = -1; }
	//Log("creating %d streams", n_streams);
	for (var i = 0; i<n_streams; ++i)
	{
		var pos = stream_border_dist + i * stream_density;
		var x0,y0;
		if (dir_y)
		{
			var dpos=Abs(pos*d/dir_y);
			if (dpos<=wdt)
			{
				// streams from horizontal border of landscape
				if (s=CreateStream(wdt-x0-dpos*sgn_x, y0)) streams[i_stream++] = s;
				continue;
			}
			pos -= Abs(wdt*dir_y/d);
		}
		// streams from vertical border of landscape
		pos=Abs(pos*d/dir_x);
		//Log("@%d", pos);
		if (s=CreateStream(x0, y0+pos*sgn_y)) streams[i_stream++] = s;
	}
	n_streams=i_stream;
	//Log("%d total", n_streams);
	streams = streams[0:n_streams];
	// create timer for stream execution
	n_exec_per_loop = n_streams;
	AddEffect("IntExecute", this, 1, exec_interval, this);
}

func FxIntExecuteTimer()
{
	for (var i_exec=0; i_exec<n_exec_per_loop; ++i_exec)
	{
		// exec current stream
		//Log("exec %d", execution_index);
		ExecuteStream(streams[execution_index]);
		// next stream (++execution_index %= n_streams may or may not work)
		++execution_index;
		execution_index %= n_streams;
	}
}

private func CreateStream(int x0, int y0)
{
	//Log("stream at %d/%d", x0,y0);
	// Not in earth
	if (GBackSolid(x0, y0)) return nil;
	// Determine length
	var len_x = max_stream_len, len_y = max_stream_len;
	if (StormStream.dir_x < 0)
		len_x = -(x0-1) / StormStream.dir_x + 1;
	else if (StormStream.dir_x)
		len_x = (LandscapeWidth()-x0-1) / StormStream.dir_x + 1;
	if (StormStream.dir_y < 0)
		len_y = -(y0-1) / StormStream.dir_y + 1;
	else if (StormStream.dir_y)
		len_y = (LandscapeHeight()-y0-1) / StormStream.dir_y + 1;
	var len = Min(len_x, len_y);
	// Initialize as laminar stream along desired path
	var x = CreateArray(len), y = CreateArray(len);
	var is_blocked = CreateArray(len);
	for (var i=0; i<len; ++i)
	{
		x[i] = x0+i*StormStream.dir_x;
		y[i] = y0+i*StormStream.dir_y;
		is_blocked[i] = (i>0); // initial stream is blocked and will be unblocked on first execution
	}
	// Create stream data struct
	var stream_debug;
	if (storm_debug) stream_debug = CreateObjectAbove(Storm_DebugDisplay,0,0,NO_OWNER);
	var new_stream = {
		Prototype = StormStream,
		"x0" = x0, "y0" = y0, // "a"=a because Guenther said so
		"len" = len,
		"x" = x, "y" = y,
		"is_blocked" = is_blocked,
		"debug" = stream_debug,
	};
	return new_stream;
}

private func ExecuteStream(proplist s)
{
	//Log("ExecStream %v", s);
	// Execute stream against wind direction, so changes dont propagate immediately but only segment-by-segment
	var do_particles = !Random(3);
	for (var i_segment = s.len-2; i_segment>=0; --i_segment)
	{
		// propagate block
		if (s.is_blocked[i_segment])
		{
			//Log("segment %d", i_segment);
			if (!s.is_blocked[i_segment+1]) StreamBlockVertex(s, i_segment+1);
			if (storm_debug)
				CreateParticle("SphereSpark", s.x[i_segment], s.y[i_segment], 0, 0, 36, {Size =  12});
			continue;
		}
		// current segment base point
		var x = s.x[i_segment], y = s.y[i_segment];
		var tx = s.x[i_segment+1], ty = s.y[i_segment+1];
		// determine direction of current segment
		var vx = tx - x;
		var vy = ty - y;

		// determine where we want to go
		var want_vx = s.x0+(i_segment+1)*s.dir_x - x;
		var want_vy = s.y0+(i_segment+1)*s.dir_y - y;

		var want_stretch = (s.dir_x*want_vy-s.dir_y*want_vx) / s.dir_len;
		//if (i_segment==8) Log("%v", want_stretch);
		// can turn?
		if (Abs(want_stretch) > s.max_segment_stretch_want)
		{
			// We cannot go all the way...turn as much as we can
			var stretch_dir = Abs(want_stretch)/want_stretch; // sign of direction
			want_stretch = s.max_segment_stretch_want * stretch_dir;
		}
		// check from want_v alternating in both directions for a free path
		var search_range = (Abs(want_stretch) + s.max_segment_stretch);
		var search_off, has_found = false;
		for (var search_offset = 0; search_offset <= search_range; search_offset = search_offset * s.search_steps_mult/100 + s.search_steps)
		{
			// search up
			search_off = want_stretch - search_offset;
			if (search_off >= -s.max_segment_stretch)
				if (StreamCheckPathFree(s,x,y,search_off)) { has_found=true; break; }
			if (!search_offset) continue; // don't check direction -0 and +0 twice
			// search down
			search_off = want_stretch + search_offset;
			if (search_off <= s.max_segment_stretch)
				if (StreamCheckPathFree(s,x,y,search_off)) { has_found=true; break; }
		}
		// did we find a path?
		if (has_found)
		{
			// path found
			if (s.is_blocked[i_segment+1]) StreamUnblockVertex(s, i_segment+1);
			var new_tx = x + s.dir_x - search_off * s.dir_y / s.dir_len;
			var new_ty = y + s.dir_y + search_off * s.dir_x / s.dir_len;
			if (new_tx != tx || new_ty != ty) StreamMoveVertex(s, i_segment+1, tx, ty, new_tx, new_ty);
			tx = new_tx; ty = new_ty;
			// determine storm density at this position
			var map_idx = MapXYToIdx(tx, ty), local_strength;
			if (map_idx>=0) local_strength = map[map_idx]; else local_strength=1;
			// fling objects along path
			vx = vx * strength / s.dir_len;
			vy = vy * strength / s.dir_len; // - 20;
			var fling_objs = FindObjects(find_mask, Find_OnLine(x,y,new_tx,new_ty)), obj;
			for (obj in fling_objs) if (obj->GetID()==ElevatorCase) { fling_objs = []; break; } // do not fling stuff in elevator case
			for (obj in fling_objs)
			{
				// check if object can be pushed
				if (obj->Stuck()) continue;
				if (!PathFree(x,y,obj->GetX(),obj->GetY())) continue; // don't push through solid
				// determine push strength. subsequent pushes of overlapping storm pathes stack diminishingly
				var push_strength = strength/20,pushfx;
				if (pushfx=GetEffect("StormPush",obj))
				{
					push_strength /= pushfx.count++;
					if (!push_strength) continue;
				}
				else
				{
					pushfx=AddEffect("StormPush", obj, 1, 5, this);
					if (pushfx) pushfx.count = 1;
				}
				// now push
				var ovx = obj->GetXDir(100);
				var ovy = obj->GetYDir(100);
				// check max speed
				if (Distance(ovx,ovy,vx,vy) > push_strength*6)
				{
					if (Distance(ovx,ovy) > 500)
						obj->Fling(BoundBy(vx-ovx,-push_strength,push_strength),BoundBy(vy-ovy,-push_strength,push_strength),100,true);
					else
					{
						obj->SetXDir(ovx+BoundBy(vx-ovx,-push_strength,push_strength),100);
						obj->SetYDir(ovy+BoundBy(vy-ovy,-push_strength,push_strength),100);
					}
				}
			}
			// Gfx
			if (do_particles && map_idx>=0)
			{
				if (local_strength >= 1)
				{
					// Two streams coincide here. Gfx!
					vx = tx-x; vy = ty-y;
					var v = Distance(vx,vy);
					vx = vx * s.dir_len / v;
					vy = vy * s.dir_len / v / 2;
					CreateParticle("Dust", PV_Random(x - 10, x + 10), PV_Random(y - 10, y + 10), PV_Random(vx * 80 / 100, vx * 120 / 100), PV_Random(vy, vy * 140 / 100), PV_Random(20, 40), storm_particles,local_strength); 
				}
			}
		}
		else
		{
			// path not found. segment blocked.
			if (!s.is_blocked[i_segment+1]) StreamBlockVertex(s, i_segment+1);
		}
	}
	if (s.debug) s.debug->ShowData(s.x, s.y);
}

private func StreamCheckPathFree(proplist s, int x, int y, int offset)
{
	// determine target coordinates
	var tx = x + s.dir_x - offset * s.dir_y / s.dir_len;
	var ty = y + s.dir_y + offset * s.dir_x / s.dir_len;
	// check path
	return PathFree(x,y,tx,ty);
}

private func StreamMoveVertex(proplist s, int i, int old_x, int old_y, int new_x, int new_y)
{
	//Log("moving %d/%d to %d/%d", old_x, old_y, new_x, new_y);
	// adjust vertex
	s.x[i] = new_x; s.y[i] = new_y;
	// adjust map
	var idx = MapXYToIdx(old_x, old_y);
	if (idx>=0) --map[idx];
	//DebugMapAdd(idx, Format("m%d.%d", s.y0, i));
	idx = MapXYToIdx(new_x, new_y);
	if (idx>=0) ++map[idx];
	//DebugMapAdd(idx, Format("M%d.%d", s.y0, i));
	return true;
}

private func StreamBlockVertex(proplist s, int i)
{
	//Log("blocking at %d/%d", s.x[i], s.y[i]);
	// adjust vertex
	s.is_blocked[i] = true;
	// adjust map
	var idx = MapXYToIdx(s.x[i], s.y[i]);
	if (idx>=0) --map[idx];
	//DebugMapAdd(idx, Format("X%d.%d", s.y0, i));
	return true;
}

private func StreamUnblockVertex(proplist s, int i)
{
	//Log("unblocking at %d/%d", s.x[i], s.y[i]);
	// adjust vertex
	s.is_blocked[i] = false;
	// adjust map
	var idx = MapXYToIdx(s.x[i], s.y[i]);
	if (idx>=0) ++map[idx];
	//DebugMapAdd(idx, Format("O%d.%d", s.y0, i));
	return true;
}

private func DumpStreamInfo(int i)
{
	var s = streams[i], q=[], idcs = [];
	for (var j=0; j<s.len; ++j)
	{
		var idx = MapXYToIdx(s.x[j], s.y[j]);
		if (idx<0) q[j] = []; else q[j] = map[idx];
		idcs[j] = idx;
	}
	Log("%v", idcs);
	return q;
}

/*private func DebugMapAdd(int i, string s)
{
	if (i<0) return;
	if (debug_map[i]) debug_map[i] = Format("%s %s", debug_map[i], s); else debug_map[i] = s;
	return true;
}*/

func GetWindEx(int x, int y)
{
	if (!map) return 0; // not initialized or zero storm
	var idx = MapXYToIdx(x, y);
	if (idx<0) return 0; // outside landscape
	// check storm density map
	return -BoundBy(map[idx]*strength/10, 0,100);
}

global func GetWind(int x, int y)
{
	if (g_storm) return g_storm->GetWindEx(x+GetX(),y+GetY());
	return _inherited(x,y,...);
}