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 (252 lines) | stat: -rw-r--r-- 6,803 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
/**
	AI Movement
	Functionality that helps the AI move.
	
	@author Sven2, Maikel
*/


/*-- Public interface --*/

// Set attack path
public func SetAttackPath(object clonk, array new_attack_path)
{
	AssertDefinitionContext(Format("SetAttackPath(%v, %v)", clonk, new_attack_path));
	var fx_ai = this->GetAI(clonk);
	if (!fx_ai)
		return false;
	fx_ai.attack_path = new_attack_path;
	return true;
}


/*-- Callbacks --*/

// Callback from the effect SaveScen()-call
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
{
	_inherited(fx_ai, props);

	if (fx_ai.attack_path)
		props->AddCall(SAVESCEN_ID_AI, fx_ai->GetControl(), "SetAttackPath", fx_ai.Target, fx_ai.attack_path);

}


/*-- Editor Properties --*/

// Callback from the Definition()-call
public func OnDefineAI(proplist def)
{
	_inherited(def);
	
	def->GetControlEffect().SetAttackPath = this.EditorDelegate_SetAttackPath;
	
	// Set the additional editor properties
	var additional_props =
	{
		attack_path = { Name = "$AttackPath$", EditorHelp = "$AttackPathHelp$", Type = "enum", Set = "SetAttackPath", Options = [
			{ Name="$None$" },
			{ Name="$AttackPath$", Type=C4V_Array, Value = [{X = 0, Y = 0}], Delegate =
				{ Name="$AttackPath$", EditorHelp="$AttackPathHelp$", Type="polyline", StartFromObject=true, DrawArrows=true, Color=0xdf0000, Relative=false }
			}
		] },
	};
	
	AddProperties(def->GetControlEffect().EditorProps, additional_props);
}


func EditorDelegate_SetAttackPath(array attack_path)
{
	// Called by editor delegate when attack mode is changed.
	// For now, attack mode parameter delegates are not supported. Just set by name.
	return this->GetControl()->SetAttackPath(this.Target, attack_path);
}


/*-- Internals --*/

// Tries to make sure the clonk stands: i.e. scales down or let's go when hangling.
public func ExecuteStand(effect fx)
{
	fx.Target->SetCommand("None");
	if (fx.Target->GetProcedure() == "SCALE")
	{
		var tx;
		if (fx.target)
			tx = fx.target->GetX() - fx.Target->GetX();
		// Scale: Either scale up if target is beyond this obstacle or let go if it's not.
		if (fx.Target->GetDir() == DIR_Left)
		{
			if (tx < -20)
				fx.Target->SetComDir(COMD_Left);
			else
				fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Right, 100); // let go
		}
		else
		{
			if (tx > -20)
				fx.Target->SetComDir(COMD_Right);
			else
				fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Left, 100); // let go
		}
	}
	else if (fx.Target->GetAction() == "Climb")
	{
		var climb_fx = GetEffect("IntClimbControl", fx.Target);
		if (climb_fx)
		{
			// For now just climb down the ladder.
			var ctrl = CON_Down;		
			EffectCall(fx.Target, climb_fx, "Control", ctrl, 0, 0, 100, false, CONS_Down);
		}	
	}
	else if (fx.Target->GetProcedure() == "HANGLE")
	{
		fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Down, 100);
	}
	else if (fx.Target->GetProcedure() == "FLIGHT" || fx.Target->GetAction() == "Roll")
	{
		// Don't do anything for these procedures and actions as they will end automatically.
	}
	else
	{		
		this->~LogAI_Warning(fx, Format("ExecuteStand has no idea what to do for action %v and procedure %v.", fx.Target->GetAction(), fx.Target->GetProcedure()));
		// Hm. What could it be? Let's just hope it resolves itself somehow...
		fx.Target->SetComDir(COMD_Stop);
	}
	return true;
}

