1 /*
2    Copyright (C) 2009 - 2018 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #include "scripting/lua_unit.hpp"
16 
17 #include "formatter.hpp"
18 #include "game_board.hpp"
19 #include "log.hpp"
20 #include "map/location.hpp"             // for map_location
21 #include "map/map.hpp"
22 #include "resources.hpp"
23 #include "scripting/lua_common.hpp"
24 #include "scripting/lua_unit_attacks.hpp"
25 #include "scripting/push_check.hpp"
26 #include "scripting/game_lua_kernel.hpp"
27 #include "units/unit.hpp"
28 #include "units/map.hpp"
29 #include "units/animation_component.hpp"
30 
31 #include "lua/lauxlib.h"
32 #include "lua/lua.h"                    // for lua_State, lua_settop, etc
33 
34 static lg::log_domain log_scripting_lua("scripting/lua");
35 #define LOG_LUA LOG_STREAM(info, log_scripting_lua)
36 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
37 
38 static const char getunitKey[] = "unit";
39 static const char ustatusKey[] = "unit status";
40 static const char unitvarKey[] = "unit variables";
41 
~lua_unit()42 lua_unit::~lua_unit()
43 {
44 }
45 
get()46 unit* lua_unit::get()
47 {
48 	if (ptr) return ptr.get();
49 	if (c_ptr) return c_ptr;
50 	if (side) {
51 		return resources::gameboard->get_team(side).recall_list().find_if_matches_underlying_id(uid).get();
52 	}
53 	unit_map::unit_iterator ui = resources::gameboard->units().find(uid);
54 	if (!ui.valid()) return nullptr;
55 	return ui.get_shared_ptr().get(); //&*ui would not be legal, must get new shared_ptr by copy ctor because the unit_map itself is holding a boost shared pointer.
56 }
get_shared()57 unit_ptr lua_unit::get_shared()
58 {
59 	if (ptr) return ptr;
60 	if (side) {
61 		return resources::gameboard->get_team(side).recall_list().find_if_matches_underlying_id(uid);
62 	}
63 	unit_map::unit_iterator ui = resources::gameboard->units().find(uid);
64 	if (!ui.valid()) return unit_ptr();
65 	return ui.get_shared_ptr(); //&*ui would not be legal, must get new shared_ptr by copy ctor because the unit_map itself is holding a boost shared pointer.
66 }
67 
68 // Having this function here not only simplifies other code, it allows us to move
69 // pointers around from one structure to another.
70 // This makes bare pointer->map in particular about 2 orders of magnitude faster,
71 // as benchmarked from Lua code.
put_map(const map_location & loc)72 bool lua_unit::put_map(const map_location &loc)
73 {
74 	if (ptr) {
75 		unit_map::unit_iterator unit_it;
76 		bool success = false;
77 
78 		std::tie(unit_it, success) = resources::gameboard->units().replace(loc, ptr);
79 
80 		if(success) {
81 			ptr.reset();
82 			uid = unit_it->underlying_id();
83 		} else {
84 			ERR_LUA << "Could not move unit " << ptr->underlying_id() << " onto map location " << loc << '\n';
85 			return false;
86 		}
87 	} else if (side) { // recall list
88 		unit_ptr it = resources::gameboard->get_team(side).recall_list().extract_if_matches_underlying_id(uid);
89 		if (it) {
90 			side = 0;
91 			// uid may be changed by unit_map on insertion
92 			uid = resources::gameboard->units().replace(loc, it).first->underlying_id();
93 		} else {
94 			ERR_LUA << "Could not find unit " << uid << " on recall list of side " << side << '\n';
95 			return false;
96 		}
97 	} else { // on map
98 		unit_map::unit_iterator ui = resources::gameboard->units().find(uid);
99 		if (ui != resources::gameboard->units().end()) {
100 			map_location from = ui->get_location();
101 			if (from != loc) { // This check is redundant in current usage
102 				resources::gameboard->units().erase(loc);
103 				resources::gameboard->units().move(from, loc);
104 			}
105 			// No need to change our contents
106 		} else {
107 			ERR_LUA << "Could not find unit " << uid << " on the map" << std::endl;
108 			return false;
109 		}
110 	}
111 	return true;
112 }
113 
luaW_isunit(lua_State * L,int index)114 bool luaW_isunit(lua_State* L, int index)
115 {
116 	return luaL_testudata(L, index,getunitKey) != nullptr;
117 }
118 
119 enum {
120 	LU_OK,
121 	LU_NOT_UNIT,
122 	LU_NOT_ON_MAP,
123 	LU_NOT_VALID,
124 };
125 
internal_get_unit(lua_State * L,int index,bool only_on_map,int & error)126 static lua_unit* internal_get_unit(lua_State *L, int index, bool only_on_map, int& error)
127 {
128 	error = LU_OK;
129 	if(!luaW_isunit(L, index)) {
130 		error = LU_NOT_UNIT;
131 		return nullptr;
132 	}
133 	lua_unit* lu = static_cast<lua_unit*>(lua_touserdata(L, index));
134 	if(only_on_map && !lu->on_map()) {
135 		error = LU_NOT_ON_MAP;
136 	}
137 	if(!lu->get()) {
138 		error = LU_NOT_VALID;
139 	}
140 	return lu;
141 }
142 
luaW_tounit(lua_State * L,int index,bool only_on_map)143 unit* luaW_tounit(lua_State *L, int index, bool only_on_map)
144 {
145 	int error;
146 	lua_unit* lu = internal_get_unit(L, index, only_on_map, error);
147 	if(error != LU_OK) {
148 		return nullptr;
149 	}
150 	return lu->get();
151 }
152 
luaW_tounit_ptr(lua_State * L,int index,bool only_on_map)153 unit_ptr luaW_tounit_ptr(lua_State *L, int index, bool only_on_map)
154 {
155 	int error;
156 	lua_unit* lu = internal_get_unit(L, index, only_on_map, error);
157 	if(error != LU_OK) {
158 		return nullptr;
159 	}
160 	return lu->get_shared();
161 }
162 
luaW_tounit_ref(lua_State * L,int index)163 lua_unit* luaW_tounit_ref(lua_State *L, int index)
164 {
165 	int error;
166 	return internal_get_unit(L, index, false, error);
167 }
168 
unit_show_error(lua_State * L,int index,int error)169 static void unit_show_error(lua_State *L, int index, int error)
170 {
171 	switch(error) {
172 		case LU_NOT_UNIT:
173 			luaW_type_error(L, index, "unit");
174 			break;
175 		case LU_NOT_VALID:
176 			luaL_argerror(L, index, "unit not found");
177 			break;
178 		case LU_NOT_ON_MAP:
179 			luaL_argerror(L, index, "unit not found on map");
180 			break;
181 	}
182 }
183 
luaW_checkunit_ptr(lua_State * L,int index,bool only_on_map)184 unit_ptr luaW_checkunit_ptr(lua_State *L, int index, bool only_on_map)
185 {
186 	int error;
187 	lua_unit* lu = internal_get_unit(L, index, only_on_map, error);
188 	unit_show_error(L, index, error);
189 	return lu->get_shared();
190 }
191 
luaW_checkunit(lua_State * L,int index,bool only_on_map)192 unit& luaW_checkunit(lua_State *L, int index, bool only_on_map)
193 {
194 	int error;
195 	lua_unit* lu = internal_get_unit(L, index, only_on_map, error);
196 	unit_show_error(L, index, error);
197 	return *lu->get();
198 }
199 
luaW_checkunit_ref(lua_State * L,int index)200 lua_unit* luaW_checkunit_ref(lua_State *L, int index)
201 {
202 	int error;
203 	lua_unit* lu = internal_get_unit(L, index, false, error);
204 	unit_show_error(L, index, error);
205 	return lu;
206 }
207 
setmetatable(lua_State * L)208 void lua_unit::setmetatable(lua_State *L)
209 {
210 	luaL_setmetatable(L, getunitKey);
211 }
212 
luaW_pushlocalunit(lua_State * L,unit & u)213 lua_unit* luaW_pushlocalunit(lua_State *L, unit& u)
214 {
215 	lua_unit* res = new(L) lua_unit(u);
216 	lua_unit::setmetatable(L);
217 	return res;
218 }
219 
220 /**
221  * Destroys a unit object before it is collected (__gc metamethod).
222  */
impl_unit_collect(lua_State * L)223 static int impl_unit_collect(lua_State *L)
224 {
225 	lua_unit *u = static_cast<lua_unit *>(lua_touserdata(L, 1));
226 	u->lua_unit::~lua_unit();
227 	return 0;
228 }
229 
230 /**
231  * Checks two lua proxy units for equality. (__eq metamethod)
232  */
impl_unit_equality(lua_State * L)233 static int impl_unit_equality(lua_State* L)
234 {
235 	unit& left = luaW_checkunit(L, 1);
236 	unit& right = luaW_checkunit(L, 2);
237 	const bool equal = left.underlying_id() == right.underlying_id();
238 	lua_pushboolean(L, equal);
239 	return 1;
240 }
241 
242 /**
243  * Gets some data on a unit (__index metamethod).
244  * - Arg 1: full userdata containing the unit id.
245  * - Arg 2: string containing the name of the property.
246  * - Ret 1: something containing the attribute.
247  */
impl_unit_get(lua_State * L)248 static int impl_unit_get(lua_State *L)
249 {
250 	lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
251 	char const *m = luaL_checkstring(L, 2);
252 	const unit* pu = lu->get();
253 
254 	if(strcmp(m, "valid") == 0) {
255 		if(!pu) {
256 			return 0;
257 		}
258 		if(lu->on_map()) {
259 			lua_pushstring(L, "map");
260 		} else if(lu->on_recall_list()) {
261 			lua_pushstring(L, "recall");
262 		} else {
263 			lua_pushstring(L, "private");
264 		}
265 		return 1;
266 	}
267 
268 	if(!pu) {
269 		return luaL_argerror(L, 1, "unknown unit");
270 	}
271 
272 	const unit& u = *pu;
273 
274 	// Find the corresponding attribute.
275 	return_int_attrib("x", u.get_location().wml_x());
276 	return_int_attrib("y", u.get_location().wml_y());
277 	if(strcmp(m, "loc") == 0) {
278 		lua_pushinteger(L, u.get_location().wml_x());
279 		lua_pushinteger(L, u.get_location().wml_y());
280 		return 2;
281 	}
282 	return_int_attrib("side", u.side());
283 	return_string_attrib("id", u.id());
284 	return_string_attrib("type", u.type_id());
285 	return_string_attrib("image_mods", u.effect_image_mods());
286 	return_string_attrib("usage", u.usage());
287 	return_int_attrib("hitpoints", u.hitpoints());
288 	return_int_attrib("max_hitpoints", u.max_hitpoints());
289 	return_int_attrib("experience", u.experience());
290 	return_int_attrib("max_experience", u.max_experience());
291 	return_int_attrib("recall_cost", u.recall_cost());
292 	return_int_attrib("moves", u.movement_left());
293 	return_int_attrib("max_moves", u.total_movement());
294 	return_int_attrib("max_attacks", u.max_attacks());
295 	return_int_attrib("attacks_left", u.attacks_left());
296 	return_tstring_attrib("name", u.name());
297 	return_bool_attrib("canrecruit", u.can_recruit());
298 	return_int_attrib("level", u.level());
299 	return_int_attrib("cost", u.cost());
300 
301 	return_vector_string_attrib("extra_recruit", u.recruits());
302 	return_vector_string_attrib("advances_to", u.advances_to());
303 
304 	if(strcmp(m, "alignment") == 0) {
305 		lua_push(L, u.alignment());
306 		return 1;
307 	}
308 
309 	if(strcmp(m, "upkeep") == 0) {
310 		unit::upkeep_t upkeep = u.upkeep_raw();
311 
312 		// Need to keep these separate in order to ensure an int value is always used if applicable.
313 		if(int* v = boost::get<int>(&upkeep)) {
314 			lua_push(L, *v);
315 		} else {
316 			const std::string type = boost::apply_visitor(unit::upkeep_type_visitor(), upkeep);
317 			lua_push(L, type);
318 		}
319 
320 		return 1;
321 	}
322 	if(strcmp(m, "advancements") == 0) {
323 		lua_push(L, u.modification_advancements());
324 		return 1;
325 	}
326 	if(strcmp(m, "overlays") == 0) {
327 		lua_push(L, u.overlays());
328 		return 1;
329 	}
330 	if(strcmp(m, "traits") == 0) {
331 		lua_push(L, u.get_traits_list());
332 		return 1;
333 	}
334 	if(strcmp(m, "abilities") == 0) {
335 		lua_push(L, u.get_ability_list());
336 		return 1;
337 	}
338 	if(strcmp(m, "status") == 0) {
339 		lua_createtable(L, 1, 0);
340 		lua_pushvalue(L, 1);
341 		lua_rawseti(L, -2, 1);
342 		luaL_setmetatable(L, ustatusKey);
343 		return 1;
344 	}
345 	if(strcmp(m, "variables") == 0) {
346 		lua_createtable(L, 1, 0);
347 		lua_pushvalue(L, 1);
348 		lua_rawseti(L, -2, 1);
349 		luaL_setmetatable(L, unitvarKey);
350 		return 1;
351 	}
352 	if(strcmp(m, "attacks") == 0) {
353 		push_unit_attacks_table(L, 1);
354 		return 1;
355 	}
356 	return_vector_string_attrib("animations", u.anim_comp().get_flags());
357 	return_cfg_attrib("recall_filter", cfg = u.recall_filter());
358 	return_bool_attrib("hidden", u.get_hidden());
359 	return_bool_attrib("petrified", u.incapacitated());
360 	return_bool_attrib("resting", u.resting());
361 	return_string_attrib("role", u.get_role());
362 	return_string_attrib("race", u.race()->id());
363 	return_string_attrib("gender", gender_string(u.gender()));
364 	return_string_attrib("variation", u.variation());
365 	return_bool_attrib("zoc", u.get_emit_zoc());
366 	return_string_attrib("facing", map_location::write_direction(u.facing()));
367 	return_string_attrib("portrait", u.big_profile() == u.absolute_image() ? u.absolute_image() + u.image_mods() : u.big_profile());
368 	return_cfg_attrib("__cfg", u.write(cfg); u.get_location().write(cfg));
369 
370 	if(luaW_getmetafield(L, 1, m)) {
371 		return 1;
372 	}
373 	return 0;
374 }
375 
376 /**
377  * Sets some data on a unit (__newindex metamethod).
378  * - Arg 1: full userdata containing the unit id.
379  * - Arg 2: string containing the name of the property.
380  * - Arg 3: something containing the attribute.
381  */
impl_unit_set(lua_State * L)382 static int impl_unit_set(lua_State *L)
383 {
384 	lua_unit *lu = static_cast<lua_unit *>(lua_touserdata(L, 1));
385 	char const *m = luaL_checkstring(L, 2);
386 	unit* pu = lu->get();
387 	if (!pu) return luaL_argerror(L, 1, "unknown unit");
388 	unit &u = *pu;
389 
390 	// Find the corresponding attribute.
391 	//modify_int_attrib_check_range("side", u.set_side(value), 1, static_cast<int>(teams().size())); TODO: Figure out if this is a good idea, to refer to teams() and make this depend on having a gamestate
392 	modify_int_attrib("side", u.set_side(value));
393 	modify_int_attrib("moves", u.set_movement(value));
394 	modify_int_attrib("hitpoints", u.set_hitpoints(value));
395 	modify_int_attrib("experience", u.set_experience(value));
396 	modify_int_attrib("recall_cost", u.set_recall_cost(value));
397 	modify_int_attrib("attacks_left", u.set_attacks(value));
398 	modify_int_attrib("level", u.set_level(value));
399 	modify_bool_attrib("resting", u.set_resting(value));
400 	modify_tstring_attrib("name", u.set_name(value));
401 	modify_string_attrib("role", u.set_role(value));
402 	modify_string_attrib("facing", u.set_facing(map_location::parse_direction(value)));
403 	modify_bool_attrib("hidden", u.set_hidden(value));
404 	modify_bool_attrib("zoc", u.set_emit_zoc(value));
405 	modify_bool_attrib("canrecruit", u.set_can_recruit(value));
406 
407 	modify_vector_string_attrib("extra_recruit", u.set_recruits(value));
408 	modify_vector_string_attrib("advances_to", u.set_advances_to(value));
409 	if(strcmp(m, "alignment") == 0) {
410 		u.set_alignment(lua_check<unit_type::ALIGNMENT>(L, 3));
411 		return 0;
412 	}
413 
414 	if(strcmp(m, "advancements") == 0) {
415 		u.set_advancements(lua_check<std::vector<config>>(L, 3));
416 		return 0;
417 	}
418 
419 	if(strcmp(m, "upkeep") == 0) {
420 		if(lua_isnumber(L, 3)) {
421 			u.set_upkeep(luaL_checkinteger(L, 3));
422 			return 0;
423 		}
424 		const char* v = luaL_checkstring(L, 3);
425 		if((strcmp(v, "loyal") == 0) || (strcmp(v, "free") == 0)) {
426 			u.set_upkeep(unit::upkeep_loyal());
427 		} else if(strcmp(v, "full") == 0) {
428 			u.set_upkeep(unit::upkeep_full());
429 		} else {
430 			std::string err_msg = "unknown upkeep value of unit: ";
431 			err_msg += v;
432 			return luaL_argerror(L, 2, err_msg.c_str());
433 		}
434 		return 0;
435 	}
436 
437 	if(!lu->on_map()) {
438 		map_location loc = u.get_location();
439 		modify_int_attrib("x", loc.set_wml_x(value); u.set_location(loc));
440 		modify_int_attrib("y", loc.set_wml_y(value); u.set_location(loc));
441 		modify_string_attrib("id", u.set_id(value));
442 	} else {
443 		const bool is_key_x = strcmp(m, "x") == 0;
444 		const bool is_key_y = strcmp(m, "y") == 0;
445 		const bool is_loc_key = strcmp(m, "loc") == 0;
446 
447 		// Handle moving an on-map unit
448 		if(is_key_x || is_key_y || is_loc_key) {
449 			game_board* gb = resources::gameboard;
450 
451 			if(!gb) {
452 				return 0;
453 			}
454 
455 			map_location src = u.get_location();
456 			map_location dst = src;
457 
458 			if(is_key_x) {
459 				dst.set_wml_x(luaL_checkinteger(L, 3));
460 			} else if(is_key_y) {
461 				dst.set_wml_y(luaL_checkinteger(L, 3));
462 			} else {
463 				dst = luaW_checklocation(L, 3);
464 			}
465 
466 			// TODO: could probably be relegated to a helper function.
467 			if(src != dst) {
468 				// If the dst isn't on the map, the unit will be clobbered. Guard against that.
469 				if(!gb->map().on_board(dst)) {
470 					std::string err_msg = formatter() << "destination hex not on map (excluding border): " << dst;
471 					return luaL_argerror(L, 2, err_msg.c_str());
472 				}
473 
474 				unit_map::iterator unit_iterator = gb->units().end();
475 				bool success = false;
476 
477 				std::tie(unit_iterator, success) = gb->units().move(src, dst);
478 
479 				if(success) {
480 					unit_iterator->anim_comp().set_standing();
481 				}
482 			}
483 
484 			return 0;
485 		}
486 	}
487 
488 	std::string err_msg = "unknown modifiable property of unit: ";
489 	err_msg += m;
490 	return luaL_argerror(L, 2, err_msg.c_str());
491 }
492 
493 /**
494  * Gets the status of a unit (__index metamethod).
495  * - Arg 1: table containing the userdata containing the unit id.
496  * - Arg 2: string containing the name of the status.
497  * - Ret 1: boolean.
498  */
impl_unit_status_get(lua_State * L)499 static int impl_unit_status_get(lua_State *L)
500 {
501 	if(!lua_istable(L, 1)) {
502 		return luaW_type_error(L, 1, "unit status");
503 	}
504 	lua_rawgeti(L, 1, 1);
505 	const unit* u = luaW_tounit(L, -1);
506 	if(!u) {
507 		return luaL_argerror(L, 1, "unknown unit");
508 	}
509 	char const *m = luaL_checkstring(L, 2);
510 	lua_pushboolean(L, u->get_state(m));
511 	return 1;
512 }
513 
514 /**
515  * Sets the status of a unit (__newindex metamethod).
516  * - Arg 1: table containing the userdata containing the unit id.
517  * - Arg 2: string containing the name of the status.
518  * - Arg 3: boolean.
519  */
impl_unit_status_set(lua_State * L)520 static int impl_unit_status_set(lua_State *L)
521 {
522 	if(!lua_istable(L, 1)) {
523 		return luaW_type_error(L, 1, "unit status");
524 	}
525 	lua_rawgeti(L, 1, 1);
526 	unit* u = luaW_tounit(L, -1);
527 	if(!u) {
528 		return luaL_argerror(L, 1, "unknown unit");
529 	}
530 	char const *m = luaL_checkstring(L, 2);
531 	u->set_state(m, luaW_toboolean(L, 3));
532 	return 0;
533 }
534 
535 /**
536  * Gets the variable of a unit (__index metamethod).
537  * - Arg 1: table containing the userdata containing the unit id.
538  * - Arg 2: string containing the name of the status.
539  * - Ret 1: boolean.
540  */
impl_unit_variables_get(lua_State * L)541 static int impl_unit_variables_get(lua_State *L)
542 {
543 	if(!lua_istable(L, 1)) {
544 		return luaW_type_error(L, 1, "unit variables");
545 	}
546 	lua_rawgeti(L, 1, 1);
547 	const unit* u = luaW_tounit(L, -1);
548 	if(!u) {
549 		return luaL_argerror(L, 2, "unknown unit");
550 	}
551 	char const *m = luaL_checkstring(L, 2);
552 	return_cfgref_attrib("__cfg", u->variables());
553 
554 	variable_access_const v(m, u->variables());
555 	return luaW_pushvariable(L, v) ? 1 : 0;
556 }
557 
558 /**
559  * Sets the variable of a unit (__newindex metamethod).
560  * - Arg 1: table containing the userdata containing the unit id.
561  * - Arg 2: string containing the name of the status.
562  * - Arg 3: scalar.
563  */
impl_unit_variables_set(lua_State * L)564 static int impl_unit_variables_set(lua_State *L)
565 {
566 	if(!lua_istable(L, 1)) {
567 		return luaW_type_error(L, 1, "unit variables");
568 	}
569 	lua_rawgeti(L, 1, 1);
570 	unit* u = luaW_tounit(L, -1);
571 	if(!u) {
572 		return luaL_argerror(L, 2, "unknown unit");
573 	}
574 	char const *m = luaL_checkstring(L, 2);
575 	if(strcmp(m, "__cfg") == 0) {
576 		u->variables() = luaW_checkconfig(L, 3);
577 		return 0;
578 	}
579 	config& vars = u->variables();
580 	if(lua_isnoneornil(L, 3)) {
581 		try {
582 			variable_access_throw(m, vars).clear(false);
583 		} catch(const invalid_variablename_exception&) {
584 		}
585 		return 0;
586 	}
587 	variable_access_create v(m, vars);
588 	luaW_checkvariable(L, v, 3);
589 	return 0;
590 }
591 
592 namespace lua_units {
register_metatables(lua_State * L)593 	std::string register_metatables(lua_State* L)
594 	{
595 		std::ostringstream cmd_out;
596 
597 		// Create the getunit metatable.
598 		cmd_out << "Adding getunit metatable...\n";
599 
600 		luaL_newmetatable(L, getunitKey);
601 		lua_pushcfunction(L, impl_unit_collect);
602 		lua_setfield(L, -2, "__gc");
603 		lua_pushcfunction(L, impl_unit_equality);
604 		lua_setfield(L, -2, "__eq");
605 		lua_pushcfunction(L, impl_unit_get);
606 		lua_setfield(L, -2, "__index");
607 		lua_pushcfunction(L, impl_unit_set);
608 		lua_setfield(L, -2, "__newindex");
609 		lua_pushstring(L, "unit");
610 		lua_setfield(L, -2, "__metatable");
611 		// Unit methods
612 		luaW_getglobal(L, "wesnoth", "match_unit");
613 		lua_setfield(L, -2, "matches");
614 		luaW_getglobal(L, "wesnoth", "put_recall_unit");
615 		lua_setfield(L, -2, "to_recall");
616 		luaW_getglobal(L, "wesnoth", "put_unit");
617 		lua_setfield(L, -2, "to_map");
618 		luaW_getglobal(L, "wesnoth", "erase_unit");
619 		lua_setfield(L, -2, "erase");
620 		luaW_getglobal(L, "wesnoth", "copy_unit");
621 		lua_setfield(L, -2, "clone");
622 		luaW_getglobal(L, "wesnoth", "extract_unit");
623 		lua_setfield(L, -2, "extract");
624 		luaW_getglobal(L, "wesnoth", "advance_unit");
625 		lua_setfield(L, -2, "advance");
626 		luaW_getglobal(L, "wesnoth", "add_modification");
627 		lua_setfield(L, -2, "add_modification");
628 		luaW_getglobal(L, "wesnoth", "unit_resistance");
629 		lua_setfield(L, -2, "resistance");
630 		luaW_getglobal(L, "wesnoth", "remove_modifications");
631 		lua_setfield(L, -2, "remove_modifications");
632 		luaW_getglobal(L, "wesnoth", "unit_defense");
633 		lua_setfield(L, -2, "defense");
634 		luaW_getglobal(L, "wesnoth", "unit_movement_cost");
635 		lua_setfield(L, -2, "movement");
636 		luaW_getglobal(L, "wesnoth", "unit_vision_cost");
637 		lua_setfield(L, -2, "vision");
638 		luaW_getglobal(L, "wesnoth", "unit_jamming_cost");
639 		lua_setfield(L, -2, "jamming");
640 		luaW_getglobal(L, "wesnoth", "unit_ability");
641 		lua_setfield(L, -2, "ability");
642 		luaW_getglobal(L, "wesnoth", "transform_unit");
643 		lua_setfield(L, -2, "transform");
644 		luaW_getglobal(L, "wesnoth", "select_unit");
645 		lua_setfield(L, -2, "select");
646 
647 		// Create the unit status metatable.
648 		cmd_out << "Adding unit status metatable...\n";
649 
650 		luaL_newmetatable(L, ustatusKey);
651 		lua_pushcfunction(L, impl_unit_status_get);
652 		lua_setfield(L, -2, "__index");
653 		lua_pushcfunction(L, impl_unit_status_set);
654 		lua_setfield(L, -2, "__newindex");
655 		lua_pushstring(L, "unit status");
656 		lua_setfield(L, -2, "__metatable");
657 
658 		// Create the unit variables metatable.
659 		cmd_out << "Adding unit variables metatable...\n";
660 
661 		luaL_newmetatable(L, unitvarKey);
662 		lua_pushcfunction(L, impl_unit_variables_get);
663 		lua_setfield(L, -2, "__index");
664 		lua_pushcfunction(L, impl_unit_variables_set);
665 		lua_setfield(L, -2, "__newindex");
666 		lua_pushstring(L, "unit variables");
667 		lua_setfield(L, -2, "__metatable");
668 
669 		return cmd_out.str();
670 	}
671 }
672