File: Script.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 (458 lines) | stat: -rw-r--r-- 13,263 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
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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/**
	Defense Enemy
	
	Defines standard enemies which attack the player or its base. The enemy is a list of properties, the main ones are:
	 * Type (id)           - The type of enemy (defaults to Clonk).
	 * Amount (int)        - The amount of this type of enemy (use to create large groups).
	 * Interval (int)      - Spawn an enemy every X frames.
	 * Bounty (int)        - The amount of clunkers received for killing the enemy.
	 * Score (int)         - The amount of points obtained for beating this enemy.
	 * Position (proplist) - The position where the enemy should be spawned {X = ??, Y = ??, Exact = true/false}.
	Secondary properties are:
	 * Energy              - Alternative amount of hitpoints of the clonk.
	 * Skin                - Alternative skin for the clonk.
	 * Inventory           - Items for the clonk, either ID or list of IDs.
	 * Vehicle             - Vehicle the enemy is controlling or riding.
	 * VehicleHP           - Hitpoints for the enemy vehicle, can be made stronger.
	 * VehicleInventory    - Items for the vehicle, either ID or list of IDs.
	 * IsCrew              - Is part of a crew of the AI that is controlling the vehicle.
	
	This also defines standard waves which attack the player or its base. The wave is a list of properties, the main ones are:
	 * Name (string)       - The name of the attack wave.
	 * Duration (int)      - Duration of the wave in seconds, if nil the next wave is only launched when this is fully eliminated.
	 * Bounty (int)        - The amount of clunkers received for beating the wave.
	 * Score (int)         - The amount of points obtained for beating this wave.
	 * Enemies (array)     - Array of enemies, see DefenseEnemy for more information.	
	
	@author Maikel
 */


/*-- Enemy Launching --*/

// Definition call which can be used to launch an enemy.
public func LaunchEnemy(proplist prop_enemy, int wave_nr, int enemy_plr)
{
	if (GetType(this) != C4V_Def)
		Log("WARNING: LaunchEnemy(%v, %d, %d) not called from definition context but from %v.", prop_enemy, wave_nr, enemy_plr, this);
		
	// Don't launch the enemy when amount equals zero.
	if (!prop_enemy || prop_enemy.Amount <= 0)
		return;	
	// Determine enemy size.
	var def = prop_enemy.Type ?? prop_enemy.Vehicle;
	if (!def) 
		def = Clonk;
	var width = def->GetDefWidth();
	var height = def->GetDefWidth();

	// If no position spawn at the top of the map and log a warning.
	var pos = prop_enemy.Position;
	if (!pos)
	{
		pos = {X = Random(LandscapeWidth()), Y = 0};
		Log("WARNING: launching enemy %v, but has no position specified and will be created at (%d, %d).", prop_enemy, pos.X, pos.Y);
	}

	// Determine where to spawn the enemy with some variation.	
	var xmin, xmax, ymin, ymax;
	var variation = 100;
	if (pos.Exact)
		variation = 0;
	xmin = BoundBy(pos.X - variation, 0 + width / 2, LandscapeWidth() - width / 2);
	xmax = BoundBy(pos.X + variation, 0 + width / 2, LandscapeWidth() - width / 2);
	ymin = BoundBy(pos.Y - variation, 0 + height / 2, LandscapeHeight() - height / 2);
	ymax = BoundBy(pos.Y + variation, 0 + height / 2, LandscapeHeight() - height / 2);
	var rect = Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
	
	// Show an arrow for the enemy position.
	CreateArrowForPlayers((xmin + xmax) / 2, (ymin + ymax) / 2);
		
	// Schedule spawning of enemy definition for the given amount.
	var interval = Max(prop_enemy.Interval, 1);
	var amount = prop_enemy.Amount;
	ScheduleCall(nil, DefenseEnemy.LaunchEnemyAt, interval, amount, prop_enemy, wave_nr, enemy_plr, rect);	
	return;	
}

