1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3
4 #include "CobEngine.h"
5 #include "CobFile.h"
6 #include "CobInstance.h"
7 #include "CobThread.h"
8 #include "UnitScriptLog.h"
9
10 #ifndef _CONSOLE
11
12 #include "Game/GameHelper.h"
13 #include "Game/GlobalUnsynced.h"
14 #include "Map/Ground.h"
15 #include "Sim/Misc/GroundBlockingObjectMap.h"
16 #include "Sim/Misc/LosHandler.h"
17 #include "Sim/Misc/RadarHandler.h"
18 #include "Sim/Misc/TeamHandler.h"
19 #include "Sim/Projectiles/ExplosionGenerator.h"
20 #include "Sim/Projectiles/PieceProjectile.h"
21 #include "Sim/Projectiles/ProjectileHandler.h"
22 #include "Sim/Projectiles/Unsynced/BubbleProjectile.h"
23 #include "Sim/Projectiles/Unsynced/HeatCloudProjectile.h"
24 #include "Sim/Projectiles/Unsynced/MuzzleFlame.h"
25 #include "Sim/Projectiles/Unsynced/SmokeProjectile.h"
26 #include "Sim/Projectiles/Unsynced/WakeProjectile.h"
27 #include "Sim/Projectiles/Unsynced/WreckProjectile.h"
28 #include "Sim/Units/CommandAI/CommandAI.h"
29 #include "Sim/Units/CommandAI/Command.h"
30 #include "Sim/Units/UnitDef.h"
31 #include "Sim/Units/Unit.h"
32 #include "Sim/Units/UnitHandler.h"
33 #include "Sim/Units/UnitTypes/TransportUnit.h"
34 #include "Sim/Weapons/BeamLaser.h"
35 #include "Sim/Weapons/PlasmaRepulser.h"
36 #include "Sim/Weapons/WeaponDefHandler.h"
37 #include "Sim/Weapons/Weapon.h"
38 #include "System/Util.h"
39 #include "System/myMath.h"
40 #include "System/Sound/ISoundChannels.h"
41 #include "System/Sync/SyncTracer.h"
42
43 #endif // _CONSOLE
44
45
46 /******************************************************************************/
47 /******************************************************************************/
48
49
HasFunction(int id) const50 inline bool CCobInstance::HasFunction(int id) const
51 {
52 return script.scriptIndex[id] >= 0;
53 }
54
55
CCobInstance(CCobFile & _script,CUnit * _unit)56 CCobInstance::CCobInstance(CCobFile& _script, CUnit* _unit)
57 : CUnitScript(_unit, pieces)
58 , script(_script)
59 {
60 staticVars.reserve(script.numStaticVars);
61 for (int i = 0; i < script.numStaticVars; ++i) {
62 staticVars.push_back(0);
63 }
64
65 MapScriptToModelPieces(unit->localModel);
66
67 hasSetSFXOccupy = HasFunction(COBFN_SetSFXOccupy);
68 hasRockUnit = HasFunction(COBFN_RockUnit);
69 hasStartBuilding = HasFunction(COBFN_StartBuilding);
70 }
71
72
~CCobInstance()73 CCobInstance::~CCobInstance()
74 {
75 //this may be dangerous, is it really desired?
76 //Destroy();
77
78 do {
79 for (int animType = ATurn; animType <= AMove; animType++) {
80 for (std::list<AnimInfo *>::iterator i = anims[animType].begin(); i != anims[animType].end(); ++i) {
81 // All threads blocking on animations can be killed safely from here since the scheduler does not
82 // know about them
83 std::list<IAnimListener *>& listeners = (*i)->listeners;
84 while (!listeners.empty()) {
85 IAnimListener* al = listeners.front();
86 listeners.pop_front();
87 delete al;
88 }
89 // the anims are deleted in ~CUnitScript
90 }
91 }
92 // callbacks may add new threads, and therefore listeners
93 } while (HaveListeners());
94
95 // Can't delete the thread here because that would confuse the scheduler to no end
96 // Instead, mark it as dead. It is the function calling Tick that is responsible for delete.
97 // Also unregister all callbacks
98 for (std::list<CCobThread *>::iterator i = threads.begin(); i != threads.end(); ++i) {
99 (*i)->state = CCobThread::Dead;
100 (*i)->SetCallback(NULL, NULL, NULL);
101 }
102 }
103
104
MapScriptToModelPieces(LocalModel * lmodel)105 void CCobInstance::MapScriptToModelPieces(LocalModel* lmodel)
106 {
107 std::vector<std::string>& pieceNames = script.pieceNames; // already in lowercase!
108 std::vector<LocalModelPiece*>& lmodelPieces = lmodel->pieces;
109
110 pieces.clear();
111 pieces.reserve(pieceNames.size());
112
113 // clear the default assumed 1:1 mapping
114 for (size_t lmodelPieceNum = 0; lmodelPieceNum < lmodelPieces.size(); lmodelPieceNum++) {
115 lmodelPieces[lmodelPieceNum]->SetScriptPieceIndex(-1);
116 }
117 for (size_t scriptPieceNum = 0; scriptPieceNum < pieceNames.size(); scriptPieceNum++) {
118 unsigned int lmodelPieceNum;
119
120 // Map this piecename to an index in the script's pieceinfo
121 for (lmodelPieceNum = 0; lmodelPieceNum < lmodelPieces.size(); lmodelPieceNum++) {
122 if (lmodelPieces[lmodelPieceNum]->original->name.compare(pieceNames[scriptPieceNum]) == 0) {
123 break;
124 }
125 }
126
127 // Not found? Try lowercase
128 if (lmodelPieceNum == lmodelPieces.size()) {
129 for (lmodelPieceNum = 0; lmodelPieceNum < lmodelPieces.size(); lmodelPieceNum++) {
130 if (StringToLower(lmodelPieces[lmodelPieceNum]->original->name).compare(pieceNames[scriptPieceNum]) == 0) {
131 break;
132 }
133 }
134 }
135
136 // Did we find it?
137 if (lmodelPieceNum < lmodelPieces.size()) {
138 lmodelPieces[lmodelPieceNum]->SetScriptPieceIndex(scriptPieceNum);
139 pieces.push_back(lmodelPieces[lmodelPieceNum]);
140 } else {
141 pieces.push_back(NULL);
142
143 const char* fmtString = "[%s] could not find piece named \"%s\" (referenced by COB script \"%s\")";
144 const char* pieceName = pieceNames[scriptPieceNum].c_str();
145 const char* scriptName = script.name.c_str();
146
147 LOG_L(L_WARNING, fmtString, __FUNCTION__, pieceName, scriptName);
148 }
149 }
150 }
151
152
GetFunctionId(const std::string & fname) const153 int CCobInstance::GetFunctionId(const std::string& fname) const
154 {
155 return script.GetFunctionId(fname);
156 }
157
158
HasBlockShot(int weaponNum) const159 bool CCobInstance::HasBlockShot(int weaponNum) const
160 {
161 return HasFunction(COBFN_BlockShot + COBFN_Weapon_Funcs * weaponNum);
162 }
163
164
HasTargetWeight(int weaponNum) const165 bool CCobInstance::HasTargetWeight(int weaponNum) const
166 {
167 return HasFunction(COBFN_TargetWeight + COBFN_Weapon_Funcs * weaponNum);
168 }
169
170
171 /******************************************************************************/
172 /******************************************************************************/
173
174
Create()175 void CCobInstance::Create()
176 {
177 // Calculate the max() of the available weapon reloadtimes
178 int maxReloadTime = 0;
179
180 for (vector<CWeapon*>::iterator i = unit->weapons.begin(); i != unit->weapons.end(); ++i) {
181 maxReloadTime = std::max(maxReloadTime, (*i)->reloadTime);
182
183 #if 0
184 if (dynamic_cast<CBeamLaser*>(*i))
185 maxReloadTime = 150; // ???
186 #endif
187 }
188
189 // convert ticks to milliseconds
190 maxReloadTime *= GAME_SPEED;
191
192 #if 0
193 // TA does some special handling depending on weapon count, Spring != TA
194 if (unit->weapons.size() > 1) {
195 maxReloadTime = std::max(maxReloadTime, 3000);
196 }
197 #endif
198
199 Call(COBFN_Create);
200 Call(COBFN_SetMaxReloadTime, maxReloadTime);
201 }
202
203
204 // Called when a unit's Killed script finishes executing
CUnitKilledCB(int retCode,void * p1,void * p2)205 static void CUnitKilledCB(int retCode, void* p1, void* p2)
206 {
207 CUnit* self = static_cast<CUnit*>(p1);
208 self->deathScriptFinished = true;
209 self->delayedWreckLevel = retCode;
210 }
211
212
Killed()213 void CCobInstance::Killed()
214 {
215 vector<int> args;
216 args.reserve(2);
217 args.push_back((int) (unit->recentDamage / unit->maxHealth * 100));
218 args.push_back(0);
219 Call(COBFN_Killed, args, &CUnitKilledCB, unit, NULL);
220 unit->delayedWreckLevel = args[1];
221 }
222
223
WindChanged(float heading,float speed)224 void CCobInstance::WindChanged(float heading, float speed)
225 {
226 Call(COBFN_SetSpeed, (int)(speed * 3000.0f));
227 Call(COBFN_SetDirection, short(heading * RAD2TAANG));
228 }
229
230
ExtractionRateChanged(float speed)231 void CCobInstance::ExtractionRateChanged(float speed)
232 {
233 Call(COBFN_SetSpeed, (int)(speed * 500.0f));
234 if (unit->activated) {
235 Call(COBFN_Go);
236 }
237 }
238
239
RockUnit(const float3 & rockDir)240 void CCobInstance::RockUnit(const float3& rockDir)
241 {
242 vector<int> args;
243 args.reserve(2);
244 args.push_back((int)(500 * rockDir.z));
245 args.push_back((int)(500 * rockDir.x));
246 Call(COBFN_RockUnit, args);
247 }
248
249
250 // ugly hack to get return value of HitByWeaponId script
251 static float weaponHitMod; ///< fraction of weapondamage to use when hit by weapon
hitByWeaponIdCallback(int retCode,void * p1,void * p2)252 static void hitByWeaponIdCallback(int retCode, void* p1, void* p2) { weaponHitMod = retCode * 0.01f; }
253
254
HitByWeapon(const float3 & hitDir,int weaponDefId,float & inout_damage)255 void CCobInstance::HitByWeapon(const float3& hitDir, int weaponDefId, float& inout_damage)
256 {
257 vector<int> args;
258 args.reserve(4);
259
260 args.push_back((int)(500 * hitDir.z));
261 args.push_back((int)(500 * hitDir.x));
262
263 if (HasFunction(COBFN_HitByWeaponId)) {
264 const WeaponDef* wd = weaponDefHandler->GetWeaponDefByID(weaponDefId);
265 args.push_back(wd ? wd->tdfId : -1);
266 args.push_back((int)(100 * inout_damage));
267
268 weaponHitMod = 1.0f;
269 Call(COBFN_HitByWeaponId, args, hitByWeaponIdCallback, NULL, NULL);
270 inout_damage *= weaponHitMod; // weaponHitMod gets set in callback function
271 }
272 else {
273 Call(COBFN_HitByWeapon, args);
274 }
275 }
276
277
SetSFXOccupy(int curTerrainType)278 void CCobInstance::SetSFXOccupy(int curTerrainType)
279 {
280 Call(COBFN_SetSFXOccupy, curTerrainType);
281 }
282
283
QueryLandingPads(std::vector<int> & out_pieces)284 void CCobInstance::QueryLandingPads(std::vector<int>& out_pieces)
285 {
286 int maxPadCount = 16; // default max pad count
287
288 if (HasFunction(COBFN_QueryLandingPadCount)) {
289 vector<int> args;
290 args.push_back(maxPadCount);
291 Call(COBFN_QueryLandingPadCount, args);
292 maxPadCount = args[0];
293 }
294
295 for (int i = 0; i < maxPadCount; i++) {
296 out_pieces.push_back(-1);
297 }
298
299 Call(COBFN_QueryLandingPad, out_pieces);
300 }
301
302
BeginTransport(const CUnit * unit)303 void CCobInstance::BeginTransport(const CUnit* unit)
304 {
305 // yes, COB is silly, while it handles integers fine it uses model height to identify units
306 Call(COBFN_BeginTransport, (int)(unit->model->height*65536));
307 }
308
309
QueryTransport(const CUnit * unit)310 int CCobInstance::QueryTransport(const CUnit* unit)
311 {
312 vector<int> args;
313 args.reserve(2);
314 args.push_back(0);
315 args.push_back((int)(unit->model->height*65536));
316 Call(COBFN_QueryTransport, args);
317 return args[0];
318 }
319
320
TransportPickup(const CUnit * unit)321 void CCobInstance::TransportPickup(const CUnit* unit)
322 {
323 // funny, now it uses unitIDs instead of model height
324 Call(COBFN_TransportPickup, unit->id);
325 }
326
327
TransportDrop(const CUnit * unit,const float3 & pos)328 void CCobInstance::TransportDrop(const CUnit* unit, const float3& pos)
329 {
330 vector<int> args;
331 args.reserve(2);
332 args.push_back(unit->id);
333 args.push_back(PACKXZ(pos.x, pos.z));
334 Call(COBFN_TransportDrop, args);
335 }
336
337
StartBuilding(float heading,float pitch)338 void CCobInstance::StartBuilding(float heading, float pitch)
339 {
340 vector<int> args;
341 args.reserve(2);
342 args.push_back(short(heading * RAD2TAANG));
343 args.push_back(short(pitch * RAD2TAANG));
344 Call(COBFN_StartBuilding, args);
345 }
346
347
QueryNanoPiece()348 int CCobInstance::QueryNanoPiece()
349 {
350 vector<int> args(1, 0);
351 Call(COBFN_QueryNanoPiece, args);
352 return args[0];
353 }
354
355
QueryBuildInfo()356 int CCobInstance::QueryBuildInfo()
357 {
358 vector<int> args(1, 0);
359 Call(COBFN_QueryBuildInfo, args);
360 return args[0];
361 }
362
363
QueryWeapon(int weaponNum)364 int CCobInstance::QueryWeapon(int weaponNum)
365 {
366 vector<int> args(1, 0);
367 Call(COBFN_QueryPrimary + COBFN_Weapon_Funcs * weaponNum, args);
368 return args[0];
369 }
370
371
372 // Called when unit's AimWeapon script finished executing
ScriptCallback(int retCode,void * p1,void * p2)373 static void ScriptCallback(int retCode, void* p1, void* p2)
374 {
375 static_cast<CWeapon*>(p1)->angleGood = (retCode == 1);
376 }
377
AimWeapon(int weaponNum,float heading,float pitch)378 void CCobInstance::AimWeapon(int weaponNum, float heading, float pitch)
379 {
380 vector<int> args;
381 args.reserve(2);
382 args.push_back(short(heading * RAD2TAANG));
383 args.push_back(short(pitch * RAD2TAANG));
384 Call(COBFN_AimPrimary + COBFN_Weapon_Funcs * weaponNum, args, ScriptCallback, unit->weapons[weaponNum], NULL);
385 }
386
387
388 // Called when unit's AimWeapon script finished executing (for shield weapon)
ShieldScriptCallback(int retCode,void * p1,void * p2)389 static void ShieldScriptCallback(int retCode, void* p1, void* p2)
390 {
391 static_cast<CPlasmaRepulser*>(p1)->SetEnabled(!!retCode);
392 }
393
AimShieldWeapon(CPlasmaRepulser * weapon)394 void CCobInstance::AimShieldWeapon(CPlasmaRepulser* weapon)
395 {
396 vector<int> args;
397 args.reserve(2);
398 args.push_back(0); // compat with AimWeapon (same script is called)
399 args.push_back(0);
400 Call(COBFN_AimPrimary + COBFN_Weapon_Funcs * weapon->weaponNum, args, ShieldScriptCallback, weapon, 0);
401 }
402
403
AimFromWeapon(int weaponNum)404 int CCobInstance::AimFromWeapon(int weaponNum)
405 {
406 vector<int> args(1, 0);
407 Call(COBFN_AimFromPrimary + COBFN_Weapon_Funcs * weaponNum, args);
408 return args[0];
409 }
410
411
Shot(int weaponNum)412 void CCobInstance::Shot(int weaponNum)
413 {
414 Call(COBFN_Shot + COBFN_Weapon_Funcs * weaponNum, 0); // why the 0 argument?
415 }
416
417
BlockShot(int weaponNum,const CUnit * targetUnit,bool userTarget)418 bool CCobInstance::BlockShot(int weaponNum, const CUnit* targetUnit, bool userTarget)
419 {
420 const int unitID = targetUnit ? targetUnit->id : 0;
421
422 vector<int> args;
423 args.reserve(3);
424
425 args.push_back(unitID);
426 args.push_back(0); // arg[1], for the return value
427 // the default is to not block the shot
428 args.push_back(userTarget);
429
430 Call(COBFN_BlockShot + COBFN_Weapon_Funcs * weaponNum, args);
431
432 return !!args[1];
433 }
434
435
TargetWeight(int weaponNum,const CUnit * targetUnit)436 float CCobInstance::TargetWeight(int weaponNum, const CUnit* targetUnit)
437 {
438 const int unitID = targetUnit ? targetUnit->id : 0;
439
440 vector<int> args;
441 args.reserve(2);
442
443 args.push_back(unitID);
444 args.push_back(COBSCALE); // arg[1], for the return value
445 // the default is 1.0
446
447 Call(COBFN_TargetWeight + COBFN_Weapon_Funcs * weaponNum, args);
448
449 return (float)args[1] / (float)COBSCALE;
450 }
451
452
Destroy()453 void CCobInstance::Destroy() { Call(COBFN_Destroy); }
StartMoving(bool reversing)454 void CCobInstance::StartMoving(bool reversing) { Call(COBFN_StartMoving, reversing); }
StopMoving()455 void CCobInstance::StopMoving() { Call(COBFN_StopMoving); }
StartUnload()456 void CCobInstance::StartUnload() { Call(COBFN_StartUnload); }
EndTransport()457 void CCobInstance::EndTransport() { Call(COBFN_EndTransport); }
StartBuilding()458 void CCobInstance::StartBuilding() { Call(COBFN_StartBuilding); }
StopBuilding()459 void CCobInstance::StopBuilding() { Call(COBFN_StopBuilding); }
Falling()460 void CCobInstance::Falling() { Call(COBFN_Falling); }
Landed()461 void CCobInstance::Landed() { Call(COBFN_Landed); }
Activate()462 void CCobInstance::Activate() { Call(COBFN_Activate); }
Deactivate()463 void CCobInstance::Deactivate() { Call(COBFN_Deactivate); }
MoveRate(int curRate)464 void CCobInstance::MoveRate(int curRate) { Call(COBFN_MoveRate0 + curRate); }
FireWeapon(int weaponNum)465 void CCobInstance::FireWeapon(int weaponNum) { Call(COBFN_FirePrimary + COBFN_Weapon_Funcs * weaponNum); }
EndBurst(int weaponNum)466 void CCobInstance::EndBurst(int weaponNum) { Call(COBFN_EndBurst + COBFN_Weapon_Funcs * weaponNum); }
467
468
469 /******************************************************************************/
470
471
472 /**
473 * @brief Calls a cob script function
474 * @param functionId int cob script function id
475 * @param args vector<int> function arguments
476 * @param cb CBCobThreadFinish Callback function
477 * @param p1 void* callback argument #1
478 * @param p2 void* callback argument #2
479 * @return 0 if the call terminated. If the caller provides a callback and the thread does not terminate,
480 * it will continue to run. Otherwise it will be killed. Returns 1 in this case.
481 */
RealCall(int functionId,vector<int> & args,CBCobThreadFinish cb,void * p1,void * p2)482 int CCobInstance::RealCall(int functionId, vector<int>& args, CBCobThreadFinish cb, void* p1, void* p2)
483 {
484 if (functionId < 0 || size_t(functionId) >= script.scriptNames.size()) {
485 if (cb) {
486 // in case the function doesnt exist the callback should still be called
487 (*cb)(0, p1, p2);
488 }
489 return -1;
490 }
491
492 CCobThread* thread = new CCobThread(script, this);
493 thread->Start(functionId, args, false);
494
495 LOG_L(L_DEBUG, "Calling %s:%s", script.name.c_str(), script.scriptNames[functionId].c_str());
496
497 const bool res = thread->Tick();
498
499 // Make sure this is run even if the call terminates instantly
500 if (cb)
501 thread->SetCallback(cb, p1, p2);
502
503 if (!res) {
504 // thread died already after one tick
505 // NOTE:
506 // the StartMoving callin now takes an argument which means
507 // there will be a mismatch between the number of arguments
508 // passed in (1) and the number returned (0) as of 95.0 -->
509 // prevent error-spam
510 unsigned int i = 0, argc = thread->CheckStack(args.size(), functionId != script.scriptIndex[COBFN_StartMoving]);
511
512 // Retrieve parameter values from stack
513 for (; i < argc; ++i)
514 args[i] = thread->GetStackVal(i);
515
516 // Set erroneous parameters to 0
517 for (; i < args.size(); ++i)
518 args[i] = 0;
519
520 delete thread;
521 return 0;
522 }
523
524 // thread has already added itself to the correct
525 // scheduler (global for sleep, or local for anim)
526 return 1;
527 }
528
529
530 /******************************************************************************/
531
532
Call(const std::string & fname)533 int CCobInstance::Call(const std::string& fname)
534 {
535 vector<int> x;
536 return Call(fname, x, NULL, NULL, NULL);
537 }
538
Call(const std::string & fname,std::vector<int> & args)539 int CCobInstance::Call(const std::string& fname, std::vector<int>& args)
540 {
541 return Call(fname, args, NULL, NULL, NULL);
542 }
543
Call(const std::string & fname,int p1)544 int CCobInstance::Call(const std::string& fname, int p1)
545 {
546 vector<int> x;
547 x.reserve(1);
548 x.push_back(p1);
549 return Call(fname, x, NULL, NULL, NULL);
550 }
551
Call(const std::string & fname,std::vector<int> & args,CBCobThreadFinish cb,void * p1,void * p2)552 int CCobInstance::Call(const std::string& fname, std::vector<int>& args, CBCobThreadFinish cb, void* p1, void* p2)
553 {
554 //TODO: Check that new behaviour of actually calling cb when the function is not defined is right?
555 // (the callback has always been called [when the function is not defined]
556 // in the id-based Call()s, but never in the string based calls.)
557 return RealCall(GetFunctionId(fname), args, cb, p1, p2);
558 }
559
560
Call(int id)561 int CCobInstance::Call(int id)
562 {
563 vector<int> x;
564 return Call(id, x, NULL, NULL, NULL);
565 }
566
Call(int id,int p1)567 int CCobInstance::Call(int id, int p1)
568 {
569 vector<int> x;
570 x.reserve(1);
571 x.push_back(p1);
572 return Call(id, x, NULL, NULL, NULL);
573 }
574
Call(int id,std::vector<int> & args)575 int CCobInstance::Call(int id, std::vector<int>& args)
576 {
577 return Call(id, args, NULL, NULL, NULL);
578 }
579
Call(int id,std::vector<int> & args,CBCobThreadFinish cb,void * p1,void * p2)580 int CCobInstance::Call(int id, std::vector<int>& args, CBCobThreadFinish cb, void* p1, void* p2)
581 {
582 return RealCall(script.scriptIndex[id], args, cb, p1, p2);
583 }
584
585
RawCall(int fn)586 void CCobInstance::RawCall(int fn)
587 {
588 vector<int> x;
589 RealCall(fn, x, NULL, NULL, NULL);
590 }
591
RawCall(int fn,std::vector<int> & args)592 int CCobInstance::RawCall(int fn, std::vector<int> &args)
593 {
594 return RealCall(fn, args, NULL, NULL, NULL);
595 }
596
597
598 /******************************************************************************/
599 /******************************************************************************/
600
601
Signal(int signal)602 void CCobInstance::Signal(int signal)
603 {
604 for (std::list<CCobThread *>::iterator i = threads.begin(); i != threads.end(); ++i) {
605 if ((signal & (*i)->signalMask) != 0) {
606 (*i)->state = CCobThread::Dead;
607 //LOG_L(L_DEBUG, "Killing a thread %d %d", signal, (*i)->signalMask);
608 }
609 }
610 }
611
612
PlayUnitSound(int snr,int attr)613 void CCobInstance::PlayUnitSound(int snr, int attr)
614 {
615 Channels::UnitReply->PlaySample(script.sounds[snr], unit->pos, unit->speed, attr);
616 }
617
618
ShowScriptError(const std::string & msg)619 void CCobInstance::ShowScriptError(const std::string& msg)
620 {
621 GCobEngine.ShowScriptError(msg);
622 }
623