1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #define LUA_SYNCED_ONLY
4 
5 #include "LuaUnitScript.h"
6 
7 #include "CobInstance.h"
8 #include "UnitScriptLog.h"
9 #include "LuaInclude.h"
10 #include "NullUnitScript.h"
11 #include "LuaScriptNames.h"
12 #include "Lua/LuaConfig.h"
13 #include "Lua/LuaCallInCheck.h"
14 #include "Lua/LuaHandleSynced.h"
15 #include "Lua/LuaUtils.h"
16 #include "Sim/Units/UnitHandler.h"
17 #include "Sim/Units/Unit.h"
18 #include "Sim/Weapons/PlasmaRepulser.h"
19 
20 
21 using std::map;
22 using std::pair;
23 using std::string;
24 using std::vector;
25 
ParseLocalModelPiece(lua_State * L,CUnitScript * script,const char * caller)26 static inline LocalModelPiece* ParseLocalModelPiece(lua_State* L, CUnitScript* script, const char* caller)
27 {
28 	const int piece = luaL_checkint(L, 1) - 1;
29 
30 	if (!script->PieceExists(piece)) {
31 		luaL_error(L, "%s(): Invalid piecenumber", caller);
32 	}
33 
34 	return (script->GetScriptLocalModelPiece(piece));
35 }
36 
ToLua(lua_State * L,const float3 & v)37 static inline int ToLua(lua_State* L, const float3& v)
38 {
39 	lua_pushnumber(L, v.x);
40 	lua_pushnumber(L, v.y);
41 	lua_pushnumber(L, v.z);
42 	return 3;
43 }
44 
45 /*
46 
47 some notes:
48 - all piece numbers are 1 based, to be consistent with other parts of interface
49 - all axes are 1 based, so 1 = x, 2 = y, 3 = z
50 - destination, speed for Move are in world coords, and NOT in COB coords
51 - therefore, compared to COB, the X axis for the Move callout is mirrored
52 - destination, speed, accel, decel for Turn, Spin, StopSpin are in radians
53 - GetUnitCOBValue(PLAY_SOUND, ...) does NOT work for Lua unit scripts,
54   use Spring.PlaySound instead (synced code can call unsynced funcs!).
55 - Because in current design CBCobThreadFinish can impossibly be called, certain
56   state changes which normally happen immediately when script returns should
57   be triggered through a call to a callOut when using Lua scripts.
58   This applies to:
59   * Spring.SetUnitShieldState(unitID, false|true) replaces return value 0|1 of
60     COB's AimWeaponX function for plasma repulsers.
61   * Spring.SetUnitWeaponState(unitID, weaponNum, "aimReady", 0|1) replaces
62     return value 0|1 of COB's AimWeaponX function for all other weapons.
63   * Spring.UnitScript.SetDeathScriptFinished(wreckLevel) replaces
64     return value of wreckLevel from Killed function.
65     This MUST be called, otherwise zombie units will eat your Spring!
66 
67 
68 callIn notes:
69 - Killed takes recentDamage,maxHealth instead of severity, to calculate
70   severity use: 'local severity = recentDamage / maxHealth'
71 - SetMaxReloadTime doesn't exist, max reload time can be calculated in Lua
72   (without the WTFs that are present in max reload time calculation for COB)
73 - RockUnit takes x,z instead of 500z,500x
74 - HitByWeapon takes x,z instead of 500z,500x
75 - HitByWeaponId takes x,z,weaponDefID,damage instead of 500z,500x,tdfID,damage
76 - HitByWeaponId returns the new damage, instead of a percentage of old damage
77 - QueryLandingPadCount doesn't exist
78 - QueryLandingPad should return an array (table) of all pieces
79 - BeginTransport and QueryTransport take unitID instead of unit->height*65536,
80   use 'local height = Spring.GetUnitHeight(unitID)' to get the height.
81 - TransportDrop takes x,y,z instead of PACKXZ(x,z)
82 - AimWeapon for a shield (plasma repulser) takes no arguments instead of 0,0
83 - Shot takes no arguments instead of 0
84 - new callins MoveFinished and TurnFinished, see below
85 
86 
87 docs for callins defined in this file:
88 
89   TODO: document other callins properly
90 
91 TurnFinished(number piece, number axis)
92 	Called after a turn finished for this unit/piece/axis (not a turn-now!)
93 	Should resume coroutine of the particular thread which called the Lua
94 	WaitForTurn function (see below).
95 
96 MoveFinished(number piece, number axis)
97 	Called after a move finished for this unit/piece/axis (not a move-now!)
98 	Should resume coroutine of the particular thread which called the Lua
99 	WaitForMove function (see below).
100 
101 
102 docs for callouts defined in this file:
103 
104 Spring.UnitScript.SetUnitValue(...)
105 	see wiki for Spring.SetUnitCOBValue (unchanged)
106 
107 Spring.UnitScript.GetUnitValue(...)
108 	see wiki for Spring.GetUnitCOBValue (unchanged)
109 
110 Spring.UnitScript.SetPieceVisibility(number piece, boolean visible) -> nil
111 	Set's piece visibility.  Same as COB's hide/show.
112 
113 Spring.UnitSript.EmitSfx(number piece, number type) -> nil
114 	Same as COB's emit-sfx.
115 
116 Spring.UnitScript.AttachUnit(number piece, number transporteeID) -> nil
117 	Same as COB's attach-unit.
118 
119 Spring.UnitScript.DropUnit(number transporteeID) -> nil
120 	Same as COB's drop-unit.
121 
122 Spring.UnitScript.Explode(number piece, number flags) -> nil
123 	Same as COB's explode.
124 
125 Spring.UnitScript.ShowFlare(number piece) -> nil
126 	Same as COB's show _inside_ FireWeaponX.
127 
128 Spring.UnitScript.Spin(number piece, number axis, number speed[, number accel]) -> nil
129 	Same as COB's spin.  If accel isn't given spinning starts at the desired speed.
130 
131 Spring.UnitScript.StopSpin(number piece, number axis[, number decel]) -> nil
132 	Same as COB's stop-spin.  If decel isn't given spinning stops immediately.
133 
134 Spring.UnitScript.Turn(number piece, number axis, number destination[, number speed]) -> nil
135 	Same as COB's turn iff speed is given and not zero, and turn-now otherwise.
136 
137 Spring.UnitScript.Move(number piece, number axis, number destination[, number speed]) -> nil
138 	Same as COB's move iff speed is given and not zero, and move-now otherwise.
139 
140 Spring.UnitScript.IsInTurn(number piece, number axis) -> boolean
141 Spring.UnitScript.IsInMove(number piece, number axis) -> boolean
142 Spring.UnitScript.IsInSpin(number piece, number axis) -> boolean
143 	Returns true iff such an animation exists, false otherwise.
144 
145 Spring.UnitScript.WaitForTurn(number piece, number axis) -> boolean
146 	Returns true iff such an animation exists, false otherwise.  Iff it returns
147 	true, the TurnFinished callIn will be called once the turn completes.
148 
149 Spring.UnitScript.WaitForMove(number piece, number axis) -> boolean
150 	Returns true iff such an animation exists, false otherwise.  Iff it returns
151 	true, the MoveFinished callIn will be called once the move completes.
152 
153 Spring.UnitScript.SetDeathScriptFinished(number wreckLevel])
154 	Tells Spring the Killed script finished, and which wreckLevel to use.
155 	If wreckLevel is not given no wreck is created.
156 
157 Spring.UnitScript.CreateScript(number unitID, table callIns) -> nil
158 	Replaces the current unit script (independent of type, also replaces COB)
159 	with the unit script given by a table of callins for the unit.
160 	Callins are similar to COB functions, e.g. a number of predefined names are
161 	called by the engine if they exist in the table.
162 
163 Spring.UnitScript.UpdateCallIn(number unitID, string fname[, function callIn]) -> number|boolean
164 	Iff callIn is a function, a single callIn is replaced or added, and the
165 	new functionID is returned.  If callIn isn't given or is nil, the callIn is
166 	nilled, returns true if it was removed, or false if the callin didn't exist.
167 	See also Spring.UnitScript.CreateScript.
168 */
169 
170 
171 #define LUA_TRACE(m) \
172 	do { \
173 		if (unit) { \
174 			LOG_L(L_DEBUG, "%s: %d: %s", __FUNCTION__, unit->id, m); \
175 		} else { \
176 			LOG_L(L_DEBUG, "%s: %s", __FUNCTION__, m); \
177 		} \
178 	} while (false)
179 
180 
181 CUnit* CLuaUnitScript::activeUnit;
182 CUnitScript* CLuaUnitScript::activeScript;
183 
184 
185 /******************************************************************************/
186 /******************************************************************************/
187 
188 
CLuaUnitScript(lua_State * L,CUnit * unit)189 CLuaUnitScript::CLuaUnitScript(lua_State* L, CUnit* unit)
190 	: CUnitScript(unit, unit->localModel->pieces)
191 	, handle(CLuaHandle::GetHandle(L)), L(L)
192 	, scriptIndex(LUAFN_Last, LUA_NOREF)
193 	, inKilled(false)
194 {
195 	for (lua_pushnil(L); lua_next(L, 2) != 0; /*lua_pop(L, 1)*/) {
196 		const string fname = lua_tostring(L, -2);
197 		const int r = luaL_ref(L, LUA_REGISTRYINDEX);
198 
199 		scriptNames.insert(pair<string, int>(fname, r));
200 		UpdateCallIn(fname, r);
201 	}
202 }
203 
204 
~CLuaUnitScript()205 CLuaUnitScript::~CLuaUnitScript()
206 {
207 	// if L is NULL then the lua_State is closed/closing (see HandleFreed)
208 	if (L != NULL) {
209 		// notify Lua the script is going down
210 		Destroy();
211 		for (map<string, int>::iterator it = scriptNames.begin(); it != scriptNames.end(); ++it) {
212 			luaL_unref(L, LUA_REGISTRYINDEX, it->second);
213 		}
214 	}
215 }
216 
217 
HandleFreed(CLuaHandle * handle)218 void CLuaUnitScript::HandleFreed(CLuaHandle* handle)
219 {
220 	std::list<CUnit*>::iterator ui;
221 	for (ui = unitHandler->activeUnits.begin(); ui != unitHandler->activeUnits.end(); ++ui) {
222 		CLuaUnitScript* script = dynamic_cast<CLuaUnitScript*>((*ui)->script);
223 
224 		// kill only the Lua scripts running in this handle
225 		if (script != NULL && script->handle == handle) {
226 
227 			// we don't have anything better ...
228 			(*ui)->script = &CNullUnitScript::value;
229 
230 			// signal the destructor it shouldn't unref refs
231 			script->L = NULL;
232 
233 			delete script;
234 		}
235 	}
236 }
237 
238 
UpdateCallIn()239 int CLuaUnitScript::UpdateCallIn()
240 {
241 	const string fname = lua_tostring(L, 2);
242 	const bool remove = lua_isnoneornil(L, 3);
243 	map<string, int>::iterator it = scriptNames.find(fname);
244 	int r = LUA_NOREF;
245 
246 	if (it != scriptNames.end()) {
247 		luaL_unref(L, LUA_REGISTRYINDEX, it->second);
248 		if (remove) {
249 			// removing existing callIn
250 			scriptNames.erase(it);
251 			lua_pushboolean(L, 1);
252 		}
253 		else {
254 			// replacing existing callIn
255 			r = luaL_ref(L, LUA_REGISTRYINDEX);
256 			it->second = r;
257 		}
258 	}
259 	else if (remove) {
260 		// removing nonexisting callIn (== no-op)
261 		lua_pushboolean(L, 0);
262 	}
263 	else {
264 		// adding new callIn
265 		r = luaL_ref(L, LUA_REGISTRYINDEX);
266 		scriptNames.insert(pair<string, int>(fname, r));
267 	}
268 
269 	UpdateCallIn(fname, r);
270 
271 	if (!remove) {
272 		// the reference doubles as the functionId, as expected by RealCall
273 		// from Lua this can be used with e.g. Spring.CallCOBScript
274 		lua_pushnumber(L, r);
275 	}
276 	return 1;
277 }
278 
279 
UpdateCallIn(const string & fname,int ref)280 void CLuaUnitScript::UpdateCallIn(const string& fname, int ref)
281 {
282 	// Map common function names to indices
283 	int num = CLuaUnitScriptNames::GetScriptNumber(fname);
284 
285 	// Check upper bound too in case user calls UpdateCallIn with nonexisting weapon.
286 	// (we only allocate slots in scriptIndex for the number of weapons the unit has)
287 	if (num >= 0 && num < int(scriptIndex.size())) {
288 		scriptIndex[num] = ref;
289 	}
290 
291 	switch (num) {
292 		case LUAFN_SetSFXOccupy:  hasSetSFXOccupy  = (ref != LUA_NOREF); break;
293 		case LUAFN_RockUnit:      hasRockUnit      = (ref != LUA_NOREF); break;
294 		case LUAFN_StartBuilding: hasStartBuilding = (ref != LUA_NOREF); break;
295 	}
296 
297 	LUA_TRACE(fname.c_str());
298 }
299 
300 
RemoveCallIn(const string & fname)301 void CLuaUnitScript::RemoveCallIn(const string& fname)
302 {
303 	map<string, int>::iterator it = scriptNames.find(fname);
304 
305 	if (it != scriptNames.end()) {
306 		luaL_unref(L, LUA_REGISTRYINDEX, it->second);
307 		scriptNames.erase(it);
308 		UpdateCallIn(fname, LUA_NOREF);
309 	}
310 
311 	LUA_TRACE(fname.c_str());
312 }
313 
314 
ShowScriptError(const string & msg)315 void CLuaUnitScript::ShowScriptError(const string& msg)
316 {
317 	// if we are in the same handle, we can truely raise an error
318 	if (handle->IsRunning()) {
319 		luaL_error(L, "Lua UnitScript error: %s", msg.c_str());
320 	}
321 	else {
322 		LOG_L(L_ERROR, "%s", msg.c_str());
323 	}
324 }
325 
326 
HasBlockShot(int weaponNum) const327 bool CLuaUnitScript::HasBlockShot(int weaponNum) const
328 {
329 	return HasFunction(LUAFN_BlockShot);
330 }
331 
332 
HasTargetWeight(int weaponNum) const333 bool CLuaUnitScript::HasTargetWeight(int weaponNum) const
334 {
335 	return HasFunction(LUAFN_TargetWeight);
336 }
337 
338 
339 /******************************************************************************/
340 /******************************************************************************/
341 
342 
PopNumber(int fn,float def)343 inline float CLuaUnitScript::PopNumber(int fn, float def)
344 {
345 	if (!lua_israwnumber(L, -1)) {
346 		const string& fname = CLuaUnitScriptNames::GetScriptName(fn);
347 
348 		LOG_L(L_ERROR, "%s: bad return value, expected number", fname.c_str());
349 		RemoveCallIn(fname);
350 
351 		lua_pop(L, 1);
352 		return def;
353 	}
354 
355 	const float ret = lua_tonumber(L, -1);
356 	lua_pop(L, 1);
357 	return ret;
358 }
359 
360 
PopBoolean(int fn,bool def)361 inline bool CLuaUnitScript::PopBoolean(int fn, bool def)
362 {
363 	if (!lua_isboolean(L, -1)) {
364 		const string& fname = CLuaUnitScriptNames::GetScriptName(fn);
365 
366 		LOG_L(L_ERROR, "%s: bad return value, expected boolean", fname.c_str());
367 		RemoveCallIn(fname);
368 
369 		lua_pop(L, 1);
370 		return def;
371 	}
372 
373 	const bool ret = lua_toboolean(L, -1);
374 	lua_pop(L, 1);
375 	return ret;
376 }
377 
378 
RawPushFunction(int functionId)379 inline void CLuaUnitScript::RawPushFunction(int functionId)
380 {
381 	// Push Lua function on the stack
382 	lua_rawgeti(L, LUA_REGISTRYINDEX, functionId);
383 }
384 
385 
PushFunction(int id)386 inline void CLuaUnitScript::PushFunction(int id)
387 {
388 	RawPushFunction(scriptIndex[id]);
389 }
390 
391 
PushUnit(const CUnit * targetUnit)392 inline void CLuaUnitScript::PushUnit(const CUnit* targetUnit)
393 {
394 	if (targetUnit) {
395 		lua_pushnumber(L, targetUnit->id);
396 	}
397 	else {
398 		lua_pushnil(L);
399 	}
400 }
401 
402 
RunCallIn(int id,int inArgs,int outArgs)403 inline bool CLuaUnitScript::RunCallIn(int id, int inArgs, int outArgs)
404 {
405 	return RawRunCallIn(scriptIndex[id], inArgs, outArgs);
406 }
407 
408 
RunQueryCallIn(int fn)409 int CLuaUnitScript::RunQueryCallIn(int fn)
410 {
411 	if (!HasFunction(fn))
412 		return -1;
413 
414 	LUA_CALL_IN_CHECK(L, -1);
415 	lua_checkstack(L, 1);
416 
417 	PushFunction(fn);
418 
419 	if (!RunCallIn(fn, 0, 1))
420 		return -1;
421 
422 	const int scriptPieceNum = (int)PopNumber(fn, 0) - 1;
423 
424 	if (LOG_IS_ENABLED(L_DEBUG)) {
425 		if (PieceExists(scriptPieceNum)) {
426 			LocalModelPiece* piece = GetScriptLocalModelPiece(scriptPieceNum);
427 			LOG_L(L_DEBUG, "%s: %d %s",
428 					CLuaUnitScriptNames::GetScriptName(fn).c_str(),
429 					scriptPieceNum,
430 					(piece->original) ? piece->original->name.c_str() : "n/a");
431 		}
432 	}
433 
434 	return scriptPieceNum;
435 }
436 
437 
RunQueryCallIn(int fn,float arg1)438 int CLuaUnitScript::RunQueryCallIn(int fn, float arg1)
439 {
440 	if (!HasFunction(fn))
441 		return -1;
442 
443 	LUA_CALL_IN_CHECK(L, -1);
444 	lua_checkstack(L, 2);
445 
446 	PushFunction(fn);
447 	lua_pushnumber(L, arg1);
448 
449 	if (!RunCallIn(fn, 1, 1))
450 		return -1;
451 
452 	const int scriptPieceNum = (int)PopNumber(fn, 0) - 1;
453 
454 	if (LOG_IS_ENABLED(L_DEBUG)) {
455 		if (PieceExists(scriptPieceNum)) {
456 			LocalModelPiece* piece = GetScriptLocalModelPiece(scriptPieceNum);
457 			LOG_L(L_DEBUG, "%s: %d %s",
458 					CLuaUnitScriptNames::GetScriptName(fn).c_str(),
459 					scriptPieceNum,
460 					(piece->original) ? piece->original->name.c_str() : "n/a");
461 		}
462 	}
463 
464 	return scriptPieceNum;
465 }
466 
467 
Call(int fn,float arg1)468 void CLuaUnitScript::Call(int fn, float arg1)
469 {
470 	if (!HasFunction(fn))
471 		return;
472 
473 	LUA_CALL_IN_CHECK(L);
474 	lua_checkstack(L, 2);
475 
476 	PushFunction(fn);
477 	lua_pushnumber(L, arg1);
478 
479 	RunCallIn(fn, 1, 0);
480 }
481 
482 
Call(int fn,float arg1,float arg2)483 void CLuaUnitScript::Call(int fn, float arg1, float arg2)
484 {
485 	if (!HasFunction(fn))
486 		return;
487 
488 	LUA_CALL_IN_CHECK(L);
489 	lua_checkstack(L, 3);
490 
491 	PushFunction(fn);
492 	lua_pushnumber(L, arg1);
493 	lua_pushnumber(L, arg2);
494 
495 	RunCallIn(fn, 2, 0);
496 }
497 
498 
Call(int fn,float arg1,float arg2,float arg3)499 void CLuaUnitScript::Call(int fn, float arg1, float arg2, float arg3)
500 {
501 	if (!HasFunction(fn))
502 		return;
503 
504 	LUA_CALL_IN_CHECK(L);
505 	lua_checkstack(L, 4);
506 
507 	PushFunction(fn);
508 	lua_pushnumber(L, arg1);
509 	lua_pushnumber(L, arg2);
510 	lua_pushnumber(L, arg3);
511 
512 	RunCallIn(fn, 3, 0);
513 }
514 
515 
516 /******************************************************************************/
517 /******************************************************************************/
518 
519 
Create()520 void CLuaUnitScript::Create()
521 {
522 	// There is no use for Create
523 	// (Lua code can just call it after Spring.UnitScript.CreateScript(...))
524 }
525 
526 
Killed()527 void CLuaUnitScript::Killed()
528 {
529 	const int fn = LUAFN_Killed;
530 
531 	if (!HasFunction(fn)) {
532 		unit->deathScriptFinished = true;
533 		return;
534 	}
535 
536 	LUA_CALL_IN_CHECK(L);
537 	lua_checkstack(L, 3);
538 
539 	PushFunction(fn);
540 	lua_pushnumber(L, unit->recentDamage);
541 	lua_pushnumber(L, unit->maxHealth);
542 
543 	inKilled = true;
544 
545 	if (!RunCallIn(fn, 2, 1))
546 		return;
547 
548 	// If Killed returns an integer, it signals it hasn't started a thread.
549 	// In this case the return value is the delayedWreckLevel.
550 	if (lua_israwnumber(L, -1)) {
551 		inKilled = false;
552 		unit->deathScriptFinished = true;
553 		unit->delayedWreckLevel = lua_toint(L, -1);
554 	}
555 	else if (!lua_isnoneornil(L, -1)) {
556 		const string& fname = CLuaUnitScriptNames::GetScriptName(fn);
557 
558 		LOG_L(L_ERROR, "%s: bad return value, expected number or nil", fname.c_str());
559 		RemoveCallIn(fname);
560 
561 		// without this we would end up with zombie units
562 		unit->deathScriptFinished = true;
563 	}
564 
565 	lua_pop(L, 1);
566 }
567 
568 
WindChanged(float heading,float speed)569 void CLuaUnitScript::WindChanged(float heading, float speed)
570 {
571 	Call(LUAFN_WindChanged, heading, speed);
572 }
573 
574 
ExtractionRateChanged(float speed)575 void CLuaUnitScript::ExtractionRateChanged(float speed)
576 {
577 	Call(LUAFN_ExtractionRateChanged, speed);
578 }
579 
580 
RockUnit(const float3 & rockDir)581 void CLuaUnitScript::RockUnit(const float3& rockDir)
582 {
583 	//FIXME: change COB to get rockDir in unit space too, instead of world space?
584 	const float c = math::cos(unit->heading * TAANG2RAD);
585 	const float s = math::sin(unit->heading * TAANG2RAD);
586 	const float x = c * rockDir.x - s * rockDir.z;
587 	const float z = s * rockDir.x + c * rockDir.z;
588 
589 	//FIXME: maybe we want rockDir.y too to be future proof?
590 	Call(LUAFN_RockUnit, x, z);
591 }
592 
593 
HitByWeapon(const float3 & hitDir,int weaponDefId,float & inout_damage)594 void CLuaUnitScript::HitByWeapon(const float3& hitDir, int weaponDefId, float& inout_damage)
595 {
596 	const int fn = LUAFN_HitByWeapon;
597 
598 	if (!HasFunction(fn))
599 		return;
600 
601 	//FIXME: change COB to get hitDir in unit space too, instead of world space?
602 	const float c = math::cos(unit->heading * TAANG2RAD);
603 	const float s = math::sin(unit->heading * TAANG2RAD);
604 	const float x = c * hitDir.x - s * hitDir.z;
605 	const float z = s * hitDir.x + c * hitDir.z;
606 
607 	LUA_CALL_IN_CHECK(L);
608 	lua_checkstack(L, 5);
609 
610 	PushFunction(fn);
611 	lua_pushnumber(L, x);
612 	// FIXME maybe we want hitDir.y too, to be future proofed?
613 	lua_pushnumber(L, z);
614 	lua_pushnumber(L, weaponDefId);
615 	lua_pushnumber(L, inout_damage);
616 
617 	if (!RunCallIn(fn, 4, 1))
618 		return;
619 
620 	if (lua_israwnumber(L, -1)) {
621 		inout_damage = lua_tonumber(L, -1);
622 	}
623 	else if (!lua_isnoneornil(L, -1)) {
624 		const string& fname = CLuaUnitScriptNames::GetScriptName(fn);
625 
626 		LOG_L(L_ERROR, "%s: bad return value, expected number or nil", fname.c_str());
627 		RemoveCallIn(fname);
628 	}
629 
630 	lua_pop(L, 1);
631 }
632 
633 
SetSFXOccupy(int curTerrainType)634 void CLuaUnitScript::SetSFXOccupy(int curTerrainType)
635 {
636 	const int fn = LUAFN_SetSFXOccupy;
637 
638 	if (!HasFunction(fn))
639 		return;
640 
641 	LUA_CALL_IN_CHECK(L);
642 	lua_checkstack(L, 2);
643 
644 	PushFunction(fn);
645 	lua_pushnumber(L, curTerrainType);
646 
647 	RunCallIn(fn, 1, 0);
648 }
649 
650 
QueryLandingPads(std::vector<int> & out_pieces)651 void CLuaUnitScript::QueryLandingPads(std::vector<int>& out_pieces)
652 {
653 	const int fn = LUAFN_QueryLandingPads;
654 
655 	if (!HasFunction(fn))
656 		return;
657 
658 	LUA_CALL_IN_CHECK(L);
659 	lua_checkstack(L, 2);
660 
661 	PushFunction(fn);
662 
663 	if (!RunCallIn(fn, 0, 1))
664 		return;
665 
666 	if (lua_istable(L, -1)) {
667 		int n = 1;
668 		// get the first piece number at t[n=1]
669 		lua_rawgeti(L, -1, n);
670 
671 		// t = {[1] = piece_number_1, [2] = piece_number_2, ...}
672 		while (lua_israwnumber(L, -1)) {
673 			out_pieces.push_back(lua_toint(L, -1) - 1);
674 			lua_pop(L, 1);
675 			lua_rawgeti(L, -1, ++n);
676 		}
677 
678 		lua_pop(L, 1);
679 	} else {
680 		const string& fname = CLuaUnitScriptNames::GetScriptName(fn);
681 
682 		LOG_L(L_ERROR, "%s: bad return value, expected table", fname.c_str());
683 		RemoveCallIn(fname);
684 	}
685 
686 	lua_pop(L, 1);
687 }
688 
689 
BeginTransport(const CUnit * unit)690 void CLuaUnitScript::BeginTransport(const CUnit* unit)
691 {
692 	Call(LUAFN_BeginTransport, unit->id);
693 }
694 
695 
QueryTransport(const CUnit * unit)696 int CLuaUnitScript::QueryTransport(const CUnit* unit)
697 {
698 	return RunQueryCallIn(LUAFN_QueryTransport, unit->id);
699 }
700 
701 
TransportPickup(const CUnit * unit)702 void CLuaUnitScript::TransportPickup(const CUnit* unit)
703 {
704 	Call(LUAFN_TransportPickup, unit->id);
705 }
706 
707 
TransportDrop(const CUnit * unit,const float3 & pos)708 void CLuaUnitScript::TransportDrop(const CUnit* unit, const float3& pos)
709 {
710 	const int fn = LUAFN_TransportDrop;
711 
712 	if (!HasFunction(fn))
713 		return;
714 
715 	LUA_CALL_IN_CHECK(L);
716 	lua_checkstack(L, 5);
717 
718 	PushFunction(fn);
719 	lua_pushnumber(L, unit->id);
720 	lua_pushnumber(L, pos.x);
721 	lua_pushnumber(L, pos.y);
722 	lua_pushnumber(L, pos.z);
723 
724 	RunCallIn(fn, 4, 0);
725 }
726 
727 
StartBuilding(float heading,float pitch)728 void CLuaUnitScript::StartBuilding(float heading, float pitch)
729 {
730 	Call(LUAFN_StartBuilding, heading, pitch);
731 }
732 
733 
QueryNanoPiece()734 int CLuaUnitScript::QueryNanoPiece()
735 {
736 	return RunQueryCallIn(LUAFN_QueryNanoPiece);
737 }
738 
739 
QueryBuildInfo()740 int CLuaUnitScript::QueryBuildInfo()
741 {
742 	return RunQueryCallIn(LUAFN_QueryBuildInfo);
743 }
744 
745 
QueryWeapon(int weaponNum)746 int CLuaUnitScript::QueryWeapon(int weaponNum)
747 {
748 	return RunQueryCallIn(LUAFN_QueryWeapon, weaponNum + LUA_WEAPON_BASE_INDEX);
749 }
750 
751 
AimWeapon(int weaponNum,float heading,float pitch)752 void CLuaUnitScript::AimWeapon(int weaponNum, float heading, float pitch)
753 {
754 	Call(LUAFN_AimWeapon, weaponNum + LUA_WEAPON_BASE_INDEX, heading, pitch);
755 }
756 
757 
AimShieldWeapon(CPlasmaRepulser * weapon)758 void  CLuaUnitScript::AimShieldWeapon(CPlasmaRepulser* weapon)
759 {
760 	Call(LUAFN_AimShield, weapon->weaponNum + LUA_WEAPON_BASE_INDEX);
761 }
762 
763 
AimFromWeapon(int weaponNum)764 int CLuaUnitScript::AimFromWeapon(int weaponNum)
765 {
766 	return RunQueryCallIn(LUAFN_AimFromWeapon, weaponNum + LUA_WEAPON_BASE_INDEX);
767 }
768 
769 
Shot(int weaponNum)770 void CLuaUnitScript::Shot(int weaponNum)
771 {
772 	// FIXME: pass projectileID?
773 	Call(LUAFN_Shot, weaponNum + LUA_WEAPON_BASE_INDEX);
774 }
775 
776 
BlockShot(int weaponNum,const CUnit * targetUnit,bool userTarget)777 bool CLuaUnitScript::BlockShot(int weaponNum, const CUnit* targetUnit, bool userTarget)
778 {
779 	const int fn = LUAFN_BlockShot;
780 
781 	if (!HasFunction(fn))
782 		return false;
783 
784 	LUA_CALL_IN_CHECK(L, false);
785 	lua_checkstack(L, 4);
786 
787 	PushFunction(fn);
788 	lua_pushnumber(L, weaponNum + LUA_WEAPON_BASE_INDEX);
789 	PushUnit(targetUnit);
790 	lua_pushboolean(L, userTarget);
791 
792 	if (!RunCallIn(fn, 3, 1))
793 		return false;
794 
795 	return PopBoolean(fn, false);
796 }
797 
798 
TargetWeight(int weaponNum,const CUnit * targetUnit)799 float CLuaUnitScript::TargetWeight(int weaponNum, const CUnit* targetUnit)
800 {
801 	const int fn = LUAFN_TargetWeight;
802 
803 	if (!HasFunction(fn))
804 		return 1.0f;
805 
806 	LUA_CALL_IN_CHECK(L, 1.0f);
807 	lua_checkstack(L, 3);
808 
809 	PushFunction(fn);
810 	lua_pushnumber(L, weaponNum + LUA_WEAPON_BASE_INDEX);
811 	PushUnit(targetUnit);
812 
813 	if (!RunCallIn(fn, 2, 1))
814 		return 1.0f;
815 
816 	return PopNumber(fn, 1.0f);
817 }
818 
819 
AnimFinished(AnimType type,int piece,int axis)820 void CLuaUnitScript::AnimFinished(AnimType type, int piece, int axis)
821 {
822 	const int fn = (type == AMove ? LUAFN_MoveFinished : LUAFN_TurnFinished);
823 
824 	Call(fn, piece + 1, axis + 1);
825 }
826 
827 
RawCall(int functionId)828 void CLuaUnitScript::RawCall(int functionId)
829 {
830 	if (functionId < 0) {
831 		return;
832 	}
833 
834 	LUA_CALL_IN_CHECK(L);
835 	lua_checkstack(L, 1);
836 
837 	RawPushFunction(functionId);
838 	RawRunCallIn(functionId, 0, 0);
839 }
840 
841 
GetScriptName(int functionId) const842 string CLuaUnitScript::GetScriptName(int functionId) const
843 {
844 	// only for error messages, so speed doesn't matter
845 	map<string, int>::const_iterator it = scriptNames.begin();
846 	for (; it != scriptNames.end(); ++it) {
847 		if (it->second == functionId) return it->first;
848 	}
849 	std::stringstream s;
850 	s << "<unnamed: " << functionId << ">";
851 	return s.str();
852 }
853 
854 
RawRunCallIn(int functionId,int inArgs,int outArgs)855 bool CLuaUnitScript::RawRunCallIn(int functionId, int inArgs, int outArgs)
856 {
857 	CUnit* oldActiveUnit = activeUnit;
858 	CUnitScript* oldActiveScript = activeScript;
859 
860 	activeUnit = unit;
861 	activeScript = this;
862 
863 	std::string err;
864 	const int error = handle->RunCallIn(L, inArgs, outArgs, err);
865 
866 	activeUnit = oldActiveUnit;
867 	activeScript = oldActiveScript;
868 
869 	if (error != 0) {
870 		const string& fname = GetScriptName(functionId);
871 
872 		LOG_L(L_ERROR, "%s::RunCallIn: error = %i, %s::%s, %s",
873 				handle->GetName().c_str(), error, "CLuaUnitScript",
874 				fname.c_str(), err.c_str());
875 		RemoveCallIn(fname);
876 
877 		return false;
878 	}
879 	return true;
880 }
881 
882 
Destroy()883 void CLuaUnitScript::Destroy() { Call(LUAFN_Destroy); }
StartMoving(bool reversing)884 void CLuaUnitScript::StartMoving(bool reversing) { Call(LUAFN_StartMoving, reversing * 1.0f); }
StopMoving()885 void CLuaUnitScript::StopMoving() { Call(LUAFN_StopMoving); }
StartUnload()886 void CLuaUnitScript::StartUnload() { Call(LUAFN_StartUnload); }
EndTransport()887 void CLuaUnitScript::EndTransport() { Call(LUAFN_EndTransport); }
StartBuilding()888 void CLuaUnitScript::StartBuilding() { Call(LUAFN_StartBuilding); }
StopBuilding()889 void CLuaUnitScript::StopBuilding() { Call(LUAFN_StopBuilding); }
Falling()890 void CLuaUnitScript::Falling() { Call(LUAFN_Falling); }
Landed()891 void CLuaUnitScript::Landed() { Call(LUAFN_Landed); }
Activate()892 void CLuaUnitScript::Activate() { Call(LUAFN_Activate); }
Deactivate()893 void CLuaUnitScript::Deactivate() { Call(LUAFN_Deactivate); }
MoveRate(int curRate)894 void CLuaUnitScript::MoveRate(int curRate) { Call(LUAFN_MoveRate, curRate); }
FireWeapon(int weaponNum)895 void CLuaUnitScript::FireWeapon(int weaponNum) { Call(LUAFN_FireWeapon, weaponNum + LUA_WEAPON_BASE_INDEX); }
EndBurst(int weaponNum)896 void CLuaUnitScript::EndBurst(int weaponNum) { Call(LUAFN_EndBurst, weaponNum + LUA_WEAPON_BASE_INDEX); }
897 
898 
899 /******************************************************************************/
900 /******************************************************************************/
901 
902 
PushEntry(lua_State * L,const char * name,lua_CFunction fun)903 static void PushEntry(lua_State* L, const char* name, lua_CFunction fun)
904 {
905 	lua_pushstring(L, name);
906 	lua_pushcfunction(L, fun);
907 	lua_rawset(L, -3);
908 }
909 
910 
PushEntries(lua_State * L)911 bool CLuaUnitScript::PushEntries(lua_State* L)
912 {
913 	lua_pushstring(L, "UnitScript");
914 	lua_newtable(L);
915 
916 #define REGISTER_LUA_CFUNC(x) \
917 	PushEntry(L, #x, x)
918 
919 	REGISTER_LUA_CFUNC(CreateScript);
920 	REGISTER_LUA_CFUNC(UpdateCallIn);
921 	REGISTER_LUA_CFUNC(CallAsUnit);
922 
923 	REGISTER_LUA_CFUNC(GetUnitValue);
924 	REGISTER_LUA_CFUNC(SetUnitValue);
925 	REGISTER_LUA_CFUNC(SetPieceVisibility);
926 	REGISTER_LUA_CFUNC(EmitSfx);
927 	REGISTER_LUA_CFUNC(AttachUnit);
928 	REGISTER_LUA_CFUNC(DropUnit);
929 	REGISTER_LUA_CFUNC(Explode);
930 	REGISTER_LUA_CFUNC(ShowFlare);
931 
932 	REGISTER_LUA_CFUNC(Spin);
933 	REGISTER_LUA_CFUNC(StopSpin);
934 	REGISTER_LUA_CFUNC(Turn);
935 	REGISTER_LUA_CFUNC(Move);
936 	REGISTER_LUA_CFUNC(IsInTurn);
937 	REGISTER_LUA_CFUNC(IsInMove);
938 	REGISTER_LUA_CFUNC(IsInSpin);
939 	REGISTER_LUA_CFUNC(WaitForTurn);
940 	REGISTER_LUA_CFUNC(WaitForMove);
941 
942 	REGISTER_LUA_CFUNC(SetDeathScriptFinished);
943 
944 	REGISTER_LUA_CFUNC(GetPieceTranslation);
945 	REGISTER_LUA_CFUNC(GetPieceRotation);
946 	REGISTER_LUA_CFUNC(GetPiecePosDir);
947 
948 	REGISTER_LUA_CFUNC(GetActiveUnitID);
949 
950 	lua_rawset(L, -3);
951 
952 	// backwards compatibility
953 	REGISTER_LUA_CFUNC(GetUnitCOBValue);
954 	REGISTER_LUA_CFUNC(SetUnitCOBValue);
955 
956 	return true;
957 }
958 
959 
960 /******************************************************************************/
961 /******************************************************************************/
962 //
963 //  Parsing helpers
964 //
965 
966 // FIXME: this badly needs a clean up, it's duplicated
967 
ParseRawUnit(lua_State * L,const char * caller,int index)968 static inline CUnit* ParseRawUnit(lua_State* L, const char* caller, int index)
969 {
970 	if (!lua_israwnumber(L, index)) {
971 		luaL_error(L, "%s(): Bad unitID", caller);
972 	}
973 	const int unitID = lua_toint(L, index);
974 	if ((unitID < 0) || (static_cast<size_t>(unitID) >= unitHandler->MaxUnits())) {
975 		luaL_error(L, "%s(): Bad unitID: %d", caller, unitID);
976 	}
977 	return unitHandler->units[unitID];
978 }
979 
980 
ParseUnit(lua_State * L,const char * caller,int index)981 static inline CUnit* ParseUnit(lua_State* L, const char* caller, int index)
982 {
983 	CUnit* unit = ParseRawUnit(L, caller, index);
984 	if (unit == NULL) {
985 		return NULL;
986 	}
987 	if (!CanControlUnit(L, unit)) {
988 		return NULL;
989 	}
990 	return unit;
991 }
992 
993 
ParseAxis(lua_State * L,const char * caller,int index)994 static inline int ParseAxis(lua_State* L, const char* caller, int index)
995 {
996 	if (!lua_israwnumber(L, index)) {
997 		luaL_error(L, "%s(): Bad axis", caller);
998 	}
999 	const int axis  = lua_toint(L, index) - 1;
1000 	if ((axis < 0) || (axis > 2)) {
1001 		luaL_error(L, "%s(): Bad axis", caller);
1002 	}
1003 	return axis;
1004 }
1005 
1006 
1007 /******************************************************************************/
1008 /******************************************************************************/
1009 
1010 
CreateScript(lua_State * L)1011 int CLuaUnitScript::CreateScript(lua_State* L)
1012 {
1013 	CUnit* unit = ParseUnit(L, __FUNCTION__, 1);
1014 	if (unit == NULL) {
1015 		LUA_TRACE("no such unit");
1016 		return 0;
1017 	}
1018 
1019 	// check table of callIns
1020 	// (we might not get a chance to clean up later on, if something is wrong)
1021 	if (!lua_istable(L, 2)) {
1022 		luaL_error(L, "%s(): error parsing callIn table", __FUNCTION__);
1023 	}
1024 	for (lua_pushnil(L); lua_next(L, 2) != 0; lua_pop(L, 1)) {
1025 		if (!lua_israwstring(L, -2) || !lua_isfunction(L, -1)) {
1026 			luaL_error(L, "%s(): error parsing callIn table", __FUNCTION__);
1027 		}
1028 	}
1029 
1030 	// replace the unit's script (ctor parses callIn table)
1031 	CLuaUnitScript* newScript = new CLuaUnitScript(L, unit);
1032 
1033 	if (unit->script != &CNullUnitScript::value) {
1034 		delete unit->script;
1035 	}
1036 	unit->script = newScript;
1037 
1038 	// flush some caches (which store availability of certain script functions)
1039 	for (std::vector<CWeapon*>::iterator wi = unit->weapons.begin(); wi != unit->weapons.end(); ++wi) {
1040 		CWeapon* w = *wi;
1041 		w->SetWeaponNum(w->weaponNum);
1042 	}
1043 
1044 	LUA_TRACE("script replaced with CLuaUnitScript");
1045 
1046 	return 0;
1047 }
1048 
1049 
UpdateCallIn(lua_State * L)1050 int CLuaUnitScript::UpdateCallIn(lua_State* L)
1051 {
1052 	CUnit* unit = ParseUnit(L, __FUNCTION__, 1);
1053 	if (unit == NULL) {
1054 		return 0;
1055 	}
1056 	CLuaUnitScript* script = dynamic_cast<CLuaUnitScript*>(unit->script);
1057 	if (script == NULL) {
1058 		luaL_error(L, "%s(): not a Lua unit script", __FUNCTION__);
1059 	}
1060 	// we would get confused if our refs aren't together in a single state
1061 	if (L != script->L) {
1062 		luaL_error(L, "%s(): incorrect lua_State", __FUNCTION__);
1063 	}
1064 	if (!lua_israwstring(L, 2) || (!lua_isfunction(L, 3) && !lua_isnoneornil(L, 3))) {
1065 		luaL_error(L, "Incorrect arguments to %s()", __FUNCTION__);
1066 	}
1067 
1068 	return script->UpdateCallIn();
1069 }
1070 
1071 
CallAsUnit(lua_State * L)1072 int CLuaUnitScript::CallAsUnit(lua_State* L)
1073 {
1074 	CUnit* unit = ParseUnit(L, __FUNCTION__, 1);
1075 	if (unit == NULL) {
1076 		return 0;
1077 	}
1078 
1079 	const int funcIndex = 2;
1080 
1081 	if (!lua_isfunction(L, funcIndex)) {
1082 		luaL_error(L, "Incorrect arguments to %s()", __FUNCTION__);
1083 	}
1084 
1085 	CUnit* oldActiveUnit = activeUnit;
1086 	CUnitScript* oldActiveScript = activeScript;
1087 
1088 	activeUnit = unit;
1089 	activeScript = unit->script;
1090 
1091 	const int error = lua_pcall(L, lua_gettop(L) - funcIndex, LUA_MULTRET, 0);
1092 
1093 	activeUnit = oldActiveUnit;
1094 	activeScript = oldActiveScript;
1095 
1096 	if (error != 0) {
1097 		lua_error(L);
1098 	}
1099 
1100 	return (lua_gettop(L) - funcIndex + 1);
1101 }
1102 
1103 
1104 // moved from LuaSyncedCtrl
1105 
GetUnitValue(lua_State * L,CUnitScript * script,int arg)1106 int CLuaUnitScript::GetUnitValue(lua_State* L, CUnitScript* script, int arg)
1107 {
1108 	bool splitData = false;
1109 	if (lua_isboolean(L, arg)) {
1110 		splitData = lua_toboolean(L, arg);
1111 		arg++;
1112 	}
1113 
1114 	const int val = luaL_checkint(L, arg); arg++;
1115 
1116 	int p[4];
1117 	for (int a = 0; a < 4; a++, arg++) {
1118 		if (lua_istable(L, arg)) {
1119 			int x, z;
1120 			lua_rawgeti(L, arg, 1); x = luaL_checkint(L, -1); lua_pop(L, 1);
1121 			lua_rawgeti(L, arg, 2); z = luaL_checkint(L, -1); lua_pop(L, 1);
1122 			p[a] = PACKXZ(x, z);
1123 		}
1124 		else {
1125 			p[a] = (int)luaL_optnumber(L, arg, 0);
1126 		}
1127 	}
1128 
1129 	const int result = script->GetUnitVal(val, p[0], p[1], p[2], p[3]);
1130 	if (!splitData) {
1131 		lua_pushnumber(L, result);
1132 		return 1;
1133 	}
1134 	lua_pushnumber(L, UNPACKX(result));
1135 	lua_pushnumber(L, UNPACKZ(result));
1136 	return 2;
1137 }
1138 
1139 
GetUnitCOBValue(lua_State * L)1140 int CLuaUnitScript::GetUnitCOBValue(lua_State* L)
1141 {
1142 	CUnit* unit = ParseUnit(L, __FUNCTION__, 1);
1143 	if (unit == NULL) {
1144 		return 0;
1145 	}
1146 
1147 	return GetUnitValue(L, unit->script, 2);
1148 }
1149 
1150 
GetUnitValue(lua_State * L)1151 int CLuaUnitScript::GetUnitValue(lua_State* L)
1152 {
1153 	if (activeScript == NULL) {
1154 		return 0;
1155 	}
1156 
1157 	return GetUnitValue(L, activeScript, 1);
1158 }
1159 
1160 
1161 // moved from LuaSyncedCtrl
1162 
SetUnitValue(lua_State * L,CUnitScript * script,int arg)1163 int CLuaUnitScript::SetUnitValue(lua_State* L, CUnitScript* script, int arg)
1164 {
1165 	const int args = lua_gettop(L) - arg; // number of arguments
1166 	const int val = luaL_checkint(L, arg++);
1167 	int param;
1168 	if (args == 1) {
1169 		param = lua_isboolean(L, arg) ? int(lua_toboolean(L, arg++)) : luaL_checkint(L, arg++);
1170 	}
1171 	else {
1172 		const int x = luaL_checkint(L, arg++);
1173 		const int z = luaL_checkint(L, arg++);
1174 		param = PACKXZ(x, z);
1175 	}
1176 	script->SetUnitVal(val, param);
1177 	return 0;
1178 }
1179 
1180 
SetUnitCOBValue(lua_State * L)1181 int CLuaUnitScript::SetUnitCOBValue(lua_State* L)
1182 {
1183 	CUnit* unit = ParseUnit(L, __FUNCTION__, 1);
1184 	if (unit == NULL) {
1185 		return 0;
1186 	}
1187 
1188 	return SetUnitValue(L, unit->script, 2);
1189 }
1190 
1191 
SetUnitValue(lua_State * L)1192 int CLuaUnitScript::SetUnitValue(lua_State* L)
1193 {
1194 	if (activeScript == NULL) {
1195 		return 0;
1196 	}
1197 
1198 	return SetUnitValue(L, activeScript, 1);
1199 }
1200 
1201 
SetPieceVisibility(lua_State * L)1202 int CLuaUnitScript::SetPieceVisibility(lua_State* L)
1203 {
1204 	// void SetVisibility(int piece, bool visible);
1205 	if (activeScript == NULL) {
1206 		return 0;
1207 	}
1208 
1209 	// note: for Lua unit scripts it would be confusing if the unit's
1210 	// unit->script->pieces differs from the unit->localModel->pieces.
1211 
1212 	const int piece = luaL_checkint(L, 1) - 1;
1213 	const bool visible = lua_toboolean(L, 2);
1214 	activeScript->SetVisibility(piece, visible);
1215 	return 0;
1216 }
1217 
1218 
EmitSfx(lua_State * L)1219 int CLuaUnitScript::EmitSfx(lua_State* L)
1220 {
1221 	// void EmitSfx(int type, int piece);
1222 	if (activeScript == NULL) {
1223 		return 0;
1224 	}
1225 
1226 	// note: the arguments are reversed compared to the C++ (and COB?) function
1227 	const int piece = luaL_checkint(L, 1) - 1;
1228 	const int type = luaL_checkint(L, 2);
1229 
1230 	activeScript->EmitSfx(type, piece);
1231 	return 0;
1232 }
1233 
1234 
AttachUnit(lua_State * L)1235 int CLuaUnitScript::AttachUnit(lua_State* L)
1236 {
1237 	// void AttachUnit(int piece, int unit);
1238 	if (activeScript == NULL) {
1239 		return 0;
1240 	}
1241 	const int piece = luaL_checkint(L, 1) - 1;
1242 	const CUnit* transportee = ParseUnit(L, __FUNCTION__, 2);
1243 	if (transportee == NULL) {
1244 		return 0;
1245 	}
1246 	activeScript->AttachUnit(piece, transportee->id);
1247 	return 0;
1248 }
1249 
1250 
DropUnit(lua_State * L)1251 int CLuaUnitScript::DropUnit(lua_State* L)
1252 {
1253 	// void DropUnit(int unit);
1254 	if (activeScript == NULL) {
1255 		return 0;
1256 	}
1257 	const CUnit* transportee = ParseUnit(L, __FUNCTION__, 1);
1258 	if (transportee == NULL) {
1259 		return 0;
1260 	}
1261 	activeScript->DropUnit(transportee->id);
1262 	return 0;
1263 }
1264 
1265 
Explode(lua_State * L)1266 int CLuaUnitScript::Explode(lua_State* L)
1267 {
1268 	// void Explode(int piece, int flags);
1269 	if (activeScript == NULL) {
1270 		return 0;
1271 	}
1272 	const int piece = luaL_checkint(L, 1) - 1;
1273 	const int flags = luaL_checkint(L, 2);
1274 	activeScript->Explode(piece, flags);
1275 	return 0;
1276 }
1277 
1278 
ShowFlare(lua_State * L)1279 int CLuaUnitScript::ShowFlare(lua_State* L)
1280 {
1281 	// void ShowFlare(int piece);
1282 	if (activeScript == NULL) {
1283 		return 0;
1284 	}
1285 	const int piece = luaL_checkint(L, 1) - 1;
1286 	activeScript->ShowFlare(piece);
1287 	return 0;
1288 }
1289 
1290 
Spin(lua_State * L)1291 int CLuaUnitScript::Spin(lua_State* L)
1292 {
1293 	// void Spin(int piece, int axis, int speed, int accel);
1294 	if (activeScript == NULL) {
1295 		return 0;
1296 	}
1297 	const int piece = luaL_checkint(L, 1) - 1;
1298 	const int axis = ParseAxis(L, __FUNCTION__, 2);
1299 	const float speed = luaL_checkfloat(L, 3);
1300 	const float accel = luaL_optfloat(L, 4, 0.0f); // accel == 0 -> start at desired speed immediately
1301 
1302 	activeScript->Spin(piece, axis, speed, accel);
1303 	return 0;
1304 }
1305 
1306 
StopSpin(lua_State * L)1307 int CLuaUnitScript::StopSpin(lua_State* L)
1308 {
1309 	// void StopSpin(int piece, int axis, int decel);
1310 	if (activeScript == NULL) {
1311 		return 0;
1312 	}
1313 	const int piece = luaL_checkint(L, 1) - 1;
1314 	const int axis = ParseAxis(L, __FUNCTION__, 2);
1315 	const float decel = luaL_optfloat(L, 3, 0.0f); // decel == 0 -> stop immediately
1316 
1317 	activeScript->StopSpin(piece, axis, decel);
1318 	return 0;
1319 }
1320 
1321 
Turn(lua_State * L)1322 int CLuaUnitScript::Turn(lua_State* L)
1323 {
1324 	// void Turn(int piece, int axis, int speed, int destination);
1325 	// void TurnNow(int piece, int axis, int destination);
1326 	if (activeScript == NULL) {
1327 		return 0;
1328 	}
1329 	const int piece = luaL_checkint(L, 1) - 1;
1330 	const int axis = ParseAxis(L, __FUNCTION__, 2);
1331 	const float dest  = luaL_checkfloat(L, 3);
1332 	const float speed = luaL_optfloat(L, 4, 0.0f); // speed == 0 -> TurnNow
1333 
1334 	if (speed == 0.0f) {
1335 		activeScript->TurnNow(piece, axis, dest);
1336 	} else {
1337 		activeScript->Turn(piece, axis, speed, dest);
1338 	}
1339 
1340 	if (lua_isboolean(L, 5) && lua_toboolean(L, 5)) {
1341 		// CUnit* unit = activeScript->GetUnit();
1342 		// LocalModel* model = unit->localModel;
1343 		LocalModelPiece* piece = ParseLocalModelPiece(L, activeScript, __FUNCTION__);
1344 
1345 		// note:
1346 		//   both of these only have effect if MoveNow() was called, but
1347 		//   the former starts from the ROOT piece (less efficient here)
1348 		// model->UpdatePieceMatrices();
1349 		piece->UpdateMatricesRec(true);
1350 	}
1351 
1352 	return 0;
1353 }
1354 
1355 
Move(lua_State * L)1356 int CLuaUnitScript::Move(lua_State* L)
1357 {
1358 	// void Move(int piece, int axis, int speed, int destination);
1359 	// void MoveNow(int piece, int axis, int destination);
1360 	if (activeScript == NULL) {
1361 		return 0;
1362 	}
1363 	const int piece = luaL_checkint(L, 1) - 1;
1364 	const int axis = ParseAxis(L, __FUNCTION__, 2);
1365 	const float dest  = luaL_checkfloat(L, 3);
1366 	const float speed = luaL_optfloat(L, 4, 0.0f); // speed == 0 -> MoveNow
1367 
1368 	if (speed == 0.0f) {
1369 		activeScript->MoveNow(piece, axis, dest);
1370 	} else {
1371 		activeScript->Move(piece, axis, speed, dest);
1372 	}
1373 
1374 	if (lua_isboolean(L, 5) && lua_toboolean(L, 5)) {
1375 		// CUnit* unit = activeScript->GetUnit();
1376 		// LocalModel* model = unit->localModel;
1377 		LocalModelPiece* piece = ParseLocalModelPiece(L, activeScript, __FUNCTION__);
1378 
1379 		// note:
1380 		//   both of these only have effect if MoveNow() was called, but
1381 		//   the former starts from the ROOT piece (less efficient here)
1382 		// model->UpdatePieceMatrices();
1383 		piece->UpdateMatricesRec(true);
1384 	}
1385 
1386 	return 0;
1387 }
1388 
1389 
IsInAnimation(lua_State * L,const char * caller,AnimType type)1390 int CLuaUnitScript::IsInAnimation(lua_State* L, const char* caller, AnimType type)
1391 {
1392 	if (activeScript == NULL) {
1393 		return 0;
1394 	}
1395 	const int piece = luaL_checkint(L, 1) - 1;
1396 	const int axis  = ParseAxis(L, caller, 2);
1397 
1398 	lua_pushboolean(L, activeScript->IsInAnimation(type, piece, axis));
1399 	return 1;
1400 }
1401 
1402 
IsInTurn(lua_State * L)1403 int CLuaUnitScript::IsInTurn(lua_State* L)
1404 {
1405 	return IsInAnimation(L, __FUNCTION__, ATurn);
1406 }
1407 
1408 
IsInMove(lua_State * L)1409 int CLuaUnitScript::IsInMove(lua_State* L)
1410 {
1411 	return IsInAnimation(L, __FUNCTION__, AMove);
1412 }
1413 
1414 
IsInSpin(lua_State * L)1415 int CLuaUnitScript::IsInSpin(lua_State* L)
1416 {
1417 	return IsInAnimation(L, __FUNCTION__, ASpin);
1418 }
1419 
1420 
WaitForAnimation(lua_State * L,const char * caller,AnimType type)1421 int CLuaUnitScript::WaitForAnimation(lua_State* L, const char* caller, AnimType type)
1422 {
1423 	if (activeScript == NULL) {
1424 		return 0;
1425 	}
1426 	CLuaUnitScript* script = dynamic_cast<CLuaUnitScript*>(activeScript);
1427 	if (script == NULL) {
1428 		luaL_error(L, "%s(): not a Lua unit script", caller);
1429 	}
1430 	const int piece = luaL_checkint(L, 1) - 1;
1431 	const int axis  = ParseAxis(L, caller, 2);
1432 
1433 	lua_pushboolean(L, script->AddAnimListener(type, piece, axis, script));
1434 	return 1;
1435 }
1436 
1437 
WaitForTurn(lua_State * L)1438 int CLuaUnitScript::WaitForTurn(lua_State* L)
1439 {
1440 	return WaitForAnimation(L, __FUNCTION__, ATurn);
1441 }
1442 
1443 
WaitForMove(lua_State * L)1444 int CLuaUnitScript::WaitForMove(lua_State* L)
1445 {
1446 	return WaitForAnimation(L, __FUNCTION__, AMove);
1447 }
1448 
1449 
SetDeathScriptFinished(lua_State * L)1450 int CLuaUnitScript::SetDeathScriptFinished(lua_State* L)
1451 {
1452 	if (activeUnit == NULL || activeScript == NULL) {
1453 		return 0;
1454 	}
1455 	CLuaUnitScript* script = dynamic_cast<CLuaUnitScript*>(activeScript);
1456 	if (script == NULL || !script->inKilled) {
1457 		luaL_error(L, "%s(): not a Lua unit script or 'Killed' not called", __FUNCTION__);
1458 	}
1459 	activeUnit->deathScriptFinished = true;
1460 	activeUnit->delayedWreckLevel = luaL_optint(L, 1, -1);
1461 	return 0;
1462 }
1463 
1464 /******************************************************************************/
1465 
GetPieceTranslation(lua_State * L)1466 int CLuaUnitScript::GetPieceTranslation(lua_State* L)
1467 {
1468 	if (activeScript == NULL) {
1469 		return 0;
1470 	}
1471 	LocalModelPiece* piece = ParseLocalModelPiece(L, activeScript, __FUNCTION__);
1472 	return ToLua(L, piece->GetPosition() - piece->original->offset);
1473 }
1474 
1475 
GetPieceRotation(lua_State * L)1476 int CLuaUnitScript::GetPieceRotation(lua_State* L)
1477 {
1478 	if (activeScript == NULL) {
1479 		return 0;
1480 	}
1481 	LocalModelPiece* piece = ParseLocalModelPiece(L, activeScript, __FUNCTION__);
1482 	return ToLua(L, piece->GetRotation());
1483 }
1484 
1485 
GetPiecePosDir(lua_State * L)1486 int CLuaUnitScript::GetPiecePosDir(lua_State* L)
1487 {
1488 	if (activeScript == NULL) {
1489 		return 0;
1490 	}
1491 	LocalModelPiece* piece = ParseLocalModelPiece(L, activeScript, __FUNCTION__);
1492 	float3 pos, dir;
1493 	if (!piece->GetEmitDirPos(pos, dir)) {
1494 		return 0;
1495 	}
1496 	ToLua(L, pos); ToLua(L, dir);
1497 	return 6;
1498 }
1499 
1500 /******************************************************************************/
1501 /******************************************************************************/
1502 
GetActiveUnitID(lua_State * L)1503 int CLuaUnitScript::GetActiveUnitID(lua_State* L)
1504 {
1505 	if (activeScript == NULL) { return 0; }
1506 	if (activeUnit == NULL) { return 0; }
1507 
1508 	lua_pushnumber(L, activeUnit->id);
1509 	return 1;
1510 }
1511