private func LaunchEnemyAt(proplist prop_enemy, int wave_nr, int enemy_plr, proplist rect)
{
	// Create enemy (per default a Clonk) at the given location.
	var x = rect.x + Random(rect.wdt);
	var y = rect.y + Random(rect.hgt);
	var enemy = CreateObjectAbove(prop_enemy.Type ?? Clonk, x, y, enemy_plr);
	if (!enemy)
		return nil;
	enemy->MakeCrewMember(enemy_plr);
	enemy->SetController(enemy_plr);
	GameCallEx("OnEnemyCreation", enemy, wave_nr);
	// Enemy visuals.
	UpdateEnemyVisuals(enemy, prop_enemy);
	// Update physical properties.
	UpdateEnemyPhysicals(enemy, prop_enemy);
	// Reward for killing enemy: clunker bounty and points.
	enemy.Bounty = prop_enemy.Bounty;
	enemy.Score = prop_enemy.Score;
	// Vehicles for enemies that are not a crew of a vehicle.
	var vehicle;
	if (prop_enemy.Vehicle && !prop_enemy.IsCrew)
	{
		if (prop_enemy.Vehicle == Balloon)
		{
			var balloon = enemy->CreateContents(Balloon);
			balloon->ControlUseStart(enemy);
		}
		else if (prop_enemy.Vehicle == DefenseBoomAttack)
		{
			vehicle = CreateObjectAbove(prop_enemy.Vehicle, x, y + 10, enemy_plr);
			enemy->SetAction("Ride", vehicle);
			// Add boom attack to enemy list.
			GameCallEx("OnEnemyCreation", vehicle, wave_nr);
		}
		else if (prop_enemy.Vehicle == Airplane)
		{
			vehicle = CreateObjectAbove(prop_enemy.Vehicle, x, y + 10, enemy_plr);
			enemy->Enter(vehicle);
			vehicle->PlaneMount(enemy);
			// Assume the plane is at one landscape side and wants to fly to the opposite side.
			if (vehicle->GetX() > LandscapeWidth() / 2)
				vehicle->FaceLeft();
			vehicle->StartInstantFlight(vehicle->GetR(), 15);
		}
		else
		{
			vehicle = CreateObjectAbove(prop_enemy.Vehicle, x, y + 10, enemy_plr);
			enemy->SetAction("Push", vehicle);
			vehicle.pilot = enemy;
		}
		// Give the vehicle more hitpoints.
		if (prop_enemy.VehicleHP)
			vehicle.HitPoints = prop_enemy.VehicleHP;
		// Add the enemy vehicle to no friendly fire rule (must be done by hand, noFF rule is for crew only be default).
		if (vehicle)
			GameCallEx("OnCreationRuleNoFF", vehicle);
	}
	// Move crew members onto their vehicles.
	if (prop_enemy.Vehicle && prop_enemy.IsCrew)
	{
		var crew_vehicle = enemy->FindObject(Find_Distance(50), Find_ID(prop_enemy.Vehicle), Sort_Distance());
		if (crew_vehicle)
		{
			// Set commander for crew.
			enemy.commander = crew_vehicle.pilot;		
		}	
	}
	// Vehicle inventory.
	if (vehicle && prop_enemy.VehicleInventory)
	{
		for (var inv in ForceToInventoryArray(prop_enemy.VehicleInventory))
		{
			var inv_obj = vehicle->CreateContents(inv);
			// Infinite ammo.
			if (inv_obj)
				inv_obj->~SetInfiniteStackCount();
		}
	}
	// Enemy inventory.
	if (prop_enemy.Inventory)
	{
		for (var inv in ForceToInventoryArray(prop_enemy.Inventory))
		{
			// Special way to pick up carry heavy objects instantly.
			if (inv->~IsCarryHeavy() && (enemy->GetOCF() & OCF_CrewMember))
				inv_obj = enemy->CreateCarryHeavyContents(inv);
			else
				inv_obj = enemy->CreateContents(inv);
			// Infinite ammo.
			if (inv_obj)
				inv_obj->~SetInfiniteStackCount();
		}
	}
	// Add AI.
	if (!enemy->~HasNoNeedForAI())
	{
		DefenseAI->AddAI(enemy);
		DefenseAI->SetMaxAggroDistance(enemy, LandscapeWidth());
		DefenseAI->SetGuardRange(enemy, 0, 0, LandscapeWidth(), LandscapeHeight());
		// Add vehicle to AI.
		if (vehicle)
			DefenseAI->SetVehicle(enemy, vehicle);
	}
	return enemy;
}

