File: ailua.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,716 kB
  • sloc: cpp: 595,001; ansic: 21,741; python: 1,174; sh: 457; makefile: 248; xml: 181
file content (414 lines) | stat: -rw-r--r-- 12,758 bytes parent folder | download
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
#include "ai/ailua.h"

#include "hud/hudsquadmsg.h"
#include "iff_defs/iff_defs.h"
#include "parse/sexp/sexp_lookup.h"
#include "parse/sexp/LuaAISEXP.h"
#include "scripting/api/objs/ai_helper.h"
#include "scripting/api/objs/enums.h"
#include "scripting/api/objs/oswpt.h"
#include "scripting/api/objs/ship.h"
#include "scripting/scripting.h"

static std::unordered_map<int, ai_mode_lua> Lua_ai_modes;
static std::unordered_map<int, player_order_lua> Lua_player_orders;

void ai_lua_add_mode(int sexp_op, const ai_mode_lua& mode) {
	Lua_ai_modes.emplace(sexp_op, mode);
}

bool ai_lua_add_order(int sexp_op, player_order_lua order) {
	Player_orders.emplace_back(order.parseText, order.displayText, -1, sexp_op);
	Player_orders.back().localize();
	
	Lua_player_orders.emplace(sexp_op, std::move(order));

	return true;
}

bool ai_lua_has_mode(int sexp_op){
	return Lua_ai_modes.count(sexp_op) > 0;
}

const ai_mode_lua* ai_lua_find_mode(int sexp_op){
	auto aiLuaMode = Lua_ai_modes.find(sexp_op);
	
	if(aiLuaMode == Lua_ai_modes.end())
		return nullptr;
	else
		return &aiLuaMode->second;
}

const player_order_lua* ai_lua_find_player_order(int sexp_op) {
	auto aiLuaOrder = Lua_player_orders.find(sexp_op);

	if (aiLuaOrder == Lua_player_orders.end())
		return nullptr;
	else
		return &aiLuaOrder->second;
}

int ai_lua_get_num_general_orders() {
	int count = 0;
	
	for (const auto& order : Lua_player_orders) {
		if (order.second.generalOrder) {
			count++;
		}
	}

	return count;
}

SCP_vector<SCP_string> ai_lua_get_general_order_categories(bool enabled_only)
{
	SCP_vector<SCP_string> list;

	for (const auto& order : Lua_player_orders) {
		const SCP_string &cat = order.second.category;

		// If it's not a general order then move on
		if (!order.second.generalOrder) {
			continue;
		}

		// If the order is not enabled then move on
		if (enabled_only && !order.second.cur_enabled) {
			continue;
		}

		Assertion((cat.length() > 0), "Lua general orders category name must be longer than 0 characters!");

		for (size_t i = 0; i <= list.size(); i++) {

			// reached the end of the list, so add it
			if (i == list.size()) {
				list.push_back(cat);
				break;
			}

			// if it's already in the list then don't add it
			if (cat == list[i]) {
				break;
			}
		}
	}

	return list;
}

// Provides a list of general orders for mission saving. You can only get one kind
// of list at a time. If onlyEnabled and onlyValid are both true, for example, you will get
// a list of onlyEnabled orders. If you need a list of enabled orders that are also valid, you'll
// need to get both lists separately and compare.
SCP_vector<SCP_string> ai_lua_get_general_orders(bool onlyEnabled, bool onlyValid, const SCP_string& category)
{
	SCP_vector<SCP_string> list;

	for (const auto& order : Lua_player_orders) {
		if (order.second.generalOrder) {
			bool add = false;

			// All general orders
			if (!onlyEnabled && !onlyValid && category.empty()) {
				add = true;
			} else {

				// Only enabled orders
				if (onlyEnabled && order.second.cur_enabled) {
					add = true;

					// Only valid orders
				} else if (onlyValid && order.second.cur_valid) {
					add = true;

					// Only orders that match the category requested
				} else if ((!category.empty()) && (order.second.category == category)) {
					add = true;
				}
			}

			if (add) {
				list.push_back(order.second.parseText);
			}
		}
	}

	return list;
}

int ai_lua_find_general_order_id(const SCP_string& name) {
	for (const auto& order : Lua_player_orders) {
		if (order.second.parseText == name) {
			return order.first;
		}
	}

	return -1;
}

void ai_lua_enable_general_order(int sexp_op, bool enable) {
	auto aiLuaOrder = Lua_player_orders.find(sexp_op);

	if (aiLuaOrder == Lua_player_orders.end())
		return;
	else
		aiLuaOrder->second.cur_enabled = enable;
}

