1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_insectoid.cpp
5 Desc: implements all of the insectoid 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
initInsectoid(Entity * my,Stat * myStats)24 void initInsectoid(Entity* my, Stat* myStats)
25 {
26 int c;
27 node_t* node;
28
29 //Sprite 455 = Insectoid head model
30 my->initMonster(455);
31
32 if ( multiplayer != CLIENT )
33 {
34 MONSTER_SPOTSND = 291;
35 MONSTER_SPOTVAR = 4;
36 MONSTER_IDLESND = 285;
37 MONSTER_IDLEVAR = 2;
38 }
39 if ( multiplayer != CLIENT && !MONSTER_INIT )
40 {
41 if ( myStats != nullptr )
42 {
43 if ( !myStats->leader_uid )
44 {
45 myStats->leader_uid = 0;
46 }
47
48 bool lesserMonster = false;
49 if ( !strncmp(myStats->name, "lesser insectoid", strlen("lesser insectoid")) )
50 {
51 lesserMonster = true;
52 myStats->HP = 110;
53 myStats->MAXHP = myStats->HP;
54 myStats->RANDOM_MAXHP = 10;
55 myStats->RANDOM_HP = myStats->RANDOM_MAXHP;
56 myStats->OLDHP = myStats->HP;
57 myStats->STR = 8;
58 myStats->RANDOM_STR = 0;
59 myStats->DEX = 6;
60 myStats->CON = 7;
61 myStats->INT = -2;
62 myStats->PER = 5;
63 myStats->CHR = 5;
64 myStats->EXP = 0;
65 myStats->LVL = 10;
66 }
67 // apply random stat increases if set in stat_shared.cpp or editor
68 setRandomMonsterStats(myStats);
69
70 // generate 6 items max, less if there are any forced items from boss variants
71 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
72
73 // boss variants
74 if ( rand() % 50 || my->flags[USERFLAG2] || myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] )
75 {
76 }
77 else
78 {
79 /*myStats->HP = 120;
80 myStats->MAXHP = 120;
81 myStats->OLDHP = myStats->HP;
82 strcpy(myStats->name, "The Potato King");
83 myStats->weapon = newItem(ARTIFACT_MACE, EXCELLENT, 1, 1, rand(), true, nullptr);
84 myStats->helmet = newItem(HAT_JESTER, SERVICABLE, 3 + rand() % 3, 1, 0, false, nullptr);
85
86 int c;
87 for ( c = 0; c < 3; c++ )
88 {
89 Entity* entity = summonMonster(GOBLIN, my->x, my->y);
90 if ( entity )
91 {
92 entity->parent = my->getUID();
93 }
94 }*/
95 }
96
97 // random effects
98 if ( rand() % 8 == 0 )
99 {
100 myStats->EFFECTS[EFF_ASLEEP] = true;
101 myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 1800 + rand() % 1800;
102 }
103
104 // generates equipment and weapons if available from editor
105 createMonsterEquipment(myStats);
106
107 // create any custom inventory items from editor if available
108 createCustomInventory(myStats, customItemsToGenerate);
109
110 // count if any custom inventory items from editor
111 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
112
113 // count any inventory items set to default in edtior
114 int defaultItems = countDefaultItems(myStats);
115
116 my->setHardcoreStats(*myStats);
117
118 // always give special spell to insectoid, undroppable.
119 newItem(SPELLBOOK_ACID_SPRAY, DECREPIT, 0, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, &myStats->inventory);
120
121 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
122 switch ( defaultItems )
123 {
124 case 6:
125 case 5:
126 case 4:
127 case 3:
128 if ( !lesserMonster && rand() % 20 == 0 )
129 {
130 newItem(SPELLBOOK_ACID_SPRAY, SERVICABLE, -1 + rand() % 3, 1, rand(), false, &myStats->inventory);
131 }
132 case 2:
133 /*if ( rand() % 2 == 0 )
134 {
135 newItem(SHORTBOW, SERVICABLE, -1 + rand() % 3, 1, rand(), false, &myStats->inventory);
136 }*/
137 case 1:
138 if ( lesserMonster )
139 {
140 newItem(IRON_DAGGER, WORN, 0, 0 + rand() % 2, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, &myStats->inventory);
141 }
142 else
143 {
144 newItem(IRON_DAGGER, WORN, 0, 2 + rand() % 4, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, &myStats->inventory);
145 }
146 break;
147 default:
148 break;
149 }
150
151 if ( lesserMonster )
152 {
153
154 //give weapon
155 if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
156 {
157 switch ( rand() % 10 )
158 {
159 case 0:
160 myStats->weapon = newItem(IRON_SPEAR, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
161 break;
162 case 1:
163 case 2:
164 case 3:
165 case 4:
166 myStats->weapon = newItem(SHORTBOW, SERVICABLE, -1 + rand() % 3, 1, rand(), false, nullptr);
167 break;
168 case 5:
169 if ( rand() % 2 )
170 {
171 myStats->weapon = newItem(LONGBOW, SERVICABLE, -1 + rand() % 3, 1, rand(), false, nullptr);
172 }
173 else
174 {
175 myStats->weapon = newItem(SHORTBOW, SERVICABLE, -1 + rand() % 3, 1, rand(), false, nullptr);
176 }
177 break;
178 case 6:
179 case 7:
180 myStats->weapon = newItem(STEEL_SWORD, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
181 break;
182 case 8:
183 case 9:
184 myStats->weapon = newItem(STEEL_HALBERD, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
185 break;
186 default:
187 break;
188 }
189 }
190
191 if ( myStats->shield == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 )
192 {
193 if ( myStats->weapon && isRangedWeapon(*myStats->weapon) )
194 {
195 my->monsterGenerateQuiverItem(myStats, lesserMonster);
196 }
197 else
198 {
199 // give shield
200 switch ( rand() % 10 )
201 {
202 case 0:
203 case 1:
204 case 2:
205 case 3:
206 case 4:
207 break;
208 case 5:
209 case 6:
210 myStats->shield = newItem(STEEL_SHIELD, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
211 break;
212 case 7:
213 case 8:
214 case 9:
215 myStats->shield = newItem(IRON_SHIELD, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
216 break;
217 default:
218 break;
219 }
220 }
221 }
222
223 // give cloak
224 if ( myStats->cloak == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 )
225 {
226 switch ( rand() % 10 )
227 {
228 case 0:
229 case 1:
230 case 2:
231 case 3:
232 case 4:
233 break;
234 case 5:
235 case 6:
236 case 7:
237 case 8:
238 case 9:
239 myStats->cloak = newItem(CLOAK, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
240 break;
241 }
242 }
243
244 // give booties
245 if ( myStats->shoes == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] == 1 )
246 {
247 switch ( rand() % 10 )
248 {
249 case 0:
250 case 1:
251 myStats->shoes = newItem(STEEL_BOOTS, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
252 break;
253 case 2:
254 case 3:
255 case 4:
256 case 5:
257 case 6:
258 myStats->shoes = newItem(IRON_BOOTS, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
259 break;
260 case 7:
261 case 8:
262 case 9:
263 break;
264 default:
265 break;
266
267 }
268 }
269 }
270 else
271 {
272
273 //give weapon
274 if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
275 {
276 switch ( rand() % 20 )
277 {
278 case 0:
279 case 1:
280 //10%
281 myStats->weapon = newItem(SHORTBOW, static_cast<Status>(SERVICABLE + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
282 break;
283 case 2:
284 case 3:
285 case 4:
286 //15%
287 myStats->weapon = newItem(LONGBOW, static_cast<Status>(SERVICABLE + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
288 break;
289 case 5:
290 case 6:
291 //10%
292 myStats->weapon = newItem(COMPOUND_BOW, static_cast<Status>(SERVICABLE + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
293 break;
294 case 7:
295 case 8:
296 //10%
297 myStats->weapon = newItem(STEEL_SWORD, static_cast<Status>(SERVICABLE + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
298 break;
299 case 9:
300 case 10:
301 case 11:
302 case 12:
303 case 13:
304 //25%
305 myStats->weapon = newItem(STEEL_HALBERD, static_cast<Status>(SERVICABLE + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
306 break;
307 case 14:
308 case 15:
309 case 16:
310 //15%
311 myStats->weapon = newItem(CRYSTAL_SWORD, static_cast<Status>(WORN + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
312 break;
313 case 17:
314 case 18:
315 case 19:
316 //15%
317 myStats->weapon = newItem(CRYSTAL_SPEAR, static_cast<Status>(WORN + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
318 break;
319 }
320 }
321
322 if ( myStats->shield == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 )
323 {
324 if ( myStats->weapon && isRangedWeapon(*myStats->weapon) )
325 {
326 my->monsterGenerateQuiverItem(myStats, lesserMonster);
327 }
328 else
329 {
330 // give shield
331 switch ( rand() % 20 )
332 {
333 case 0:
334 case 1:
335 case 2:
336 case 3:
337 case 4:
338 case 5:
339 case 6:
340 case 7:
341 myStats->shield = newItem(STEEL_SHIELD, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
342 break;
343 case 8:
344 case 9:
345 case 10:
346 case 11:
347 myStats->shield = newItem(IRON_SHIELD, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
348 break;
349 case 18:
350 myStats->shield = newItem(CRYSTAL_SHIELD, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
351 break;
352 case 19:
353 myStats->shield = newItem(CRYSTAL_SHIELD, static_cast<Status>(WORN + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
354 break;
355 default:
356 break;
357 }
358 }
359 }
360
361 // give cloak
362 if ( myStats->cloak == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 )
363 {
364 switch ( rand() % 10 )
365 {
366 case 0:
367 case 1:
368 case 2:
369 case 3:
370 case 4:
371 break;
372 case 5:
373 case 6:
374 case 7:
375 case 8:
376 case 9:
377 myStats->cloak = newItem(CLOAK, static_cast<Status>(WORN + rand() % 2), -1 + rand() % 3, 1, rand(), false, nullptr);
378 break;
379 }
380 }
381
382 // give helmet
383 /*if ( myStats->helmet == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_HELM] == 1 )
384 {
385 switch ( rand() % 10 )
386 {
387 case 0:
388 case 1:
389 case 2:
390 break;
391 case 3:
392 case 4:
393 myStats->helmet = newItem(HAT_PHRYGIAN, WORN, -1 + rand() % 3, 1, 0, false, nullptr);
394 break;
395 case 5:
396 myStats->helmet = newItem(HAT_WIZARD, WORN, -1 + rand() % 3, 1, 0, false, nullptr);
397 break;
398 case 6:
399 case 7:
400 myStats->helmet = newItem(LEATHER_HELM, WORN, -1 + rand() % 3, 1, 0, false, nullptr);
401 break;
402 case 8:
403 case 9:
404 myStats->helmet = newItem(IRON_HELM, WORN, -1 + rand() % 3, 1, 0, false, nullptr);
405 break;
406 }
407 }*/
408
409 // give armor
410 /*if ( myStats->breastplate == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_ARMOR] == 1 )
411 {
412 switch ( rand() % 10 )
413 {
414 case 0:
415 case 1:
416 case 2:
417 case 3:
418 case 4:
419 case 5:
420 case 6:
421 case 7:
422 break;
423 case 8:
424 myStats->breastplate = newItem(IRON_BREASTPIECE, static_cast<Status>(DECREPIT + rand() % 4), -1 + rand() % 3, 1, rand(), false, nullptr);
425 break;
426 case 9:
427 myStats->breastplate = newItem(STEEL_BREASTPIECE, static_cast<Status>(DECREPIT + rand() % 4), -1 + rand() % 3, 1, rand(), false, nullptr);
428 break;
429 }
430 }*/
431
432 // give booties
433 if ( myStats->shoes == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] == 1 )
434 {
435 switch ( rand() % 20 )
436 {
437 case 0:
438 case 1:
439 case 2:
440 myStats->shoes = newItem(STEEL_BOOTS, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
441 break;
442 case 3:
443 case 4:
444 case 5:
445 case 6:
446 myStats->shoes = newItem(IRON_BOOTS, static_cast<Status>(DECREPIT + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
447 break;
448 case 19:
449 myStats->shoes = newItem(CRYSTAL_BOOTS, static_cast<Status>(WORN + rand() % 3), -1 + rand() % 3, 1, rand(), false, nullptr);
450 break;
451 default:
452 break;
453
454 }
455 }
456 }
457 }
458 }
459
460 // torso
461 Entity* entity = newEntity(458, 0, map.entities, nullptr); //Limb entity.
462 entity->sizex = 4;
463 entity->sizey = 4;
464 entity->skill[2] = my->getUID();
465 entity->scalex = 1.01;
466 entity->scaley = 1.01;
467 entity->scalez = 1.01;
468 entity->flags[PASSABLE] = true;
469 entity->flags[NOUPDATE] = true;
470 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
471 entity->focalx = limbs[INSECTOID][1][0]; // 0
472 entity->focaly = limbs[INSECTOID][1][1]; // 0
473 entity->focalz = limbs[INSECTOID][1][2]; // 0
474 entity->behavior = &actInsectoidLimb;
475 entity->parent = my->getUID();
476 node = list_AddNodeLast(&my->children);
477 node->element = entity;
478 node->deconstructor = &emptyDeconstructor;
479 node->size = sizeof(Entity*);
480 my->bodyparts.push_back(entity);
481
482 // right leg
483 entity = newEntity(457, 0, map.entities, nullptr); //Limb entity.
484 entity->sizex = 4;
485 entity->sizey = 4;
486 entity->skill[2] = my->getUID();
487 entity->flags[PASSABLE] = true;
488 entity->flags[NOUPDATE] = true;
489 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
490 entity->focalx = limbs[INSECTOID][2][0]; // 0
491 entity->focaly = limbs[INSECTOID][2][1]; // 0
492 entity->focalz = limbs[INSECTOID][2][2]; // 2
493 entity->behavior = &actInsectoidLimb;
494 entity->parent = my->getUID();
495 node = list_AddNodeLast(&my->children);
496 node->element = entity;
497 node->deconstructor = &emptyDeconstructor;
498 node->size = sizeof(Entity*);
499 my->bodyparts.push_back(entity);
500
501 // left leg
502 entity = newEntity(456, 0, map.entities, nullptr); //Limb entity.
503 entity->sizex = 4;
504 entity->sizey = 4;
505 entity->skill[2] = my->getUID();
506 entity->flags[PASSABLE] = true;
507 entity->flags[NOUPDATE] = true;
508 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
509 entity->focalx = limbs[INSECTOID][3][0]; // 0
510 entity->focaly = limbs[INSECTOID][3][1]; // 0
511 entity->focalz = limbs[INSECTOID][3][2]; // 2
512 entity->behavior = &actInsectoidLimb;
513 entity->parent = my->getUID();
514 node = list_AddNodeLast(&my->children);
515 node->element = entity;
516 node->deconstructor = &emptyDeconstructor;
517 node->size = sizeof(Entity*);
518 my->bodyparts.push_back(entity);
519
520 // right arm
521 entity = newEntity(453, 0, map.entities, nullptr); //Limb entity.
522 entity->sizex = 4;
523 entity->sizey = 4;
524 entity->skill[2] = my->getUID();
525 entity->flags[PASSABLE] = true;
526 entity->flags[NOUPDATE] = true;
527 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
528 entity->focalx = limbs[INSECTOID][4][0]; // 0
529 entity->focaly = limbs[INSECTOID][4][1]; // 0
530 entity->focalz = limbs[INSECTOID][4][2]; // 1.5
531 entity->behavior = &actInsectoidLimb;
532 entity->parent = my->getUID();
533 node = list_AddNodeLast(&my->children);
534 node->element = entity;
535 node->deconstructor = &emptyDeconstructor;
536 node->size = sizeof(Entity*);
537 my->bodyparts.push_back(entity);
538
539 // left arm
540 entity = newEntity(451, 0, map.entities, nullptr); //Limb entity.
541 entity->sizex = 4;
542 entity->sizey = 4;
543 entity->skill[2] = my->getUID();
544 entity->flags[PASSABLE] = true;
545 entity->flags[NOUPDATE] = true;
546 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
547 entity->focalx = limbs[INSECTOID][5][0]; // 0
548 entity->focaly = limbs[INSECTOID][5][1]; // 0
549 entity->focalz = limbs[INSECTOID][5][2]; // 1.5
550 entity->behavior = &actInsectoidLimb;
551 entity->parent = my->getUID();
552 node = list_AddNodeLast(&my->children);
553 node->element = entity;
554 node->deconstructor = &emptyDeconstructor;
555 node->size = sizeof(Entity*);
556 my->bodyparts.push_back(entity);
557
558 // world weapon
559 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
560 entity->sizex = 4;
561 entity->sizey = 4;
562 entity->skill[2] = my->getUID();
563 entity->flags[PASSABLE] = true;
564 entity->flags[NOUPDATE] = true;
565 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
566 entity->focalx = limbs[INSECTOID][6][0]; // 1.5
567 entity->focaly = limbs[INSECTOID][6][1]; // 0
568 entity->focalz = limbs[INSECTOID][6][2]; // -.5
569 entity->behavior = &actInsectoidLimb;
570 entity->parent = my->getUID();
571 entity->pitch = .25;
572 node = list_AddNodeLast(&my->children);
573 node->element = entity;
574 node->deconstructor = &emptyDeconstructor;
575 node->size = sizeof(Entity*);
576 my->bodyparts.push_back(entity);
577
578 // shield
579 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
580 entity->sizex = 4;
581 entity->sizey = 4;
582 entity->skill[2] = my->getUID();
583 entity->flags[PASSABLE] = true;
584 entity->flags[NOUPDATE] = true;
585 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
586 entity->focalx = limbs[INSECTOID][7][0]; // 2
587 entity->focaly = limbs[INSECTOID][7][1]; // 0
588 entity->focalz = limbs[INSECTOID][7][2]; // 0
589 entity->behavior = &actInsectoidLimb;
590 entity->parent = my->getUID();
591 node = list_AddNodeLast(&my->children);
592 node->element = entity;
593 node->deconstructor = &emptyDeconstructor;
594 node->size = sizeof(Entity*);
595 my->bodyparts.push_back(entity);
596
597 // cloak
598 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
599 entity->sizex = 4;
600 entity->sizey = 4;
601 entity->skill[2] = my->getUID();
602 entity->flags[PASSABLE] = true;
603 entity->flags[NOUPDATE] = true;
604 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
605 entity->focalx = limbs[INSECTOID][8][0]; // 0
606 entity->focaly = limbs[INSECTOID][8][1]; // 0
607 entity->focalz = limbs[INSECTOID][8][2]; // 4
608 entity->behavior = &actInsectoidLimb;
609 entity->parent = my->getUID();
610 node = list_AddNodeLast(&my->children);
611 node->element = entity;
612 node->deconstructor = &emptyDeconstructor;
613 node->size = sizeof(Entity*);
614 my->bodyparts.push_back(entity);
615
616 // helmet
617 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
618 entity->sizex = 4;
619 entity->sizey = 4;
620 entity->skill[2] = my->getUID();
621 entity->scalex = 1.01;
622 entity->scaley = 1.01;
623 entity->scalez = 1.01;
624 entity->flags[PASSABLE] = true;
625 entity->flags[NOUPDATE] = true;
626 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
627 entity->focalx = limbs[INSECTOID][9][0]; // 0
628 entity->focaly = limbs[INSECTOID][9][1]; // 0
629 entity->focalz = limbs[INSECTOID][9][2]; // -2
630 entity->behavior = &actInsectoidLimb;
631 entity->parent = my->getUID();
632 node = list_AddNodeLast(&my->children);
633 node->element = entity;
634 node->deconstructor = &emptyDeconstructor;
635 node->size = sizeof(Entity*);
636 my->bodyparts.push_back(entity);
637
638 // mask
639 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
640 entity->sizex = 4;
641 entity->sizey = 4;
642 entity->skill[2] = my->getUID();
643 entity->flags[PASSABLE] = true;
644 entity->flags[NOUPDATE] = true;
645 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
646 entity->focalx = limbs[INSECTOID][10][0]; // 0
647 entity->focaly = limbs[INSECTOID][10][1]; // 0
648 entity->focalz = limbs[INSECTOID][10][2]; // .25
649 entity->behavior = &actInsectoidLimb;
650 entity->parent = my->getUID();
651 node = list_AddNodeLast(&my->children);
652 node->element = entity;
653 node->deconstructor = &emptyDeconstructor;
654 node->size = sizeof(Entity*);
655 my->bodyparts.push_back(entity);
656
657 // additional limb 1
658 entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity.
659 entity->sizex = 1;
660 entity->sizey = 1;
661 entity->skill[2] = my->getUID();
662 entity->flags[PASSABLE] = true;
663 entity->flags[NOUPDATE] = true;
664 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
665 entity->focalx = limbs[INSECTOID][11][0];
666 entity->focaly = limbs[INSECTOID][11][1];
667 entity->focalz = limbs[INSECTOID][11][2];
668 entity->behavior = &actInsectoidLimb;
669 entity->parent = my->getUID();
670 node = list_AddNodeLast(&my->children);
671 node->element = entity;
672 node->deconstructor = &emptyDeconstructor;
673 node->size = sizeof(Entity*);
674 my->bodyparts.push_back(entity);
675
676 // additional limb 2
677 entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity.
678 entity->sizex = 1;
679 entity->sizey = 1;
680 entity->skill[2] = my->getUID();
681 entity->flags[PASSABLE] = true;
682 entity->flags[NOUPDATE] = true;
683 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
684 entity->focalx = limbs[INSECTOID][12][0];
685 entity->focaly = limbs[INSECTOID][12][1];
686 entity->focalz = limbs[INSECTOID][12][2];
687 entity->behavior = &actInsectoidLimb;
688 entity->parent = my->getUID();
689 node = list_AddNodeLast(&my->children);
690 node->element = entity;
691 node->deconstructor = &emptyDeconstructor;
692 node->size = sizeof(Entity*);
693 my->bodyparts.push_back(entity);
694
695 if ( multiplayer == CLIENT || MONSTER_INIT )
696 {
697 return;
698 }
699 }
700
actInsectoidLimb(Entity * my)701 void actInsectoidLimb(Entity* my)
702 {
703 my->actMonsterLimb(true);
704 }
705
insectoidDie(Entity * my)706 void insectoidDie(Entity* my)
707 {
708 int c;
709 for ( c = 0; c < 5; c++ )
710 {
711 Entity* gib = spawnGib(my);
712 serverSpawnGibForClient(gib);
713 }
714
715 my->spawnBlood(212);
716
717 playSoundEntity(my, 287 + rand() % 4, 128);
718
719 my->removeMonsterDeathNodes();
720
721 list_RemoveNode(my->mynode);
722 return;
723 }
724
725 #define INSECTOIDWALKSPEED .13
726
insectoidMoveBodyparts(Entity * my,Stat * myStats,double dist)727 void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist)
728 {
729 node_t* node;
730 Entity* entity = nullptr, *entity2 = nullptr;
731 Entity* rightbody = nullptr;
732 Entity* weaponarm = nullptr;
733 Entity* torso = nullptr;
734 int bodypart;
735 bool wearingring = false;
736
737 // set invisibility //TODO: isInvisible()?
738 if ( multiplayer != CLIENT )
739 {
740 if ( myStats->ring != nullptr )
741 if ( myStats->ring->type == RING_INVISIBILITY )
742 {
743 wearingring = true;
744 }
745 if ( myStats->cloak != nullptr )
746 if ( myStats->cloak->type == CLOAK_INVISIBILITY )
747 {
748 wearingring = true;
749 }
750 if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true )
751 {
752 my->flags[INVISIBLE] = true;
753 my->flags[BLOCKSIGHT] = false;
754 bodypart = 0;
755 for (node = my->children.first; node != nullptr; node = node->next)
756 {
757 if ( bodypart < 2 )
758 {
759 bodypart++;
760 continue;
761 }
762 if ( bodypart >= 7 )
763 {
764 break;
765 }
766 entity = (Entity*)node->element;
767 if ( !entity->flags[INVISIBLE] )
768 {
769 entity->flags[INVISIBLE] = true;
770 serverUpdateEntityBodypart(my, bodypart);
771 }
772 bodypart++;
773 }
774 }
775 else
776 {
777 my->flags[INVISIBLE] = false;
778 my->flags[BLOCKSIGHT] = true;
779 bodypart = 0;
780 for (node = my->children.first; node != nullptr; node = node->next)
781 {
782 if ( bodypart < 2 )
783 {
784 bodypart++;
785 continue;
786 }
787 if ( bodypart >= 7 )
788 {
789 break;
790 }
791 entity = (Entity*)node->element;
792 if ( entity->flags[INVISIBLE] )
793 {
794 entity->flags[INVISIBLE] = false;
795 serverUpdateEntityBodypart(my, bodypart);
796 serverUpdateEntityFlag(my, INVISIBLE);
797 }
798 bodypart++;
799 }
800 }
801
802 // sleeping
803 if ( myStats->EFFECTS[EFF_ASLEEP] )
804 {
805 my->z = 2.5;
806 my->pitch = PI / 4;
807 }
808 else
809 {
810 my->z = 0;
811 if ( my->monsterAttack == 0 )
812 {
813 my->pitch = 0;
814 }
815 }
816 }
817
818 Entity* shieldarm = nullptr;
819 Entity* additionalLimb = nullptr;
820 Entity* helmet = nullptr;
821
822 //Move bodyparts
823 for (bodypart = 0, node = my->children.first; node != nullptr; node = node->next, bodypart++)
824 {
825 if ( bodypart < LIMB_HUMANOID_TORSO )
826 {
827 // post-swing head animation. client doesn't need to adjust the entity pitch, server will handle.
828 if ( my->monsterAttack != MONSTER_POSE_MAGIC_WINDUP3 && bodypart == 1 && multiplayer != CLIENT )
829 {
830 limbAnimateToLimit(my, ANIMATE_PITCH, 0.1, 0, false, 0.0);
831 }
832 continue;
833 }
834 entity = (Entity*)node->element;
835 entity->x = my->x;
836 entity->y = my->y;
837 entity->z = my->z;
838 if ( (my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1 ) && bodypart == LIMB_HUMANOID_RIGHTARM )
839 {
840 // don't let the creatures's yaw move the casting arm
841 }
842 else
843 {
844 entity->yaw = my->yaw;
845 }
846 if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
847 {
848 if ( bodypart == LIMB_HUMANOID_LEFTARM &&
849 (my->monsterSpecialState == INSECTOID_ACID && my->monsterAttack != 0) )
850 {
851 Entity* weaponarm = nullptr;
852 // leftarm follows the right arm during special acid attack
853 node_t* weaponarmNode = list_Node(&my->children, LIMB_HUMANOID_RIGHTARM);
854 if ( weaponarmNode )
855 {
856 weaponarm = (Entity*)weaponarmNode->element;
857 }
858 else
859 {
860 return;
861 }
862 entity->pitch = weaponarm->pitch;
863 entity->roll = -weaponarm->roll;
864 }
865 else
866 {
867 my->humanoidAnimateWalk(entity, node, bodypart, INSECTOIDWALKSPEED, dist, 0.4);
868 }
869 }
870 else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM || bodypart == LIMB_HUMANOID_CLOAK )
871 {
872 // left leg, right arm, cloak.
873 if ( bodypart == LIMB_HUMANOID_RIGHTARM )
874 {
875 weaponarm = entity;
876 if ( my->monsterAttack > 0 )
877 {
878 Entity* rightbody = nullptr;
879 // set rightbody to left leg.
880 node_t* rightbodyNode = list_Node(&my->children, LIMB_HUMANOID_LEFTLEG);
881 if ( rightbodyNode )
882 {
883 rightbody = (Entity*)rightbodyNode->element;
884 }
885 else
886 {
887 return;
888 }
889
890 if ( my->monsterAttack == MONSTER_POSE_RANGED_WINDUP3 )
891 {
892 if ( my->monsterAttackTime == 0 )
893 {
894 // init rotations
895 weaponarm->pitch = 0;
896 my->monsterArmbended = 0;
897 my->monsterWeaponYaw = 0;
898 weaponarm->roll = 0;
899 weaponarm->skill[1] = 0;
900 createParticleDot(my);
901 }
902
903 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
904
905 if ( my->monsterAttackTime >= 3 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
906 {
907 if ( multiplayer != CLIENT )
908 {
909 my->attack(MONSTER_POSE_INSECTOID_DOUBLETHROW, 0, nullptr);
910 }
911 }
912 }
913 // vertical throw
914 else if ( my->monsterAttack == MONSTER_POSE_INSECTOID_DOUBLETHROW )
915 {
916 if ( weaponarm->pitch >= 3 * PI / 2 )
917 {
918 my->monsterArmbended = 1;
919 }
920
921 if ( weaponarm->skill[1] == 0 )
922 {
923 // chop forwards
924 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.4, PI / 3, false, 0.0) )
925 {
926 weaponarm->skill[1] = 1;
927 }
928 }
929 else if ( weaponarm->skill[1] == 1 )
930 {
931 // return to neutral
932 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, 7 * PI / 4, false, 0.0) )
933 {
934 weaponarm->skill[0] = rightbody->skill[0];
935 my->monsterWeaponYaw = 0;
936 weaponarm->pitch = rightbody->pitch;
937 weaponarm->roll = 0;
938 my->monsterArmbended = 0;
939
940 if ( multiplayer != CLIENT && my->monsterSpecialState == INSECTOID_DOUBLETHROW_FIRST )
941 {
942 my->monsterSpecialState = INSECTOID_DOUBLETHROW_SECOND;
943 my->attack(MONSTER_POSE_RANGED_WINDUP3, 0, nullptr);
944 }
945 else
946 {
947 my->monsterAttack = 0;
948 }
949 }
950 }
951 ++my->monsterAttackTime;
952 }
953 else if ( my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP3 )
954 {
955 if ( my->monsterAttackTime == 0 )
956 {
957 // init rotations
958 weaponarm->pitch = 0;
959 my->monsterArmbended = 0;
960 my->monsterWeaponYaw = 0;
961 weaponarm->roll = 0;
962 weaponarm->skill[1] = 0;
963 createParticleDot(my);
964 // play casting sound
965 playSoundEntityLocal(my, 170, 64);
966 // monster scream
967 playSoundEntityLocal(my, MONSTER_SPOTSND + 1, 128);
968 if ( multiplayer != CLIENT )
969 {
970 myStats->EFFECTS[EFF_PARALYZED] = true;
971 myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 60;
972 }
973 }
974
975 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
976 if ( multiplayer != CLIENT )
977 {
978 // move the head and weapon yaw
979 limbAnimateToLimit(my, ANIMATE_PITCH, -0.1, 15 * PI / 8, true, 0.1);
980 limbAnimateToLimit(my, ANIMATE_WEAPON_YAW, 0.25, 4 * PI / 8, false, 0.0);
981 }
982
983 if ( my->monsterAttackTime >= 6 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
984 {
985 if ( multiplayer != CLIENT )
986 {
987 my->attack(MONSTER_POSE_MELEE_WINDUP1, 0, nullptr);
988 }
989 }
990 }
991 else
992 {
993 my->handleWeaponArmAttack(entity);
994 }
995 }
996 }
997 else if ( bodypart == LIMB_HUMANOID_CLOAK )
998 {
999 entity->pitch = entity->fskill[0];
1000 }
1001
1002 my->humanoidAnimateWalk(entity, node, bodypart, INSECTOIDWALKSPEED, dist, 0.4);
1003
1004 if ( bodypart == LIMB_HUMANOID_CLOAK )
1005 {
1006 entity->fskill[0] = entity->pitch;
1007 entity->roll = my->roll - fabs(entity->pitch) / 2;
1008 entity->pitch = 0;
1009 }
1010 }
1011 switch ( bodypart )
1012 {
1013 // torso
1014 case LIMB_HUMANOID_TORSO:
1015 torso = entity;
1016 if ( multiplayer != CLIENT )
1017 {
1018 if ( myStats->breastplate == nullptr )
1019 {
1020 entity->sprite = 458;
1021 }
1022 else
1023 {
1024 entity->sprite = itemModel(myStats->breastplate);
1025 entity->scalex = 0.9;
1026 // shrink the width of the breastplate
1027 entity->scaley = 0.9;
1028 }
1029 if ( multiplayer == SERVER )
1030 {
1031 // update sprites for clients
1032 if ( entity->skill[10] != entity->sprite )
1033 {
1034 entity->skill[10] = entity->sprite;
1035 serverUpdateEntityBodypart(my, bodypart);
1036 }
1037 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1038 {
1039 serverUpdateEntityBodypart(my, bodypart);
1040 }
1041 }
1042 }
1043 else if ( multiplayer == CLIENT )
1044 {
1045 if ( entity->sprite != 468 )
1046 {
1047 entity->scalex = 0.9;
1048 // shrink the width of the breastplate
1049 entity->scaley = 0.9;
1050 }
1051 else
1052 {
1053 entity->scalex = 1;
1054 entity->scaley = 1;
1055 }
1056 }
1057 my->setHumanoidLimbOffset(entity, INSECTOID, LIMB_HUMANOID_TORSO);
1058 break;
1059 // right leg
1060 case LIMB_HUMANOID_RIGHTLEG:
1061 if ( multiplayer != CLIENT )
1062 {
1063 if ( myStats->shoes == nullptr )
1064 {
1065 entity->sprite = 457;
1066 }
1067 else
1068 {
1069 my->setBootSprite(entity, SPRITE_BOOT_RIGHT_OFFSET);
1070 }
1071 if ( multiplayer == SERVER )
1072 {
1073 // update sprites for clients
1074 if ( entity->skill[10] != entity->sprite )
1075 {
1076 entity->skill[10] = entity->sprite;
1077 serverUpdateEntityBodypart(my, bodypart);
1078 }
1079 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1080 {
1081 serverUpdateEntityBodypart(my, bodypart);
1082 }
1083 }
1084 }
1085 my->setHumanoidLimbOffset(entity, INSECTOID, LIMB_HUMANOID_RIGHTLEG);
1086 break;
1087 // left leg
1088 case LIMB_HUMANOID_LEFTLEG:
1089 if ( multiplayer != CLIENT )
1090 {
1091 if ( myStats->shoes == nullptr )
1092 {
1093 entity->sprite = 456;
1094 }
1095 else
1096 {
1097 my->setBootSprite(entity, SPRITE_BOOT_LEFT_OFFSET);
1098 }
1099 if ( multiplayer == SERVER )
1100 {
1101 // update sprites for clients
1102 if ( entity->skill[10] != entity->sprite )
1103 {
1104 entity->skill[10] = entity->sprite;
1105 serverUpdateEntityBodypart(my, bodypart);
1106 }
1107 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1108 {
1109 serverUpdateEntityBodypart(my, bodypart);
1110 }
1111 }
1112 }
1113 my->setHumanoidLimbOffset(entity, INSECTOID, LIMB_HUMANOID_LEFTLEG);
1114 break;
1115 // right arm
1116 case LIMB_HUMANOID_RIGHTARM:
1117 {
1118 node_t* weaponNode = list_Node(&my->children, LIMB_HUMANOID_WEAPON);
1119 if ( weaponNode )
1120 {
1121 Entity* weapon = (Entity*)weaponNode->element;
1122 if ( MONSTER_ARMBENDED || (weapon->flags[INVISIBLE] && my->monsterState == MONSTER_STATE_WAIT) )
1123 {
1124 // if weapon invisible and I'm not attacking, relax arm.
1125 entity->focalx = limbs[INSECTOID][4][0]; // 0
1126 entity->focaly = limbs[INSECTOID][4][1]; // 0
1127 entity->focalz = limbs[INSECTOID][4][2]; // 2
1128 entity->sprite = 453;
1129 }
1130 else
1131 {
1132 // else flex arm.
1133 entity->focalx = limbs[INSECTOID][4][0] + 0.75;
1134 entity->focaly = limbs[INSECTOID][4][1];
1135 entity->focalz = limbs[INSECTOID][4][2] - 0.75;
1136 entity->sprite = 454;
1137 }
1138 }
1139 my->setHumanoidLimbOffset(entity, INSECTOID, LIMB_HUMANOID_RIGHTARM);
1140 entity->yaw += MONSTER_WEAPONYAW;
1141 break;
1142 }
1143 // left arm
1144 case LIMB_HUMANOID_LEFTARM:
1145 {
1146 shieldarm = entity;
1147 node_t* shieldNode = list_Node(&my->children, LIMB_HUMANOID_SHIELD);
1148 if ( shieldNode )
1149 {
1150 Entity* shield = (Entity*)shieldNode->element;
1151 if ( shield->flags[INVISIBLE] && my->monsterState == MONSTER_STATE_WAIT )
1152 {
1153 entity->focalx = limbs[INSECTOID][5][0]; // 0
1154 entity->focaly = limbs[INSECTOID][5][1]; // 0
1155 entity->focalz = limbs[INSECTOID][5][2]; // 2
1156 entity->sprite = 451;
1157 }
1158 else
1159 {
1160 entity->focalx = limbs[INSECTOID][5][0] + 0.75;
1161 entity->focaly = limbs[INSECTOID][5][1];
1162 entity->focalz = limbs[INSECTOID][5][2] - 0.75;
1163 entity->sprite = 452;
1164 }
1165 }
1166 my->setHumanoidLimbOffset(entity, INSECTOID, LIMB_HUMANOID_LEFTARM);
1167 if ( my->monsterDefend && my->monsterAttack == 0 )
1168 {
1169 MONSTER_SHIELDYAW = PI / 5;
1170 }
1171 else
1172 {
1173 MONSTER_SHIELDYAW = 0;
1174 }
1175 entity->yaw += MONSTER_SHIELDYAW;
1176 break;
1177 }
1178 // weapon
1179 case LIMB_HUMANOID_WEAPON:
1180 if ( multiplayer != CLIENT )
1181 {
1182 if ( myStats->weapon == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1183 {
1184 entity->flags[INVISIBLE] = true;
1185 }
1186 else
1187 {
1188 entity->sprite = itemModel(myStats->weapon);
1189 if ( itemCategory(myStats->weapon) == SPELLBOOK )
1190 {
1191 entity->flags[INVISIBLE] = true;
1192 }
1193 else
1194 {
1195 entity->flags[INVISIBLE] = false;
1196 }
1197 }
1198 if ( multiplayer == SERVER )
1199 {
1200 // update sprites for clients
1201 if ( entity->skill[10] != entity->sprite )
1202 {
1203 entity->skill[10] = entity->sprite;
1204 serverUpdateEntityBodypart(my, bodypart);
1205 }
1206 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1207 {
1208 entity->skill[11] = entity->flags[INVISIBLE];
1209 serverUpdateEntityBodypart(my, bodypart);
1210 }
1211 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1212 {
1213 serverUpdateEntityBodypart(my, bodypart);
1214 }
1215 }
1216 }
1217 else
1218 {
1219 if ( entity->sprite <= 0 )
1220 {
1221 entity->flags[INVISIBLE] = true;
1222 }
1223 }
1224 if ( weaponarm != nullptr )
1225 {
1226 my->handleHumanoidWeaponLimb(entity, weaponarm);
1227 }
1228 break;
1229 // shield
1230 case LIMB_HUMANOID_SHIELD:
1231 if ( multiplayer != CLIENT )
1232 {
1233 if ( myStats->shield == nullptr )
1234 {
1235 entity->flags[INVISIBLE] = true;
1236 entity->sprite = 0;
1237 }
1238 else
1239 {
1240 entity->flags[INVISIBLE] = false;
1241 entity->sprite = itemModel(myStats->shield);
1242 if ( itemTypeIsQuiver(myStats->shield->type) )
1243 {
1244 entity->handleQuiverThirdPersonModel(*myStats);
1245 }
1246 }
1247 if ( myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1248 {
1249 entity->flags[INVISIBLE] = true;
1250 }
1251 if ( multiplayer == SERVER )
1252 {
1253 // update sprites for clients
1254 if ( entity->skill[10] != entity->sprite )
1255 {
1256 entity->skill[10] = entity->sprite;
1257 serverUpdateEntityBodypart(my, bodypart);
1258 }
1259 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1260 {
1261 entity->skill[11] = entity->flags[INVISIBLE];
1262 serverUpdateEntityBodypart(my, bodypart);
1263 }
1264 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1265 {
1266 serverUpdateEntityBodypart(my, bodypart);
1267 }
1268 }
1269 }
1270 else
1271 {
1272 if ( entity->sprite <= 0 )
1273 {
1274 entity->flags[INVISIBLE] = true;
1275 }
1276 }
1277 my->handleHumanoidShieldLimb(entity, shieldarm);
1278 break;
1279 // cloak
1280 case LIMB_HUMANOID_CLOAK:
1281 if ( multiplayer != CLIENT )
1282 {
1283 if ( myStats->cloak == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1284 {
1285 entity->flags[INVISIBLE] = true;
1286 }
1287 else
1288 {
1289 entity->flags[INVISIBLE] = false;
1290 entity->sprite = itemModel(myStats->cloak);
1291 }
1292 if ( multiplayer == SERVER )
1293 {
1294 // update sprites for clients
1295 if ( entity->skill[10] != entity->sprite )
1296 {
1297 entity->skill[10] = entity->sprite;
1298 serverUpdateEntityBodypart(my, bodypart);
1299 }
1300 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1301 {
1302 entity->skill[11] = entity->flags[INVISIBLE];
1303 serverUpdateEntityBodypart(my, bodypart);
1304 }
1305 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1306 {
1307 serverUpdateEntityBodypart(my, bodypart);
1308 }
1309 }
1310 }
1311 else
1312 {
1313 if ( entity->sprite <= 0 )
1314 {
1315 entity->flags[INVISIBLE] = true;
1316 }
1317 }
1318 entity->x -= cos(my->yaw);
1319 entity->y -= sin(my->yaw);
1320 entity->yaw += PI / 2;
1321 break;
1322 // helm
1323 case LIMB_HUMANOID_HELMET:
1324 helmet = entity;
1325 entity->focalx = limbs[INSECTOID][9][0]; // 0
1326 entity->focaly = limbs[INSECTOID][9][1]; // 0
1327 entity->focalz = limbs[INSECTOID][9][2]; // -2
1328 entity->pitch = my->pitch;
1329 entity->roll = 0;
1330 if ( multiplayer != CLIENT )
1331 {
1332 entity->sprite = itemModel(myStats->helmet);
1333 if ( myStats->helmet == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1334 {
1335 entity->flags[INVISIBLE] = true;
1336 }
1337 else
1338 {
1339 entity->flags[INVISIBLE] = false;
1340 }
1341 if ( multiplayer == SERVER )
1342 {
1343 // update sprites for clients
1344 if ( entity->skill[10] != entity->sprite )
1345 {
1346 entity->skill[10] = entity->sprite;
1347 serverUpdateEntityBodypart(my, bodypart);
1348 }
1349 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1350 {
1351 entity->skill[11] = entity->flags[INVISIBLE];
1352 serverUpdateEntityBodypart(my, bodypart);
1353 }
1354 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1355 {
1356 serverUpdateEntityBodypart(my, bodypart);
1357 }
1358 }
1359 }
1360 else
1361 {
1362 if ( entity->sprite <= 0 )
1363 {
1364 entity->flags[INVISIBLE] = true;
1365 }
1366 }
1367 my->setHelmetLimbOffset(entity);
1368 break;
1369 // mask
1370 case LIMB_HUMANOID_MASK:
1371 entity->focalx = limbs[INSECTOID][10][0]; // 0
1372 entity->focaly = limbs[INSECTOID][10][1]; // 0
1373 entity->focalz = limbs[INSECTOID][10][2]; // .25
1374 entity->pitch = my->pitch;
1375 entity->roll = PI / 2;
1376 if ( multiplayer != CLIENT )
1377 {
1378 bool hasSteelHelm = false;
1379 if ( myStats->helmet )
1380 {
1381 if ( myStats->helmet->type == STEEL_HELM
1382 || myStats->helmet->type == CRYSTAL_HELM
1383 || myStats->helmet->type == ARTIFACT_HELM )
1384 {
1385 hasSteelHelm = true;
1386 }
1387 }
1388 if ( myStats->mask == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring || hasSteelHelm ) //TODO: isInvisible()?
1389 {
1390 entity->flags[INVISIBLE] = true;
1391 }
1392 else
1393 {
1394 entity->flags[INVISIBLE] = false;
1395 }
1396 if ( myStats->mask != nullptr )
1397 {
1398 if ( myStats->mask->type == TOOL_GLASSES )
1399 {
1400 entity->sprite = 165; // GlassesWorn.vox
1401 }
1402 else
1403 {
1404 entity->sprite = itemModel(myStats->mask);
1405 }
1406 }
1407 if ( multiplayer == SERVER )
1408 {
1409 // update sprites for clients
1410 if ( entity->skill[10] != entity->sprite )
1411 {
1412 entity->skill[10] = entity->sprite;
1413 serverUpdateEntityBodypart(my, bodypart);
1414 }
1415 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1416 {
1417 entity->skill[11] = entity->flags[INVISIBLE];
1418 serverUpdateEntityBodypart(my, bodypart);
1419 }
1420 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1421 {
1422 serverUpdateEntityBodypart(my, bodypart);
1423 }
1424 }
1425 }
1426 else
1427 {
1428 if ( entity->sprite <= 0 )
1429 {
1430 entity->flags[INVISIBLE] = true;
1431 }
1432 }
1433 if ( entity->sprite != 165 )
1434 {
1435 if ( entity->sprite == items[MASK_SHAMAN].index )
1436 {
1437 entity->roll = 0;
1438 my->setHelmetLimbOffset(entity);
1439 my->setHelmetLimbOffsetWithMask(helmet, entity);
1440 }
1441 else
1442 {
1443 entity->focalx = limbs[INSECTOID][10][0] + .35; // .35
1444 entity->focaly = limbs[INSECTOID][10][1] - 2; // -2
1445 entity->focalz = limbs[INSECTOID][10][2]; // .25
1446 }
1447 }
1448 else
1449 {
1450 entity->focalx = limbs[INSECTOID][10][0] + .25; // .25
1451 entity->focaly = limbs[INSECTOID][10][1] - 2.25; // -2.25
1452 entity->focalz = limbs[INSECTOID][10][2]; // .25
1453 }
1454 break;
1455 case 12:
1456 {
1457 additionalLimb = entity;
1458 entity->focalx = limbs[INSECTOID][11][0];
1459 entity->focaly = limbs[INSECTOID][11][1];
1460 entity->focalz = limbs[INSECTOID][11][2];
1461 entity->flags[INVISIBLE] = true;
1462
1463 entity->flags[INVISIBLE] = my->flags[INVISIBLE];
1464 entity->sprite = 750;
1465 if ( torso && torso->sprite != 727 && torso->sprite != 761 && torso->sprite != 458 )
1466 {
1467 // wearing armor, offset more.
1468 entity->x -= 2.25 * cos(my->yaw);
1469 entity->y -= 2.25 * sin(my->yaw);
1470 }
1471 else
1472 {
1473 entity->x -= 1.5 * cos(my->yaw);
1474 entity->y -= 1.5 * sin(my->yaw);
1475 }
1476 bool moving = false;
1477 if ( fabs(my->vel_x) > 0.1 || fabs(my->vel_y) > 0.1 )
1478 {
1479 moving = true;
1480 }
1481
1482 if ( entity->skill[0] == 0 )
1483 {
1484 if ( moving )
1485 {
1486 entity->fskill[0] += std::min(dist * INSECTOIDWALKSPEED, 2.f * INSECTOIDWALKSPEED); // move proportional to move speed
1487 }
1488 else if ( my->monsterAttack != 0 )
1489 {
1490 entity->fskill[0] += INSECTOIDWALKSPEED; // move fixed speed when attacking if stationary
1491 }
1492 else
1493 {
1494 entity->fskill[0] += 0.01; // otherwise move slow idle
1495 }
1496
1497 if ( entity->fskill[0] > PI / 3 || ((!moving || my->monsterAttack != 0) && entity->fskill[0] > PI / 5) )
1498 {
1499 // switch direction if angle too great, angle is shorter if attacking or stationary
1500 entity->skill[0] = 1;
1501 }
1502 }
1503 else // reverse of the above
1504 {
1505 if ( moving )
1506 {
1507 entity->fskill[0] -= std::min(dist * INSECTOIDWALKSPEED, 2.f * INSECTOIDWALKSPEED);
1508 }
1509 else if ( my->monsterAttack != 0 )
1510 {
1511 entity->fskill[0] -= INSECTOIDWALKSPEED;
1512 }
1513 else
1514 {
1515 entity->fskill[0] -= 0.007;
1516 }
1517
1518 if ( entity->fskill[0] < -PI / 32 )
1519 {
1520 entity->skill[0] = 0;
1521 }
1522 }
1523 entity->yaw += entity->fskill[0];
1524 break;
1525 }
1526 case 13:
1527 entity->focalx = limbs[INSECTOID][12][0];
1528 entity->focaly = limbs[INSECTOID][12][1];
1529 entity->focalz = limbs[INSECTOID][12][2];
1530 entity->flags[INVISIBLE] = true;
1531 entity->flags[INVISIBLE] = my->flags[INVISIBLE];
1532 entity->sprite = 751;
1533 if ( additionalLimb ) // follow the yaw of the previous limb.
1534 {
1535 entity->yaw -= additionalLimb->fskill[0];
1536 }
1537 if ( torso && torso->sprite != 727 && torso->sprite != 761 && torso->sprite != 458 )
1538 {
1539 // wearing armor, offset more.
1540 entity->x -= 2.25 * cos(my->yaw);
1541 entity->y -= 2.25 * sin(my->yaw);
1542 }
1543 else
1544 {
1545 entity->x -= 1.5 * cos(my->yaw);
1546 entity->y -= 1.5 * sin(my->yaw);
1547 }
1548 break;
1549 }
1550 }
1551 // rotate shield a bit
1552 node_t* shieldNode = list_Node(&my->children, 8);
1553 if ( shieldNode )
1554 {
1555 Entity* shieldEntity = (Entity*)shieldNode->element;
1556 if ( shieldEntity->sprite != items[TOOL_TORCH].index && shieldEntity->sprite != items[TOOL_LANTERN].index && shieldEntity->sprite != items[TOOL_CRYSTALSHARD].index )
1557 {
1558 shieldEntity->yaw -= PI / 6;
1559 }
1560 }
1561 if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
1562 {
1563 MONSTER_ATTACKTIME++;
1564 }
1565 else if ( MONSTER_ATTACK == 0 )
1566 {
1567 MONSTER_ATTACKTIME = 0;
1568 }
1569 else
1570 {
1571 // do nothing, don't reset attacktime or increment it.
1572 }
1573 }
1574
insectoidCanWieldItem(const Item & item) const1575 bool Entity::insectoidCanWieldItem(const Item& item) const
1576 {
1577 Stat* myStats = getStats();
1578 if ( !myStats )
1579 {
1580 return false;
1581 }
1582
1583 switch ( itemCategory(&item) )
1584 {
1585 case WEAPON:
1586 return true;
1587 case POTION:
1588 return false;
1589 case THROWN:
1590 return true;
1591 case ARMOR:
1592 { //Little baby compiler stop whining, wah wah.
1593 int equipType = checkEquipType(&item);
1594 if ( equipType == TYPE_HAT || equipType == TYPE_HELM )
1595 {
1596 return false; //No can wear hats, because antennae.
1597 }
1598 return true; //Can wear all other armor.
1599 }
1600 default:
1601 return false;
1602 }
1603
1604 return false;
1605 }
1606
insectoidChooseWeapon(const Entity * target,double dist)1607 void Entity::insectoidChooseWeapon(const Entity* target, double dist)
1608 {
1609 if ( monsterSpecialState != 0 )
1610 {
1611 //Holding a weapon assigned from the special attack. Don't switch weapons.
1612 //messagePlayer()
1613 return;
1614 }
1615
1616 Stat *myStats = getStats();
1617 if ( !myStats )
1618 {
1619 return;
1620 }
1621
1622 /*if ( myStats->weapon && (itemCategory(myStats->weapon) == MAGICSTAFF || itemCategory(myStats->weapon) == SPELLBOOK) )
1623 {
1624 return;
1625 }*/
1626
1627 int specialRoll = -1;
1628 int bonusFromHP = 0;
1629
1630 // throwing weapons
1631 // occurs less often against fellow monsters.
1632 if ( monsterSpecialTimer == 0 && (ticks % 10 == 0) && monsterAttack == 0 )
1633 {
1634 specialRoll = rand() % (40 + 40 * (target != nullptr && target->behavior == &actMonster));
1635 //messagePlayer(0, "rolled: %d", specialRoll);
1636 if ( myStats->HP <= myStats->MAXHP * 0.6 )
1637 {
1638 bonusFromHP += 1; // +5% chance if on low health
1639 }
1640 if ( myStats->HP <= myStats->MAXHP * 0.3 )
1641 {
1642 bonusFromHP += 1; // +extra 5% chance if on lower health
1643 }
1644 if ( specialRoll < (1 + bonusFromHP) ) // +5% base
1645 {
1646 node_t* node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), THROWN);
1647 if ( node != nullptr )
1648 {
1649 bool swapped = swapMonsterWeaponWithInventoryItem(this, myStats, node, true, true);
1650 if ( swapped )
1651 {
1652 if ( myStats->weapon->count > 1 )
1653 {
1654 monsterSpecialState = INSECTOID_DOUBLETHROW_FIRST + rand() % 2; // 50% for double throw.
1655 }
1656 else
1657 {
1658 monsterSpecialState = INSECTOID_DOUBLETHROW_SECOND;
1659 }
1660 }
1661 return;
1662 }
1663 }
1664 }
1665
1666 bool inMeleeRange = monsterInMeleeRange(target, dist);
1667
1668 if ( inMeleeRange )
1669 {
1670 //Switch to a melee weapon if not already wielding one. Unless monster special state is overriding the AI.
1671 if ( !myStats->weapon || !isMeleeWeapon(*myStats->weapon) )
1672 {
1673 node_t* weaponNode = getMeleeWeaponItemNodeInInventory(myStats);
1674 if ( !weaponNode )
1675 {
1676 return; //Resort to fists.
1677 }
1678
1679 bool swapped = swapMonsterWeaponWithInventoryItem(this, myStats, weaponNode, false, false);
1680 if ( !swapped )
1681 {
1682 //Don't return so that monsters will at least equip ranged weapons in melee range if they don't have anything else.
1683 }
1684 else
1685 {
1686 return;
1687 }
1688 }
1689 else
1690 {
1691 return;
1692 }
1693 }
1694
1695 //Switch to a thrown weapon or a ranged weapon.
1696 if ( !myStats->weapon || isMeleeWeapon(*myStats->weapon) )
1697 {
1698 node_t *weaponNode = getRangedWeaponItemNodeInInventory(myStats, true);
1699 if ( !weaponNode )
1700 {
1701 return; //Nothing available
1702 }
1703 bool swapped = swapMonsterWeaponWithInventoryItem(this, myStats, weaponNode, false, false);
1704 return;
1705 }
1706 return;
1707 }
1708