private func UpdateEnemyVisuals(object enemy, proplist prop_enemy)
{
	if (prop_enemy.Skin)
	{
		if (GetType(prop_enemy.Skin) == C4V_Array)
		{
			enemy->SetSkin(prop_enemy.Skin[0]);
			enemy->SetMeshMaterial(prop_enemy.Skin[1]);
		}
		else
			enemy->SetSkin(prop_enemy.Skin);
	}
	if (GetType(prop_enemy.Backpack)) enemy->~RemoveBackpack();
	if (prop_enemy.Scale) enemy->SetMeshTransformation(Trans_Scale(prop_enemy.Scale[0], prop_enemy.Scale[1], prop_enemy.Scale[2]), CLONK_MESH_TRANSFORM_SLOT_Scale);
	if (prop_enemy.Name) enemy->SetName(prop_enemy.Name);
	enemy->SetColor(prop_enemy.Color);
	return;
}

private func UpdateEnemyPhysicals(object enemy, proplist prop_enemy)
{
	enemy.MaxEnergy = (prop_enemy.Energy ?? 50) * 1000;
	enemy->DoEnergy(enemy.MaxEnergy / 1000);
	if (prop_enemy.Speed)
	{
		// Speed: Modify Speed in all ActMap entries
		if (enemy.ActMap == enemy.Prototype.ActMap) enemy.ActMap = new enemy.ActMap {};
		for (var action in /*obj.ActMap->GetProperties()*/ ["Walk", "Scale", "Dig", "Swim", "Hangle", "Jump", "WallJump", "Dive", "Push"]) // obj.ActMap->GetProperties() doesn't work :(
		{
			if (action == "Prototype") continue;
			if (enemy.ActMap[action] == enemy.Prototype.ActMap[action]) enemy.ActMap[action] = new enemy.ActMap[action] {};
			enemy.ActMap[action].Speed = enemy.ActMap[action].Speed * enemy.Speed / 100;
		}
		enemy.JumpSpeed = enemy.JumpSpeed * prop_enemy.Speed / 100;
		enemy.FlySpeed = enemy.FlySpeed * prop_enemy.Speed / 100;
	}
	return;
}

private func ForceToInventoryArray(/*any*/ list)
{
	// Convert single ID to array.
	if (GetType(list) != C4V_Array)
		return [list];
	// Check in array if entries of the form [ID, amount] appear and convert them.
	for (var i = 0; i < GetLength(list); )
	{
		var element = list[i];
		if (GetType(element) == C4V_Array)
		{
			for (var j = 0; j < element[1]; j++)
				PushBack(list, element[0]);
			RemoveArrayValue(list, element);
			continue;
		}
		i++;
	}
	return list;
}

// Create an arrow which show the direction the enemy is coming from.
private func CreateArrowForPlayers(int x, int y)
{
	for (var plr in GetPlayers(C4PT_User))
	{
		var cursor = GetCursor(plr);
		if (!cursor) 
			continue;
		var arrow = CreateObject(GUI_GoalArrow, cursor->GetX(), cursor->GetY(), plr);
		if (!arrow) 
			continue;
		arrow->SetAction("Show", cursor);
		arrow->SetR(Angle(cursor->GetX(), cursor->GetY(), x, y));
		arrow->SetClrModulation(RGBa(255, 50, 0, 128));
		Schedule(arrow, "RemoveObject()", 8 * 36);
	}
	return;
}


/*-- Enemies --*/

static const CSKIN_Default   = 0,
             CSKIN_Steampunk = 1,
             CSKIN_Alchemist = 2,
             CSKIN_Farmer    = 3;

// Default enemy, all other enemies inherit from this.
local DefaultEnemy =
{
	Type = nil,
	Inventory = nil,
	Energy = nil,
	Amount = 1,
	Interval = 1,
	Bounty = 0,	
	Score = 0,
	Position = nil
};

// A clonk with a sword.
local Swordsman = new DefaultEnemy
{
	Name = "$EnemySwordsman$",
	Inventory = [Sword, Helmet],
	Energy = 30,
	Bounty = 20,
	Color=0xff0000ff
};

// A clonk with bow and arrow.
local Archer = new DefaultEnemy
{
	Name = "$EnemyArcher$",
	Inventory = [Bow, Arrow],
	Energy = 10,
	Bounty = 5,
	Color = 0xff00ff00,
	Skin = CSKIN_Farmer
};

