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