1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: actarrow.cpp
5 Desc: behavior function for arrows/bolts
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 "monster.hpp"
17 #include "sound.hpp"
18 #include "interface/interface.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "items.hpp"
22 #include "magic/magic.hpp"
23 #include "scores.hpp"
24 #include "player.hpp"
25
26 /*-------------------------------------------------------------------------------
27
28 act*
29
30 The following function describes an entity behavior. The function
31 takes a pointer to the entity that uses it as an argument.
32
33 -------------------------------------------------------------------------------*/
34
35 #define ARROW_STUCK my->skill[0]
36 #define ARROW_LIFE my->skill[1]
37 #define ARROW_VELX my->vel_x
38 #define ARROW_VELY my->vel_y
39 #define ARROW_VELZ my->vel_z
40 #define ARROW_OLDX my->fskill[2]
41 #define ARROW_OLDY my->fskill[3]
42 #define ARROW_MAXLIFE 600
43 #define ARROW_INIT my->skill[10]
44 #define ARROW_FLICKER my->skill[11]
45 #define ARROW_LIGHTING my->skill[12]
46 #define ARROW_STUCK_CLIENT my->skill[13]
47
48 enum ArrowSpriteTypes : int
49 {
50 PROJECTILE_ROCK_SPRITE = 78,
51 PROJECTILE_NORMAL_SPRITE = 166,
52 PROJECTILE_BOLT_SPRITE = 167,
53 PROJECTILE_SILVER_SPRITE = 924,
54 PROJECTILE_PIERCE_SPRITE,
55 PROJECTILE_SWIFT_SPRITE,
56 PROJECTILE_FIRE_SPRITE,
57 PROJECTILE_HEAVY_SPRITE,
58 PROJECTILE_CRYSTAL_SPRITE,
59 PROJECTILE_HUNTING_SPRITE
60 };
61
actArrow(Entity * my)62 void actArrow(Entity* my)
63 {
64 double dist;
65 int damage;
66 Entity* entity;
67 node_t* node;
68 double tangent;
69
70
71 // lifespan
72 ARROW_LIFE++;
73 my->removeLightField();
74
75 if ( ARROW_LIFE >= ARROW_MAXLIFE )
76 {
77 list_RemoveNode(my->mynode);
78 return;
79 }
80
81 // falling out of the map
82 if ( my->z > 32 )
83 {
84 list_RemoveNode(my->mynode);
85 return;
86 }
87
88 if ( my->arrowQuiverType == QUIVER_FIRE || my->sprite == PROJECTILE_FIRE_SPRITE )
89 {
90 if ( ARROW_LIFE > 1 )
91 {
92 int intensity = 64;
93 if ( abs(ARROW_VELX) < 0.01 && abs(ARROW_VELY) < 0.01 )
94 {
95 intensity = 192;
96 }
97 my->light = lightSphereShadow(my->x / 16, my->y / 16, 5, intensity);
98 if ( flickerLights )
99 {
100 //Torches will never flicker if this setting is disabled.
101 ARROW_FLICKER++;
102 }
103 if ( ARROW_FLICKER > 5 )
104 {
105 ARROW_LIGHTING = (ARROW_LIGHTING == 1) + 1;
106
107 if ( ARROW_LIGHTING == 1 )
108 {
109 my->removeLightField();
110 my->light = lightSphereShadow(my->x / 16, my->y / 16, 5, intensity);
111 }
112 else
113 {
114 my->removeLightField();
115 my->light = lightSphereShadow(my->x / 16, my->y / 16, 5, intensity - 16);
116 }
117 ARROW_FLICKER = 0;
118 }
119
120 if ( ARROW_STUCK != 0 )
121 {
122 Entity* entity = spawnFlame(my, SPRITE_FLAME);
123 if ( ARROW_STUCK == 1 )
124 {
125 entity->x += .5 * cos(my->yaw);
126 entity->y += .5 * sin(my->yaw);
127 }
128 else
129 {
130 entity->x += 1.5 * cos(my->yaw);
131 entity->y += 1.5 * sin(my->yaw);
132 }
133 entity->z = my->z;
134 }
135 else
136 {
137 Entity* flame = spawnMagicParticleCustom(my, SPRITE_FLAME, 0.5, 4); // this looks nicer than the spawnFlame :)
138 if ( flame )
139 {
140 flame->flags[SPRITE] = true;
141 }
142 }
143 }
144 }
145 else if ( my->arrowQuiverType == QUIVER_KNOCKBACK || my->sprite == PROJECTILE_HEAVY_SPRITE )
146 {
147 if ( ARROW_STUCK == 0 )
148 {
149 Entity* particle = spawnMagicParticleCustom(my, 159, 0.5, 4);
150 if ( particle )
151 {
152 particle->flags[SPRITE] = true;
153 }
154 }
155 }
156 else if ( my->arrowQuiverType == QUIVER_SILVER || my->sprite == PROJECTILE_SILVER_SPRITE )
157 {
158 if ( ARROW_STUCK == 0 )
159 {
160 Entity* particle = spawnMagicParticleCustom(my, 160, 0.5, 4);
161 if ( particle )
162 {
163 particle->flags[SPRITE] = true;
164 }
165 }
166 }
167 else if ( my->arrowQuiverType == QUIVER_CRYSTAL || my->sprite == PROJECTILE_CRYSTAL_SPRITE )
168 {
169 if ( ARROW_STUCK == 0 )
170 {
171 Entity* particle = spawnMagicParticleCustom(my, 155, 0.5, 4);
172 if ( particle )
173 {
174 particle->flags[SPRITE] = true;
175 }
176 }
177 }
178 else if ( my->arrowQuiverType == QUIVER_PIERCE || my->sprite == PROJECTILE_PIERCE_SPRITE )
179 {
180 if ( ARROW_STUCK == 0 )
181 {
182 Entity* particle = spawnMagicParticleCustom(my, 158, 0.5, 4);
183 if ( particle )
184 {
185 particle->flags[SPRITE] = true;
186 }
187 }
188 }
189 else if ( my->arrowQuiverType == QUIVER_LIGHTWEIGHT || my->sprite == PROJECTILE_SWIFT_SPRITE )
190 {
191 if ( ARROW_STUCK == 0 )
192 {
193 Entity* particle = spawnMagicParticleCustom(my, 156, 0.5, 4);
194 if ( particle )
195 {
196 particle->flags[SPRITE] = true;
197 }
198 }
199 }
200 else if ( my->arrowQuiverType == QUIVER_HUNTING || my->sprite == PROJECTILE_HUNTING_SPRITE )
201 {
202 if ( ARROW_STUCK == 0 )
203 {
204 Entity* particle = spawnMagicParticleCustom(my, 157, 0.5, 4);
205 if ( particle )
206 {
207 particle->flags[SPRITE] = true;
208 }
209 }
210 }
211
212 if ( multiplayer != CLIENT )
213 {
214 my->skill[2] = -(1000 + my->arrowShotByWeapon); // invokes actArrow for clients.
215 my->flags[INVISIBLE] = false;
216 }
217
218 if ( !ARROW_INIT )
219 {
220 if ( multiplayer == CLIENT )
221 {
222 if ( my->setArrowProjectileProperties(my->arrowShotByWeapon) )
223 {
224 ARROW_INIT = 1;
225 }
226 else
227 {
228 return;
229 }
230 }
231 else
232 {
233 if ( my->arrowPower == 0 )
234 {
235 my->arrowPower = 10 + (my->sprite == PROJECTILE_BOLT_SPRITE);
236 }
237 if ( my->arrowShotByParent == 0 ) // shot by trap
238 {
239 my->arrowSpeed = 7;
240 }
241 ARROW_INIT = 1;
242 }
243 }
244
245 if ( ARROW_STUCK == 0 )
246 {
247 if ( my->arrowFallSpeed > 0 )
248 {
249 real_t pitchChange = 0.02;
250 if ( my->arrowShotByWeapon == LONGBOW )
251 {
252 pitchChange = 0.005;
253 }
254 if ( my->arrowBoltDropOffRange > 0 )
255 {
256 if ( my->ticks >= my->arrowBoltDropOffRange )
257 {
258 ARROW_VELZ += my->arrowFallSpeed;
259 my->z += ARROW_VELZ;
260 my->pitch = std::min(my->pitch + pitchChange, PI / 8);
261 }
262 }
263 else
264 {
265 ARROW_VELZ += my->arrowFallSpeed;
266 my->z += ARROW_VELZ;
267 my->pitch = std::min(my->pitch + pitchChange, PI / 8);
268 }
269 }
270
271 Entity* arrowSpawnedInsideEntity = nullptr;
272 if ( ARROW_LIFE == 1 && my->arrowShotByParent == 0 && multiplayer != CLIENT ) // shot by trap
273 {
274 Entity* parent = uidToEntity(my->parent);
275 if ( parent && parent->behavior == &actArrowTrap )
276 {
277 for ( node_t* node = map.creatures->first; node != nullptr; node = node->next )
278 {
279 entity = (Entity*)node->element;
280 if ( entity && (entity->behavior == &actMonster || entity->behavior == &actPlayer) )
281 {
282 if ( entityInsideEntity(my, entity) )
283 {
284 arrowSpawnedInsideEntity = entity;
285 }
286 break;
287 }
288 }
289 }
290 }
291
292 if ( multiplayer != CLIENT )
293 {
294 // horizontal motion
295 ARROW_VELX = cos(my->yaw) * my->arrowSpeed;
296 ARROW_VELY = sin(my->yaw) * my->arrowSpeed;
297 ARROW_OLDX = my->x;
298 ARROW_OLDY = my->y;
299 dist = clipMove(&my->x, &my->y, ARROW_VELX, ARROW_VELY, my);
300 }
301
302 bool arrowInGround = false;
303 int index = (int)(my->y / 16) * MAPLAYERS + (int)(my->x / 16) * MAPLAYERS * map.height;
304 if ( map.tiles[index] )
305 {
306 if ( my->sprite == PROJECTILE_BOLT_SPRITE || my->sprite == PROJECTILE_ROCK_SPRITE ) // bolt/rock
307 {
308 if ( my->z >= 7 )
309 {
310 arrowInGround = true;
311 }
312 }
313 else
314 {
315 if ( my->pitch >= PI / 12 ) // heavily pitched
316 {
317 if ( my->z >= 6.5 )
318 {
319 arrowInGround = true;
320 }
321 }
322 else
323 {
324 if ( my->z >= 7 ) // shallow pitch, make ground criteria lower.
325 {
326 arrowInGround = true;
327 }
328 }
329 }
330
331 if ( arrowInGround && (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) )
332 {
333 my->removeLightField();
334 list_RemoveNode(my->mynode);
335 return;
336 }
337 }
338
339 if ( multiplayer == CLIENT )
340 {
341 if ( arrowInGround )
342 {
343 ARROW_STUCK = 2;
344 }
345 if ( fabs(ARROW_VELX) < 0.01 && fabs(ARROW_VELY) < 0.01 )
346 {
347 ++ARROW_STUCK_CLIENT;
348 }
349 if ( ARROW_STUCK_CLIENT > 5 )
350 {
351 // sometimes the arrow may have 0 velocity during normal movement updates from the server.
352 // let it hit 5 iterations before the client assumes it's stuck so z velocity updates no longer happen
353 ARROW_STUCK = 1;
354 }
355 return;
356 }
357
358 // damage monsters
359 if ( arrowSpawnedInsideEntity || dist != sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY) || arrowInGround )
360 {
361 if ( arrowInGround )
362 {
363 ARROW_STUCK = 2;
364 }
365 else
366 {
367 ARROW_STUCK = 1;
368 }
369 serverUpdateEntitySkill(my, 0);
370 my->x = ARROW_OLDX;
371 my->y = ARROW_OLDY;
372
373 if ( arrowSpawnedInsideEntity )
374 {
375 hit.entity = arrowSpawnedInsideEntity;
376 }
377 Entity* oentity = hit.entity;
378 lineTrace(my, my->x, my->y, my->yaw, sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY), 0, false);
379 hit.entity = oentity;
380 my->x = hit.x - cos(my->yaw);
381 my->y = hit.y - sin(my->yaw);
382 ARROW_VELX = 0;
383 ARROW_VELY = 0;
384 ARROW_VELZ = 0;
385 my->entityCheckIfTriggeredBomb(true);
386 if ( hit.entity != NULL )
387 {
388 Entity* parent = uidToEntity(my->parent);
389 Stat* hitstats = hit.entity->getStats();
390 playSoundEntity(my, 72 + rand() % 3, 64);
391 if ( hitstats != NULL && hit.entity != parent )
392 {
393 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
394 {
395 // test for friendly fire
396 if ( parent && parent->checkFriend(hit.entity) )
397 {
398 my->removeLightField();
399 list_RemoveNode(my->mynode);
400 return;
401 }
402 }
403
404 bool silverDamage = false;
405 bool huntingDamage = false;
406 if ( my->arrowQuiverType == QUIVER_SILVER )
407 {
408 switch ( hitstats->type )
409 {
410 case SKELETON:
411 case CREATURE_IMP:
412 case GHOUL:
413 case DEMON:
414 case SUCCUBUS:
415 case INCUBUS:
416 case VAMPIRE:
417 case LICH:
418 case LICH_ICE:
419 case LICH_FIRE:
420 case DEVIL:
421 // smite these creatures
422 silverDamage = true;
423 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 981);
424 playSoundEntity(hit.entity, 249, 64);
425 break;
426 default:
427 silverDamage = false;
428 break;
429 }
430 }
431 else if ( my->arrowQuiverType == QUIVER_HUNTING )
432 {
433 switch ( hitstats->type )
434 {
435 case RAT:
436 case SPIDER:
437 case SCORPION:
438 case SCARAB:
439 case SLIME:
440 case CREATURE_IMP:
441 case DEMON:
442 case MINOTAUR:
443 case KOBOLD:
444 case COCKATRICE:
445 case TROLL:
446 // more damage to these creatures
447 huntingDamage = true;
448 for ( int gibs = 0; gibs < 10; ++gibs )
449 {
450 Entity* gib = spawnGib(hit.entity);
451 serverSpawnGibForClient(gib);
452 }
453 break;
454 default:
455 huntingDamage = false;
456 break;
457 }
458 }
459
460 // do damage
461 if ( my->arrowArmorPierce > 0 && AC(hitstats) > 0 )
462 {
463 if ( my->arrowQuiverType == QUIVER_PIERCE )
464 {
465 bool oldDefend = hitstats->defending;
466 hitstats->defending = false;
467 damage = std::max(my->arrowPower - (AC(hitstats) / 2), 0); // pierce half armor not caring about shield
468 hitstats->defending = oldDefend;
469 }
470 else
471 {
472 damage = std::max(my->arrowPower - (AC(hitstats) / 2), 0); // pierce half armor.
473 }
474 }
475 else
476 {
477 damage = std::max(my->arrowPower - AC(hitstats), 0); // normal damage.
478 }
479
480 if ( silverDamage || huntingDamage )
481 {
482 damage *= 1.5;
483 }
484
485 bool hitWeaklyOnTarget = false;
486 int nominalDamage = damage;
487 if ( parent )
488 {
489 if ( my->arrowFallSpeed > 0 )
490 {
491 if ( my->z >= 5.5 )
492 {
493 switch ( hitstats->type )
494 {
495 case RAT:
496 case SPIDER:
497 case SCORPION:
498 case SCARAB:
499 case GNOME:
500 case KOBOLD:
501 // small creatures, no penalty for low shot.
502 hitWeaklyOnTarget = false;
503 break;
504 default:
505 hitWeaklyOnTarget = true;
506 break;
507 }
508 }
509 }
510 }
511 real_t damageMultiplier = hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_RANGED);
512 if ( huntingDamage || silverDamage )
513 {
514 damageMultiplier = std::max(0.75, damageMultiplier);
515 }
516
517 if ( hitWeaklyOnTarget )
518 {
519 damage = damage * (std::max(0.1, damageMultiplier - 0.5));
520 }
521 else
522 {
523 damage *= damageMultiplier;
524 }
525 /*messagePlayer(0, "My damage: %d, AC: %d, Pierce: %d", my->arrowPower, AC(hitstats), my->arrowArmorPierce);
526 messagePlayer(0, "Resolved to %d damage.", damage);*/
527 hit.entity->modHP(-damage);
528 // write obituary
529 if ( parent )
530 {
531 if ( parent->behavior == &actArrowTrap )
532 {
533 hit.entity->setObituary(language[1503]);
534 }
535 else
536 {
537 parent->killedByMonsterObituary(hit.entity);
538 }
539
540 if ( hit.entity->behavior == &actMonster && parent->behavior == &actPlayer )
541 {
542 if ( damage >= 80 && hitstats->type != HUMAN && !parent->checkFriend(hit.entity) )
543 {
544 achievementObserver.awardAchievement(parent->skill[2], AchievementObserver::BARONY_ACH_FELL_BEAST);
545 }
546 if ( my->arrowQuiverType == QUIVER_LIGHTWEIGHT
547 && my->arrowShotByWeapon == COMPOUND_BOW )
548 {
549 achievementObserver.updatePlayerAchievement(parent->skill[2], AchievementObserver::BARONY_ACH_STRUNG_OUT, AchievementObserver::ACH_EVENT_NONE);
550 }
551 }
552 }
553
554 if ( damage > 0 )
555 {
556 Entity* gib = spawnGib(hit.entity);
557 serverSpawnGibForClient(gib);
558 playSoundEntity(hit.entity, 28, 64);
559 if ( hit.entity->behavior == &actPlayer )
560 {
561 if ( hit.entity->skill[2] == clientnum || splitscreen )
562 {
563 cameravars[hit.entity->skill[2]].shakex += .1;
564 cameravars[hit.entity->skill[2]].shakey += 10;
565 }
566 else
567 {
568 if ( hit.entity->skill[2] > 0 )
569 {
570 strcpy((char*)net_packet->data, "SHAK");
571 net_packet->data[4] = 10; // turns into .1
572 net_packet->data[5] = 10;
573 net_packet->address.host = net_clients[hit.entity->skill[2] - 1].host;
574 net_packet->address.port = net_clients[hit.entity->skill[2] - 1].port;
575 net_packet->len = 6;
576 sendPacketSafe(net_sock, -1, net_packet, hit.entity->skill[2] - 1);
577 }
578 }
579 }
580 if ( rand() % 10 == 0 && parent )
581 {
582 parent->increaseSkill(PRO_RANGED);
583 }
584 }
585 else
586 {
587 // playSoundEntity(hit.entity, 66, 64); //*tink*
588 if ( hit.entity->behavior == &actPlayer )
589 {
590 if ( hit.entity->skill[2] == clientnum || splitscreen )
591 {
592 cameravars[hit.entity->skill[2]].shakex += .05;
593 cameravars[hit.entity->skill[2]].shakey += 5;
594 }
595 else
596 {
597 if ( hit.entity->skill[2] > 0 )
598 {
599 strcpy((char*)net_packet->data, "SHAK");
600 net_packet->data[4] = 5; // turns into .05
601 net_packet->data[5] = 5;
602 net_packet->address.host = net_clients[hit.entity->skill[2] - 1].host;
603 net_packet->address.port = net_clients[hit.entity->skill[2] - 1].port;
604 net_packet->len = 6;
605 sendPacketSafe(net_sock, -1, net_packet, hit.entity->skill[2] - 1);
606 }
607 }
608 }
609 }
610
611 if ( hitstats->HP <= 0 && parent)
612 {
613 parent->awardXP( hit.entity, true, true );
614 }
615
616 // alert the monster
617 if ( hit.entity->behavior == &actMonster && parent != nullptr )
618 {
619 bool alertTarget = true;
620 if ( parent->behavior == &actMonster && parent->monsterAllyIndex != -1 )
621 {
622 if ( hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1 )
623 {
624 // if a player ally + hit another ally, don't aggro back
625 alertTarget = false;
626 }
627 }
628
629 if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) )
630 {
631 hit.entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true);
632 }
633
634 bool alertAllies = true;
635 if ( parent->behavior == &actPlayer || parent->monsterAllyIndex != -1 )
636 {
637 if ( hit.entity->behavior == &actPlayer || (hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1) )
638 {
639 // if a player ally + hit another ally or player, don't alert other allies.
640 alertAllies = false;
641 }
642 }
643
644 // alert other monsters too
645 Entity* ohitentity = hit.entity;
646 for ( node = map.creatures->first; node != nullptr && alertAllies; node = node->next )
647 {
648 entity = (Entity*)node->element;
649 if ( entity && entity->behavior == &actMonster && entity != ohitentity )
650 {
651 Stat* buddystats = entity->getStats();
652 if ( buddystats != nullptr )
653 {
654 if ( entity->checkFriend(hit.entity) )
655 {
656 if ( entity->monsterState == MONSTER_STATE_WAIT ) // monster is waiting
657 {
658 tangent = atan2( entity->y - ohitentity->y, entity->x - ohitentity->x );
659 lineTrace(ohitentity, ohitentity->x, ohitentity->y, tangent, 1024, 0, false);
660 if ( hit.entity == entity )
661 {
662 entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH);
663 }
664 }
665 }
666 }
667 }
668 }
669 hit.entity = ohitentity;
670 if ( parent->behavior == &actPlayer )
671 {
672 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
673 if ( huntingDamage )
674 {
675 // plunges into the %s!
676 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3750], language[3751], MSG_COMBAT);
677 }
678 else if ( silverDamage )
679 {
680 // smites the %s!
681 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3743], language[3744], MSG_COMBAT);
682 }
683 else if ( damage <= (nominalDamage * .7) )
684 {
685 // weak shot.
686 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3733], language[3734], MSG_COMBAT);
687 }
688 else
689 {
690 // you shot the %s!
691 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[446], language[448], MSG_COMBAT);
692 }
693 if ( my->arrowArmorPierce > 0 && AC(hitstats) > 0 )
694 {
695 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2513], language[2514], MSG_COMBAT);
696 }
697 }
698 }
699 if ( hit.entity->behavior == &actPlayer )
700 {
701 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
702 if ( silverDamage )
703 {
704 messagePlayerColor(hit.entity->skill[2], color, language[3745]); // you are smited!
705 }
706 else if ( huntingDamage )
707 {
708 messagePlayerColor(hit.entity->skill[2], color, language[3752]); // arrow plunged into you!
709 }
710 else if ( my->arrowQuiverType == QUIVER_KNOCKBACK )
711 {
712 // no "hit by arrow!" message, let the knockback do the work.
713 }
714 else if ( my->arrowQuiverType == QUIVER_HUNTING && !(hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE)
715 && !(hitstats->type == INSECTOID) )
716 {
717 // no "hit by arrow!" message, let the hunting arrow effect do the work.
718 }
719 else
720 {
721 if ( my )
722 {
723 if ( my->sprite == PROJECTILE_ROCK_SPRITE )
724 {
725 // rock.
726 messagePlayerColor(hit.entity->skill[2], color, language[2512]);
727 }
728 else if (my->sprite == PROJECTILE_BOLT_SPRITE )
729 {
730 // bolt.
731 messagePlayerColor(hit.entity->skill[2], color, language[2511]);
732 }
733 else
734 {
735 // arrow.
736 messagePlayerColor(hit.entity->skill[2], color, language[451]);
737 }
738 }
739 else
740 {
741 messagePlayerColor(hit.entity->skill[2], color, language[451]);
742 }
743 }
744
745 if ( my->arrowArmorPierce > 0 && AC(hitstats) > 0 )
746 {
747 messagePlayerColor(hit.entity->skill[2], color, language[2515]);
748 }
749 }
750
751 bool statusEffectApplied = false;
752 if ( hitstats->HP > 0 )
753 {
754 if ( my->arrowQuiverType == QUIVER_FIRE )
755 {
756 bool burning = hit.entity->flags[BURNING];
757 hit.entity->SetEntityOnFire(my);
758 if ( hitstats )
759 {
760 hitstats->burningInflictedBy = static_cast<Sint32>(my->parent);
761 }
762 if ( !burning && hit.entity->flags[BURNING] )
763 {
764 if ( parent && parent->behavior == &actPlayer )
765 {
766 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
767 if ( hitstats )
768 {
769 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3739], language[3740], MSG_COMBAT);
770 if ( hit.entity->behavior == &actMonster )
771 {
772 achievementObserver.addEntityAchievementTimer(hit.entity, AchievementObserver::BARONY_ACH_PLEASE_HOLD, 150, true, 0);
773 }
774 }
775 }
776 if ( hit.entity->behavior == &actPlayer )
777 {
778 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
779 messagePlayerColor(hit.entity->skill[2], color, language[3741]);
780 }
781 statusEffectApplied = true;
782 }
783 }
784 else if ( my->arrowQuiverType == QUIVER_KNOCKBACK && hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) )
785 {
786 real_t pushbackMultiplier = 0.6;
787 if ( !hit.entity->isMobile() )
788 {
789 pushbackMultiplier += 0.3;
790 }
791 /*if ( hitWeaklyOnTarget )
792 {
793 pushbackMultiplier -= 0.3;
794 }*/
795
796 if ( hit.entity->behavior == &actMonster )
797 {
798 if ( parent )
799 {
800 real_t tangent = atan2(hit.entity->y - parent->y, hit.entity->x - parent->x);
801 hit.entity->vel_x = cos(tangent) * pushbackMultiplier;
802 hit.entity->vel_y = sin(tangent) * pushbackMultiplier;
803 hit.entity->monsterKnockbackVelocity = 0.01;
804 hit.entity->monsterKnockbackUID = my->parent;
805 hit.entity->monsterKnockbackTangentDir = tangent;
806 //hit.entity->lookAtEntity(*parent);
807 }
808 else
809 {
810 real_t tangent = atan2(hit.entity->y - my->y, hit.entity->x - my->x);
811 hit.entity->vel_x = cos(tangent) * pushbackMultiplier;
812 hit.entity->vel_y = sin(tangent) * pushbackMultiplier;
813 hit.entity->monsterKnockbackVelocity = 0.01;
814 hit.entity->monsterKnockbackTangentDir = tangent;
815 //hit.entity->lookAtEntity(*my);
816 }
817 }
818 else if ( hit.entity->behavior == &actPlayer )
819 {
820 if ( parent )
821 {
822 real_t dist = entityDist(parent, hit.entity);
823 if ( dist < TOUCHRANGE )
824 {
825 pushbackMultiplier += 0.5;
826 }
827 }
828 if ( hit.entity->skill[2] != clientnum )
829 {
830 hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
831 hit.entity->monsterKnockbackTangentDir = my->yaw;
832 serverUpdateEntityFSkill(hit.entity, 11);
833 serverUpdateEntityFSkill(hit.entity, 9);
834 }
835 else
836 {
837 hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
838 hit.entity->monsterKnockbackTangentDir = my->yaw;
839 }
840 }
841
842 if ( parent && parent->behavior == &actPlayer )
843 {
844 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
845 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3215], language[3214], MSG_COMBAT);
846
847 if ( hit.entity->behavior == &actMonster )
848 {
849 achievementObserver.awardAchievementIfActive(parent->skill[2], hit.entity, AchievementObserver::BARONY_ACH_PLEASE_HOLD);
850 }
851 }
852 if ( hit.entity->behavior == &actPlayer )
853 {
854 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
855 messagePlayerColor(hit.entity->skill[2], color, language[3742]);
856 }
857
858 if ( hit.entity->monsterAttack == 0 )
859 {
860 hit.entity->monsterHitTime = std::max(HITRATE - 12, hit.entity->monsterHitTime);
861 }
862 statusEffectApplied = true;
863 }
864 else if ( my->arrowQuiverType == QUIVER_HUNTING && !(hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE)
865 && !(hitstats->type == INSECTOID) )
866 {
867 if ( !hitstats->EFFECTS[EFF_POISONED] )
868 {
869 hitstats->poisonKiller = my->parent;
870 hitstats->EFFECTS[EFF_POISONED] = true;
871 hitstats->EFFECTS[EFF_SLOW] = true;
872 if ( my->arrowPoisonTime > 0 )
873 {
874 hitstats->EFFECTS_TIMERS[EFF_POISONED] = my->arrowPoisonTime;
875 hitstats->EFFECTS_TIMERS[EFF_SLOW] = my->arrowPoisonTime;
876 }
877 else
878 {
879 hitstats->EFFECTS_TIMERS[EFF_POISONED] = 160;
880 hitstats->EFFECTS_TIMERS[EFF_SLOW] = 160;
881 }
882 statusEffectApplied = true;
883 }
884 if ( statusEffectApplied )
885 {
886 serverUpdateEffects(hit.entity->skill[2]);
887 if ( parent && parent->behavior == &actPlayer )
888 {
889 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
890 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3747], language[3748], MSG_COMBAT);
891
892 achievementObserver.addEntityAchievementTimer(hit.entity, AchievementObserver::BARONY_ACH_PLEASE_HOLD, 150, true, 0);
893 }
894 if ( hit.entity->behavior == &actPlayer )
895 {
896 if ( rand() % 8 == 0 && hit.entity->skill[26] == 0 && !hitstats->EFFECTS[EFF_VOMITING] )
897 {
898 // maybe vomit
899 messagePlayer(hit.entity->skill[2], language[634]);
900 if ( hitstats->type != SKELETON
901 && hit.entity->effectShapeshift == NOTHING
902 && hitstats->type != AUTOMATON )
903 {
904 hit.entity->skill[26] = 140 + rand() % 60;
905 }
906 }
907 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
908 messagePlayerColor(hit.entity->skill[2], color, language[3749]);
909 }
910 }
911 else if ( hit.entity->behavior == &actPlayer )
912 {
913 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
914 messagePlayerColor(hit.entity->skill[2], color, language[451]); // you are hit by an arrow!
915 }
916 }
917 }
918 else
919 {
920 // HP <= 0
921 if ( parent && parent->behavior == &actPlayer )
922 {
923 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
924 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[692], language[697], MSG_COMBAT);
925 }
926 }
927
928 if ( damage == 0 && !statusEffectApplied )
929 {
930 playSoundEntity(hit.entity, 66, 64); //*tink*
931 if ( hit.entity->behavior == &actPlayer )
932 {
933 messagePlayer(hit.entity->skill[2], language[452]); // player notified no damage.
934 }
935 if ( parent && parent->behavior == &actPlayer )
936 {
937 if ( hitstats->type == HUMAN )
938 {
939 if ( hitstats->sex )
940 {
941 messagePlayer(parent->skill[2], language[449]);
942 }
943 else
944 {
945 messagePlayer(parent->skill[2], language[450]);
946 }
947 }
948 else
949 {
950 messagePlayer(parent->skill[2], language[447]);
951 }
952 }
953 }
954 else if ( damage == 0 && statusEffectApplied )
955 {
956 playSoundEntity(hit.entity, 28, 64);
957 }
958
959 // update enemy bar for attacker
960 if ( !strcmp(hitstats->name, "") )
961 {
962 if ( hitstats->type < KOBOLD ) //Original monster count
963 {
964 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
965 }
966 else if ( hitstats->type >= KOBOLD ) //New monsters
967 {
968 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
969 }
970
971 }
972 else
973 {
974 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
975 }
976
977 }
978 my->removeLightField();
979 list_RemoveNode(my->mynode);
980 }
981 else if ( my->sprite == PROJECTILE_ROCK_SPRITE )
982 {
983 my->removeLightField();
984 list_RemoveNode(my->mynode); // rocks don't stick to walls...
985 }
986 else
987 {
988 playSoundEntity(my, 72 + rand() % 3, 64);
989 }
990 }
991 }
992 }
993