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