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