void ai_lua_validate_general_order(int sexp_op, bool validity)
{
	auto aiLuaOrder = Lua_player_orders.find(sexp_op);

	if (aiLuaOrder == Lua_player_orders.end())
		return;
	else
		aiLuaOrder->second.cur_valid = validity;
}

void ai_lua_reset_general_orders() {
	for (const auto& order : Lua_player_orders) {
		ai_lua_enable_general_order(order.first, false);
		ai_lua_validate_general_order(order.first, false);
	}
}

bool ai_lua_is_general_order(int sexp_op) {
	auto aiLuaOrder = Lua_player_orders.find(sexp_op);

	if (aiLuaOrder == Lua_player_orders.end())
		return false;

	return aiLuaOrder->second.generalOrder;
}

void run_ai_lua_action(const luacpp::LuaFunction& action, const ai_mode_lua& lua_ai, ai_info* aip) {
	if (!action.isValid()) {
		Error(LOCATION,
			"Lua AI SEXP called without a valid action function! A script probably failed to set the action for some reason.");
		return;
	}

	luacpp::LuaValueList luaParameters;
	if (aip->shipnum >= 0){
		luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_AI_Helper.Set(object_h(&Objects[Ships[aip->shipnum].objnum]))));
	} else {
		luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_AI_Helper.Set(object_h())));
	}
	if (lua_ai.needsTarget) {
		luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(aip->lua_ai_target.target)));
	}
	for (const auto& additionalParam : aip->lua_ai_target.arguments) {
		luaParameters.push_back(additionalParam);
	}

	auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);

	if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::BOOLEAN) {
		if (retVals[0].getValue<bool>()) {

			// If we don't have a ship then it's a general order and we have nothing else to do
			if (aip->shipnum >= 0) {
				ai_mission_goal_complete(aip);
			}
		}
	}
}

void ai_lua(ai_info* aip){
	Assertion(aip->mode == AIM_LUA, "Tried to invoke LuaAI on a ship not in LuaAI-Mode");

	const auto& lua_ai = Lua_ai_modes.at(aip->submode);
	
	const auto& lua_ai_sexp = lua_ai.sexp;
	const auto& action = lua_ai_sexp.getAction();

	run_ai_lua_action(action, lua_ai, aip);
}

void ai_lua_start(ai_goal* aigp, object* objp){

	Assert(objp->type == OBJ_SHIP);
	Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));

	ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];

	aip->mode = AIM_LUA;
	aip->submode = aigp->ai_submode;
	aip->lua_ai_target = aigp->lua_ai_target;
	aip->submode_start_time = Missiontime;
	
	const auto& lua_ai = Lua_ai_modes.at(aigp->ai_submode);

	const auto& lua_ai_sexp = lua_ai.sexp;
	const auto& action = lua_ai_sexp.getActionEnter();

	run_ai_lua_action(action, lua_ai, aip);
}

// For sending general orders that don't run on a ship.
// Essentially just runs arbitrary lua script when the command is called
void ai_lua_start_general(int lua_sexp_id, int target_objnum)
{
	//Create a dummy aip to pass the target data
	ai_info aip;
	aip.shipnum = -1;

	if (target_objnum >= 0 && Objects[target_objnum].type == OBJ_SHIP) {
		aip.lua_ai_target = {object_ship_wing_point_team(&Ships[Objects[target_objnum].instance]), {}};
	}
	
	const auto& lua_ai = ai_lua_find_mode(lua_sexp_id);

	const auto& lua_ai_sexp = lua_ai->sexp;
	const auto& action = lua_ai_sexp.getActionEnter();

	run_ai_lua_action(action, *lua_ai, &aip);
}

bool ai_lua_is_valid_target_intrinsic(int sexp_op, int target_objnum, ship* self) {
	ship* target = &Ships[Objects[target_objnum].instance];

	const player_order_lua& order = *ai_lua_find_player_order(sexp_op);
	switch (order.targetRestrictions) {
	case player_order_lua::target_restrictions::TARGET_ALL:
		return true;
	case player_order_lua::target_restrictions::TARGET_OWN:
		return target->team == self->team;
	case player_order_lua::target_restrictions::TARGET_ALLIES:
		return !iff_x_attacks_y(self->team, target->team);
	case player_order_lua::target_restrictions::TARGET_ENEMIES:
		return iff_x_attacks_y(self->team, target->team);
	case player_order_lua::target_restrictions::TARGET_SAME_WING:
		return target->wingnum != -1 && self->wingnum == target->wingnum;
	case player_order_lua::target_restrictions::TARGET_PLAYER_WING:
		return target->wingnum != -1 && Ships[Player_obj->instance].wingnum == target->wingnum;
	case player_order_lua::target_restrictions::TARGET_ALL_CAPS:
		return Ship_info[target->ship_info_index].is_big_ship();
	case player_order_lua::target_restrictions::TARGET_ALLIED_CAPS:
		return !iff_x_attacks_y(self->team, target->team) && Ship_info[target->ship_info_index].is_big_ship();
	case player_order_lua::target_restrictions::TARGET_ENEMY_CAPS:
		return iff_x_attacks_y(self->team, target->team) && Ship_info[target->ship_info_index].is_big_ship();
	case player_order_lua::target_restrictions::TARGET_NOT_SELF:
		return self->objnum != target_objnum;
	}

	return false;
}

