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