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