bool ai_lua_is_valid_target_lua(const ai_mode_lua& mode, int target_objnum, ship* self) {

	const auto& action = mode.sexp.getTargetRestrict();

	if (!action.isValid())
		return true;

	luacpp::LuaValueList luaParameters;
	luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_Ship.Set(object_h(&Objects[self->objnum]))));
	if (mode.needsTarget) {
		luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(object_ship_wing_point_team(&Ships[Objects[target_objnum].instance]))));
	}

	auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);

	if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::BOOLEAN) {
		return retVals[0].getValue<bool>();
	}
	else {
		Error(LOCATION, "LuaAI target restriction function did not return a valid boolean!");
	}

	return true;
}

bool ai_lua_is_valid_target(int sexp_op, int target_objnum, ship* self, size_t order) {
	const ai_mode_lua& mode = *ai_lua_find_mode(sexp_op);

	//All targetless AI modes are fine
	if (mode.needsTarget) {

		//No target is then not valid
		if (target_objnum == -1)
			return false;

		//As of now, only accept ships
		if (Objects[target_objnum].type != OBJ_SHIP)
			return false;

		if (!ai_lua_is_valid_target_intrinsic(sexp_op, target_objnum, self))
			return false;

		// check if this order can be issued against the target
		ship *shipp = &Ships[Objects[target_objnum].instance];
		if (shipp->orders_allowed_against.find(order) == shipp->orders_allowed_against.end()) {
			return false;
		}
	}

	//If we haven't bailed yet, query the custom callback
	return ai_lua_is_valid_target_lua(mode, target_objnum, self);
}

bool ai_lua_is_valid_ship(int sexp_op, bool isWing, ship* self)
{
	const player_order_lua& order = *ai_lua_find_player_order(sexp_op);

	switch (order.shipRestrictions) {
	case player_order_lua::ship_restrictions::ANY:
		return true;
	case player_order_lua::ship_restrictions::WING:
		return isWing;
	case player_order_lua::ship_restrictions::IN_PLAYER_WING:
		return self->wingnum != -1 && Ships[Player_obj->instance].wingnum == self->wingnum;
	case player_order_lua::ship_restrictions::PLAYER_WING:
		return isWing && self->wingnum != -1 && Ships[Player_obj->instance].wingnum == self->wingnum;
	}

	return false;
}

ai_achievability ai_lua_is_achievable(const ai_goal* aigp, int objnum){
	const auto& lua_ai = Lua_ai_modes.at(aigp->ai_submode);

	const auto& action = lua_ai.sexp.getAchievable();

	if (!action.isValid())
		return ai_achievability::ACHIEVABLE;

	luacpp::LuaValueList luaParameters;
	luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_Ship.Set(object_h(&Objects[objnum]))));
	if (lua_ai.needsTarget) {
		luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(aigp->lua_ai_target.target)));
	}
	for (const auto& additionalParam : aigp->lua_ai_target.arguments) {
		luaParameters.push_back(additionalParam);
	}

	auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);

	if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::USERDATA) {
		scripting::api::enum_h enumData;
		retVals[0].getValue(scripting::api::l_Enum.Get(&enumData));
		if (!enumData.isValid()) {
			Error(LOCATION, "LuaAI SEXP achievability hook returned an invalid avilability enum.");
			return ai_achievability::ACHIEVABLE;
		}

		switch (enumData.index) {
		case scripting::api::LE_LUAAI_ACHIEVABLE:
			return ai_achievability::ACHIEVABLE;
		case scripting::api::LE_LUAAI_NOT_YET_ACHIEVABLE:
			return ai_achievability::NOT_KNOWN;
		case scripting::api::LE_LUAAI_UNACHIEVABLE:
			return ai_achievability::NOT_ACHIEVABLE;
		default:
			Error(LOCATION, "LuaAI SEXP achievability hook returned an invalid avilability enum.");
			return ai_achievability::ACHIEVABLE;
		}
	}
	else {
		Error(LOCATION, "LuaAI SEXP achievability hook did not return any avilability enum!");
	}

	return ai_achievability::ACHIEVABLE;
}