// Evade a threat from the given coordinates.
public func ExecuteEvade(effect fx, int threat_dx, int threat_dy)
{
	// Don't try to evade if the AI has a commander, if an AI is being commanded
	// it has more important tasks, like staying on an airship.
	if (fx.commander)
		return false;
	// Evade from threat at position delta threat_dx, threat_dy.
	if (threat_dx < 0)
		fx.Target->SetComDir(COMD_Left);
	else
		fx.Target->SetComDir(COMD_Right);
	if (threat_dy >= -5 && !Random(2))
		if (this->ExecuteJump(fx))
			return true;
	// Shield? Todo.
	return true;
}

public func ExecuteJump(effect fx)
{
	// Jump if standing on floor.
	if (fx.Target->GetProcedure() == "WALK")
	{
		if (fx.Target->~ControlJump())
			return true; // For clonks.
		return fx.Target->Jump(); // For others.
	}
	return false;
}

// Follow attack path or return to the AI's home if not yet there.
public func ExecuteIdle(effect fx)
{
	// Persist commands because constant command resets may hinder execution
	if (fx.Target->GetCommand() && Random(4)) return true;
	// Follow attack path
	if (this->ExecuteAttackPath(fx)) return true;
	// Movement done (for now)
	fx.Target->SetCommand("None");
	fx.Target->SetComDir(COMD_Stop);
	fx.Target->SetDir(fx.home_dir);
	if (fx.vehicle) fx.vehicle->SetCommand();
	// Nothing to do.
	return false;
}

public func ExecuteAttackPath(effect fx)
{
	// Attack path is to follow the commander.
	if (fx.commander) return true;
	if (fx.attack_path)
	{
		// Follow attack path
		var next_pt = fx.attack_path[0];
		// Check for structure to kill on path. Only if the structure is alive or of the clonk can attack structures with the current weapon.
		var alive_check;
		if (!fx.can_attack_structures && !fx.can_attack_structures_after_weapon_respawn)
		{
			alive_check = Find_OCF(OCF_Alive);
		}
		if ((fx.target = FindObject(Find_AtPoint(next_pt.X, next_pt.Y), Find_Func("IsStructure"), alive_check)))
		{
			// Do not advance on path unless target(s) destroyed.
			return true;
		}
		// Follow path
		fx.home_x = next_pt.X;
		fx.home_y = next_pt.Y;
		fx.home_dir = Random(2);
	}
	// Check if we need to move/push to a target
	if (fx.vehicle)
	{
		if (!Inside(fx.vehicle->GetX() - fx.home_x, -15, 15) || !Inside(fx.vehicle->GetY() - fx.home_y, -20, 20))
		{
			if (fx.vehicle->~IsAirship())
			{
				if (!fx.vehicle->GetCommand())
				{
					fx.vehicle->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
				}
			}
			else
			{
				// Default vehicle movement
				return fx.Target->SetCommand("PushTo", fx.vehicle, fx.home_x, fx.home_y);
			}
			return true;
		}
	}
	else
	{
		if (!Inside(fx.Target->GetX() - fx.home_x, -5, 5) || !Inside(fx.Target->GetY() - fx.home_y, -15, 15))
		{
			return fx.Target->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
		}
	}
	// Next section on path or done?
	this->AdvanceAttackPath(fx);
	return false;
}

public func AdvanceAttackPath(effect fx)
{
	// Pick next element in attack path if an attack path is set. Return whether a path remains.
	if (fx.attack_path)
	{
		if (GetLength(fx.attack_path) > 1)
		{
			fx.attack_path = fx.attack_path[1:];
			return true;
		}
		else
		{
			fx.attack_path = nil;
		}
	}
	return false;
}

// Turns around the AI such that it looks at its target.
public func ExecuteLookAtTarget(effect fx)
{
	// Set direction to look at target, we can assume this is instantaneous.
	if (fx.target->GetX() > fx.Target->GetX())
		fx.Target->SetDir(DIR_Right);
	else
		fx.Target->SetDir(DIR_Left);
	return true;
}