1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 /* heavily based on CobInstance.cpp */
4 #include "UnitScript.h"
5
6 #include "CobDefines.h"
7 #include "CobFile.h"
8 #include "CobInstance.h"
9 #include "UnitScriptEngine.h"
10
11 #ifndef _CONSOLE
12
13 #include "Game/GameHelper.h"
14 #include "Game/GlobalUnsynced.h"
15 #include "Map/Ground.h"
16 #include "Sim/Misc/GroundBlockingObjectMap.h"
17 #include "Sim/Misc/LosHandler.h"
18 #include "Sim/Misc/TeamHandler.h"
19 #include "Sim/MoveTypes/AAirMoveType.h"
20 #include "Sim/MoveTypes/GroundMoveType.h"
21 #include "Sim/MoveTypes/MoveDefHandler.h"
22 #include "Sim/MoveTypes/MoveType.h"
23 #include "Sim/Projectiles/ExplosionGenerator.h"
24 #include "Sim/Projectiles/PieceProjectile.h"
25 #include "Sim/Projectiles/ProjectileHandler.h"
26 #include "Sim/Projectiles/Unsynced/BubbleProjectile.h"
27 #include "Sim/Projectiles/Unsynced/HeatCloudProjectile.h"
28 #include "Sim/Projectiles/Unsynced/MuzzleFlame.h"
29 #include "Sim/Projectiles/Unsynced/SmokeProjectile.h"
30 #include "Sim/Projectiles/Unsynced/WakeProjectile.h"
31 #include "Sim/Projectiles/Unsynced/WreckProjectile.h"
32 #include "Sim/Units/CommandAI/CommandAI.h"
33 #include "Sim/Units/CommandAI/Command.h"
34 #include "Sim/Units/UnitDef.h"
35 #include "Sim/Units/Unit.h"
36 #include "Sim/Units/UnitHandler.h"
37 #include "Sim/Units/UnitTypes/TransportUnit.h"
38 #include "Sim/Weapons/PlasmaRepulser.h"
39 #include "Sim/Weapons/Weapon.h"
40 #include "Sim/Weapons/WeaponDef.h"
41 #include "System/FastMath.h"
42 #include "System/myMath.h"
43 #include "System/Log/ILog.h"
44 #include "System/Util.h"
45 #include "System/Sound/ISoundChannels.h"
46 #include "System/Sync/SyncTracer.h"
47
48 #endif
49
50
51 std::vector< std::vector<int> > CUnitScript::teamVars;
52 std::vector< std::vector<int> > CUnitScript::allyVars;
53 int CUnitScript::globalVars[GLOBAL_VAR_COUNT] = { 0 };
54
55
InitVars(int numTeams,int numAllyTeams)56 void CUnitScript::InitVars(int numTeams, int numAllyTeams)
57 {
58 teamVars.resize(numTeams, std::vector<int>());
59 for (int t = 0; t < numTeams; t++) {
60 teamVars[t].resize(TEAM_VAR_COUNT, 0);
61 }
62
63 allyVars.resize(numAllyTeams, std::vector<int>());
64 for (int t = 0; t < numAllyTeams; t++) {
65 allyVars[t].resize(ALLY_VAR_COUNT, 0);
66 }
67 }
68
69
CUnitScript(CUnit * unit,const std::vector<LocalModelPiece * > & pieces)70 CUnitScript::CUnitScript(CUnit* unit, const std::vector<LocalModelPiece*>& pieces)
71 : unit(unit)
72 , yardOpen(false)
73 , busy(false)
74 , hasSetSFXOccupy(false)
75 , hasRockUnit(false)
76 , hasStartBuilding(false)
77 , pieces(pieces)
78 {
79 memset(unitVars, 0, sizeof(unitVars));
80 }
81
82
~CUnitScript()83 CUnitScript::~CUnitScript()
84 {
85 bool haveAnimations = false;
86
87 for (int animType = ATurn; animType <= AMove; animType++) {
88 for (std::list<AnimInfo*>::iterator i = anims[animType].begin(); i != anims[animType].end(); ++i) {
89 // anim listeners are not owned by the anim in general, so don't delete them here
90 delete *i;
91 }
92
93 haveAnimations = (haveAnimations || !anims[animType].empty());
94 }
95
96 // Remove us from possible animation ticking
97 if (haveAnimations)
98 GUnitScriptEngine.RemoveInstance(this);
99 }
100
101
102 /******************************************************************************/
103
104
105 /**
106 * @brief Unblocks all threads waiting on an animation
107 * @param anim AnimInfo the corresponding animation
108 */
UnblockAll(AnimInfo * anim)109 void CUnitScript::UnblockAll(AnimInfo* anim)
110 {
111 std::list<IAnimListener *>::iterator li;
112
113 for (li = anim->listeners.begin(); li != anim->listeners.end(); ++li) {
114 (*li)->AnimFinished(anim->type, anim->piece, anim->axis);
115 }
116 }
117
118
119 /**
120 * @brief Updates move animations
121 * @param cur float value to update
122 * @param dest float final value
123 * @param speed float max increment per tick
124 * @return returns true if destination was reached, false otherwise
125 */
MoveToward(float & cur,float dest,float speed)126 bool CUnitScript::MoveToward(float& cur, float dest, float speed)
127 {
128 const float delta = dest - cur;
129
130 if (math::fabsf(delta) <= speed) {
131 cur = dest;
132 return true;
133 }
134
135 if (delta > 0.0f) {
136 cur += speed;
137 } else {
138 cur -= speed;
139 }
140
141 return false;
142 }
143
144
145 /**
146 * @brief Updates turn animations
147 * @param cur float value to update
148 * @param dest float final value
149 * @param speed float max increment per tick
150 * @return returns true if destination was reached, false otherwise
151 */
TurnToward(float & cur,float dest,float speed)152 bool CUnitScript::TurnToward(float& cur, float dest, float speed)
153 {
154 float delta = dest - cur;
155
156 // clamp: -pi .. 0 .. +pi (remainder(x,TWOPI) would do the same but is slower due to streflop)
157 if (delta > PI) {
158 delta -= TWOPI;
159 } else if (delta<=-PI) {
160 delta += TWOPI;
161 }
162
163 if (math::fabsf(delta) <= speed) {
164 cur = dest;
165 return true;
166 }
167
168 if (delta > 0.0f) {
169 cur += speed;
170 } else {
171 cur -= speed;
172 }
173
174 ClampRad(&cur);
175
176 return false;
177 }
178
179
180 /**
181 * @brief Updates spin animations
182 * @param cur float value to update
183 * @param dest float the final desired speed (NOT the final angle!)
184 * @param speed float is updated if it is not equal to dest
185 * @param divisor int is the deltatime, it is not added before the call because speed may have to be updated
186 * @return true if the desired speed is 0 and it is reached, false otherwise
187 */
DoSpin(float & cur,float dest,float & speed,float accel,int divisor)188 bool CUnitScript::DoSpin(float& cur, float dest, float &speed, float accel, int divisor)
189 {
190 const float delta = dest - speed;
191
192 // Check if we are not at the final speed and
193 // make sure we dont go past desired speed
194 if (math::fabsf(delta) <= accel) {
195 speed = dest;
196 if (speed == 0.0f)
197 return true;
198 }
199 else {
200 if (delta > 0.0f) {
201 // accelerations are defined in speed/frame (at GAME_SPEED fps)
202 speed += accel * (float(GAME_SPEED) / divisor);
203 } else {
204 speed -= accel * (float(GAME_SPEED) / divisor);
205 }
206 }
207
208 cur += (speed / divisor);
209 ClampRad(&cur);
210
211 return false;
212 }
213
214
215
TickAnims(int deltaTime,AnimType type,std::list<std::list<AnimInfo * >::iterator> & doneAnims)216 void CUnitScript::TickAnims(int deltaTime, AnimType type, std::list< std::list<AnimInfo*>::iterator >& doneAnims) {
217 switch (type) {
218 case AMove: {
219 for (std::list<AnimInfo*>::iterator it = anims[type].begin(); it != anims[type].end(); ++it) {
220 AnimInfo* ai = *it;
221
222 // NOTE: we should not need to copy-and-set here, because
223 // MoveToward/TurnToward/DoSpin modify pos/rot by reference
224 float3 pos = pieces[ai->piece]->GetPosition();
225
226 if (MoveToward(pos[ai->axis], ai->dest, ai->speed / (1000 / deltaTime))) {
227 ai->done = true; doneAnims.push_back(it);
228 }
229
230 pieces[ai->piece]->SetPosition(pos);
231 unit->localModel->PieceUpdated(ai->piece);
232 }
233 } break;
234
235 case ATurn: {
236 for (std::list<AnimInfo*>::iterator it = anims[type].begin(); it != anims[type].end(); ++it) {
237 AnimInfo* ai = *it;
238 float3 rot = pieces[ai->piece]->GetRotation();
239
240 if (TurnToward(rot[ai->axis], ai->dest, ai->speed / (1000 / deltaTime))) {
241 ai->done = true; doneAnims.push_back(it);
242 }
243
244 pieces[ai->piece]->SetRotation(rot);
245 unit->localModel->PieceUpdated(ai->piece);
246 }
247 } break;
248
249 case ASpin: {
250 for (std::list<AnimInfo*>::iterator it = anims[type].begin(); it != anims[type].end(); ++it) {
251 AnimInfo* ai = *it;
252 float3 rot = pieces[ai->piece]->GetRotation();
253
254 if (DoSpin(rot[ai->axis], ai->dest, ai->speed, ai->accel, 1000 / deltaTime)) {
255 ai->done = true; doneAnims.push_back(it);
256 }
257
258 pieces[ai->piece]->SetRotation(rot);
259 unit->localModel->PieceUpdated(ai->piece);
260 }
261 } break;
262
263 default: {
264 } break;
265 }
266 }
267
268 /**
269 * @brief Called by the engine when we are registered as animating.
270 If we return false there are no active animations left.
271 * @param deltaTime int delta time to update
272 * @return true if there are still active animations
273 */
Tick(int deltaTime)274 bool CUnitScript::Tick(int deltaTime)
275 {
276 typedef std::list<AnimInfo*>::iterator AnimInfoIt;
277
278 // list of _iterators_ to finished animations,
279 // so we can get rid of them in constant time
280 std::list<AnimInfoIt> doneAnims;
281
282 for (int animType = ATurn; animType <= AMove; animType++) {
283 TickAnims(deltaTime, AnimType(animType), doneAnims);
284 }
285
286 //! Tell listeners to unblock, and remove finished animations from the unit/script.
287 //! NOTE:
288 //! removing a finished animation _must_ happen before notifying its listeners,
289 //! otherwise the callback function (AnimFinished()) can call AddAnimListener()
290 //! and append it to the listeners-list again (causing an endless loop)!
291 //! NOTE: UnblockAll might result in new anims being added
292 for (std::list<AnimInfoIt>::const_iterator it = doneAnims.begin(); it != doneAnims.end(); ++it) {
293 AnimInfoIt animInfoIt = *it;
294 AnimInfo* animInfo = *animInfoIt;
295
296 anims[animInfo->type].erase(animInfoIt);
297 UnblockAll(animInfo);
298 delete animInfo;
299 }
300
301 return (HaveAnimations());
302 }
303
304
305
FindAnim(AnimType type,int piece,int axis)306 std::list<CUnitScript::AnimInfo*>::iterator CUnitScript::FindAnim(AnimType type, int piece, int axis)
307 {
308 for (std::list<AnimInfo*>::iterator i = anims[type].begin(); i != anims[type].end(); ++i) {
309 if (((*i)->piece == piece) && ((*i)->axis == axis))
310 return i;
311 }
312
313 return anims[type].end();
314 }
315
RemoveAnim(AnimType type,const std::list<AnimInfo * >::iterator & animInfoIt)316 void CUnitScript::RemoveAnim(AnimType type, const std::list<AnimInfo*>::iterator& animInfoIt)
317 {
318 if (animInfoIt != anims[type].end()) {
319 AnimInfo* ai = *animInfoIt;
320 anims[type].erase(animInfoIt);
321
322 // If this was the last animation, remove from currently animating list
323 // FIXME: this could be done in a cleaner way
324 if (!HaveAnimations()) {
325 GUnitScriptEngine.RemoveInstance(this);
326 }
327
328 //! We need to unblock threads waiting on this animation, otherwise they will be lost in the void
329 //! NOTE: UnblockAll might result in new anims being added
330 UnblockAll(ai);
331
332 delete ai;
333 }
334 }
335
336
337 //Overwrites old information. This means that threads blocking on turn completion
338 //will now wait for this new turn instead. Not sure if this is the expected behaviour
339 //Other option would be to kill them. Or perhaps unblock them.
AddAnim(AnimType type,int piece,int axis,float speed,float dest,float accel)340 void CUnitScript::AddAnim(AnimType type, int piece, int axis, float speed, float dest, float accel)
341 {
342 if (!PieceExists(piece)) {
343 ShowScriptError("Invalid piecenumber");
344 return;
345 }
346
347 float destf = 0.0f;
348
349 if (type == AMove) {
350 destf = pieces[piece]->original->offset[axis] + dest;
351 } else {
352 destf = dest;
353 if (type == ATurn) {
354 ClampRad(&destf);
355 }
356 }
357
358 std::list<AnimInfo*>::iterator animInfoIt;
359 AnimInfo* ai = NULL;
360 AnimType overrideType = ANone;
361
362 // first find an animation of a type we override
363 // Turns override spins.. Not sure about the other way around? If so
364 // the system should probably be redesigned to only have two types of
365 // anims (turns and moves), with spin as a bool
366 switch (type) {
367 case ATurn: {
368 overrideType = ASpin;
369 animInfoIt = FindAnim(overrideType, piece, axis);
370 } break;
371 case ASpin: {
372 overrideType = ATurn;
373 animInfoIt = FindAnim(overrideType, piece, axis);
374 } break;
375 case AMove: {
376 // ensure we never remove an animation of this type
377 overrideType = AMove;
378 animInfoIt = anims[overrideType].end();
379 } break;
380 default: {
381 } break;
382 }
383
384 if (animInfoIt != anims[overrideType].end())
385 RemoveAnim(overrideType, animInfoIt);
386
387 // now find an animation of our own type
388 animInfoIt = FindAnim(type, piece, axis);
389
390 if (animInfoIt == anims[type].end()) {
391 // If we were not animating before, inform the engine of this so it can schedule us
392 // FIXME: this could be done in a cleaner way
393 if (!HaveAnimations()) {
394 GUnitScriptEngine.AddInstance(this);
395 }
396
397 ai = new AnimInfo();
398 ai->type = type;
399 ai->piece = piece;
400 ai->axis = axis;
401 anims[type].push_back(ai);
402 } else {
403 ai = *animInfoIt;
404 }
405
406 ai->dest = destf;
407 ai->speed = speed;
408 ai->accel = accel;
409 ai->done = false;
410 }
411
412
Spin(int piece,int axis,float speed,float accel)413 void CUnitScript::Spin(int piece, int axis, float speed, float accel)
414 {
415 std::list<AnimInfo*>::iterator animInfoIt = FindAnim(ASpin, piece, axis);
416
417 //If we are already spinning, we may have to decelerate to the new speed
418 if (animInfoIt != anims[ASpin].end()) {
419 AnimInfo* ai = *animInfoIt;
420 ai->dest = speed;
421
422 if (accel > 0) {
423 ai->accel = accel;
424 } else {
425 //Go there instantly. Or have a defaul accel?
426 ai->speed = speed;
427 ai->accel = 0;
428 }
429 } else {
430 //No accel means we start at desired speed instantly
431 if (accel <= 0)
432 AddAnim(ASpin, piece, axis, speed, speed, 0);
433 else
434 AddAnim(ASpin, piece, axis, 0, speed, accel);
435 }
436 }
437
438
StopSpin(int piece,int axis,float decel)439 void CUnitScript::StopSpin(int piece, int axis, float decel)
440 {
441 std::list<AnimInfo*>::iterator animInfoIt = FindAnim(ASpin, piece, axis);
442
443 if (decel <= 0) {
444 RemoveAnim(ASpin, animInfoIt);
445 } else {
446 if (animInfoIt == anims[ASpin].end())
447 return;
448
449 AnimInfo* ai = *animInfoIt;
450 ai->dest = 0;
451 ai->accel = decel;
452 }
453 }
454
455
Turn(int piece,int axis,float speed,float destination)456 void CUnitScript::Turn(int piece, int axis, float speed, float destination)
457 {
458 AddAnim(ATurn, piece, axis, std::max(speed, -speed), destination, 0);
459 }
460
461
Move(int piece,int axis,float speed,float destination)462 void CUnitScript::Move(int piece, int axis, float speed, float destination)
463 {
464 AddAnim(AMove, piece, axis, std::max(speed, -speed), destination, 0);
465 }
466
467
MoveNow(int piece,int axis,float destination)468 void CUnitScript::MoveNow(int piece, int axis, float destination)
469 {
470 if (!PieceExists(piece)) {
471 ShowScriptError("Invalid piecenumber");
472 return;
473 }
474
475 LocalModel* m = unit->localModel;
476 LocalModelPiece* p = pieces[piece];
477
478 float3 pos = p->GetPosition();
479 pos[axis] = pieces[piece]->original->offset[axis] + destination;
480
481 p->SetPosition(pos);
482 m->PieceUpdated(piece);
483 }
484
485
TurnNow(int piece,int axis,float destination)486 void CUnitScript::TurnNow(int piece, int axis, float destination)
487 {
488 if (!PieceExists(piece)) {
489 ShowScriptError("Invalid piecenumber");
490 return;
491 }
492
493 LocalModel* m = unit->localModel;
494 LocalModelPiece* p = pieces[piece];
495
496 float3 rot = p->GetRotation();
497 rot[axis] = destination;
498
499 p->SetRotation(rot);
500 m->PieceUpdated(piece);
501 }
502
503
SetVisibility(int piece,bool visible)504 void CUnitScript::SetVisibility(int piece, bool visible)
505 {
506 if (!PieceExists(piece)) {
507 ShowScriptError("Invalid piecenumber");
508 return;
509 }
510
511 pieces[piece]->scriptSetVisible = visible;
512 }
513
514
EmitSfx(int sfxType,int piece)515 void CUnitScript::EmitSfx(int sfxType, int piece)
516 {
517 #ifndef _CONSOLE
518 if (!PieceExists(piece)) {
519 ShowScriptError("Invalid piecenumber for emit-sfx");
520 return;
521 }
522
523 if (projectileHandler->particleSaturation > 1.0f && sfxType < SFX_CEG) {
524 // skip adding (unsynced!) particles when we have too many
525 return;
526 }
527
528 // Make sure wakes are only emitted on water
529 if ((sfxType >= SFX_WAKE) && (sfxType <= SFX_REVERSE_WAKE_2)) {
530 if (CGround::GetApproximateHeight(unit->pos.x, unit->pos.z) > 0.0f) {
531 return;
532 }
533 }
534
535 float3 relPos = ZeroVector;
536 float3 relDir = UpVector;
537
538 if (!GetEmitDirPos(piece, relPos, relDir)) {
539 ShowScriptError("emit-sfx: GetEmitDirPos failed");
540 return;
541 }
542
543 relDir.SafeNormalize();
544
545 const float3 pos = unit->GetObjectSpacePos(relPos);
546 const float3 dir = unit->GetObjectSpaceVec(relDir);
547
548 float alpha = 0.3f + gu->RandFloat() * 0.2f;
549 float alphaFalloff = 0.004f;
550 float fadeupTime = 4;
551
552 const UnitDef* ud = unit->unitDef;
553 const MoveDef* md = unit->moveDef;
554
555 // hovercraft need special care
556 if (md != NULL && md->speedModClass == MoveDef::Hover) {
557 fadeupTime = 8.0f;
558 alpha = 0.15f + gu->RandFloat() * 0.2f;
559 alphaFalloff = 0.008f;
560 }
561
562 switch (sfxType) {
563 case SFX_REVERSE_WAKE:
564 case SFX_REVERSE_WAKE_2: { //reverse wake
565 new CWakeProjectile(
566 unit,
567 pos + gu->RandVector() * 2.0f,
568 dir * 0.4f,
569 6.0f + gu->RandFloat() * 4.0f,
570 0.15f + gu->RandFloat() * 0.3f,
571 alpha, alphaFalloff, fadeupTime
572 );
573 break;
574 }
575
576 case SFX_WAKE_2: //wake 2, in TA it lives longer..
577 case SFX_WAKE: { //regular ship wake
578 new CWakeProjectile(
579 unit,
580 pos + gu->RandVector() * 2.0f,
581 dir * 0.4f,
582 6.0f + gu->RandFloat() * 4.0f,
583 0.15f + gu->RandFloat() * 0.3f,
584 alpha, alphaFalloff, fadeupTime
585 );
586 break;
587 }
588
589 case SFX_BUBBLE: { //submarine bubble. does not provide direction through piece vertices..
590 float3 pspeed = gu->RandVector() * 0.1f;
591 pspeed.y += 0.2f;
592
593 new CBubbleProjectile(
594 unit,
595 pos + gu->RandVector() * 2.0f,
596 pspeed,
597 40.0f + gu->RandFloat() * GAME_SPEED,
598 1.0f + gu->RandFloat() * 2.0f,
599 0.01f,
600 0.3f + gu->RandFloat() * 0.3f
601 );
602 } break;
603
604 case SFX_WHITE_SMOKE: //damaged unit smoke
605 new CSmokeProjectile(unit, pos, gu->RandVector() * 0.5f + UpVector * 1.1f, 60, 4, 0.5f, 0.5f);
606 break;
607 case SFX_BLACK_SMOKE: //damaged unit smoke
608 new CSmokeProjectile(unit, pos, gu->RandVector() * 0.5f + UpVector * 1.1f, 60, 4, 0.5f, 0.6f);
609 break;
610 case SFX_VTOL: {
611 const float3 speed =
612 unit->speed * 0.7f +
613 unit->frontdir * 0.5f * relDir.z +
614 unit->updir * 0.5f * -math::fabs(relDir.y) +
615 unit->rightdir * 0.5f * relDir.x;
616
617 CHeatCloudProjectile* hc = new CHeatCloudProjectile(
618 unit,
619 pos,
620 speed,
621 10 + gu->RandFloat() * 5,
622 3 + gu->RandFloat() * 2
623 );
624 hc->size = 3;
625 break;
626 }
627 default: {
628 if (sfxType & SFX_CEG) {
629 // emit defined explosion-generator (can only be custom, not standard)
630 // index is made valid by callee, an ID of -1 means CEG failed to load
631 explGenHandler->GenExplosion(ud->GetModelExplosionGeneratorID(sfxType - SFX_CEG), pos, dir, unit->cegDamage, 1.0f, 0.0f, unit, NULL);
632 }
633 else if (sfxType & SFX_FIRE_WEAPON) {
634 // make a weapon fire from the piece
635 const unsigned index = sfxType - SFX_FIRE_WEAPON;
636 if (index >= unit->weapons.size() || unit->weapons[index] == NULL) {
637 ShowScriptError("Invalid weapon index for emit-sfx");
638 break;
639 }
640
641 CWeapon* weapon = unit->weapons[index];
642
643 const float3 targetPos = weapon->targetPos;
644 const float3 weaponMuzzlePos = weapon->weaponMuzzlePos;
645
646 weapon->targetPos = pos + dir;
647 weapon->weaponMuzzlePos = pos;
648 weapon->Fire(true);
649 weapon->weaponMuzzlePos = weaponMuzzlePos;
650 weapon->targetPos = targetPos;
651 }
652 else if (sfxType & SFX_DETONATE_WEAPON) {
653 const unsigned index = sfxType - SFX_DETONATE_WEAPON;
654 if (index >= unit->weapons.size() || unit->weapons[index] == NULL) {
655 ShowScriptError("Invalid weapon index for emit-sfx");
656 break;
657 }
658
659 // detonate weapon from piece
660 const WeaponDef* weaponDef = unit->weapons[index]->weaponDef;
661
662 CGameHelper::ExplosionParams params = {
663 pos,
664 ZeroVector,
665 weaponDef->damages,
666 weaponDef,
667 unit, // owner
668 NULL, // hitUnit
669 NULL, // hitFeature
670 weaponDef->craterAreaOfEffect,
671 weaponDef->damageAreaOfEffect,
672 weaponDef->edgeEffectiveness,
673 weaponDef->explosionSpeed,
674 1.0f, // gfxMod
675 weaponDef->impactOnly,
676 weaponDef->noSelfDamage, // ignoreOwner
677 true, // damageGround
678 -1u // projectileID
679 };
680
681 helper->Explosion(params);
682 }
683 } break;
684 }
685
686
687 #endif
688 }
689
690
AttachUnit(int piece,int u)691 void CUnitScript::AttachUnit(int piece, int u)
692 {
693 // -1 is valid, indicates that the unit should be hidden
694 if ((piece >= 0) && (!PieceExists(piece))) {
695 ShowScriptError("Invalid piecenumber for attach");
696 return;
697 }
698
699 #ifndef _CONSOLE
700 CTransportUnit* tu = dynamic_cast<CTransportUnit*>(unit);
701
702 if (tu && unitHandler->units[u]) {
703 tu->AttachUnit(unitHandler->units[u], piece);
704 }
705 #endif
706 }
707
708
DropUnit(int u)709 void CUnitScript::DropUnit(int u)
710 {
711 #ifndef _CONSOLE
712 CTransportUnit* tu = dynamic_cast<CTransportUnit*>(unit);
713
714 if (tu && unitHandler->units[u]) {
715 tu->DetachUnit(unitHandler->units[u]);
716 }
717 #endif
718 }
719
720
721 //Returns true if there was an animation to listen to
AddAnimListener(AnimType type,int piece,int axis,IAnimListener * listener)722 bool CUnitScript::AddAnimListener(AnimType type, int piece, int axis, IAnimListener *listener)
723 {
724 std::list<AnimInfo*>::iterator animInfoIt = FindAnim(type, piece, axis);
725
726 if (animInfoIt != anims[type].end()) {
727 AnimInfo* ai = *animInfoIt;
728
729 if (!ai->done) {
730 ai->listeners.push_back(listener);
731 return true;
732 }
733
734 // if the animation is already finished, listening for
735 // it just adds some overhead since either the current
736 // or the next Tick will remove it and call UnblockAll
737 // (which calls AnimFinished for each listener)
738 //
739 // we could notify the listener here, but a cleaner way
740 // is to treat the animation as if it did not exist and
741 // simply disregard the WaitFor* (no side-effects)
742 //
743 // listener->AnimFinished(ai->type, ai->piece, ai->axis);
744 }
745
746 return false;
747 }
748
749
750 //Flags as defined by the cob standard
Explode(int piece,int flags)751 void CUnitScript::Explode(int piece, int flags)
752 {
753 if (!PieceExists(piece)) {
754 ShowScriptError("Invalid piecenumber for explode");
755 return;
756 }
757
758 #ifndef _CONSOLE
759 const float3 relPos = GetPiecePos(piece);
760 const float3 absPos = unit->GetObjectSpacePos(relPos);
761
762 #ifdef TRACE_SYNC
763 tracefile << "Cob explosion: ";
764 tracefile << absPos.x << " " << absPos.y << " " << absPos.z << " " << piece << " " << flags << "\n";
765 #endif
766
767 if (!(flags & PF_NoHeatCloud)) {
768 // Do an explosion at the location first
769 new CHeatCloudProjectile(NULL, absPos, ZeroVector, 30, 30);
770 }
771
772 // If this is true, no stuff should fly off
773 if (flags & PF_NONE)
774 return;
775
776 // This means that we are going to do a full fledged piece explosion!
777 float3 baseSpeed = unit->speed;
778 float3 explSpeed((0.5f - gs->randFloat()) * 6.0f, 1.2f + gs->randFloat() * 5.0f, (0.5f - gs->randFloat()) * 6.0f);
779
780 if (baseSpeed.SqLength() > 9) {
781 const float l = baseSpeed.Length();
782 const float l2 = 3 + math::sqrt(l - 3);
783 baseSpeed *= (l2 / l);
784 }
785 if (unit->pos.y - CGround::GetApproximateHeight(unit->pos.x, unit->pos.z) > 15) {
786 explSpeed.y = (0.5f - gs->randFloat()) * 6.0f;
787 }
788
789 explSpeed += baseSpeed;
790
791 // limit projectile speed to 12 elmos/frame (why?)
792 if (false && explSpeed.SqLength() > (12.0f*12.0f)) {
793 explSpeed = (explSpeed.Normalize() * 12.0f);
794 }
795
796 if (flags & PF_Shatter) {
797 Shatter(piece, absPos, explSpeed);
798 return;
799 }
800
801 if (pieces[piece]->original == NULL)
802 return;
803
804 // projectiles that don't fall could live forever
805 int newflags = PF_Fall;
806
807 if (flags & PF_Explode) { newflags |= PF_Explode; }
808 // if (flags & PF_Fall) { newflags |= PF_Fall; }
809 if ((flags & PF_Smoke) && projectileHandler->particleSaturation < 1.0f) { newflags |= PF_Smoke; }
810 if ((flags & PF_Fire) && projectileHandler->particleSaturation < 0.95f) { newflags |= PF_Fire; }
811 if (flags & PF_NoCEGTrail) { newflags |= PF_NoCEGTrail; }
812
813 new CPieceProjectile(unit, pieces[piece], absPos, explSpeed, newflags, 0.5f);
814 #endif
815 }
816
817
Shatter(int piece,const float3 & pos,const float3 & speed)818 void CUnitScript::Shatter(int piece, const float3& pos, const float3& speed)
819 {
820 const LocalModelPiece* lmp = pieces[piece];
821 const S3DModelPiece* omp = lmp->original;
822 const float pieceChance = 1.0f - (projectileHandler->currentParticles - (projectileHandler->maxParticles - 2000)) / 2000.0f;
823
824 if (pieceChance > 0.0f) {
825 omp->Shatter(pieceChance, unit->model->textureType, unit->team, pos, speed);
826 }
827 }
828
829
ShowFlare(int piece)830 void CUnitScript::ShowFlare(int piece)
831 {
832 if (!PieceExists(piece)) {
833 ShowScriptError("Invalid piecenumber for show(flare)");
834 return;
835 }
836 #ifndef _CONSOLE
837 const float3 relPos = GetPiecePos(piece);
838 const float3 absPos = unit->GetObjectSpacePos(relPos);
839 const float3 dir = unit->lastMuzzleFlameDir;
840 const float size = unit->lastMuzzleFlameSize;
841
842 new CMuzzleFlame(absPos, unit->speed, dir, size);
843 #endif
844 }
845
846
847 /******************************************************************************/
848
849
GetUnitVal(int val,int p1,int p2,int p3,int p4)850 int CUnitScript::GetUnitVal(int val, int p1, int p2, int p3, int p4)
851 {
852 // may happen in case one uses Spring.GetUnitCOBValue (Lua) on a unit with CNullUnitScript
853 if (!unit) {
854 ShowScriptError("Error: no unit (in GetUnitVal)");
855 return 0;
856 }
857
858 #ifndef _CONSOLE
859 switch (val)
860 {
861 case ACTIVATION:
862 if (unit->activated)
863 return 1;
864 else
865 return 0;
866 break;
867 case STANDINGMOVEORDERS:
868 return unit->moveState;
869 break;
870 case STANDINGFIREORDERS:
871 return unit->fireState;
872 break;
873 case HEALTH: {
874 if (p1 <= 0)
875 return int((unit->health / unit->maxHealth) * 100.0f);
876
877 const CUnit* u = unitHandler->GetUnit(p1);
878
879 if (u == NULL)
880 return 0;
881 else
882 return int((u->health / u->maxHealth) * 100.0f);
883 }
884 case INBUILDSTANCE:
885 if (unit->inBuildStance)
886 return 1;
887 else
888 return 0;
889 case BUSY:
890 if (busy)
891 return 1;
892 else
893 return 0;
894 break;
895 case PIECE_XZ: {
896 if (!PieceExists(p1)) {
897 ShowScriptError("Invalid piecenumber for get piece_xz");
898 break;
899 }
900 const float3 relPos = GetPiecePos(p1);
901 const float3 absPos = unit->GetObjectSpacePos(relPos);
902 return PACKXZ(absPos.x, absPos.z);
903 }
904 case PIECE_Y: {
905 if (!PieceExists(p1)) {
906 ShowScriptError("Invalid piecenumber for get piece_y");
907 break;
908 }
909 const float3 relPos = GetPiecePos(p1);
910 const float3 absPos = unit->GetObjectSpacePos(relPos);
911 return int(absPos.y * COBSCALE);
912 }
913 case UNIT_XZ: {
914 if (p1 <= 0)
915 return PACKXZ(unit->pos.x, unit->pos.z);
916
917 const CUnit* u = unitHandler->GetUnit(p1);
918
919 if (u == NULL)
920 return PACKXZ(0, 0);
921 else
922 return PACKXZ(u->pos.x, u->pos.z);
923 }
924 case UNIT_Y: {
925 if (p1 <= 0)
926 return int(unit->pos.y * COBSCALE);
927
928 const CUnit* u = unitHandler->GetUnit(p1);
929
930 if (u == NULL)
931 return 0;
932 else
933 return int(u->pos.y * COBSCALE);
934 }
935 case UNIT_HEIGHT: {
936 if (p1 <= 0)
937 return int(unit->radius * COBSCALE);
938
939 const CUnit* u = unitHandler->GetUnit(p1);
940
941 if (u == NULL)
942 return 0;
943 else
944 return int(u->radius * COBSCALE);
945 }
946 case XZ_ATAN:
947 return int(RAD2TAANG*math::atan2((float)UNPACKX(p1), (float)UNPACKZ(p1)) + 32768 - unit->heading);
948 case XZ_HYPOT:
949 return int(math::hypot((float)UNPACKX(p1), (float)UNPACKZ(p1)) * COBSCALE);
950 case ATAN:
951 return int(RAD2TAANG*math::atan2((float)p1, (float)p2));
952 case HYPOT:
953 return int(math::hypot((float)p1, (float)p2));
954 case GROUND_HEIGHT:
955 return int(CGround::GetHeightAboveWater(UNPACKX(p1), UNPACKZ(p1)) * COBSCALE);
956 case GROUND_WATER_HEIGHT:
957 return int(CGround::GetHeightReal(UNPACKX(p1), UNPACKZ(p1)) * COBSCALE);
958 case BUILD_PERCENT_LEFT:
959 return int((1.0f - unit->buildProgress) * 100);
960
961 case YARD_OPEN:
962 if (yardOpen)
963 return 1;
964 else
965 return 0;
966 case BUGGER_OFF:
967 break;
968 case ARMORED:
969 if (unit->armoredState)
970 return 1;
971 else
972 return 0;
973 case VETERAN_LEVEL:
974 return int(100 * unit->experience);
975 case CURRENT_SPEED:
976 return int(unit->speed.w * COBSCALE);
977 case ON_ROAD:
978 return 0;
979 case IN_WATER:
980 return (unit->IsInWater());
981 case MAX_ID:
982 return unitHandler->MaxUnits()-1;
983 case MY_ID:
984 return unit->id;
985
986 case UNIT_TEAM: {
987 const CUnit* u = unitHandler->GetUnit(p1);
988 return (u != NULL)? unit->team : 0;
989 }
990 case UNIT_ALLIED: {
991 const CUnit* u = unitHandler->GetUnit(p1);
992
993 if (u != NULL) {
994 return teamHandler->Ally(unit->allyteam, u->allyteam) ? 1 : 0;
995 }
996
997 return 0;
998 }
999 case UNIT_BUILD_PERCENT_LEFT: {
1000 const CUnit* u = unitHandler->GetUnit(p1);
1001
1002 if (u != NULL) {
1003 return int((1.0f - u->buildProgress) * 100);
1004 }
1005
1006 return 0;
1007 }
1008 case MAX_SPEED: {
1009 return int(unit->moveType->GetMaxSpeed() * COBSCALE);
1010 } break;
1011 case REVERSING: {
1012 CGroundMoveType* gmt = dynamic_cast<CGroundMoveType*>(unit->moveType);
1013 return ((gmt != NULL)? int(gmt->IsReversing()): 0);
1014 } break;
1015 case CLOAKED:
1016 return !!unit->isCloaked;
1017 case WANT_CLOAK:
1018 return !!unit->wantCloak;
1019 case UPRIGHT:
1020 return !!unit->upright;
1021 case POW:
1022 return int(math::pow(((float)p1)/COBSCALE,((float)p2)/COBSCALE)*COBSCALE);
1023 case PRINT:
1024 LOG("Value 1: %d, 2: %d, 3: %d, 4: %d", p1, p2, p3, p4);
1025 break;
1026 case HEADING: {
1027 if (p1 <= 0) {
1028 return unit->heading;
1029 }
1030
1031 const CUnit* u = unitHandler->GetUnit(p1);
1032
1033 if (u != NULL) {
1034 return u->heading;
1035 }
1036
1037 return -1;
1038 }
1039 case TARGET_ID: {
1040 if (unit->weapons[p1 - 1]) {
1041 const CWeapon* weapon = unit->weapons[p1 - 1];
1042 const TargetType tType = weapon->targetType;
1043
1044 if (tType == Target_Unit)
1045 return unit->weapons[p1 - 1]->targetUnit->id;
1046 else if (tType == Target_None)
1047 return -1;
1048 else if (tType == Target_Pos)
1049 return -2;
1050 else // Target_Intercept
1051 return -3;
1052 }
1053 return -4; // weapon does not exist
1054 }
1055
1056 case LAST_ATTACKER_ID:
1057 return unit->lastAttacker? unit->lastAttacker->id: -1;
1058 case LOS_RADIUS:
1059 return unit->realLosRadius;
1060 case AIR_LOS_RADIUS:
1061 return unit->realAirLosRadius;
1062 case RADAR_RADIUS:
1063 return unit->radarRadius;
1064 case JAMMER_RADIUS:
1065 return unit->jammerRadius;
1066 case SONAR_RADIUS:
1067 return unit->sonarRadius;
1068 case SONAR_JAM_RADIUS:
1069 return unit->sonarJamRadius;
1070 case SEISMIC_RADIUS:
1071 return unit->seismicRadius;
1072
1073 case DO_SEISMIC_PING:
1074 float pingSize;
1075 if (p1 == 0) {
1076 pingSize = unit->seismicSignature;
1077 } else {
1078 pingSize = p1;
1079 }
1080 unit->DoSeismicPing(pingSize);
1081 break;
1082
1083 case CURRENT_FUEL:
1084 return int(unit->currentFuel * float(COBSCALE));
1085 case TRANSPORT_ID:
1086 return unit->transporter?unit->transporter->id:-1;
1087
1088 case SHIELD_POWER: {
1089 const CWeapon* shield = unit->shieldWeapon;
1090
1091 if (shield == NULL)
1092 return -1;
1093
1094 return int(static_cast<const CPlasmaRepulser*>(shield)->GetCurPower() * float(COBSCALE));
1095 }
1096
1097 case STEALTH: {
1098 return unit->stealth ? 1 : 0;
1099 }
1100 case SONAR_STEALTH: {
1101 return unit->sonarStealth ? 1 : 0;
1102 }
1103 case CRASHING:
1104 return !!unit->IsCrashing();
1105 case ALPHA_THRESHOLD: {
1106 return int(unit->alphaThreshold * 255);
1107 }
1108
1109 case COB_ID: {
1110 if (p1 <= 0) {
1111 return unit->unitDef->cobID;
1112 } else {
1113 const CUnit* u = unitHandler->GetUnit(p1);
1114 return ((u == NULL)? -1 : u->unitDef->cobID);
1115 }
1116 }
1117
1118 case PLAY_SOUND: {
1119 // FIXME: this can currently only work for CCobInstance, because Lua can not get sound IDs
1120 // (however, for Lua scripts there is already LuaUnsyncedCtrl::PlaySoundFile)
1121 CCobInstance* cob = dynamic_cast<CCobInstance*>(this);
1122 if (cob == NULL) {
1123 return 1;
1124 }
1125 const CCobFile* script = cob->GetScriptAddr();
1126 if (script == NULL) {
1127 return 1;
1128 }
1129 if ((p1 < 0) || (static_cast<size_t>(p1) >= script->sounds.size())) {
1130 return 1;
1131 }
1132 switch (p3) { //who hears the sound
1133 case 0: //ALOS
1134 if (!losHandler->InAirLos(unit->pos,gu->myAllyTeam)) { return 0; }
1135 break;
1136 case 1: //LOS
1137 if (!(unit->losStatus[gu->myAllyTeam] & LOS_INLOS)) { return 0; }
1138 break;
1139 case 2: //ALOS or radar
1140 if (!(losHandler->InAirLos(unit->pos,gu->myAllyTeam) || unit->losStatus[gu->myAllyTeam] & (LOS_INRADAR))) { return 0; }
1141 break;
1142 case 3: //LOS or radar
1143 if (!(unit->losStatus[gu->myAllyTeam] & (LOS_INLOS | LOS_INRADAR))) { return 0; }
1144 break;
1145 case 4: //everyone
1146 break;
1147 case 5: //allies
1148 if (unit->allyteam != gu->myAllyTeam) { return 0; }
1149 break;
1150 case 6: //team
1151 if (unit->team != gu->myTeam) { return 0; }
1152 break;
1153 case 7: //enemies
1154 if (unit->allyteam == gu->myAllyTeam) { return 0; }
1155 break;
1156 }
1157 if (p4 == 0) {
1158 Channels::General->PlaySample(script->sounds[p1], unit->pos, unit->speed, float(p2) / COBSCALE);
1159 } else {
1160 Channels::General->PlaySample(script->sounds[p1], float(p2) / COBSCALE);
1161 }
1162 return 0;
1163 }
1164 case SET_WEAPON_UNIT_TARGET: {
1165 const unsigned int weaponID = p1 - 1;
1166 const unsigned int targetID = p2;
1167 const bool userTarget = !!p3;
1168
1169 if (weaponID >= unit->weapons.size()) {
1170 return 0;
1171 }
1172
1173 CWeapon* weapon = unit->weapons[weaponID];
1174
1175 if (weapon == NULL) {
1176 return 0;
1177 }
1178
1179 //! if targetID is 0, just sets weapon->haveUserTarget
1180 //! to false (and targetType to None) without attacking
1181 CUnit* target = (targetID > 0)? unitHandler->GetUnit(targetID): NULL;
1182 return (weapon->AttackUnit(target, userTarget) ? 1 : 0);
1183 }
1184 case SET_WEAPON_GROUND_TARGET: {
1185 const int weaponID = p1 - 1;
1186 const float3 pos = float3(float(UNPACKX(p2)),
1187 float(p3) / float(COBSCALE),
1188 float(UNPACKZ(p2)));
1189 const bool userTarget = !!p4;
1190 if ((weaponID < 0) || (static_cast<size_t>(weaponID) >= unit->weapons.size())) {
1191 return 0;
1192 }
1193 CWeapon* weapon = unit->weapons[weaponID];
1194 if (weapon == NULL) { return 0; }
1195
1196 return weapon->AttackGround(pos, userTarget) ? 1 : 0;
1197 }
1198 case MIN:
1199 return std::min(p1, p2);
1200 case MAX:
1201 return std::max(p1, p2);
1202 case ABS:
1203 return abs(p1);
1204 case KSIN:
1205 return int(1024*math::sinf(TAANG2RAD*(float)p1));
1206 case KCOS:
1207 return int(1024*math::cosf(TAANG2RAD*(float)p1));
1208 case KTAN:
1209 return int(1024*math::tanf(TAANG2RAD*(float)p1));
1210 case SQRT:
1211 return int(math::sqrt((float)p1));
1212 case FLANK_B_MODE:
1213 return unit->flankingBonusMode;
1214 case FLANK_B_DIR:
1215 switch (p1) {
1216 case 1: return int(unit->flankingBonusDir.x * COBSCALE);
1217 case 2: return int(unit->flankingBonusDir.y * COBSCALE);
1218 case 3: return int(unit->flankingBonusDir.z * COBSCALE);
1219 case 4: unit->flankingBonusDir.x = (p2/(float)COBSCALE); return 0;
1220 case 5: unit->flankingBonusDir.y = (p2/(float)COBSCALE); return 0;
1221 case 6: unit->flankingBonusDir.z = (p2/(float)COBSCALE); return 0;
1222 case 7: unit->flankingBonusDir = float3(p2/(float)COBSCALE, p3/(float)COBSCALE, p4/(float)COBSCALE).Normalize(); return 0;
1223 default: return(-1);
1224 }
1225 case FLANK_B_MOBILITY_ADD:
1226 return int(unit->flankingBonusMobilityAdd * COBSCALE);
1227 case FLANK_B_MAX_DAMAGE:
1228 return int((unit->flankingBonusAvgDamage + unit->flankingBonusDifDamage) * COBSCALE);
1229 case FLANK_B_MIN_DAMAGE:
1230 return int((unit->flankingBonusAvgDamage - unit->flankingBonusDifDamage) * COBSCALE);
1231 case KILL_UNIT: {
1232 //! ID 0 is reserved for the script's owner
1233 CUnit* u = (p1 > 0)? unitHandler->GetUnit(p1): this->unit;
1234
1235 if (u == NULL) {
1236 return 0;
1237 }
1238
1239 if (u->beingBuilt) {
1240 // no explosions and no corpse for units under construction
1241 u->KillUnit(NULL, false, true);
1242 } else {
1243 u->KillUnit(NULL, p2 != 0, p3 != 0);
1244 }
1245
1246 return 1;
1247 }
1248
1249
1250 case WEAPON_RELOADSTATE: {
1251 const int np1 = -p1;
1252
1253 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1254 return unit->weapons[p1 - 1]->reloadStatus;
1255
1256 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1257 CWeapon* w = unit->weapons[np1 - 1];
1258 const int old = w->reloadStatus;
1259 w->reloadStatus = p2;
1260 return old;
1261 }
1262
1263 return -1;
1264 }
1265 case WEAPON_RELOADTIME: {
1266 const int np1 = -p1;
1267
1268 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1269 return unit->weapons[p1 - 1]->reloadTime;
1270
1271 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1272 CWeapon* w = unit->weapons[np1 - 1];
1273 const int old = w->reloadTime;
1274 w->reloadTime = p2;
1275 return old;
1276 }
1277
1278 return -1;
1279 }
1280 case WEAPON_ACCURACY: {
1281 const int np1 = -p1;
1282
1283 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1284 return int(unit->weapons[p1 - 1]->accuracyError * COBSCALE);
1285
1286 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1287 CWeapon* w = unit->weapons[np1 - 1];
1288 const int old = w->accuracyError * COBSCALE;
1289 w->accuracyError = float(p2) / COBSCALE;
1290 return old;
1291 }
1292
1293 return -1;
1294 }
1295 case WEAPON_SPRAY: {
1296 const int np1 = -p1;
1297
1298 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1299 return int(unit->weapons[p1 - 1]->sprayAngle * COBSCALE);
1300
1301 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1302 CWeapon* w = unit->weapons[np1 - 1];
1303 const int old = w->sprayAngle * COBSCALE;
1304 w->sprayAngle = float(p2) / COBSCALE;
1305 return old;
1306 }
1307
1308 return -1;
1309 }
1310 case WEAPON_RANGE: {
1311 const int np1 = -p1;
1312
1313 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1314 return int(unit->weapons[p1 - 1]->range * COBSCALE);
1315
1316 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1317 CWeapon* w = unit->weapons[np1 - 1];
1318 const int old = w->range * COBSCALE;
1319 w->range = float(p2) / COBSCALE;
1320 return old;
1321 }
1322
1323 return -1;
1324 }
1325 case WEAPON_PROJECTILE_SPEED: {
1326 const int np1 = -p1;
1327
1328 if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size())
1329 return int(unit->weapons[p1 - 1]->projectileSpeed * COBSCALE);
1330
1331 if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) {
1332 CWeapon* w = unit->weapons[np1 - 1];
1333 const int old = w->projectileSpeed * COBSCALE;
1334 w->projectileSpeed = float(p2) / COBSCALE;
1335 return old;
1336 }
1337
1338 return -1;
1339 }
1340
1341
1342 case GAME_FRAME: {
1343 return gs->frameNum;
1344 }
1345 default:
1346 if ((val >= GLOBAL_VAR_START) && (val <= GLOBAL_VAR_END)) {
1347 return globalVars[val - GLOBAL_VAR_START];
1348 }
1349 else if ((val >= TEAM_VAR_START) && (val <= TEAM_VAR_END)) {
1350 return teamVars[unit->team][val - TEAM_VAR_START];
1351 }
1352 else if ((val >= ALLY_VAR_START) && (val <= ALLY_VAR_END)) {
1353 return allyVars[unit->allyteam][val - ALLY_VAR_START];
1354 }
1355 else if ((val >= UNIT_VAR_START) && (val <= UNIT_VAR_END)) {
1356 const int varID = val - UNIT_VAR_START;
1357
1358 if (p1 == 0) {
1359 return unitVars[varID];
1360 }
1361 else if (p1 > 0) {
1362 // get the unit var for another unit
1363 const CUnit* u = unitHandler->GetUnit(p1);
1364
1365 if (u != NULL && u->script != NULL) {
1366 return u->script->unitVars[varID];
1367 }
1368 }
1369 else {
1370 // set the unit var for another unit
1371 p1 = -p1;
1372
1373 CUnit* u = unitHandler->GetUnit(p1);
1374
1375 if (u != NULL && u->script != NULL) {
1376 u->script->unitVars[varID] = p2;
1377 return 1;
1378 }
1379 }
1380 return 0;
1381 }
1382 else {
1383 LOG_L(L_ERROR,
1384 "CobError: Unknown get constant %d (params = %d %d %d %d)",
1385 val, p1, p2, p3, p4);
1386 }
1387 }
1388 #endif
1389
1390 return 0;
1391 }
1392
1393
SetUnitVal(int val,int param)1394 void CUnitScript::SetUnitVal(int val, int param)
1395 {
1396 // may happen in case one uses Spring.SetUnitCOBValue (Lua) on a unit with CNullUnitScript
1397 if (!unit) {
1398 ShowScriptError("Error: no unit (in SetUnitVal)");
1399 return;
1400 }
1401
1402 #ifndef _CONSOLE
1403 switch (val) {
1404 case ACTIVATION: {
1405 if(unit->unitDef->onoffable) {
1406 Command c(CMD_ONOFF, 0, (param == 0) ? 0 : 1);
1407 unit->commandAI->GiveCommand(c);
1408 }
1409 else {
1410 if(param == 0) {
1411 unit->Deactivate();
1412 }
1413 else {
1414 unit->Activate();
1415 }
1416 }
1417 break;
1418 }
1419 case STANDINGMOVEORDERS: {
1420 if (param >= 0 && param <= 2) {
1421 Command c(CMD_MOVE_STATE, 0, param);
1422 unit->commandAI->GiveCommand(c);
1423 }
1424 break;
1425 }
1426 case STANDINGFIREORDERS: {
1427 if (param >= 0 && param <= 2) {
1428 Command c(CMD_FIRE_STATE, 0, param);
1429 unit->commandAI->GiveCommand(c);
1430 }
1431 break;
1432 }
1433 case HEALTH: {
1434 break;
1435 }
1436 case INBUILDSTANCE: {
1437 unit->inBuildStance = (param != 0);
1438 break;
1439 }
1440 case BUSY: {
1441 busy = (param != 0);
1442 break;
1443 }
1444 case PIECE_XZ: {
1445 break;
1446 }
1447 case PIECE_Y: {
1448 break;
1449 }
1450 case UNIT_XZ: {
1451 break;
1452 }
1453 case UNIT_Y: {
1454 break;
1455 }
1456 case UNIT_HEIGHT: {
1457 break;
1458 }
1459 case XZ_ATAN: {
1460 break;
1461 }
1462 case XZ_HYPOT: {
1463 break;
1464 }
1465 case ATAN: {
1466 break;
1467 }
1468 case HYPOT: {
1469 break;
1470 }
1471 case GROUND_HEIGHT: {
1472 break;
1473 }
1474 case GROUND_WATER_HEIGHT: {
1475 break;
1476 }
1477 case BUILD_PERCENT_LEFT: {
1478 break;
1479 }
1480 case YARD_OPEN: {
1481 if (unit->blockMap != NULL) {
1482 // note: if this unit is a factory, engine-controlled
1483 // OpenYard() and CloseYard() calls can interfere with
1484 // the yardOpen state (they probably should be removed
1485 // at some point)
1486 if (param == 0) {
1487 if (groundBlockingObjectMap->CanCloseYard(unit)) {
1488 groundBlockingObjectMap->CloseBlockingYard(unit);
1489 yardOpen = false;
1490 }
1491 } else {
1492 if (groundBlockingObjectMap->CanOpenYard(unit)) {
1493 groundBlockingObjectMap->OpenBlockingYard(unit);
1494 yardOpen = true;
1495 }
1496 }
1497 }
1498 break;
1499 }
1500 case BUGGER_OFF: {
1501 if (param != 0) {
1502 CGameHelper::BuggerOff(unit->pos + unit->frontdir * unit->radius, unit->radius * 1.5f, true, false, unit->team, NULL);
1503 }
1504 break;
1505 }
1506 case ARMORED: {
1507 if (param) {
1508 unit->curArmorMultiple = unit->armoredMultiple;
1509 } else {
1510 unit->curArmorMultiple = 1;
1511 }
1512 unit->armoredState = (param != 0);
1513 break;
1514 }
1515 case VETERAN_LEVEL: {
1516 unit->experience = param * 0.01f;
1517 break;
1518 }
1519 case MAX_SPEED: {
1520 // interpret negative values as non-persistent changes
1521 unit->commandAI->SetScriptMaxSpeed(std::max(param, -param) / float(COBSCALE), (param >= 0));
1522 break;
1523 }
1524 case CLOAKED: {
1525 unit->wantCloak = !!param;
1526 break;
1527 }
1528 case WANT_CLOAK: {
1529 unit->wantCloak = !!param;
1530 break;
1531 }
1532 case UPRIGHT: {
1533 unit->upright = !!param;
1534 break;
1535 }
1536 case HEADING: {
1537 unit->heading = param % COBSCALE;
1538 unit->UpdateDirVectors(!unit->upright);
1539 unit->UpdateMidAndAimPos();
1540 } break;
1541 case LOS_RADIUS: {
1542 unit->ChangeLos(param, unit->realAirLosRadius);
1543 unit->realLosRadius = param;
1544 break;
1545 }
1546 case AIR_LOS_RADIUS: {
1547 unit->ChangeLos(unit->realLosRadius, param);
1548 unit->realAirLosRadius = param;
1549 break;
1550 }
1551 case RADAR_RADIUS: {
1552 unit->ChangeSensorRadius(&unit->radarRadius, param);
1553 break;
1554 }
1555 case JAMMER_RADIUS: {
1556 unit->ChangeSensorRadius(&unit->jammerRadius, param);
1557 break;
1558 }
1559 case SONAR_RADIUS: {
1560 unit->ChangeSensorRadius(&unit->sonarRadius, param);
1561 break;
1562 }
1563 case SONAR_JAM_RADIUS: {
1564 unit->ChangeSensorRadius(&unit->sonarJamRadius, param);
1565 break;
1566 }
1567 case SEISMIC_RADIUS: {
1568 unit->ChangeSensorRadius(&unit->seismicRadius, param);
1569 break;
1570 }
1571 case CURRENT_FUEL: {
1572 unit->currentFuel = param / (float) COBSCALE;
1573 break;
1574 }
1575 case SHIELD_POWER: {
1576 if (unit->shieldWeapon != NULL) {
1577 CPlasmaRepulser* shield = static_cast<CPlasmaRepulser*>(unit->shieldWeapon);
1578 shield->SetCurPower(std::max(0.0f, float(param) / float(COBSCALE)));
1579 }
1580 break;
1581 }
1582 case STEALTH: {
1583 unit->stealth = !!param;
1584 break;
1585 }
1586 case SONAR_STEALTH: {
1587 unit->sonarStealth = !!param;
1588 break;
1589 }
1590 case CRASHING: {
1591 AAirMoveType* amt = dynamic_cast<AAirMoveType*>(unit->moveType);
1592 if (amt != NULL) {
1593 if (!!param) {
1594 amt->SetState(AAirMoveType::AIRCRAFT_CRASHING);
1595 } else {
1596 amt->SetState(AAirMoveType::AIRCRAFT_FLYING);
1597 }
1598 }
1599 break;
1600 }
1601 case CHANGE_TARGET: {
1602 if (param < 0) { return; }
1603 if (param >= unit->weapons.size()) { return; }
1604
1605 unit->weapons[param]->avoidTarget = true;
1606 break;
1607 }
1608 case ALPHA_THRESHOLD: {
1609 unit->alphaThreshold = param / 255.0f;
1610 break;
1611 }
1612 case CEG_DAMAGE: {
1613 unit->cegDamage = param;
1614 break;
1615 }
1616 case FLANK_B_MODE:
1617 unit->flankingBonusMode = param;
1618 break;
1619 case FLANK_B_MOBILITY_ADD:
1620 unit->flankingBonusMobilityAdd = (param / (float)COBSCALE);
1621 break;
1622 case FLANK_B_MAX_DAMAGE: {
1623 float mindamage = unit->flankingBonusAvgDamage - unit->flankingBonusDifDamage;
1624 unit->flankingBonusAvgDamage = (param / (float)COBSCALE + mindamage)*0.5f;
1625 unit->flankingBonusDifDamage = (param / (float)COBSCALE - mindamage)*0.5f;
1626 break;
1627 }
1628 case FLANK_B_MIN_DAMAGE: {
1629 float maxdamage = unit->flankingBonusAvgDamage + unit->flankingBonusDifDamage;
1630 unit->flankingBonusAvgDamage = (maxdamage + param / (float)COBSCALE)*0.5f;
1631 unit->flankingBonusDifDamage = (maxdamage - param / (float)COBSCALE)*0.5f;
1632 break;
1633 }
1634 default: {
1635 if ((val >= GLOBAL_VAR_START) && (val <= GLOBAL_VAR_END)) {
1636 globalVars[val - GLOBAL_VAR_START] = param;
1637 }
1638 else if ((val >= TEAM_VAR_START) && (val <= TEAM_VAR_END)) {
1639 teamVars[unit->team][val - TEAM_VAR_START] = param;
1640 }
1641 else if ((val >= ALLY_VAR_START) && (val <= ALLY_VAR_END)) {
1642 allyVars[unit->allyteam][val - ALLY_VAR_START] = param;
1643 }
1644 else if ((val >= UNIT_VAR_START) && (val <= UNIT_VAR_END)) {
1645 unitVars[val - UNIT_VAR_START] = param;
1646 }
1647 else {
1648 LOG_L(L_ERROR, "CobError: Unknown set constant %d", val);
1649 }
1650 }
1651 }
1652 #endif
1653 }
1654
1655 /******************************************************************************/
1656 /******************************************************************************/
1657
ScriptToModel(int scriptPieceNum) const1658 int CUnitScript::ScriptToModel(int scriptPieceNum) const {
1659 if (!PieceExists(scriptPieceNum))
1660 return -1;
1661
1662 const LocalModelPiece* smp = GetScriptLocalModelPiece(scriptPieceNum);
1663
1664 return (smp->GetLModelPieceIndex());
1665 }
1666
ModelToScript(int lmodelPieceNum) const1667 int CUnitScript::ModelToScript(int lmodelPieceNum) const {
1668 const LocalModel* lm = unit->localModel;
1669
1670 if (!lm->HasPiece(lmodelPieceNum))
1671 return -1;
1672
1673 const LocalModelPiece* lmp = lm->GetPiece(lmodelPieceNum);
1674
1675 return (lmp->GetScriptPieceIndex());
1676 }
1677
1678