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_attacks.hpp"
16 
17 #include "scripting/lua_common.hpp"
18 #include "scripting/lua_unit.hpp"
19 #include "scripting/lua_unit_type.hpp"
20 #include "units/unit.hpp"
21 #include "units/attack_type.hpp"
22 #include "utils/const_clone.hpp"
23 #include "utils/type_trait_aliases.hpp"
24 
25 #include "lua/lauxlib.h"
26 #include "lua/lua.h"                    // for lua_State, lua_settop, etc
27 
28 #include <type_traits>
29 
30 static const char uattacksKey[] = "unit attacks table";
31 static const char uattackKey[] = "unit attack";
32 
33 struct attack_ref {
34 	attack_ptr attack;
35 	const_attack_ptr cattack;
attack_refattack_ref36 	attack_ref(attack_ptr atk) : attack(atk), cattack(atk) {}
attack_refattack_ref37 	attack_ref(const_attack_ptr atk) : cattack(atk) {}
38 };
39 
push_unit_attacks_table(lua_State * L,int idx)40 void push_unit_attacks_table(lua_State* L, int idx)
41 {
42 	idx = lua_absindex(L, idx);
43 	lua_createtable(L, 1, 0);
44 	lua_pushvalue(L, idx);
45 	// hack: store the unit_type at 0 because we want positive indices to refer to the attacks.
46 	lua_rawseti(L, -2, 0);
47 	luaL_setmetatable(L, uattacksKey);
48 }
49 
luaW_pushweapon(lua_State * L,attack_ptr weapon)50 void luaW_pushweapon(lua_State* L, attack_ptr weapon)
51 {
52 	if(weapon != nullptr) {
53 		new(L) attack_ref(weapon);
54 		luaL_setmetatable(L, uattackKey);
55 	} else {
56 		lua_pushnil(L);
57 	}
58 }
59 
luaW_pushweapon(lua_State * L,const_attack_ptr weapon)60 void luaW_pushweapon(lua_State* L, const_attack_ptr weapon)
61 {
62 	if(weapon != nullptr) {
63 		new(L) attack_ref(weapon);
64 		luaL_setmetatable(L, uattackKey);
65 	} else {
66 		lua_pushnil(L);
67 	}
68 }
69 
luaW_checkweapon_ref(lua_State * L,int idx)70 static attack_ref& luaW_checkweapon_ref(lua_State* L, int idx)
71 {
72 	return *static_cast<attack_ref*>(luaL_checkudata(L, idx, uattackKey));
73 }
74 
luaW_toweapon(lua_State * L,int idx)75 const_attack_ptr luaW_toweapon(lua_State* L, int idx)
76 {
77 	if(void* p = luaL_testudata(L, idx, uattackKey)) {
78 		return static_cast<attack_ref*>(p)->cattack;
79 	}
80 	return nullptr;
81 }
82 
luaW_checkweapon(lua_State * L,int idx)83 attack_type& luaW_checkweapon(lua_State* L, int idx)
84 {
85 	attack_ref& atk = luaW_checkweapon_ref(L, idx);
86 	if(!atk.attack) {
87 		luaL_argerror(L, idx, "attack is read-only");
88 	}
89 	return *atk.attack;
90 }
91 
92 template<typename T>
93 using attack_ptr_in = std::shared_ptr<utils::const_clone_t<attack_type, utils::remove_pointer_t<T>>>;
94 
95 // Note that these two templates are designed on the assumption that T is either unit or unit_type
96 template<typename T>
find_attack(T * u,const std::string & id)97 auto find_attack(T* u, const std::string& id) -> attack_ptr_in<T>
98 {
99 	auto attacks = u->attacks();
100 	for(auto at = attacks.begin(); at != attacks.end(); ++at) {
101 		if(at->id() == id) {
102 			return *at.base();
103 		}
104 	}
105 	return nullptr;
106 }
107 
108 template<typename T>
find_attack(T * u,size_t i)109 auto find_attack(T* u, size_t i) -> attack_ptr_in<T>
110 {
111 	auto attacks = u->attacks();
112 	if(i < static_cast<size_t>(attacks.size())) {
113 		auto iter = attacks.begin();
114 		iter += i;
115 		return *iter.base();
116 	}
117 	return nullptr;
118 }
119 
120 /**
121  * Gets the attacks of a unit or unit type (__index metamethod).
122  * - Arg 1: table containing the userdata containing the unit or unit type.
123  * - Arg 2: index (int) or id (string) identifying a particular attack.
124  * - Ret 1: the unit's attacks.
125  */
impl_unit_attacks_get(lua_State * L)126 static int impl_unit_attacks_get(lua_State *L)
127 {
128 	if(!lua_istable(L, 1)) {
129 		return luaW_type_error(L, 1, "unit attacks");
130 	}
131 	lua_rawgeti(L, 1, 0);
132 	lua_unit* lu = luaW_tounit_ref(L, -1);
133 	const unit_type* ut = luaW_tounittype(L, -1);
134 	if(lu && lu->get()) {
135 		unit* u = lu->get();
136 		attack_ptr atk = lua_isnumber(L, 2) ? find_attack(u, luaL_checkinteger(L, 2) - 1) : find_attack(u, luaL_checkstring(L, 2));
137 		luaW_pushweapon(L, atk);
138 	} else if(ut) {
139 		const_attack_ptr atk = lua_isnumber(L, 2) ? find_attack(ut, luaL_checkinteger(L, 2) - 1) : find_attack(ut, luaL_checkstring(L, 2));
140 		luaW_pushweapon(L, atk);
141 	} else {
142 		return luaL_argerror(L, 1, "unit not found");
143 	}
144 	return 1;
145 }
146 
get_attack_iter(unit & u,attack_ptr atk)147 static attack_itors::iterator get_attack_iter(unit& u, attack_ptr atk)
148 {
149 	// This is slightly inefficient since it walks the attack list a second time...
150 	return std::find_if(u.attacks().begin(), u.attacks().end(), [&atk](const attack_type& atk2) {
151 		return &atk2 == atk.get();
152 	});
153 }
154 
impl_unit_attacks_set(lua_State * L)155 static int impl_unit_attacks_set(lua_State* L)
156 {
157 	if(!lua_istable(L, 1)) {
158 		return luaW_type_error(L, 1, "unit attacks");
159 	}
160 	lua_rawgeti(L, 1, 0);
161 	const unit_type* ut = luaW_tounittype(L, -1);
162 	if(ut) {
163 		return luaL_argerror(L, 1, "unit type attack table is immutable");
164 	}
165 
166 	unit& u = luaW_checkunit(L, -1);
167 	attack_ptr atk = lua_isnumber(L, 2) ? find_attack(&u, luaL_checkinteger(L, 2) - 1) : find_attack(&u, luaL_checkstring(L, 2));
168 	if(lua_isnumber(L, 2) && lua_tonumber(L, 2) - 1 > u.attacks().size()) {
169 		return luaL_argerror(L, 2, "attack can only be added at the end of the list");
170 	}
171 
172 	if(lua_isnil(L, 3)) {
173 		// Delete the attack
174 		u.remove_attack(atk);
175 		return 0;
176 	}
177 
178 	auto iter = get_attack_iter(u, atk), end = u.attacks().end();
179 	if(const_attack_ptr atk2 = luaW_toweapon(L, 3)) {
180 		if(iter == end) {
181 			atk = u.add_attack(end, *atk2);
182 		} else {
183 			iter.base()->reset(new attack_type(*atk2));
184 			atk = *iter.base();
185 		}
186 	} else {
187 		config cfg = luaW_checkconfig(L, 3);
188 		if(iter == end) {
189 			atk = u.add_attack(end, cfg);
190 		} else {
191 			iter.base()->reset(new attack_type(cfg));
192 			atk = *iter.base();
193 		}
194 	}
195 	if(!lua_isnumber(L, 2)) {
196 		atk->set_id(lua_tostring(L, 2));
197 	}
198 	return 0;
199 }
200 
201 /**
202  * Counts the attacks of a unit (__len metamethod).
203  * - Arg 1: table containing the userdata containing the unit id.
204  * - Ret 1: size of unit attacks vector.
205  */
impl_unit_attacks_len(lua_State * L)206 static int impl_unit_attacks_len(lua_State *L)
207 {
208 	if(!lua_istable(L, 1)) {
209 		return luaW_type_error(L, 1, "unit attacks");
210 	}
211 	lua_rawgeti(L, 1, 0);
212 	const unit* u = luaW_tounit(L, -1);
213 	const unit_type* ut = luaW_tounittype(L, -1);
214 	if(!u && !ut) {
215 		return luaL_argerror(L, 1, "unknown unit");
216 	}
217 	lua_pushinteger(L, (u ? u->attacks() : ut->attacks()).size());
218 	return 1;
219 }
220 
impl_unit_attacks_next(lua_State * L)221 static int impl_unit_attacks_next(lua_State *L)
222 {
223 	lua_len(L, 1);
224 	int n = luaL_checknumber(L, 2) + 1;
225 	int max_n = luaL_checknumber(L, -1);
226 	if(n > max_n) {
227 		return 0;
228 	}
229 	lua_pushnumber(L, n);
230 	lua_pushvalue(L, -1);
231 	lua_gettable(L, 1);
232 	return 2;
233 }
234 
impl_unit_attacks_iter(lua_State * L)235 static int impl_unit_attacks_iter(lua_State* L)
236 {
237 	lua_pushcfunction(L, impl_unit_attacks_next);
238 	lua_pushvalue(L, 1);
239 	lua_pushnumber(L, 0);
240 	return 3;
241 }
242 
243 /**
244  * Gets a property of a units attack (__index metamethod).
245  * - Arg 1: table containing the userdata containing the unit id. and a string identifying the attack.
246  * - Arg 2: string
247  * - Ret 1:
248  */
impl_unit_attack_get(lua_State * L)249 static int impl_unit_attack_get(lua_State *L)
250 {
251 	attack_ref& atk_ref = luaW_checkweapon_ref(L, 1);
252 	const attack_type& attack = *atk_ref.cattack;
253 	char const *m = luaL_checkstring(L, 2);
254 	return_bool_attrib("read_only", atk_ref.attack == nullptr);
255 	return_string_attrib("description", attack.name());
256 	return_string_attrib("name", attack.id());
257 	return_string_attrib("type", attack.type());
258 	return_string_attrib("icon", attack.icon());
259 	return_string_attrib("range", attack.range());
260 	return_int_attrib("damage", attack.damage());
261 	return_int_attrib("number", attack.num_attacks());
262 	return_int_attrib("attack_weight", attack.attack_weight());
263 	return_int_attrib("defense_weight", attack.defense_weight());
264 	return_int_attrib("accuracy", attack.accuracy());
265 	return_int_attrib("movement_used", attack.movement_used());
266 	return_int_attrib("parry", attack.parry());
267 	return_cfgref_attrib("specials", attack.specials());
268 	return_cfgref_attrib("__cfg", attack.to_config());
269 	if(luaW_getmetafield(L, 1, m)) {
270 		return 1;
271 	}
272 	std::string err_msg = "unknown property of attack: ";
273 	err_msg += m;
274 	return luaL_argerror(L, 2, err_msg.c_str());
275 }
276 
277 /**
278  * Gets a property of a units attack (__index metamethod).
279  * - Arg 1: table containing the userdata containing the unit id. and a string identyfying the attack.
280  * - Arg 2: string
281  * - Ret 1:
282  */
impl_unit_attack_set(lua_State * L)283 static int impl_unit_attack_set(lua_State *L)
284 {
285 	attack_type& attack = luaW_checkweapon(L, 1);
286 	char const *m = luaL_checkstring(L, 2);
287 	modify_tstring_attrib("description", attack.set_name(value));
288 	modify_string_attrib("name", attack.set_id(value));
289 	modify_string_attrib("type", attack.set_type(value));
290 	modify_string_attrib("icon", attack.set_icon(value));
291 	modify_string_attrib("range", attack.set_range(value));
292 	modify_int_attrib("damage", attack.set_damage(value));
293 	modify_int_attrib("number", attack.set_num_attacks(value));
294 	modify_int_attrib("attack_weight", attack.set_attack_weight(value));
295 	modify_int_attrib("defense_weight", attack.set_defense_weight(value));
296 	modify_int_attrib("accuracy", attack.set_accuracy(value));
297 	modify_int_attrib("movement_used", attack.set_movement_used(value));
298 	modify_int_attrib("parry", attack.set_parry(value));
299 
300 	if(strcmp(m, "specials") == 0) {
301 		attack.set_specials(luaW_checkconfig(L, 3));
302 		return 0;
303 	}
304 
305 	std::string err_msg = "unknown modifiable property of attack: ";
306 	err_msg += m;
307 	return luaL_argerror(L, 2, err_msg.c_str());
308 }
309 
impl_unit_attack_equal(lua_State * L)310 static int impl_unit_attack_equal(lua_State* L)
311 {
312 	const_attack_ptr ut1 = luaW_toweapon(L, 1);
313 	const_attack_ptr ut2 = luaW_toweapon(L, 2);
314 	lua_pushboolean(L, ut1 == ut2);
315 	return 1;
316 }
317 
impl_unit_attack_match(lua_State * L)318 static int impl_unit_attack_match(lua_State* L)
319 {
320 	const_attack_ptr atk = luaW_toweapon(L, 1);
321 	config cfg = luaW_checkconfig(L, 2);
322 	if(!atk) {
323 		return luaL_argerror(L, 1, "invalid attack");
324 	}
325 	lua_pushboolean(L, atk->matches_filter(cfg));
326 	return 1;
327 }
328 
impl_unit_attack_collect(lua_State * L)329 static int impl_unit_attack_collect(lua_State* L)
330 {
331 	attack_ref* atk = static_cast<attack_ref*>(luaL_checkudata(L, 1, uattackKey));
332 	atk->~attack_ref();
333 	return 0;
334 }
335 
intf_create_attack(lua_State * L)336 static int intf_create_attack(lua_State* L)
337 {
338 	auto atk = std::make_shared<attack_type>(luaW_checkconfig(L, 1));
339 	luaW_pushweapon(L, atk);
340 	return 1;
341 }
342 
343 namespace lua_units {
register_attacks_metatables(lua_State * L)344 	std::string register_attacks_metatables(lua_State* L)
345 	{
346 		std::ostringstream cmd_out;
347 
348 		// Create the unit attacks metatable.
349 		cmd_out << "Adding unit attacks metatable...\n";
350 
351 		luaL_newmetatable(L, uattacksKey);
352 		lua_pushcfunction(L, impl_unit_attacks_get);
353 		lua_setfield(L, -2, "__index");
354 		lua_pushcfunction(L, impl_unit_attacks_set);
355 		lua_setfield(L, -2, "__newindex");
356 		lua_pushcfunction(L, impl_unit_attacks_len);
357 		lua_setfield(L, -2, "__len");
358 		lua_pushcfunction(L, impl_unit_attacks_iter);
359 		lua_setfield(L, -2, "__ipairs");
360 		lua_pushstring(L, uattacksKey);
361 		lua_setfield(L, -2, "__metatable");
362 
363 		// Create the unit attack metatable
364 		luaL_newmetatable(L, uattackKey);
365 		lua_pushcfunction(L, impl_unit_attack_get);
366 		lua_setfield(L, -2, "__index");
367 		lua_pushcfunction(L, impl_unit_attack_set);
368 		lua_setfield(L, -2, "__newindex");
369 		lua_pushcfunction(L, impl_unit_attack_equal);
370 		lua_setfield(L, -2, "__eq");
371 		lua_pushcfunction(L, impl_unit_attack_collect);
372 		lua_setfield(L, -2, "__gc");
373 		lua_pushstring(L, uattackKey);
374 		lua_setfield(L, -2, "__metatable");
375 		lua_pushcfunction(L, impl_unit_attack_match);
376 		lua_setfield(L, -2, "matches");
377 
378 		// Add create_attack
379 		luaW_getglobal(L, "wesnoth");
380 		lua_pushcfunction(L, intf_create_attack);
381 		lua_setfield(L, -2, "create_weapon");
382 		lua_pop(L, 1);
383 
384 		return cmd_out.str();
385 	}
386 }
387