1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_automaton.cpp
5 Desc: implements all of the automaton monster's code
6
7 Copyright 2013-2016 (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 "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23
initAutomaton(Entity * my,Stat * myStats)24 void initAutomaton(Entity* my, Stat* myStats)
25 {
26 node_t* node;
27
28 //Sprite 467 = Automaton head model
29 my->initMonster(467);
30
31 if ( multiplayer != CLIENT )
32 {
33 MONSTER_SPOTSND = 263;
34 MONSTER_SPOTVAR = 3;
35 MONSTER_IDLESND = 257;
36 MONSTER_IDLEVAR = 2;
37 }
38 if ( multiplayer != CLIENT && !MONSTER_INIT )
39 {
40 if ( myStats != NULL )
41 {
42 if ( !myStats->leader_uid )
43 {
44 myStats->leader_uid = 0;
45 }
46 bool lesserMonster = false;
47 bool greaterMonster = false;
48 if ( !strncmp(myStats->name, "damaged automaton", strlen("damaged automaton")) )
49 {
50 lesserMonster = true;
51 myStats->HP = 80;
52 myStats->MAXHP = 115;
53 myStats->RANDOM_MAXHP = 0;
54 myStats->RANDOM_HP = 30;
55 myStats->OLDHP = myStats->HP;
56 myStats->STR = 13;
57 myStats->DEX = 4;
58 myStats->CON = 8;
59 myStats->INT = -1;
60 myStats->PER = 10;
61 myStats->CHR = -3;
62 myStats->EXP = 0;
63 myStats->LVL = 16;
64 }
65 else if ( !strncmp(myStats->name, "corrupted automaton", strlen("corrupted automaton")) )
66 {
67 greaterMonster = true;
68 myStats->HP = 150;
69 myStats->MAXHP = 150;
70 myStats->RANDOM_MAXHP = 0;
71 myStats->RANDOM_HP = 0;
72 myStats->OLDHP = myStats->HP;
73 myStats->STR = 35;
74 myStats->DEX = 13;
75 myStats->CON = 8;
76 myStats->INT = 10;
77 myStats->PER = 25;
78 myStats->CHR = -3;
79 myStats->LVL = 30;
80 myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 1;
81 myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 1;
82 myStats->EDITOR_ITEMS[ITEM_SLOT_ARMOR] = 1;
83 myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] = 1;
84 myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] = 1;
85 }
86 // apply random stat increases if set in stat_shared.cpp or editor
87 setRandomMonsterStats(myStats);
88
89 // generate 6 items max, less if there are any forced items from boss variants
90 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
91
92 // boss variants
93 //if ( rand() % 50 || my->flags[USERFLAG2] )
94 //{
95 // if ( strncmp(map.name, "Underworld", 10) )
96 // {
97 // switch ( rand() % 10 )
98 // {
99 // case 0:
100 // case 1:
101 // //myStats->weapon = newItem(BRONZE_AXE, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
102 // break;
103 // case 2:
104 // case 3:
105 // //myStats->weapon = newItem(BRONZE_SWORD, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
106 // break;
107 // case 4:
108 // case 5:
109 // //myStats->weapon = newItem(IRON_SPEAR, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
110 // break;
111 // case 6:
112 // case 7:
113 // //myStats->weapon = newItem(IRON_AXE, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
114 // break;
115 // case 8:
116 // case 9:
117 // //myStats->weapon = newItem(IRON_SWORD, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
118 // break;
119 // }
120 // }
121 //}
122 //else
123 //{
124 // myStats->HP = 100;
125 // myStats->MAXHP = 100;
126 // strcpy(myStats->name, "Funny Bones");
127 // myStats->weapon = newItem(ARTIFACT_AXE, EXCELLENT, 1, 1, rand(), true, NULL);
128 // myStats->cloak = newItem(CLOAK_PROTECTION, WORN, 0, 1, 2, true, NULL);
129 //}
130
131 // random effects
132
133 // generates equipment and weapons if available from editor
134 createMonsterEquipment(myStats);
135
136 // create any custom inventory items from editor if available
137 createCustomInventory(myStats, customItemsToGenerate);
138
139 // count if any custom inventory items from editor
140 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
141
142 // count any inventory items set to default in edtior
143 int defaultItems = countDefaultItems(myStats);
144
145 my->setHardcoreStats(*myStats);
146
147 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
148 switch ( defaultItems )
149 {
150 case 6:
151 case 5:
152 case 4:
153 case 3:
154 case 2:
155 case 1:
156 break;
157 default:
158 break;
159 }
160
161 //give weapon
162 if ( myStats->weapon == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
163 {
164 if ( greaterMonster )
165 {
166 switch ( rand() % 4 )
167 {
168 case 0:
169 myStats->weapon = newItem(MAGICSTAFF_LIGHTNING, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
170 break;
171 case 1:
172 myStats->weapon = newItem(CRYSTAL_SPEAR, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
173 break;
174 case 2:
175 myStats->weapon = newItem(SHORTBOW, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
176 break;
177 case 3:
178 myStats->weapon = newItem(CROSSBOW, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
179 break;
180 default:
181 break;
182 }
183 }
184 }
185
186 //give helmet
187 if ( myStats->helmet == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_HELM] == 1 )
188 {
189 switch ( rand() % 10 )
190 {
191 case 0:
192 case 1:
193 case 2:
194 case 3:
195 case 4:
196 break;
197 case 5:
198 //myStats->helmet = newItem(LEATHER_HELM, DECREPIT, -1 + rand() % 2, 1, 0, false, NULL);
199 break;
200 case 6:
201 case 7:
202 case 8:
203 case 9:
204 //myStats->helmet = newItem(IRON_HELM, DECREPIT, -1 + rand() % 2, 1, 0, false, NULL);
205 break;
206 }
207 }
208
209 //give shield
210 if ( myStats->shield == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 )
211 {
212 if ( myStats->weapon && isRangedWeapon(*myStats->weapon) )
213 {
214 my->monsterGenerateQuiverItem(myStats);
215 }
216 else
217 {
218 if ( greaterMonster )
219 {
220 switch ( rand() % 4 )
221 {
222 case 0:
223 myStats->shield = newItem(CRYSTAL_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
224 break;
225 case 1:
226 myStats->shield = newItem(STEEL_SHIELD_RESISTANCE, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
227 break;
228 case 2:
229 myStats->shield = newItem(STEEL_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
230 break;
231 case 3:
232 myStats->shield = newItem(MIRROR_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
233 break;
234 default:
235 break;
236 }
237 }
238 }
239 }
240
241 //give boots
242 if ( myStats->shoes == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] == 1 )
243 {
244 if ( greaterMonster )
245 {
246 switch ( rand() % 4 )
247 {
248 case 0:
249 case 1:
250 case 2:
251 case 3:
252 myStats->shoes = newItem(STEEL_BOOTS_LEVITATION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
253 break;
254 default:
255 break;
256 }
257 }
258 }
259
260 //give cloak
261 if ( myStats->cloak == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 )
262 {
263 if ( greaterMonster )
264 {
265 switch ( rand() % 4 )
266 {
267 case 0:
268 myStats->cloak = newItem(CLOAK_MAGICREFLECTION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
269 break;
270 case 1:
271 myStats->cloak = newItem(CLOAK_PROTECTION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
272 break;
273 case 2:
274 myStats->cloak = newItem(CLOAK, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
275 break;
276 case 3:
277 break;
278 default:
279 break;
280 }
281 }
282 }
283 }
284 }
285
286 // torso
287 Entity* entity = newEntity(468, 0, map.entities, nullptr); //Limb entity.
288 entity->sizex = 4;
289 entity->sizey = 4;
290 entity->skill[2] = my->getUID();
291 entity->flags[PASSABLE] = true;
292 entity->flags[NOUPDATE] = true;
293 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
294 entity->focalx = limbs[AUTOMATON][1][0]; // 0
295 entity->focaly = limbs[AUTOMATON][1][1]; // 0
296 entity->focalz = limbs[AUTOMATON][1][2]; // 0
297 entity->behavior = &actAutomatonLimb;
298 entity->parent = my->getUID();
299 node = list_AddNodeLast(&my->children);
300 node->element = entity;
301 node->deconstructor = &emptyDeconstructor;
302 node->size = sizeof(Entity*);
303 my->bodyparts.push_back(entity);
304
305 // right leg
306 entity = newEntity(474, 0, map.entities, nullptr); //Limb entity.
307 entity->sizex = 4;
308 entity->sizey = 4;
309 entity->skill[2] = my->getUID();
310 entity->flags[PASSABLE] = true;
311 entity->flags[NOUPDATE] = true;
312 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
313 entity->focalx = limbs[AUTOMATON][2][0]; // 0
314 entity->focaly = limbs[AUTOMATON][2][1]; // 0
315 entity->focalz = limbs[AUTOMATON][2][2]; // 2
316 entity->behavior = &actAutomatonLimb;
317 entity->parent = my->getUID();
318 node = list_AddNodeLast(&my->children);
319 node->element = entity;
320 node->deconstructor = &emptyDeconstructor;
321 node->size = sizeof(Entity*);
322 my->bodyparts.push_back(entity);
323
324 // left leg
325 entity = newEntity(473, 0, map.entities, nullptr); //Limb entity.
326 entity->sizex = 4;
327 entity->sizey = 4;
328 entity->skill[2] = my->getUID();
329 entity->flags[PASSABLE] = true;
330 entity->flags[NOUPDATE] = true;
331 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
332 entity->focalx = limbs[AUTOMATON][3][0]; // 0
333 entity->focaly = limbs[AUTOMATON][3][1]; // 0
334 entity->focalz = limbs[AUTOMATON][3][2]; // 2
335 entity->behavior = &actAutomatonLimb;
336 entity->parent = my->getUID();
337 node = list_AddNodeLast(&my->children);
338 node->element = entity;
339 node->deconstructor = &emptyDeconstructor;
340 node->size = sizeof(Entity*);
341 my->bodyparts.push_back(entity);
342
343 // right arm
344 entity = newEntity(471, 0, map.entities, nullptr); //Limb entity.
345 entity->sizex = 4;
346 entity->sizey = 4;
347 entity->skill[2] = my->getUID();
348 entity->flags[PASSABLE] = true;
349 entity->flags[NOUPDATE] = true;
350 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
351 entity->focalx = limbs[AUTOMATON][4][0]; // 0
352 entity->focaly = limbs[AUTOMATON][4][1]; // 0
353 entity->focalz = limbs[AUTOMATON][4][2]; // 2
354 entity->behavior = &actAutomatonLimb;
355 entity->parent = my->getUID();
356 node = list_AddNodeLast(&my->children);
357 node->element = entity;
358 node->deconstructor = &emptyDeconstructor;
359 node->size = sizeof(Entity*);
360 my->bodyparts.push_back(entity);
361
362 // left arm
363 entity = newEntity(469, 0, map.entities, nullptr); //Limb entity.
364 entity->sizex = 4;
365 entity->sizey = 4;
366 entity->skill[2] = my->getUID();
367 entity->flags[PASSABLE] = true;
368 entity->flags[NOUPDATE] = true;
369 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
370 entity->focalx = limbs[AUTOMATON][5][0]; // 0
371 entity->focaly = limbs[AUTOMATON][5][1]; // 0
372 entity->focalz = limbs[AUTOMATON][5][2]; // 2
373 entity->behavior = &actAutomatonLimb;
374 entity->parent = my->getUID();
375 node = list_AddNodeLast(&my->children);
376 node->element = entity;
377 node->deconstructor = &emptyDeconstructor;
378 node->size = sizeof(Entity*);
379 my->bodyparts.push_back(entity);
380
381 // world weapon
382 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
383 entity->sizex = 4;
384 entity->sizey = 4;
385 entity->skill[2] = my->getUID();
386 entity->flags[PASSABLE] = true;
387 entity->flags[NOUPDATE] = true;
388 entity->flags[INVISIBLE] = true;
389 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
390 entity->focalx = limbs[AUTOMATON][6][0]; // 2.5
391 entity->focaly = limbs[AUTOMATON][6][1]; // 0
392 entity->focalz = limbs[AUTOMATON][6][2]; // 0
393 entity->behavior = &actAutomatonLimb;
394 entity->parent = my->getUID();
395 entity->pitch = .25;
396 node = list_AddNodeLast(&my->children);
397 node->element = entity;
398 node->deconstructor = &emptyDeconstructor;
399 node->size = sizeof(Entity*);
400 my->bodyparts.push_back(entity);
401
402 // shield
403 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
404 entity->sizex = 4;
405 entity->sizey = 4;
406 entity->skill[2] = my->getUID();
407 entity->flags[PASSABLE] = true;
408 entity->flags[NOUPDATE] = true;
409 entity->flags[INVISIBLE] = true;
410 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
411 entity->focalx = limbs[AUTOMATON][7][0]; // 2
412 entity->focaly = limbs[AUTOMATON][7][1]; // 0
413 entity->focalz = limbs[AUTOMATON][7][2]; // 0
414 entity->behavior = &actAutomatonLimb;
415 entity->parent = my->getUID();
416 node = list_AddNodeLast(&my->children);
417 node->element = entity;
418 node->deconstructor = &emptyDeconstructor;
419 node->size = sizeof(Entity*);
420 my->bodyparts.push_back(entity);
421
422 // cloak
423 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
424 entity->sizex = 4;
425 entity->sizey = 4;
426 entity->skill[2] = my->getUID();
427 entity->scalex = 1.01;
428 entity->scaley = 1.01;
429 entity->scalez = 1.01;
430 entity->flags[PASSABLE] = true;
431 entity->flags[NOUPDATE] = true;
432 entity->flags[INVISIBLE] = true;
433 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
434 entity->focalx = limbs[AUTOMATON][8][0]; // 0
435 entity->focaly = limbs[AUTOMATON][8][1]; // 0
436 entity->focalz = limbs[AUTOMATON][8][2]; // 4
437 entity->behavior = &actAutomatonLimb;
438 entity->parent = my->getUID();
439 node = list_AddNodeLast(&my->children);
440 node->element = entity;
441 node->deconstructor = &emptyDeconstructor;
442 node->size = sizeof(Entity*);
443 my->bodyparts.push_back(entity);
444
445 // helmet
446 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
447 entity->sizex = 4;
448 entity->sizey = 4;
449 entity->skill[2] = my->getUID();
450 entity->scalex = 1.01;
451 entity->scaley = 1.01;
452 entity->scalez = 1.01;
453 entity->flags[PASSABLE] = true;
454 entity->flags[NOUPDATE] = true;
455 entity->flags[INVISIBLE] = true;
456 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
457 entity->focalx = limbs[AUTOMATON][9][0]; // 0
458 entity->focaly = limbs[AUTOMATON][9][1]; // 0
459 entity->focalz = limbs[AUTOMATON][9][2]; // -2
460 entity->behavior = &actAutomatonLimb;
461 entity->parent = my->getUID();
462 node = list_AddNodeLast(&my->children);
463 node->element = entity;
464 node->deconstructor = &emptyDeconstructor;
465 node->size = sizeof(Entity*);
466 my->bodyparts.push_back(entity);
467
468 // mask
469 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
470 entity->sizex = 4;
471 entity->sizey = 4;
472 entity->skill[2] = my->getUID();
473 entity->flags[PASSABLE] = true;
474 entity->flags[NOUPDATE] = true;
475 entity->flags[INVISIBLE] = true;
476 //entity->flags[USERFLAG2] = my->flags[USERFLAG2];
477 entity->focalx = limbs[AUTOMATON][10][0]; // 0
478 entity->focaly = limbs[AUTOMATON][10][1]; // 0
479 entity->focalz = limbs[AUTOMATON][10][2]; // .5
480 entity->behavior = &actAutomatonLimb;
481 entity->parent = my->getUID();
482 node = list_AddNodeLast(&my->children);
483 node->element = entity;
484 node->deconstructor = &emptyDeconstructor;
485 node->size = sizeof(Entity*);
486 my->bodyparts.push_back(entity);
487
488 if ( multiplayer == CLIENT || MONSTER_INIT )
489 {
490 return;
491 }
492 }
493
actAutomatonLimb(Entity * my)494 void actAutomatonLimb(Entity* my)
495 {
496 my->actMonsterLimb(true);
497 }
498
automatonDie(Entity * my)499 void automatonDie(Entity* my)
500 {
501 my->removeMonsterDeathNodes();
502
503 int c;
504 for ( c = 0; c < 6; c++ )
505 {
506 Entity* entity = spawnGib(my);
507 if ( entity )
508 {
509 switch ( c )
510 {
511 case 0:
512 entity->sprite = 467;
513 break;
514 case 1:
515 entity->sprite = 468;
516 break;
517 case 2:
518 entity->sprite = 469;
519 break;
520 case 3:
521 entity->sprite = 470;
522 break;
523 case 4:
524 entity->sprite = 471;
525 break;
526 case 5:
527 entity->sprite = 472;
528 break;
529 }
530 serverSpawnGibForClient(entity);
531 }
532 }
533 playSoundEntity(my, 260 + rand() % 2, 128);
534 list_RemoveNode(my->mynode);
535 return;
536 }
537
538 #define AUTOMATONWALKSPEED .13
539
automatonMoveBodyparts(Entity * my,Stat * myStats,double dist)540 void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist)
541 {
542 node_t* node;
543 Entity* entity = NULL, *entity2 = NULL;
544 Entity* rightbody = NULL;
545 Entity* weaponarm = NULL;
546 int bodypart;
547 bool wearingring = false;
548
549 // set invisibility //TODO: use isInvisible()?
550 if ( multiplayer != CLIENT )
551 {
552 if ( myStats->ring != NULL )
553 if ( myStats->ring->type == RING_INVISIBILITY )
554 {
555 wearingring = true;
556 }
557 if ( myStats->cloak != NULL )
558 if ( myStats->cloak->type == CLOAK_INVISIBILITY )
559 {
560 wearingring = true;
561 }
562 if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true )
563 {
564 my->flags[INVISIBLE] = true;
565 my->flags[BLOCKSIGHT] = false;
566 bodypart = 0;
567 for (node = my->children.first; node != NULL; node = node->next)
568 {
569 if ( bodypart < LIMB_HUMANOID_TORSO )
570 {
571 bodypart++;
572 continue;
573 }
574 if ( bodypart >= LIMB_HUMANOID_WEAPON )
575 {
576 break;
577 }
578 entity = (Entity*)node->element;
579 if ( !entity->flags[INVISIBLE] )
580 {
581 entity->flags[INVISIBLE] = true;
582 serverUpdateEntityBodypart(my, bodypart);
583 }
584 bodypart++;
585 }
586 }
587 else
588 {
589 my->flags[INVISIBLE] = false;
590 my->flags[BLOCKSIGHT] = true;
591 bodypart = 0;
592 for (node = my->children.first; node != NULL; node = node->next)
593 {
594 if ( bodypart < LIMB_HUMANOID_TORSO )
595 {
596 bodypart++;
597 continue;
598 }
599 if ( bodypart >= LIMB_HUMANOID_WEAPON )
600 {
601 break;
602 }
603 entity = (Entity*)node->element;
604 if ( entity->flags[INVISIBLE] )
605 {
606 entity->flags[INVISIBLE] = false;
607 serverUpdateEntityBodypart(my, bodypart);
608 serverUpdateEntityFlag(my, INVISIBLE);
609 }
610 bodypart++;
611 }
612 }
613
614 // sleeping
615 if ( myStats->EFFECTS[EFF_ASLEEP]
616 && (my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN) )
617 {
618 my->z = 2;
619 my->pitch = PI / 4;
620 }
621 else
622 {
623 if ( my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN )
624 {
625 my->z = -.5;
626 my->pitch = 0;
627 if ( (myStats->HP < 25 && !myStats->EFFECTS[EFF_CONFUSED])
628 || (myStats->HP < 50 && !strncmp(myStats->name, "corrupted automaton", strlen("corrupted automaton")))
629 )
630 {
631 // threshold for boom boom
632 if ( rand() % 4 > 0 ) // 3/4
633 {
634 my->monsterSpecialState = AUTOMATON_MALFUNCTION_START;
635 my->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_AUTOMATON_MALFUNCTION;
636 serverUpdateEntitySkill(my, 33);
637
638 myStats->EFFECTS[EFF_PARALYZED] = true;
639 myStats->EFFECTS_TIMERS[EFF_PARALYZED] = -1;
640 }
641 else
642 {
643 myStats->EFFECTS[EFF_CONFUSED] = true;
644 myStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1;
645 myStats->EFFECTS[EFF_PARALYZED] = true;
646 myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 25;
647 playSoundEntity(my, 263, 128);
648 spawnMagicEffectParticles(my->x, my->y, my->z, 170);
649 }
650 }
651 }
652 }
653 }
654
655 if ( my->monsterSpecialState == AUTOMATON_MALFUNCTION_START || my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
656 {
657 if ( my->monsterSpecialState == AUTOMATON_MALFUNCTION_START )
658 {
659 my->monsterSpecialState = AUTOMATON_MALFUNCTION_RUN;
660 createParticleExplosionCharge(my, 174, 100, 0.1);
661 }
662 if ( multiplayer != CLIENT )
663 {
664 if ( my->monsterSpecialTimer <= 0 )
665 {
666 my->attack(MONSTER_POSE_AUTOMATON_MALFUNCTION, 0, my);
667 spawnExplosion(my->x, my->y, my->z);
668 my->modHP(-1000);
669 }
670 }
671 }
672
673 if ( ticks % (2 * TICKS_PER_SECOND) == 0 && rand() % 5 > 0 )
674 {
675 playSoundEntityLocal(my, 259, 16);
676 }
677
678 Entity* shieldarm = nullptr;
679 Entity* helmet = nullptr;
680 //Move bodyparts
681 for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++)
682 {
683 if ( bodypart < LIMB_HUMANOID_TORSO )
684 {
685 if ( multiplayer != CLIENT )
686 {
687 if ( bodypart == 0 && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
688 {
689 --my->monsterSpecialTimer;
690 if ( my->monsterSpecialTimer == 100 )
691 {
692 playSoundEntity(my, 321, 128);
693 }
694 if ( my->monsterSpecialTimer < 80 )
695 {
696 my->z += 0.02;
697 limbAnimateToLimit(my, ANIMATE_PITCH, 0.1, 0.7, true, 0.1);
698 }
699 else
700 {
701 limbAnimateToLimit(my, ANIMATE_PITCH, 0.01, PI / 5, false, 0.0);
702 }
703 }
704 }
705 continue;
706 }
707 entity = (Entity*)node->element;
708 entity->x = my->x;
709 entity->y = my->y;
710 if ( my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN )
711 {
712 entity->z = my->z;
713 }
714 else
715 {
716 if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTLEG )
717 {
718 entity->z = -.5;
719 }
720 else
721 {
722 entity->z = my->z;
723 }
724 }
725
726 if ( (MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP1 || MONSTER_ATTACK == MONSTER_POSE_SPECIAL_WINDUP1) && bodypart == LIMB_HUMANOID_RIGHTARM )
727 {
728 // don't let the creatures's yaw move the casting arm
729 }
730 else
731 {
732 entity->yaw = my->yaw;
733 }
734
735 if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
736 {
737 if ( bodypart == LIMB_HUMANOID_LEFTARM && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
738 {
739 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 13 * PI / 8, true, 0.1);
740 }
741 else
742 {
743 my->humanoidAnimateWalk(entity, node, bodypart, AUTOMATONWALKSPEED, dist, 0.1);
744 }
745 }
746 else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM || bodypart == LIMB_HUMANOID_CLOAK )
747 {
748 // left leg, right arm, cloak.
749 if ( bodypart == LIMB_HUMANOID_RIGHTARM )
750 {
751 weaponarm = entity;
752 if ( MONSTER_ATTACK > 0 )
753 {
754 if ( my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1 )
755 {
756 Entity* rightbody = nullptr;
757 // set rightbody to left leg.
758 node_t* rightbodyNode = list_Node(&my->children, LIMB_HUMANOID_LEFTLEG);
759 if ( rightbodyNode )
760 {
761 rightbody = (Entity*)rightbodyNode->element;
762 }
763 else
764 {
765 return;
766 }
767
768 // magic wiggle hands
769 if ( my->monsterAttackTime == 0 )
770 {
771 // init rotations
772 my->monsterArmbended = 0;
773 my->monsterWeaponYaw = 0;
774 weaponarm->roll = 0;
775 weaponarm->pitch = 0;
776 weaponarm->yaw = my->yaw;
777 weaponarm->skill[1] = 0;
778 // casting particles
779 createParticleDot(my);
780 // play casting sound
781 playSoundEntityLocal(my, 170, 32);
782 if ( multiplayer != CLIENT )
783 {
784 myStats->EFFECTS[EFF_PARALYZED] = true;
785 myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 30;
786 }
787 }
788
789 double animationYawSetpoint = 0.f;
790 double animationYawEndpoint = 0.f;
791 double armSwingRate = 0.f;
792 double animationPitchSetpoint = 0.f;
793 double animationPitchEndpoint = 0.f;
794
795 switch ( my->monsterSpellAnimation )
796 {
797 case MONSTER_SPELLCAST_HUMANOID:
798 animationYawSetpoint = normaliseAngle2PI(my->yaw + 1 * PI / 8);
799 animationYawEndpoint = normaliseAngle2PI(my->yaw - 1 * PI / 8);
800 animationPitchSetpoint = 1 * PI / 8;
801 animationPitchEndpoint = 15 * PI / 8;
802 armSwingRate = 0.15;
803 break;
804 default:
805 break;
806 }
807
808 if ( weaponarm->skill[1] == 0 )
809 {
810 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, armSwingRate, animationPitchSetpoint, false, 0.0) )
811 {
812 if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, armSwingRate, animationYawSetpoint, false, 0.0) )
813 {
814 weaponarm->skill[1] = 1;
815 }
816 }
817 }
818 else
819 {
820 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -armSwingRate, animationPitchEndpoint, false, 0.0) )
821 {
822 if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, -armSwingRate, animationYawEndpoint, false, 0.0) )
823 {
824 weaponarm->skill[1] = 0;
825 }
826 }
827 }
828
829 if ( my->monsterAttackTime >= 3 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
830 {
831 weaponarm->skill[0] = rightbody->skill[0];
832 weaponarm->pitch = rightbody->pitch;
833 my->monsterArmbended = 0;
834 my->monsterAttack = 0;
835 if ( multiplayer != CLIENT )
836 {
837 spawnMagicEffectParticles(my->x, my->y, my->z / 2, 174);
838 my->monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_COMPLETE;
839 }
840 }
841 }
842 else
843 {
844 my->handleWeaponArmAttack(entity);
845 }
846 }
847 }
848 else if ( bodypart == LIMB_HUMANOID_CLOAK )
849 {
850 entity->pitch = entity->fskill[0];
851 }
852
853 if ( bodypart == LIMB_HUMANOID_RIGHTARM && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
854 {
855 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 13 * PI / 8, true, 0.1);
856 }
857 else
858 {
859 my->humanoidAnimateWalk(entity, node, bodypart, AUTOMATONWALKSPEED, dist, 0.1);
860 }
861
862 if ( bodypart == LIMB_HUMANOID_CLOAK )
863 {
864 entity->fskill[0] = entity->pitch;
865 entity->roll = my->roll - fabs(entity->pitch) / 2;
866 entity->pitch = 0;
867 }
868 }
869 switch ( bodypart )
870 {
871 // torso
872 case LIMB_HUMANOID_TORSO:
873 if ( multiplayer != CLIENT )
874 {
875 if ( myStats->breastplate == nullptr )
876 {
877 entity->sprite = 468;
878 }
879 else
880 {
881 entity->sprite = itemModel(myStats->breastplate);
882 entity->scalex = 1;
883 // shrink the width of the breastplate
884 entity->scaley = 0.8;
885 }
886 if ( multiplayer == SERVER )
887 {
888 // update sprites for clients
889 if ( entity->skill[10] != entity->sprite )
890 {
891 entity->skill[10] = entity->sprite;
892 serverUpdateEntityBodypart(my, bodypart);
893 }
894 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
895 {
896 serverUpdateEntityBodypart(my, bodypart);
897 }
898 }
899 }
900 else if ( multiplayer == CLIENT )
901 {
902 if ( entity->sprite != 468 )
903 {
904 entity->scalex = 1;
905 // shrink the width of the breastplate
906 entity->scaley = 0.8;
907 }
908 else
909 {
910 entity->scalex = 1;
911 entity->scaley = 1;
912 }
913 }
914 my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_TORSO);
915 break;
916 // right leg
917 case LIMB_HUMANOID_RIGHTLEG:
918 if ( multiplayer != CLIENT )
919 {
920 if ( myStats->shoes == nullptr )
921 {
922 entity->sprite = 474;
923 }
924 else
925 {
926 my->setBootSprite(entity, SPRITE_BOOT_RIGHT_OFFSET);
927 }
928 if ( multiplayer == SERVER )
929 {
930 // update sprites for clients
931 if ( entity->skill[10] != entity->sprite )
932 {
933 entity->skill[10] = entity->sprite;
934 serverUpdateEntityBodypart(my, bodypart);
935 }
936 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
937 {
938 serverUpdateEntityBodypart(my, bodypart);
939 }
940 }
941 }
942 my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_RIGHTLEG);
943 break;
944 // left leg
945 case LIMB_HUMANOID_LEFTLEG:
946 if ( multiplayer != CLIENT )
947 {
948 if ( myStats->shoes == nullptr )
949 {
950 entity->sprite = 473;
951 }
952 else
953 {
954 my->setBootSprite(entity, SPRITE_BOOT_LEFT_OFFSET);
955 }
956 if ( multiplayer == SERVER )
957 {
958 // update sprites for clients
959 if ( entity->skill[10] != entity->sprite )
960 {
961 entity->skill[10] = entity->sprite;
962 serverUpdateEntityBodypart(my, bodypart);
963 }
964 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
965 {
966 serverUpdateEntityBodypart(my, bodypart);
967 }
968 }
969 }
970 my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_LEFTLEG);
971 break;
972 // right arm
973 case LIMB_HUMANOID_RIGHTARM:
974 {
975 node_t* weaponNode = list_Node(&my->children, 7);
976 if ( weaponNode )
977 {
978 Entity* weapon = (Entity*)weaponNode->element;
979 if ( MONSTER_ARMBENDED || (weapon->flags[INVISIBLE] && my->monsterAttack == 0) )
980 {
981 // if weapon invisible and I'm not attacking, relax arm.
982 entity->focalx = limbs[AUTOMATON][4][0]; // 0
983 entity->focaly = limbs[AUTOMATON][4][1]; // 0
984 entity->focalz = limbs[AUTOMATON][4][2]; // 2
985 entity->sprite = 471;
986 }
987 else
988 {
989 // else flex arm.
990 entity->focalx = limbs[AUTOMATON][4][0] + 1.5; // 1
991 entity->focaly = limbs[AUTOMATON][4][1] + 0.25; // 0
992 entity->focalz = limbs[AUTOMATON][4][2] - 1; // 1
993 entity->sprite = 472;
994 }
995 }
996 my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_RIGHTARM);
997 entity->yaw += MONSTER_WEAPONYAW;
998 break;
999 // left arm
1000 }
1001 case LIMB_HUMANOID_LEFTARM:
1002 {
1003 shieldarm = entity;
1004 node_t* shieldNode = list_Node(&my->children, 8);
1005 if ( shieldNode )
1006 {
1007 Entity* shield = (Entity*)shieldNode->element;
1008 if ( shield->flags[INVISIBLE] )
1009 {
1010 // if shield invisible, relax arm.
1011 entity->sprite = 469;
1012 entity->focalx = limbs[AUTOMATON][5][0]; // 0
1013 entity->focaly = limbs[AUTOMATON][5][1]; // 0
1014 entity->focalz = limbs[AUTOMATON][5][2]; // 2
1015 }
1016 else
1017 {
1018 // else flex arm.
1019 entity->sprite = 470;
1020 entity->focalx = limbs[AUTOMATON][5][0] + 1.5; // 1
1021 entity->focaly = limbs[AUTOMATON][5][1] - 0.25; // 0
1022 entity->focalz = limbs[AUTOMATON][5][2] - 1; // 1
1023 }
1024 }
1025 my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_LEFTARM);
1026 if ( my->monsterDefend && my->monsterAttack == 0 )
1027 {
1028 MONSTER_SHIELDYAW = PI / 5;
1029 }
1030 else
1031 {
1032 MONSTER_SHIELDYAW = 0;
1033 }
1034 entity->yaw += MONSTER_SHIELDYAW;
1035 break;
1036 }
1037 // weapon
1038 case LIMB_HUMANOID_WEAPON:
1039 if ( multiplayer != CLIENT )
1040 {
1041 if ( myStats->weapon == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1042 {
1043 entity->flags[INVISIBLE] = true;
1044 }
1045 else
1046 {
1047 entity->sprite = itemModel(myStats->weapon);
1048 if ( itemCategory(myStats->weapon) == SPELLBOOK )
1049 {
1050 entity->flags[INVISIBLE] = true;
1051 }
1052 else
1053 {
1054 entity->flags[INVISIBLE] = false;
1055 }
1056 }
1057 if ( multiplayer == SERVER )
1058 {
1059 // update sprites for clients
1060 if ( entity->skill[10] != entity->sprite )
1061 {
1062 entity->skill[10] = entity->sprite;
1063 serverUpdateEntityBodypart(my, bodypart);
1064 }
1065 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1066 {
1067 entity->skill[11] = entity->flags[INVISIBLE];
1068 serverUpdateEntityBodypart(my, bodypart);
1069 }
1070 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1071 {
1072 serverUpdateEntityBodypart(my, bodypart);
1073 }
1074 }
1075 }
1076 else
1077 {
1078 if ( entity->sprite <= 0 )
1079 {
1080 entity->flags[INVISIBLE] = true;
1081 }
1082 }
1083 if ( weaponarm != nullptr )
1084 {
1085 my->handleHumanoidWeaponLimb(entity, weaponarm);
1086 }
1087 break;
1088 // shield
1089 case LIMB_HUMANOID_SHIELD:
1090 if ( multiplayer != CLIENT )
1091 {
1092 if ( myStats->shield == NULL )
1093 {
1094 entity->flags[INVISIBLE] = true;
1095 entity->sprite = 0;
1096 }
1097 else
1098 {
1099 entity->flags[INVISIBLE] = false;
1100 entity->sprite = itemModel(myStats->shield);
1101 if ( itemTypeIsQuiver(myStats->shield->type) )
1102 {
1103 entity->handleQuiverThirdPersonModel(*myStats);
1104 }
1105 }
1106 if ( myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1107 {
1108 entity->flags[INVISIBLE] = true;
1109 }
1110 if ( multiplayer == SERVER )
1111 {
1112 // update sprites for clients
1113 if ( entity->skill[10] != entity->sprite )
1114 {
1115 entity->skill[10] = entity->sprite;
1116 serverUpdateEntityBodypart(my, bodypart);
1117 }
1118 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1119 {
1120 entity->skill[11] = entity->flags[INVISIBLE];
1121 serverUpdateEntityBodypart(my, bodypart);
1122 }
1123 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1124 {
1125 serverUpdateEntityBodypart(my, bodypart);
1126 }
1127 }
1128 }
1129 else
1130 {
1131 if ( entity->sprite <= 0 )
1132 {
1133 entity->flags[INVISIBLE] = true;
1134 }
1135 }
1136 my->handleHumanoidShieldLimb(entity, shieldarm);
1137 break;
1138 // cloak
1139 case LIMB_HUMANOID_CLOAK:
1140 if ( multiplayer != CLIENT )
1141 {
1142 if ( myStats->cloak == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1143 {
1144 entity->flags[INVISIBLE] = true;
1145 }
1146 else
1147 {
1148 entity->flags[INVISIBLE] = false;
1149 entity->sprite = itemModel(myStats->cloak);
1150 }
1151 if ( multiplayer == SERVER )
1152 {
1153 // update sprites for clients
1154 if ( entity->skill[10] != entity->sprite )
1155 {
1156 entity->skill[10] = entity->sprite;
1157 serverUpdateEntityBodypart(my, bodypart);
1158 }
1159 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1160 {
1161 entity->skill[11] = entity->flags[INVISIBLE];
1162 serverUpdateEntityBodypart(my, bodypart);
1163 }
1164 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1165 {
1166 serverUpdateEntityBodypart(my, bodypart);
1167 }
1168 }
1169 }
1170 else
1171 {
1172 if ( entity->sprite <= 0 )
1173 {
1174 entity->flags[INVISIBLE] = true;
1175 }
1176 }
1177 entity->x -= cos(my->yaw);
1178 entity->y -= sin(my->yaw);
1179 entity->yaw += PI / 2;
1180 break;
1181 // helm
1182 case LIMB_HUMANOID_HELMET:
1183 helmet = entity;
1184 entity->focalx = limbs[AUTOMATON][9][0]; // 0
1185 entity->focaly = limbs[AUTOMATON][9][1]; // 0
1186 entity->focalz = limbs[AUTOMATON][9][2]; // -2
1187 entity->pitch = my->pitch;
1188 entity->roll = 0;
1189 if ( multiplayer != CLIENT )
1190 {
1191 entity->sprite = itemModel(myStats->helmet);
1192 if ( myStats->helmet == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1193 {
1194 entity->flags[INVISIBLE] = true;
1195 }
1196 else
1197 {
1198 entity->flags[INVISIBLE] = false;
1199 }
1200 if ( multiplayer == SERVER )
1201 {
1202 // update sprites for clients
1203 if ( entity->skill[10] != entity->sprite )
1204 {
1205 entity->skill[10] = entity->sprite;
1206 serverUpdateEntityBodypart(my, bodypart);
1207 }
1208 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1209 {
1210 entity->skill[11] = entity->flags[INVISIBLE];
1211 serverUpdateEntityBodypart(my, bodypart);
1212 }
1213 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1214 {
1215 serverUpdateEntityBodypart(my, bodypart);
1216 }
1217 }
1218 }
1219 else
1220 {
1221 if ( entity->sprite <= 0 )
1222 {
1223 entity->flags[INVISIBLE] = true;
1224 }
1225 }
1226 my->setHelmetLimbOffset(entity);
1227 break;
1228 // mask
1229 case LIMB_HUMANOID_MASK:
1230 entity->focalx = limbs[AUTOMATON][10][0]; // 0
1231 entity->focaly = limbs[AUTOMATON][10][1]; // 0
1232 entity->focalz = limbs[AUTOMATON][10][2]; // .5
1233 entity->pitch = my->pitch;
1234 entity->roll = PI / 2;
1235 if ( multiplayer != CLIENT )
1236 {
1237 bool hasSteelHelm = false;
1238 if ( myStats->helmet )
1239 {
1240 if ( myStats->helmet->type == STEEL_HELM
1241 || myStats->helmet->type == CRYSTAL_HELM
1242 || myStats->helmet->type == ARTIFACT_HELM )
1243 {
1244 hasSteelHelm = true;
1245 }
1246 }
1247 if ( myStats->mask == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring || hasSteelHelm ) //TODO: isInvisible()?
1248 {
1249 entity->flags[INVISIBLE] = true;
1250 }
1251 else
1252 {
1253 entity->flags[INVISIBLE] = false;
1254 }
1255 if ( myStats->mask != NULL )
1256 {
1257 if ( myStats->mask->type == TOOL_GLASSES )
1258 {
1259 entity->sprite = 165; // GlassesWorn.vox
1260 }
1261 else
1262 {
1263 entity->sprite = itemModel(myStats->mask);
1264 }
1265 }
1266 if ( multiplayer == SERVER )
1267 {
1268 // update sprites for clients
1269 if ( entity->skill[10] != entity->sprite )
1270 {
1271 entity->skill[10] = entity->sprite;
1272 serverUpdateEntityBodypart(my, bodypart);
1273 }
1274 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1275 {
1276 entity->skill[11] = entity->flags[INVISIBLE];
1277 serverUpdateEntityBodypart(my, bodypart);
1278 }
1279 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1280 {
1281 serverUpdateEntityBodypart(my, bodypart);
1282 }
1283 }
1284 }
1285 else
1286 {
1287 if ( entity->sprite <= 0 )
1288 {
1289 entity->flags[INVISIBLE] = true;
1290 }
1291 }
1292 if ( entity->sprite != 165 )
1293 {
1294 if ( entity->sprite == items[MASK_SHAMAN].index )
1295 {
1296 entity->roll = 0;
1297 my->setHelmetLimbOffset(entity);
1298 my->setHelmetLimbOffsetWithMask(helmet, entity);
1299 }
1300 else
1301 {
1302 entity->focalx = limbs[AUTOMATON][10][0] + .35; // .35
1303 entity->focaly = limbs[AUTOMATON][10][1] - 2; // -2
1304 entity->focalz = limbs[AUTOMATON][10][2]; // .5
1305 }
1306 }
1307 else
1308 {
1309 entity->focalx = limbs[AUTOMATON][10][0] + .25; // .25
1310 entity->focaly = limbs[AUTOMATON][10][1] - 2.25; // -2.25
1311 entity->focalz = limbs[AUTOMATON][10][2]; // .5
1312 }
1313 break;
1314 }
1315 }
1316 // rotate shield a bit
1317 node_t* shieldNode = list_Node(&my->children, LIMB_HUMANOID_SHIELD);
1318 if ( shieldNode )
1319 {
1320 Entity* shieldEntity = (Entity*)shieldNode->element;
1321 if ( shieldEntity->sprite != items[TOOL_TORCH].index && shieldEntity->sprite != items[TOOL_LANTERN].index && shieldEntity->sprite != items[TOOL_CRYSTALSHARD].index )
1322 {
1323 shieldEntity->yaw -= PI / 6;
1324 }
1325 }
1326 if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
1327 {
1328 MONSTER_ATTACKTIME++;
1329 }
1330 else if ( MONSTER_ATTACK == 0 )
1331 {
1332 MONSTER_ATTACKTIME = 0;
1333 }
1334 else
1335 {
1336 // do nothing, don't reset attacktime or increment it.
1337 }
1338 }
1339
automatonCanWieldItem(const Item & item) const1340 bool Entity::automatonCanWieldItem(const Item& item) const
1341 {
1342 Stat* myStats = getStats();
1343 if ( !myStats )
1344 {
1345 return false;
1346 }
1347
1348 if ( monsterAllyIndex >= 0 && (monsterAllyClass != ALLY_CLASS_MIXED || item.interactNPCUid == getUID()) )
1349 {
1350 return monsterAllyEquipmentInClass(item);
1351 }
1352
1353 switch ( itemCategory(&item) )
1354 {
1355 case WEAPON:
1356 return true;
1357 case ARMOR:
1358 {
1359 int equipType = checkEquipType(&item);
1360 if ( equipType == TYPE_HAT )
1361 {
1362 return false; //No can wear hats, beep boop
1363 }
1364 return true;
1365 }
1366 case THROWN:
1367 return true;
1368 case TOOL:
1369 if ( itemTypeIsQuiver(item.type) )
1370 {
1371 return true;
1372 }
1373 break;
1374 case MAGICSTAFF:
1375 return true;
1376 default:
1377 return false;
1378 }
1379
1380 return false;
1381 }
1382
1383
automatonRecycleItem()1384 void Entity::automatonRecycleItem()
1385 {
1386 Stat* myStats = getStats();
1387 if ( !myStats )
1388 {
1389 return;
1390 }
1391
1392 if ( this->monsterSpecialTimer > 0 && !(this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_COMPLETE) )
1393 {
1394 // if we're on cooldown, skip checking
1395 // also we need the callback from my->attack() to set monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_COMPLETE once the animation completes.
1396 return;
1397 }
1398
1399 node_t* node = nullptr;
1400 node_t* nextnode = nullptr;
1401 int numItemsHeld = list_Size(&myStats->inventory);
1402 //messagePlayer(0, "Numitems: %d", numItemsHeld);
1403 if ( numItemsHeld < 2 )
1404 {
1405 return;
1406 }
1407
1408 if ( this->monsterSpecialTimer == 0 && this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_WAITING )
1409 {
1410 // put the skill on cooldown.
1411 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_AUTOMATON_RECYCLE;
1412 }
1413
1414 int i = 0;
1415 int itemIndex = 0;
1416 int chances[10] = { -1 }; // max 10 items
1417 int matches = 0;
1418
1419 // search for valid recyclable items, set chance for valid index.
1420 for ( node = myStats->inventory.first; node != nullptr; node = nextnode )
1421 {
1422 if ( matches >= 10 )
1423 {
1424 break;
1425 }
1426 nextnode = node->next;
1427 Item* item = (Item*)node->element;
1428 if ( item != nullptr )
1429 {
1430 if ( (itemCategory(item) == WEAPON || itemCategory(item) == THROWN || itemCategory(item) == ARMOR)
1431 && item->status > BROKEN )
1432 {
1433 chances[matches] = itemIndex;
1434 ++matches;
1435 }
1436 }
1437 ++itemIndex;
1438 }
1439
1440 //messagePlayer(0, "Found %d items", matches);
1441
1442 if ( matches < 2 ) // not enough valid items found.
1443 {
1444 this->monsterSpecialTimer = 250; // reset cooldown to 5 seconds to check again quicker.
1445 return;
1446 }
1447
1448 if ( this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_WAITING )
1449 {
1450 // this is the first run of the check, we'll execute the casting animation and wait for this to be set to 1 when it ends.
1451 this->attack(MONSTER_POSE_SPECIAL_WINDUP1, 0, nullptr);
1452 return;
1453 }
1454
1455 this->monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_WAITING; // reset my special state after the previous lines.
1456 int pickItem1 = rand() % matches; // pick random valid item index in inventory
1457 int pickItem2 = rand() % matches;
1458 while ( pickItem2 == pickItem1 )
1459 {
1460 pickItem2 = rand() % matches; // make sure index 2 is unique
1461 }
1462
1463 itemIndex = 0;
1464 Item* item1 = nullptr;
1465 Item* item2 = nullptr;
1466
1467 // search again for the 2 indexes, store the items and nodes.
1468 for ( node = myStats->inventory.first; node != nullptr; node = nextnode )
1469 {
1470 nextnode = node->next;
1471 if ( chances[pickItem1] == itemIndex )
1472 {
1473 item1 = (Item*)node->element;
1474 }
1475 else if ( chances[pickItem2] == itemIndex )
1476 {
1477 item2 = (Item*)node->element;
1478 }
1479 ++itemIndex;
1480 }
1481
1482 if ( item1 == nullptr || item2 == nullptr )
1483 {
1484 return;
1485 }
1486
1487 //messagePlayer(0, "made it past");
1488
1489 int maxGoldValue = ((items[item1->type].value + items[item2->type].value) * 2) / 3;
1490 if ( rand() % 2 == 0 )
1491 {
1492 maxGoldValue = ((items[item1->type].value + items[item2->type].value) * 1) / 2;
1493 }
1494 int minGoldValue = ((items[item1->type].value + items[item2->type].value) * 1) / 3;
1495 ItemType type;
1496 // generate a weapon/armor piece and add it into the inventory.
1497 switch ( rand() % 10 )
1498 {
1499 case 0:
1500 case 1:
1501 case 2:
1502 type = itemTypeWithinGoldValue(WEAPON, minGoldValue, maxGoldValue);
1503 break;
1504 case 3:
1505 case 4:
1506 case 5:
1507 case 6:
1508 case 7:
1509 case 8:
1510 type = itemTypeWithinGoldValue(ARMOR, minGoldValue, maxGoldValue);
1511 break;
1512 case 9:
1513 type = itemTypeWithinGoldValue(THROWN, minGoldValue, maxGoldValue);
1514 break;
1515 default:
1516 break;
1517 }
1518
1519 if ( type != GEM_ROCK ) // found an item in category
1520 {
1521 Item* item = nullptr;
1522 // recycle item1 or item2, reduce durability.
1523 if ( rand() % 2 == 0 )
1524 {
1525 item = newItem(type, item1->status, item1->beatitude, 1, rand(), item1->identified, &myStats->inventory);
1526 item1->status = static_cast<Status>(std::max(0, item1->status - 2));
1527 }
1528 else
1529 {
1530 item = newItem(type, item2->status, item2->beatitude, 1, rand(), item2->identified, &myStats->inventory);
1531 item2->status = static_cast<Status>(std::max(0, item2->status - 2));
1532 }
1533 // drop newly created item. To pickup if possible or leave behind if overburdened.
1534 dropItemMonster(item, this, myStats);
1535 //messagePlayer(0, "Generated %d!", type);
1536 if ( myStats->leader_uid != 0 )
1537 {
1538 for ( int c = 0; c < MAXPLAYERS; ++c )
1539 {
1540 if ( players[c] && players[c]->entity )
1541 {
1542 Uint32 playerUid = players[c]->entity->getUID();
1543 if ( playerUid == myStats->leader_uid
1544 && (item1->ownerUid == playerUid || item2->ownerUid == playerUid) )
1545 {
1546 steamAchievementClient(c, "BARONY_ACH_RECYCLER");
1547 break;
1548 }
1549 }
1550 }
1551 }
1552 //messagePlayer(0, "%d, %d", item1->ownerUid, item2->ownerUid);
1553 }
1554
1555 return;
1556 }
1557