1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: monster_sentrybot.cpp
5 	Desc: implements all of the kobold monster's code
6 
7 	Copyright 2013-2019 (c) Turning Wheel LLC, all rights reserved.
8 	See LICENSE for details.
9 
10 -------------------------------------------------------------------------------*/
11 
12 #include "main.hpp"
13 #include "game.hpp"
14 #include "stat.hpp"
15 #include "entity.hpp"
16 #include "items.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "book.hpp"
20 #include "net.hpp"
21 #include "collision.hpp"
22 #include "player.hpp"
23 #include "magic/magic.hpp"
24 #include "interface/interface.hpp"
25 
26 std::unordered_map<Uint32, int> gyroBotDetectedUids;
27 
initSentryBot(Entity * my,Stat * myStats)28 void initSentryBot(Entity* my, Stat* myStats)
29 {
30 	node_t* node;
31 
32 	my->initMonster(my->sprite);
33 
34 	if ( multiplayer != CLIENT )
35 	{
36 		MONSTER_SPOTSND = 456;
37 		MONSTER_SPOTVAR = 3;
38 		MONSTER_IDLESND = -1;
39 		MONSTER_IDLEVAR = 1;
40 	}
41 	if ( multiplayer != CLIENT && !MONSTER_INIT )
42 	{
43 		if ( myStats != nullptr )
44 		{
45 			if ( !myStats->leader_uid )
46 			{
47 				myStats->leader_uid = 0;
48 			}
49 
50 			// apply random stat increases if set in stat_shared.cpp or editor
51 			setRandomMonsterStats(myStats);
52 
53 			// generate 6 items max, less if there are any forced items from boss variants
54 			int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
55 
56 			// generates equipment and weapons if available from editor
57 			createMonsterEquipment(myStats);
58 
59 			// create any custom inventory items from editor if available
60 			createCustomInventory(myStats, customItemsToGenerate);
61 
62 			// count if any custom inventory items from editor
63 			int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
64 
65 			// count any inventory items set to default in edtior
66 			int defaultItems = countDefaultItems(myStats);
67 
68 			//my->setHardcoreStats(*myStats);
69 
70 			if ( myStats->weapon == nullptr && my->sprite == 872/*&& myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1*/ )
71 			{
72 				myStats->weapon = newItem(CROSSBOW, EXCELLENT, 0, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr);
73 			}
74 			else if ( myStats->weapon == nullptr && my->sprite == 885/*&& myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1*/ )
75 			{
76 				if ( myStats->LVL >= 15 )
77 				{
78 					myStats->weapon = newItem(SPELLBOOK_MAGICMISSILE, EXCELLENT, 0, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr);
79 				}
80 				else
81 				{
82 					myStats->weapon = newItem(SPELLBOOK_FORCEBOLT, EXCELLENT, 0, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr);
83 				}
84 			}
85 		}
86 	}
87 
88 	int race = my->getMonsterTypeFromSprite();
89 
90 	// tripod
91 	Entity* entity = newEntity(873, 0, map.entities, nullptr); //Limb entity.
92 	entity->sizex = 4;
93 	entity->sizey = 4;
94 	entity->skill[2] = my->getUID();
95 	entity->flags[PASSABLE] = true;
96 	entity->flags[NOUPDATE] = true;
97 	entity->yaw = my->yaw;
98 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
99 	entity->focalx = limbs[race][1][0];
100 	entity->focaly = limbs[race][1][1];
101 	entity->focalz = limbs[race][1][2];
102 	entity->behavior = &actSentryBotLimb;
103 	entity->parent = my->getUID();
104 	node = list_AddNodeLast(&my->children);
105 	node->element = entity;
106 	node->deconstructor = &emptyDeconstructor;
107 	node->size = sizeof(Entity*);
108 	my->bodyparts.push_back(entity);
109 
110 	// gear 1 left head
111 	entity = newEntity(874, 0, map.entities, nullptr); //Limb entity.
112 	entity->sizex = 4;
113 	entity->sizey = 4;
114 	entity->skill[2] = my->getUID();
115 	entity->flags[PASSABLE] = true;
116 	entity->flags[NOUPDATE] = true;
117 	entity->flags[INVISIBLE] = my->flags[INVISIBLE];
118 	entity->yaw = my->yaw;
119 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
120 	entity->focalx = limbs[race][2][0];
121 	entity->focaly = limbs[race][2][1];
122 	entity->focalz = limbs[race][2][2];
123 	entity->behavior = &actSentryBotLimb;
124 	entity->parent = my->getUID();
125 	node = list_AddNodeLast(&my->children);
126 	node->element = entity;
127 	node->deconstructor = &emptyDeconstructor;
128 	node->size = sizeof(Entity*);
129 	my->bodyparts.push_back(entity);
130 
131 	// gear 1 right head
132 	entity = newEntity(874, 0, map.entities, nullptr); //Limb entity.
133 	entity->sizex = 4;
134 	entity->sizey = 4;
135 	entity->skill[2] = my->getUID();
136 	entity->flags[PASSABLE] = true;
137 	entity->flags[NOUPDATE] = true;
138 	entity->flags[INVISIBLE] = my->flags[INVISIBLE];
139 	entity->yaw = my->yaw;
140 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
141 	entity->focalx = limbs[race][2][0];
142 	entity->focaly = -limbs[race][2][1];
143 	entity->focalz = limbs[race][2][2];
144 	entity->behavior = &actSentryBotLimb;
145 	entity->parent = my->getUID();
146 	node = list_AddNodeLast(&my->children);
147 	node->element = entity;
148 	node->deconstructor = &emptyDeconstructor;
149 	node->size = sizeof(Entity*);
150 	my->bodyparts.push_back(entity);
151 
152 	// gear 1 left body
153 	entity = newEntity(874, 0, map.entities, nullptr); //Limb entity.
154 	entity->sizex = 4;
155 	entity->sizey = 4;
156 	entity->skill[2] = my->getUID();
157 	entity->flags[PASSABLE] = true;
158 	entity->flags[NOUPDATE] = true;
159 	entity->yaw = my->yaw;
160 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
161 	entity->focalx = limbs[race][3][0];
162 	entity->focaly = limbs[race][3][1];
163 	entity->focalz = limbs[race][3][2];
164 	entity->behavior = &actSentryBotLimb;
165 	entity->parent = my->getUID();
166 	node = list_AddNodeLast(&my->children);
167 	node->element = entity;
168 	node->deconstructor = &emptyDeconstructor;
169 	node->size = sizeof(Entity*);
170 	my->bodyparts.push_back(entity);
171 
172 	// gear 1 right body
173 	entity = newEntity(874, 0, map.entities, nullptr); //Limb entity.
174 	entity->sizex = 4;
175 	entity->sizey = 4;
176 	entity->skill[2] = my->getUID();
177 	entity->flags[PASSABLE] = true;
178 	entity->flags[NOUPDATE] = true;
179 	entity->yaw = my->yaw;
180 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
181 	entity->focalx = limbs[race][3][0];
182 	entity->focaly = -limbs[race][3][1];
183 	entity->focalz = limbs[race][3][2];
184 	entity->behavior = &actSentryBotLimb;
185 	entity->parent = my->getUID();
186 	node = list_AddNodeLast(&my->children);
187 	node->element = entity;
188 	node->deconstructor = &emptyDeconstructor;
189 	node->size = sizeof(Entity*);
190 	my->bodyparts.push_back(entity);
191 
192 	// gear 2 middle
193 	entity = newEntity(875, 0, map.entities, nullptr); //Limb entity.
194 	entity->sizex = 4;
195 	entity->sizey = 4;
196 	entity->skill[2] = my->getUID();
197 	entity->flags[PASSABLE] = true;
198 	entity->flags[NOUPDATE] = true;
199 	entity->yaw = my->yaw;
200 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
201 	entity->focalx = limbs[race][4][0];
202 	entity->focaly = limbs[race][4][1];
203 	entity->focalz = limbs[race][4][2];
204 	entity->scalex = 0.99;
205 	entity->scaley = 0.99;
206 	entity->scalez = 0.99;
207 	entity->behavior = &actSentryBotLimb;
208 	entity->parent = my->getUID();
209 	node = list_AddNodeLast(&my->children);
210 	node->element = entity;
211 	node->deconstructor = &emptyDeconstructor;
212 	node->size = sizeof(Entity*);
213 	my->bodyparts.push_back(entity);
214 
215 	// loader
216 	entity = newEntity(876, 0, map.entities, nullptr); //Limb entity.
217 	entity->sizex = 4;
218 	entity->sizey = 4;
219 	entity->skill[2] = my->getUID();
220 	entity->flags[PASSABLE] = true;
221 	entity->flags[NOUPDATE] = true;
222 	entity->flags[INVISIBLE] = my->flags[INVISIBLE];
223 	entity->yaw = my->yaw;
224 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
225 	entity->focalx = limbs[race][5][0];
226 	entity->focaly = limbs[race][5][1];
227 	entity->focalz = limbs[race][5][2];
228 	entity->behavior = &actSentryBotLimb;
229 	entity->parent = my->getUID();
230 	node = list_AddNodeLast(&my->children);
231 	node->element = entity;
232 	node->deconstructor = &emptyDeconstructor;
233 	node->size = sizeof(Entity*);
234 	my->bodyparts.push_back(entity);
235 
236 	// world weapon
237 	entity = newEntity(167, 0, map.entities, nullptr); //Limb entity.
238 	entity->sizex = 4;
239 	entity->sizey = 4;
240 	entity->skill[2] = my->getUID();
241 	entity->flags[PASSABLE] = true;
242 	entity->flags[NOUPDATE] = true;
243 	entity->flags[INVISIBLE] = true;
244 	entity->yaw = my->yaw;
245 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
246 	entity->focalx = limbs[race][6][0];
247 	entity->focaly = limbs[race][6][1];
248 	entity->focalz = limbs[race][6][2];
249 	entity->scalex = 0.99;
250 	entity->scaley = 0.99;
251 	entity->scalez = 0.99;
252 	entity->behavior = &actSentryBotLimb;
253 	entity->parent = my->getUID();
254 	node = list_AddNodeLast(&my->children);
255 	node->element = entity;
256 	node->deconstructor = &emptyDeconstructor;
257 	node->size = sizeof(Entity*);
258 	my->bodyparts.push_back(entity);
259 
260 	if ( multiplayer == CLIENT || MONSTER_INIT )
261 	{
262 		return;
263 	}
264 }
265 
initGyroBot(Entity * my,Stat * myStats)266 void initGyroBot(Entity* my, Stat* myStats)
267 {
268 	node_t* node;
269 	gyroBotDetectedUids.clear();
270 
271 	my->initMonster(886);
272 
273 	if ( multiplayer != CLIENT )
274 	{
275 		MONSTER_SPOTSND = -1;
276 		MONSTER_SPOTVAR = 1;
277 		MONSTER_IDLESND = -1;
278 		MONSTER_IDLEVAR = 1;
279 	}
280 	if ( multiplayer != CLIENT && !MONSTER_INIT )
281 	{
282 		if ( myStats != nullptr )
283 		{
284 			if ( !myStats->leader_uid )
285 			{
286 				myStats->leader_uid = 0;
287 			}
288 
289 			// apply random stat increases if set in stat_shared.cpp or editor
290 			setRandomMonsterStats(myStats);
291 
292 			myStats->EFFECTS[EFF_LEVITATING] = true;
293 			myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
294 
295 			// generate 6 items max, less if there are any forced items from boss variants
296 			int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
297 
298 			// generates equipment and weapons if available from editor
299 			createMonsterEquipment(myStats);
300 
301 			// create any custom inventory items from editor if available
302 			createCustomInventory(myStats, customItemsToGenerate);
303 
304 			// count if any custom inventory items from editor
305 			int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
306 
307 			// count any inventory items set to default in edtior
308 			int defaultItems = countDefaultItems(myStats);
309 
310 			//my->setHardcoreStats(*myStats);
311 		}
312 	}
313 
314 	// rotor large
315 	Entity* entity = newEntity(887, 0, map.entities, nullptr); //Limb entity.
316 	entity->sizex = 2;
317 	entity->sizey = 2;
318 	entity->skill[2] = my->getUID();
319 	entity->flags[PASSABLE] = true;
320 	entity->flags[NOUPDATE] = true;
321 	entity->yaw = my->yaw;
322 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
323 	entity->focalx = limbs[GYROBOT][1][0];
324 	entity->focaly = limbs[GYROBOT][1][1];
325 	entity->focalz = limbs[GYROBOT][1][2];
326 	entity->behavior = &actGyroBotLimb;
327 	entity->parent = my->getUID();
328 	node = list_AddNodeLast(&my->children);
329 	node->element = entity;
330 	node->deconstructor = &emptyDeconstructor;
331 	node->size = sizeof(Entity*);
332 	my->bodyparts.push_back(entity);
333 
334 	// rotor small
335 	entity = newEntity(888, 0, map.entities, nullptr); //Limb entity.
336 	entity->sizex = 2;
337 	entity->sizey = 2;
338 	entity->skill[2] = my->getUID();
339 	entity->flags[PASSABLE] = true;
340 	entity->flags[NOUPDATE] = true;
341 	entity->yaw = my->yaw;
342 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
343 	entity->focalx = limbs[GYROBOT][2][0];
344 	entity->focaly = limbs[GYROBOT][2][1];
345 	entity->focalz = limbs[GYROBOT][2][2];
346 	entity->behavior = &actGyroBotLimb;
347 	entity->parent = my->getUID();
348 	node = list_AddNodeLast(&my->children);
349 	node->element = entity;
350 	node->deconstructor = &emptyDeconstructor;
351 	node->size = sizeof(Entity*);
352 	my->bodyparts.push_back(entity);
353 
354 	// bomb
355 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
356 	entity->sizex = 2;
357 	entity->sizey = 2;
358 	entity->skill[2] = my->getUID();
359 	entity->flags[PASSABLE] = true;
360 	entity->flags[NOUPDATE] = true;
361 	entity->flags[INVISIBLE] = true;
362 	entity->yaw = my->yaw;
363 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
364 	entity->focalx = limbs[GYROBOT][6][0];
365 	entity->focaly = limbs[GYROBOT][6][1];
366 	entity->focalz = limbs[GYROBOT][6][2];
367 	entity->behavior = &actGyroBotLimb;
368 	entity->parent = my->getUID();
369 	node = list_AddNodeLast(&my->children);
370 	node->element = entity;
371 	node->deconstructor = &emptyDeconstructor;
372 	node->size = sizeof(Entity*);
373 	my->bodyparts.push_back(entity);
374 
375 	if ( multiplayer == CLIENT || MONSTER_INIT )
376 	{
377 		return;
378 	}
379 }
380 
actSentryBotLimb(Entity * my)381 void actSentryBotLimb(Entity* my)
382 {
383 	my->actMonsterLimb(false);
384 }
385 
sentryBotDie(Entity * my)386 void sentryBotDie(Entity* my)
387 {
388 	bool gibs = true;
389 	if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
390 	{
391 		// don't make noises etc.
392 		Stat* myStats = my->getStats();
393 		if ( myStats && !strncmp(myStats->obituary, language[3631], strlen(language[3631])) )
394 		{
395 			// returning to land, don't explode into gibs.
396 			gibs = false;
397 		}
398 	}
399 	else
400 	{
401 		ItemType type = TOOL_SENTRYBOT;
402 		Stat* myStats = my->getStats();
403 		if ( myStats && myStats->type == SPELLBOT )
404 		{
405 			type = TOOL_SPELLBOT;
406 		}
407 		bool dropBrokenShell = true;
408 		if ( myStats && myStats->monsterNoDropItems == 1 && !my->monsterAllyGetPlayerLeader() )
409 		{
410 			dropBrokenShell = false;
411 		}
412 		/*if ( myStats->monsterTinkeringStatus == EXCELLENT && rand() % 100 < 90 )
413 		{
414 			dropBrokenShell = true;
415 		}
416 		else if ( myStats->monsterTinkeringStatus == SERVICABLE && rand() % 100 < 80 )
417 		{
418 			dropBrokenShell = true;
419 		}
420 		else if ( myStats->monsterTinkeringStatus == WORN && rand() % 100 < 70 )
421 		{
422 			dropBrokenShell = true;
423 		}
424 		else if ( myStats->monsterTinkeringStatus == DECREPIT && rand() % 100 < 60 )
425 		{
426 			dropBrokenShell = true;
427 		}*/
428 
429 		if ( dropBrokenShell )
430 		{
431 			Item* item = newItem(type, BROKEN, 0, 1, 0, true, nullptr);
432 			Entity* entity = dropItemMonster(item, my, myStats);
433 			if ( entity )
434 			{
435 				entity->flags[USERFLAG1] = true;    // makes items passable, improves performance
436 			}
437 		}
438 		playSoundEntity(my, 451 + rand() % 2, 128);
439 	}
440 
441 	my->removeMonsterDeathNodes();
442 	if ( gibs )
443 	{
444 		int c;
445 		for ( c = 0; c < 6; c++ )
446 		{
447 			Entity* entity = spawnGib(my);
448 			if ( entity )
449 			{
450 				switch ( c )
451 				{
452 					case 0:
453 						entity->sprite = 873;
454 						break;
455 					case 1:
456 						entity->sprite = 874;
457 						break;
458 					case 2:
459 						entity->sprite = 874;
460 						break;
461 					case 3:
462 						entity->sprite = 874;
463 						break;
464 					case 4:
465 						entity->sprite = 874;
466 						break;
467 					case 5:
468 						entity->sprite = 875;
469 						break;
470 					default:
471 						break;
472 				}
473 				serverSpawnGibForClient(entity);
474 			}
475 		}
476 	}
477 
478 	// playSoundEntity(my, 298 + rand() % 4, 128);
479 	list_RemoveNode(my->mynode);
480 	return;
481 }
482 
483 #define SENTRYBOTWALKSPEED .13
484 #define BODY_TRIPOD 2
485 #define GEAR_HEAD_LEFT 3
486 #define GEAR_HEAD_RIGHT 4
487 #define GEAR_BODY_LEFT 5
488 #define GEAR_BODY_RIGHT 6
489 #define GEAR_MIDDLE 7
490 #define WEAPON_LOADER 8
491 #define WEAPON_LIMB 9
492 
sentryBotAnimate(Entity * my,Stat * myStats,double dist)493 void sentryBotAnimate(Entity* my, Stat* myStats, double dist)
494 {
495 	node_t* node;
496 	Entity* entity = nullptr, *entity2 = nullptr;
497 	Entity* rightbody = nullptr;
498 	Entity* weaponarm = nullptr;
499 	int bodypart;
500 
501 	if ( multiplayer != CLIENT )
502 	{
503 		if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
504 		{
505 			if ( limbAnimateToLimit(my, ANIMATE_PITCH, 0.01, PI / 8, false, 0.0) )
506 			{
507 				int appearance = monsterTinkeringConvertHPToAppearance(myStats);
508 				ItemType type = TOOL_SENTRYBOT;
509 				if ( myStats->type == SPELLBOT )
510 				{
511 					type = TOOL_SPELLBOT;
512 				}
513 				Item* item = newItem(type, static_cast<Status>(myStats->monsterTinkeringStatus), 0, 1, appearance, true, &myStats->inventory);
514 				myStats->HP = 0;
515 				my->setObituary(language[3631]);
516 				return;
517 			}
518 		}
519 		else
520 		{
521 			//my->z = 2.25;
522 			my->pitch = 0;
523 		}
524 	}
525 
526 	int race = my->getMonsterTypeFromSprite();
527 
528 	my->focalx = limbs[race][0][0];
529 	my->focaly = limbs[race][0][1];
530 	my->focalz = limbs[race][0][2];
531 	if ( multiplayer != CLIENT )
532 	{
533 		my->z = limbs[race][11][2];
534 	}
535 
536 	if ( ticks % (3 * TICKS_PER_SECOND) == 0 && rand() % 5 > 0 )
537 	{
538 		playSoundEntityLocal(my, 259, 8);
539 	}
540 
541 	Entity* tripod = nullptr;
542 	Entity* gearBodyLeft = nullptr;
543 	Entity* gearHeadLeft = nullptr;
544 	Entity* weaponLoader = nullptr;
545 	//Move bodyparts
546 	for (bodypart = 0, node = my->children.first; node != nullptr; node = node->next, ++bodypart)
547 	{
548 		if ( bodypart < BODY_TRIPOD )
549 		{
550 			continue;
551 		}
552 
553 		entity = (Entity*)node->element;
554 		entity->x = my->x;
555 		entity->y = my->y;
556 		entity->z = my->z;
557 
558 		if ( bodypart == WEAPON_LOADER || bodypart == GEAR_HEAD_LEFT
559 			|| bodypart == GEAR_HEAD_RIGHT || bodypart == WEAPON_LIMB )
560 		{
561 			entity->yaw = my->yaw; // face the monster's direction
562 		}
563 		if ( bodypart == WEAPON_LOADER || bodypart == WEAPON_LIMB )
564 		{
565 			if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
566 			{
567 				entity->pitch = my->pitch;
568 			}
569 		}
570 
571 		if ( bodypart == GEAR_MIDDLE && !my->flags[INVISIBLE] )
572 		{
573 			if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
574 			{
575 				entity->pitch += 0.02;
576 			}
577 			else
578 			{
579 				entity->pitch += 0.1;
580 			}
581 			if ( entity->pitch > 2 * PI )
582 			{
583 				entity->pitch -= 2 * PI;
584 			}
585 		}
586 		else if ( bodypart == GEAR_HEAD_LEFT )
587 		{
588 			gearHeadLeft = entity;
589 			if ( entity->pitch < 0 )
590 			{
591 				entity->pitch += 2 * PI;
592 			}
593 			else if ( entity->pitch > 2 * PI )
594 			{
595 				entity->pitch -= 2 * PI;
596 			}
597 
598 			if ( my->monsterAttack > 0 )
599 			{
600 				if ( my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1 )
601 				{
602 					if ( my->monsterAttackTime == 0 )
603 					{
604 						//createParticleDot(my);
605 						Entity* particle = createParticleAestheticOrbit(my, 173, 15, PARTICLE_EFFECT_SPELLBOT_ORBIT);
606 						if ( particle )
607 						{
608 							particle->actmagicOrbitDist = 1;
609 							particle->x = my->x + 2 * cos(my->yaw);
610 							particle->y = my->y + 2 * sin(my->yaw);
611 							particle->fskill[0] = particle->x;
612 							particle->fskill[1] = particle->y;
613 							particle->z = my->z - 1.5;
614 							particle->scalex = 0.5;
615 							particle->scaley = 0.5;
616 							particle->scalez = 0.5;
617 						}
618 						entity->fskill[0] = -0.2;
619 					}
620 
621 					entity->pitch += entity->fskill[0];
622 
623 					if ( my->monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
624 					{
625 						if ( multiplayer != CLIENT )
626 						{
627 							my->attack(MONSTER_POSE_RANGED_SHOOT1, 0, nullptr);
628 						}
629 					}
630 				}
631 				else if ( my->monsterAttack == MONSTER_POSE_RANGED_WINDUP1 )
632 				{
633 					if ( my->monsterAttackTime == 0 )
634 					{
635 						entity->fskill[0] = -0.2;
636 					}
637 
638 					entity->pitch += entity->fskill[0];
639 
640 					if ( my->monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
641 					{
642 						if ( multiplayer != CLIENT )
643 						{
644 							my->attack(MONSTER_POSE_MAGIC_CAST1, 0, nullptr);
645 						}
646 					}
647 				}
648 				else if ( my->monsterAttack == MONSTER_POSE_RANGED_SHOOT1 || my->monsterAttack == MONSTER_POSE_MAGIC_CAST1 )
649 				{
650 					if ( entity->fskill[0] < 0.01 )
651 					{
652 						entity->fskill[0] = 1;
653 					}
654 					else
655 					{
656 						entity->pitch += entity->fskill[0];
657 						entity->fskill[0] = std::max(entity->fskill[0] * 0.95, 0.01);
658 					}
659 
660 					if ( my->monsterAttackTime >= 20 )
661 					{
662 						my->monsterAttack = 0;
663 					}
664 				}
665 			}
666 			else
667 			{
668 				if ( abs(entity->fskill[0]) > 0.01 )
669 				{
670 					entity->skill[0] = 1;
671 					entity->pitch += entity->fskill[0];
672 					entity->fskill[0] = std::max(entity->fskill[0] * 0.95, 0.01);
673 				}
674 				else if ( entity->skill[0] == 1 )
675 				{
676 					// fall to rest on a 90 degree angle.
677 					if ( entity->pitch < PI / 2 )
678 					{
679 						if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.01, PI / 2, false, 0.f) )
680 						{
681 							entity->skill[0] = 0;
682 						}
683 					}
684 					else if ( entity->pitch < PI )
685 					{
686 						if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.01, PI, false, 0.f) )
687 						{
688 							entity->skill[0] = 0;
689 						}
690 					}
691 					else if ( entity->pitch < (3 * PI / 2) )
692 					{
693 						if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.01, 3 * PI / 2, false, 0.f) )
694 						{
695 							entity->skill[0] = 0;
696 						}
697 					}
698 					else
699 					{
700 						if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.01, 0.f, false, 0.f) )
701 						{
702 							entity->skill[0] = 0;
703 						}
704 					}
705 				}
706 			}
707 			//entity->pitch += 0.1;
708 			//if ( entity->pitch > 2 * PI )
709 			//{
710 			//	entity->pitch -= 2 * PI;
711 			//}
712 		}
713 		else if ( (bodypart == GEAR_BODY_LEFT || bodypart == GEAR_BODY_RIGHT)
714 			&& !my->flags[INVISIBLE] )
715 		{
716 			// normalize rotations
717 			if ( my->yaw > 2 * PI )
718 			{
719 				my->yaw -= 2 * PI;
720 			}
721 			else if ( my->yaw < 0 )
722 			{
723 				my->yaw += 2 * PI;
724 			}
725 			if ( entity->pitch > 4 * PI )
726 			{
727 				entity->pitch -= 4 * PI;
728 			}
729 			else if ( entity->pitch < 0 )
730 			{
731 				entity->pitch += 4 * PI;
732 			}
733 
734 			// spin the gear as the head turns.
735 			if ( bodypart == GEAR_BODY_LEFT && my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
736 			{
737 				entity->pitch -= 0.1;
738 			}
739 			else if ( bodypart == GEAR_BODY_RIGHT )
740 			{
741 				if ( gearBodyLeft )
742 				{
743 					entity->pitch = -gearBodyLeft->pitch;
744 				}
745 			}
746 			else if ( !limbAngleWithinRange(entity->pitch, entity->fskill[0], my->yaw * 2) && abs(entity->fskill[0]) < 0.01 )
747 			{
748 				if ( entity->pitch <= my->yaw * 2 )
749 				{
750 					if ( (my->yaw * 2 - entity->pitch) > 2 * PI ) // quicker to go the opposite way.
751 					{
752 						entity->fskill[0] = -0.4;
753 					}
754 					else
755 					{
756 						entity->fskill[0] = 0.4;
757 					}
758 				}
759 				else if ( entity->pitch > my->yaw * 2 )
760 				{
761 					if ( (entity->pitch - my->yaw * 2) > 2 * PI ) // quicker to go the opposite way.
762 					{
763 						entity->fskill[0] = 0.4;
764 					}
765 					else
766 					{
767 						entity->fskill[0] = -0.4;
768 					}
769 				}
770 			}
771 			else
772 			{
773 				if ( limbAngleWithinRange(entity->pitch, entity->fskill[0], my->yaw * 2) )
774 				{
775 					entity->pitch = my->yaw * 2;
776 				}
777 				else
778 				{
779 					entity->pitch += entity->fskill[0];
780 				}
781 				entity->fskill[0] *= 0.95;
782 			}
783 		}
784 
785 		switch ( bodypart )
786 		{
787 			case BODY_TRIPOD:
788 				tripod = entity;
789 				entity->focalx = limbs[race][1][0];
790 				entity->focaly = limbs[race][1][1];
791 				entity->focalz = limbs[race][1][2];
792 				entity->x += limbs[race][7][0];
793 				entity->y += limbs[race][7][1];
794 				entity->z += limbs[race][7][2];
795 				break;
796 			case GEAR_HEAD_LEFT:
797 				entity->flags[INVISIBLE] = my->flags[INVISIBLE];
798 				entity->focalx = limbs[race][2][0];
799 				entity->focaly = limbs[race][2][1];
800 				entity->focalz = limbs[race][2][2];
801 				if ( tripod )
802 				{
803 					entity->x += limbs[race][8][0] * cos(tripod->yaw + PI / 2) + limbs[race][8][1] * cos(tripod->yaw);
804 					entity->y += limbs[race][8][0] * sin(tripod->yaw + PI / 2) + limbs[race][8][1] * sin(tripod->yaw);
805 					entity->z += limbs[race][8][2];
806 				}
807 				break;
808 			case GEAR_HEAD_RIGHT:
809 				entity->flags[INVISIBLE] = my->flags[INVISIBLE];
810 				if ( gearHeadLeft )
811 				{
812 					entity->pitch = gearHeadLeft->pitch;
813 				}
814 				entity->focalx = limbs[race][2][0];
815 				entity->focaly = -limbs[race][2][1];
816 				entity->focalz = limbs[race][2][2];
817 				if ( tripod )
818 				{
819 					entity->x -= limbs[race][8][0] * cos(tripod->yaw + PI / 2) + limbs[race][8][1] * cos(tripod->yaw);
820 					entity->y -= limbs[race][8][0] * sin(tripod->yaw + PI / 2) + limbs[race][8][1] * sin(tripod->yaw);
821 					entity->z += limbs[race][8][2];
822 				}
823 				break;
824 			case GEAR_BODY_LEFT:
825 				gearBodyLeft = entity;
826 				entity->focalx = limbs[race][3][0];
827 				entity->focaly = limbs[race][3][1];
828 				entity->focalz = limbs[race][3][2];
829 				if ( tripod )
830 				{
831 					entity->x += limbs[race][12][0] * cos(tripod->yaw + PI / 2) + limbs[race][12][1] * cos(tripod->yaw);
832 					entity->y += limbs[race][12][0] * sin(tripod->yaw + PI / 2) + limbs[race][12][1] * sin(tripod->yaw);
833 					entity->z += limbs[race][12][2];
834 				}
835 				break;
836 			case GEAR_BODY_RIGHT:
837 				entity->focalx = limbs[race][3][0];
838 				entity->focaly = -limbs[race][3][1];
839 				entity->focalz = limbs[race][3][2];
840 				if ( tripod )
841 				{
842 					entity->x -= limbs[race][12][0] * cos(tripod->yaw + PI / 2) + limbs[race][12][1] * cos(tripod->yaw);
843 					entity->y -= limbs[race][12][0] * sin(tripod->yaw + PI / 2) + limbs[race][12][1] * sin(tripod->yaw);
844 					entity->z += limbs[race][12][2];
845 				}
846 				break;
847 			case GEAR_MIDDLE:
848 				entity->focalx = limbs[race][4][0];
849 				entity->focaly = limbs[race][4][1];
850 				entity->focalz = limbs[race][4][2];
851 				entity->yaw = tripod->yaw + PI / 2;
852 				if ( tripod )
853 				{
854 					entity->x += limbs[race][9][0] * cos(tripod->yaw + PI / 2) + limbs[race][9][1] * cos(tripod->yaw);
855 					entity->y += limbs[race][9][0] * sin(tripod->yaw + PI / 2) + limbs[race][9][1] * sin(tripod->yaw);
856 					entity->z += limbs[race][9][2];
857 				}
858 				//if ( multiplayer != CLIENT )
859 				//{
860 				//	entity->sprite = 875;
861 				//	if ( multiplayer == SERVER )
862 				//	{
863 				//		// update sprites for clients
864 				//		if ( entity->skill[10] != entity->sprite )
865 				//		{
866 				//			entity->skill[10] = entity->sprite;
867 				//			serverUpdateEntityBodypart(my, bodypart);
868 				//		}
869 				//		if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
870 				//		{
871 				//			serverUpdateEntityBodypart(my, bodypart);
872 				//		}
873 				//	}
874 				//}
875 				break;
876 			case WEAPON_LOADER:
877 				weaponLoader = entity;
878 				entity->focalx = limbs[race][5][0];
879 				entity->focaly = limbs[race][5][1];
880 				entity->focalz = limbs[race][5][2];
881 				entity->x += limbs[race][10][0];
882 				entity->y += limbs[race][10][1];
883 				entity->z += limbs[race][10][2];
884 				if ( my->monsterAttack == MONSTER_POSE_RANGED_SHOOT1 )
885 				{
886 					entity->fskill[0] = std::min(3.5, 2 + entity->fskill[0]);
887 					entity->focalx += entity->fskill[0];
888 				}
889 				else
890 				{
891 					entity->fskill[0] = std::max(0.0, entity->fskill[0] - 0.1);
892 					entity->focalx += entity->fskill[0];
893 				}
894 				if ( race == SPELLBOT )
895 				{
896 					entity->flags[INVISIBLE] = true;
897 				}
898 				else
899 				{
900 					entity->flags[INVISIBLE] = my->flags[INVISIBLE];
901 				}
902 				break;
903 			case WEAPON_LIMB:
904 				entity->focalx = limbs[race][6][0];
905 				entity->focaly = limbs[race][6][1];
906 				entity->focalz = limbs[race][6][2];
907 				if ( my->monsterAttack == MONSTER_POSE_RANGED_SHOOT1 )
908 				{
909 					entity->flags[INVISIBLE] = true;
910 				}
911 				else
912 				{
913 					if ( weaponLoader )
914 					{
915 						entity->fskill[0] = weaponLoader->fskill[0];
916 						entity->focalx += entity->fskill[0];
917 					}
918 					entity->flags[INVISIBLE] = false;
919 				}
920 				if ( race == SPELLBOT )
921 				{
922 					entity->flags[INVISIBLE] = true;
923 				}
924 				break;
925 			default:
926 				break;
927 		}
928 	}
929 
930 	if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
931 	{
932 		MONSTER_ATTACKTIME++;
933 	}
934 	else if ( MONSTER_ATTACK == 0 )
935 	{
936 		MONSTER_ATTACKTIME = 0;
937 	}
938 	else
939 	{
940 		// do nothing, don't reset attacktime or increment it.
941 	}
942 }
943 
944 #define GYRO_ROTOR_LARGE 2
945 #define GYRO_ROTOR_SMALL 3
946 #define GYRO_BOMB 4
947 
gyroBotFoundNewEntity(Entity & ent)948 bool gyroBotFoundNewEntity(Entity& ent)
949 {
950 	auto find = gyroBotDetectedUids.find(ent.getUID());
951 	if ( find == gyroBotDetectedUids.end() )
952 	{
953 		gyroBotDetectedUids.insert(std::make_pair(ent.getUID(), ticks));
954 		return true; // new uid.
955 	}
956 	else
957 	{
958 		if ( ent.behavior == &actItem )
959 		{
960 			gyroBotDetectedUids[ent.getUID()] = ticks;
961 			return false; // items never alert again.
962 		}
963 		else if ( ticks - gyroBotDetectedUids[ent.getUID()] > 20 * TICKS_PER_SECOND )
964 		{
965 			gyroBotDetectedUids[ent.getUID()] = ticks;
966 			return true; // count this as new, it will be a monster/trap/something important.
967 		}
968 		else
969 		{
970 			gyroBotDetectedUids[ent.getUID()] = ticks;
971 			return false;
972 		}
973 	}
974 	return false;
975 }
976 
gyroBotAnimate(Entity * my,Stat * myStats,double dist)977 void gyroBotAnimate(Entity* my, Stat* myStats, double dist)
978 {
979 	node_t* node;
980 	Entity* entity = nullptr;
981 	int bodypart;
982 
983 	if ( multiplayer != CLIENT )
984 	{
985 		// sleeping
986 		//if ( myStats->EFFECTS[EFF_ASLEEP] )
987 		//{
988 		//	//my->z = 4;
989 		//	my->pitch = PI / 4;
990 		//}
991 		//else
992 		//{
993 		//	//my->z = 2.25;
994 		//	//my->pitch = 0;
995 		//}
996 		/*if ( my->monsterSpecialState == GYRO_RETURN_LANDING || my->monsterSpecialState == GYRO_INTERACT_LANDING )
997 		{
998 			my->flags[PASSABLE] = false;
999 		}
1000 		else
1001 		{
1002 		}*/
1003 		my->flags[PASSABLE] = true;
1004 
1005 		if ( my->ticks == 25 )
1006 		{
1007 			// drop any bots we collected from the previous level.
1008 			node_t* invNodeNext = nullptr;
1009 			bool dropped = false;
1010 			for ( node_t* invNode = myStats->inventory.first; invNode; invNode = invNodeNext )
1011 			{
1012 				invNodeNext = invNode->next;
1013 				Item* item = (Item*)invNode->element;
1014 				if ( item && (item->type == TOOL_DUMMYBOT || item->type == TOOL_SENTRYBOT || item->type == TOOL_SPELLBOT) )
1015 				{
1016 					for ( int c = item->count; c > 0; c-- )
1017 					{
1018 						Entity* itemDropped = dropItemMonster(item, my, myStats);
1019 						if ( itemDropped )
1020 						{
1021 							dropped = true;
1022 							itemDropped->flags[USERFLAG1] = true;    // makes items passable, improves performance
1023 						}
1024 					}
1025 				}
1026 			}
1027 			if ( dropped )
1028 			{
1029 				int leader = my->monsterAllyIndex;
1030 				if ( leader >= 0 )
1031 				{
1032 					Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1033 					messagePlayerColor(leader, color, language[3651]);
1034 				}
1035 			}
1036 		}
1037 	}
1038 
1039 	int detectDuration = 5 * TICKS_PER_SECOND;
1040 	if ( my->ticks % (detectDuration) == 0 && my->monsterAllyIndex == clientnum )
1041 	{
1042 		Entity* playerLeader = my->monsterAllyGetPlayerLeader();
1043 		bool doPing = false;
1044 		int foundGoodSound = 0;
1045 		int foundBadSound = 0;
1046 		for ( node_t* searchNode = map.entities->first; searchNode != nullptr; searchNode = searchNode->next )
1047 		{
1048 			Entity* ent = (Entity*)searchNode->element;
1049 			if ( !ent || ent == my )
1050 			{
1051 				continue;
1052 			}
1053 			if ( ent->skill[28] > 0 ) // mechanism
1054 			{
1055 				if ( my->monsterAllyPickupItems != ALLY_GYRO_DETECT_TRAPS )
1056 				{
1057 					continue;
1058 				}
1059 			}
1060 			if ( playerLeader )
1061 			{
1062 				if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_MONSTERS )
1063 				{
1064 					if ( ent->behavior == &actMonster && ent->monsterAllyIndex < 0 )
1065 					{
1066 						if ( entityDist(my, ent) < TOUCHRANGE * 5 )
1067 						{
1068 							if ( gyroBotFoundNewEntity(*ent) )
1069 							{
1070 								++foundBadSound;
1071 							}
1072 							if ( ent->entityShowOnMap < detectDuration )
1073 							{
1074 								ent->entityShowOnMap = detectDuration;
1075 							}
1076 						}
1077 					}
1078 				}
1079 				else if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_TRAPS )
1080 				{
1081 					if ( ent->behavior == &actBoulderTrap || ent->behavior == &actArrowTrap
1082 						|| ent->behavior == &actMagicTrap || ent->behavior == &actMagicTrapCeiling
1083 						|| ent->behavior == &actBoulderTrapEast || ent->behavior == &actBoulderTrapWest
1084 						|| ent->behavior == &actBoulderTrapNorth || ent->behavior == &actBoulderTrapSouth
1085 						|| ent->behavior == &actSummonTrap || ent->behavior == &actSpearTrap )
1086 					{
1087 						if ( entityDist(my, ent) < TOUCHRANGE * 5 )
1088 						{
1089 							if ( gyroBotFoundNewEntity(*ent) )
1090 							{
1091 								foundBadSound = 3;
1092 							}
1093 							if ( ent->entityShowOnMap < detectDuration )
1094 							{
1095 								ent->entityShowOnMap = detectDuration;
1096 							}
1097 						}
1098 					}
1099 				}
1100 				else if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_EXITS )
1101 				{
1102 					if ( ent->behavior == &actLadder || ent->behavior == &actPortal )
1103 					{
1104 						if ( entityDist(my, ent) < TOUCHRANGE * 5 )
1105 						{
1106 							if ( gyroBotFoundNewEntity(*ent) )
1107 							{
1108 								foundGoodSound = 5;
1109 							}
1110 							if ( ent->entityShowOnMap < detectDuration )
1111 							{
1112 								ent->entityShowOnMap = detectDuration;
1113 							}
1114 						}
1115 					}
1116 				}
1117 				else if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_METAL
1118 					|| my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_MAGIC
1119 					|| my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_VALUABLE )
1120 				{
1121 					if ( ent->behavior == &actItem )
1122 					{
1123 						if ( entityDist(my, ent) < TOUCHRANGE * 5 )
1124 						{
1125 							Item* itemOnGround = newItemFromEntity(ent);
1126 							int metal = 0;
1127 							int magic = 0;
1128 							if ( itemOnGround )
1129 							{
1130 								GenericGUI.tinkeringGetItemValue(itemOnGround, &metal, &magic);
1131 								if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_METAL
1132 									&& metal > 0 )
1133 								{
1134 									if ( gyroBotFoundNewEntity(*ent) )
1135 									{
1136 										++foundGoodSound;
1137 									}
1138 									if ( ent->entityShowOnMap < detectDuration )
1139 									{
1140 										ent->entityShowOnMap = detectDuration;
1141 									}
1142 								}
1143 								else if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_MAGIC
1144 									&& magic > 0 )
1145 								{
1146 									if ( gyroBotFoundNewEntity(*ent) )
1147 									{
1148 										++foundGoodSound;
1149 									}
1150 									if ( ent->entityShowOnMap < detectDuration )
1151 									{
1152 										ent->entityShowOnMap = detectDuration;
1153 									}
1154 								}
1155 								else if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_VALUABLE
1156 									&& items[itemOnGround->type].value >= 400 )
1157 								{
1158 									if ( gyroBotFoundNewEntity(*ent) )
1159 									{
1160 										foundGoodSound = 5;
1161 									}
1162 									if ( ent->entityShowOnMap < detectDuration )
1163 									{
1164 										ent->entityShowOnMap = detectDuration;
1165 									}
1166 								}
1167 								free(itemOnGround);
1168 							}
1169 						}
1170 					}
1171 				}
1172 			}
1173 			if ( ent->entityShowOnMap > 0 )
1174 			{
1175 				doPing = true;
1176 			}
1177 		}
1178 		if ( doPing )
1179 		{
1180 			int pingx = my->x / 16;
1181 			int pingy = my->y / 16;
1182 			MinimapPing radiusPing(ticks, clientnum, pingx, pingy, true);
1183 			minimapPingAdd(radiusPing);
1184 
1185 			if ( foundGoodSound >= 1 )
1186 			{
1187 				playSoundEntity(my, 444 + rand() % 5, 128);
1188 			}
1189 			else if ( foundBadSound >= 1 )
1190 			{
1191 				playSoundEntity(my, 450, 128);
1192 			}
1193 		}
1194 	}
1195 
1196 	my->removeLightField();
1197 	if ( my->monsterAllyClass > ALLY_GYRO_LIGHT_NONE )
1198 	{
1199 		switch ( my->monsterAllyClass )
1200 		{
1201 			case ALLY_GYRO_LIGHT_FAINT:
1202 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 128);
1203 				break;
1204 			case ALLY_GYRO_LIGHT_BRIGHT:
1205 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 128);
1206 				break;
1207 			default:
1208 				break;
1209 		}
1210 	}
1211 
1212 	my->focalx = limbs[GYROBOT][0][0];
1213 	my->focaly = limbs[GYROBOT][0][1];
1214 	my->focalz = limbs[GYROBOT][0][2];
1215 	if ( multiplayer != CLIENT )
1216 	{
1217 		//my->z = limbs[GYROBOT][3][2];
1218 		if ( my->ticks % (TICKS_PER_SECOND * 15) == 0
1219 			&& my->monsterSpecialTimer == 0
1220 			&& my->monsterSpecialState == 0 )
1221 		{
1222 			// doACoolFlip = true!
1223 			my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr);
1224 			my->monsterSpecialTimer = TICKS_PER_SECOND * 8;
1225 		}
1226 
1227 		if ( my->monsterSpecialState == GYRO_RETURN_LANDING )
1228 		{
1229 			if ( limbAnimateToLimit(my, ANIMATE_Z, 0.05, 0, false, 0.0) )
1230 			{
1231 				int appearance = monsterTinkeringConvertHPToAppearance(myStats);
1232 				Item* item = newItem(TOOL_GYROBOT, static_cast<Status>(myStats->monsterTinkeringStatus), 0, 1, appearance, true, &myStats->inventory);
1233 				myStats->HP = 0;
1234 				my->setObituary(language[3631]);
1235 				return;
1236 			}
1237 		}
1238 		else if ( my->monsterSpecialState == GYRO_INTERACT_LANDING )
1239 		{
1240 			if ( limbAnimateToLimit(my, ANIMATE_Z, 0.1, 0, false, 0.0) )
1241 			{
1242 				my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr);
1243 				my->monsterSpecialTimer = TICKS_PER_SECOND * 5;
1244 				if ( my->monsterAllySetInteract() )
1245 				{
1246 					// do interact.
1247 					my->monsterAllyInteractTarget = 0;
1248 					my->monsterAllyState = ALLY_STATE_DEFAULT;
1249 				}
1250 				my->monsterSpecialState = 0;
1251 				serverUpdateEntitySkill(my, 33);
1252 				my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_ENDPOINT;
1253 			}
1254 		}
1255 		else
1256 		{
1257 			if ( my->z > -4.5 )
1258 			{
1259 				limbAnimateToLimit(my, ANIMATE_Z, -0.1, -5, false, 0.0);
1260 			}
1261 			else
1262 			{
1263 				if ( my->monsterSpecialState == GYRO_START_FLYING )
1264 				{
1265 					if ( limbAnimateToLimit(my, ANIMATE_Z, -0.1, -6, false, 0.0) )
1266 					{
1267 						my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
1268 						my->monsterSpecialState = 0;
1269 						serverUpdateEntitySkill(my, 33);
1270 					}
1271 				}
1272 				else
1273 				{
1274 					if ( my->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_NONE )
1275 					{
1276 						my->z = -6;
1277 						my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
1278 					}
1279 					if ( dist < 0.1 )
1280 					{
1281 						// not moving, float.
1282 						limbAnimateWithOvershoot(my, ANIMATE_Z, 0.01, -4.5, 0.01, -6, ANIMATE_DIR_POSITIVE);
1283 					}
1284 				}
1285 			}
1286 		}
1287 		if ( !myStats->EFFECTS[EFF_LEVITATING] )
1288 		{
1289 			myStats->EFFECTS[EFF_LEVITATING] = true;
1290 			myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
1291 		}
1292 	}
1293 
1294 	//Move bodyparts
1295 	for ( bodypart = 0, node = my->children.first; node != nullptr; node = node->next, ++bodypart )
1296 	{
1297 		if ( bodypart < GYRO_ROTOR_LARGE )
1298 		{
1299 			continue;
1300 		}
1301 
1302 		entity = (Entity*)node->element;
1303 		entity->x = my->x;
1304 		entity->y = my->y;
1305 		entity->z = my->z;
1306 
1307 		if ( bodypart == GYRO_ROTOR_SMALL )
1308 		{
1309 			entity->yaw = my->yaw; // face the monster's direction
1310 			if ( my->monsterAttack == MONSTER_POSE_RANGED_WINDUP1 )
1311 			{
1312 				entity->skill[0] = 1;
1313 				my->monsterAttack = 0;
1314 				my->pitch = 0;
1315 				entity->fskill[0] = 0.05;
1316 			}
1317 			if ( entity->skill[0] == 1 )
1318 			{
1319 				my->pitch = (entity->fskill[0] * 2);
1320 				entity->fskill[0] -= 0.05;
1321 				if ( entity->fskill[0] < -PI )
1322 				{
1323 					entity->skill[0] = 0;
1324 					entity->fskill[0] = 0;
1325 					my->pitch = 0;
1326 				}
1327 			}
1328 			else
1329 			{
1330 				if ( multiplayer != CLIENT )
1331 				{
1332 					if ( dist > 0.1 )
1333 					{
1334 						my->pitch = PI / 16;
1335 					}
1336 					else
1337 					{
1338 						my->pitch = 0;
1339 					}
1340 				}
1341 			}
1342 		}
1343 
1344 		if ( bodypart == GYRO_ROTOR_LARGE )
1345 		{
1346 			entity->pitch = my->pitch + PI / 2;
1347 			entity->yaw = my->yaw;
1348 			entity->roll += 0.1;
1349 
1350 			if ( (my->z > -4 && my->monsterSpecialState == 0) || my->monsterSpecialState == GYRO_START_FLYING )
1351 			{
1352 				entity->roll += 1;
1353 			}
1354 			else if ( dist > 0.1 )
1355 			{
1356 				entity->roll += 0.5;
1357 			}
1358 			else
1359 			{
1360 				entity->roll += 0.2;
1361 			}
1362 			if ( entity->yaw > 2 * PI )
1363 			{
1364 				entity->yaw -= 2 * PI;
1365 			}
1366 		}
1367 		else if ( bodypart == GYRO_ROTOR_SMALL )
1368 		{
1369 			entity->pitch += 0.4;
1370 			if ( entity->pitch > 2 * PI )
1371 			{
1372 				entity->pitch -= 2 * PI;
1373 			}
1374 		}
1375 		else if ( bodypart == GYRO_BOMB )
1376 		{
1377 			entity->pitch = my->pitch + PI / 2;
1378 			entity->yaw = my->yaw;
1379 		}
1380 
1381 		switch ( bodypart )
1382 		{
1383 			case GYRO_ROTOR_LARGE:
1384 				entity->x += limbs[GYROBOT][4][0] * sin(my->pitch) * cos(my->yaw);
1385 				entity->y += limbs[GYROBOT][4][1] * sin(my->pitch) * sin(my->yaw);
1386 				entity->z += limbs[GYROBOT][4][2] * cos(my->pitch);
1387 				entity->focalx = limbs[GYROBOT][1][0];
1388 				entity->focaly = limbs[GYROBOT][1][1];
1389 				entity->focalz = limbs[GYROBOT][1][2];
1390 				//entity->x += limbs[GYROBOT][4][0] * cos(my->yaw + PI / 2) + limbs[GYROBOT][4][1] * cos(my->yaw);
1391 				//entity->y += limbs[GYROBOT][4][0] * sin(my->yaw + PI / 2) + limbs[GYROBOT][4][1] * sin(my->yaw);
1392 				//entity->z += limbs[GYROBOT][4][2];
1393 				break;
1394 			case GYRO_ROTOR_SMALL:
1395 				entity->x += (limbs[GYROBOT][5][0] * cos(my->pitch + PI / 8)) * cos(my->yaw);
1396 				entity->y += (limbs[GYROBOT][5][0] * cos(my->pitch + PI / 8)) * sin(my->yaw);
1397 				entity->z += limbs[GYROBOT][5][2] * sin(my->pitch + PI / 8);
1398 				entity->focalx = limbs[GYROBOT][2][0];
1399 				entity->focaly = limbs[GYROBOT][2][1];
1400 				entity->focalz = limbs[GYROBOT][2][2];
1401 				break;
1402 			case GYRO_BOMB:
1403 				entity->x += limbs[GYROBOT][7][0] * sin(my->pitch) * cos(my->yaw);
1404 				entity->y += limbs[GYROBOT][7][1] * sin(my->pitch) * sin(my->yaw);
1405 				entity->z += limbs[GYROBOT][7][2] * cos(my->pitch);
1406 				entity->focalx = limbs[GYROBOT][6][0];
1407 				entity->focaly = limbs[GYROBOT][6][1];
1408 				entity->focalz = limbs[GYROBOT][6][2];
1409 
1410 				if ( multiplayer != CLIENT )
1411 				{
1412 					entity->sprite = -1;
1413 					for ( node_t* inv = myStats->inventory.first; inv; inv = inv->next )
1414 					{
1415 						Item* holding = (Item*)inv->element;
1416 						if ( holding && holding->type >= TOOL_BOMB && holding->type <= TOOL_TELEPORT_BOMB )
1417 						{
1418 							entity->sprite = items[holding->type].index;
1419 						}
1420 					}
1421 					if ( entity->sprite == -1 )
1422 					{
1423 						entity->flags[INVISIBLE] = true;
1424 					}
1425 					else
1426 					{
1427 						entity->flags[INVISIBLE] = my->flags[INVISIBLE];
1428 					}
1429 				}
1430 
1431 				if ( multiplayer == SERVER )
1432 				{
1433 					// update sprites for clients
1434 					if ( entity->skill[10] != entity->sprite )
1435 					{
1436 						entity->skill[10] = entity->sprite;
1437 						serverUpdateEntityBodypart(my, bodypart);
1438 					}
1439 					if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1440 					{
1441 						serverUpdateEntityBodypart(my, bodypart);
1442 					}
1443 				}
1444 				break;
1445 			default:
1446 				break;
1447 		}
1448 	}
1449 }
1450 
actGyroBotLimb(Entity * my)1451 void actGyroBotLimb(Entity* my)
1452 {
1453 	my->actMonsterLimb(false);
1454 }
1455 
gyroBotDie(Entity * my)1456 void gyroBotDie(Entity* my)
1457 {
1458 	bool gibs = true;
1459 	if ( my->monsterSpecialState == GYRO_RETURN_LANDING )
1460 	{
1461 		// don't make noises etc.
1462 		Stat* myStats = my->getStats();
1463 		if ( myStats && !strncmp(myStats->obituary, language[3631], strlen(language[3631])) )
1464 		{
1465 			// returning to land, don't explode into gibs.
1466 			gibs = false;
1467 		}
1468 	}
1469 
1470 	my->removeMonsterDeathNodes();
1471 	if ( gibs )
1472 	{
1473 		playSoundEntity(my, 451 + rand() % 2, 128);
1474 		playSoundEntity(my, 450, 128);
1475 		int c;
1476 		for ( c = 0; c < 4; c++ )
1477 		{
1478 			Entity* entity = spawnGib(my);
1479 			if ( entity )
1480 			{
1481 				switch ( c )
1482 				{
1483 					case 0:
1484 						entity->sprite = 886;
1485 						break;
1486 					case 1:
1487 						entity->sprite = 887;
1488 						break;
1489 					case 2:
1490 						entity->sprite = 888;
1491 						break;
1492 					case 3:
1493 						entity->sprite = 874;
1494 						break;
1495 					default:
1496 						break;
1497 				}
1498 				serverSpawnGibForClient(entity);
1499 			}
1500 		}
1501 	}
1502 
1503 	list_RemoveNode(my->mynode);
1504 	return;
1505 }
1506 
initDummyBot(Entity * my,Stat * myStats)1507 void initDummyBot(Entity* my, Stat* myStats)
1508 {
1509 	node_t* node;
1510 
1511 	my->initMonster(889);
1512 	my->flags[INVISIBLE] = true; // hide the "AI" bodypart
1513 	if ( multiplayer != CLIENT )
1514 	{
1515 		MONSTER_SPOTSND = -1;
1516 		MONSTER_SPOTVAR = 1;
1517 		MONSTER_IDLESND = 456;
1518 		MONSTER_IDLEVAR = 3;
1519 	}
1520 	if ( multiplayer != CLIENT && !MONSTER_INIT )
1521 	{
1522 		if ( myStats != nullptr )
1523 		{
1524 			if ( !myStats->leader_uid )
1525 			{
1526 				myStats->leader_uid = 0;
1527 			}
1528 
1529 			// apply random stat increases if set in stat_shared.cpp or editor
1530 			setRandomMonsterStats(myStats);
1531 
1532 			// generate 6 items max, less if there are any forced items from boss variants
1533 			int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
1534 
1535 			// generates equipment and weapons if available from editor
1536 			createMonsterEquipment(myStats);
1537 
1538 			// create any custom inventory items from editor if available
1539 			createCustomInventory(myStats, customItemsToGenerate);
1540 
1541 			// count if any custom inventory items from editor
1542 			int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
1543 
1544 			// count any inventory items set to default in edtior
1545 			int defaultItems = countDefaultItems(myStats);
1546 
1547 			//my->setHardcoreStats(*myStats);
1548 		}
1549 	}
1550 
1551 	// head
1552 	Entity* entity = newEntity(889, 0, map.entities, nullptr); //Limb entity.
1553 	entity->sizex = 2;
1554 	entity->sizey = 2;
1555 	entity->skill[2] = my->getUID();
1556 	entity->flags[PASSABLE] = true;
1557 	entity->flags[NOUPDATE] = true;
1558 	entity->yaw = my->yaw;
1559 	entity->z = 6;
1560 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1561 	entity->focalx = limbs[DUMMYBOT][1][0];
1562 	entity->focaly = limbs[DUMMYBOT][1][1];
1563 	entity->focalz = limbs[DUMMYBOT][1][2];
1564 	entity->behavior = &actDummyBotLimb;
1565 	entity->parent = my->getUID();
1566 	node = list_AddNodeLast(&my->children);
1567 	node->element = entity;
1568 	node->deconstructor = &emptyDeconstructor;
1569 	node->size = sizeof(Entity*);
1570 	my->bodyparts.push_back(entity);
1571 
1572 	// body
1573 	entity = newEntity(890, 0, map.entities, nullptr); //Limb entity.
1574 	entity->sizex = 2;
1575 	entity->sizey = 2;
1576 	entity->skill[2] = my->getUID();
1577 	entity->flags[PASSABLE] = true;
1578 	entity->flags[NOUPDATE] = true;
1579 	entity->yaw = my->yaw;
1580 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1581 	entity->focalx = limbs[DUMMYBOT][2][0];
1582 	entity->focaly = limbs[DUMMYBOT][2][1];
1583 	entity->focalz = limbs[DUMMYBOT][2][2];
1584 	entity->behavior = &actDummyBotLimb;
1585 	entity->parent = my->getUID();
1586 	node = list_AddNodeLast(&my->children);
1587 	node->element = entity;
1588 	node->deconstructor = &emptyDeconstructor;
1589 	node->size = sizeof(Entity*);
1590 	my->bodyparts.push_back(entity);
1591 
1592 	// shield
1593 	entity = newEntity(891, 0, map.entities, nullptr); //Limb entity.
1594 	entity->sizex = 2;
1595 	entity->sizey = 2;
1596 	entity->skill[2] = my->getUID();
1597 	entity->flags[PASSABLE] = true;
1598 	entity->flags[NOUPDATE] = true;
1599 	entity->yaw = my->yaw;
1600 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1601 	entity->focalx = limbs[DUMMYBOT][3][0];
1602 	entity->focaly = limbs[DUMMYBOT][3][1];
1603 	entity->focalz = limbs[DUMMYBOT][3][2];
1604 	entity->behavior = &actDummyBotLimb;
1605 	entity->parent = my->getUID();
1606 	node = list_AddNodeLast(&my->children);
1607 	node->element = entity;
1608 	node->deconstructor = &emptyDeconstructor;
1609 	node->size = sizeof(Entity*);
1610 	my->bodyparts.push_back(entity);
1611 
1612 	// box
1613 	entity = newEntity(892, 0, map.entities, nullptr); //Limb entity.
1614 	entity->sizex = 2;
1615 	entity->sizey = 2;
1616 	entity->skill[2] = my->getUID();
1617 	entity->flags[PASSABLE] = true;
1618 	entity->flags[NOUPDATE] = true;
1619 	entity->yaw = my->yaw;
1620 	real_t prevYaw = entity->yaw;
1621 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1622 	entity->focalx = limbs[DUMMYBOT][4][0];
1623 	entity->focaly = limbs[DUMMYBOT][4][1];
1624 	entity->focalz = limbs[DUMMYBOT][4][2];
1625 	entity->behavior = &actDummyBotLimb;
1626 	entity->parent = my->getUID();
1627 	node = list_AddNodeLast(&my->children);
1628 	node->element = entity;
1629 	node->deconstructor = &emptyDeconstructor;
1630 	node->size = sizeof(Entity*);
1631 	my->bodyparts.push_back(entity);
1632 
1633 	// lid
1634 	entity = newEntity(893, 0, map.entities, nullptr); //Limb entity.
1635 	entity->sizex = 2;
1636 	entity->sizey = 2;
1637 	entity->skill[2] = my->getUID();
1638 	entity->flags[PASSABLE] = true;
1639 	entity->flags[NOUPDATE] = true;
1640 	entity->yaw = prevYaw;
1641 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1642 	entity->focalx = limbs[DUMMYBOT][5][0];
1643 	entity->focaly = limbs[DUMMYBOT][5][1];
1644 	entity->focalz = limbs[DUMMYBOT][5][2];
1645 	entity->scalex = 1.01;
1646 	entity->scaley = 1.01;
1647 	entity->scalez = 1.01;
1648 	entity->pitch = PI;
1649 	entity->behavior = &actDummyBotLimb;
1650 	entity->parent = my->getUID();
1651 	node = list_AddNodeLast(&my->children);
1652 	node->element = entity;
1653 	node->deconstructor = &emptyDeconstructor;
1654 	node->size = sizeof(Entity*);
1655 	my->bodyparts.push_back(entity);
1656 
1657 	// crank
1658 	entity = newEntity(895, 0, map.entities, nullptr); //Limb entity.
1659 	entity->sizex = 1;
1660 	entity->sizey = 1;
1661 	entity->skill[2] = my->getUID();
1662 	entity->flags[PASSABLE] = true;
1663 	entity->flags[NOUPDATE] = true;
1664 	entity->yaw = prevYaw;
1665 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
1666 	entity->focalx = limbs[DUMMYBOT][11][0];
1667 	entity->focaly = limbs[DUMMYBOT][11][1];
1668 	entity->focalz = limbs[DUMMYBOT][11][2];
1669 	entity->fskill[0] = 1;
1670 	entity->behavior = &actDummyBotLimb;
1671 	entity->parent = my->getUID();
1672 	node = list_AddNodeLast(&my->children);
1673 	node->element = entity;
1674 	node->deconstructor = &emptyDeconstructor;
1675 	node->size = sizeof(Entity*);
1676 	my->bodyparts.push_back(entity);
1677 
1678 	if ( multiplayer == CLIENT || MONSTER_INIT )
1679 	{
1680 		return;
1681 	}
1682 }
1683 
actDummyBotLimb(Entity * my)1684 void actDummyBotLimb(Entity* my)
1685 {
1686 	my->actMonsterLimb(false);
1687 }
1688 
dummyBotDie(Entity * my)1689 void dummyBotDie(Entity* my)
1690 {
1691 	bool gibs = true;
1692 	if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
1693 	{
1694 		// don't make noises etc.
1695 		Stat* myStats = my->getStats();
1696 		if ( myStats && !strncmp(myStats->obituary, language[3643], strlen(language[3643])) )
1697 		{
1698 			// returning to box, don't explode into gibs.
1699 			gibs = false;
1700 		}
1701 	}
1702 	else
1703 	{
1704 		Stat* myStats = my->getStats();
1705 		bool dropBrokenShell = true;
1706 		if ( myStats && myStats->monsterNoDropItems == 1 && !my->monsterAllyGetPlayerLeader() )
1707 		{
1708 			dropBrokenShell = false;
1709 		}
1710 		/*if ( myStats->monsterTinkeringStatus == EXCELLENT && rand() % 100 < 80 )
1711 		{
1712 			dropBrokenShell = true;
1713 		}
1714 		else if ( myStats->monsterTinkeringStatus == SERVICABLE && rand() % 100 < 60 )
1715 		{
1716 			dropBrokenShell = true;
1717 		}
1718 		else if ( myStats->monsterTinkeringStatus == WORN && rand() % 100 < 40 )
1719 		{
1720 			dropBrokenShell = true;
1721 		}
1722 		else if ( myStats->monsterTinkeringStatus == DECREPIT && rand() % 100 < 20 )
1723 		{
1724 			dropBrokenShell = true;
1725 		}*/
1726 
1727 		if ( dropBrokenShell )
1728 		{
1729 			Item* item = newItem(TOOL_DUMMYBOT, BROKEN, 0, 1, 0, true, nullptr);
1730 			Entity* entity = dropItemMonster(item, my, myStats);
1731 			if ( entity )
1732 			{
1733 				entity->flags[USERFLAG1] = true;    // makes items passable, improves performance
1734 			}
1735 		}
1736 	}
1737 
1738 	my->removeMonsterDeathNodes();
1739 	if ( gibs )
1740 	{
1741 		playSoundEntity(my, 451 + rand() % 2, 128);
1742 		int c;
1743 		for ( c = 0; c < 5; c++ )
1744 		{
1745 			Entity* entity = spawnGib(my);
1746 			if ( entity )
1747 			{
1748 				switch ( c )
1749 				{
1750 					case 0:
1751 						entity->sprite = 889;
1752 						break;
1753 					case 1:
1754 						entity->sprite = 890;
1755 						break;
1756 					case 2:
1757 						entity->sprite = 891;
1758 						break;
1759 					case 3:
1760 						entity->sprite = 892;
1761 						break;
1762 					case 4:
1763 						entity->sprite = 893;
1764 						break;
1765 					default:
1766 						break;
1767 				}
1768 				serverSpawnGibForClient(entity);
1769 			}
1770 		}
1771 	}
1772 
1773 	list_RemoveNode(my->mynode);
1774 	return;
1775 }
1776 
1777 #define DUMMY_HEAD 2
1778 #define DUMMY_BODY 3
1779 #define DUMMY_SHIELD 4
1780 #define DUMMY_BOX 5
1781 #define DUMMY_LID 6
1782 #define DUMMY_CRANK 7
1783 
dummyBotAnimate(Entity * my,Stat * myStats,double dist)1784 void dummyBotAnimate(Entity* my, Stat* myStats, double dist)
1785 {
1786 	node_t* node;
1787 	Entity* entity = nullptr;
1788 	Entity* head = nullptr;
1789 	int bodypart;
1790 
1791 	my->flags[INVISIBLE] = true; // hide the "AI" bodypart
1792 
1793 	my->focalx = limbs[DUMMYBOT][0][0];
1794 	my->focaly = limbs[DUMMYBOT][0][1];
1795 	my->focalz = limbs[DUMMYBOT][0][2];
1796 	if ( multiplayer != CLIENT )
1797 	{
1798 		my->z = 0;
1799 	}
1800 
1801 	//Move bodyparts
1802 	for ( bodypart = 0, node = my->children.first; node != nullptr; node = node->next, ++bodypart )
1803 	{
1804 		if ( bodypart < DUMMY_HEAD )
1805 		{
1806 			continue;
1807 		}
1808 
1809 		entity = (Entity*)node->element;
1810 		entity->x = my->x;
1811 		entity->y = my->y;
1812 		if ( bodypart == DUMMY_HEAD )
1813 		{
1814 			head = entity;
1815 			if ( multiplayer != CLIENT && entity->skill[0] == 2 )
1816 			{
1817 				if ( entity->skill[3] > 0 && myStats->HP < entity->skill[3] )
1818 				{
1819 					// on hit, bounce a bit.
1820 					my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr);
1821 				}
1822 				entity->skill[3] = myStats->HP;
1823 			}
1824 
1825 			if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
1826 			{
1827 				bool pitchZero = false;
1828 				while ( entity->pitch > 2 * PI )
1829 				{
1830 					entity->pitch -= 2 * PI;
1831 				}
1832 				while ( entity->pitch < 0 )
1833 				{
1834 					entity->pitch += 2 * PI;
1835 				}
1836 				if ( entity->pitch > 0 && entity->pitch <= PI )
1837 				{
1838 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 0.0, false, 0.0) )
1839 					{
1840 						pitchZero = true;
1841 					}
1842 				}
1843 				else
1844 				{
1845 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.1, 0.0, false, 0.0) )
1846 					{
1847 						pitchZero = true;
1848 					}
1849 				}
1850 				entity->skill[0] = 3;
1851 				real_t rate = 0.5;
1852 				if ( entity->z > 12 )
1853 				{
1854 					rate = 0.1;
1855 				}
1856 				if ( pitchZero && limbAnimateToLimit(entity, ANIMATE_Z, rate, 7.5 + limbs[DUMMYBOT][6][2], false, 0.0) )
1857 				{
1858 					if ( multiplayer != CLIENT )
1859 					{
1860 						// kill me!
1861 						int appearance = monsterTinkeringConvertHPToAppearance(myStats);
1862 						Item* item = newItem(TOOL_DUMMYBOT, static_cast<Status>(myStats->monsterTinkeringStatus), 0, 1, appearance, true, &myStats->inventory);
1863 						myStats->HP = 0;
1864 						my->setObituary(language[3643]);
1865 						return;
1866 					}
1867 				}
1868 			}
1869 			else if ( entity->skill[0] == 0 ) // non initialized.
1870 			{
1871 				entity->skill[0] = 1;
1872 				entity->fskill[0] = -1;
1873 				entity->z = 6 + limbs[DUMMYBOT][6][2];
1874 			}
1875 			else if ( entity->skill[0] == 1 ) // rising.
1876 			{
1877 				if ( limbAnimateToLimit(entity, ANIMATE_Z, entity->fskill[0], limbs[DUMMYBOT][6][2], false, 0.0) )
1878 				{
1879 					entity->skill[0] = 2;
1880 					entity->z = my->z;
1881 				}
1882 				else
1883 				{
1884 					entity->fskill[0] *= 0.85;
1885 				}
1886 			}
1887 			else
1888 			{
1889 				entity->z = my->z;
1890 				if ( my->monsterAttack == MONSTER_POSE_RANGED_WINDUP1 )
1891 				{
1892 					my->monsterAttack = 0;
1893 					entity->skill[4] = 1;
1894 					entity->fskill[1] = 0.06;
1895 					entity->fskill[2] = PI / 6;
1896 					entity->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
1897 				}
1898 				if ( entity->skill[4] > 0 )
1899 				{
1900 					if ( entity->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_NONE )
1901 					{
1902 						if ( entity->pitch > 0 )
1903 						{
1904 							if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -entity->fskill[1], 0.0, false, 0.0) )
1905 							{
1906 								entity->skill[4] = 0;
1907 							}
1908 						}
1909 						else
1910 						{
1911 							if ( limbAnimateToLimit(entity, ANIMATE_PITCH, entity->fskill[1], 0.0, false, 0.0) )
1912 							{
1913 								entity->skill[4] = 0;
1914 							}
1915 						}
1916 					}
1917 					else
1918 					{
1919 						limbAnimateWithOvershoot(entity, ANIMATE_PITCH, entity->fskill[1], 2 * PI - entity->fskill[2],
1920 							entity->fskill[1], PI / 12, ANIMATE_DIR_NEGATIVE);
1921 					}
1922 				}
1923 			}
1924 		}
1925 		else if ( bodypart != DUMMY_LID && bodypart != DUMMY_BOX && bodypart != DUMMY_CRANK )
1926 		{
1927 			if ( head )
1928 			{
1929 				if ( head->skill[0] == 2 )
1930 				{
1931 					entity->z = my->z;
1932 					entity->pitch = head->pitch;
1933 				}
1934 				else
1935 				{
1936 					if ( head->skill[0] == 3 ) // returning to box
1937 					{
1938 						entity->pitch = head->pitch;
1939 					}
1940 					entity->z = head->z - limbs[DUMMYBOT][6][2];
1941 				}
1942 			}
1943 		}
1944 
1945 		if ( bodypart == DUMMY_BODY || bodypart == DUMMY_SHIELD || bodypart == DUMMY_HEAD )
1946 		{
1947 			entity->yaw = my->yaw;
1948 		}
1949 		else if ( bodypart == DUMMY_LID )
1950 		{
1951 			if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
1952 			{
1953 				if ( head && head->z > 11 )
1954 				{
1955 					limbAnimateToLimit(entity, ANIMATE_PITCH, 0.5, PI, false, 0.0);
1956 				}
1957 			}
1958 			else if ( entity->skill[0] == 0 )
1959 			{
1960 				if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.5, 7 * PI / 4, false, 0.0) )
1961 				{
1962 					entity->skill[0] = 1;
1963 				}
1964 			}
1965 		}
1966 		else if ( bodypart == DUMMY_CRANK )
1967 		{
1968 			if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM )
1969 			{
1970 				entity->pitch -= 0.5;
1971 				if ( entity->pitch < 0 )
1972 				{
1973 					entity->pitch += 2 * PI;
1974 				}
1975 			}
1976 			else if ( entity->fskill[0] > 0.08 )
1977 			{
1978 				entity->pitch += entity->fskill[0];
1979 				if ( entity->pitch > 2 * PI )
1980 				{
1981 					entity->pitch -= 2 * PI;
1982 				}
1983 				entity->fskill[0] *= 0.95;
1984 			}
1985 			else if ( entity->skill[0] == 0 )
1986 			{
1987 				// fall to rest on a 90 degree angle.
1988 				if ( entity->pitch < PI / 2 )
1989 				{
1990 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.08, PI / 2, false, 0.f) )
1991 					{
1992 						entity->skill[0] = 1;
1993 					}
1994 				}
1995 				else if ( entity->pitch < PI )
1996 				{
1997 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.08, PI, false, 0.f) )
1998 					{
1999 						entity->skill[0] = 1;
2000 					}
2001 				}
2002 				else if ( entity->pitch < (3 * PI / 2) )
2003 				{
2004 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.08, 3 * PI / 2, false, 0.f) )
2005 					{
2006 						entity->skill[0] = 1;
2007 					}
2008 				}
2009 				else
2010 				{
2011 					if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.08, 0.f, false, 0.f) )
2012 					{
2013 						entity->skill[0] = 1;
2014 					}
2015 				}
2016 			}
2017 		}
2018 
2019 		switch ( bodypart )
2020 		{
2021 			case DUMMY_HEAD:
2022 				entity->x += limbs[DUMMYBOT][6][0] * cos(my->yaw);
2023 				entity->y += limbs[DUMMYBOT][6][1] * sin(my->yaw);
2024 				if ( entity->skill[0] == 2 )
2025 				{
2026 					entity->z += limbs[DUMMYBOT][6][2];
2027 				}
2028 				entity->focalx = limbs[DUMMYBOT][1][0];
2029 				entity->focaly = limbs[DUMMYBOT][1][1];
2030 				entity->focalz = limbs[DUMMYBOT][1][2];
2031 				break;
2032 			case DUMMY_BODY:
2033 				entity->x += limbs[DUMMYBOT][7][0] * cos(my->yaw);
2034 				entity->y += limbs[DUMMYBOT][7][1] * sin(my->yaw);
2035 				entity->z += limbs[DUMMYBOT][7][2];
2036 				entity->focalx = limbs[DUMMYBOT][2][0];
2037 				entity->focaly = limbs[DUMMYBOT][2][1];
2038 				entity->focalz = limbs[DUMMYBOT][2][2];
2039 				break;
2040 			case DUMMY_SHIELD:
2041 				if ( head && head->skill[0] != 2 )
2042 				{
2043 					entity->flags[INVISIBLE] = true;
2044 				}
2045 				else
2046 				{
2047 					if ( entity->flags[INVISIBLE] )
2048 					{
2049 						playSoundEntityLocal(my, 44 + rand() % 3, 92);
2050 					}
2051 					entity->flags[INVISIBLE] = false;
2052 				}
2053 				entity->x += limbs[DUMMYBOT][8][0] * cos(my->yaw) + limbs[DUMMYBOT][8][1] * cos(my->yaw + PI / 2);
2054 				entity->y += limbs[DUMMYBOT][8][0] * sin(my->yaw) + limbs[DUMMYBOT][8][1] * sin(my->yaw + PI / 2);
2055 				entity->z += limbs[DUMMYBOT][8][2];
2056 				entity->focalx = limbs[DUMMYBOT][3][0];
2057 				entity->focaly = limbs[DUMMYBOT][3][1];
2058 				entity->focalz = limbs[DUMMYBOT][3][2];
2059 				break;
2060 			case DUMMY_BOX:
2061 				entity->x += limbs[DUMMYBOT][9][0] * cos(entity->yaw);
2062 				entity->y += limbs[DUMMYBOT][9][1] * sin(entity->yaw);
2063 				entity->z = limbs[DUMMYBOT][9][2];
2064 				entity->focalx = limbs[DUMMYBOT][4][0];
2065 				entity->focaly = limbs[DUMMYBOT][4][1];
2066 				entity->focalz = limbs[DUMMYBOT][4][2];
2067 				break;
2068 			case DUMMY_LID:
2069 				entity->x += limbs[DUMMYBOT][10][0] * cos(entity->yaw);
2070 				entity->y += limbs[DUMMYBOT][10][1] * sin(entity->yaw);
2071 				entity->z = limbs[DUMMYBOT][10][2];
2072 				entity->focalx = limbs[DUMMYBOT][5][0];
2073 				entity->focaly = limbs[DUMMYBOT][5][1];
2074 				entity->focalz = limbs[DUMMYBOT][5][2];
2075 				break;
2076 			case DUMMY_CRANK:
2077 				entity->x += limbs[DUMMYBOT][12][0] * cos(entity->yaw) + limbs[DUMMYBOT][12][1] * cos(entity->yaw + PI / 2);
2078 				entity->y += limbs[DUMMYBOT][12][0] * sin(entity->yaw) + limbs[DUMMYBOT][12][1] * sin(entity->yaw + PI / 2);
2079 				entity->z = limbs[DUMMYBOT][12][2];
2080 				entity->focalx = limbs[DUMMYBOT][11][0];
2081 				entity->focaly = limbs[DUMMYBOT][11][1];
2082 				entity->focalz = limbs[DUMMYBOT][11][2];
2083 				break;
2084 			default:
2085 				break;
2086 		}
2087 	}
2088 
2089 	if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
2090 	{
2091 		MONSTER_ATTACKTIME++;
2092 	}
2093 	else if ( MONSTER_ATTACK == 0 )
2094 	{
2095 		MONSTER_ATTACKTIME = 0;
2096 	}
2097 	else
2098 	{
2099 		// do nothing, don't reset attacktime or increment it.
2100 	}
2101 }
2102 
tinkerBotSetStats(Stat * myStats,int rank)2103 void Entity::tinkerBotSetStats(Stat* myStats, int rank)
2104 {
2105 	if ( !myStats )
2106 	{
2107 		return;
2108 	}
2109 
2110 	if ( myStats->type == SENTRYBOT )
2111 	{
2112 		switch ( rank )
2113 		{
2114 			case DECREPIT:
2115 				myStats->LVL = 3;
2116 				myStats->HP = 50;
2117 				myStats->CON = 0;
2118 				myStats->PER = 4;
2119 				break;
2120 			case WORN:
2121 				myStats->LVL = 5;
2122 				myStats->HP = 75;
2123 				myStats->CON = 3;
2124 				myStats->PER = 8;
2125 				break;
2126 			case SERVICABLE:
2127 				myStats->LVL = 10;
2128 				myStats->HP = 125;
2129 				myStats->CON = 6;
2130 				myStats->PER = 12;
2131 				break;
2132 			case EXCELLENT:
2133 				myStats->LVL = 15;
2134 				myStats->HP = 150;
2135 				myStats->CON = 9;
2136 				myStats->PER = 16;
2137 				break;
2138 			default:
2139 				break;
2140 		}
2141 	}
2142 	else if ( myStats->type == SPELLBOT )
2143 	{
2144 		switch ( rank )
2145 		{
2146 			case DECREPIT:
2147 				myStats->LVL = 3;
2148 				myStats->HP = 50;
2149 				myStats->CON = 0;
2150 				myStats->PER = 4;
2151 				break;
2152 			case WORN:
2153 				myStats->LVL = 5;
2154 				myStats->HP = 75;
2155 				myStats->CON = 3;
2156 				myStats->PER = 8;
2157 				break;
2158 			case SERVICABLE:
2159 				myStats->LVL = 10;
2160 				myStats->HP = 125;
2161 				myStats->CON = 6;
2162 				myStats->PER = 12;
2163 				break;
2164 			case EXCELLENT:
2165 				myStats->LVL = 15;
2166 				myStats->HP = 150;
2167 				myStats->CON = 9;
2168 				myStats->PER = 16;
2169 				break;
2170 			default:
2171 				break;
2172 		}
2173 	}
2174 	else if ( myStats->type == GYROBOT )
2175 	{
2176 		switch ( rank )
2177 		{
2178 			case DECREPIT:
2179 				myStats->LVL = 1;
2180 				myStats->HP = 10;
2181 				break;
2182 			case WORN:
2183 				myStats->LVL = 5;
2184 				myStats->HP = 35;
2185 				break;
2186 			case SERVICABLE:
2187 				myStats->LVL = 10;
2188 				myStats->HP = 60;
2189 				break;
2190 			case EXCELLENT:
2191 				myStats->LVL = 15;
2192 				myStats->HP = 85;
2193 				break;
2194 			default:
2195 				break;
2196 		}
2197 	}
2198 	else if ( myStats->type == DUMMYBOT )
2199 	{
2200 		switch ( rank )
2201 		{
2202 			case DECREPIT:
2203 				myStats->LVL = 3;
2204 				myStats->HP = 50;
2205 				myStats->CON = 5;
2206 				break;
2207 			case WORN:
2208 				myStats->LVL = 5;
2209 				myStats->HP = 100;
2210 				myStats->CON = 8;
2211 				break;
2212 			case SERVICABLE:
2213 				myStats->LVL = 10;
2214 				myStats->HP = 150;
2215 				myStats->CON = 10;
2216 				break;
2217 			case EXCELLENT:
2218 				myStats->LVL = 15;
2219 				myStats->HP = 200;
2220 				myStats->CON = 15;
2221 				break;
2222 			default:
2223 				break;
2224 		}
2225 	}
2226 
2227 	myStats->MAXHP = myStats->HP;
2228 	myStats->OLDHP = myStats->HP;
2229 	return;
2230 }