// A clonk with javelins.
local Spearman = new DefaultEnemy
{
	Name = "$EnemySpearman$",
	Inventory = Javelin,
	Energy = 15,
	Bounty = 5,
	Color = 0xff0000ff,
	Skin = CSKIN_Steampunk
};

// A clonk with a grenade launcher.
local Grenadier = new DefaultEnemy
{
	Name = "$EnemyGrenadier$",
	Inventory = [GrenadeLauncher, [IronBomb, 8]],
	Energy = 25,
	Bounty = 5,
	Color = 0xffa0a0ff,
	Skin = CSKIN_Alchemist
};

// A rocket which moves to a target.
local BoomAttack = new DefaultEnemy
{
	Name = "$EnemyBoomAttack$",
	Type = DefenseBoomAttack,
	Bounty = 2
};

// A faster rocket which moves to a target.
local RapidBoomAttack = new DefaultEnemy
{
	Name = "$EnemyRapidBoomAttack$",
	Type = DefenseBoomAttack,
	Bounty = 2,
	Speed = 300
};

// A parachutist coming from above with a balloon.
local Ballooner = new DefaultEnemy
{
	Name = "$EnemyBallooner$",
	Inventory = Sword,
	Energy = 30,
	Bounty = 15,
	Color = 0xff008000,
	Skin = CSKIN_Default,
	Vehicle = Balloon
};

// An archer riding a boom attack.
local Rocketeer = new DefaultEnemy
{
	Name = "$EnemyRocketeer$",
	Inventory = [Bow, Arrow],
	Energy = 15,
	Bounty = 15,
	Color = 0xffffffff,
	Skin = CSKIN_Steampunk,
	Vehicle = DefenseBoomAttack
};

// An archer riding a boom attack.
local Bomber = new DefaultEnemy
{
	Name = "$EnemyBomber$",
	Inventory = PowderKeg,
	Energy = 50,
	Bounty = 10,
	Color = 0xff55aaff,
	Skin = CSKIN_Default
};

// Commander of the airship.
local AirshipPilot = new DefaultEnemy
{
	Name = "$EnemyAirshipPilot$",
	Inventory = [[Rock, 5]],
	Energy = 35,
	Bounty = 15,
	Color = 0xffff00ff,
	Skin = CSKIN_Alchemist,
	Vehicle = Airship,
	VehilceHP = 100
};

// Crew of the airship.
local AirshipCrew = new DefaultEnemy
{
	Name = "$EnemyAirshipCrew$",
	Inventory = [Sword, Shield],
	Energy = 35,
	Bounty = 5,
	Color = 0xff0000aa,
	Skin = CSKIN_Farmer,
	Vehicle = Airship,
	IsCrew = true
};

// A pilot and a bomber plane.
local BomberPlane = new DefaultEnemy
{
	Name = "$EnemyBomberPlane$",
	Inventory = [],
	Energy = 35,
	Bounty = 20,
	Color = 0xffaaddff,
	Skin = CSKIN_Alchemist,
	Vehicle = Airplane,
	VehicleHP = 50,
	VehicleInventory = [[IronBomb, 8], [Dynamite, 4]]
};


/*-- Wave Launching --*/

// Definition call which can be used to launch an attack wave.
public func LaunchWave(proplist prop_wave, int wave_nr, int enemy_plr)
{
	if (GetType(this) != C4V_Def)
		Log("WARNING: LaunchWave(%v, %d, %d) not called from definition context but from %v.", prop_wave, wave_nr, enemy_plr, this);

	// Create count down until next wave and play wave start sound.
	GUI_Clock->CreateCountdown(prop_wave.Duration);
	CustomMessage(Format("$MsgWave$", wave_nr, prop_wave.Name));
	Sound("UI::Ding");

	// Launch enemies.
	if (prop_wave.Enemies)
		for (var enemy in prop_wave.Enemies)
			this->LaunchEnemy(enemy, wave_nr, enemy_plr);
	return;
}


/*-- Waves --*/

// Default wave, all other waves inherit from this.
local DefaultWave =
{
	Name = nil,
	Duration = nil,
	Bounty = nil,
	Score = nil,
	Enemies = nil
};

// A wave with no enemies: either at the start or to allow for a short break.
local BreakWave = new DefaultWave
{
	Name = "$WaveBreak$",
	Duration = 60
};