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