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 }