1 /*-------------------------------------------------------------------------------
2 
3 BARONY
4 File: entity.cpp
5 Desc: implements entity code
6 
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9 
10 -------------------------------------------------------------------------------*/
11 
12 #include "main.hpp"
13 #include "game.hpp"
14 #include "stat.hpp"
15 #include "entity.hpp"
16 #include "items.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "magic/magic.hpp"
20 #include "interface/interface.hpp"
21 #include "net.hpp"
22 #include "collision.hpp"
23 #include "paths.hpp"
24 #include "book.hpp"
25 #ifdef STEAMWORKS
26 #include <steam/steam_api.h>
27 #endif
28 #include "player.hpp"
29 #include "scores.hpp"
30 #include "menu.hpp"
31 #include "mod_tools.hpp"
32 #ifdef __ARM_NEON__
33 #include <arm_neon.h>
34 #endif
35 
36 /*-------------------------------------------------------------------------------
37 
38 Entity::Entity)
39 
40 Construct an Entity
41 
42 -------------------------------------------------------------------------------*/
43 
Entity(Sint32 in_sprite,Uint32 pos,list_t * entlist,list_t * creaturelist)44 Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creaturelist) :
45 	char_gonnavomit(skill[26]),
46 	char_heal(skill[22]),
47 	char_energize(skill[23]),
48 	char_torchtime(skill[25]),
49 	char_poison(skill[21]),
50 	char_fire(skill[36]),
51 	chanceToPutOutFire(skill[37]),
52 	circuit_status(skill[28]),
53 	switch_power(skill[0]),
54 	chestInit(skill[0]),
55 	chestStatus(skill[1]),
56 	chestHealth(skill[3]),
57 	chestLocked(skill[4]),
58 	chestOpener(skill[5]),
59 	chestLidClicked(skill[6]),
60 	chestAmbience(skill[7]),
61 	chestMaxHealth(skill[8]),
62 	chestType(skill[9]),
63 	chestPreventLockpickCapstoneExploit(skill[10]),
64 	chestHasVampireBook(skill[11]),
65 	chestLockpickHealth(skill[12]),
66 	monsterState(skill[0]),
67 	monsterTarget(skill[1]),
68 	monsterTargetX(fskill[2]),
69 	monsterTargetY(fskill[3]),
70 	crystalInitialised(skill[1]),
71 	crystalTurning(skill[3]),
72 	crystalTurnStartDir(skill[4]),
73 	crystalGeneratedElectricityNodes(skill[5]),
74 	crystalNumElectricityNodes(skill[6]),
75 	crystalHoverDirection(skill[7]),
76 	crystalHoverWaitTimer(skill[8]),
77 	crystalTurnReverse(skill[9]),
78 	crystalSpellToActivate(skill[10]),
79 	crystalStartZ(fskill[0]),
80 	crystalMaxZVelocity(fskill[1]),
81 	crystalMinZVelocity(fskill[2]),
82 	crystalTurnVelocity(fskill[3]),
83 	monsterAnimationLimbDirection(skill[20]),
84 	monsterAnimationLimbOvershoot(skill[30]),
85 	monsterSpecialTimer(skill[29]),
86 	monsterSpecialState(skill[33]),
87 	monsterSpellAnimation(skill[31]),
88 	monsterFootstepType(skill[32]),
89 	monsterLookTime(skill[4]),
90 	monsterMoveTime(skill[6]),
91 	monsterLookDir(fskill[4]),
92 	monsterEntityRenderAsTelepath(skill[41]),
93 	playerLevelEntrySpeech(skill[18]),
94 	playerAliveTime(skill[12]),
95 	playerVampireCurse(skill[51]),
96 	playerAutomatonDeathCounter(skill[15]),
97 	playerCreatedDeathCam(skill[16]),
98 	monsterAttack(skill[8]),
99 	monsterAttackTime(skill[9]),
100 	monsterArmbended(skill[10]),
101 	monsterWeaponYaw(fskill[5]),
102 	monsterShadowInitialMimic(skill[34]),
103 	monsterShadowDontChangeName(skill[35]),
104 	monsterLichFireMeleeSeq(skill[34]),
105 	monsterLichFireMeleePrev(skill[35]),
106 	monsterLichIceCastSeq(skill[34]),
107 	monsterLichIceCastPrev(skill[35]),
108 	monsterLichMagicCastCount(skill[37]),
109 	monsterLichMeleeSwingCount(skill[38]),
110 	monsterLichBattleState(skill[27]),
111 	monsterLichTeleportTimer(skill[40]),
112 	monsterLichAllyStatus(skill[18]),
113 	monsterLichAllyUID(skill[17]),
114 	monsterPathBoundaryXStart(skill[14]),
115 	monsterPathBoundaryYStart(skill[15]),
116 	monsterPathBoundaryXEnd(skill[16]),
117 	monsterPathBoundaryYEnd(skill[17]),
118 	monsterStoreType(skill[18]),
119 	monsterStrafeDirection(skill[39]),
120 	monsterPathCount(skill[38]),
121 	monsterAllyIndex(skill[42]),
122 	monsterAllyState(skill[43]),
123 	monsterAllyPickupItems(skill[44]),
124 	monsterAllyInteractTarget(skill[45]),
125 	monsterAllyClass(skill[46]),
126 	monsterDefend(skill[47]),
127 	monsterAllySpecial(skill[48]),
128 	monsterAllySpecialCooldown(skill[49]),
129 	monsterAllySummonRank(skill[50]),
130 	monsterKnockbackVelocity(fskill[9]),
131 	monsterKnockbackUID(skill[51]),
132 	creatureWebbedSlowCount(skill[52]),
133 	monsterFearfulOfUid(skill[53]),
134 	creatureShadowTaggedThisUid(skill[54]),
135 	monsterIllusionTauntingThisUid(skill[55]),
136 	monsterLastDistractedByNoisemaker(skill[55]), // shares with above as above only applies to inner demons.
137 	monsterSentrybotLookDir(fskill[10]),
138 	monsterKnockbackTangentDir(fskill[11]),
139 	playerStrafeVelocity(fskill[12]),
140 	playerStrafeDir(fskill[13]),
141 	particleDuration(skill[0]),
142 	particleShrink(skill[1]),
143 	monsterHitTime(skill[7]),
144 	itemNotMoving(skill[18]),
145 	itemNotMovingClient(skill[19]),
146 	itemSokobanReward(skill[20]),
147 	itemOriginalOwner(skill[21]),
148 	itemStolen(skill[22]),
149 	itemShowOnMap(skill[23]),
150 	itemDelayMonsterPickingUp(skill[24]),
151 	itemReceivedDetailsFromServer(skill[25]),
152 	itemAutoSalvageByPlayer(skill[26]),
153 	gateInit(skill[1]),
154 	gateStatus(skill[3]),
155 	gateRattle(skill[4]),
156 	gateStartHeight(fskill[0]),
157 	gateVelZ(vel_z),
158 	gateInverted(skill[5]),
159 	gateDisableOpening(skill[6]),
160 	leverStatus(skill[1]),
161 	leverTimerTicks(skill[3]),
162 	boulderTrapRefireAmount(skill[1]),
163 	boulderTrapRefireDelay(skill[3]),
164 	boulderTrapAmbience(skill[6]),
165 	boulderTrapFired(skill[0]),
166 	boulderTrapRefireCounter(skill[4]),
167 	boulderTrapPreDelay(skill[5]),
168 	boulderTrapRocksToSpawn(skill[7]),
169 	doorDir(skill[0]),
170 	doorInit(skill[1]),
171 	doorStatus(skill[3]),
172 	doorHealth(skill[4]),
173 	doorLocked(skill[5]),
174 	doorSmacked(skill[6]),
175 	doorTimer(skill[7]),
176 	doorOldStatus(skill[8]),
177 	doorMaxHealth(skill[9]),
178 	doorStartAng(fskill[0]),
179 	doorPreventLockpickExploit(skill[10]),
180 	doorForceLockedUnlocked(skill[11]),
181 	doorDisableLockpicks(skill[12]),
182 	doorDisableOpening(skill[13]),
183 	doorLockpickHealth(skill[14]),
184 	particleTimerDuration(skill[0]),
185 	particleTimerEndAction(skill[1]),
186 	particleTimerEndSprite(skill[3]),
187 	particleTimerCountdownAction(skill[4]),
188 	particleTimerCountdownSprite(skill[5]),
189 	particleTimerTarget(skill[6]),
190 	particleTimerPreDelay(skill[7]),
191 	particleTimerVariable1(skill[8]),
192 	particleTimerVariable2(skill[9]),
193 	pedestalHasOrb(skill[0]),
194 	pedestalOrbType(skill[1]),
195 	pedestalInvertedPower(skill[3]),
196 	pedestalInGround(skill[4]),
197 	pedestalInit(skill[5]),
198 	pedestalAmbience(skill[6]),
199 	pedestalLockOrb(skill[7]),
200 	orbInitialised(skill[1]),
201 	orbHoverDirection(skill[7]),
202 	orbHoverWaitTimer(skill[8]),
203 	orbStartZ(fskill[0]),
204 	orbMaxZVelocity(fskill[1]),
205 	orbMinZVelocity(fskill[2]),
206 	orbTurnVelocity(fskill[3]),
207 	portalAmbience(skill[0]),
208 	portalInit(skill[1]),
209 	portalNotSecret(skill[3]),
210 	portalVictoryType(skill[4]),
211 	portalFireAnimation(skill[5]),
212 	portalCustomLevelsToJump(skill[6]),
213 	portalCustomRequiresPower(skill[7]),
214 	portalCustomSprite(skill[8]),
215 	portalCustomSpriteAnimationFrames(skill[9]),
216 	portalCustomZOffset(skill[10]),
217 	portalCustomLevelText1(skill[11]),
218 	portalCustomLevelText2(skill[12]),
219 	portalCustomLevelText3(skill[13]),
220 	portalCustomLevelText4(skill[14]),
221 	portalCustomLevelText5(skill[15]),
222 	portalCustomLevelText6(skill[16]),
223 	portalCustomLevelText7(skill[17]),
224 	portalCustomLevelText8(skill[18]),
225 	teleporterX(skill[0]),
226 	teleporterY(skill[1]),
227 	teleporterType(skill[3]),
228 	teleporterAmbience(skill[4]),
229 	spellTrapType(skill[0]),
230 	spellTrapRefire(skill[1]),
231 	spellTrapLatchPower(skill[3]),
232 	spellTrapFloorTile(skill[4]),
233 	spellTrapRefireRate(skill[5]),
234 	spellTrapAmbience(skill[6]),
235 	spellTrapInit(skill[7]),
236 	spellTrapCounter(skill[8]),
237 	spellTrapReset(skill[9]),
238 	ceilingTileModel(skill[0]),
239 	floorDecorationModel(skill[0]),
240 	floorDecorationRotation(skill[1]),
241 	floorDecorationHeightOffset(skill[3]),
242 	floorDecorationXOffset(skill[4]),
243 	floorDecorationYOffset(skill[5]),
244 	floorDecorationInteractText1(skill[8]),
245 	floorDecorationInteractText2(skill[9]),
246 	floorDecorationInteractText3(skill[10]),
247 	floorDecorationInteractText4(skill[11]),
248 	floorDecorationInteractText5(skill[12]),
249 	floorDecorationInteractText6(skill[13]),
250 	floorDecorationInteractText7(skill[14]),
251 	floorDecorationInteractText8(skill[15]),
252 	furnitureType(skill[0]),
253 	furnitureInit(skill[1]),
254 	furnitureDir(skill[3]),
255 	furnitureHealth(skill[4]),
256 	furnitureMaxHealth(skill[9]),
257 	furnitureTableRandomItemChance(skill[10]),
258 	furnitureTableSpawnChairs(skill[11]),
259 	pistonCamDir(skill[0]),
260 	pistonCamTimer(skill[1]),
261 	pistonCamRotateSpeed(fskill[0]),
262 	arrowPower(skill[3]),
263 	arrowPoisonTime(skill[4]),
264 	arrowArmorPierce(skill[5]),
265 	arrowSpeed(fskill[4]),
266 	arrowFallSpeed(fskill[5]),
267 	arrowBoltDropOffRange(skill[6]),
268 	arrowShotByWeapon(skill[7]),
269 	arrowQuiverType(skill[8]),
270 	arrowShotByParent(skill[9]),
271 	actmagicIsVertical(skill[6]),
272 	actmagicIsOrbiting(skill[7]),
273 	actmagicOrbitDist(skill[8]),
274 	actmagicOrbitVerticalDirection(skill[9]),
275 	actmagicOrbitLifetime(skill[10]),
276 	actmagicMirrorReflected(skill[24]),
277 	actmagicMirrorReflectedCaster(skill[12]),
278 	actmagicCastByMagicstaff(skill[13]),
279 	actmagicOrbitVerticalSpeed(fskill[2]),
280 	actmagicOrbitStartZ(fskill[3]),
281 	actmagicOrbitStationaryX(fskill[4]),
282 	actmagicOrbitStationaryY(fskill[5]),
283 	actmagicOrbitStationaryCurrentDist(fskill[6]),
284 	actmagicOrbitStationaryHitTarget(skill[14]),
285 	actmagicOrbitHitTargetUID1(skill[15]),
286 	actmagicOrbitHitTargetUID2(skill[16]),
287 	actmagicOrbitHitTargetUID3(skill[17]),
288 	actmagicOrbitHitTargetUID4(skill[18]),
289 	actmagicProjectileArc(skill[19]),
290 	actmagicOrbitCastFromSpell(skill[20]),
291 	actmagicSpellbookBonus(skill[21]),
292 	actmagicCastByTinkerTrap(skill[22]),
293 	actmagicTinkerTrapFriendlyFire(skill[23]),
294 	goldAmount(skill[0]),
295 	goldAmbience(skill[1]),
296 	goldSokoban(skill[2]),
297 	interactedByMonster(skill[47]),
298 	soundSourceFired(skill[0]),
299 	soundSourceToPlay(skill[1]),
300 	soundSourceVolume(skill[2]),
301 	soundSourceLatchOn(skill[3]),
302 	soundSourceDelay(skill[4]),
303 	soundSourceDelayCounter(skill[5]),
304 	soundSourceOrigin(skill[6]),
305 	lightSourceBrightness(skill[0]),
306 	lightSourceAlwaysOn(skill[1]),
307 	lightSourceInvertPower(skill[2]),
308 	lightSourceLatchOn(skill[3]),
309 	lightSourceRadius(skill[4]),
310 	lightSourceFlicker(skill[5]),
311 	lightSourceDelay(skill[6]),
312 	lightSourceDelayCounter(skill[7]),
313 	textSourceColorRGB(skill[0]),
314 	textSourceVariables4W(skill[1]),
315 	textSourceDelay(skill[2]),
316 	textSourceIsScript(skill[3]),
317 	textSourceBegin(skill[4]),
318 	signalActivateDelay(skill[1]),
319 	signalTimerInterval(skill[2]),
320 	signalTimerRepeatCount(skill[3]),
321 	signalTimerLatchInput(skill[4]),
322 	signalInputDirection(skill[5]),
323 	effectPolymorph(skill[50]),
324 	effectShapeshift(skill[53]),
325 	entityShowOnMap(skill[59]),
326 	thrownProjectilePower(skill[19]),
327 	thrownProjectileCharge(skill[20]),
328 	playerStartDir(skill[1])
329 {
330 	int c;
331 	// add the entity to the entity list
332 	if ( !pos )
333 	{
334 		mynode = list_AddNodeFirst(entlist);
335 	}
336 	else
337 	{
338 		mynode = list_AddNodeLast(entlist);
339 	}
340 	mynode->element = this;
341 	mynode->deconstructor = &entityDeconstructor;
342 	mynode->size = sizeof(Entity);
343 
344 	myCreatureListNode = nullptr;
345 	if ( creaturelist )
346 	{
347 		addToCreatureList(creaturelist);
348 	}
349 	myTileListNode = nullptr;
350 
351 	// now reset all of my data elements
352 	lastupdate = 0;
353 	lastupdateserver = 0;
354 	ticks = 0;
355 	x = 0;
356 	y = 0;
357 	z = 0;
358 	new_x = 0;
359 	new_y = 0;
360 	new_z = 0;
361 	focalx = 0;
362 	focaly = 0;
363 	focalz = 0;
364 	scalex = 1;
365 	scaley = 1;
366 	scalez = 1;
367 	vel_x = 0;
368 	vel_y = 0;
369 	vel_z = 0;
370 	sizex = 0;
371 	sizey = 0;
372 	yaw = 0;
373 	pitch = 0;
374 	roll = 0;
375 	new_yaw = 0;
376 	new_pitch = 0;
377 	new_roll = 0;
378 	sprite = in_sprite;
379 	light = nullptr;
380 	string = nullptr;
381 	children.first = nullptr;
382 	children.last = nullptr;
383 	//this->magic_effects = (list_t *) malloc(sizeof(list_t));
384 	//this->magic_effects->first = NULL; this->magic_effects->last = NULL;
385 	for ( c = 0; c < NUMENTITYSKILLS; ++c )
386 	{
387 		skill[c] = 0;
388 	}
389 	for ( c = 0; c < NUMENTITYFSKILLS; ++c )
390 	{
391 		fskill[c] = 0;
392 	}
393 	skill[2] = -1;
394 	for ( c = 0; c < 16; c++ )
395 	{
396 		flags[c] = false;
397 	}
398 	if ( entlist == map.entities )
399 	{
400 		if ( multiplayer != CLIENT || loading )
401 		{
402 			uid = entity_uids;
403 			entity_uids++;
404 			map.entities_map.insert({ uid, mynode });
405 		}
406 		else
407 		{
408 			uid = -2;
409 		}
410 	}
411 	else
412 	{
413 		uid = -2;
414 	}
415 	behavior = nullptr;
416 	ranbehavior = false;
417 	parent = 0;
418 	path = nullptr;
419 	monsterAllyIndex = -1; // set to -1 to not reference player indices 0-3.
420 	/*if ( checkSpriteType(this->sprite) > 1 )
421 	{
422 		setSpriteAttributes(this, nullptr, nullptr);
423 	}*/
424 
425 	clientStats = nullptr;
426 	clientsHaveItsStats = false;
427 }
428 
setUID(Uint32 new_uid)429 void Entity::setUID(Uint32 new_uid)
430 {
431 	if ( mynode->list == map.entities )
432 	{
433 		map.entities_map.erase(uid);
434 		map.entities_map.insert({ new_uid, mynode });
435 	}
436 	uid = new_uid;
437 }
438 
439 /*-------------------------------------------------------------------------------
440 
441 Entity::~Entity)
442 
443 Deconstruct an Entity
444 
445 -------------------------------------------------------------------------------*/
446 
~Entity()447 Entity::~Entity()
448 {
449 	node_t* node;
450 	//node_t *node2;
451 	int i;
452 	//deleteent_t *deleteent;
453 
454 	// remove any remaining "parent" references
455 	/*if( entity->mynode != NULL ) {
456 	if( entity->mynode->list != NULL ) {
457 	for( node2=entity->mynode->list->first; node2!=NULL; node2=node2->next ) {
458 	Entity *entity2 = (Entity *)node2->element;
459 	if( entity2 != entity && entity2 != NULL )
460 	if( entity2->parent == entity )
461 	entity2->parent = NULL;
462 	}
463 	}
464 	}*/
465 
466 	//Remove me from the
467 	if ( myCreatureListNode )
468 	{
469 		list_RemoveNode(myCreatureListNode);
470 		myCreatureListNode = nullptr;
471 	}
472 	if ( myTileListNode )
473 	{
474 		list_RemoveNode(myTileListNode);
475 		myTileListNode = nullptr;
476 	}
477 
478 	// alert clients of the entity's deletion
479 	if ( multiplayer == SERVER && !loading )
480 	{
481 		if ( mynode->list == map.entities && uid != 0 && flags[NOUPDATE] == false )
482 		{
483 			for ( i = 1; i < MAXPLAYERS; ++i )
484 			{
485 				if ( client_disconnected[i] == true )
486 				{
487 					continue;
488 				}
489 
490 				// create a reminder for the server to continue informing the client of the deletion
491 				/*deleteent = (deleteent_t *) malloc(sizeof(deleteent_t)); //TODO: C++-PORT: Replace with new + class.
492 				deleteent->uid = uid;
493 				deleteent->tries = 0;
494 				node = list_AddNodeLast(&entitiesToDelete[i]);
495 				node->element = deleteent;
496 				node->deconstructor = &defaultDeconstructor;*/
497 
498 				// send the delete entity command to the client
499 				strcpy((char*)net_packet->data, "ENTD");
500 				SDLNet_Write32((Uint32)uid, &net_packet->data[4]);
501 				net_packet->address.host = net_clients[i - 1].host;
502 				net_packet->address.port = net_clients[i - 1].port;
503 				net_packet->len = 8;
504 				/*if ( directConnect ) {
505 				SDLNet_UDP_Send(net_sock,-1,net_packet);
506 				} else {
507 				#ifdef STEAMWORKS
508 				SteamNetworking()->SendP2PPacket(*static_cast<CSteamID* >(steamIDRemote[i - 1]), net_packet->data, net_packet->len, k_EP2PSendUnreliable, 0);
509 				#endif
510 				}*/
511 				sendPacketSafe(net_sock, -1, net_packet, i - 1);
512 			}
513 		}
514 	}
515 
516 	// set appropriate player pointer to NULL
517 	for ( i = 0; i < MAXPLAYERS; ++i )
518 	{
519 		if ( this == players[i]->entity )
520 		{
521 			players[i]->entity = nullptr;    //TODO: PLAYERSWAP VERIFY. Should this do anything to the player itself?
522 		}
523 	}
524 	// destroy my children
525 	list_FreeAll(&this->children);
526 
527 	node = list_AddNodeLast(&entitiesdeleted);
528 	node->element = this;
529 	node->deconstructor = &emptyDeconstructor;
530 
531 	if ( clientStats )
532 	{
533 		delete clientStats;
534 	}
535 }
536 
537 /*-------------------------------------------------------------------------------
538 
539 Entity::setObituary
540 
541 Sets the obituary text on an entity.
542 
543 -------------------------------------------------------------------------------*/
544 
setObituary(char * obituary)545 void Entity::setObituary(char* obituary)
546 {
547 	Stat* tempStats = this->getStats();
548 	if ( !tempStats )
549 	{
550 		return;
551 	}
552 	strncpy(tempStats->obituary, obituary, 127);
553 }
554 
555 /*-------------------------------------------------------------------------------
556 
557 Entity::killedByMonsterObituary
558 
559 Sets the obituary to that of a mon
560 
561 -------------------------------------------------------------------------------*/
562 
killedByMonsterObituary(Entity * victim)563 void Entity::killedByMonsterObituary(Entity* victim)
564 {
565 	if ( !victim )
566 	{
567 		return;
568 	}
569 	if ( behavior == &actBoulder)
570 	{
571 		if ( sprite == 989 )
572 		{
573 			victim->setObituary(language[3898]);
574 		}
575 		else if ( sprite == 990 )
576 		{
577 			victim->setObituary(language[3899]);
578 		}
579 		return;
580 	}
581 	Stat* hitstats = victim->getStats();
582 	Stat* myStats = this->getStats();
583 	if ( !hitstats || !myStats )
584 	{
585 		return;
586 	}
587 
588 	if ( myStats->type == hitstats->type )
589 	{
590 		if ( hitstats->sex == MALE )
591 		{
592 			if ( hitstats->type < KOBOLD ) //Original monster count
593 			{
594 				snprintf(tempstr, 256, language[1509], language[90 + hitstats->type]);
595 			}
596 			else if ( hitstats->type >= KOBOLD ) //New monsters
597 			{
598 				snprintf(tempstr, 256, language[1509], language[2000 + (hitstats->type - KOBOLD)]);
599 			}
600 		}
601 		else
602 		{
603 			if ( hitstats->type < KOBOLD ) //Original monster count
604 			{
605 				snprintf(tempstr, 256, language[1510], language[90 + hitstats->type]);
606 			}
607 			else if ( hitstats->type >= KOBOLD ) //New monsters
608 			{
609 				snprintf(tempstr, 256, language[1510], language[2000 + (hitstats->type - KOBOLD)]);
610 			}
611 		}
612 		victim->setObituary(tempstr);
613 	}
614 	else
615 	{
616 		switch ( myStats->type )
617 		{
618 			case HUMAN:
619 				victim->setObituary(language[1511]);
620 				break;
621 			case RAT:
622 				victim->setObituary(language[1512]);
623 				break;
624 			case GOBLIN:
625 				victim->setObituary(language[1513]);
626 				break;
627 			case SLIME:
628 				victim->setObituary(language[1514]);
629 				break;
630 			case TROLL:
631 				victim->setObituary(language[1515]);
632 				break;
633 			case SPIDER:
634 				victim->setObituary(language[1516]);
635 				break;
636 			case GHOUL:
637 				victim->setObituary(language[1517]);
638 				break;
639 			case SKELETON:
640 				victim->setObituary(language[1518]);
641 				break;
642 			case SCORPION:
643 				victim->setObituary(language[1519]);
644 				break;
645 			case CREATURE_IMP:
646 				victim->setObituary(language[1520]);
647 				break;
648 			case GNOME:
649 				victim->setObituary(language[1521]);
650 				break;
651 			case DEMON:
652 				victim->setObituary(language[1522]);
653 				break;
654 			case SUCCUBUS:
655 				victim->setObituary(language[1523]);
656 				break;
657 			case LICH:
658 				victim->setObituary(language[1524]);
659 				break;
660 			case MINOTAUR:
661 				victim->setObituary(language[1525]);
662 				break;
663 			case DEVIL:
664 				victim->setObituary(language[1526]);
665 				break;
666 			case SHOPKEEPER:
667 				if ( victim->behavior == &actPlayer )
668 				{
669 					if ( hitstats->type != HUMAN )
670 					{
671 						if ( hitstats->type < KOBOLD )
672 						{
673 							snprintf(hitstats->obituary, 127, language[3244], language[111 + hitstats->type], myStats->name);
674 						}
675 						else if ( hitstats->type >= KOBOLD )
676 						{
677 							snprintf(hitstats->obituary, 127, language[3244], language[2050 + hitstats->type - KOBOLD], myStats->name);
678 						}
679 					}
680 					else
681 					{
682 						victim->setObituary(language[1527]); // attempts a robbery.
683 					}
684 				}
685 				else
686 				{
687 					victim->setObituary(language[1527]); // attempts a robbery.
688 				}
689 				break;
690 			case KOBOLD:
691 				victim->setObituary(language[2150]);
692 				break;
693 			case SCARAB:
694 				victim->setObituary(language[2151]);
695 				break;
696 			case CRYSTALGOLEM:
697 				victim->setObituary(language[2152]);
698 				break;
699 			case INCUBUS:
700 				victim->setObituary(language[2153]);
701 				break;
702 			case VAMPIRE:
703 				victim->setObituary(language[2154]);
704 				break;
705 			case SHADOW:
706 				victim->setObituary(language[2155]);
707 				break;
708 			case COCKATRICE:
709 				victim->setObituary(language[2156]);
710 				break;
711 			case INSECTOID:
712 				victim->setObituary(language[2157]);
713 				break;
714 			case GOATMAN:
715 				victim->setObituary(language[2158]);
716 				break;
717 			case AUTOMATON:
718 				victim->setObituary(language[2159]);
719 				break;
720 			case LICH_ICE:
721 				victim->setObituary(language[2160]);
722 				break;
723 			case LICH_FIRE:
724 				victim->setObituary(language[2161]);
725 				break;
726 			default:
727 				victim->setObituary(language[1500]);
728 				break;
729 		}
730 	}
731 }
732 
733 /*-------------------------------------------------------------------------------
734 
735 Entity::light
736 
737 Returns the illumination of the given entity
738 
739 -------------------------------------------------------------------------------*/
740 
entityLight()741 int Entity::entityLight()
742 {
743 	if ( this->flags[BRIGHT] )
744 	{
745 		return 255;
746 	}
747 	if ( this->x < 0 || this->y < 0 || this->x >= map.width << 4 || this->y >= map.height << 4 )
748 	{
749 		return 255;
750 	}
751 	int light_x = (int)this->x / 16;
752 	int light_y = (int)this->y / 16;
753 	return lightmap[light_y + light_x * map.height];
754 }
755 
756 /*-------------------------------------------------------------------------------
757 
758 Entity::entityLightAfterReductions
759 
760 Returns new entities' illumination,
761 after reductions depending on the entity stats and another entity observing
762 
763 -------------------------------------------------------------------------------*/
764 
entityLightAfterReductions(Stat & myStats,Entity * observer)765 int Entity::entityLightAfterReductions(Stat& myStats, Entity* observer)
766 {
767 	int player = -1;
768 	int light = entityLight(); // max 255 light to start with.
769 	if ( !isInvisible() )
770 	{
771 		if ( behavior == &actPlayer )
772 		{
773 			player = skill[2];
774 			if ( player > -1 )
775 			{
776 				if ( stats[player]->shield )
777 				{
778 					if ( stats[player]->shield->type == TOOL_TORCH || stats[player]->shield->type == TOOL_CRYSTALSHARD
779 						|| stats[player]->shield->type == TOOL_LANTERN )
780 					{
781 					}
782 					else
783 					{
784 						light -= 95; // shields, quivers, spellbooks etc
785 					}
786 				}
787 				else
788 				{
789 					light -= 95;
790 				}
791 				if ( stats[player]->sneaking == 1 && !stats[player]->defending )
792 				{
793 					light -= 92;
794 				}
795 			}
796 		}
797 		// reduce light level 0-200 depending on target's stealth.
798 		// add light level 0-150 for PER 0-30
799 		if ( observer )
800 		{
801 			light -= myStats.PROFICIENCIES[PRO_STEALTH] * 2 - observer->getPER() * 5;
802 			Stat* observerStats = observer->getStats();
803 			if ( observerStats && observerStats->EFFECTS[EFF_BLIND] )
804 			{
805 				light = TOUCHRANGE;
806 			}
807 			if ( observer->monsterLastDistractedByNoisemaker > 0 && uidToEntity(observer->monsterLastDistractedByNoisemaker) )
808 			{
809 				if ( observer->monsterTarget == observer->monsterLastDistractedByNoisemaker
810 					|| myStats.EFFECTS[EFF_DISORIENTED] )
811 				{
812 					// currently hunting noisemaker.
813 					light = 16;
814 				}
815 			}
816 		}
817 		else
818 		{
819 			light -= myStats.PROFICIENCIES[PRO_STEALTH] * 2;
820 		}
821 	}
822 	else
823 	{
824 		light = TOUCHRANGE;
825 	}
826 	light = std::max(light, 0);
827 	if ( myStats.type == DUMMYBOT )
828 	{
829 		light = std::max(light, 256); // dummybots can always be seen at least 16 tiles away.
830 	}
831 	return light;
832 }
833 
834 /*-------------------------------------------------------------------------------
835 
836 Entity::effectTimes
837 
838 Counts down effect timers and toggles effects whose timers reach zero
839 
840 -------------------------------------------------------------------------------*/
841 
effectTimes()842 void Entity::effectTimes()
843 {
844 	Stat* myStats = this->getStats();
845 	int player;
846 	spell_t* spell = NULL;
847 	node_t* node = NULL;
848 	int count = 0;
849 
850 	if ( myStats == NULL )
851 	{
852 		return;
853 	}
854 	if ( this->behavior == &actPlayer )
855 	{
856 		player = this->skill[2];
857 	}
858 	else
859 	{
860 		player = -1;
861 	}
862 
863 
864 	spell_t* invisibility_hijacked = nullptr; //If NULL, function proceeds as normal. If points to something, it ignores the invisibility timer since a spell is doing things. //TODO: Incorporate the spell into isInvisible() instead?
865 	spell_t* levitation_hijacked = nullptr; //If NULL, function proceeds as normal. If points to something, it ignore the levitation timer since a spell is doing things.
866 	spell_t* reflectMagic_hijacked = nullptr;
867 	spell_t* amplifyMagic_hijacked = nullptr;
868 	spell_t* vampiricAura_hijacked = nullptr;
869 	//Handle magic effects (like invisibility)
870 	for ( node = myStats->magic_effects.first; node; node = node->next, ++count )
871 	{
872 		//printlog( "%s\n", "Potato.");
873 		//Handle magic effects.
874 		spell = (spell_t*)node->element;
875 		if ( !spell->sustain )
876 		{
877 			node_t* temp = NULL;
878 			if ( node->prev )
879 			{
880 				temp = node->prev;
881 			}
882 			else if ( node->next )
883 			{
884 				temp = node->next;
885 			}
886 			spell->magic_effects_node = NULL; //To prevent recursive removal, which results in a crash.
887 			if ( player > 0 && multiplayer == SERVER )
888 			{
889 				strcpy((char*)net_packet->data, "UNCH");
890 				net_packet->data[4] = player;
891 				SDLNet_Write32(spell->ID, &net_packet->data[5]);
892 				net_packet->address.host = net_clients[player - 1].host;
893 				net_packet->address.port = net_clients[player - 1].port;
894 				net_packet->len = 9;
895 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
896 			}
897 			list_RemoveNode(node); //Bugger the spell.
898 			node = temp;
899 			if ( !node )
900 			{
901 				break; //Done with list. Stop.
902 			}
903 			continue; //Skip this spell.
904 		}
905 
906 		bool unsustain = false;
907 		switch ( spell->ID )
908 		{
909 			case SPELL_INVISIBILITY:
910 				invisibility_hijacked = spell;
911 				if ( !myStats->EFFECTS[EFF_INVISIBLE] )
912 				{
913 					for ( int c = 0; c < MAXPLAYERS; ++c )
914 					{
915 						if ( players[c] && players[c]->entity && players[c]->entity == uidToEntity(spell->caster) )
916 						{
917 							messagePlayer(c, language[591]);    //If cure ailments or somesuch bombs the status effects.
918 						}
919 					}
920 					node_t* temp = nullptr;
921 					if ( node->prev )
922 					{
923 						temp = node->prev;
924 					}
925 					else if ( node->next )
926 					{
927 						temp = node->next;
928 					}
929 					unsustain = true;
930 					list_RemoveNode(node); //Remove this here node.
931 					node = temp;
932 				}
933 				break;
934 			case SPELL_LEVITATION:
935 				levitation_hijacked = spell;
936 				if ( !myStats->EFFECTS[EFF_LEVITATING] )
937 				{
938 					for ( int c = 0; c < MAXPLAYERS; ++c )
939 					{
940 						if ( players[c] && players[c]->entity && players[c]->entity == uidToEntity(spell->caster) )
941 						{
942 							messagePlayer(c, language[592]);
943 						}
944 					}
945 					node_t* temp = nullptr;
946 					if ( node->prev )
947 					{
948 						temp = node->prev;
949 					}
950 					else if ( node->next )
951 					{
952 						temp = node->next;
953 					}
954 					unsustain = true;
955 					list_RemoveNode(node); //Remove this here node.
956 					node = temp;
957 				}
958 				break;
959 			case SPELL_REFLECT_MAGIC:
960 				reflectMagic_hijacked = spell;
961 				if ( !myStats->EFFECTS[EFF_MAGICREFLECT] )
962 				{
963 					for ( int c = 0; c < MAXPLAYERS; ++c )
964 					{
965 						if ( players[c] && players[c]->entity && players[c]->entity == uidToEntity(spell->caster) )
966 						{
967 							messagePlayer(c, language[2446]);
968 						}
969 					}
970 					node_t* temp = nullptr;
971 					if ( node->prev )
972 					{
973 						temp = node->prev;
974 					}
975 					else if ( node->next )
976 					{
977 						temp = node->next;
978 					}
979 					unsustain = true;
980 					list_RemoveNode(node); //Remove this here node.
981 					node = temp;
982 				}
983 				break;
984 			case SPELL_AMPLIFY_MAGIC:
985 				amplifyMagic_hijacked = spell;
986 				if ( !myStats->EFFECTS[EFF_MAGICAMPLIFY] )
987 				{
988 					for ( int c = 0; c < MAXPLAYERS; ++c )
989 					{
990 						if ( players[c] && players[c]->entity && players[c]->entity == uidToEntity(spell->caster) )
991 						{
992 							messagePlayer(c, language[3441]);
993 						}
994 					}
995 					node_t* temp = nullptr;
996 					if ( node->prev )
997 					{
998 						temp = node->prev;
999 					}
1000 					else if ( node->next )
1001 					{
1002 						temp = node->next;
1003 					}
1004 					unsustain = true;
1005 					list_RemoveNode(node); //Remove this here node.
1006 					node = temp;
1007 				}
1008 				break;
1009 			case SPELL_VAMPIRIC_AURA:
1010 				vampiricAura_hijacked = spell;
1011 				if ( !myStats->EFFECTS[EFF_VAMPIRICAURA] )
1012 				{
1013 					for ( int c = 0; c < MAXPLAYERS; ++c )
1014 					{
1015 						if ( players[c] && players[c]->entity && players[c]->entity == uidToEntity(spell->caster) )
1016 						{
1017 							messagePlayer(c, language[2447]);
1018 						}
1019 					}
1020 					node_t* temp = nullptr;
1021 					if ( node->prev )
1022 					{
1023 						temp = node->prev;
1024 					}
1025 					else if ( node->next )
1026 					{
1027 						temp = node->next;
1028 					}
1029 					unsustain = true;
1030 					list_RemoveNode(node); //Remove this here node.
1031 					node = temp;
1032 				}
1033 				break;
1034 			default:
1035 				//Unknown spell, undefined effect. Like, say, a fireball spell wound up in here for some reason. That's a nono.
1036 				printlog("[entityEffectTimes] Warning: magic_effects spell that's not relevant. Should not be in the magic_effects list!\n");
1037 				list_RemoveNode(node);
1038 		}
1039 
1040 		if ( unsustain )
1041 		{
1042 			// the node has been removed, tell the client to unsustain in their list.
1043 			if ( player > 0 && multiplayer == SERVER )
1044 			{
1045 				strcpy((char*)net_packet->data, "UNCH");
1046 				net_packet->data[4] = player;
1047 				SDLNet_Write32(spell->ID, &net_packet->data[5]);
1048 				net_packet->address.host = net_clients[player - 1].host;
1049 				net_packet->address.port = net_clients[player - 1].port;
1050 				net_packet->len = 9;
1051 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
1052 			}
1053 		}
1054 
1055 		if ( !node )
1056 		{
1057 			break;    //BREAK OUT. YEAAAAAH. Because otherwise it crashes.
1058 		}
1059 	}
1060 	if ( count )
1061 	{
1062 		//printlog( "Number of magic effects spells: %d\n", count); //Debugging output.
1063 	}
1064 
1065 	bool dissipate = true;
1066 	bool updateClient = false;
1067 	spell_t* unsustainSpell = nullptr;
1068 
1069 	for ( int c = 0; c < NUMEFFECTS; c++ )
1070 	{
1071 		if ( myStats->EFFECTS_TIMERS[c] > 0 )
1072 		{
1073 			myStats->EFFECTS_TIMERS[c]--;
1074 			if ( c == EFF_POLYMORPH )
1075 			{
1076 				if ( myStats->EFFECTS_TIMERS[c] == TICKS_PER_SECOND * 15 )
1077 				{
1078 					playSoundPlayer(player, 32, 128);
1079 					messagePlayer(player, language[3193]);
1080 				}
1081 			}
1082 			else if ( c == EFF_SHAPESHIFT )
1083 			{
1084 				if ( myStats->EFFECTS_TIMERS[c] == TICKS_PER_SECOND * 15 )
1085 				{
1086 					playSoundPlayer(player, 32, 128);
1087 					messagePlayer(player, language[3475]);
1088 				}
1089 			}
1090 			if ( myStats->EFFECTS_TIMERS[c] == 0 )
1091 			{
1092 				myStats->EFFECTS[c] = false;
1093 				switch ( c )
1094 				{
1095 					case EFF_ASLEEP:
1096 						messagePlayer(player, language[593]);
1097 						if ( monsterAllyGetPlayerLeader() && monsterAllySpecial == ALLY_SPECIAL_CMD_REST )
1098 						{
1099 							monsterAllySpecial = ALLY_SPECIAL_CMD_NONE;
1100 							myStats->EFFECTS[EFF_HP_REGEN] = false;
1101 							myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = 0;
1102 						}
1103 						break;
1104 					case EFF_HP_REGEN:
1105 						//messagePlayer(player, language[3476]);
1106 						updateClient = true;
1107 						break;
1108 					case EFF_MP_REGEN:
1109 						//messagePlayer(player, language[3477]);
1110 						updateClient = true;
1111 						break;
1112 					case EFF_POISONED:
1113 						messagePlayer(player, language[594]);
1114 						break;
1115 					case EFF_STUNNED:
1116 						//messagePlayer(player, language[595]);
1117 						break;
1118 					case EFF_CONFUSED:
1119 						messagePlayer(player, language[596]);
1120 						break;
1121 					case EFF_DRUNK:
1122 						messagePlayer(player, language[597]);
1123 						break;
1124 					case EFF_INVISIBLE:
1125 						; //To make the compiler shut up: "error: a label can only be part of a statement and a declaration is not a statement"
1126 						dissipate = true; //Remove the effect by default.
1127 						if ( invisibility_hijacked )
1128 						{
1129 							bool sustained = false;
1130 							Entity* caster = uidToEntity(invisibility_hijacked->caster);
1131 							if ( caster )
1132 							{
1133 								//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
1134 								bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds
1135 								if ( deducted )
1136 								{
1137 									sustained = true;
1138 									myStats->EFFECTS[c] = true;
1139 									myStats->EFFECTS_TIMERS[c] = invisibility_hijacked->channel_duration;
1140 								}
1141 								else
1142 								{
1143 									int i = 0;
1144 									for ( i = 0; i < MAXPLAYERS; ++i )
1145 									{
1146 										if ( players[i]->entity == caster )
1147 										{
1148 											messagePlayer(i, language[598]);
1149 										}
1150 									}
1151 									unsustainSpell = invisibility_hijacked;
1152 									list_RemoveNode(invisibility_hijacked->magic_effects_node); //Remove it from the entity's magic effects. This has the side effect of removing it from the sustained spells list too.
1153 																								//list_RemoveNode(invisibility_hijacked->sustain_node); //Remove it from the channeled spells list.
1154 								}
1155 							}
1156 							if ( sustained )
1157 							{
1158 								dissipate = false;    //Sustained the spell, so do not stop being invisible.
1159 							}
1160 						}
1161 						if ( dissipate )
1162 						{
1163 							if ( !this->isBlind() )
1164 							{
1165 								messagePlayer(player, language[599]);
1166 							}
1167 						}
1168 						break;
1169 					case EFF_BLIND:
1170 						if ( !this->isBlind() )
1171 						{
1172 							messagePlayer(player, language[600]);
1173 						}
1174 						else
1175 						{
1176 							messagePlayer(player, language[601]);
1177 						}
1178 						break;
1179 					case EFF_GREASY:
1180 						messagePlayer(player, language[602]);
1181 						break;
1182 					case EFF_MESSY:
1183 						messagePlayer(player, language[603]);
1184 						break;
1185 					case EFF_FAST:
1186 						messagePlayer(player, language[604]);
1187 						break;
1188 					case EFF_PARALYZED:
1189 						messagePlayer(player, language[605]);
1190 						break;
1191 					case EFF_POTION_STR:
1192 						messagePlayer(player, language[3355]);
1193 						break;
1194 					case EFF_LEVITATING:
1195 						; //To make the compiler shut up: "error: a label can only be part of a statement and a declaration is not a statement"
1196 						dissipate = true; //Remove the effect by default.
1197 						if ( levitation_hijacked )
1198 						{
1199 							bool sustained = false;
1200 							Entity* caster = uidToEntity(levitation_hijacked->caster);
1201 							if ( caster )
1202 							{
1203 								//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
1204 								bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds
1205 								if ( deducted )
1206 								{
1207 									sustained = true;
1208 									myStats->EFFECTS[c] = true;
1209 									myStats->EFFECTS_TIMERS[c] = levitation_hijacked->channel_duration;
1210 								}
1211 								else
1212 								{
1213 									int i = 0;
1214 									for ( i = 0; i < MAXPLAYERS; ++i )
1215 									{
1216 										if ( players[i]->entity == caster )
1217 										{
1218 											messagePlayer(i, language[606]);    //TODO: Unhardcode name?
1219 										}
1220 									}
1221 									unsustainSpell = levitation_hijacked;
1222 									list_RemoveNode(levitation_hijacked->magic_effects_node); //Remove it from the entity's magic effects. This has the side effect of removing it from the sustained spells list too.
1223 								}
1224 							}
1225 							if ( sustained )
1226 							{
1227 								dissipate = false;    //Sustained the spell, so do not stop levitating.
1228 							}
1229 						}
1230 						if ( dissipate )
1231 						{
1232 							if ( !isLevitating(myStats) )
1233 							{
1234 								messagePlayer(player, language[607]);
1235 							}
1236 						}
1237 						break;
1238 					case EFF_TELEPATH:
1239 						if ( myStats->mask != nullptr && myStats->mask->type == TOOL_BLINDFOLD_TELEPATHY )
1240 						{
1241 							// don't play any messages since we'll reset the counter in due time.
1242 							// likely to happen on level change.
1243 						}
1244 						else
1245 						{
1246 							setEffect(EFF_TELEPATH, false, 0, true);
1247 							messagePlayer(player, language[608]);
1248 							if ( player == clientnum )
1249 							{
1250 								for ( node_t* mapNode = map.creatures->first; mapNode != nullptr; mapNode = mapNode->next )
1251 								{
1252 									Entity* mapCreature = (Entity*)mapNode->element;
1253 									if ( mapCreature )
1254 									{
1255 										// undo telepath rendering.
1256 										mapCreature->monsterEntityRenderAsTelepath = 0;
1257 									}
1258 								}
1259 							}
1260 						}
1261 						break;
1262 					case EFF_VOMITING:
1263 						messagePlayer(player, language[609]);
1264 						if ( myStats->HUNGER > 1500 )
1265 						{
1266 							messagePlayer(player, language[610]);
1267 						}
1268 						else if ( myStats->HUNGER > 150 && myStats->HUNGER <= 250 )
1269 						{
1270 							messagePlayer(player, language[611]);
1271 							playSoundPlayer(player, 32, 128);
1272 						}
1273 						else if ( myStats->HUNGER > 50 && myStats->HUNGER <= 150 )
1274 						{
1275 							messagePlayer(player, language[612]);
1276 							playSoundPlayer(player, 32, 128);
1277 						}
1278 						else if ( myStats->HUNGER <= 50 )
1279 						{
1280 							myStats->HUNGER = 50;
1281 							messagePlayer(player, language[613]);
1282 							playSoundPlayer(player, 32, 128);
1283 						}
1284 						serverUpdateHunger(player);
1285 						break;
1286 					case EFF_BLEEDING:
1287 						messagePlayer(player, language[614]);
1288 						break;
1289 					case EFF_MAGICRESIST:
1290 						messagePlayer(player, language[2470]);
1291 						break;
1292 					case EFF_FLUTTER:
1293 						if ( !isLevitating(myStats) )
1294 						{
1295 							messagePlayer(player, language[607]);
1296 							if ( behavior == &actPlayer
1297 								&& achievementObserver.playerAchievements[skill[2]].flutterShyCoordinates.first > 0.01
1298 								&& achievementObserver.playerAchievements[skill[2]].flutterShyCoordinates.second > 0.01 )
1299 							{
1300 								int playerx = std::min(std::max<unsigned int>(1, this->x / 16), map.width - 2);
1301 								int playery = std::min(std::max<unsigned int>(1, this->y / 16), map.height - 2);
1302 								if ( map.tiles[0 + playery * MAPLAYERS + playerx * MAPLAYERS * map.height] )
1303 								{
1304 									// there's ground..
1305 									achievementObserver.playerAchievements[skill[2]].checkPathBetweenObjects(this, nullptr, AchievementObserver::BARONY_ACH_FLUTTERSHY);
1306 								}
1307 							}
1308 						}
1309 						break;
1310 					case EFF_MAGICREFLECT:
1311 						dissipate = true; //Remove the effect by default.
1312 						if ( reflectMagic_hijacked )
1313 						{
1314 							bool sustained = false;
1315 							Entity* caster = uidToEntity(reflectMagic_hijacked->caster);
1316 							if ( caster )
1317 							{
1318 								//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
1319 								bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds
1320 								if ( deducted )
1321 								{
1322 									sustained = true;
1323 									myStats->EFFECTS[c] = true;
1324 									myStats->EFFECTS_TIMERS[c] = reflectMagic_hijacked->channel_duration;
1325 								}
1326 								else
1327 								{
1328 									int i = 0;
1329 									for ( i = 0; i < MAXPLAYERS; ++i )
1330 									{
1331 										if ( players[i]->entity == caster )
1332 										{
1333 											messagePlayer(i, language[2474]);
1334 										}
1335 									}
1336 									unsustainSpell = reflectMagic_hijacked;
1337 									list_RemoveNode(reflectMagic_hijacked->magic_effects_node); //Remove it from the entity's magic effects. This has the side effect of removing it from the sustained spells list too.
1338 																								//list_RemoveNode(reflectMagic_hijacked->sustain_node); //Remove it from the channeled spells list.
1339 								}
1340 							}
1341 							if ( sustained )
1342 							{
1343 								dissipate = false; //Sustained the spell, so do not stop being invisible.
1344 							}
1345 						}
1346 						if ( dissipate )
1347 						{
1348 							messagePlayer(player, language[2471]);
1349 							updateClient = true;
1350 						}
1351 						break;
1352 					case EFF_MAGICAMPLIFY:
1353 						dissipate = true; //Remove the effect by default.
1354 						if ( amplifyMagic_hijacked )
1355 						{
1356 							bool sustained = false;
1357 							Entity* caster = uidToEntity(amplifyMagic_hijacked->caster);
1358 							if ( caster )
1359 							{
1360 								//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
1361 								bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds
1362 								if ( deducted )
1363 								{
1364 									sustained = true;
1365 									myStats->EFFECTS[c] = true;
1366 									myStats->EFFECTS_TIMERS[c] = amplifyMagic_hijacked->channel_duration;
1367 								}
1368 								else
1369 								{
1370 									int i = 0;
1371 									for ( i = 0; i < MAXPLAYERS; ++i )
1372 									{
1373 										if ( players[i]->entity == caster )
1374 										{
1375 											messagePlayer(i, language[3443]);
1376 										}
1377 									}
1378 									unsustainSpell = amplifyMagic_hijacked;
1379 									list_RemoveNode(amplifyMagic_hijacked->magic_effects_node); //Remove it from the entity's magic effects. This has the side effect of removing it from the sustained spells list too.
1380 								}
1381 							}
1382 							if ( sustained )
1383 							{
1384 								dissipate = false; //Sustained the spell, so do not stop being invisible.
1385 							}
1386 						}
1387 						if ( dissipate )
1388 						{
1389 							messagePlayer(player, language[3441]);
1390 							updateClient = true;
1391 						}
1392 						break;
1393 					case EFF_VAMPIRICAURA:
1394 						dissipate = true; //Remove the effect by default.
1395 						if ( vampiricAura_hijacked )
1396 						{
1397 							bool sustained = false;
1398 							Entity* caster = uidToEntity(vampiricAura_hijacked->caster);
1399 							if ( caster )
1400 							{
1401 								//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
1402 								bool deducted = caster->safeConsumeMP(1); //Consume 3 mana ever duration / mana seconds
1403 								if ( deducted )
1404 								{
1405 									sustained = true;
1406 									myStats->EFFECTS[c] = true;
1407 									myStats->EFFECTS_TIMERS[c] = vampiricAura_hijacked->channel_duration;
1408 
1409 									// monsters have a chance to un-sustain the spell each MP consume.
1410 									if ( caster->behavior == &actMonster && rand() % 20 == 0 )
1411 									{
1412 										sustained = false;
1413 										list_RemoveNode(vampiricAura_hijacked->magic_effects_node);
1414 									}
1415 								}
1416 								else
1417 								{
1418 									int i = 0;
1419 									for ( i = 0; i < MAXPLAYERS; ++i )
1420 									{
1421 										if ( players[i]->entity == caster )
1422 										{
1423 											//messagePlayer(player, language[2449]);
1424 										}
1425 									}
1426 									unsustainSpell = vampiricAura_hijacked;
1427 									list_RemoveNode(vampiricAura_hijacked->magic_effects_node); //Remove it from the entity's magic effects. This has the side effect of removing it from the sustained spells list too.
1428 																								//list_RemoveNode(reflectMagic_hijacked->sustain_node); //Remove it from the channeled spells list.
1429 								}
1430 							}
1431 							if ( sustained )
1432 							{
1433 								dissipate = false; //Sustained the spell, so do not stop being invisible.
1434 							}
1435 						}
1436 						if ( dissipate )
1437 						{
1438 							//if ( myStats->HUNGER > 250 )
1439 							//{
1440 							//	myStats->HUNGER = 252; // set to above 250 to trigger the hunger sound/messages when it decrements to 250.
1441 							//	serverUpdateHunger(player);
1442 							//}
1443 							messagePlayer(player, language[2449]);
1444 							updateClient = true;
1445 						}
1446 						break;
1447 					case EFF_SLOW:
1448 						messagePlayer(player, language[604]); // "You return to your normal speed."
1449 						break;
1450 					case EFF_POLYMORPH:
1451 						effectPolymorph = 0;
1452 						serverUpdateEntitySkill(this, 50);
1453 						messagePlayer(player, language[3185]);
1454 
1455 						playSoundEntity(this, 400, 92);
1456 						createParticleDropRising(this, 593, 1.f);
1457 						serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 593);
1458 						updateClient = true;
1459 						break;
1460 					case EFF_SHAPESHIFT:
1461 						effectShapeshift = 0;
1462 						serverUpdateEntitySkill(this, 53);
1463 						messagePlayer(player, language[3417]);
1464 
1465 						playSoundEntity(this, 400, 92);
1466 						createParticleDropRising(this, 593, 1.f);
1467 						serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 593);
1468 						updateClient = true;
1469 						break;
1470 					case EFF_TROLLS_BLOOD:
1471 						messagePlayer(player, language[3491]);
1472 						updateClient = true;
1473 						break;
1474 					case EFF_KNOCKBACK:
1475 						break;
1476 					case EFF_WITHDRAWAL:
1477 						if ( player >= 0 && player < MAXPLAYERS )
1478 						{
1479 							if ( myStats->EFFECTS[EFF_DRUNK] )
1480 							{
1481 								// we still drunk! no need for hangover just yet...
1482 								// extend another 15 seconds.
1483 								myStats->EFFECTS_TIMERS[EFF_WITHDRAWAL] = TICKS_PER_SECOND * 15;
1484 							}
1485 							else
1486 							{
1487 								playSoundPlayer(player, 32, 128);
1488 								messagePlayer(player, language[3247 + rand() % 3]);
1489 								messagePlayer(player, language[3222]);
1490 								this->setEffect(EFF_WITHDRAWAL, true, -2, true); // set effect as "active"
1491 							}
1492 						}
1493 						break;
1494 					case EFF_FEAR:
1495 						this->monsterFearfulOfUid = 0;
1496 						messagePlayer(player, language[3439]);
1497 						updateClient = true;
1498 						break;
1499 					case EFF_PACIFY:
1500 					case EFF_SHADOW_TAGGED:
1501 					case EFF_WEBBED:
1502 						updateClient = true;
1503 						break;
1504 					default:
1505 						break;
1506 				}
1507 				if ( player > 0 && multiplayer == SERVER )
1508 				{
1509 					serverUpdateEffects(player);
1510 				}
1511 			}
1512 			else if ( myStats->EFFECTS_TIMERS[c] == ((TICKS_PER_SECOND * 5) - 1) )
1513 			{
1514 				if ( player > 0 && multiplayer == SERVER )
1515 				{
1516 					serverUpdateEffects(player);
1517 				}
1518 			}
1519 		}
1520 		if ( unsustainSpell )
1521 		{
1522 			// we need to tell the client to un-sustain from their list.
1523 			if ( player > 0 && multiplayer == SERVER )
1524 			{
1525 				strcpy((char*)net_packet->data, "UNCH");
1526 				net_packet->data[4] = player;
1527 				SDLNet_Write32(unsustainSpell->ID, &net_packet->data[5]);
1528 				net_packet->address.host = net_clients[player - 1].host;
1529 				net_packet->address.port = net_clients[player - 1].port;
1530 				net_packet->len = 9;
1531 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
1532 			}
1533 		}
1534 		unsustainSpell = nullptr;
1535 	}
1536 
1537 	if ( updateClient )
1538 	{
1539 		//Only a select few effects have something that needs to be handled on the client's end.
1540 		//(such as spawning particles for the magic reflection effect)
1541 		//Only update the entity's effects in that case.
1542 		serverUpdateEffectsForEntity(true);
1543 	}
1544 }
1545 
1546 /*-------------------------------------------------------------------------------
1547 
1548 Entity::increaseSkill
1549 
1550 Increases the given skill of the given entity by 1.
1551 
1552 -------------------------------------------------------------------------------*/
1553 
increaseSkill(int skill,bool notify)1554 void Entity::increaseSkill(int skill, bool notify)
1555 {
1556 	Stat* myStats = this->getStats();
1557 	int player = -1;
1558 
1559 	if ( myStats == NULL )
1560 	{
1561 		return;
1562 	}
1563 	if ( this->behavior == &actPlayer )
1564 	{
1565 		player = this->skill[2];
1566 	}
1567 
1568 	Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
1569 	if ( myStats->PROFICIENCIES[skill] < 100 )
1570 	{
1571 		myStats->PROFICIENCIES[skill]++;
1572 		if ( notify )
1573 		{
1574 			messagePlayerColor(player, color, language[615], getSkillLangEntry(skill));
1575 		}
1576 		switch ( myStats->PROFICIENCIES[skill] )
1577 		{
1578 			case 20:
1579 				messagePlayerColor(player, color, language[616], getSkillLangEntry(skill));
1580 				break;
1581 			case 40:
1582 				messagePlayerColor(player, color, language[617], getSkillLangEntry(skill));
1583 				break;
1584 			case 60:
1585 				messagePlayerColor(player, color, language[618], getSkillLangEntry(skill));
1586 				break;
1587 			case 80:
1588 				messagePlayerColor(player, color, language[619], getSkillLangEntry(skill));
1589 				break;
1590 			case 100:
1591 				messagePlayerColor(player, color, language[620], getSkillLangEntry(skill));
1592 				break;
1593 			default:
1594 				break;
1595 		}
1596 
1597 		if ( skill == PRO_SPELLCASTING && skillCapstoneUnlockedEntity(PRO_SPELLCASTING) )
1598 		{
1599 			//Spellcasting capstone = free casting of Forcebolt.
1600 			//Give the player the spell if they haven't learned it yet.
1601 			if ( player > 0 && multiplayer == SERVER )
1602 			{
1603 				strcpy((char*)net_packet->data, "ASPL");
1604 				net_packet->data[4] = clientnum;
1605 				net_packet->data[5] = SPELL_FORCEBOLT;
1606 				net_packet->address.host = net_clients[player - 1].host;
1607 				net_packet->address.port = net_clients[player - 1].port;
1608 				net_packet->len = 6;
1609 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
1610 			}
1611 			else if ( player >= 0 )
1612 			{
1613 				addSpell(SPELL_FORCEBOLT, player, true);
1614 			}
1615 		}
1616 
1617 		if ( skill == PRO_STEALTH && myStats->PROFICIENCIES[skill] == 100 )
1618 		{
1619 			if ( client_classes[player] == CLASS_ACCURSED )
1620 			{
1621 				steamAchievementClient(player, "BARONY_ACH_BLOOD_RUNS_CLEAR");
1622 			}
1623 		}
1624 
1625 		if ( player >= 0 && stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0
1626 			&& myStats->PROFICIENCIES[skill] == 100 )
1627 		{
1628 			switch ( skill )
1629 			{
1630 				case PRO_SWORD:
1631 				case PRO_POLEARM:
1632 				case PRO_AXE:
1633 				case PRO_MACE:
1634 				case PRO_UNARMED:
1635 					steamAchievementClient(player, "BARONY_ACH_SAVAGE");
1636 					break;
1637 				default:
1638 					break;
1639 			}
1640 		}
1641 
1642 		if ( skill == PRO_ALCHEMY )
1643 		{
1644 			if ( player == clientnum )
1645 			{
1646 				GenericGUI.alchemyLearnRecipeOnLevelUp(myStats->PROFICIENCIES[skill]);
1647 			}
1648 		}
1649 
1650 		if ( skill == PRO_SWIMMING && !(svFlags & SV_FLAG_HUNGER) )
1651 		{
1652 			// hunger off and swimming is raised.
1653 			serverUpdatePlayerGameplayStats(player, STATISTICS_HOT_TUB_TIME_MACHINE, 1);
1654 		}
1655 
1656 		if ( skill == PRO_MAGIC && skillCapstoneUnlockedEntity(PRO_MAGIC) )
1657 		{
1658 			//magic capstone = bonus spell: Dominate.
1659 			if ( player > 0 && multiplayer == SERVER )
1660 			{
1661 				strcpy((char*)net_packet->data, "ASPL");
1662 				net_packet->data[4] = clientnum;
1663 				net_packet->data[5] = SPELL_DOMINATE;
1664 				net_packet->address.host = net_clients[player - 1].host;
1665 				net_packet->address.port = net_clients[player - 1].port;
1666 				net_packet->len = 6;
1667 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
1668 			}
1669 			else if ( player >= 0 )
1670 			{
1671 				addSpell(SPELL_DOMINATE, player, true);
1672 			}
1673 		}
1674 		myStats->EXP += 2;
1675 	}
1676 
1677 	int statBonusSkill = getStatForProficiency(skill);
1678 
1679 	if ( statBonusSkill >= STAT_STR )
1680 	{
1681 		// stat has chance for bonus point if the relevant proficiency has been trained.
1682 		// write the last proficiency that effected the skill.
1683 		myStats->PLAYER_LVL_STAT_BONUS[statBonusSkill] = skill;
1684 	}
1685 
1686 
1687 
1688 	if ( player > 0 && multiplayer == SERVER )
1689 	{
1690 		// update SKILL
1691 		strcpy((char*)net_packet->data, "SKIL");
1692 		net_packet->data[4] = clientnum;
1693 		net_packet->data[5] = skill;
1694 		net_packet->data[6] = myStats->PROFICIENCIES[skill];
1695 		net_packet->address.host = net_clients[player - 1].host;
1696 		net_packet->address.port = net_clients[player - 1].port;
1697 		net_packet->len = 7;
1698 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
1699 
1700 		// update EXP
1701 		strcpy((char*)net_packet->data, "ATTR");
1702 		net_packet->data[4] = clientnum;
1703 		net_packet->data[5] = (Sint8)myStats->STR;
1704 		net_packet->data[6] = (Sint8)myStats->DEX;
1705 		net_packet->data[7] = (Sint8)myStats->CON;
1706 		net_packet->data[8] = (Sint8)myStats->INT;
1707 		net_packet->data[9] = (Sint8)myStats->PER;
1708 		net_packet->data[10] = (Sint8)myStats->CHR;
1709 		net_packet->data[11] = (Sint8)myStats->EXP;
1710 		net_packet->data[12] = (Sint8)myStats->LVL;
1711 		SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]);
1712 		SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]);
1713 		SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]);
1714 		SDLNet_Write16((Sint16)myStats->MAXMP, &net_packet->data[19]);
1715 		net_packet->address.host = net_clients[player - 1].host;
1716 		net_packet->address.port = net_clients[player - 1].port;
1717 		net_packet->len = 21;
1718 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
1719 	}
1720 }
1721 
1722 /*-------------------------------------------------------------------------------
1723 
1724 Entity::stats
1725 
1726 Returns a pointer to a Stat instance given a pointer to an entity
1727 
1728 -------------------------------------------------------------------------------*/
1729 
getStats() const1730 Stat* Entity::getStats() const
1731 {
1732 	if ( this->behavior == &actMonster ) // monsters
1733 	{
1734 		if ( multiplayer == CLIENT && clientStats )
1735 		{
1736 			return clientStats;
1737 		}
1738 		if ( this->children.first != nullptr )
1739 		{
1740 			if ( this->children.first->next != nullptr )
1741 			{
1742 				return (Stat*)this->children.first->next->element;
1743 			}
1744 		}
1745 	}
1746 	else if ( this->behavior == &actPlayer ) // players
1747 	{
1748 		return stats[this->skill[2]];
1749 	}
1750 	else if ( this->behavior == &actPlayerLimb ) // player bodyparts
1751 	{
1752 		return stats[this->skill[2]];
1753 	}
1754 
1755 	return nullptr;
1756 }
1757 
1758 /*-------------------------------------------------------------------------------
1759 
1760 Entity::checkBetterEquipment
1761 
1762 Checks the tiles immediately surrounding the given entity for items and
1763 replaces the entity's equipment with those items if they are better
1764 
1765 -------------------------------------------------------------------------------*/
1766 
checkBetterEquipment(Stat * myStats)1767 void Entity::checkBetterEquipment(Stat* myStats)
1768 {
1769 	if ( !myStats )
1770 	{
1771 		return; //Can't continue without these.
1772 	}
1773 
1774 	list_t* items = nullptr;
1775 	//X and Y in terms of tiles.
1776 	int tx = x / 16;
1777 	int ty = y / 16;
1778 	getItemsOnTile(tx, ty, &items); //Check the tile the goblin is on for items.
1779 	getItemsOnTile(tx - 1, ty, &items); //Check tile to the left.
1780 	getItemsOnTile(tx + 1, ty, &items); //Check tile to the right.
1781 	getItemsOnTile(tx, ty - 1, &items); //Check tile up.
1782 	getItemsOnTile(tx, ty + 1, &items); //Check tile down.
1783 	getItemsOnTile(tx - 1, ty - 1, &items); //Check tile diagonal up left.
1784 	getItemsOnTile(tx + 1, ty - 1, &items); //Check tile diagonal up right.
1785 	getItemsOnTile(tx - 1, ty + 1, &items); //Check tile diagonal down left.
1786 	getItemsOnTile(tx + 1, ty + 1, &items); //Check tile diagonal down right.
1787 	int currentAC, newAC;
1788 	Item* oldarmor = nullptr;
1789 
1790 	node_t* node = nullptr;
1791 
1792 	bool glovesandshoes = false;
1793 	if ( myStats->type == HUMAN )
1794 	{
1795 		glovesandshoes = true;
1796 	}
1797 
1798 	if ( items )
1799 	{
1800 		/*
1801 		* Rundown of the function:
1802 		* Loop through all items.
1803 		* Check the monster's item. Compare and grab the best item.
1804 		*/
1805 
1806 		for ( node = items->first; node != nullptr; node = node->next )
1807 		{
1808 			//Turn the entity into an item.
1809 			if ( node->element )
1810 			{
1811 				Entity* entity = (Entity*)node->element;
1812 				Item* item = nullptr;
1813 				if ( entity != nullptr )
1814 				{
1815 					item = newItemFromEntity(entity);
1816 				}
1817 				if ( !item )
1818 				{
1819 					continue;
1820 				}
1821 				if ( !canWieldItem(*item) )
1822 				{
1823 					free(item);
1824 					continue;
1825 				}
1826 
1827 				//If weapon.
1828 				if ( itemCategory(item) == WEAPON )
1829 				{
1830 					if ( myStats->weapon == nullptr ) //Not currently holding a weapon.
1831 					{
1832 						myStats->weapon = item; //Assign the monster's weapon.
1833 						item = nullptr;
1834 						list_RemoveNode(entity->mynode);
1835 					}
1836 					else
1837 					{
1838 						//Ok, the monster has a weapon already. First check if the monster's weapon is cursed. Can't drop it if it is.
1839 						if ( myStats->weapon->beatitude >= 0 && itemCategory(myStats->weapon) != MAGICSTAFF && itemCategory(myStats->weapon) != POTION && itemCategory(myStats->weapon) != THROWN && itemCategory(myStats->weapon) != GEM )
1840 						{
1841 							//Next compare the two weapons. If the item on the ground is better, drop the weapon it's carrying and equip that one.
1842 							int weapon_tohit = myStats->weapon->weaponGetAttack();
1843 							int new_weapon_tohit = item->weaponGetAttack();
1844 
1845 							//If the new weapon does more damage than the current weapon.
1846 							if ( new_weapon_tohit > weapon_tohit )
1847 							{
1848 								dropItemMonster(myStats->weapon, this, myStats);
1849 								myStats->weapon = item;
1850 								item = nullptr;
1851 								list_RemoveNode(entity->mynode);
1852 							}
1853 						}
1854 					}
1855 				}
1856 				else if ( itemCategory(item) == ARMOR )
1857 				{
1858 					if ( checkEquipType(item) == TYPE_HAT ) // hats
1859 					{
1860 						if ( myStats->helmet == nullptr ) // nothing on head currently
1861 						{
1862 							// goblins love hats.
1863 							myStats->helmet = item; // pick up the hat.
1864 							item = nullptr;
1865 							list_RemoveNode(entity->mynode);
1866 						}
1867 					}
1868 					else if ( checkEquipType(item) == TYPE_HELM ) // helmets
1869 					{
1870 						if ( myStats->helmet == nullptr ) // nothing on head currently
1871 						{
1872 							myStats->helmet = item; // pick up the helmet.
1873 							item = nullptr;
1874 							list_RemoveNode(entity->mynode);
1875 						}
1876 						else
1877 						{
1878 							if ( myStats->helmet->beatitude >= 0 ) // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
1879 							{
1880 								// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
1881 								// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
1882 								currentAC = AC(myStats);
1883 								oldarmor = myStats->helmet;
1884 								myStats->helmet = item;
1885 								newAC = AC(myStats);
1886 								myStats->helmet = oldarmor;
1887 
1888 								//If the new armor is better than the current armor.
1889 								if ( newAC > currentAC )
1890 								{
1891 									dropItemMonster(myStats->helmet, this, myStats);
1892 									myStats->helmet = item;
1893 									item = nullptr;
1894 									list_RemoveNode(entity->mynode);
1895 								}
1896 							}
1897 						}
1898 					}
1899 					else if ( checkEquipType(item) == TYPE_SHIELD )     // shields
1900 					{
1901 						if ( myStats->shield == nullptr ) // nothing in left hand currently
1902 						{
1903 							myStats->shield = item; // pick up the shield.
1904 							item = nullptr;
1905 							list_RemoveNode(entity->mynode);
1906 						}
1907 						else
1908 						{
1909 							if ( myStats->shield->beatitude >= 0 )   // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
1910 							{
1911 								// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
1912 								// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
1913 								currentAC = AC(myStats);
1914 								oldarmor = myStats->shield;
1915 								myStats->shield = item;
1916 								newAC = AC(myStats);
1917 								myStats->shield = oldarmor;
1918 
1919 								//If the new armor is better than the current armor (OR we're not carrying anything)
1920 								if ( newAC > currentAC || !myStats->shield )
1921 								{
1922 									dropItemMonster(myStats->shield, this, myStats);
1923 									myStats->shield = item;
1924 									item = nullptr;
1925 									list_RemoveNode(entity->mynode);
1926 								}
1927 							}
1928 						}
1929 					}
1930 					else if ( checkEquipType(item) == TYPE_BREASTPIECE ) // breastpieces
1931 					{
1932 						if ( myStats->breastplate == nullptr ) // nothing on torso currently
1933 						{
1934 							myStats->breastplate = item; // pick up the armor.
1935 							item = nullptr;
1936 							list_RemoveNode(entity->mynode);
1937 						}
1938 						else
1939 						{
1940 							if ( myStats->breastplate->beatitude >= 0 ) // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
1941 							{
1942 								// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
1943 								// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
1944 								currentAC = AC(myStats);
1945 								oldarmor = myStats->breastplate;
1946 								myStats->breastplate = item;
1947 								newAC = AC(myStats);
1948 								myStats->breastplate = oldarmor;
1949 
1950 								//If the new armor is better than the current armor.
1951 								if ( newAC > currentAC )
1952 								{
1953 									dropItemMonster(myStats->breastplate, this, myStats);
1954 									myStats->breastplate = item;
1955 									item = nullptr;
1956 									list_RemoveNode(entity->mynode);
1957 								}
1958 							}
1959 						}
1960 					}
1961 					else if ( checkEquipType(item) == TYPE_CLOAK ) // cloaks
1962 					{
1963 						if ( myStats->cloak == nullptr ) // nothing on back currently
1964 						{
1965 							myStats->cloak = item; // pick up the armor.
1966 							item = nullptr;
1967 							list_RemoveNode(entity->mynode);
1968 						}
1969 						else
1970 						{
1971 							if ( myStats->cloak->beatitude >= 0 )   // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
1972 							{
1973 								// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
1974 								// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
1975 								currentAC = AC(myStats);
1976 								oldarmor = myStats->cloak;
1977 								myStats->cloak = item;
1978 								newAC = AC(myStats);
1979 								myStats->cloak = oldarmor;
1980 
1981 								//If the new armor is better than the current armor.
1982 								if ( newAC > currentAC )
1983 								{
1984 									dropItemMonster(myStats->cloak, this, myStats);
1985 									myStats->cloak = item;
1986 									item = nullptr;
1987 									list_RemoveNode(entity->mynode);
1988 								}
1989 							}
1990 						}
1991 					}
1992 					if ( glovesandshoes && item != nullptr )
1993 					{
1994 						if ( checkEquipType(item) == TYPE_BOOTS ) // boots
1995 						{
1996 							if ( myStats->shoes == nullptr )
1997 							{
1998 								myStats->shoes = item; // pick up the armor
1999 								item = nullptr;
2000 								list_RemoveNode(entity->mynode);
2001 							}
2002 							else
2003 							{
2004 								if ( myStats->shoes->beatitude >= 0 ) // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
2005 								{
2006 									// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
2007 									// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
2008 									currentAC = AC(myStats);
2009 									oldarmor = myStats->shoes;
2010 									myStats->shoes = item;
2011 									newAC = AC(myStats);
2012 									myStats->shoes = oldarmor;
2013 
2014 									//If the new armor is better than the current armor.
2015 									if ( newAC > currentAC )
2016 									{
2017 										dropItemMonster(myStats->shoes, this, myStats);
2018 										myStats->shoes = item;
2019 										item = nullptr;
2020 										list_RemoveNode(entity->mynode);
2021 									}
2022 								}
2023 							}
2024 						}
2025 						else if ( checkEquipType(item) == TYPE_GLOVES )
2026 						{
2027 							if ( myStats->gloves == nullptr )
2028 							{
2029 								myStats->gloves = item; // pick up the armor
2030 								item = nullptr;
2031 								list_RemoveNode(entity->mynode);
2032 							}
2033 							else
2034 							{
2035 								if ( myStats->gloves->beatitude >= 0 ) // if the armor is not cursed, proceed. Won't do anything if the armor is cursed.
2036 								{
2037 									// to compare the armors, we use the AC function to check the Armor Class of the equipment the goblin
2038 									// is currently wearing versus the Armor Class that the goblin would have if it had the new armor.
2039 									currentAC = AC(myStats);
2040 									oldarmor = myStats->gloves;
2041 									myStats->gloves = item;
2042 									newAC = AC(myStats);
2043 									myStats->gloves = oldarmor;
2044 
2045 									//If the new armor is better than the current armor.
2046 									if ( newAC > currentAC )
2047 									{
2048 										dropItemMonster(myStats->gloves, this, myStats);
2049 										myStats->gloves = item;
2050 										item = nullptr;
2051 										list_RemoveNode(entity->mynode);
2052 									}
2053 								}
2054 							}
2055 						}
2056 					}
2057 				}
2058 				else if ( itemCategory(item) == POTION )
2059 				{
2060 					if ( myStats->weapon == nullptr ) //Not currently holding a weapon.
2061 					{
2062 						myStats->weapon = item; //Assign the monster's weapon.
2063 						item = nullptr;
2064 						list_RemoveNode(entity->mynode);
2065 					}
2066 					//Don't pick up if already wielding something.
2067 				}
2068 				else if ( itemCategory(item) == THROWN )
2069 				{
2070 					if ( myStats->weapon == nullptr ) //Not currently holding a weapon.
2071 					{
2072 						if ( !entity->itemNotMoving && entity->parent && entity->parent != uid )
2073 						{
2074 							//Don't pick up the item.
2075 						}
2076 						else
2077 						{
2078 							myStats->weapon = item; //Assign the monster's weapon.
2079 							item = nullptr;
2080 							list_RemoveNode(entity->mynode);
2081 						}
2082 					}
2083 					//Don't pick up if already wielding something.
2084 				}
2085 
2086 				if ( item != nullptr )
2087 				{
2088 					free(item);
2089 				}
2090 			}
2091 		}
2092 
2093 		list_FreeAll(items);
2094 		free(items);
2095 	}
2096 }
2097 
2098 /*-------------------------------------------------------------------------------
2099 
2100 uidToEntity
2101 
2102 Returns an entity pointer from the given entity UID, provided one exists.
2103 Otherwise returns NULL
2104 
2105 -------------------------------------------------------------------------------*/
2106 
uidToEntity(Sint32 uidnum)2107 Entity* uidToEntity(Sint32 uidnum)
2108 {
2109 	node_t* node;
2110 	Entity* entity;
2111 
2112 	auto it = map.entities_map.find(uidnum);
2113 	if ( it != map.entities_map.end() )
2114 		return (Entity*)it->second->element;
2115 
2116 	return NULL;
2117 }
2118 
2119 /*-------------------------------------------------------------------------------
2120 
2121 Entity::setHP
2122 
2123 sets the HP of the given entity
2124 
2125 -------------------------------------------------------------------------------*/
2126 
setHP(int amount)2127 void Entity::setHP(int amount)
2128 {
2129 	Stat* entitystats = this->getStats();
2130 	if ( !entitystats )
2131 	{
2132 		return;
2133 	}
2134 
2135 	int healthDiff = entitystats->HP;
2136 
2137 	if ( this->behavior == &actPlayer && godmode )
2138 	{
2139 		amount = entitystats->MAXHP;
2140 	}
2141 	if ( !entitystats || amount == entitystats->HP )
2142 	{
2143 		return;
2144 	}
2145 	entitystats->HP = std::min(std::max(0, amount), entitystats->MAXHP);
2146 	healthDiff -= entitystats->HP;
2147 	strncpy(entitystats->obituary, language[1500], 127);
2148 
2149 	if ( this->behavior == &actPlayer && buddhamode && entitystats->HP < 1 )
2150 	{
2151 		entitystats->HP = 1; //Buddhas never die!
2152 	}
2153 
2154 	if ( multiplayer == SERVER )
2155 	{
2156 		for ( int i = 1; i < MAXPLAYERS; i++ )
2157 		{
2158 			if ( players[i] && this == players[i]->entity )
2159 			{
2160 				// tell the client its HP changed
2161 				strcpy((char*)net_packet->data, "UPHP");
2162 				SDLNet_Write32((Uint32)entitystats->HP, &net_packet->data[4]);
2163 				SDLNet_Write32((Uint32)NOTHING, &net_packet->data[8]);
2164 				net_packet->address.host = net_clients[i - 1].host;
2165 				net_packet->address.port = net_clients[i - 1].port;
2166 				net_packet->len = 12;
2167 				sendPacketSafe(net_sock, -1, net_packet, i - 1);
2168 			}
2169 			if ( this->behavior == &actPlayer && abs(healthDiff) > 0 )
2170 			{
2171 				if ( serverSchedulePlayerHealthUpdate == 0 )
2172 				{
2173 					serverSchedulePlayerHealthUpdate = ticks;
2174 				}
2175 			}
2176 		}
2177 		if ( this->behavior == &actMonster )
2178 		{
2179 			if ( this->monsterAllyIndex >= 1 && this->monsterAllyIndex < MAXPLAYERS )
2180 			{
2181 				if ( abs(healthDiff) == 1 || healthDiff == 0 )
2182 				{
2183 					serverUpdateAllyHP(this->monsterAllyIndex, getUID(), entitystats->HP, entitystats->MAXHP, true);
2184 				}
2185 				else
2186 				{
2187 					serverUpdateAllyHP(this->monsterAllyIndex, getUID(), entitystats->HP, entitystats->MAXHP, true);
2188 				}
2189 			}
2190 		}
2191 	}
2192 }
2193 
2194 /*-------------------------------------------------------------------------------
2195 
2196 Entity::modHP
2197 
2198 modifies the HP of the given entity
2199 
2200 -------------------------------------------------------------------------------*/
2201 
modHP(int amount)2202 void Entity::modHP(int amount)
2203 {
2204 	Stat* entitystats = this->getStats();
2205 
2206 	if ( this->behavior == &actPlayer )
2207 	{
2208 		if ( godmode && amount < 0 )
2209 		{
2210 			amount = 0;
2211 		}
2212 		else if ( entitystats && entitystats->type == AUTOMATON && entitystats->HP <= 0 && this->playerAutomatonDeathCounter != 0 )
2213 		{
2214 			return;
2215 		}
2216 	}
2217 	if ( !entitystats || amount == 0 )
2218 	{
2219 		return;
2220 	}
2221 
2222 	this->setHP(entitystats->HP + amount);
2223 }
2224 
2225 /*-------------------------------------------------------------------------------
2226 
2227 Entity::setMP
2228 
2229 sets the MP of the given entity
2230 
2231 -------------------------------------------------------------------------------*/
2232 
setMP(int amount,bool updateClients)2233 void Entity::setMP(int amount, bool updateClients)
2234 {
2235 	Stat* entitystats = this->getStats();
2236 
2237 	if ( this->behavior == &actPlayer && godmode )
2238 	{
2239 		amount = entitystats->MAXMP;
2240 	}
2241 	if ( !entitystats || amount == entitystats->MP )
2242 	{
2243 		return;
2244 	}
2245 	entitystats->MP = std::min(std::max(0, amount), entitystats->MAXMP);
2246 
2247 	if ( multiplayer == SERVER && updateClients )
2248 	{
2249 		for ( int i = 1; i < MAXPLAYERS; i++ )
2250 		{
2251 			if ( players[i] && this == players[i]->entity )
2252 			{
2253 				// tell the client its MP just changed
2254 				strcpy((char*)net_packet->data, "UPMP");
2255 				SDLNet_Write32((Uint32)entitystats->MP, &net_packet->data[4]);
2256 				net_packet->address.host = net_clients[i - 1].host;
2257 				net_packet->address.port = net_clients[i - 1].port;
2258 				net_packet->len = 8;
2259 				sendPacketSafe(net_sock, -1, net_packet, i - 1);
2260 			}
2261 		}
2262 	}
2263 }
2264 
2265 /*-------------------------------------------------------------------------------
2266 
2267 Entity::modMP
2268 
2269 modifies the MP of the given entity
2270 
2271 -------------------------------------------------------------------------------*/
2272 
modMP(int amount,bool updateClients)2273 void Entity::modMP(int amount, bool updateClients)
2274 {
2275 	Stat* entitystats = this->getStats();
2276 
2277 	if ( !entitystats )
2278 	{
2279 		return;
2280 	}
2281 
2282 	if ( this->behavior == &actPlayer && godmode && amount < 0 )
2283 	{
2284 		amount = 0;
2285 	}
2286 	if ( !entitystats || amount == 0 )
2287 	{
2288 		return;
2289 	}
2290 
2291 	this->setMP(entitystats->MP + amount, updateClients);
2292 }
2293 
getMP()2294 int Entity::getMP()
2295 {
2296 	Stat* myStats = getStats();
2297 
2298 	if ( !myStats )
2299 	{
2300 		return 0;
2301 	}
2302 
2303 	return myStats->MP;
2304 }
2305 
getHP()2306 int Entity::getHP()
2307 {
2308 	Stat* myStats = getStats();
2309 
2310 	if ( !myStats )
2311 	{
2312 		return 0;
2313 	}
2314 
2315 	return myStats->HP;
2316 }
2317 
2318 /*-------------------------------------------------------------------------------
2319 
2320 Entity::drainMP
2321 
2322 Removes this much from MP. Anything over the entity's MP is subtracted from their health. Can be very dangerous.
2323 
2324 -------------------------------------------------------------------------------*/
2325 
drainMP(int amount,bool notifyOverexpend)2326 void Entity::drainMP(int amount, bool notifyOverexpend)
2327 {
2328 	//A pointer to the entity's stats.
2329 	Stat* entitystats = this->getStats();
2330 
2331 	//Check if no stats found.
2332 	if ( entitystats == NULL || amount == 0 )
2333 	{
2334 		return;
2335 	}
2336 
2337 	int overdrawn = 0;
2338 	entitystats->MP -= amount;
2339 	int player = -1;
2340 	for ( int i = 0; i < MAXPLAYERS; ++i )
2341 	{
2342 		if ( players[i] && this == players[i]->entity )
2343 		{
2344 			player = i; //Set the player.
2345 		}
2346 	}
2347 
2348 	if ( player >= 0 && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 )
2349 	{
2350 		if ( svFlags & SV_FLAG_HUNGER )
2351 		{
2352 			// we cast a spell or forcibly reduced our MP. therefore our hunger should reduce to match the MP value.
2353 			if ( amount > 0 )
2354 			{
2355 				Sint32 hungerPointPerMana = playerInsectoidHungerValueOfManaPoint(*entitystats);
2356 				Sint32 oldHunger = entitystats->HUNGER;
2357 				entitystats->HUNGER -= amount * hungerPointPerMana;
2358 				entitystats->HUNGER = std::max(0, entitystats->HUNGER);
2359 				if ( player > 0 )
2360 				{
2361 					serverUpdateHunger(player);
2362 				}
2363 			}
2364 		}
2365 	}
2366 
2367 	if ( entitystats->MP < 0 )
2368 	{
2369 		//Overdrew. Take that extra and flow it over into HP.
2370 		overdrawn = entitystats->MP;
2371 		entitystats->MP = 0;
2372 	}
2373 	if ( multiplayer == SERVER )
2374 	{
2375 		//First check if the entity is the player.
2376 		for ( int i = 1; i < MAXPLAYERS; ++i )
2377 		{
2378 			if ( players[i] && this == players[i]->entity )
2379 			{
2380 				//It is. Tell the client its MP just changed.
2381 				strcpy((char*)net_packet->data, "UPMP");
2382 				SDLNet_Write32((Uint32)entitystats->MP, &net_packet->data[4]);
2383 				SDLNet_Write32((Uint32)stats[i]->type, &net_packet->data[8]);
2384 				net_packet->address.host = net_clients[i - 1].host;
2385 				net_packet->address.port = net_clients[i - 1].port;
2386 				net_packet->len = 12;
2387 				sendPacketSafe(net_sock, -1, net_packet, i - 1);
2388 			}
2389 		}
2390 	}
2391 	else if ( clientnum != 0 && multiplayer == CLIENT )
2392 	{
2393 		if ( this == players[clientnum]->entity )
2394 		{
2395 			//It's the player entity. Tell the server its MP changed.
2396 			strcpy((char*)net_packet->data, "UPMP");
2397 			net_packet->data[4] = clientnum;
2398 			SDLNet_Write32((Uint32)entitystats->MP, &net_packet->data[5]);
2399 			SDLNet_Write32((Uint32)stats[clientnum]->type, &net_packet->data[9]);
2400 			net_packet->address.host = net_server.host;
2401 			net_packet->address.port = net_server.port;
2402 			net_packet->len = 13;
2403 			sendPacketSafe(net_sock, -1, net_packet, 0);
2404 		}
2405 	}
2406 
2407 	if ( overdrawn < 0 )
2408 	{
2409 		if ( player >= 0 && notifyOverexpend )
2410 		{
2411 			Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
2412 			messagePlayerColor(player, color, language[621]);
2413 		}
2414 		this->modHP(overdrawn); //Drain the extra magic from health.
2415 		Stat* tempStats = this->getStats();
2416 		if ( tempStats )
2417 		{
2418 			if ( tempStats->sex == MALE )
2419 			{
2420 				this->setObituary(language[1528]);
2421 			}
2422 			else
2423 			{
2424 				this->setObituary(language[1529]);
2425 			}
2426 		}
2427 	}
2428 }
2429 
2430 /*-------------------------------------------------------------------------------
2431 
2432 Entity::safeConsumeMP
2433 
2434 A function for the magic code. Attempts to remove mana without overdrawing the player. Returns true if success, returns false if didn't have enough mana.
2435 
2436 -------------------------------------------------------------------------------*/
2437 
safeConsumeMP(int amount)2438 bool Entity::safeConsumeMP(int amount)
2439 {
2440 	Stat* stat = this->getStats();
2441 
2442 	//Check if no stats found.
2443 	if ( !stat )
2444 	{
2445 		return false;
2446 	}
2447 
2448 	if ( amount > stat->MP )
2449 	{
2450 		if ( behavior == &actPlayer && stat->type == VAMPIRE )
2451 		{
2452 			int HP = stat->HP;
2453 			this->drainMP(amount, false);
2454 			if ( (HP - stat->HP > 0) && (stat->HP % 5 == 0) )
2455 			{
2456 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
2457 				messagePlayerColor(skill[2], color, language[621]);
2458 			}
2459 			return true;
2460 		}
2461 		return false;    //Not enough mana.
2462 	}
2463 	else
2464 	{
2465 		if ( behavior == &actPlayer && stat->playerRace == RACE_INSECTOID && stat->appearance == 0 )
2466 		{
2467 			if ( svFlags & SV_FLAG_HUNGER )
2468 			{
2469 				// we cast a spell or forcibly reduced our MP. therefore our hunger should reduce to match the MP value.
2470 				if ( amount > 0 )
2471 				{
2472 					Sint32 hungerPointPerMana = playerInsectoidHungerValueOfManaPoint(*stat);
2473 					Sint32 oldHunger = stat->HUNGER;
2474 					stat->HUNGER -= amount * hungerPointPerMana;
2475 					stat->HUNGER = std::max(0, stat->HUNGER);
2476 					if ( this->skill[2] > 0 )
2477 					{
2478 						serverUpdateHunger(this->skill[2]);
2479 					}
2480 				}
2481 			}
2482 		}
2483 		this->modMP(-amount);
2484 		return true;
2485 	}
2486 
2487 	return false;
2488 }
2489 
2490 /*-------------------------------------------------------------------------------
2491 
2492 Entity::handleEffects
2493 
2494 processes general character status updates for a given entity, such as
2495 hunger, level ups, poison, etc.
2496 
2497 -------------------------------------------------------------------------------*/
2498 
handleEffects(Stat * myStats)2499 void Entity::handleEffects(Stat* myStats)
2500 {
2501 	int increasestat[3] = { 0, 0, 0 };
2502 	int i, c;
2503 	int player = -1;
2504 
2505 	if ( !myStats )
2506 	{
2507 		return;
2508 	}
2509 	if ( this->behavior == &actPlayer )
2510 	{
2511 		player = this->skill[2];
2512 
2513 		// god mode and buddha mode
2514 		if ( godmode )
2515 		{
2516 			myStats->HP = myStats->MAXHP;
2517 			myStats->MP = myStats->MAXMP;
2518 		}
2519 		else if ( buddhamode )
2520 		{
2521 			if ( myStats->HP <= 0 )
2522 			{
2523 				myStats->HP = 1;
2524 			}
2525 		}
2526 	}
2527 
2528 	auto& camera_shakex = cameravars[player >= 0 ? player : 0].shakex;
2529 	auto& camera_shakey = cameravars[player >= 0 ? player : 0].shakey;
2530 	auto& camera_shakex2 = cameravars[player >= 0 ? player : 0].shakex2;
2531 	auto& camera_shakey2 = cameravars[player >= 0 ? player : 0].shakey2;
2532 
2533 	// sleep Zs
2534 	if ( myStats->EFFECTS[EFF_ASLEEP] && ticks % 30 == 0 )
2535 	{
2536 		spawnSleepZ(this->x + cos(this->yaw) * 2, this->y + sin(this->yaw) * 2, this->z);
2537 	}
2538 
2539 	int startingHPInHandleEffects = myStats->HP;
2540 
2541 	if ( creatureShadowTaggedThisUid != 0 )
2542 	{
2543 		Entity* tagged = uidToEntity(creatureShadowTaggedThisUid);
2544 		if ( !tagged )
2545 		{
2546 			creatureShadowTaggedThisUid = 0;
2547 			serverUpdateEntitySkill(this, 54);
2548 		}
2549 		else
2550 		{
2551 			Stat* tagStats = tagged->getStats();
2552 			if ( tagStats && !tagStats->EFFECTS[EFF_SHADOW_TAGGED] ) // effect timed out.
2553 			{
2554 				creatureShadowTaggedThisUid = 0;
2555 				serverUpdateEntitySkill(this, 54);
2556 			}
2557 		}
2558 	}
2559 
2560 
2561 
2562 	// level ups
2563 	if ( myStats->EXP >= 100 )
2564 	{
2565 		myStats->EXP -= 100;
2566 		myStats->LVL++;
2567 		Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
2568 		messagePlayerColor(player, color, language[622]);
2569 		playSoundPlayer(player, 97, 128);
2570 
2571 		// increase MAXHP/MAXMP
2572 		myStats->MAXHP += HP_MOD;
2573 		modHP(HP_MOD);
2574 		myStats->HP = std::min(myStats->HP, myStats->MAXHP);
2575 		if ( !(behavior == &actMonster && monsterAllySummonRank != 0) )
2576 		{
2577 			myStats->MP += MP_MOD;
2578 			myStats->MAXMP += MP_MOD;
2579 			if ( behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 )
2580 			{
2581 				myStats->MAXMP = std::min(50, myStats->MAXMP);
2582 				if ( svFlags & SV_FLAG_HUNGER )
2583 				{
2584 					Sint32 hungerPointPerMana = playerInsectoidHungerValueOfManaPoint(*myStats);
2585 					myStats->HUNGER += MP_MOD * hungerPointPerMana;
2586 					myStats->HUNGER = std::min(1000, myStats->HUNGER);
2587 					serverUpdateHunger(skill[2]);
2588 				}
2589 			}
2590 			myStats->MP = std::min(myStats->MP, myStats->MAXMP);
2591 		}
2592 
2593 		// now pick three attributes to increase
2594 
2595 		if ( player >= 0 )
2596 		{
2597 			// players only.
2598 			this->playerStatIncrease(client_classes[player], increasestat);
2599 		}
2600 		else if ( behavior == &actMonster && monsterAllySummonRank != 0 )
2601 		{
2602 			bool secondSummon = false;
2603 			if ( !strcmp(myStats->name, "skeleton knight") )
2604 			{
2605 				this->playerStatIncrease(CLASS_WARRIOR, increasestat); // warrior weighting
2606 			}
2607 			else if ( !strcmp(myStats->name, "skeleton sentinel") )
2608 			{
2609 				secondSummon = true;
2610 				this->playerStatIncrease(CLASS_ROGUE, increasestat); // rogue weighting
2611 			}
2612 
2613 			bool rankUp = false;
2614 
2615 			if ( myStats->type == SKELETON )
2616 			{
2617 				int rank = myStats->LVL / 5;
2618 				if ( rank <= 6 && myStats->LVL % 5 == 0 )
2619 				{
2620 					// went up a rank (every 5 LVLs)
2621 					rank = std::min(1 + rank, 7);
2622 					rankUp = true;
2623 					createParticleDropRising(this, 791, 1.0);
2624 					serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 791);
2625 					skeletonSummonSetEquipment(myStats, std::min(7, 1 + (myStats->LVL / 5)));
2626 				}
2627 				else if ( myStats->LVL == 35 )
2628 				{
2629 					steamAchievementClient(this->monsterAllyIndex, "BARONY_ACH_BONE_TO_PICK");
2630 				}
2631 			}
2632 
2633 			for ( i = 0; i < 3; i++ )
2634 			{
2635 				switch ( increasestat[i] )
2636 				{
2637 					case STAT_STR:
2638 						myStats->STR++;
2639 						break;
2640 					case STAT_DEX:
2641 						myStats->DEX++;
2642 						break;
2643 					case STAT_CON:
2644 						myStats->CON++;
2645 						break;
2646 					case STAT_INT:
2647 						myStats->INT++;
2648 						break;
2649 					case STAT_PER:
2650 						myStats->PER++;
2651 						break;
2652 					case STAT_CHR:
2653 						myStats->CHR++;
2654 						break;
2655 					default:
2656 						break;
2657 				}
2658 
2659 			}
2660 			Entity* leader = uidToEntity(myStats->leader_uid);
2661 			if ( leader )
2662 			{
2663 				Stat* leaderStats = leader->getStats();
2664 				if ( leaderStats )
2665 				{
2666 					if ( !secondSummon )
2667 					{
2668 						leaderStats->playerSummonLVLHP = (myStats->LVL << 16);
2669 						leaderStats->playerSummonLVLHP |= (myStats->MAXHP);
2670 
2671 						leaderStats->playerSummonSTRDEXCONINT = (myStats->STR << 24);
2672 						leaderStats->playerSummonSTRDEXCONINT |= (myStats->DEX << 16);
2673 						leaderStats->playerSummonSTRDEXCONINT |= (myStats->CON << 8);
2674 						leaderStats->playerSummonSTRDEXCONINT |= (myStats->INT);
2675 
2676 						leaderStats->playerSummonPERCHR = (myStats->PER << 24);
2677 						leaderStats->playerSummonPERCHR |= (myStats->CHR << 16);
2678 						leaderStats->playerSummonPERCHR |= (this->monsterAllySummonRank << 8);
2679 					}
2680 					else
2681 					{
2682 						leaderStats->playerSummon2LVLHP = (myStats->LVL << 16);
2683 						leaderStats->playerSummon2LVLHP |= (myStats->MAXHP);
2684 
2685 						leaderStats->playerSummon2STRDEXCONINT = (myStats->STR << 24);
2686 						leaderStats->playerSummon2STRDEXCONINT |= (myStats->DEX << 16);
2687 						leaderStats->playerSummon2STRDEXCONINT |= (myStats->CON << 8);
2688 						leaderStats->playerSummon2STRDEXCONINT |= (myStats->INT);
2689 
2690 						leaderStats->playerSummon2PERCHR = (myStats->PER << 24);
2691 						leaderStats->playerSummon2PERCHR |= (myStats->CHR << 16);
2692 						leaderStats->playerSummon2PERCHR |= (this->monsterAllySummonRank << 8);
2693 					}
2694 					if ( leader->behavior == &actPlayer )
2695 					{
2696 						serverUpdatePlayerSummonStrength(leader->skill[2]);
2697 						if ( rankUp )
2698 						{
2699 							color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
2700 							messagePlayerMonsterEvent(leader->skill[2], color, *myStats, language[3197], language[3197], MSG_GENERIC);
2701 							playSoundPlayer(leader->skill[2], 40, 64);
2702 						}
2703 					}
2704 				}
2705 			}
2706 		}
2707 		else
2708 		{
2709 			// monsters use this.
2710 			increasestat[0] = rand() % 6;
2711 			int r = rand() % 6;
2712 			while ( r == increasestat[0] ) {
2713 				r = rand() % 6;
2714 			}
2715 			increasestat[1] = r;
2716 			r = rand() % 6;
2717 			while ( r == increasestat[0] || r == increasestat[1] ) {
2718 				r = rand() % 6;
2719 			}
2720 			increasestat[2] = r;
2721 
2722 			for ( i = 0; i < 3; i++ )
2723 			{
2724 				switch ( increasestat[i] )
2725 				{
2726 					case STAT_STR:
2727 						myStats->STR++;
2728 						break;
2729 					case STAT_DEX:
2730 						myStats->DEX++;
2731 						break;
2732 					case STAT_CON:
2733 						myStats->CON++;
2734 						break;
2735 					case STAT_INT:
2736 						myStats->INT++;
2737 						break;
2738 					case STAT_PER:
2739 						myStats->PER++;
2740 						break;
2741 					case STAT_CHR:
2742 						myStats->CHR++;
2743 						break;
2744 				}
2745 			}
2746 		}
2747 
2748 		if ( behavior == &actMonster )
2749 		{
2750 			if ( myStats->leader_uid )
2751 			{
2752 				Entity* leader = uidToEntity(myStats->leader_uid);
2753 				if ( leader )
2754 				{
2755 					for ( i = 0; i < MAXPLAYERS; ++i )
2756 					{
2757 						if ( players[i] && players[i]->entity == leader )
2758 						{
2759 							color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2760 							messagePlayerMonsterEvent(i, color, *myStats, language[2379], language[2379], MSG_GENERIC);
2761 							playSoundEntity(this, 97, 128);
2762 							serverUpdateAllyStat(i, getUID(), myStats->LVL, myStats->HP, myStats->MAXHP, myStats->type);
2763 						}
2764 					}
2765 				}
2766 			}
2767 		}
2768 
2769 		if ( player >= 0 )
2770 		{
2771 			for ( i = 0; i < NUMSTATS * 2; ++i )
2772 			{
2773 				myStats->PLAYER_LVL_STAT_TIMER[i] = 0;
2774 			}
2775 
2776 			bool rolledBonusStat = false;
2777 			int statIconTicks = 250;
2778 
2779 			for ( i = 0; i < 3; i++ )
2780 			{
2781 				messagePlayerColor(player, color, language[623 + increasestat[i]]);
2782 				switch ( increasestat[i] )
2783 				{
2784 					case STAT_STR: // STR
2785 						myStats->STR++;
2786 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2787 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2788 						{
2789 							if ( rand() % 5 == 0 )
2790 							{
2791 								myStats->STR++;
2792 								rolledBonusStat = true;
2793 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2794 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2795 							}
2796 						}
2797 						break;
2798 					case STAT_DEX: // DEX
2799 						myStats->DEX++;
2800 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2801 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2802 						{
2803 							if ( rand() % 5 == 0 )
2804 							{
2805 								myStats->DEX++;
2806 								rolledBonusStat = true;
2807 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2808 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2809 							}
2810 						}
2811 						break;
2812 					case STAT_CON: // CON
2813 						myStats->CON++;
2814 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2815 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2816 						{
2817 							if ( rand() % 5 == 0 )
2818 							{
2819 								myStats->CON++;
2820 								rolledBonusStat = true;
2821 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2822 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2823 							}
2824 						}
2825 						break;
2826 					case STAT_INT: // INT
2827 						myStats->INT++;
2828 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2829 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2830 						{
2831 							if ( rand() % 5 == 0 )
2832 							{
2833 								myStats->INT++;
2834 								rolledBonusStat = true;
2835 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2836 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2837 							}
2838 						}
2839 						break;
2840 					case STAT_PER: // PER
2841 						myStats->PER++;
2842 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2843 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2844 						{
2845 							if ( rand() % 5 == 0 )
2846 							{
2847 								myStats->PER++;
2848 								rolledBonusStat = true;
2849 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2850 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2851 							}
2852 						}
2853 						break;
2854 					case STAT_CHR: // CHR
2855 						myStats->CHR++;
2856 						myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks;
2857 						if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat )
2858 						{
2859 							if ( rand() % 5 == 0 )
2860 							{
2861 								myStats->CHR++;
2862 								rolledBonusStat = true;
2863 								myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks;
2864 								//messagePlayer(0, "Rolled bonus in %d", increasestat[i]);
2865 							}
2866 						}
2867 						break;
2868 				}
2869 			}
2870 
2871 			for ( i = 0; i < MAXPLAYERS; ++i )
2872 			{
2873 				// broadcast a player levelled up to other players.
2874 				if ( i != player )
2875 				{
2876 					if ( client_disconnected[i] )
2877 					{
2878 						continue;
2879 					}
2880 					messagePlayerMonsterEvent(i, color, *myStats, language[2379], language[2379], MSG_GENERIC, this);
2881 				}
2882 			}
2883 		}
2884 
2885 		// inform clients of stat changes
2886 		if ( multiplayer == SERVER )
2887 		{
2888 			if ( player > 0 )
2889 			{
2890 				strcpy((char*)net_packet->data, "ATTR");
2891 				net_packet->data[4] = clientnum;
2892 				net_packet->data[5] = (Sint8)myStats->STR;
2893 				net_packet->data[6] = (Sint8)myStats->DEX;
2894 				net_packet->data[7] = (Sint8)myStats->CON;
2895 				net_packet->data[8] = (Sint8)myStats->INT;
2896 				net_packet->data[9] = (Sint8)myStats->PER;
2897 				net_packet->data[10] = (Sint8)myStats->CHR;
2898 				net_packet->data[11] = (Sint8)myStats->EXP;
2899 				net_packet->data[12] = (Sint8)myStats->LVL;
2900 				SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]);
2901 				SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]);
2902 				SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]);
2903 				SDLNet_Write16((Sint16)myStats->MAXMP, &net_packet->data[19]);
2904 				net_packet->address.host = net_clients[player - 1].host;
2905 				net_packet->address.port = net_clients[player - 1].port;
2906 				net_packet->len = 21;
2907 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
2908 
2909 				strcpy((char*)net_packet->data, "LVLI");
2910 				net_packet->data[4] = clientnum;
2911 				net_packet->data[5] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_STR];
2912 				net_packet->data[6] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_DEX];
2913 				net_packet->data[7] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_CON];
2914 				net_packet->data[8] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_INT];
2915 				net_packet->data[9] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_PER];
2916 				net_packet->data[10] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_CHR];
2917 				net_packet->data[11] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_STR + NUMSTATS];
2918 				net_packet->data[12] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_DEX + NUMSTATS];
2919 				net_packet->data[13] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_CON + NUMSTATS];
2920 				net_packet->data[14] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_INT + NUMSTATS];
2921 				net_packet->data[15] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_PER + NUMSTATS];
2922 				net_packet->data[16] = (Uint8)myStats->PLAYER_LVL_STAT_TIMER[STAT_CHR + NUMSTATS];
2923 				net_packet->address.host = net_clients[player - 1].host;
2924 				net_packet->address.port = net_clients[player - 1].port;
2925 				net_packet->len = 17;
2926 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
2927 			}
2928 			serverUpdatePlayerLVL(); // update all clients of party levels.
2929 		}
2930 
2931 		for ( i = 0; i < NUMSTATS; ++i )
2932 		{
2933 			myStats->PLAYER_LVL_STAT_BONUS[i] = -1;
2934 		}
2935 	}
2936 
2937 	// hunger
2938 	int hungerring = 0;
2939 	if ( myStats->ring != NULL )
2940 	{
2941 		if ( myStats->ring->type == RING_SLOWDIGESTION )
2942 		{
2943 			if ( myStats->ring->beatitude >= 0 )
2944 			{
2945 				hungerring = 1;
2946 			}
2947 			else
2948 			{
2949 				if ( behavior == &actPlayer && shouldInvertEquipmentBeatitude(myStats) )
2950 				{
2951 					hungerring = 1;
2952 				}
2953 				else
2954 				{
2955 					hungerring = -1;
2956 				}
2957 			}
2958 		}
2959 	}
2960 	int vampiricHunger = 0;
2961 	if ( myStats->EFFECTS[EFF_VAMPIRICAURA] )
2962 	{
2963 		if ( myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] == -2 )
2964 		{
2965 			vampiricHunger = 2;
2966 		}
2967 		else
2968 		{
2969 			vampiricHunger = 1;
2970 		}
2971 	}
2972 
2973 	int hungerTickRate = 30; // how many ticks to reduce hunger by a point.
2974 	if ( !strncmp(map.name, "Sanctum", 7)
2975 		|| !strncmp(map.name, "Boss", 4)
2976 		|| !strncmp(map.name, "Hell Boss", 9)
2977 		|| !strncmp(map.name, "Mages Guild", 11) )
2978 	{
2979 		hungerring = 1; // slow down hunger on boss stages.
2980 		if ( vampiricHunger > 0 )
2981 		{
2982 			vampiricHunger *= 8;
2983 		}
2984 	}
2985 
2986 	if ( vampiricHunger > 0 )
2987 	{
2988 		hungerTickRate = 5 * vampiricHunger;
2989 	}
2990 	else if ( hungerring > 0 )
2991 	{
2992 		hungerTickRate = 120;
2993 	}
2994 	else if ( hungerring < 0 )
2995 	{
2996 		hungerTickRate = 15;
2997 	}
2998 
2999 	int playerCount = 0;
3000 	for ( i = 0; i < MAXPLAYERS; ++i )
3001 	{
3002 		if ( !client_disconnected[i] )
3003 		{
3004 			++playerCount;
3005 		}
3006 	}
3007 
3008 	if ( playerCount == 3 )
3009 	{
3010 		hungerTickRate *= 1.25;
3011 	}
3012 	else if ( playerCount == 4 )
3013 	{
3014 		hungerTickRate *= 1.5;
3015 	}
3016 	if ( myStats->type == INSECTOID )
3017 	{
3018 		hungerTickRate *= 1.5;
3019 	}
3020 
3021 	bool processHunger = (svFlags & SV_FLAG_HUNGER) && !MFLAG_DISABLEHUNGER; // check server flags if hunger is enabled.
3022 	if ( player >= 0 )
3023 	{
3024 		if ( myStats->type == SKELETON || myStats->type == AUTOMATON )
3025 		{
3026 			processHunger = false;
3027 		}
3028 	}
3029 
3030 	bool playerAutomaton = (myStats->type == AUTOMATON && player >= 0);
3031 
3032 	if ( playerAutomaton )
3033 	{
3034 		// give a little extra hunger duration.
3035 		if ( playerCount == 3 )
3036 		{
3037 			hungerTickRate *= 1.25; // 1.55x (1.25 x 1.25)
3038 		}
3039 		else if ( playerCount == 4 )
3040 		{
3041 			hungerTickRate *= 1.5; // 2.55x (1.5 x 1.5)
3042 		}
3043 
3044 		if ( myStats->HUNGER > 1000 && hungerTickRate > 30 )
3045 		{
3046 			hungerTickRate = 30; // don't slow down during superheated.
3047 		}
3048 
3049 		if ( ticks % (hungerTickRate / 2) == 0 )
3050 		{
3051 			//messagePlayer(0, "hungertick %d, curr %d, players: %d", hungerTickRate, myStats->HUNGER, playerCount);
3052 			if ( myStats->HUNGER > 0 )
3053 			{
3054 				bool update = (myStats->HUNGER % 100 == 0);
3055 				if ( myStats->HUNGER > 300 && myStats->HUNGER <= 600 )
3056 				{
3057 					update = (myStats->HUNGER % 25 == 0); // critical levels for players to show hunger meter.
3058 				}
3059 				myStats->HUNGER--;
3060 				if ( update )
3061 				{
3062 					serverUpdateHunger(player);
3063 				}
3064 				if ( myStats->HUNGER == 299 )
3065 				{
3066 					messagePlayer(player, language[3708]);
3067 					messagePlayer(player, language[3709]);
3068 					playSoundPlayer(player, 32, 128);
3069 				}
3070 				else if ( myStats->HUNGER == 0 )
3071 				{
3072 					messagePlayer(player, language[3708]);
3073 					messagePlayer(player, language[3710]);
3074 					playSoundPlayer(player, 32, 128);
3075 				}
3076 			}
3077 			else
3078 			{
3079 				myStats->HUNGER = 0;
3080 			}
3081 		}
3082 	}
3083 
3084 	if ( !processHunger && !playerAutomaton )
3085 	{
3086 		if ( behavior == &actMonster )
3087 		{
3088 			myStats->HUNGER = 500;
3089 		}
3090 		else if ( myStats->HUNGER < 100 )
3091 		{
3092 			myStats->HUNGER = 100;
3093 			serverUpdateHunger(player);
3094 		}
3095 		if ( vampiricHunger > 0 )
3096 		{
3097 			if ( ticks % (TICKS_PER_SECOND * 25) == 0 )
3098 			{
3099 				this->modHP(-1);
3100 				if ( myStats->HP <= 0 )
3101 				{
3102 					this->setObituary(language[1530]);
3103 				}
3104 
3105 				// Give the Player feedback on being hurt
3106 				playSoundEntity(this, 28, 64); // "Damage.ogg"
3107 
3108 				if ( myStats->HP > 0 )
3109 				{
3110 					messagePlayer(player, language[3253]);
3111 
3112 					// Shake the Host's screen
3113 					if ( myStats->HP <= 10 )
3114 					{
3115 						if ( player == clientnum )
3116 						{
3117 							camera_shakex += .1;
3118 							camera_shakey += 10;
3119 						}
3120 						else if ( player > 0 && multiplayer == SERVER )
3121 						{
3122 							// Shake the Client's screen
3123 							strcpy((char*)net_packet->data, "SHAK");
3124 							net_packet->data[4] = 10; // turns into .1
3125 							net_packet->data[5] = 10;
3126 							net_packet->address.host = net_clients[player - 1].host;
3127 							net_packet->address.port = net_clients[player - 1].port;
3128 							net_packet->len = 6;
3129 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
3130 						}
3131 					}
3132 				}
3133 			}
3134 		}
3135 	}
3136 	else if ( ticks % hungerTickRate == 0 )
3137 	{
3138 		//messagePlayer(0, "hungertick %d, curr %d, players: %d", hungerTickRate, myStats->HUNGER, playerCount);
3139 		if ( myStats->HUNGER > 0 && !playerAutomaton )
3140 		{
3141 			myStats->HUNGER--;
3142 			Sint32 noLongerFull = 1500;
3143 			Sint32 youFeelHungry = 250;
3144 			Sint32 youFeelWeak = 150;
3145 			Sint32 youFeelFaint = 50;
3146 
3147 			if ( behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 )
3148 			{
3149 				youFeelHungry = 100;
3150 				youFeelWeak = 50;
3151 				youFeelFaint = 25;
3152 			}
3153 
3154 			if ( myStats->HUNGER == noLongerFull )
3155 			{
3156 				if ( !myStats->EFFECTS[EFF_VOMITING] )
3157 				{
3158 					messagePlayer(player, language[629]);
3159 				}
3160 				serverUpdateHunger(player);
3161 			}
3162 			else if ( myStats->HUNGER == youFeelHungry )
3163 			{
3164 				if ( !myStats->EFFECTS[EFF_VOMITING] )
3165 				{
3166 					messagePlayer(player, language[630]);
3167 					playSoundPlayer(player, 32, 128);
3168 				}
3169 				serverUpdateHunger(player);
3170 			}
3171 			else if ( myStats->HUNGER == youFeelWeak )
3172 			{
3173 				if ( !myStats->EFFECTS[EFF_VOMITING] )
3174 				{
3175 					messagePlayer(player, language[631]);
3176 					playSoundPlayer(player, 32, 128);
3177 				}
3178 				serverUpdateHunger(player);
3179 			}
3180 			else if ( myStats->HUNGER == youFeelFaint )
3181 			{
3182 				if ( !myStats->EFFECTS[EFF_VOMITING] )
3183 				{
3184 					messagePlayer(player, language[632]);
3185 					playSoundPlayer(player, 32, 128);
3186 				}
3187 				serverUpdateHunger(player);
3188 			}
3189 		}
3190 		else
3191 		{
3192 			bool doStarvation = true;
3193 			// Process HUNGER Effect - Wasting Away
3194 			if ( playerAutomaton )
3195 			{
3196 				if ( myStats->HUNGER == 0 && myStats->MP <= 0 )
3197 				{
3198 					// deal HP damage.
3199 					/*if ( myStats->HUNGER > 1 )
3200 					{
3201 						myStats->HUNGER = 1;
3202 					}*/
3203 				}
3204 				else
3205 				{
3206 					doStarvation = false;
3207 				}
3208 			}
3209 			else
3210 			{
3211 				myStats->HUNGER = 0;
3212 			}
3213 
3214 			// Deal Hunger damage every three seconds
3215 			if ( doStarvation && !myStats->EFFECTS[EFF_VOMITING] && ticks % 150 == 0 )
3216 			{
3217 				serverUpdateHunger(player);
3218 				bool allowStarve = true;
3219 				if ( playerAutomaton )
3220 				{
3221 					if ( !(svFlags & SV_FLAG_HUNGER) )
3222 					{
3223 						allowStarve = false; // hunger off, don't starve at 0 MP.
3224 					}
3225 				}
3226 
3227 				if ( player >= 0 && allowStarve ) // Only Players can starve
3228 				{
3229 					if ( buddhamode )
3230 					{
3231 						if ( myStats->HP - 4 > 0 )
3232 						{
3233 							this->modHP(-4);
3234 						}
3235 						else
3236 						{
3237 							// Instead of killing the Buddha Player, set their HP to 1
3238 							this->setHP(1);
3239 						}
3240 					}
3241 					else
3242 					{
3243 						this->modHP(-4);
3244 
3245 						if ( myStats->HP <= 0 )
3246 						{
3247 							this->setObituary(language[1530]);
3248 							if ( playerAutomaton )
3249 							{
3250 								this->setObituary(language[3864]);
3251 								steamAchievementEntity(this, "BARONY_ACH_RUST_IN_PEACE");
3252 							}
3253 						}
3254 					}
3255 
3256 					// Give the Player feedback on being hurt
3257 					playSoundEntity(this, 28, 64); // "Damage.ogg"
3258 
3259 					if ( myStats->HP > 0 )
3260 					{
3261 						if ( playerAutomaton )
3262 						{
3263 							messagePlayer(player, language[3714]);
3264 						}
3265 						else
3266 						{
3267 							messagePlayer(player, language[633]);
3268 						}
3269 					}
3270 
3271 					// Shake the Host's screen
3272 					if ( player == clientnum )
3273 					{
3274 						camera_shakex += .1;
3275 						camera_shakey += 10;
3276 					}
3277 					else if ( player > 0 && multiplayer == SERVER )
3278 					{
3279 						// Shake the Client's screen
3280 						strcpy((char*)net_packet->data, "SHAK");
3281 						net_packet->data[4] = 10; // turns into .1
3282 						net_packet->data[5] = 10;
3283 						net_packet->address.host = net_clients[player - 1].host;
3284 						net_packet->address.port = net_clients[player - 1].port;
3285 						net_packet->len = 6;
3286 						sendPacketSafe(net_sock, -1, net_packet, player - 1);
3287 					}
3288 				}
3289 			}
3290 		}
3291 	}
3292 
3293 	// "random" vomiting
3294 	if ( !this->char_gonnavomit && !myStats->EFFECTS[EFF_VOMITING]
3295 		&& myStats->type != SKELETON && effectShapeshift == NOTHING && myStats->type != AUTOMATON )
3296 	{
3297 		if ( myStats->HUNGER > 1500 && rand() % 1000 == 0 )
3298 		{
3299 			// oversatiation
3300 			if ( !(svFlags & SV_FLAG_HUNGER) || MFLAG_DISABLEHUNGER )
3301 			{
3302 				myStats->HUNGER = std::min(myStats->HUNGER, 1000); // reset hunger to safe level.
3303 			}
3304 			else
3305 			{
3306 				messagePlayer(player, language[634]);
3307 				this->char_gonnavomit = 140 + rand() % 60;
3308 			}
3309 		}
3310 		else if ( ticks % 60 == 0 && rand() % 200 == 0 && myStats->EFFECTS[EFF_DRUNK] && myStats->type != GOATMAN )
3311 		{
3312 			// drunkenness
3313 			messagePlayer(player, language[634]);
3314 			this->char_gonnavomit = 140 + rand() % 60;
3315 		}
3316 	}
3317 	if ( this->char_gonnavomit > 0 )
3318 	{
3319 		this->char_gonnavomit--;
3320 		if ( this->char_gonnavomit == 0 )
3321 		{
3322 			messagePlayer(player, language[635]);
3323 			myStats->EFFECTS[EFF_VOMITING] = true;
3324 			myStats->EFFECTS_TIMERS[EFF_VOMITING] = 50 + rand() % 20;
3325 			serverUpdateEffects(player);
3326 			if ( player == clientnum )
3327 			{
3328 				camera_shakey += 9;
3329 			}
3330 			else if ( player > 0 && multiplayer == SERVER )
3331 			{
3332 				strcpy((char*)net_packet->data, "SHAK");
3333 				net_packet->data[4] = 0; // turns into 0
3334 				net_packet->data[5] = 9;
3335 				net_packet->address.host = net_clients[player - 1].host;
3336 				net_packet->address.port = net_clients[player - 1].port;
3337 				net_packet->len = 6;
3338 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
3339 			}
3340 			playSoundEntity(this, 78, 96);
3341 			serverUpdatePlayerGameplayStats(player, STATISTICS_TEMPT_FATE, 5);
3342 
3343 			if ( myStats->type == INSECTOID )
3344 			{
3345 				castSpell(uid, &spell_acidSpray, true, false);
3346 			}
3347 		}
3348 	}
3349 
3350 	// vomiting
3351 	if ( myStats->EFFECTS[EFF_VOMITING] && ticks % 2 == 0 )
3352 	{
3353 		Entity* entity = spawnGib(this);
3354 		if ( entity )
3355 		{
3356 			entity->sprite = 29;
3357 			entity->flags[SPRITE] = true;
3358 			entity->flags[GENIUS] = true;
3359 			entity->flags[INVISIBLE] = false;
3360 			entity->yaw = this->yaw - 0.1 + (rand() % 20) * 0.01;
3361 			entity->pitch = (rand() % 360) * PI / 180.0;
3362 			entity->roll = (rand() % 360) * PI / 180.0;
3363 			double vel = (rand() % 15) / 10.f;
3364 			entity->vel_x = vel * cos(entity->yaw);
3365 			entity->vel_y = vel * sin(entity->yaw);
3366 			entity->vel_z = -.5;
3367 			if ( (svFlags & SV_FLAG_HUNGER) )
3368 			{
3369 				if ( myStats->type != INSECTOID && myStats->type != AUTOMATON
3370 					&& myStats->type != SKELETON && effectShapeshift == NOTHING )
3371 				{
3372 					myStats->HUNGER -= 40;
3373 					if ( myStats->HUNGER <= 50 )
3374 					{
3375 						myStats->HUNGER = 50;
3376 						myStats->EFFECTS_TIMERS[EFF_VOMITING] = 1;
3377 					}
3378 				}
3379 			}
3380 			serverSpawnGibForClient(entity);
3381 		}
3382 	}
3383 
3384 	// healing over time
3385 	int healring = 0;
3386 	int healthRegenInterval = getHealthRegenInterval(*myStats);
3387 	if ( healthRegenInterval == -1 && behavior == &actPlayer && myStats->type == SKELETON )
3388 	{
3389 		healthRegenInterval = HEAL_TIME * 4;
3390 	}
3391 	bool naturalHeal = false;
3392 	if ( healthRegenInterval >= 0 )
3393 	{
3394 		if ( myStats->HP < myStats->MAXHP )
3395 		{
3396 			this->char_heal++;
3397 			if ( (svFlags & SV_FLAG_HUNGER) || behavior == &actMonster || (behavior == &actPlayer && myStats->type == SKELETON) )
3398 			{
3399 				if ( this->char_heal >= healthRegenInterval )
3400 				{
3401 					this->char_heal = 0;
3402 					this->modHP(1);
3403 					naturalHeal = true;
3404 				}
3405 			}
3406 		}
3407 		else
3408 		{
3409 			this->char_heal = 0;
3410 		}
3411 	}
3412 
3413 	// random teleportation
3414 	if ( myStats->ring != NULL )
3415 	{
3416 		if ( myStats->ring->type == RING_TELEPORTATION )
3417 		{
3418 			if ( rand() % 1000 == 0 )   // .1% chance every frame
3419 			{
3420 				teleportRandom();
3421 			}
3422 		}
3423 	}
3424 
3425 	// regaining energy over time
3426 	if ( myStats->type == AUTOMATON && player >= 0 )
3427 	{
3428 		int manaRegenInterval = getManaRegenInterval(*myStats);
3429 		this->char_energize++;
3430 
3431 		if ( myStats->HUNGER <= 300 )
3432 		{
3433 			manaRegenInterval /= 6; // degrade faster
3434 		}
3435 		else if ( myStats->HUNGER > 1200 )
3436 		{
3437 			achievementObserver.playerAchievements[player].ticksSpentOverclocked++;
3438 			if ( myStats->MP / static_cast<real_t>(std::max(1, myStats->MAXMP)) <= 0.5 )
3439 			{
3440 				manaRegenInterval /= 4; // increase faster at < 50% mana
3441 			}
3442 			else
3443 			{
3444 				manaRegenInterval /= 2; // increase less faster at > 50% mana
3445 			}
3446 		}
3447 		else if ( myStats->HUNGER > 300 )
3448 		{
3449 			// normal manaRegenInterval 300-1200 hunger.
3450 		}
3451 
3452 		if ( this->char_energize >= manaRegenInterval && myStats->HUNGER <= 300 )
3453 		{
3454 			/*if ( rand() % 5 == 0 )
3455 			{
3456 				messagePlayer(0, "1 MP every %f seconds", manaRegenInterval / 50.f);
3457 			}*/
3458 			this->char_energize = 0;
3459 			if ( manaRegenInterval / 50.f < 0.5 ) // less than half a second, don't update clients as often.
3460 			{
3461 				if ( ticks % 25 == 0 )
3462 				{
3463 					this->modMP(-1, true);
3464 				}
3465 				else
3466 				{
3467 					this->modMP(-1, false);
3468 				}
3469 			}
3470 			else
3471 			{
3472 				this->modMP(-1);
3473 			}
3474 		}
3475 		else if ( this->char_energize >= manaRegenInterval )
3476 		{
3477 			/*if ( rand() % 5 == 0 )
3478 			{
3479 				messagePlayer(0, "1 MP every %f seconds", manaRegenInterval / 50.f);
3480 			}*/
3481 			this->char_energize = 0;
3482 			this->modMP(1);
3483 		}
3484 	}
3485 	else if ( this->behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 )
3486 	{
3487 		if ( (svFlags & SV_FLAG_HUNGER) && !MFLAG_DISABLEHUNGER )
3488 		{
3489 			this->char_energize++;
3490 			if ( this->char_energize > 0 && this->char_energize % 5 == 0 ) // check every 5 ticks.
3491 			{
3492 				real_t manaPercentFromHunger = myStats->HUNGER / 1000.f;
3493 				real_t expectedManaValue = std::floor(myStats->MAXMP * manaPercentFromHunger);
3494 				Sint32 Sint32expectedMana = static_cast<Sint32>(expectedManaValue);
3495 				if ( myStats->HUNGER > 0 )
3496 				{
3497 					// add extra expected mana point here.
3498 					// i.e 950 hunger is still full mana to avoid always having 1 short.
3499 					// skip 0 hunger as it will be 0 expected.
3500 					Sint32expectedMana++;
3501 				}
3502 				//messagePlayer(0, "Hunger: %d, expected MP: %d", myStats->HUNGER, Sint32expectedMana);
3503 
3504 				if ( myStats->MP < Sint32expectedMana )
3505 				{
3506 					if ( player == 0 ) // singleplayer/server only.
3507 					{
3508 						int difference = Sint32expectedMana - myStats->MP;
3509 						if ( difference > 8 )
3510 						{
3511 							this->modMP(1);
3512 							this->char_energize = 0;
3513 						}
3514 						else if ( difference > 4 )
3515 						{
3516 							if ( this->char_energize >= 10 )
3517 							{
3518 								this->modMP(1);
3519 								this->char_energize = 0;
3520 							}
3521 						}
3522 						else
3523 						{
3524 							if ( this->char_energize >= 15 )
3525 							{
3526 								this->modMP(1);
3527 								this->char_energize = 0;
3528 							}
3529 						}
3530 					}
3531 					else
3532 					{
3533 						int difference = Sint32expectedMana - myStats->MP;
3534 						if ( this->char_energize % 50 == 0 ) // only update clients every 1 second.
3535 						{
3536 							this->modMP(std::min(difference, 5)); // jump by max of 5.
3537 							this->char_energize = 0;
3538 						}
3539 					}
3540 				}
3541 				else if ( myStats->MP > Sint32expectedMana )
3542 				{
3543 					if ( this->char_energize % 50 == 0 )
3544 					{
3545 						this->modMP(-1); // update MP decrease every second.
3546 						this->char_energize = 0;
3547 					}
3548 				}
3549 				else
3550 				{
3551 					this->char_energize = 0;
3552 				}
3553 			}
3554 		}
3555 	}
3556 	else if ( myStats->MP < myStats->MAXMP )
3557 	{
3558 		int manaRegenInterval = getManaRegenInterval(*myStats);
3559 		// summons don't regen MP. we use this to refund mana to the caster.
3560 		bool doManaRegen = true;
3561 		if ( this->behavior == &actMonster && this->monsterAllySummonRank != 0 )
3562 		{
3563 			doManaRegen = false;
3564 		}
3565 
3566 		if ( doManaRegen )
3567 		{
3568 			this->char_energize++;
3569 			if ( this->char_energize >= manaRegenInterval )
3570 			{
3571 				this->char_energize = 0;
3572 				this->modMP(1);
3573 			}
3574 		}
3575 		else
3576 		{
3577 			this->char_energize = 0;
3578 		}
3579 	}
3580 	else
3581 	{
3582 		this->char_energize = 0;
3583 	}
3584 
3585 	// effects of greasy fingers
3586 	if ( myStats->EFFECTS[EFF_GREASY] == true )
3587 	{
3588 		// add some weird timing so it doesn't auto drop out of your hand immediately.
3589 		// intended to fix multiplayer duplication.
3590 		if ( ticks % 70 == 0 || ticks % 130 == 0 )
3591 		{
3592 			if ( myStats->weapon != NULL
3593 				&& (myStats->weapon->beatitude == 0
3594 					|| !shouldInvertEquipmentBeatitude(myStats) && myStats->weapon->beatitude > 0
3595 					|| shouldInvertEquipmentBeatitude(myStats) && myStats->weapon->beatitude < 0)
3596 				)
3597 			{
3598 				messagePlayer(player, language[636]);
3599 				if ( player >= 0 )
3600 				{
3601 					dropItem(myStats->weapon, player);
3602 					if ( player > 0 && multiplayer == SERVER )
3603 					{
3604 						strcpy((char*)net_packet->data, "DROP");
3605 						net_packet->data[4] = 5;
3606 						net_packet->address.host = net_clients[player - 1].host;
3607 						net_packet->address.port = net_clients[player - 1].port;
3608 						net_packet->len = 5;
3609 						sendPacketSafe(net_sock, -1, net_packet, player - 1);
3610 					}
3611 				}
3612 				else
3613 				{
3614 					dropItemMonster(myStats->weapon, this, myStats);
3615 				}
3616 				myStats->weapon = NULL;
3617 			}
3618 		}
3619 	}
3620 
3621 	// torches/lamps burn down
3622 	if ( myStats->shield != NULL )
3623 	{
3624 		if ( myStats->shield->type == TOOL_TORCH || myStats->shield->type == TOOL_LANTERN )
3625 		{
3626 			this->char_torchtime++;
3627 			if ( (this->char_torchtime >= 7200 && myStats->shield->type == TOOL_TORCH) || (this->char_torchtime >= 10260) )
3628 			{
3629 				this->char_torchtime = 0;
3630 				if ( player == clientnum )
3631 				{
3632 					if ( myStats->shield->count > 1 )
3633 					{
3634 						newItem(myStats->shield->type, myStats->shield->status, myStats->shield->beatitude, myStats->shield->count - 1, myStats->shield->appearance, myStats->shield->identified, &myStats->inventory);
3635 					}
3636 				}
3637 				myStats->shield->count = 1;
3638 				myStats->shield->status = static_cast<Status>(myStats->shield->status - 1);
3639 				if ( myStats->shield->status > BROKEN )
3640 				{
3641 					messagePlayer(player, language[637], myStats->shield->getName());
3642 				}
3643 				else
3644 				{
3645 					messagePlayer(player, language[638], myStats->shield->getName());
3646 				}
3647 				if ( multiplayer == SERVER && player > 0 )
3648 				{
3649 					strcpy((char*)net_packet->data, "ARMR");
3650 					net_packet->data[4] = 4;
3651 					net_packet->data[5] = myStats->shield->status;
3652 					net_packet->address.host = net_clients[player - 1].host;
3653 					net_packet->address.port = net_clients[player - 1].port;
3654 					net_packet->len = 6;
3655 					sendPacketSafe(net_sock, -1, net_packet, player - 1);
3656 				}
3657 			}
3658 		}
3659 	}
3660 
3661 	// effects of being poisoned
3662 	if ( myStats->EFFECTS[EFF_POISONED] )
3663 	{
3664 		if ( myStats->type == INSECTOID )
3665 		{
3666 			messagePlayer(player, language[640]);
3667 			myStats->EFFECTS_TIMERS[EFF_POISONED] = 0;
3668 			myStats->EFFECTS[EFF_POISONED] = false;
3669 			serverUpdateEffects(player);
3670 			this->char_poison = 0;
3671 		}
3672 		else if ( myStats->amulet && myStats->amulet->type == AMULET_POISONRESISTANCE )
3673 		{
3674 			messagePlayer(player, language[639]);
3675 			messagePlayer(player, language[640]);
3676 			myStats->EFFECTS_TIMERS[EFF_POISONED] = 0;
3677 			myStats->EFFECTS[EFF_POISONED] = false;
3678 			serverUpdateEffects(player);
3679 			this->char_poison = 0;
3680 		}
3681 
3682 		this->char_poison++;
3683 		if ( this->char_poison > 150 )   // three seconds
3684 		{
3685 			this->char_poison = 0;
3686 			int poisonhurt = std::max(3, (myStats->MAXHP / 20));
3687 			if ( myStats->type == LICH_ICE
3688 				|| myStats->type == LICH_FIRE
3689 				|| myStats->type == LICH
3690 				|| myStats->type == DEVIL )
3691 			{
3692 				poisonhurt = std::min(poisonhurt, 15); // prevent doing 50+ dmg
3693 			}
3694 			if ( poisonhurt > 3 )
3695 			{
3696 				poisonhurt -= rand() % (std::max(1, poisonhurt / 4));
3697 			}
3698 			this->modHP(-poisonhurt);
3699 			for ( int tmp = 0; tmp < 3; ++tmp )
3700 			{
3701 				Entity* gib = spawnGib(this, 211);
3702 				serverSpawnGibForClient(gib);
3703 			}
3704 			Entity* killer = uidToEntity(myStats->poisonKiller);
3705 			if ( myStats->HP <= 0 )
3706 			{
3707 				if ( killer )
3708 				{
3709 					killer->awardXP(this, true, true);
3710 				}
3711 				else
3712 				{
3713 					if ( achievementObserver.checkUidIsFromPlayer(myStats->poisonKiller) >= 0 )
3714 					{
3715 						steamAchievementClient(achievementObserver.checkUidIsFromPlayer(myStats->poisonKiller), "BARONY_ACH_TAKING_WITH");
3716 					}
3717 				}
3718 			}
3719 			if ( killer && killer->behavior == &actPlayer )
3720 			{
3721 				bool lowPriority = true;
3722 				// update enemy bar for attacker
3723 				if ( !strcmp(myStats->name, "") )
3724 				{
3725 					if ( myStats->type < KOBOLD ) //Original monster count
3726 					{
3727 						updateEnemyBar(killer, this, language[90 + myStats->type], myStats->HP, myStats->MAXHP, lowPriority);
3728 					}
3729 					else if ( myStats->type >= KOBOLD ) //New monsters
3730 					{
3731 						updateEnemyBar(killer, this, language[2000 + (myStats->type - KOBOLD)], myStats->HP, myStats->MAXHP, lowPriority);
3732 					}
3733 				}
3734 				else
3735 				{
3736 					updateEnemyBar(killer, this, myStats->name, myStats->HP, myStats->MAXHP, lowPriority);
3737 				}
3738 			}
3739 			this->setObituary(language[1531]);
3740 			playSoundEntity(this, 28, 64);
3741 			if ( player == clientnum )
3742 			{
3743 				camera_shakex += .1;
3744 				camera_shakey += 10;
3745 			}
3746 			else if ( player > 0 && multiplayer == SERVER )
3747 			{
3748 				strcpy((char*)net_packet->data, "SHAK");
3749 				net_packet->data[4] = 10; // turns into .1
3750 				net_packet->data[5] = 10;
3751 				net_packet->address.host = net_clients[player - 1].host;
3752 				net_packet->address.port = net_clients[player - 1].port;
3753 				net_packet->len = 6;
3754 				sendPacketSafe(net_sock, -1, net_packet, player - 1);
3755 			}
3756 			if ( rand() % 5 == 0 && getCON() >= -3 )
3757 			{
3758 				messagePlayer(player, language[641]);
3759 				myStats->EFFECTS_TIMERS[EFF_POISONED] = 0;
3760 				myStats->EFFECTS[EFF_POISONED] = false;
3761 				serverUpdateEffects(player);
3762 			}
3763 		}
3764 	}
3765 	else
3766 	{
3767 		this->char_poison = 0;
3768 		myStats->poisonKiller = 0;
3769 	}
3770 
3771 	if ( !myStats->EFFECTS[EFF_WEBBED] )
3772 	{
3773 		if ( creatureWebbedSlowCount > 0 )
3774 		{
3775 			creatureWebbedSlowCount = 0; // reset counter.
3776 			if ( behavior == &actPlayer )
3777 			{
3778 				serverUpdateEntitySkill(this, 52); // update player.
3779 			}
3780 		}
3781 	}
3782 
3783 	// bleeding
3784 	if ( myStats->EFFECTS[EFF_BLEEDING] )
3785 	{
3786 		if ( ticks % 120 == 0 )
3787 		{
3788 			if ( myStats->HP > 5 + (std::max(0, getCON())) ) // CON increases when bleeding stops.
3789 			{
3790 				int bleedhurt = 1 + myStats->MAXHP / 30;
3791 				if ( bleedhurt > 1 )
3792 				{
3793 					bleedhurt -= rand() % (std::max(1, bleedhurt / 2));
3794 				}
3795 				if ( getCON() > 0 )
3796 				{
3797 					bleedhurt -= (getCON() / 5);
3798 				}
3799 				if ( myStats->type == LICH_ICE
3800 					|| myStats->type == LICH_FIRE
3801 					|| myStats->type == LICH
3802 					|| myStats->type == DEVIL )
3803 				{
3804 					bleedhurt = std::min(bleedhurt, 15); // prevent doing 50+ dmg
3805 				}
3806 				bleedhurt = std::max(1, bleedhurt);
3807 				this->modHP(-bleedhurt);
3808 				this->setObituary(language[1532]);
3809 				Entity* gib = spawnGib(this);
3810 				serverSpawnGibForClient(gib);
3811 				if ( player == clientnum )
3812 				{
3813 					camera_shakex -= .03;
3814 					camera_shakey += 3;
3815 				}
3816 				else if ( player > 0 && multiplayer == SERVER )
3817 				{
3818 					strcpy((char*)net_packet->data, "SHAK");
3819 					net_packet->data[4] = -3; // turns into -.03
3820 					net_packet->data[5] = 3;
3821 					net_packet->address.host = net_clients[player - 1].host;
3822 					net_packet->address.port = net_clients[player - 1].port;
3823 					net_packet->len = 6;
3824 					sendPacketSafe(net_sock, -1, net_packet, player - 1);
3825 				}
3826 				messagePlayer(player, language[642]);
3827 				if ( spawn_blood )
3828 				{
3829 					Entity* entity = nullptr;
3830 					if ( gibtype[myStats->type] == 1 )
3831 					{
3832 						entity = newEntity(203, 1, map.entities, nullptr); //Blood entity.
3833 					}
3834 					else if ( gibtype[myStats->type] == 2 )
3835 					{
3836 						entity = newEntity(213, 1, map.entities, nullptr); //Blood entity.
3837 					}
3838 					else if ( gibtype[myStats->type] == 4 )
3839 					{
3840 						entity = newEntity(682, 1, map.entities, nullptr); //Blood entity.
3841 					}
3842 					if ( entity != NULL )
3843 					{
3844 						entity->x = this->x;
3845 						entity->y = this->y;
3846 						entity->z = 8.0 + (rand() % 20) / 100.0;
3847 						entity->parent = this->uid;
3848 						entity->sizex = 2;
3849 						entity->sizey = 2;
3850 						entity->yaw = (rand() % 360) * PI / 180.0;
3851 						entity->flags[UPDATENEEDED] = true;
3852 						entity->flags[PASSABLE] = true;
3853 					}
3854 				}
3855 
3856 				Entity* killer = uidToEntity(static_cast<Uint32>(myStats->bleedInflictedBy));
3857 				if ( killer && killer->behavior == &actPlayer )
3858 				{
3859 					bool lowPriority = true;
3860 					// update enemy bar for attacker
3861 					if ( !strcmp(myStats->name, "") )
3862 					{
3863 						if ( myStats->type < KOBOLD ) //Original monster count
3864 						{
3865 							updateEnemyBar(killer, this, language[90 + myStats->type], myStats->HP, myStats->MAXHP, lowPriority);
3866 						}
3867 						else if ( myStats->type >= KOBOLD ) //New monsters
3868 						{
3869 							updateEnemyBar(killer, this, language[2000 + (myStats->type - KOBOLD)], myStats->HP, myStats->MAXHP, lowPriority);
3870 						}
3871 					}
3872 					else
3873 					{
3874 						updateEnemyBar(killer, this, myStats->name, myStats->HP, myStats->MAXHP, lowPriority);
3875 					}
3876 				}
3877 			}
3878 			else
3879 			{
3880 				messagePlayer(player, language[643]);
3881 				myStats->EFFECTS[EFF_BLEEDING] = false;
3882 				myStats->EFFECTS_TIMERS[EFF_BLEEDING] = 0;
3883 				serverUpdateEffects(player);
3884 			}
3885 		}
3886 	}
3887 	else
3888 	{
3889 		myStats->bleedInflictedBy = 0;
3890 	}
3891 
3892 	// webbed
3893 	if ( myStats->EFFECTS[EFF_WEBBED] )
3894 	{
3895 		if ( ticks % 25 == 0 )
3896 		{
3897 			Entity* gib = spawnGib(this, 863);
3898 			serverSpawnGibForClient(gib);
3899 		}
3900 		if ( ticks % 40 == 0 )
3901 		{
3902 			Entity* entity = newEntity(862, 1, map.entities, nullptr); //Web pool entity.
3903 			if ( entity != NULL )
3904 			{
3905 				entity->x = this->x;
3906 				entity->y = this->y;
3907 				entity->z = 8.0 + (rand() % 20) / 100.0;
3908 				entity->parent = this->uid;
3909 				entity->sizex = 2;
3910 				entity->sizey = 2;
3911 				entity->yaw = (rand() % 360) * PI / 180.0;
3912 				real_t scale = 0.75 + 0.25 * (rand() % 100) / 100.f;
3913 				entity->scalex = scale;
3914 				entity->scaley = scale;
3915 				entity->flags[UPDATENEEDED] = true;
3916 				entity->flags[PASSABLE] = true;
3917 			}
3918 		}
3919 	}
3920 
3921 	if ( player >= 0 && (myStats->EFFECTS[EFF_LEVITATING] || myStats->EFFECTS[EFF_FLUTTER]) && MFLAG_DISABLELEVITATION)
3922 	{
3923 		Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
3924 		messagePlayerColor(player, color, language[2382]); // disabled levitation.
3925 		this->setEffect(EFF_LEVITATING, false, 0, true);
3926 		this->setEffect(EFF_FLUTTER, false, 0, true);
3927 	}
3928 
3929 	if ( myStats->EFFECTS[EFF_MAGICREFLECT] )
3930 	{
3931 		spawnAmbientParticles(80, 579, 10 + rand() % 40, 1.0, false);
3932 	}
3933 
3934 	if (myStats->EFFECTS[EFF_VAMPIRICAURA])
3935 	{
3936 		spawnAmbientParticles(40, 600, 20 + rand() % 30, 0.5, true);
3937 	}
3938 
3939 	if ( myStats->EFFECTS[EFF_FEAR] )
3940 	{
3941 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
3942 		{
3943 			spawnAmbientParticles(1, 864, 20 + rand() % 10, 0.5, true);
3944 		}
3945 	}
3946 
3947 	if ( myStats->EFFECTS[EFF_TROLLS_BLOOD] )
3948 	{
3949 		spawnAmbientParticles(80, 169, 20 + rand() % 10, 0.5, true);
3950 	}
3951 
3952 	if ( myStats->EFFECTS[EFF_PACIFY] )
3953 	{
3954 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
3955 		{
3956 			spawnAmbientParticles(1, 685, 20 + rand() % 10, 0.5, true);
3957 		}
3958 	}
3959 	else if ( myStats->monsterIsCharmed == 1 )
3960 	{
3961 		if ( ticks % 80 == 0 || ticks % 100 == 0 )
3962 		{
3963 			spawnAmbientParticles(1, 685, 20 + rand() % 10, 0.5, true);
3964 		}
3965 	}
3966 
3967 	if ( myStats->EFFECTS[EFF_SHADOW_TAGGED] )
3968 	{
3969 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
3970 		{
3971 			spawnAmbientParticles(1, 871, 20 + rand() % 10, 0.5, true);
3972 		}
3973 	}
3974 
3975 	if ( myStats->EFFECTS[EFF_POLYMORPH] )
3976 	{
3977 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
3978 		{
3979 			spawnAmbientParticles(1, 593, 20 + rand() % 10, 0.5, true);
3980 		}
3981 	}
3982 
3983 	if ( myStats->EFFECTS[EFF_INVISIBLE] && myStats->type == SHADOW )
3984 	{
3985 		spawnAmbientParticles(20, 175, 20 + rand() % 30, 0.5, true);
3986 	}
3987 
3988 	// Process Burning Status Effect
3989 	if ( this->flags[BURNING] )
3990 	{
3991 		this->char_fire--; // Decrease the fire counter
3992 
3993 		// Check to see if time has run out
3994 		if ( this->char_fire <= 0 )
3995 		{
3996 			this->flags[BURNING] = false;
3997 			messagePlayer(player, language[647]); // "The flames go out."
3998 			serverUpdateEntityFlag(this, BURNING);
3999 		}
4000 		else
4001 		{
4002 			// If 0.6 seconds have passed (30 ticks), process the Burning Status Effect
4003 			if ( (this->char_fire % TICKS_TO_PROCESS_FIRE) == 0 )
4004 			{
4005 				// Buddha should not die to fire
4006 				if ( buddhamode )
4007 				{
4008 					Sint32 fireDamage = (-2 - rand() % 3); // Deal between -2 to -5 damage
4009 
4010 					// Fire damage is negative, so it needs to be added
4011 					if ( myStats->HP + fireDamage > 0 )
4012 					{
4013 						this->modHP(fireDamage);
4014 					}
4015 					else
4016 					{
4017 						this->setHP(1); // Instead of killing the Buddha Player, set their HP to 1
4018 					}
4019 				}
4020 				else
4021 				{
4022 					// Player is not Buddha, process fire damage normally
4023 					this->modHP(-2 - rand() % 3); // Deal between -2 to -5 damage
4024 
4025 					Entity* killer = uidToEntity(static_cast<Uint32>(myStats->burningInflictedBy));
4026 					// If the Entity died, handle experience
4027 					if ( myStats->HP <= 0 )
4028 					{
4029 						this->setObituary(language[1533]); // "burns to a crisp."
4030 
4031 						if ( killer != nullptr )
4032 						{
4033 							killer->awardXP(this, true, true);
4034 						}
4035 						else
4036 						{
4037 							if ( achievementObserver.checkUidIsFromPlayer(static_cast<Uint32>(myStats->burningInflictedBy)) >= 0 )
4038 							{
4039 								steamAchievementClient(achievementObserver.checkUidIsFromPlayer(myStats->poisonKiller), "BARONY_ACH_TAKING_WITH");
4040 							}
4041 						}
4042 					}
4043 
4044 					if ( killer && killer->behavior == &actPlayer )
4045 					{
4046 						bool lowPriority = true;
4047 						// update enemy bar for attacker
4048 						if ( !strcmp(myStats->name, "") )
4049 						{
4050 							if ( myStats->type < KOBOLD ) //Original monster count
4051 							{
4052 								updateEnemyBar(killer, this, language[90 + myStats->type], myStats->HP, myStats->MAXHP, lowPriority);
4053 							}
4054 							else if ( myStats->type >= KOBOLD ) //New monsters
4055 							{
4056 								updateEnemyBar(killer, this, language[2000 + (myStats->type - KOBOLD)], myStats->HP, myStats->MAXHP, lowPriority);
4057 							}
4058 						}
4059 						else
4060 						{
4061 							updateEnemyBar(killer, this, myStats->name, myStats->HP, myStats->MAXHP, lowPriority);
4062 						}
4063 					}
4064 				}
4065 
4066 				// Give the Player feedback on being hurt
4067 				messagePlayer(player, language[644]); // "It burns! It burns!"
4068 				playSoundEntity(this, 28, 64); // "Damage.ogg"
4069 
4070 				// Shake the Camera
4071 				if ( player == clientnum )
4072 				{
4073 					camera_shakey += 5;
4074 				}
4075 				else if ( player > 0 && multiplayer == SERVER )
4076 				{
4077 					strcpy((char*)net_packet->data, "SHAK");
4078 					net_packet->data[4] = 0; // turns into 0
4079 					net_packet->data[5] = 5;
4080 					net_packet->address.host = net_clients[player - 1].host;
4081 					net_packet->address.port = net_clients[player - 1].port;
4082 					net_packet->len = 6;
4083 					sendPacketSafe(net_sock, -1, net_packet, player - 1);
4084 				}
4085 
4086 				// If the Entity has a Cloak, process dealing damage to the Entity's Cloak
4087 				if ( myStats->cloak != nullptr )
4088 				{
4089 					// 1 in 10 chance of dealing damage to Entity's cloak
4090 					if ( rand() % 10 == 0 && myStats->cloak->type != ARTIFACT_CLOAK && myStats->cloak->type != CLOAK_BACKPACK )
4091 					{
4092 						if ( player == clientnum )
4093 						{
4094 							if ( myStats->cloak->count > 1 )
4095 							{
4096 								newItem(myStats->cloak->type, myStats->cloak->status, myStats->cloak->beatitude, myStats->cloak->count - 1, myStats->cloak->appearance, myStats->cloak->identified, &myStats->inventory);
4097 							}
4098 						}
4099 						myStats->cloak->count = 1;
4100 						myStats->cloak->status = static_cast<Status>(myStats->cloak->status - 1);
4101 						if ( myStats->cloak->status != BROKEN )
4102 						{
4103 							messagePlayer(player, language[645], myStats->cloak->getName()); // "Your %s smoulders!"
4104 						}
4105 						else
4106 						{
4107 							messagePlayer(player, language[646], myStats->cloak->getName()); // "Your %s burns to ash!"
4108 						}
4109 						if ( player > 0 && multiplayer == SERVER )
4110 						{
4111 							strcpy((char*)net_packet->data, "ARMR");
4112 							net_packet->data[4] = 6;
4113 							net_packet->data[5] = myStats->cloak->status;
4114 							net_packet->address.host = net_clients[player - 1].host;
4115 							net_packet->address.port = net_clients[player - 1].port;
4116 							net_packet->len = 6;
4117 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
4118 						}
4119 					}
4120 				}
4121 
4122 				// Check to see if the fire is put out
4123 				if ( (rand() % this->chanceToPutOutFire) == 0 )
4124 				{
4125 					this->flags[BURNING] = false;
4126 					messagePlayer(player, language[647]); // "The flames go out."
4127 					serverUpdateEntityFlag(this, BURNING);
4128 				}
4129 			}
4130 		}
4131 	}
4132 	else
4133 	{
4134 		this->char_fire = 0; // If not on fire, then reset fire counter TODOR: This seems unecessary, but is what poison does, this is happening every tick
4135 		myStats->burningInflictedBy = 0;
4136 	}
4137 
4138 	if ( player >= 0 && (stats[player]->type == SKELETON || (stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0)) )
4139 	{
4140 		// life saving
4141 		if ( myStats->HP <= 0 )
4142 		{
4143 			int spellCost = getCostOfSpell(&spell_summon, this);
4144 			int numSummonedAllies = 0;
4145 			int firstManaToRefund = 0;
4146 			int secondManaToRefund = 0;
4147 			for ( node_t* node = myStats->FOLLOWERS.first; node != nullptr; node = node->next )
4148 			{
4149 				Uint32* c = (Uint32*)node->element;
4150 				Entity* mySummon = nullptr;
4151 				if ( c )
4152 				{
4153 					mySummon = uidToEntity(*c);
4154 				}
4155 				if ( mySummon && mySummon->monsterAllySummonRank != 0 )
4156 				{
4157 					Stat* mySummonStats = mySummon->getStats();
4158 					if ( mySummonStats )
4159 					{
4160 						if ( numSummonedAllies == 0 )
4161 						{
4162 							mySummon->setMP(mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
4163 							firstManaToRefund += std::min(spellCost, static_cast<int>((mySummonStats->MP / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
4164 							mySummon->setHP(0); // sacrifice!
4165 							++numSummonedAllies;
4166 						}
4167 						else if ( numSummonedAllies == 1 )
4168 						{
4169 							mySummon->setMP(mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
4170 							secondManaToRefund += std::min(spellCost, static_cast<int>((mySummonStats->MP / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
4171 							mySummon->setHP(0); // for glorious leader!
4172 							++numSummonedAllies;
4173 							break;
4174 						}
4175 					}
4176 				}
4177 			}
4178 
4179 			if ( numSummonedAllies == 2 )
4180 			{
4181 				firstManaToRefund /= 2;
4182 				secondManaToRefund /= 2;
4183 			}
4184 			bool revivedWithFriendship = false;
4185 			if ( myStats->MP < 75 && numSummonedAllies > 0 )
4186 			{
4187 				revivedWithFriendship = true;
4188 			}
4189 
4190 			int manaTotal = myStats->MP + firstManaToRefund + secondManaToRefund;
4191 
4192 			if ( manaTotal >= 75 )
4193 			{
4194 				messagePlayer(player, language[651]);
4195 				if ( revivedWithFriendship )
4196 				{
4197 					messagePlayer(player, language[3198]);
4198 				}
4199 				else
4200 				{
4201 					messagePlayer(player, language[3180]);
4202 				}
4203 				messagePlayer(player, language[654]);
4204 
4205 				steamAchievementClient(player, "BARONY_ACH_SECOND_CHANCE");
4206 
4207 				playSoundEntity(this, 167, 128);
4208 				createParticleDropRising(this, 174, 1.0);
4209 				serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 174);
4210 				// convert MP to HP
4211 				manaTotal = myStats->MP;
4212 				if ( safeConsumeMP(myStats->MP) )
4213 				{
4214 					this->setHP(std::min(manaTotal, myStats->MAXHP));
4215 					if ( player > 0 && multiplayer == SERVER )
4216 					{
4217 						strcpy((char*)net_packet->data, "ATTR");
4218 						net_packet->data[4] = clientnum;
4219 						net_packet->data[5] = (Sint8)myStats->STR;
4220 						net_packet->data[6] = (Sint8)myStats->DEX;
4221 						net_packet->data[7] = (Sint8)myStats->CON;
4222 						net_packet->data[8] = (Sint8)myStats->INT;
4223 						net_packet->data[9] = (Sint8)myStats->PER;
4224 						net_packet->data[10] = (Sint8)myStats->CHR;
4225 						net_packet->data[11] = (Sint8)myStats->EXP;
4226 						net_packet->data[12] = (Sint8)myStats->LVL;
4227 						SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]);
4228 						SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]);
4229 						SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]);
4230 						SDLNet_Write16((Sint16)myStats->MAXMP, &net_packet->data[19]);
4231 						net_packet->address.host = net_clients[player - 1].host;
4232 						net_packet->address.port = net_clients[player - 1].port;
4233 						net_packet->len = 21;
4234 						sendPacketSafe(net_sock, -1, net_packet, player - 1);
4235 					}
4236 				}
4237 				for ( c = 0; c < NUMEFFECTS; c++ )
4238 				{
4239 					if ( !(c == EFF_VAMPIRICAURA && myStats->EFFECTS_TIMERS[c] == -2)
4240 						&& c != EFF_WITHDRAWAL && c != EFF_SHAPESHIFT )
4241 					{
4242 						myStats->EFFECTS[c] = false;
4243 						myStats->EFFECTS_TIMERS[c] = 0;
4244 					}
4245 				}
4246 
4247 				myStats->EFFECTS[EFF_LEVITATING] = true;
4248 				myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 5 * TICKS_PER_SECOND;
4249 
4250 				this->flags[BURNING] = false;
4251 				serverUpdateEntityFlag(this, BURNING);
4252 				serverUpdateEffects(player);
4253 			}
4254 			else
4255 			{
4256 				messagePlayer(player, language[3181]);
4257 			}
4258 		}
4259 	}
4260 
4261 	// amulet effects
4262 	if ( myStats->amulet != NULL )
4263 	{
4264 		// strangulation
4265 		if ( myStats->amulet->type == AMULET_STRANGULATION )
4266 		{
4267 			if ( ticks % 60 == 0 )
4268 			{
4269 				if ( rand() % 25 )
4270 				{
4271 					messagePlayer(player, language[648]);
4272 					this->modHP(-(2 + rand() % 3));
4273 					playSoundEntity(this, 28, 64); // "Damage.ogg"
4274 					if ( player >= 0 )
4275 					{
4276 						if ( myStats->type == SUCCUBUS || myStats->type == INCUBUS )
4277 						{
4278 							if ( rand() % 3 > 0 && myStats->MP < myStats->MAXMP )
4279 							{
4280 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
4281 								messagePlayerColor(player, color, language[3358]);
4282 								int amount = 2 + rand() % 2;
4283 								int oldMP = myStats->MP;
4284 								this->modMP(amount);
4285 								if ( stats[player]->appearance == 0 )
4286 								{
4287 									if ( stats[player]->playerRace == RACE_INCUBUS || stats[player]->playerRace == RACE_SUCCUBUS )
4288 									{
4289 										if ( oldMP < myStats->MP )
4290 										{
4291 											steamStatisticUpdateClient(player, STEAM_STAT_SERIAL_THRILLA, STEAM_STAT_INT, myStats->MP - oldMP);
4292 										}
4293 									}
4294 								}
4295 							}
4296 						}
4297 					}
4298 					this->setObituary(language[1534]);
4299 					if ( myStats->HP <= 0 )
4300 					{
4301 						if ( player <= 0 )
4302 						{
4303 							Item* item = myStats->amulet;
4304 							if ( item->count > 1 )
4305 							{
4306 								newItem(item->type, item->status, item->beatitude, item->count - 1, item->appearance, item->identified, &myStats->inventory);
4307 							}
4308 						}
4309 						myStats->amulet->count = 1;
4310 						myStats->amulet->status = BROKEN;
4311 						playSoundEntity(this, 76, 64);
4312 						if ( player > 0 && multiplayer == SERVER )
4313 						{
4314 							strcpy((char*)net_packet->data, "ARMR");
4315 							net_packet->data[4] = 7;
4316 							net_packet->data[5] = myStats->amulet->status;
4317 							net_packet->address.host = net_clients[player - 1].host;
4318 							net_packet->address.port = net_clients[player - 1].port;
4319 							net_packet->len = 6;
4320 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
4321 						}
4322 					}
4323 					if ( player == clientnum )
4324 					{
4325 						camera_shakey += 8;
4326 					}
4327 					else if ( player > 0 && multiplayer == SERVER )
4328 					{
4329 						strcpy((char*)net_packet->data, "SHAK");
4330 						net_packet->data[4] = 0; // turns into 0
4331 						net_packet->data[5] = 8;
4332 						net_packet->address.host = net_clients[player - 1].host;
4333 						net_packet->address.port = net_clients[player - 1].port;
4334 						net_packet->len = 6;
4335 						sendPacketSafe(net_sock, -1, net_packet, player - 1);
4336 					}
4337 				}
4338 				else
4339 				{
4340 					messagePlayer(player, language[649]);
4341 					messagePlayer(player, language[650]);
4342 					if ( player <= 0 )
4343 					{
4344 						Item* item = myStats->amulet;
4345 						if ( item->count > 1 )
4346 						{
4347 							newItem(item->type, item->status, item->beatitude, item->count - 1, item->appearance, item->identified, &myStats->inventory);
4348 						}
4349 					}
4350 					myStats->amulet->count = 1;
4351 					myStats->amulet->status = BROKEN;
4352 					playSoundEntity(this, 76, 64);
4353 					if ( player > 0 && multiplayer == SERVER )
4354 					{
4355 						strcpy((char*)net_packet->data, "ARMR");
4356 						net_packet->data[4] = 7;
4357 						net_packet->data[5] = myStats->amulet->status;
4358 						net_packet->address.host = net_clients[player - 1].host;
4359 						net_packet->address.port = net_clients[player - 1].port;
4360 						net_packet->len = 6;
4361 						sendPacketSafe(net_sock, -1, net_packet, player - 1);
4362 					}
4363 				}
4364 			}
4365 		}
4366 		// life saving
4367 		if ( myStats->amulet->type == AMULET_LIFESAVING )   //Fixed! (saves against boulder traps.)
4368 		{
4369 			if ( myStats->HP <= 0 )
4370 			{
4371 				if ( myStats->HUNGER > 0 )
4372 				{
4373 					messagePlayer(player, language[651]);
4374 				}
4375 				if ( !this->isBlind() )
4376 				{
4377 					messagePlayer(player, language[652]);
4378 				}
4379 				else
4380 				{
4381 					messagePlayer(player, language[653]);
4382 				}
4383 				if ( myStats->amulet->beatitude >= 0 || shouldInvertEquipmentBeatitude(myStats) )
4384 				{
4385 					messagePlayer(player, language[654]);
4386 					messagePlayer(player, language[655]);
4387 
4388 					playSoundEntity(this, 167, 128);
4389 					createParticleDropRising(this, 174, 1.0);
4390 					serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 174);
4391 
4392 					steamAchievementClient(player, "BARONY_ACH_BORN_AGAIN");
4393 					myStats->HUNGER = 800;
4394 					if ( myStats->MAXHP < 10 )
4395 					{
4396 						myStats->MAXHP = 10;
4397 						if ( player > 0 && multiplayer == SERVER )
4398 						{
4399 							strcpy((char*)net_packet->data, "ATTR");
4400 							net_packet->data[4] = clientnum;
4401 							net_packet->data[5] = (Sint8)myStats->STR;
4402 							net_packet->data[6] = (Sint8)myStats->DEX;
4403 							net_packet->data[7] = (Sint8)myStats->CON;
4404 							net_packet->data[8] = (Sint8)myStats->INT;
4405 							net_packet->data[9] = (Sint8)myStats->PER;
4406 							net_packet->data[10] = (Sint8)myStats->CHR;
4407 							net_packet->data[11] = (Sint8)myStats->EXP;
4408 							net_packet->data[12] = (Sint8)myStats->LVL;
4409 							SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]);
4410 							SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]);
4411 							SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]);
4412 							SDLNet_Write16((Sint16)myStats->MAXMP, &net_packet->data[19]);
4413 							net_packet->address.host = net_clients[player - 1].host;
4414 							net_packet->address.port = net_clients[player - 1].port;
4415 							net_packet->len = 21;
4416 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
4417 						}
4418 					}
4419 					this->setHP(std::max(myStats->MAXHP, 10));
4420 					for ( c = 0; c < NUMEFFECTS; c++ )
4421 					{
4422 						if ( !(c == EFF_VAMPIRICAURA && myStats->EFFECTS_TIMERS[c] == -2)
4423 							&& c != EFF_WITHDRAWAL && c != EFF_SHAPESHIFT )
4424 						{
4425 							myStats->EFFECTS[c] = false;
4426 							myStats->EFFECTS_TIMERS[c] = 0;
4427 						}
4428 					}
4429 
4430 					// check if hovering over a pit
4431 					//if ( !isLevitating(myStats) )
4432 					//{
4433 					//	int my_x, my_y, u, v;
4434 					//	my_x = std::min(std::max<unsigned int>(1, this->x / 16), map.width - 2);
4435 					//	my_y = std::min(std::max<unsigned int>(1, this->y / 16), map.height - 2);
4436 					//	for ( u = my_x - 1; u <= my_x + 1; u++ )
4437 					//	{
4438 					//		for ( v = my_y - 1; v <= my_y + 1; v++ )
4439 					//		{
4440 					//			if ( entityInsideTile(this, u, v, 0) )   // no floor
4441 					//			{
4442 					//				break;
4443 					//			}
4444 					//		}
4445 					//	}
4446 					//}
4447 					myStats->EFFECTS[EFF_LEVITATING] = true;
4448 					myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 5 * TICKS_PER_SECOND;
4449 
4450 					this->flags[BURNING] = false;
4451 					serverUpdateEntityFlag(this, BURNING);
4452 					serverUpdateEffects(player);
4453 				}
4454 				else
4455 				{
4456 					messagePlayer(player, language[656]);
4457 					messagePlayer(player, language[657]);
4458 				}
4459 				myStats->amulet->status = BROKEN;
4460 				playSoundEntity(this, 76, 64);
4461 				if ( player > 0 && multiplayer == SERVER )
4462 				{
4463 					strcpy((char*)net_packet->data, "ARMR");
4464 					net_packet->data[4] = 7;
4465 					net_packet->data[5] = myStats->amulet->status;
4466 					net_packet->address.host = net_clients[player - 1].host;
4467 					net_packet->address.port = net_clients[player - 1].port;
4468 					net_packet->len = 6;
4469 					sendPacketSafe(net_sock, -1, net_packet, player - 1);
4470 				}
4471 				myStats->amulet = NULL;
4472 			}
4473 		}
4474 	}
4475 
4476 	if ( player >= 0
4477 		&& myStats->mask != nullptr
4478 		&& myStats->mask->type == TOOL_BLINDFOLD_TELEPATHY
4479 		&& (ticks % 45 == 0 || !myStats->EFFECTS[EFF_TELEPATH]) )
4480 	{
4481 		setEffect(EFF_TELEPATH, true, 60, true);
4482 	}
4483 
4484 	if ( player >= 0
4485 		&& myStats->mask != nullptr
4486 		&& (myStats->mask->type == TOOL_BLINDFOLD || myStats->mask->type == TOOL_BLINDFOLD_FOCUS || myStats->mask->type == TOOL_BLINDFOLD_TELEPATHY )
4487 		&& (ticks % 45 == 0 || !myStats->EFFECTS[EFF_BLIND]) )
4488 	{
4489 		setEffect(EFF_BLIND, true, 60, true);
4490 		if ( myStats->mask->type == TOOL_BLINDFOLD_FOCUS )
4491 		{
4492 			bool cured = false;
4493 			if ( myStats->EFFECTS_TIMERS[EFF_ASLEEP] > 0 )
4494 			{
4495 				cured = true;
4496 				myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 1; // tick over to 0 and dissipate on the next check, and play the appropriate message.
4497 			}
4498 			if ( myStats->EFFECTS_TIMERS[EFF_PARALYZED] > 0 )
4499 			{
4500 				cured = true;
4501 				myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 1; // tick over to 0 and dissipate on the next check, and play the appropriate message.
4502 			}
4503 			if ( cured )
4504 			{
4505 				playSoundEntity(this, 168, 128);
4506 			}
4507 		}
4508 	}
4509 
4510 	// unparalyze certain boss characters
4511 	if ( myStats->EFFECTS[EFF_PARALYZED] && ((myStats->type >= LICH && myStats->type < KOBOLD)
4512 		|| myStats->type == COCKATRICE || myStats->type == LICH_FIRE || myStats->type == LICH_ICE) )
4513 	{
4514 		myStats->EFFECTS[EFF_PARALYZED] = false;
4515 		myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 0;
4516 	}
4517 
4518 	// wake up
4519 	if ( myStats->EFFECTS[EFF_ASLEEP] && (myStats->OLDHP > myStats->HP || (myStats->type >= LICH && myStats->type < KOBOLD)
4520 		|| myStats->type == COCKATRICE || myStats->type == LICH_FIRE || myStats->type == LICH_ICE) )
4521 	{
4522 		messagePlayer(player, language[658]);
4523 		if ( monsterAllyGetPlayerLeader() && monsterAllySpecial == ALLY_SPECIAL_CMD_REST )
4524 		{
4525 			// allies resting. if poison/bleed damage here, then ignore it (startingHPInHandleEffects will equal current HP)
4526 			if ( !naturalHeal && startingHPInHandleEffects == myStats->HP )
4527 			{
4528 				myStats->EFFECTS[EFF_ASLEEP] = false; // wake up
4529 				myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 0;
4530 				myStats->EFFECTS[EFF_HP_REGEN] = false; // stop regen
4531 				myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = 0;
4532 				monsterAllySpecial = ALLY_SPECIAL_CMD_NONE;
4533 			}
4534 		}
4535 		else
4536 		{
4537 			myStats->EFFECTS[EFF_ASLEEP] = false;
4538 			myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 0;
4539 		}
4540 		serverUpdateEffects(player);
4541 	}
4542 	else if ( myStats->EFFECTS[EFF_ASLEEP] && monsterAllyGetPlayerLeader() && monsterAllySpecial == ALLY_SPECIAL_CMD_REST )
4543 	{
4544 		if ( myStats->HP == myStats->MAXHP )
4545 		{
4546 			myStats->EFFECTS[EFF_ASLEEP] = false; // wake up
4547 			myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 0;
4548 			myStats->EFFECTS[EFF_HP_REGEN] = false; // stop regen
4549 			myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = 0;
4550 			monsterAllySpecial = ALLY_SPECIAL_CMD_NONE;
4551 			messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF, *myStats, language[3881], language[3881], MSG_GENERIC);
4552 		}
4553 	}
4554 	myStats->OLDHP = myStats->HP;
4555 }
4556 
4557 /*-------------------------------------------------------------------------------
4558 
4559 Entity::getAttack
4560 
4561 returns the attack power of an entity based on strength, weapon, and a
4562 base number
4563 
4564 -------------------------------------------------------------------------------*/
4565 
getAttack()4566 Sint32 Entity::getAttack()
4567 {
4568 	Stat* entitystats;
4569 	Sint32 attack = 0;
4570 
4571 	if ( (entitystats = this->getStats()) == nullptr )
4572 	{
4573 		return 0;
4574 	}
4575 
4576 	attack = BASE_MELEE_DAMAGE; // base attack strength
4577 	if ( entitystats->weapon != nullptr )
4578 	{
4579 		attack += entitystats->weapon->weaponGetAttack(entitystats);
4580 	}
4581 	else if ( entitystats->weapon == nullptr )
4582 	{
4583 		// bare handed.
4584 		if ( behavior == &actPlayer )
4585 		{
4586 			attack = BASE_PLAYER_UNARMED_DAMAGE;
4587 			attack += (entitystats->PROFICIENCIES[PRO_UNARMED] / 20); // 0, 1, 2, 3, 4, 5 damage from total
4588 		}
4589 		if ( entitystats->gloves )
4590 		{
4591 			int beatitude = entitystats->gloves->beatitude;
4592 			if ( entitystats->gloves->type == BRASS_KNUCKLES )
4593 			{
4594 				attack += 1 + (shouldInvertEquipmentBeatitude(entitystats) ? abs(beatitude) : beatitude);
4595 			}
4596 			else if ( entitystats->gloves->type == IRON_KNUCKLES )
4597 			{
4598 				attack += 2 + (shouldInvertEquipmentBeatitude(entitystats) ? abs(beatitude) : beatitude);
4599 			}
4600 			else if ( entitystats->gloves->type == SPIKED_GAUNTLETS )
4601 			{
4602 				attack += 3 + (shouldInvertEquipmentBeatitude(entitystats) ? abs(beatitude) : beatitude);
4603 			}
4604 		}
4605 		if ( entitystats->ring )
4606 		{
4607 			int beatitude = entitystats->ring->beatitude;
4608 			attack += 1 + (shouldInvertEquipmentBeatitude(entitystats) ? abs(beatitude) : beatitude);
4609 		}
4610 	}
4611 	if ( entitystats->weapon && entitystats->weapon->type == TOOL_WHIP )
4612 	{
4613 		int atk = this->getSTR() + this->getDEX();
4614 		atk = std::min(atk / 2, atk);
4615 		attack += atk;
4616 	}
4617 	else
4618 	{
4619 		attack += this->getSTR();
4620 	}
4621 
4622 	return attack;
4623 }
4624 
4625 /*-------------------------------------------------------------------------------
4626 
4627 Entity::getRangedAttack
4628 
4629 returns the ranged attack power of an entity based on dex, ranged weapon, and a
4630 base number
4631 
4632 -------------------------------------------------------------------------------*/
4633 
getRangedAttack()4634 Sint32 Entity::getRangedAttack()
4635 {
4636 	Stat* entitystats;
4637 	int attack = BASE_RANGED_DAMAGE; // base ranged attack strength
4638 
4639 	if ( (entitystats = this->getStats()) == nullptr )
4640 	{
4641 		return 0;
4642 	}
4643 
4644 	if ( entitystats->weapon )
4645 	{
4646 		attack += entitystats->weapon->weaponGetAttack(entitystats);
4647 		attack += getDEX();
4648 		if ( behavior == &actMonster )
4649 		{
4650 			attack += getPER(); // monsters take PER into their ranged attacks to avoid having to increase their speed.
4651 			attack += entitystats->PROFICIENCIES[PRO_RANGED] / 20; // 0 to 5 bonus attack for monsters
4652 		}
4653 	}
4654 	else
4655 	{
4656 		return 0;
4657 	}
4658 	return attack;
4659 }
4660 
4661 /*-------------------------------------------------------------------------------
4662 
4663 Entity::getThrownAttack
4664 
4665 returns the thrown attack power of an entity based on dex, thrown weapon, and a
4666 base number. For tooltip only.
4667 
4668 -------------------------------------------------------------------------------*/
4669 
getThrownAttack()4670 Sint32 Entity::getThrownAttack()
4671 {
4672 	Stat* entitystats;
4673 	int attack = BASE_THROWN_DAMAGE; // base thrown attack strength
4674 
4675 	if ( (entitystats = this->getStats()) == nullptr )
4676 	{
4677 		return attack;
4678 	}
4679 
4680 	int skillLVL = entitystats->PROFICIENCIES[PRO_RANGED] / 20;
4681 
4682 	if ( entitystats->weapon )
4683 	{
4684 		if ( itemCategory(entitystats->weapon) == THROWN )
4685 		{
4686 			int dex = getDEX() / 4;
4687 			attack += dex;
4688 			attack += entitystats->weapon->weaponGetAttack(entitystats);
4689 			attack *= thrownDamageSkillMultipliers[std::min(skillLVL, 5)];
4690 		}
4691 		else if ( itemCategory(entitystats->weapon) == POTION )
4692 		{
4693 			int skillLVL = entitystats->PROFICIENCIES[PRO_ALCHEMY] / 20;
4694 			/*int dex = getDEX() / 4;
4695 			attack += dex;*/
4696 			attack *= potionDamageSkillMultipliers[std::min(skillLVL, 5)];
4697 		}
4698 		else
4699 		{
4700 			int dex = getDEX() / 4;
4701 			attack += dex;
4702 			attack += entitystats->weapon->weaponGetAttack(entitystats);
4703 			attack += entitystats->PROFICIENCIES[PRO_RANGED] / 10; // 0 to 10 bonus attack.
4704 		}
4705 	}
4706 	else
4707 	{
4708 		return 0;
4709 	}
4710 	return attack;
4711 }
4712 
4713 /*-------------------------------------------------------------------------------
4714 
4715 Entity::getBonusAttackOnTarget
4716 
4717 returns the attack power depending on targets attributes, status effects and race
4718 
4719 -------------------------------------------------------------------------------*/
4720 
getBonusAttackOnTarget(Stat & hitstats)4721 Sint32 Entity::getBonusAttackOnTarget(Stat& hitstats)
4722 {
4723 	Stat* entitystats;
4724 	Sint32 bonusAttack = 0;
4725 
4726 	if ( (entitystats = this->getStats()) == nullptr )
4727 	{
4728 		return 0;
4729 	}
4730 
4731 	if ( entitystats->weapon )
4732 	{
4733 		if ( hitstats.EFFECTS[EFF_VAMPIRICAURA] )
4734 		{
4735 			// blessed weapons deal more damage under this effect.
4736 			bonusAttack += entitystats->weapon->beatitude;
4737 		}
4738 	}
4739 
4740 	return bonusAttack;
4741 }
4742 
4743 /*-------------------------------------------------------------------------------
4744 
4745 Entity::getSTR()
4746 
4747 returns the STR attribute of an entity, post modifiers
4748 
4749 -------------------------------------------------------------------------------*/
4750 
getSTR()4751 Sint32 Entity::getSTR()
4752 {
4753 	Stat* entitystats;
4754 
4755 	if ( (entitystats = this->getStats()) == nullptr )
4756 	{
4757 		return 0;
4758 	}
4759 	return statGetSTR(entitystats, this);
4760 }
4761 
statGetSTR(Stat * entitystats,Entity * my)4762 Sint32 statGetSTR(Stat* entitystats, Entity* my)
4763 {
4764 	Sint32 STR;
4765 
4766 	STR = entitystats->STR;
4767 
4768 	bool cursedItemIsBuff = false;
4769 	bool shapeshifted = false;
4770 	if ( my && my->behavior == &actPlayer )
4771 	{
4772 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
4773 		if ( my->effectShapeshift != NOTHING )
4774 		{
4775 			shapeshifted = true;
4776 			if ( my->effectShapeshift == TROLL )
4777 			{
4778 				int bonusSTR = 5;
4779 				STR += bonusSTR;
4780 				if ( STR >= 0 )
4781 				{
4782 					STR *= 1.33;
4783 				}
4784 			}
4785 			else if ( my->effectShapeshift == SPIDER )
4786 			{
4787 				int bonusSTR = 3;
4788 				STR += bonusSTR;
4789 				if ( STR >= 0 )
4790 				{
4791 					STR *= 1.25;
4792 				}
4793 			}
4794 		}
4795 	}
4796 
4797 	if ( svFlags & SV_FLAG_HUNGER )
4798 	{
4799 		if ( entitystats->HUNGER >= 1500 )
4800 		{
4801 			STR--;
4802 		}
4803 		if ( entitystats->HUNGER <= 150 )
4804 		{
4805 			STR--;
4806 		}
4807 		if ( entitystats->HUNGER <= 50 )
4808 		{
4809 			STR--;
4810 		}
4811 	}
4812 	if ( entitystats->EFFECTS[EFF_VAMPIRICAURA] && my && my->behavior == &actPlayer )
4813 	{
4814 		if ( entitystats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] == -2 )
4815 		{
4816 			STR += 3; // player cursed vampiric bonus
4817 		}
4818 		else
4819 		{
4820 			STR += 5;
4821 		}
4822 	}
4823 	if ( entitystats->gloves != nullptr )
4824 	{
4825 		if ( entitystats->gloves->type == GAUNTLETS_STRENGTH )
4826 		{
4827 			if ( entitystats->gloves->beatitude >= 0 || cursedItemIsBuff )
4828 			{
4829 				STR++;
4830 			}
4831 			STR += (cursedItemIsBuff ? abs(entitystats->gloves->beatitude) : entitystats->gloves->beatitude);
4832 		}
4833 	}
4834 	if ( entitystats->ring != nullptr )
4835 	{
4836 		if ( entitystats->ring->type == RING_STRENGTH )
4837 		{
4838 			if ( entitystats->ring->beatitude >= 0 || cursedItemIsBuff )
4839 			{
4840 				STR++;
4841 			}
4842 			STR += (cursedItemIsBuff ? abs(entitystats->ring->beatitude) : entitystats->ring->beatitude);
4843 		}
4844 	}
4845 	if ( entitystats->EFFECTS[EFF_DRUNK] )
4846 	{
4847 		switch ( entitystats->type )
4848 		{
4849 			case GOATMAN:
4850 				if ( my && my->behavior == &actMonster )
4851 				{
4852 					STR += 10; //Goatman love booze.
4853 				}
4854 				else if ( my && my->behavior == &actPlayer )
4855 				{
4856 					STR += 4;
4857 				}
4858 				break;
4859 			default:
4860 				++STR;
4861 				break;
4862 		}
4863 	}
4864 	if ( entitystats->EFFECTS[EFF_SHRINE_RED_BUFF] )
4865 	{
4866 		STR += 8;
4867 	}
4868 	if ( entitystats->EFFECTS[EFF_POTION_STR] )
4869 	{
4870 		STR += 5;
4871 	}
4872 	return STR;
4873 }
4874 
4875 /*-------------------------------------------------------------------------------
4876 
4877 Entity::getDEX
4878 
4879 returns the DEX attribute of an entity, post modifiers
4880 
4881 -------------------------------------------------------------------------------*/
4882 
getDEX()4883 Sint32 Entity::getDEX()
4884 {
4885 	Stat* entitystats;
4886 
4887 	if ( (entitystats = this->getStats()) == nullptr )
4888 	{
4889 		return 0;
4890 	}
4891 	return statGetDEX(entitystats, this);
4892 }
4893 
statGetDEX(Stat * entitystats,Entity * my)4894 Sint32 statGetDEX(Stat* entitystats, Entity* my)
4895 {
4896 	Sint32 DEX;
4897 
4898 	// paralyzed
4899 	if ( entitystats->EFFECTS[EFF_PARALYZED] )
4900 	{
4901 		return -10;
4902 	}
4903 	if ( entitystats->EFFECTS[EFF_ASLEEP] )
4904 	{
4905 		return -10;
4906 	}
4907 
4908 	DEX = entitystats->DEX;
4909 
4910 	bool cursedItemIsBuff = false;
4911 	bool shapeshifted = false;
4912 	if ( my && my->behavior == &actPlayer )
4913 	{
4914 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
4915 		if ( my->effectShapeshift != NOTHING )
4916 		{
4917 			shapeshifted = true;
4918 			if ( my->effectShapeshift == TROLL )
4919 			{
4920 				int bonusDEX = -5;
4921 				DEX += bonusDEX;
4922 				if ( DEX >= 0 )
4923 				{
4924 					DEX *= 0.67;
4925 				}
4926 			}
4927 			else if ( my->effectShapeshift == RAT )
4928 			{
4929 				int bonusDEX = 3;
4930 				DEX += bonusDEX;
4931 				if ( DEX >= 0 )
4932 				{
4933 					DEX *= 1.25;
4934 				}
4935 			}
4936 		}
4937 	}
4938 
4939 	if ( entitystats->EFFECTS[EFF_VAMPIRICAURA] && !entitystats->EFFECTS[EFF_FAST] && !entitystats->EFFECTS[EFF_SLOW] )
4940 	{
4941 		if ( entitystats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] == -2 )
4942 		{
4943 			DEX += 3; // player cursed vampiric bonus
4944 		}
4945 		else
4946 		{
4947 			DEX += 5;
4948 			if ( my && entitystats->type == VAMPIRE && my->behavior == &actMonster )
4949 			{
4950 				DEX += 3; // monster vampires
4951 			}
4952 		}
4953 	}
4954 	else if ( entitystats->EFFECTS[EFF_FAST] && !entitystats->EFFECTS[EFF_SLOW] )
4955 	{
4956 		if ( my && my->behavior == &actPlayer )
4957 		{
4958 			DEX += 5;
4959 		}
4960 		else
4961 		{
4962 			DEX += 10;
4963 		}
4964 	}
4965 	if ( entitystats->EFFECTS[EFF_STUNNED] )
4966 	{
4967 		//DEX -= 5;
4968 	}
4969 
4970 	if ( my && my->monsterAllyGetPlayerLeader() )
4971 	{
4972 		if ( stats[my->monsterAllyIndex] )
4973 		{
4974 			DEX += 1 + (stats[my->monsterAllyIndex]->PROFICIENCIES[PRO_LEADERSHIP] / 20);
4975 		}
4976 	}
4977 
4978 	if ( my && my->behavior == &actPlayer && entitystats->type == AUTOMATON )
4979 	{
4980 		real_t ratio = entitystats->MP / static_cast<real_t>(entitystats->MAXMP);
4981 		if ( ratio < 0.1 )
4982 		{
4983 			DEX -= std::max((std::max(0, DEX) / 2), 3);
4984 		}
4985 		else if ( ratio < 0.25 )
4986 		{
4987 			DEX -= std::max((std::max(0, DEX) / 4), 2);
4988 		}
4989 
4990 		if ( entitystats->HUNGER == 0 )
4991 		{
4992 			DEX -= 2;
4993 		}
4994 	}
4995 	else if ( svFlags & SV_FLAG_HUNGER )
4996 	{
4997 		if ( entitystats->HUNGER >= 1500 )
4998 		{
4999 			DEX--;
5000 		}
5001 		if ( entitystats->HUNGER <= 150 )
5002 		{
5003 			DEX--;
5004 		}
5005 		if ( entitystats->HUNGER <= 50 )
5006 		{
5007 			DEX--;
5008 		}
5009 	}
5010 
5011 	if ( entitystats->EFFECTS[EFF_WEBBED] && !entitystats->EFFECTS[EFF_SLOW] )
5012 	{
5013 		DEX = std::max(std::min(DEX, 2) - 2 * my->creatureWebbedSlowCount, -4);
5014 	}
5015 	if ( !entitystats->EFFECTS[EFF_FAST] && entitystats->EFFECTS[EFF_SLOW] )
5016 	{
5017 		if ( my && my->behavior == &actPlayer )
5018 		{
5019 			DEX = std::max(DEX - 5, -2);
5020 		}
5021 		else
5022 		{
5023 			DEX = std::min(DEX - 3, -2);
5024 		}
5025 	}
5026 	if ( entitystats->shoes != nullptr )
5027 	{
5028 		if ( entitystats->shoes->type == LEATHER_BOOTS_SPEED )
5029 		{
5030 			if ( entitystats->shoes->beatitude >= 0 || cursedItemIsBuff )
5031 			{
5032 				DEX++;
5033 			}
5034 			DEX += (cursedItemIsBuff ? abs(entitystats->shoes->beatitude) : entitystats->shoes->beatitude);
5035 		}
5036 	}
5037 	if ( entitystats->gloves != nullptr )
5038 	{
5039 		if ( entitystats->gloves->type == GLOVES_DEXTERITY )
5040 		{
5041 			if ( entitystats->gloves->beatitude >= 0 || cursedItemIsBuff )
5042 			{
5043 				DEX++;
5044 			}
5045 			DEX += (cursedItemIsBuff ? abs(entitystats->gloves->beatitude) : entitystats->gloves->beatitude);
5046 		}
5047 	}
5048 	if ( entitystats->EFFECTS[EFF_DRUNK] )
5049 	{
5050 		switch ( entitystats->type )
5051 		{
5052 			case GOATMAN:
5053 			{
5054 				DEX -= 2;
5055 				int minusDex = DEX;
5056 				if ( minusDex > 0 )
5057 				{
5058 					DEX -= (minusDex / 4); // -1 DEX for every 4 DEX we have.
5059 				}
5060 			}
5061 			break;
5062 			default:
5063 				--DEX;
5064 				break;
5065 		}
5066 	}
5067 
5068 	if ( !(svFlags & SV_FLAG_HUNGER) )
5069 	{
5070 		if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 )
5071 		{
5072 			int dexDebuff = 0;
5073 			if ( entitystats->MP < (entitystats->MAXMP) / 5 )
5074 			{
5075 				dexDebuff = 2;
5076 			}
5077 			else if ( entitystats->MP < 2 * (entitystats->MAXMP) / 5 )
5078 			{
5079 				dexDebuff = 1;
5080 			}
5081 			DEX -= dexDebuff;
5082 			if ( DEX > 0 )
5083 			{
5084 				DEX -= dexDebuff * (DEX / 4); // -X DEX for every 4 DEX we have.
5085 			}
5086 		}
5087 	}
5088 
5089 	if ( entitystats->EFFECTS[EFF_WITHDRAWAL] && !entitystats->EFFECTS[EFF_DRUNK] )
5090 	{
5091 		DEX -= 3; // hungover.
5092 		int minusDex = DEX;
5093 		if ( minusDex > 0 )
5094 		{
5095 			DEX -= (minusDex / 4); // -1 DEX for every 4 DEX we have.
5096 		}
5097 	}
5098 	if ( entitystats->EFFECTS[EFF_SHRINE_GREEN_BUFF] )
5099 	{
5100 		DEX += 8;
5101 	}
5102 	return DEX;
5103 }
5104 
5105 /*-------------------------------------------------------------------------------
5106 
5107 Entity::getCON
5108 
5109 returns the CON attribute of an entity, post modifiers
5110 
5111 -------------------------------------------------------------------------------*/
5112 
getCON()5113 Sint32 Entity::getCON()
5114 {
5115 	Stat* entitystats;
5116 
5117 	if ( (entitystats = this->getStats()) == nullptr )
5118 	{
5119 		return 0;
5120 	}
5121 	return statGetCON(entitystats, this);
5122 }
5123 
statGetCON(Stat * entitystats,Entity * my)5124 Sint32 statGetCON(Stat* entitystats, Entity* my)
5125 {
5126 	Sint32 CON;
5127 
5128 	CON = entitystats->CON;
5129 
5130 	bool cursedItemIsBuff = false;
5131 	bool shapeshifted = false;
5132 	if ( my && my->behavior == &actPlayer )
5133 	{
5134 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
5135 		if ( my->effectShapeshift != NOTHING )
5136 		{
5137 			shapeshifted = true;
5138 			if ( my->effectShapeshift == SPIDER )
5139 			{
5140 				int bonusCON = 3;
5141 				CON += bonusCON;
5142 				if ( CON >= 0 )
5143 				{
5144 					CON *= 1.25;
5145 				}
5146 			}
5147 			else if ( my->effectShapeshift == TROLL )
5148 			{
5149 				int bonusCON = 5;
5150 				CON += bonusCON;
5151 				if ( CON >= 0 )
5152 				{
5153 					CON *= 1.33;
5154 				}
5155 			}
5156 		}
5157 	}
5158 
5159 	if ( entitystats->ring != nullptr )
5160 	{
5161 		if ( entitystats->ring->type == RING_CONSTITUTION )
5162 		{
5163 			if ( entitystats->ring->beatitude >= 0 || cursedItemIsBuff )
5164 			{
5165 				CON++;
5166 			}
5167 			CON += (cursedItemIsBuff ? abs(entitystats->ring->beatitude) : entitystats->ring->beatitude);
5168 		}
5169 	}
5170 	if ( entitystats->gloves != nullptr )
5171 	{
5172 		if ( entitystats->gloves->type == BRACERS_CONSTITUTION )
5173 		{
5174 			if ( entitystats->gloves->beatitude >= 0 || cursedItemIsBuff )
5175 			{
5176 				CON++;
5177 			}
5178 			CON += (cursedItemIsBuff ? abs(entitystats->gloves->beatitude) : entitystats->gloves->beatitude);
5179 		}
5180 	}
5181 	if ( entitystats->EFFECTS[EFF_SHRINE_RED_BUFF] )
5182 	{
5183 		CON += 8;
5184 	}
5185 	return CON;
5186 }
5187 
5188 /*-------------------------------------------------------------------------------
5189 
5190 Entity::getINT
5191 
5192 returns the INT attribute of an entity, post modifiers
5193 
5194 -------------------------------------------------------------------------------*/
5195 
getINT()5196 Sint32 Entity::getINT()
5197 {
5198 	Stat* entitystats;
5199 
5200 	if ( (entitystats = this->getStats()) == nullptr )
5201 	{
5202 		return 0;
5203 	}
5204 	return statGetINT(entitystats, this);
5205 }
5206 
statGetINT(Stat * entitystats,Entity * my)5207 Sint32 statGetINT(Stat* entitystats, Entity* my)
5208 {
5209 	Sint32 INT;
5210 
5211 	INT = entitystats->INT;
5212 
5213 	bool cursedItemIsBuff = false;
5214 	bool shapeshifted = false;
5215 	if ( my && my->behavior == &actPlayer )
5216 	{
5217 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
5218 		if ( my->effectShapeshift != NOTHING )
5219 		{
5220 			shapeshifted = true;
5221 			if ( my->effectShapeshift == RAT )
5222 			{
5223 				int bonusINT = 3;
5224 				INT += bonusINT;
5225 				if ( INT >= 0 )
5226 				{
5227 					INT *= 1.25;
5228 				}
5229 			}
5230 			else if ( my->effectShapeshift == CREATURE_IMP )
5231 			{
5232 				int bonusINT = 5;
5233 				INT += bonusINT;
5234 				if ( INT >= 0 )
5235 				{
5236 					INT *= 1.33;
5237 				}
5238 			}
5239 		}
5240 	}
5241 
5242 	if ( svFlags & SV_FLAG_HUNGER )
5243 	{
5244 		if ( entitystats->HUNGER <= 50 )
5245 		{
5246 			INT--;
5247 		}
5248 	}
5249 	if ( entitystats->helmet != nullptr )
5250 	{
5251 		if ( entitystats->helmet->type == HAT_WIZARD )
5252 		{
5253 			if ( entitystats->helmet->beatitude >= 0 || cursedItemIsBuff )
5254 			{
5255 				INT++;
5256 			}
5257 			INT += (cursedItemIsBuff ? abs(entitystats->helmet->beatitude) : entitystats->helmet->beatitude);
5258 		}
5259 		else if ( entitystats->helmet->type == ARTIFACT_HELM )
5260 		{
5261 			if ( entitystats->helmet->beatitude >= 0 || cursedItemIsBuff )
5262 			{
5263 				INT += 8;
5264 			}
5265 			INT += (cursedItemIsBuff ? abs(entitystats->helmet->beatitude) : entitystats->helmet->beatitude);
5266 		}
5267 	}
5268 	if ( my && entitystats->EFFECTS[EFF_DRUNK] && my->behavior == &actPlayer && entitystats->type == GOATMAN )
5269 	{
5270 		INT -= 8;
5271 	}
5272 	if ( entitystats->EFFECTS[EFF_SHRINE_BLUE_BUFF] )
5273 	{
5274 		INT += 8;
5275 	}
5276 	return INT;
5277 }
5278 
5279 /*-------------------------------------------------------------------------------
5280 
5281 Entity::getPER
5282 
5283 returns the PER attribute of an entity, post modifiers
5284 
5285 -------------------------------------------------------------------------------*/
5286 
getPER()5287 Sint32 Entity::getPER()
5288 {
5289 	Stat* entitystats;
5290 
5291 	if ( (entitystats = this->getStats()) == nullptr )
5292 	{
5293 		return 0;
5294 	}
5295 	return statGetPER(entitystats, this);
5296 }
5297 
statGetPER(Stat * entitystats,Entity * my)5298 Sint32 statGetPER(Stat* entitystats, Entity* my)
5299 {
5300 	Sint32 PER;
5301 
5302 	PER = entitystats->PER;
5303 
5304 	bool cursedItemIsBuff = false;
5305 	bool shapeshifted = false;
5306 	if ( my && my->behavior == &actPlayer )
5307 	{
5308 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
5309 		if ( my->effectShapeshift != NOTHING )
5310 		{
5311 			shapeshifted = true;
5312 			if ( my->effectShapeshift == SPIDER )
5313 			{
5314 				int bonusPER = 5;
5315 				PER += bonusPER;
5316 				if ( PER >= 0 )
5317 				{
5318 					PER *= 1.33;
5319 				}
5320 			}
5321 			else if ( my->effectShapeshift == CREATURE_IMP )
5322 			{
5323 				int bonusPER = 3;
5324 				PER += bonusPER;
5325 				if ( PER >= 0 )
5326 				{
5327 					PER *= 1.25;
5328 				}
5329 			}
5330 			else if ( my->effectShapeshift == RAT )
5331 			{
5332 				int bonusPER = 3;
5333 				PER += bonusPER;
5334 				if ( PER >= 0 )
5335 				{
5336 					PER *= 1.25;
5337 				}
5338 			}
5339 		}
5340 	}
5341 
5342 	if ( svFlags & SV_FLAG_HUNGER )
5343 	{
5344 		if ( entitystats->HUNGER <= 50 )
5345 		{
5346 			PER--;
5347 		}
5348 	}
5349 	if ( entitystats->mask )
5350 	{
5351 		if ( entitystats->mask->type == TOOL_GLASSES )
5352 		{
5353 			if ( entitystats->mask->beatitude >= 0 || cursedItemIsBuff )
5354 			{
5355 				PER++;
5356 			}
5357 			PER += (cursedItemIsBuff ? abs(entitystats->mask->beatitude) : entitystats->mask->beatitude);
5358 		}
5359 		else if ( entitystats->mask->type == TOOL_BLINDFOLD
5360 					|| entitystats->mask->type == TOOL_BLINDFOLD_TELEPATHY
5361 					|| entitystats->mask->type == TOOL_BLINDFOLD_FOCUS )
5362 		{
5363 			if ( entitystats->mask->type == TOOL_BLINDFOLD_TELEPATHY
5364 				|| entitystats->mask->type == TOOL_BLINDFOLD_FOCUS )
5365 			{
5366 				PER += 0;
5367 			}
5368 			else
5369 			{
5370 				PER -= 10;
5371 			}
5372 			PER += (cursedItemIsBuff ? abs(entitystats->mask->beatitude) : entitystats->mask->beatitude);
5373 		}
5374 	}
5375 	if ( entitystats->breastplate )
5376 	{
5377 		if ( entitystats->breastplate->type == MACHINIST_APRON )
5378 		{
5379 			if ( entitystats->breastplate->beatitude >= 0 || cursedItemIsBuff )
5380 			{
5381 				PER += 2;
5382 			}
5383 			PER += (cursedItemIsBuff ? abs(entitystats->breastplate->beatitude) : entitystats->breastplate->beatitude);
5384 		}
5385 	}
5386 
5387 	if ( !(svFlags & SV_FLAG_HUNGER) )
5388 	{
5389 		if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 )
5390 		{
5391 			int perDebuff = 0;
5392 			if ( entitystats->MP < (entitystats->MAXMP) / 5 )
5393 			{
5394 				perDebuff = 2;
5395 			}
5396 			else if ( entitystats->MP < 2 * (entitystats->MAXMP) / 5 )
5397 			{
5398 				perDebuff = 1;
5399 			}
5400 			PER -= perDebuff;
5401 			if ( PER > 0 )
5402 			{
5403 				PER -= perDebuff * (PER / 4); // -X DEX for every 4 DEX we have.
5404 			}
5405 		}
5406 	}
5407 
5408 	if ( entitystats->EFFECTS[EFF_SHRINE_GREEN_BUFF] )
5409 	{
5410 		PER += 8;
5411 	}
5412 	if ( entitystats->EFFECTS[EFF_POTION_STR] )
5413 	{
5414 		PER -= 5;
5415 	}
5416 	return PER;
5417 }
5418 
5419 /*-------------------------------------------------------------------------------
5420 
5421 Entity::getCHR
5422 
5423 returns the CHR attribute of an entity, post modifiers
5424 
5425 -------------------------------------------------------------------------------*/
5426 
getCHR()5427 Sint32 Entity::getCHR()
5428 {
5429 	Stat* entitystats;
5430 
5431 	if ( (entitystats = this->getStats()) == nullptr )
5432 	{
5433 		return 0;
5434 	}
5435 	return statGetCHR(entitystats, this);
5436 }
5437 
statGetCHR(Stat * entitystats,Entity * my)5438 Sint32 statGetCHR(Stat* entitystats, Entity* my)
5439 {
5440 	Sint32 CHR;
5441 
5442 	CHR = entitystats->CHR;
5443 
5444 	bool cursedItemIsBuff = false;
5445 	bool shapeshifted = false;
5446 	if ( my && my->behavior == &actPlayer )
5447 	{
5448 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(entitystats);
5449 		if ( my->effectShapeshift != NOTHING )
5450 		{
5451 			shapeshifted = true;
5452 			//if ( my->effectShapeshift == CREATURE_IMP )
5453 			//{
5454 			//	int bonusCHR = (2 + (std::max(0, entitystats->CHR) / 10)); // +2 + 10% base CHR
5455 			//	CHR += bonusCHR;
5456 			//}
5457 		}
5458 	}
5459 
5460 	if ( entitystats->helmet != nullptr )
5461 	{
5462 		if ( entitystats->helmet->type == HAT_JESTER )
5463 		{
5464 			if ( entitystats->helmet->beatitude >= 0 || cursedItemIsBuff )
5465 			{
5466 				CHR++;
5467 			}
5468 			CHR += (cursedItemIsBuff ? abs(entitystats->helmet->beatitude) : entitystats->helmet->beatitude);
5469 		}
5470 	}
5471 	if ( entitystats->ring != nullptr )
5472 	{
5473 		if ( entitystats->ring->type == RING_ADORNMENT )
5474 		{
5475 			if ( entitystats->ring->beatitude >= 0 || cursedItemIsBuff )
5476 			{
5477 				CHR++;
5478 			}
5479 			CHR += (cursedItemIsBuff ? abs(entitystats->ring->beatitude) : entitystats->ring->beatitude);
5480 		}
5481 	}
5482 	if ( entitystats->monsterDemonHasBeenExorcised >= 3 )
5483 	{
5484 		CHR += 5;
5485 	}
5486 	if ( my && entitystats->EFFECTS[EFF_DRUNK] && my->behavior == &actPlayer && entitystats->type == GOATMAN )
5487 	{
5488 		CHR += 4;
5489 	}
5490 	return CHR;
5491 }
5492 
5493 /*-------------------------------------------------------------------------------
5494 
5495 Entity::isBlind
5496 
5497 returns true if the given entity is blind, and false if it is not
5498 
5499 -------------------------------------------------------------------------------*/
5500 
isBlind()5501 bool Entity::isBlind()
5502 {
5503 	Stat* entitystats;
5504 	if ( (entitystats = this->getStats()) == nullptr )
5505 	{
5506 		return false;
5507 	}
5508 
5509 	bool shapeshifted = false;
5510 	if ( this->behavior == &actPlayer )
5511 	{
5512 		if ( effectShapeshift != NOTHING )
5513 		{
5514 			shapeshifted = true;
5515 		}
5516 	}
5517 
5518 	// being blind
5519 	if ( entitystats->EFFECTS[EFF_BLIND] == true )
5520 	{
5521 		return true;
5522 	}
5523 
5524 	// asleep
5525 	if ( entitystats->EFFECTS[EFF_ASLEEP] == true )
5526 	{
5527 		return true;
5528 	}
5529 
5530 	// messy face
5531 	if ( entitystats->EFFECTS[EFF_MESSY] == true )
5532 	{
5533 		return true;
5534 	}
5535 
5536 	// wearing blindfolds
5537 	if ( entitystats->mask != nullptr && !shapeshifted )
5538 	{
5539 		if ( entitystats->mask->type == TOOL_BLINDFOLD
5540 			|| entitystats->mask->type == TOOL_BLINDFOLD_TELEPATHY
5541 			|| entitystats->mask->type == TOOL_BLINDFOLD_FOCUS )
5542 		{
5543 			return true;
5544 		}
5545 	}
5546 
5547 	return false;
5548 }
5549 
5550 /*-------------------------------------------------------------------------------
5551 
5552 Entity::isInvisible
5553 
5554 returns true if the given entity is invisible or else wearing something
5555 that would make it invisible
5556 
5557 -------------------------------------------------------------------------------*/
5558 
isInvisible() const5559 bool Entity::isInvisible() const
5560 {
5561 	Stat* entitystats;
5562 	if ( (entitystats = getStats()) == NULL )
5563 	{
5564 		return false;
5565 	}
5566 
5567 	// being invisible
5568 	if ( entitystats->EFFECTS[EFF_INVISIBLE] == true )
5569 	{
5570 		return true;
5571 	}
5572 
5573 	// wearing invisibility cloaks
5574 	if ( entitystats->cloak != NULL )
5575 	{
5576 		if ( entitystats->cloak->type == CLOAK_INVISIBILITY )
5577 		{
5578 			return true;
5579 		}
5580 	}
5581 
5582 	// wearing invisibility ring
5583 	if ( entitystats->ring != NULL )
5584 	{
5585 		if ( entitystats->ring->type == RING_INVISIBILITY )
5586 		{
5587 			return true;
5588 		}
5589 	}
5590 
5591 	if ( this->behavior == &actPlayer )
5592 	{
5593 		if ( this->skill[2] >= 0 && this->skill[2] < MAXPLAYERS )
5594 		{
5595 			if ( skillCapstoneUnlockedEntity(PRO_STEALTH) && (stats[this->skill[2]]->sneaking && !stats[this->skill[2]]->defending) )
5596 			{
5597 				if ( this->skill[9] == 0 ) // player attack variable.
5598 				{
5599 					return true;
5600 				}
5601 			}
5602 		}
5603 	}
5604 	else if ( skillCapstoneUnlockedEntity(PRO_STEALTH) )
5605 	{
5606 		return true;
5607 	}
5608 
5609 	return false;
5610 }
5611 
5612 /*-------------------------------------------------------------------------------
5613 
5614 Entity::isMobile
5615 
5616 returns true if the given entity can move, or false if it cannot
5617 
5618 -------------------------------------------------------------------------------*/
5619 
isMobile()5620 bool Entity::isMobile()
5621 {
5622 	Stat* entitystats;
5623 	if ( (entitystats = getStats()) == nullptr )
5624 	{
5625 		return true;
5626 	}
5627 
5628 	if ( behavior == &actPlayer && (entitystats->EFFECTS[EFF_PACIFY] || entitystats->EFFECTS[EFF_FEAR]) )
5629 	{
5630 		return false;
5631 	}
5632 	else if ( behavior == &actPlayer && entitystats->HP <= 0 )
5633 	{
5634 		return false;
5635 	}
5636 
5637 	if ( behavior == &actPlayer &&
5638 		(this->skill[9] == MONSTER_POSE_SPECIAL_WINDUP1 || this->skill[9] == PLAYER_POSE_GOLEM_SMASH) // special strike attack
5639 		)
5640 	{
5641 		return false;
5642 	}
5643 
5644 	if ( behavior == &actMonster &&
5645 		(introstage == 9
5646 		|| introstage == 11 + MOVIE_MIDGAME_BAPHOMET_HUMAN_AUTOMATON
5647 		|| introstage == 11 + MOVIE_MIDGAME_BAPHOMET_MONSTERS
5648 		|| introstage == 11 + MOVIE_MIDGAME_HERX_MONSTERS) )
5649 	{
5650 		return false; // mid-game crawls.
5651 	}
5652 
5653 	// paralyzed
5654 	if ( entitystats->EFFECTS[EFF_PARALYZED] )
5655 	{
5656 		return false;
5657 	}
5658 
5659 	// asleep
5660 	if ( entitystats->EFFECTS[EFF_ASLEEP] )
5661 	{
5662 		return false;
5663 	}
5664 
5665 	// stunned
5666 	if ( entitystats->EFFECTS[EFF_STUNNED] )
5667 	{
5668 		return false;
5669 	}
5670 
5671 	if ( (entitystats->type == LICH_FIRE || entitystats->type == LICH_ICE)
5672 		&& monsterLichBattleState < LICH_BATTLE_READY )
5673 	{
5674 		return false;
5675 	}
5676 
5677 	if ( entitystats->type == GYROBOT
5678 		&& (monsterSpecialState == GYRO_RETURN_LANDING
5679 			|| monsterSpecialState == GYRO_INTERACT_LANDING
5680 			|| monsterSpecialState == GYRO_START_FLYING) )
5681 	{
5682 		return false;
5683 	}
5684 	else if ( (entitystats->type == DUMMYBOT || entitystats->type == SENTRYBOT || entitystats->type == SPELLBOT)
5685 		&& (monsterSpecialState == DUMMYBOT_RETURN_FORM) )
5686 	{
5687 		return false;
5688 	}
5689 
5690 	if ( entitystats->MISC_FLAGS[STAT_FLAG_NPC] != 0 && !strcmp(entitystats->name, "scriptNPC") )
5691 	{
5692 		return false;
5693 	}
5694 
5695 	return true;
5696 }
5697 
5698 /*-------------------------------------------------------------------------------
5699 
5700 checkTileForEntity
5701 
5702 returns a list of entities that are occupying the map tile specified at
5703 (x, y)
5704 
5705 -------------------------------------------------------------------------------*/
5706 
checkTileForEntity(int x,int y)5707 list_t* checkTileForEntity(int x, int y)
5708 {
5709 	if ( x < 0 || y < 0 || x > 255 || y > 255 )
5710 	{
5711 		return nullptr; // invalid grid reference!
5712 	}
5713 	return &TileEntityList.gridEntities[x][y];
5714 
5715 //	list_t* return_val = NULL;
5716 //
5717 //	//Loop through the list.
5718 //	//If the entity's x and y match the tile's x and y (correcting for the difference in the two x/y systems, of course), then the entity is on the tile.
5719 //	//Traverse map.entities...
5720 //	node_t* node = NULL;
5721 //	node_t* node2 = NULL;
5722 //#ifdef __ARM_NEON__
5723 //	const int32x2_t xy = { x, y };
5724 //#endif
5725 //
5726 //	for ( node = map.entities->first; node != NULL; node = node->next )
5727 //	{
5728 //		if ( node->element )
5729 //		{
5730 //			Entity* entity = (Entity*)node->element;
5731 //			if ( entity ) {
5732 //#ifdef __ARM_NEON__
5733 //				uint32x2_t eqxy = vceq_s32(vcvt_s32_f32(vmul_n_f32(vld1_f32(&entity->x), 1.0f / 16.0f)), xy);
5734 //				if ( eqxy[0] && eqxy[1] )
5735 //#else
5736 //				if ( (int)floor((entity->x / 16)) == x && (int)floor((entity->y / 16)) == y )   //Check if the current entity is on the tile.
5737 //#endif
5738 //				{
5739 //					//Right. So. Create the list if it doesn't exist.
5740 //					if ( !return_val )
5741 //					{
5742 //						return_val = (list_t*)malloc(sizeof(list_t));
5743 //						return_val->first = NULL;
5744 //						return_val->last = NULL;
5745 //					}
5746 //
5747 //					//And add the current entity to it.
5748 //					node2 = list_AddNodeLast(return_val);
5749 //					node2->element = entity;
5750 //					node2->deconstructor = &emptyDeconstructor;
5751 //				}
5752 //			}
5753 //		}
5754 //	}
5755 //
5756 //	return return_val;
5757 }
5758 
5759 /*-------------------------------------------------------------------------------
5760 
5761 getItemsOnTile
5762 
5763 Fills the given list with nodes for every item entity on the given
5764 map tile (x, y)
5765 
5766 -------------------------------------------------------------------------------*/
5767 
getItemsOnTile(int x,int y,list_t ** list)5768 void getItemsOnTile(int x, int y, list_t** list)
5769 {
5770 
5771 	//Take the return value of checkTileForEntity() and sort that list for items.
5772 	//if( entity->behavior == &actItem )
5773 	//And then free the list returned by checkTileForEntity.
5774 
5775 	//Right. First, grab all the entities on the tile.
5776 	list_t* entities = NULL;
5777 	entities = checkTileForEntity(x, y);
5778 
5779 	if ( !entities )
5780 	{
5781 		return;    //No use continuing of got no entities.
5782 	}
5783 
5784 	node_t* node = NULL;
5785 	node_t* node2 = NULL;
5786 	//Loop through the list of entities.
5787 	for ( node = entities->first; node != NULL; node = node->next )
5788 	{
5789 		if ( node->element )
5790 		{
5791 			Entity* entity = (Entity*)node->element;
5792 			//Check if the entity is an item.
5793 			if ( entity && entity->behavior == &actItem )
5794 			{
5795 				//If this is the first item found, the list needs to be created.
5796 				if ( !(*list) )
5797 				{
5798 					*list = (list_t*)malloc(sizeof(list_t));
5799 					(*list)->first = NULL;
5800 					(*list)->last = NULL;
5801 				}
5802 
5803 				//Add the current entity to it.
5804 				node2 = list_AddNodeLast(*list);
5805 				node2->element = entity;
5806 				node2->deconstructor = &emptyDeconstructor;
5807 			}
5808 		}
5809 	}
5810 
5811 	/*if ( entities )
5812 	{
5813 		list_FreeAll(entities);
5814 		free(entities);
5815 	}*/
5816 
5817 	//return return_val;
5818 }
5819 
5820 /*-------------------------------------------------------------------------------
5821 
5822 Entity::attack
5823 
5824 Causes an entity to attack using whatever weapon it's holding
5825 
5826 -------------------------------------------------------------------------------*/
5827 
attack(int pose,int charge,Entity * target)5828 void Entity::attack(int pose, int charge, Entity* target)
5829 {
5830 	Stat* hitstats = nullptr;
5831 	Stat* myStats = nullptr;
5832 	int player, playerhit = -1;
5833 	double dist;
5834 	int c, i;
5835 	int weaponskill = -1;
5836 	node_t* node = nullptr;
5837 	double tangent;
5838 
5839 	if ( (myStats = getStats()) == nullptr )
5840 	{
5841 		return;
5842 	}
5843 
5844 	// get the player number, if applicable
5845 	if ( behavior == &actPlayer )
5846 	{
5847 		player = skill[2];
5848 	}
5849 	else
5850 	{
5851 		player = -1; // not a player
5852 	}
5853 
5854 	if ( multiplayer != CLIENT )
5855 	{
5856 		// animation
5857 		if ( player >= 0 )
5858 		{
5859 			players[player]->entity->skill[10] = 0; // PLAYER_ATTACKTIME
5860 			if ( pose == MONSTER_POSE_SPECIAL_WINDUP1 || pose == PLAYER_POSE_GOLEM_SMASH || pose == MONSTER_POSE_SPECIAL_WINDUP2 )
5861 			{
5862 				players[player]->entity->skill[9] = pose; // PLAYER_ATTACK
5863 				if ( pose == MONSTER_POSE_SPECIAL_WINDUP1 || pose == MONSTER_POSE_SPECIAL_WINDUP2 )
5864 				{
5865 					if ( multiplayer == SERVER )
5866 					{
5867 						if ( player >= 0 && player < MAXPLAYERS )
5868 						{
5869 							serverUpdateEntitySkill(players[player]->entity, 9);
5870 							serverUpdateEntitySkill(players[player]->entity, 10);
5871 						}
5872 					}
5873 					return;
5874 				}
5875 				else if ( pose == PLAYER_POSE_GOLEM_SMASH )
5876 				{
5877 					players[player]->entity->skill[10] = 1; // to avoid resetting the animation
5878 				}
5879 			}
5880 			else if ( stats[player]->weapon != nullptr )
5881 			{
5882 				if ( stats[player]->type == CREATURE_IMP && itemCategory(stats[player]->weapon) != MAGICSTAFF )
5883 				{
5884 					players[player]->entity->skill[9] = 1;
5885 				}
5886 				else
5887 				{
5888 					players[player]->entity->skill[9] = pose; // PLAYER_ATTACK
5889 				}
5890 			}
5891 			else
5892 			{
5893 				players[player]->entity->skill[9] = 1; // special case for punch to eliminate spanking motion :p
5894 			}
5895 		}
5896 		else
5897 		{
5898 			if ( pose >= MONSTER_POSE_MELEE_WINDUP1 && pose <= MONSTER_POSE_SPECIAL_WINDUP3 )
5899 			{
5900 				monsterAttack = pose;
5901 				monsterAttackTime = 0;
5902 				if ( multiplayer == SERVER )
5903 				{
5904 					// be sure to update the clients with the new wind-up pose.
5905 					serverUpdateEntitySkill(this, 8);
5906 					serverUpdateEntitySkill(this, 9);
5907 				}
5908 				return; // don't execute the attack, let the monster animation call the attack() function again.
5909 			}
5910 			else if ( (myStats->type == INCUBUS && (pose == MONSTER_POSE_INCUBUS_TELEPORT || pose == MONSTER_POSE_INCUBUS_TAUNT))
5911 				|| (myStats->type == VAMPIRE && (pose == MONSTER_POSE_VAMPIRE_DRAIN || pose == MONSTER_POSE_VAMPIRE_AURA_CHARGE))
5912 				|| (myStats->type == LICH_FIRE && pose == MONSTER_POSE_MAGIC_CAST1)
5913 				|| (myStats->type == LICH_ICE && pose == MONSTER_POSE_MAGIC_CAST1)
5914 				|| (myStats->type == LICH_ICE
5915 						&& (monsterLichIceCastPrev == LICH_ATK_CHARGE_AOE
5916 							|| monsterLichIceCastPrev == LICH_ATK_RISING_RAIN
5917 							|| monsterLichIceCastPrev == LICH_ATK_FALLING_DIAGONAL
5918 							|| monsterState == MONSTER_STATE_LICH_CASTSPELLS
5919 							)
5920 					)
5921 			)
5922 			{
5923 				// calls animation, but doesn't actually attack
5924 				monsterAttack = pose;
5925 				monsterAttackTime = 0;
5926 				if ( multiplayer == SERVER )
5927 				{
5928 					// be sure to update the clients with the new wind-up pose.
5929 					serverUpdateEntitySkill(this, 8);
5930 					serverUpdateEntitySkill(this, 9);
5931 				}
5932 				return; // don't execute the attack, let the monster animation call the attack() function again.
5933 			}
5934 			else if ( myStats->type == VAMPIRE && pose == MONSTER_POSE_VAMPIRE_AURA_CAST )
5935 			{
5936 				monsterAttack = 0;
5937 			}
5938 			else if ( myStats->weapon != nullptr || myStats->type == CRYSTALGOLEM || myStats->type == COCKATRICE )
5939 			{
5940 				monsterAttack = pose;
5941 			}
5942 			else
5943 			{
5944 				monsterAttack = 1;    // punching
5945 			}
5946 			monsterAttackTime = 0;
5947 		}
5948 
5949 		// special AoE attack.
5950 		if ( behavior == &actPlayer && pose == MONSTER_POSE_AUTOMATON_MALFUNCTION )
5951 		{
5952 			list_t* aoeTargets = nullptr;
5953 			getTargetsAroundEntity(this, this, 24, PI, MONSTER_TARGET_ALL, &aoeTargets);
5954 			if ( aoeTargets )
5955 			{
5956 				for ( node = aoeTargets->first; node != NULL; node = node->next )
5957 				{
5958 					Entity* tmpEntity = (Entity*)node->element;
5959 					if ( tmpEntity != nullptr )
5960 					{
5961 						spawnExplosion(tmpEntity->x, tmpEntity->y, tmpEntity->z);
5962 						Stat* tmpStats = tmpEntity->getStats();
5963 						if ( tmpStats )
5964 						{
5965 							int explodeDmg = (10 + rand() % 10 + myStats->LVL) * tmpEntity->getDamageTableMultiplier(*tmpStats, DAMAGE_TABLE_MAGIC); // check base magic damage resist.
5966 							Entity* gib = spawnGib(tmpEntity);
5967 							serverSpawnGibForClient(gib);
5968 							if ( tmpEntity->behavior == &actPlayer )
5969 							{
5970 								playerhit = tmpEntity->skill[2];
5971 								if ( playerhit > 0 && multiplayer == SERVER )
5972 								{
5973 									strcpy((char*)net_packet->data, "SHAK");
5974 									net_packet->data[4] = 20; // turns into .1
5975 									net_packet->data[5] = 20;
5976 									net_packet->address.host = net_clients[playerhit - 1].host;
5977 									net_packet->address.port = net_clients[playerhit - 1].port;
5978 									net_packet->len = 6;
5979 									sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
5980 								}
5981 								else if ( playerhit == 0 || splitscreen )
5982 								{
5983 									cameravars[playerhit].shakex += 0.2;
5984 									cameravars[playerhit].shakey += 20;
5985 								}
5986 								if ( playerhit >= 0 )
5987 								{
5988 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
5989 									messagePlayerColor(playerhit, color, language[2523]);
5990 								}
5991 							}
5992 							tmpEntity->modHP(-explodeDmg);
5993 						}
5994 					}
5995 				}
5996 				//Free the list.
5997 				list_FreeAll(aoeTargets);
5998 				free(aoeTargets);
5999 			}
6000 			return;
6001 		}
6002 
6003 		if ( multiplayer == SERVER )
6004 		{
6005 			if ( player >= 0 && player < MAXPLAYERS )
6006 			{
6007 				serverUpdateEntitySkill(players[player]->entity, 9);
6008 				serverUpdateEntitySkill(players[player]->entity, 10);
6009 			}
6010 			else
6011 			{
6012 				serverUpdateEntitySkill(this, 8);
6013 				serverUpdateEntitySkill(this, 9);
6014 			}
6015 		}
6016 
6017 		if ( myStats->type == SHADOW )
6018 		{
6019 			if ( myStats->EFFECTS[EFF_INVISIBLE] )
6020 			{
6021 				//Shadows lose invisibility when they attack.
6022 				//TODO: How does this play with the passive invisibility?
6023 				setEffect(EFF_INVISIBLE, false, 0, true);
6024 			}
6025 		}
6026 
6027 		if ( behavior == &actMonster && monsterAllyIndex != -1 )
6028 		{
6029 			Entity* myTarget = uidToEntity(monsterTarget);
6030 			if ( myTarget )
6031 			{
6032 				if ( myTarget->monsterAllyIndex != -1 || myTarget->behavior == &actPlayer )
6033 				{
6034 					this->monsterReleaseAttackTarget(true); // stop attacking player allies or players after this hit executes.
6035 				}
6036 			}
6037 		}
6038 
6039 		bool shapeshifted = false;
6040 		if ( this->behavior == &actPlayer && this->effectShapeshift != NOTHING )
6041 		{
6042 			shapeshifted = true;
6043 		}
6044 
6045 		bool isIllusion = false;
6046 		if ( myStats->type == INCUBUS
6047 			&& !strncmp(myStats->name, "inner demon", strlen("inner demon")) )
6048 		{
6049 			isIllusion = true;
6050 		}
6051 
6052 		if ( myStats->weapon != nullptr
6053 			&& (!shapeshifted || (shapeshifted && myStats->type == CREATURE_IMP && itemCategory(myStats->weapon) == MAGICSTAFF)) )
6054 		{
6055 			// if non-shapeshifted, or you're an imp with a staff then process throwing/magic weapons
6056 
6057 			// magical weapons
6058 			if ( itemCategory(myStats->weapon) == SPELLBOOK || itemCategory(myStats->weapon) == MAGICSTAFF )
6059 			{
6060 				if ( itemCategory(myStats->weapon) == MAGICSTAFF )
6061 				{
6062 					switch ( myStats->weapon->type )
6063 					{
6064 						case MAGICSTAFF_LIGHT:
6065 							castSpell(uid, &spell_light, true, false);
6066 							break;
6067 						case MAGICSTAFF_DIGGING:
6068 							castSpell(uid, &spell_dig, true, false);
6069 							break;
6070 						case MAGICSTAFF_LOCKING:
6071 							castSpell(uid, &spell_locking, true, false);
6072 							break;
6073 						case MAGICSTAFF_MAGICMISSILE:
6074 							castSpell(uid, &spell_magicmissile, true, false);
6075 							break;
6076 						case MAGICSTAFF_OPENING:
6077 							castSpell(uid, &spell_opening, true, false);
6078 							break;
6079 						case MAGICSTAFF_SLOW:
6080 							castSpell(uid, &spell_slow, true, false);
6081 							break;
6082 						case MAGICSTAFF_COLD:
6083 							castSpell(uid, &spell_cold, true, false);
6084 							break;
6085 						case MAGICSTAFF_FIRE:
6086 							castSpell(uid, &spell_fireball, true, false);
6087 							break;
6088 						case MAGICSTAFF_LIGHTNING:
6089 							castSpell(uid, &spell_lightning, true, false);
6090 							break;
6091 						case MAGICSTAFF_SLEEP:
6092 							castSpell(uid, &spell_sleep, true, false);
6093 							break;
6094 						case MAGICSTAFF_SUMMON:
6095 							castSpell(uid, &spell_summon, true, false);
6096 							break;
6097 						case MAGICSTAFF_STONEBLOOD:
6098 							castSpell(uid, &spell_stoneblood, true, false);
6099 							break;
6100 						case MAGICSTAFF_BLEED:
6101 							castSpell(uid, &spell_bleed, true, false);
6102 							break;
6103 						case MAGICSTAFF_CHARM:
6104 							castSpell(uid, &spell_charmMonster, true, false);
6105 							break;
6106 						case MAGICSTAFF_POISON:
6107 							castSpell(uid, &spell_poison, true, false);
6108 							break;
6109 						default:
6110 							messagePlayer(player, "This is my wish stick! Wishy wishy wish!");
6111 							break;
6112 					}
6113 
6114 					// magicstaffs deplete themselves for each use
6115 					bool degradeWeapon = true;
6116 					if ( myStats->type == SHADOW || myStats->type == LICH_FIRE || myStats->type == LICH_ICE )
6117 					{
6118 						degradeWeapon = false; //certain monster's weapons don't degrade.
6119 					}
6120 					bool forceDegrade = false;
6121 					if ( degradeWeapon )
6122 					{
6123 						if ( myStats->weapon->type == MAGICSTAFF_CHARM )
6124 						{
6125 							if ( myStats->weapon->status <= WORN )
6126 							{
6127 								forceDegrade = true;
6128 							}
6129 						}
6130 					}
6131 
6132 					if ( (rand() % 3 == 0 && degradeWeapon && !(svFlags & SV_FLAG_HARDCORE)) || forceDegrade
6133 						|| ((svFlags & SV_FLAG_HARDCORE) && rand() % 6 == 0 && degradeWeapon) )
6134 					{
6135 						if ( player == clientnum )
6136 						{
6137 							if ( myStats->weapon->count > 1 )
6138 							{
6139 								newItem(myStats->weapon->type, myStats->weapon->status, myStats->weapon->beatitude, myStats->weapon->count - 1, myStats->weapon->appearance, myStats->weapon->identified, &myStats->inventory);
6140 							}
6141 						}
6142 						myStats->weapon->count = 1;
6143 						myStats->weapon->status = static_cast<Status>(myStats->weapon->status - 1);
6144 						if ( myStats->weapon->status != BROKEN )
6145 						{
6146 							messagePlayer(player, language[659]);
6147 						}
6148 						else
6149 						{
6150 							if ( itemCategory(myStats->weapon) == MAGICSTAFF && myStats->weapon->beatitude < 0 )
6151 							{
6152 								steamAchievementClient(player, "BARONY_ACH_ONE_MANS_TRASH");
6153 							}
6154 							messagePlayer(player, language[660]);
6155 							if ( player == clientnum && client_classes[player] == CLASS_MESMER )
6156 							{
6157 								if ( myStats->weapon->type == MAGICSTAFF_CHARM )
6158 								{
6159 									bool foundCharmSpell = false;
6160 									for ( node_t* spellnode = stats[clientnum]->inventory.first; spellnode != nullptr; spellnode = spellnode->next )
6161 									{
6162 										Item* item = (Item*)spellnode->element;
6163 										if ( item && itemCategory(item) == SPELL_CAT )
6164 										{
6165 											spell_t* spell = getSpellFromItem(item);
6166 											if ( spell && spell->ID == SPELL_CHARM_MONSTER )
6167 											{
6168 												foundCharmSpell = true;
6169 												break;
6170 											}
6171 										}
6172 									}
6173 									if ( !foundCharmSpell )
6174 									{
6175 										steamAchievement("BARONY_ACH_WHAT_NOW");
6176 									}
6177 								}
6178 							}
6179 						}
6180 						if ( player > 0 && multiplayer == SERVER )
6181 						{
6182 							strcpy((char*)net_packet->data, "ARMR");
6183 							net_packet->data[4] = 5;
6184 							net_packet->data[5] = myStats->weapon->status;
6185 							net_packet->address.host = net_clients[player - 1].host;
6186 							net_packet->address.port = net_clients[player - 1].port;
6187 							net_packet->len = 6;
6188 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
6189 						}
6190 					}
6191 				}
6192 				else
6193 				{
6194 					// this is mostly used for monsters that "cast" spells
6195 					switch ( myStats->weapon->type )
6196 					{
6197 						case SPELLBOOK_FORCEBOLT:
6198 							if ( myStats->type == SPELLBOT )
6199 							{
6200 								Entity* cast = castSpell(uid, &spell_forcebolt, true, false);
6201 								if ( cast )
6202 								{
6203 									cast->z -= 1;
6204 									cast->x = this->x + 2 * cos(this->yaw);
6205 									cast->y = this->y + 2 * sin(this->yaw);
6206 								}
6207 							}
6208 							else
6209 							{
6210 								castSpell(uid, &spell_forcebolt, true, false);
6211 							}
6212 							break;
6213 						case SPELLBOOK_MAGICMISSILE:
6214 							if ( myStats->type == SPELLBOT )
6215 							{
6216 								Entity* cast = castSpell(uid, &spell_magicmissile, true, false);
6217 								if ( cast )
6218 								{
6219 									cast->z -= 1;
6220 									cast->x = this->x + 2 * cos(this->yaw);
6221 									cast->y = this->y + 2 * sin(this->yaw);
6222 								}
6223 							}
6224 							else
6225 							{
6226 								castSpell(uid, &spell_magicmissile, true, false);
6227 							}
6228 							break;
6229 						case SPELLBOOK_COLD:
6230 							castSpell(uid, &spell_cold, true, false);
6231 							break;
6232 						case SPELLBOOK_FIREBALL:
6233 							castSpell(uid, &spell_fireball, true, false);
6234 							break;
6235 						case SPELLBOOK_LIGHTNING:
6236 							castSpell(uid, &spell_lightning, true, false);
6237 							break;
6238 						case SPELLBOOK_SLEEP:
6239 							castSpell(uid, &spell_sleep, true, false);
6240 							break;
6241 						case SPELLBOOK_CONFUSE:
6242 							castSpell(uid, &spell_confuse, true, false);
6243 							break;
6244 						case SPELLBOOK_SLOW:
6245 							castSpell(uid, &spell_slow, true, false);
6246 							break;
6247 						case SPELLBOOK_DIG:
6248 							castSpell(uid, &spell_dig, true, false);
6249 							break;
6250 						case SPELLBOOK_STONEBLOOD:
6251 							castSpell(uid, &spell_stoneblood, true, false);
6252 							break;
6253 						case SPELLBOOK_BLEED:
6254 							castSpell(uid, &spell_bleed, true, false);
6255 							break;
6256 						case SPELLBOOK_SUMMON:
6257 							castSpell(uid, &spell_summon, true, false);
6258 							break;
6259 						case SPELLBOOK_ACID_SPRAY:
6260 							castSpell(uid, &spell_acidSpray, true, false);
6261 							break;
6262 						case SPELLBOOK_STEAL_WEAPON:
6263 							castSpell(uid, &spell_stealWeapon, true, false);
6264 							break;
6265 						case SPELLBOOK_DRAIN_SOUL:
6266 							castSpell(uid, &spell_drainSoul, true, false);
6267 							break;
6268 						case SPELLBOOK_VAMPIRIC_AURA:
6269 							castSpell(uid, &spell_vampiricAura, true, false);
6270 							break;
6271 						case SPELLBOOK_CHARM_MONSTER:
6272 							castSpell(uid, &spell_charmMonster, true, false);
6273 							break;
6274 						case SPELLBOOK_POISON:
6275 							castSpell(uid, &spell_poison, true, false);
6276 							break;
6277 						case SPELLBOOK_SPRAY_WEB:
6278 							castSpell(uid, &spell_sprayWeb, true, false);
6279 							break;
6280 						case SPELLBOOK_SPEED:
6281 							castSpell(uid, &spell_speed, true, false);
6282 							break;
6283 						case SPELLBOOK_HEALING:
6284 							castSpell(uid, &spell_healing, true, false);
6285 							break;
6286 						case SPELLBOOK_EXTRAHEALING:
6287 							castSpell(uid, &spell_extrahealing, true, false);
6288 							break;
6289 						case SPELLBOOK_TROLLS_BLOOD:
6290 							castSpell(uid, &spell_trollsBlood, true, false);
6291 							break;
6292 						case SPELLBOOK_REFLECT_MAGIC:
6293 							castSpell(uid, &spell_reflectMagic, true, false);
6294 							break;
6295 						case SPELLBOOK_DASH:
6296 							castSpell(uid, &spell_dash, true, false);
6297 							break;
6298 						case SPELLBOOK_FEAR:
6299 							castSpell(uid, &spell_fear, true, false);
6300 							break;
6301 						//case SPELLBOOK_REFLECT_MAGIC: //TODO: Test monster support. Maybe better to just use a special ability that directly casts the spell.
6302 						//castSpell(uid, &spell_reflectMagic, true, false)
6303 						//break;
6304 						default:
6305 							break;
6306 					}
6307 
6308 					// DEPRECATED!!
6309 					/*if( myStats->MP>0 ) {
6310 					castMagic(my);
6311 
6312 					// spells deplete MP
6313 					myStats->MP--;
6314 					if( multiplayer==SERVER && player!=clientnum ) {
6315 					strcpy((char *)net_packet->data,"UPMP");
6316 					SDLNet_Write32((Uint32)myStats->MP,&net_packet->data[4]);
6317 					net_packet->address.host = net_clients[player-1].host;
6318 					net_packet->address.port = net_clients[player-1].port;
6319 					net_packet->len = 8;
6320 					sendPacketSafe(net_sock, -1, net_packet, player-1);
6321 					}
6322 					} else {
6323 					messagePlayer(player,"You lack the energy to cast magic!");
6324 					}*/
6325 				}
6326 				return;
6327 			}
6328 
6329 			// ranged weapons (bows)
6330 			else if ( isRangedWeapon(*myStats->weapon) )
6331 			{
6332 				// damage weapon if applicable
6333 				int bowDegradeChance = 50;
6334 				if ( behavior == &actPlayer )
6335 				{
6336 					bowDegradeChance += (stats[skill[2]]->PROFICIENCIES[PRO_RANGED] / 20) * 10;
6337 				}
6338 				if ( myStats->type == GOBLIN )
6339 				{
6340 					bowDegradeChance += 20;
6341 					if ( myStats->PROFICIENCIES[PRO_RANGED] < SKILL_LEVEL_LEGENDARY )
6342 					{
6343 						bowDegradeChance = std::min(bowDegradeChance, 90);
6344 					}
6345 				}
6346 				if ( bowDegradeChance < 100 && rand() % bowDegradeChance == 0 && myStats->weapon->type != ARTIFACT_BOW )
6347 				{
6348 					if ( myStats->weapon != NULL )
6349 					{
6350 						if ( player == clientnum )
6351 						{
6352 							if ( myStats->weapon->count > 1 )
6353 							{
6354 								newItem(myStats->weapon->type, myStats->weapon->status, myStats->weapon->beatitude, myStats->weapon->count - 1, myStats->weapon->appearance, myStats->weapon->identified, &myStats->inventory);
6355 							}
6356 						}
6357 						myStats->weapon->count = 1;
6358 						myStats->weapon->status = static_cast<Status>(myStats->weapon->status - 1);
6359 						if ( myStats->weapon->status != BROKEN )
6360 						{
6361 							messagePlayer(player, language[661], myStats->weapon->getName());
6362 						}
6363 						else
6364 						{
6365 							playSoundEntity(this, 76, 64);
6366 							messagePlayer(player, language[662], myStats->weapon->getName());
6367 						}
6368 						if ( player > 0 && multiplayer == SERVER )
6369 						{
6370 							strcpy((char*)net_packet->data, "ARMR");
6371 							net_packet->data[4] = 5;
6372 							net_packet->data[5] = myStats->weapon->status;
6373 							net_packet->address.host = net_clients[player - 1].host;
6374 							net_packet->address.port = net_clients[player - 1].port;
6375 							net_packet->len = 6;
6376 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
6377 						}
6378 					}
6379 				}
6380 				Entity* entity = nullptr;
6381 				if ( myStats->weapon->type == SLING )
6382 				{
6383 					entity = newEntity(78, 1, map.entities, nullptr); // rock
6384 					playSoundEntity(this, 239 + rand() % 3, 96);
6385 				}
6386 				else if ( myStats->weapon->type == CROSSBOW || myStats->weapon->type == HEAVY_CROSSBOW )
6387 				{
6388 					entity = newEntity(167, 1, map.entities, nullptr); // bolt
6389 					if ( myStats->weapon->type == HEAVY_CROSSBOW )
6390 					{
6391 						playSoundEntity(this, 411 + rand() % 3, 128);
6392 						if ( this->behavior == &actPlayer && this->skill[2] > 0 )
6393 						{
6394 							this->setEffect(EFF_KNOCKBACK, true, 30, false);
6395 						}
6396 					}
6397 					else
6398 					{
6399 						playSoundEntity(this, 239 + rand() % 3, 96);
6400 					}
6401 				}
6402 				else
6403 				{
6404 					entity = newEntity(166, 1, map.entities, nullptr); // arrow
6405 					playSoundEntity(this, 239 + rand() % 3, 96);
6406 				}
6407 				if ( !entity )
6408 				{
6409 					return;
6410 				}
6411 				entity->parent = uid;
6412 				entity->x = x;
6413 				entity->y = y;
6414 				entity->z = z;
6415 				if ( myStats->type == SENTRYBOT )
6416 				{
6417 					entity->z -= 1;
6418 				}
6419 				entity->yaw = yaw;
6420 				entity->sizex = 1;
6421 				entity->sizey = 1;
6422 				entity->behavior = &actArrow;
6423 				entity->flags[UPDATENEEDED] = true;
6424 				entity->flags[PASSABLE] = true;
6425 
6426 				// set properties of the arrow.
6427 				if ( pose == MONSTER_POSE_RANGED_SHOOT2 && myStats->weapon->type == ARTIFACT_BOW )
6428 				{
6429 					entity->setRangedProjectileAttack(*this, *myStats, QUIVER_SILVER + rand() % 7);
6430 				}
6431 				else
6432 				{
6433 					entity->setRangedProjectileAttack(*this, *myStats);
6434 				}
6435 
6436 				if ( entity->arrowQuiverType != 0 && myStats->shield && itemTypeIsQuiver(myStats->shield->type) )
6437 				{
6438 					//TODO: Refactor this so that we don't have to copy paste this check a million times whenever some-one uses up an item.
6439 					if ( behavior == &actPlayer && pose != MONSTER_POSE_RANGED_SHOOT2 )
6440 					{
6441 						myStats->shield->count--;
6442 						if ( myStats->shield->count <= 0 )
6443 						{
6444 							if ( myStats->shield->node )
6445 							{
6446 								list_RemoveNode(myStats->shield->node);
6447 							}
6448 							else
6449 							{
6450 								free(myStats->shield);
6451 							}
6452 							myStats->shield = nullptr;
6453 						}
6454 					}
6455 				}
6456 				return;
6457 			}
6458 
6459 			// potions & gems (throwing), and thrown weapons
6460 			if ( itemCategory(myStats->weapon) == POTION
6461 				|| itemCategory(myStats->weapon) == GEM
6462 				|| itemCategory(myStats->weapon) == THROWN
6463 				|| myStats->weapon->type == FOOD_CREAMPIE
6464 				|| itemIsThrowableTinkerTool(myStats->weapon) )
6465 			{
6466 				bool drankPotion = false;
6467 				if ( behavior == &actMonster && myStats->type == GOATMAN && itemCategory(myStats->weapon) == POTION )
6468 				{
6469 					//Goatmen chug potions & then toss them at you.
6470 					if ( myStats->weapon->type == POTION_BOOZE && !myStats->EFFECTS[EFF_DRUNK] )
6471 					{
6472 						item_PotionBooze(myStats->weapon, this, this, false);
6473 						drankPotion = true;
6474 					}
6475 					else if ( myStats->weapon->type == POTION_HEALING )
6476 					{
6477 						item_PotionHealing(myStats->weapon, this, this, false);
6478 						drankPotion = true;
6479 					}
6480 					else if ( myStats->weapon->type == POTION_EXTRAHEALING )
6481 					{
6482 						item_PotionExtraHealing(myStats->weapon, this, this, false);
6483 						drankPotion = true;
6484 					}
6485 				}
6486 
6487 				if ( myStats->weapon->type == BOOMERANG )
6488 				{
6489 					playSoundEntity(this, 75, 64);
6490 					//playSoundEntity(this, 427 + rand() % 4, 128);
6491 
6492 				}
6493 				else
6494 				{
6495 					playSoundEntity(this, 75, 64);
6496 				}
6497 				Entity* entity = nullptr;
6498 				if ( drankPotion )
6499 				{
6500 					Item* emptyBottle = newItem(POTION_EMPTY, myStats->weapon->status, myStats->weapon->beatitude, 1, myStats->weapon->appearance, myStats->weapon->appearance, nullptr);
6501 					entity = newEntity(itemModel(emptyBottle), 1, map.entities, nullptr); // thrown item
6502 					entity->parent = uid;
6503 					entity->x = x;
6504 					entity->y = y;
6505 					entity->z = z;
6506 					entity->yaw = yaw;
6507 					entity->sizex = 1;
6508 					entity->sizey = 1;
6509 					entity->behavior = &actThrown;
6510 					entity->flags[UPDATENEEDED] = true;
6511 					entity->flags[PASSABLE] = true;
6512 					entity->skill[10] = emptyBottle->type;
6513 					entity->skill[11] = emptyBottle->status;
6514 					entity->skill[12] = emptyBottle->beatitude;
6515 					entity->skill[13] = 1;
6516 					entity->skill[14] = emptyBottle->appearance;
6517 					entity->skill[15] = emptyBottle->identified;
6518 				}
6519 				else
6520 				{
6521 					entity = newEntity(itemModel(myStats->weapon), 1, map.entities, nullptr); // thrown item
6522 					entity->parent = uid;
6523 					entity->x = x;
6524 					entity->y = y;
6525 					entity->z = z;
6526 					entity->yaw = yaw;
6527 					entity->sizex = 1;
6528 					entity->sizey = 1;
6529 					entity->behavior = &actThrown;
6530 					entity->flags[UPDATENEEDED] = true;
6531 					entity->flags[PASSABLE] = true;
6532 					entity->skill[10] = myStats->weapon->type;
6533 					entity->skill[11] = myStats->weapon->status;
6534 					entity->skill[12] = myStats->weapon->beatitude;
6535 					entity->skill[13] = 1;
6536 					entity->skill[14] = myStats->weapon->appearance;
6537 					entity->skill[15] = myStats->weapon->identified;
6538 				}
6539 
6540 				if ( itemCategory(myStats->weapon) == THROWN )
6541 				{
6542 					real_t speed = 5.f;
6543 					real_t normalisedCharge = (charge * 1.5 / MAXCHARGE); // 0-1.5
6544 					if ( myStats->weapon->type == BOOMERANG )
6545 					{
6546 						speed = 3.75 + normalisedCharge; //3.75
6547 					}
6548 					else
6549 					{
6550 						speed = 5.f + normalisedCharge;
6551 					}
6552 
6553 					// thrown items have slightly faster velocities
6554 					if ( (myStats->weapon->type == STEEL_CHAKRAM || myStats->weapon->type == CRYSTAL_SHURIKEN) )
6555 					{
6556 						if ( this->behavior == &actPlayer )
6557 						{
6558 							// todo: change velocity of chakram/shuriken?
6559 							entity->vel_x = speed * cos(players[player]->entity->yaw);
6560 							entity->vel_y = speed * sin(players[player]->entity->yaw);
6561 							entity->vel_z = -.3;
6562 						}
6563 						else if ( this->behavior == &actMonster )
6564 						{
6565 							// todo: change velocity of chakram/shuriken?
6566 							entity->vel_x = 6 * cos(this->yaw);
6567 							entity->vel_y = 6 * sin(this->yaw);
6568 							entity->vel_z = -.3;
6569 						}
6570 					}
6571 					else if ( myStats->weapon->type == BOOMERANG )
6572 					{
6573 						entity->sprite = 977;
6574 						entity->pitch = PI;
6575 						entity->yaw -= PI / 2;
6576 						if ( this->behavior == &actPlayer )
6577 						{
6578 							entity->vel_x = speed * cos(players[player]->entity->yaw);
6579 							entity->vel_y = speed * sin(players[player]->entity->yaw);
6580 							entity->vel_z = -.1;
6581 						}
6582 						else if ( this->behavior == &actMonster )
6583 						{
6584 							entity->vel_x = 6 * cos(this->yaw);
6585 							entity->vel_y = 6 * sin(this->yaw);
6586 							entity->vel_z = -.1;
6587 						}
6588 					}
6589 					else
6590 					{
6591 						if ( this->behavior == &actPlayer )
6592 						{
6593 							entity->vel_x = speed * cos(players[player]->entity->yaw);
6594 							entity->vel_y = speed * sin(players[player]->entity->yaw);
6595 							entity->vel_z = -.3;
6596 						}
6597 						else if ( this->behavior == &actMonster )
6598 						{
6599 							entity->vel_x = 6 * cos(this->yaw);
6600 							entity->vel_y = 6 * sin(this->yaw);
6601 							entity->vel_z = -.3;
6602 						}
6603 					}
6604 					entity->thrownProjectilePower = this->getThrownAttack();
6605 					if ( behavior == &actPlayer )
6606 					{
6607 						entity->thrownProjectileCharge = normalisedCharge * 10;
6608 					}
6609 				}
6610 				else if ( itemIsThrowableTinkerTool(myStats->weapon) )
6611 				{
6612 					real_t normalisedCharge = (charge * 0.5);
6613 					normalisedCharge /= MAXCHARGE;
6614 					entity->sizex = 4;
6615 					entity->sizey = 4;
6616 					if ( myStats->weapon->type >= TOOL_BOMB && myStats->weapon->type <= TOOL_TELEPORT_BOMB )
6617 					{
6618 						entity->sizex = 2;
6619 						entity->sizey = 2;
6620 					}
6621 					if ( behavior == &actPlayer )
6622 					{
6623 						entity->vel_x = (1.f + normalisedCharge) * cos(players[player]->entity->yaw);
6624 						entity->vel_y = (1.f + normalisedCharge) * sin(players[player]->entity->yaw);
6625 					}
6626 					entity->vel_z = -.3;
6627 					entity->roll -= (PI / 2 - 0.1 + (rand() % 10) * 0.02);
6628 					if ( myStats->type == GYROBOT )
6629 					{
6630 						entity->vel_x = 0.0;
6631 						entity->vel_y = 0.0;
6632 						if ( monsterAllyGetPlayerLeader() )
6633 						{
6634 							entity->parent = monsterAllyGetPlayerLeader()->getUID();
6635 						}
6636 					}
6637 				}
6638 				else
6639 				{
6640 					real_t speed = 5.f;
6641 					if ( itemCategory(myStats->weapon) == GEM )
6642 					{
6643 						real_t normalisedCharge = (charge * 1.5 / MAXCHARGE); // 0-1.5
6644 						speed = 3.f + normalisedCharge;
6645 						if ( behavior == &actPlayer )
6646 						{
6647 							entity->thrownProjectileCharge = normalisedCharge * 10;
6648 						}
6649 					}
6650 					entity->thrownProjectilePower = this->getThrownAttack();
6651 					if ( this->behavior == &actPlayer )
6652 					{
6653 						entity->vel_x = speed * cos(players[player]->entity->yaw);
6654 						entity->vel_y = speed * sin(players[player]->entity->yaw);
6655 						entity->vel_z = -.5;
6656 					}
6657 					else if ( this->behavior == &actMonster )
6658 					{
6659 						entity->vel_x = speed * cos(this->yaw);
6660 						entity->vel_y = speed * sin(this->yaw);
6661 						entity->vel_z = -.5;
6662 					}
6663 				}
6664 
6665 				//TODO: Refactor this so that we don't have to copy paste this check a million times whenever some-one uses up an item.
6666 				myStats->weapon->count--;
6667 				if ( myStats->weapon->count <= 0 )
6668 				{
6669 					if ( myStats->weapon->node )
6670 					{
6671 						list_RemoveNode(myStats->weapon->node);
6672 					}
6673 					else
6674 					{
6675 						free(myStats->weapon);
6676 					}
6677 					myStats->weapon = nullptr;
6678 				}
6679 				return;
6680 			}
6681 		}
6682 		bool whip = myStats->weapon && myStats->weapon->type == TOOL_WHIP;
6683 		// normal attacks
6684 		if ( target == nullptr )
6685 		{
6686 			if ( whip )
6687 			{
6688 				dist = lineTrace(this, x, y, yaw, STRIKERANGE * 1.5, 0, false);
6689 				playSoundEntity(this, 23 + rand() % 5, 128); // whoosh noise
6690 			}
6691 			else
6692 			{
6693 				playSoundEntity(this, 23 + rand() % 5, 128); // whoosh noise
6694 				dist = lineTrace(this, x, y, yaw, STRIKERANGE, 0, false);
6695 			}
6696 		}
6697 		else
6698 		{
6699 			hit.entity = target;
6700 		}
6701 
6702 		if ( hit.entity != nullptr )
6703 		{
6704 			if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
6705 			{
6706 				// test for friendly fire
6707 				if ( checkFriend(hit.entity) )
6708 				{
6709 					return;
6710 				}
6711 			}
6712 			else if ( (myStats->type == LICH_FIRE && hit.entity->getRace() == LICH_ICE)
6713 				|| (myStats->type == LICH_ICE && hit.entity->getRace() == LICH_FIRE) )
6714 			{
6715 				// friendship <3
6716 				return;
6717 			}
6718 
6719 			Sint32 previousMonsterState = -1;
6720 
6721 			if ( hit.entity->behavior == &actBoulder )
6722 			{
6723 				if ( myStats->weapon != nullptr || pose == PLAYER_POSE_GOLEM_SMASH )
6724 				{
6725 					if ( (myStats->weapon && myStats->weapon->type == TOOL_PICKAXE) || pose == PLAYER_POSE_GOLEM_SMASH )
6726 					{
6727 						// spawn several rock items
6728 						if ( pose == PLAYER_POSE_GOLEM_SMASH )
6729 						{
6730 							createParticleRock(hit.entity);
6731 						}
6732 						else
6733 						{
6734 							int i = 8 + rand() % 4;
6735 							int c;
6736 							for ( c = 0; c < i; c++ )
6737 							{
6738 								Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock/item entity.
6739 								entity->flags[INVISIBLE] = true;
6740 								entity->flags[UPDATENEEDED] = true;
6741 								entity->x = hit.entity->x - 4 + rand() % 8;
6742 								entity->y = hit.entity->y - 4 + rand() % 8;
6743 								entity->z = -6 + rand() % 12;
6744 								entity->sizex = 4;
6745 								entity->sizey = 4;
6746 								entity->yaw = rand() % 360 * PI / 180;
6747 								entity->vel_x = (rand() % 20 - 10) / 10.0;
6748 								entity->vel_y = (rand() % 20 - 10) / 10.0;
6749 								entity->vel_z = -.25 - (rand() % 5) / 10.0;
6750 								entity->flags[PASSABLE] = true;
6751 								entity->behavior = &actItem;
6752 								entity->flags[USERFLAG1] = true; // no collision: helps performance
6753 								entity->skill[10] = GEM_ROCK;    // type
6754 								entity->skill[11] = WORN;        // status
6755 								entity->skill[12] = 0;           // beatitude
6756 								entity->skill[13] = 1;           // count
6757 								entity->skill[14] = 0;           // appearance
6758 								entity->skill[15] = 1;			 // identified
6759 							}
6760 						}
6761 
6762 						double ox = hit.entity->x;
6763 						double oy = hit.entity->y;
6764 
6765 						if ( player >= 0 && (abs(hit.entity->vel_x) > 0.01 || abs(hit.entity->vel_y) > 0.01) )
6766 						{
6767 							// boulder rolling, check if rolling towards player.
6768 							bool lastResort = false;
6769 							int boulderDirection = 0;
6770 							if ( abs(hit.entity->yaw - (PI / 2)) < 0.1 )
6771 							{
6772 								boulderDirection = 1;
6773 							}
6774 							else if ( abs(hit.entity->yaw - (PI)) < 0.1 )
6775 							{
6776 								boulderDirection = 2;
6777 							}
6778 							else if ( abs(hit.entity->yaw - (3 * PI / 2)) < 0.1 )
6779 							{
6780 								boulderDirection = 3;
6781 							}
6782 
6783 							switch ( boulderDirection )
6784 							{
6785 								case 0: // east
6786 									if ( static_cast<int>(oy / 16) == static_cast<int>(y / 16)
6787 										&& static_cast<int>(ox / 16) <= static_cast<int>(x / 16) )
6788 									{
6789 										lastResort = true;
6790 									}
6791 									break;
6792 								case 1: // south
6793 									if ( static_cast<int>(ox / 16) == static_cast<int>(x / 16)
6794 										&& static_cast<int>(oy / 16) <= static_cast<int>(y / 16) )
6795 									{
6796 										lastResort = true;
6797 									}
6798 									break;
6799 								case 2: // west
6800 									if ( static_cast<int>(oy / 16) == static_cast<int>(y / 16)
6801 										&& static_cast<int>(ox / 16) >= static_cast<int>(x / 16) )
6802 									{
6803 										lastResort = true;
6804 									}
6805 									break;
6806 								case 3: // north
6807 									if ( static_cast<int>(ox / 16) == static_cast<int>(x / 16)
6808 										&& static_cast<int>(oy / 16) >= static_cast<int>(y / 16) )
6809 									{
6810 										lastResort = true;
6811 									}
6812 									break;
6813 								default:
6814 									break;
6815 							}
6816 							if ( lastResort )
6817 							{
6818 								steamAchievementClient(player, "BARONY_ACH_LAST_RESORT");
6819 							}
6820 						}
6821 
6822 						boulderLavaOrArcaneOnDestroy(hit.entity, hit.entity->sprite, nullptr);
6823 
6824 						// destroy the boulder
6825 						playSoundEntity(hit.entity, 67, 128);
6826 						list_RemoveNode(hit.entity->mynode);
6827 						messagePlayer(player, language[663]);
6828 						if ( myStats->weapon && rand() % 2 && pose != PLAYER_POSE_GOLEM_SMASH )
6829 						{
6830 							myStats->weapon->status = static_cast<Status>(myStats->weapon->status - 1);
6831 							if ( myStats->weapon->status < BROKEN )
6832 							{
6833 								myStats->weapon->status = BROKEN; // bounds checking.
6834 							}
6835 							if ( myStats->weapon->status == BROKEN )
6836 							{
6837 								messagePlayer(player, language[664]);
6838 								playSoundEntity(this, 76, 64);
6839 							}
6840 							else
6841 							{
6842 								messagePlayer(player, language[665]);
6843 							}
6844 							if ( player > 0 && multiplayer == SERVER )
6845 							{
6846 								strcpy((char*)net_packet->data, "ARMR");
6847 								net_packet->data[4] = 5;
6848 								net_packet->data[5] = myStats->weapon->status;
6849 								net_packet->address.host = net_clients[player - 1].host;
6850 								net_packet->address.port = net_clients[player - 1].port;
6851 								net_packet->len = 6;
6852 								sendPacketSafe(net_sock, -1, net_packet, player - 1);
6853 							}
6854 						}
6855 
6856 						// on sokoban, destroying boulders spawns scorpions
6857 						if ( !strcmp(map.name, "Sokoban") )
6858 						{
6859 							Entity* monster = nullptr;
6860 							if ( rand() % 2 == 0 )
6861 							{
6862 								monster = summonMonster(INSECTOID, ox, oy);
6863 							}
6864 							else
6865 							{
6866 								monster = summonMonster(SCORPION, ox, oy);
6867 							}
6868 							if ( monster )
6869 							{
6870 								int c;
6871 								for ( c = 0; c < MAXPLAYERS; c++ )
6872 								{
6873 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
6874 									messagePlayerColor(c, color, language[406]);
6875 								}
6876 							}
6877 							boulderSokobanOnDestroy(false);
6878 						}
6879 					}
6880 					else
6881 					{
6882 						spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0);
6883 					}
6884 				}
6885 				else
6886 				{
6887 					//spawnBang(hit.x - cos(my->yaw)*2,hit.y - sin(my->yaw)*2,0);
6888 					playSoundPos(hit.x, hit.y, 183, 64);
6889 				}
6890 			}
6891 			else if ( hit.entity->behavior == &actMonster )
6892 			{
6893 				previousMonsterState = hit.entity->monsterState;
6894 				if ( hit.entity->children.first != nullptr )
6895 				{
6896 					if ( hit.entity->children.first->next != nullptr )
6897 					{
6898 						hitstats = (Stat*)hit.entity->children.first->next->element;
6899 
6900 						bool alertTarget = true;
6901 						if ( behavior == &actMonster && monsterAllyIndex != -1 && hit.entity->monsterAllyIndex != -1 )
6902 						{
6903 							// if we're both allies of players, don't alert the hit target.
6904 							alertTarget = false;
6905 						}
6906 
6907 						// alert the monster!
6908 						if ( hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) )
6909 						{
6910 							if ( alertTarget )
6911 							{
6912 								hit.entity->monsterAcquireAttackTarget(*this, MONSTER_STATE_PATH, true);
6913 							}
6914 						}
6915 
6916 						// alert other monsters too
6917 						Entity* ohitentity = hit.entity;
6918 						for ( node = map.creatures->first; node != nullptr && alertTarget; node = node->next ) //Only searching for monsters, so don't iterate full map.entities.
6919 						{
6920 							Entity* entity = (Entity*)node->element;
6921 							if ( entity && entity->behavior == &actMonster && entity != ohitentity )
6922 							{
6923 								Stat* buddystats = entity->getStats();
6924 								if ( buddystats != nullptr )
6925 								{
6926 									if ( entity->checkFriend(hit.entity) )
6927 									{
6928 										if ( entity->monsterState == MONSTER_STATE_WAIT )
6929 										{
6930 											tangent = atan2(entity->y - ohitentity->y, entity->x - ohitentity->x);
6931 											lineTrace(ohitentity, ohitentity->x, ohitentity->y, tangent, 1024, 0, false);
6932 											if ( hit.entity == entity )
6933 											{
6934 												Entity* attackTarget = uidToEntity(uid);
6935 												if ( attackTarget )
6936 												{
6937 													entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
6938 												}
6939 											}
6940 										}
6941 									}
6942 								}
6943 							}
6944 						}
6945 						hit.entity = ohitentity;
6946 					}
6947 				}
6948 			}
6949 			else if ( hit.entity->behavior == &actPlayer )
6950 			{
6951 				hitstats = stats[hit.entity->skill[2]];
6952 				playerhit = hit.entity->skill[2];
6953 
6954 				bool alertAllies = true;
6955 				if ( behavior == &actMonster && monsterAllyIndex != -1 )
6956 				{
6957 					// if I'm a player ally, don't alert other allies.
6958 					alertAllies = false;
6959 				}
6960 
6961 				// alert the player's followers!
6962 				//Maybe should send a signal to each follower, with some kind of attached priority, which determines if they change their target to bumrush the player's assailant.
6963 				for ( node = hitstats->FOLLOWERS.first; node != nullptr && alertAllies; node = node->next )
6964 				{
6965 					Uint32* c = (Uint32*)node->element;
6966 					Entity* entity = nullptr;
6967 					if ( c )
6968 					{
6969 						entity = uidToEntity(*c);
6970 					}
6971 					Entity* ohitentity = hit.entity;
6972 					if ( entity )
6973 					{
6974 						Stat* buddystats = entity->getStats();
6975 						if ( buddystats != nullptr )
6976 						{
6977 							if ( entity->monsterState == MONSTER_STATE_WAIT || (entity->monsterState == MONSTER_STATE_HUNT && entity->monsterTarget != uid) ) // monster is waiting or hunting
6978 							{
6979 								if ( entity->monsterAllyState == ALLY_STATE_DEFEND )
6980 								{
6981 									// monster is defending, make em stay put unless line of sight.
6982 									tangent = atan2(entity->y - ohitentity->y, entity->x - ohitentity->x);
6983 									lineTrace(ohitentity, ohitentity->x, ohitentity->y, tangent, 1024, 0, false);
6984 									if ( hit.entity == entity )
6985 									{
6986 										Entity* attackTarget = uidToEntity(uid);
6987 										if ( attackTarget )
6988 										{
6989 											entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
6990 										}
6991 									}
6992 								}
6993 								else
6994 								{
6995 									Entity* attackTarget = uidToEntity(uid);
6996 									if ( attackTarget )
6997 									{
6998 										entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
6999 									}
7000 								}
7001 							}
7002 						}
7003 					}
7004 					hit.entity = ohitentity;
7005 				}
7006 			}
7007 			else if ( hit.entity->behavior == &actDoor || hit.entity->behavior == &::actFurniture || hit.entity->behavior == &::actChest )
7008 			{
7009 				int axe = 0;
7010 				if ( myStats->weapon && !shapeshifted )
7011 				{
7012 					if ( myStats->weapon->type == BRONZE_AXE || myStats->weapon->type == IRON_AXE || myStats->weapon->type == STEEL_AXE
7013 						|| myStats->weapon->type == CRYSTAL_BATTLEAXE )
7014 					{
7015 						axe = 1; // axes do extra damage to doors :)
7016 					}
7017 				}
7018 				else
7019 				{
7020 					axe = (myStats->PROFICIENCIES[PRO_UNARMED] / 20);
7021 					if ( myStats->PROFICIENCIES[PRO_UNARMED] >= SKILL_LEVEL_LEGENDARY )
7022 					{
7023 						axe = 7;
7024 					}
7025 					if ( charge > MAXCHARGE / 2 )
7026 					{
7027 						axe *= 3;
7028 					}
7029 				}
7030 				if ( pose == PLAYER_POSE_GOLEM_SMASH )
7031 				{
7032 					if ( hit.entity->behavior == &actDoor || hit.entity->behavior == &::actFurniture )
7033 					{
7034 						axe += 20;
7035 					}
7036 					else if ( hit.entity->behavior == &::actChest )
7037 					{
7038 						axe = std::min(axe + 50, 50);
7039 					}
7040 				}
7041 				if ( hit.entity->behavior != &::actChest )
7042 				{
7043 					if ( charge < MAXCHARGE / 2 )
7044 					{
7045 						hit.entity->skill[4] -= 1 + axe; // decrease door/furniture health
7046 					}
7047 					else
7048 					{
7049 						hit.entity->skill[4] -= 2 + axe; // decrease door/furniture health extra
7050 					}
7051 				}
7052 				else
7053 				{
7054 					if ( charge < MAXCHARGE / 2 )
7055 					{
7056 						hit.entity->skill[3] -= 1 + axe; // decrease chest health
7057 					}
7058 					else
7059 					{
7060 						hit.entity->skill[3] -= 2 + axe; // decrease chest health extra
7061 					}
7062 				}
7063 				if ( whip )
7064 				{
7065 					playSoundEntity(hit.entity, 407 + rand() % 3, 64);
7066 				}
7067 				else
7068 				{
7069 					playSoundEntity(hit.entity, 28, 64);
7070 				}
7071 				if ( (hit.entity->behavior != &::actChest && hit.entity->skill[4] > 0) || (hit.entity->behavior == &::actChest && hit.entity->skill[3] > 0) )
7072 				{
7073 					if ( hit.entity->behavior == &actDoor )
7074 					{
7075 						messagePlayer(player, language[666]);
7076 					}
7077 					else if ( hit.entity->behavior == &::actChest )
7078 					{
7079 						messagePlayer(player, language[667]);
7080 					}
7081 					else if ( hit.entity->behavior == &::actFurniture )
7082 					{
7083 						switch ( hit.entity->furnitureType )
7084 						{
7085 							case FURNITURE_CHAIR:
7086 								messagePlayer(player, language[669]);
7087 								break;
7088 							case FURNITURE_TABLE:
7089 								messagePlayer(player, language[668]);
7090 								break;
7091 							case FURNITURE_BED:
7092 								messagePlayer(player, language[2509], language[2505]);
7093 								break;
7094 							case FURNITURE_BUNKBED:
7095 								messagePlayer(player, language[2509], language[2506]);
7096 								break;
7097 							case FURNITURE_PODIUM:
7098 								messagePlayer(player, language[2509], language[2507]);
7099 								break;
7100 							default:
7101 								break;
7102 						}
7103 					}
7104 				}
7105 				else
7106 				{
7107 					hit.entity->skill[4] = 0;
7108 					if ( hit.entity->behavior == &actDoor )
7109 					{
7110 						messagePlayer(player, language[670]);
7111 						if ( !hit.entity->skill[0] )
7112 						{
7113 							hit.entity->skill[6] = (x > hit.entity->x);
7114 						}
7115 						else
7116 						{
7117 							hit.entity->skill[6] = (y < hit.entity->y);
7118 						}
7119 					}
7120 					else if ( hit.entity->behavior == &::actChest )
7121 					{
7122 						messagePlayer(player, language[671]);
7123 					}
7124 					else if ( hit.entity->behavior == &::actFurniture )
7125 					{
7126 						switch ( hit.entity->furnitureType )
7127 						{
7128 							case FURNITURE_CHAIR:
7129 								messagePlayer(player, language[673]);
7130 								break;
7131 							case FURNITURE_TABLE:
7132 								messagePlayer(player, language[672]);
7133 								break;
7134 							case FURNITURE_BED:
7135 								messagePlayer(player, language[2510], language[2505]);
7136 								break;
7137 							case FURNITURE_BUNKBED:
7138 								messagePlayer(player, language[2510], language[2506]);
7139 								break;
7140 							case FURNITURE_PODIUM:
7141 								messagePlayer(player, language[2510], language[2507]);
7142 								break;
7143 							default:
7144 								break;
7145 						}
7146 					}
7147 				}
7148 				if ( hit.entity->behavior == &actDoor )
7149 				{
7150 					updateEnemyBar(this, hit.entity, language[674], hit.entity->skill[4], hit.entity->skill[9]);
7151 				}
7152 				else if ( hit.entity->behavior == &::actChest )
7153 				{
7154 					updateEnemyBar(this, hit.entity, language[675], hit.entity->skill[3], hit.entity->skill[8]);
7155 				}
7156 				else if ( hit.entity->behavior == &::actFurniture )
7157 				{
7158 					switch ( hit.entity->furnitureType )
7159 					{
7160 						case FURNITURE_CHAIR:
7161 							updateEnemyBar(this, hit.entity, language[677], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
7162 							break;
7163 						case FURNITURE_TABLE:
7164 							updateEnemyBar(this, hit.entity, language[676], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
7165 							break;
7166 						case FURNITURE_BED:
7167 							updateEnemyBar(this, hit.entity, language[2505], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
7168 							break;
7169 						case FURNITURE_BUNKBED:
7170 							updateEnemyBar(this, hit.entity, language[2506], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
7171 							break;
7172 						case FURNITURE_PODIUM:
7173 							updateEnemyBar(this, hit.entity, language[2507], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
7174 							break;
7175 						default:
7176 							break;
7177 					}
7178 				}
7179 			}
7180 			else if ( hit.entity->behavior == &actSink )
7181 			{
7182 				if ( whip )
7183 				{
7184 					playSoundEntity(hit.entity, 407 + rand() % 3, 64);
7185 				}
7186 				else
7187 				{
7188 					playSoundEntity(hit.entity, 28, 64);
7189 				}
7190 				playSoundEntity(hit.entity, 140 + rand(), 64);
7191 				messagePlayer(player, language[678]);
7192 				if ( hit.entity->skill[0] > 0 )
7193 				{
7194 					hit.entity->skill[0]--; //Deplete one usage.
7195 
7196 											//50% chance spawn a slime.
7197 					if ( rand() % 2 == 0 )
7198 					{
7199 						// spawn slime
7200 						Entity* monster = summonMonster(SLIME, x, y);
7201 						if ( monster )
7202 						{
7203 							messagePlayer(player, language[582]);
7204 							Stat* monsterStats = monster->getStats();
7205 							monsterStats->LVL = 4;
7206 						}
7207 					}
7208 
7209 					if ( hit.entity->skill[0] == 0 )   //Depleted.
7210 					{
7211 						messagePlayer(player, language[585]); //TODO: Alert all players that see (or otherwise in range) it?
7212 						playSoundEntity(hit.entity, 132, 64);
7213 					}
7214 				}
7215 			}
7216 			else
7217 			{
7218 				if ( myStats->weapon && !shapeshifted && pose != PLAYER_POSE_GOLEM_SMASH )
7219 				{
7220 					// bang
7221 					spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0);
7222 				}
7223 				else
7224 				{
7225 					playSoundPos(hit.x, hit.y, 183, 64);
7226 				}
7227 			}
7228 
7229 			if ( hitstats != nullptr )
7230 			{
7231 				// hit chance
7232 				//int hitskill=5; // for unarmed combat
7233 
7234 				if ( behavior == &actPlayer )
7235 				{
7236 					if ( skill[2] != clientnum )
7237 					{
7238 						if ( achievementRangedMode[skill[2]] && !playerFailedRangedOnlyConduct[skill[2]] )
7239 						{
7240 							messagePlayer(skill[2], language[3923]); // prevent attack.
7241 							return;
7242 						}
7243 						if ( achievementRangedMode[skill[2]] )
7244 						{
7245 							messagePlayer(skill[2], language[3924]); // notify no longer eligible for achievement but still atk.
7246 						}
7247 						if ( !playerFailedRangedOnlyConduct[skill[2]] )
7248 						{
7249 							playerFailedRangedOnlyConduct[skill[2]] = true;
7250 							serverUpdatePlayerConduct(skill[2], CONDUCT_RANGED_ONLY, 0);
7251 						}
7252 					}
7253 					else if ( skill[2] == clientnum )
7254 					{
7255 						if ( achievementRangedMode[skill[2]] && conductGameChallenges[CONDUCT_RANGED_ONLY] )
7256 						{
7257 							messagePlayer(skill[2], language[3923]); // prevent attack.
7258 							return;
7259 						}
7260 						if ( achievementRangedMode[skill[2]] )
7261 						{
7262 							messagePlayer(skill[2], language[3924]); // notify no longer eligible for achievement but still atk.
7263 						}
7264 						conductGameChallenges[CONDUCT_RANGED_ONLY] = 0;
7265 					}
7266 				}
7267 
7268 				weaponskill = getWeaponSkill(myStats->weapon);
7269 				if ( behavior == &actMonster && weaponskill == PRO_UNARMED )
7270 				{
7271 					weaponskill = -1;
7272 				}
7273 				if ( shapeshifted || pose == PLAYER_POSE_GOLEM_SMASH )
7274 				{
7275 					weaponskill = PRO_UNARMED;
7276 				}
7277 
7278 				real_t weaponMultipliers = 0.0;
7279 				if ( weaponskill == PRO_UNARMED )
7280 				{
7281 					weaponMultipliers = hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_UNARMED);
7282 				}
7283 				else if ( weaponskill == PRO_RANGED )
7284 				{
7285 					weaponMultipliers = hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_RANGED);
7286 				}
7287 				else if ( weaponskill >= 0 )
7288 				{
7289 					DamageTableType dmgType = static_cast<DamageTableType>(weaponskill - PRO_SWORD);
7290 					weaponMultipliers = hit.entity->getDamageTableMultiplier(*hitstats, dmgType);
7291 				}
7292 
7293 				bool dyrnwynSmite = false;
7294 				bool gugnirProc = false;
7295 				if ( weaponskill == PRO_SWORD && myStats->weapon && myStats->weapon->type == ARTIFACT_SWORD )
7296 				{
7297 					switch ( hitstats->type )
7298 					{
7299 						case SKELETON:
7300 						case CREATURE_IMP:
7301 						case GHOUL:
7302 						case DEMON:
7303 						case SUCCUBUS:
7304 						case INCUBUS:
7305 						case VAMPIRE:
7306 						case LICH:
7307 						case LICH_ICE:
7308 						case LICH_FIRE:
7309 						case DEVIL:
7310 						{
7311 							// smite these creatures
7312 							real_t amount = 0.0;
7313 							real_t percent = getArtifactWeaponEffectChance(myStats->weapon->type, *myStats, &amount);
7314 							if ( rand() % 100 < static_cast<int>(percent) )
7315 							{
7316 								weaponMultipliers += amount;
7317 								dyrnwynSmite = true;
7318 								spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 981);
7319 								//playSoundEntity(hit.entity, 249, 64);
7320 							}
7321 							break;
7322 						}
7323 						default:
7324 							break;
7325 					}
7326 				}
7327 				/*if( weaponskill>=0 )
7328 				hitskill = myStats->PROFICIENCIES[weaponskill]/5;
7329 				c = rand()%20 + hitskill + (weaponskill==PRO_POLEARM);
7330 				bool hitsuccess=false;
7331 				if( myStats->weapon ) {
7332 				if( myStats->weapon->type == ARTIFACT_SPEAR ) {
7333 				hitsuccess=true; // Gungnir always lands a hit!
7334 				}
7335 				}
7336 				if( c > 10+std::min(std::max(-3,hit.entity->getDEX()-my->getDEX()),3) ) {
7337 				hitsuccess=true;
7338 				}
7339 				if( hitsuccess )*/
7340 				{
7341 					// calculate and perform damage to opponent
7342 					int damage = 0;
7343 					int damagePreMultiplier = 1;
7344 
7345 					if ( (myStats->type == CRYSTALGOLEM && pose == MONSTER_POSE_GOLEM_SMASH)
7346 						|| (myStats->type == LICH_FIRE && pose == 3) )
7347 					{
7348 						damagePreMultiplier = 2;
7349 					}
7350 					else if ( player >= 0 && pose == PLAYER_POSE_GOLEM_SMASH )
7351 					{
7352 						damagePreMultiplier = 2;
7353 					}
7354 
7355 					if ( weaponskill == PRO_UNARMED )
7356 					{
7357 						damage = std::max(0, (getAttack() * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats) - AC(hitstats)) * weaponMultipliers;
7358 					}
7359 					else if ( weaponskill == PRO_RANGED )
7360 					{
7361 						damage = std::max(0, (getAttack() * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats) - AC(hitstats)) * weaponMultipliers;
7362 					}
7363 					else if ( weaponskill >= 0 )
7364 					{
7365 						int enemyAC = AC(hitstats);
7366 						if ( weaponskill == PRO_POLEARM && myStats->weapon && myStats->weapon->type == ARTIFACT_SPEAR )
7367 						{
7368 							real_t amount = 0.f;
7369 							real_t percent = getArtifactWeaponEffectChance(ARTIFACT_SPEAR, *myStats, &amount);
7370 							if ( (rand() % 100 < static_cast<int>(percent)) )
7371 							{
7372 								enemyAC *= amount;
7373 								gugnirProc = true;
7374 							}
7375 						}
7376 						damage = std::max(0, (getAttack() * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats) - enemyAC) * weaponMultipliers;
7377 					}
7378 					else
7379 					{
7380 						damage = std::max(0, (getAttack() * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats) - AC(hitstats));
7381 					}
7382 					if ( weaponskill == PRO_AXE )
7383 					{
7384 						damage++;
7385 					}
7386 					if ( myStats->type == LICH_FIRE && !hitstats->defending )
7387 					{
7388 						if ( damage <= 8 )
7389 						{
7390 							damage += (8 - damage) + rand() % 9; // 8 - 16 minimum damage.
7391 						}
7392 					}
7393 					if ( behavior == &actMonster && myStats->EFFECTS[EFF_VAMPIRICAURA] )
7394 					{
7395 						damage += 5; // 5 bonus damage after reductions.
7396 					}
7397 
7398 					bool backstab = false;
7399 					bool flanking = false;
7400 					if ( player >= 0 && !monsterIsImmobileTurret(hit.entity, hitstats) )
7401 					{
7402 						real_t hitAngle = hit.entity->yawDifferenceFromPlayer(player);
7403 						if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc
7404 						{
7405 							int stealthCapstoneBonus = 1;
7406 							if ( skillCapstoneUnlockedEntity(PRO_STEALTH) )
7407 							{
7408 								stealthCapstoneBonus = 2;
7409 							}
7410 
7411 							if ( previousMonsterState == MONSTER_STATE_WAIT
7412 								|| previousMonsterState == MONSTER_STATE_PATH
7413 								|| (previousMonsterState == MONSTER_STATE_HUNT && uidToEntity(monsterTarget) == nullptr) )
7414 							{
7415 								// unaware monster, get backstab damage.
7416 								backstab = true;
7417 								damage += (stats[player]->PROFICIENCIES[PRO_STEALTH] / 20 + 2) * (2 * stealthCapstoneBonus);
7418 								if ( rand() % 4 > 0 )
7419 								{
7420 									this->increaseSkill(PRO_STEALTH);
7421 								}
7422 							}
7423 							else if ( rand() % 2 == 0 )
7424 							{
7425 								// monster currently engaged in some form of combat maneuver
7426 								// 1 in 2 chance to flank defenses.
7427 								flanking = true;
7428 								damage += (stats[player]->PROFICIENCIES[PRO_STEALTH] / 20 + 1) * (stealthCapstoneBonus);
7429 								if ( rand() % 20 == 0 )
7430 								{
7431 									this->increaseSkill(PRO_STEALTH);
7432 								}
7433 							}
7434 						}
7435 					}
7436 
7437 					bool gungnir = false;
7438 					if ( myStats->weapon )
7439 					{
7440 						if ( myStats->weapon->type == ARTIFACT_SPEAR )
7441 						{
7442 							gungnir = true;
7443 						}
7444 					}
7445 					if ( (weaponskill >= PRO_SWORD && weaponskill < PRO_SHIELD && !gungnir) || weaponskill == PRO_UNARMED || weaponskill == PRO_RANGED )
7446 					{
7447 						int chance = 0;
7448 						if ( weaponskill == PRO_POLEARM )
7449 						{
7450 							chance = (damage / 3) * (100 - myStats->PROFICIENCIES[weaponskill]) / 100.f;
7451 						}
7452 						else
7453 						{
7454 							chance = (damage / 2) * (100 - myStats->PROFICIENCIES[weaponskill]) / 100.f;
7455 						}
7456 						if ( chance > 0 )
7457 						{
7458 							damage = (damage - chance) + (rand() % chance) + 1;
7459 						}
7460 					}
7461 
7462 					int olddamage = damage;
7463 					damage *= std::max(charge, MAXCHARGE / 2) / ((double)(MAXCHARGE / 2));
7464 					bool parashuProc = false;
7465 					if ( myStats->weapon )
7466 					{
7467 						if ( myStats->weapon->type == ARTIFACT_AXE )
7468 						{
7469 							real_t amount = 0.0;
7470 							real_t percent = getArtifactWeaponEffectChance(myStats->weapon->type, *myStats, &amount);
7471 
7472 							if ( rand() % 100 < static_cast<int>(percent) )
7473 							{
7474 								damage *= amount; // Parashu sometimes multiplier damage
7475 								parashuProc = true;
7476 							}
7477 						}
7478 					}
7479 
7480 					if ( hitstats->type == DUMMYBOT )
7481 					{
7482 						// higher level dummy bots have damage cap limits on hit.
7483 						if ( myStats->type != MINOTAUR && myStats->type != LICH_FIRE )
7484 						{
7485 							if ( hitstats->LVL >= 10 )
7486 							{
7487 								damage = std::min(damage, 15);
7488 							}
7489 							else if ( hitstats->LVL >= 5 )
7490 							{
7491 								damage = std::min(damage, 30);
7492 							}
7493 						}
7494 					}
7495 
7496 					hit.entity->modHP(-damage); // do the damage
7497 					bool skillIncreased = false;
7498 					// skill increase
7499 					// can raise skills up to skill level 20 on dummybots...
7500 					bool doSkillIncrease = true;
7501 					if ( monsterIsImmobileTurret(hit.entity, hitstats) )
7502 					{
7503 						if ( hitstats->type == DUMMYBOT && hitstats->HP > 0 )
7504 						{
7505 							doSkillIncrease = true; // can train on dummybots.
7506 						}
7507 						else
7508 						{
7509 							doSkillIncrease = false; // no skill for killing/hurting other turrets.
7510 						}
7511 					}
7512 					if ( doSkillIncrease
7513 						&& ((weaponskill >= PRO_SWORD && weaponskill <= PRO_POLEARM) || weaponskill == PRO_UNARMED) )
7514 					{
7515 						if ( myStats->weapon &&
7516 							(myStats->weapon->type == CRYSTAL_BATTLEAXE
7517 								|| myStats->weapon->type == CRYSTAL_MACE
7518 								|| myStats->weapon->type == CRYSTAL_SWORD
7519 								|| myStats->weapon->type == CRYSTAL_SPEAR) )
7520 						{
7521 							int chance = 6;
7522 							bool notify = true;
7523 							if ( myStats->type == GOBLIN )
7524 							{
7525 								chance = 10;
7526 								notify = false;
7527 							}
7528 
7529 							if ( rand() % chance == 0 )
7530 							{
7531 								if ( hitstats->type != DUMMYBOT || (hitstats->type == DUMMYBOT && myStats->PROFICIENCIES[weaponskill] < 20) )
7532 								{
7533 									this->increaseSkill(weaponskill, notify);
7534 									skillIncreased = true;
7535 								}
7536 							}
7537 						}
7538 						else if ( hitstats->HP <= 0 )
7539 						{
7540 							if ( player >= 0 && weaponskill == PRO_UNARMED
7541 								&& stats[player]->type == GOATMAN
7542 								&& stats[player]->EFFECTS[EFF_DRUNK] )
7543 							{
7544 								steamStatisticUpdateClient(player, STEAM_STAT_BARFIGHT_CHAMP, STEAM_STAT_INT, 1);
7545 							}
7546 							int chance = 8;
7547 							bool notify = true;
7548 							if ( myStats->type == GOBLIN )
7549 							{
7550 								chance = 12;
7551 								notify = false;
7552 							}
7553 							if ( rand() % chance == 0 )
7554 							{
7555 								this->increaseSkill(weaponskill, notify);
7556 								skillIncreased = true;
7557 							}
7558 						}
7559 						else
7560 						{
7561 							int chance = 10;
7562 							bool notify = true;
7563 							if ( myStats->type == GOBLIN )
7564 							{
7565 								chance = 14;
7566 								notify = false;
7567 							}
7568 							if ( rand() % chance == 0 )
7569 							{
7570 								if ( hitstats->type != DUMMYBOT || (hitstats->type == DUMMYBOT && myStats->PROFICIENCIES[weaponskill] < 20) )
7571 								{
7572 									this->increaseSkill(weaponskill, notify);
7573 									skillIncreased = true;
7574 								}
7575 							}
7576 						}
7577 					}
7578 
7579 					if ( skillIncreased && myStats->type == GOBLIN )
7580 					{
7581 						// goblins level up all combat skills at once.
7582 						if ( weaponskill != PRO_SWORD )
7583 						{
7584 							this->increaseSkill(PRO_SWORD, false);
7585 						}
7586 						if ( weaponskill != PRO_MACE )
7587 						{
7588 							this->increaseSkill(PRO_MACE, false);
7589 						}
7590 						if ( weaponskill != PRO_AXE )
7591 						{
7592 							this->increaseSkill(PRO_AXE, false);
7593 						}
7594 						if ( weaponskill != PRO_POLEARM )
7595 						{
7596 							this->increaseSkill(PRO_POLEARM, false);
7597 						}
7598 						if ( weaponskill != PRO_UNARMED )
7599 						{
7600 							this->increaseSkill(PRO_UNARMED, false);
7601 						}
7602 						if ( player >= 0 )
7603 						{
7604 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
7605 							messagePlayerColor(player, color, language[3446]);
7606 						}
7607 					}
7608 
7609 					// write the obituary
7610 					if ( hit.entity != this )
7611 					{
7612 						killedByMonsterObituary(hit.entity);
7613 					}
7614 
7615 					// damage weapon if applicable
7616 
7617 					bool isWeakWeapon = false;
7618 					bool artifactWeapon = false;
7619 					bool degradeWeapon = false;
7620 					ItemType weaponType = static_cast<ItemType>(WOODEN_SHIELD);
7621 					bool hasMeleeGloves = false;
7622 					if ( myStats->gloves && !shapeshifted )
7623 					{
7624 						switch ( myStats->gloves->type )
7625 						{
7626 							case BRASS_KNUCKLES:
7627 							case IRON_KNUCKLES:
7628 							case SPIKED_GAUNTLETS:
7629 								hasMeleeGloves = true;
7630 								break;
7631 							default:
7632 								break;
7633 						}
7634 					}
7635 
7636 					Item** weaponToBreak = nullptr;
7637 
7638 					if ( myStats->weapon )
7639 					{
7640 						weaponToBreak = &myStats->weapon;
7641 					}
7642 					else if ( hasMeleeGloves )
7643 					{
7644 						weaponToBreak = &myStats->gloves;
7645 					}
7646 
7647 					if ( weaponToBreak != nullptr && !shapeshifted )
7648 					{
7649 						weaponType = (*weaponToBreak)->type;
7650 						if ( weaponType == ARTIFACT_AXE || weaponType == ARTIFACT_MACE || weaponType == ARTIFACT_SPEAR || weaponType == ARTIFACT_SWORD )
7651 						{
7652 							artifactWeapon = true;
7653 						}
7654 						else if ( weaponType == CRYSTAL_BATTLEAXE || weaponType == CRYSTAL_MACE || weaponType == CRYSTAL_SWORD || weaponType == CRYSTAL_SPEAR )
7655 						{
7656 							// crystal weapons degrade faster.
7657 							isWeakWeapon = true;
7658 						}
7659 
7660 						if ( myStats->type == GOBLIN )
7661 						{
7662 							isWeakWeapon = false;
7663 						}
7664 
7665 						if ( !artifactWeapon )
7666 						{
7667 							// normal weapons chance to not degrade 75% chance on 0 dmg, else 98%
7668 							int degradeOnZeroDMG = 4 + (myStats->type == GOBLIN ? 4 : 0);
7669 							int degradeOnNormalDMG = 50 + (myStats->type == GOBLIN ? 20 : 0);
7670 
7671 							if ( !myStats->weapon && (*weaponToBreak) )
7672 							{
7673 								// unarmed glove weapons, 87.5% to not degrade on 0 dmg, else 99%
7674 								degradeOnZeroDMG = 8 + (myStats->type == GOBLIN ? 4 : 0);
7675 								degradeOnNormalDMG = 100 + (myStats->type == GOBLIN ? 20 : 0);
7676 							}
7677 							else if ( isWeakWeapon )
7678 							{
7679 								// crystal weapons chance to not degrade 66% chance on 0 dmg, else 97.5%
7680 								degradeOnZeroDMG = 3;
7681 								degradeOnNormalDMG = 40;
7682 							}
7683 
7684 							if ( behavior == &actPlayer && ((weaponskill >= PRO_SWORD && weaponskill <= PRO_POLEARM)
7685 								|| weaponskill == PRO_UNARMED || weaponskill == PRO_RANGED) )
7686 							{
7687 								int skillLVL = myStats->PROFICIENCIES[weaponskill] / 20;
7688 								degradeOnZeroDMG += skillLVL; // increase by 1-5
7689 								degradeOnNormalDMG += (skillLVL * 10); // increase by 10-50
7690 							}
7691 							if ( myStats->weapon && myStats->weapon->type == TOOL_WHIP )
7692 							{
7693 								degradeOnZeroDMG = degradeOnNormalDMG; // don't degrade faster on 0 dmg.
7694 							}
7695 							if ( behavior == &actPlayer && (svFlags & SV_FLAG_HARDCORE) )
7696 							{
7697 								// double durability.
7698 								degradeOnZeroDMG *= 2;
7699 								degradeOnNormalDMG *= 2;
7700 							}
7701 
7702 							if	( (rand() % degradeOnZeroDMG == 0 && damage == 0)
7703 									|| (rand() % degradeOnNormalDMG == 0 && damage > 0)
7704 								)
7705 							{
7706 								degradeWeapon = true;
7707 							}
7708 
7709 							if ( behavior == &actPlayer && skillCapstoneUnlocked(skill[2], weaponskill) )
7710 							{
7711 								// don't degrade on capstone skill.
7712 								degradeWeapon = false;
7713 							}
7714 							else if ( myStats->type == SHADOW || myStats->type == LICH_FIRE || myStats->type == LICH_ICE )
7715 							{
7716 								degradeWeapon = false; //certain monster's weapons don't degrade.
7717 							}
7718 							else if ( myStats->type == SKELETON && behavior == &actMonster && monsterAllySummonRank != 0 )
7719 							{
7720 								degradeWeapon = false;
7721 							}
7722 							else if ( isIllusion )
7723 							{
7724 								degradeWeapon = false;
7725 							}
7726 							else if ( myStats->weapon && myStats->weapon->type == TOOL_WHIP )
7727 							{
7728 								degradeWeapon = false;
7729 							}
7730 
7731 							if ( degradeWeapon )
7732 							{
7733 								if ( player == clientnum || player < 0 )
7734 								{
7735 									if ( (*weaponToBreak)->count > 1 )
7736 									{
7737 										newItem((*weaponToBreak)->type, (*weaponToBreak)->status, (*weaponToBreak)->beatitude,
7738 											(*weaponToBreak)->count - 1, (*weaponToBreak)->appearance, (*weaponToBreak)->identified, &myStats->inventory);
7739 									}
7740 								}
7741 								(*weaponToBreak)->count = 1;
7742 								(*weaponToBreak)->status = static_cast<Status>((*weaponToBreak)->status - 1);
7743 								if ( (*weaponToBreak)->status != BROKEN )
7744 								{
7745 									messagePlayer(player, language[679]);
7746 								}
7747 								else
7748 								{
7749 									playSoundEntity(this, 76, 64);
7750 									messagePlayer(player, language[680]);
7751 								}
7752 								if ( player > 0 && multiplayer == SERVER )
7753 								{
7754 									strcpy((char*)net_packet->data, "ARMR");
7755 									if ( weaponToBreak == &myStats->weapon )
7756 									{
7757 										net_packet->data[4] = 5;
7758 									}
7759 									else
7760 									{
7761 										net_packet->data[4] = 2;
7762 									}
7763 									net_packet->data[5] = (*weaponToBreak)->status;
7764 									net_packet->address.host = net_clients[player - 1].host;
7765 									net_packet->address.port = net_clients[player - 1].port;
7766 									net_packet->len = 6;
7767 									sendPacketSafe(net_sock, -1, net_packet, player - 1);
7768 								}
7769 								if ( (*weaponToBreak)->status == BROKEN && behavior == &actMonster && playerhit >= 0 )
7770 								{
7771 									steamStatisticUpdateClient(playerhit, STEAM_STAT_TOUGH_AS_NAILS, STEAM_STAT_INT, 1);
7772 								}
7773 							}
7774 						}
7775 					}
7776 
7777 					// damage opponent armor if applicable
7778 					Item* armor = NULL;
7779 					int armornum = 0;
7780 					bool isWeakArmor = false;
7781 
7782 					if ( damage > 0 )
7783 					{
7784 						// choose random piece of equipment to target
7785 						int armorRoll = rand() % 6;
7786 						if ( armorRoll == 4 && hitstats->shield )
7787 						{
7788 							if ( itemTypeIsQuiver(hitstats->shield->type)
7789 								|| itemCategory(hitstats->shield) == SPELLBOOK
7790 								|| hitstats->shield->type == TOOL_TINKERING_KIT )
7791 							{
7792 								armorRoll = rand() % 4; // reroll for non-shield slot.
7793 							}
7794 						}
7795 						switch ( rand() % 6 )
7796 						{
7797 							case 0:
7798 								armor = hitstats->helmet;
7799 								armornum = 0;
7800 								break;
7801 							case 1:
7802 								armor = hitstats->breastplate;
7803 								armornum = 1;
7804 								break;
7805 							case 2:
7806 								armor = hitstats->gloves;
7807 								armornum = 2;
7808 								break;
7809 							case 3:
7810 								armor = hitstats->shoes;
7811 								armornum = 3;
7812 								break;
7813 							case 4:
7814 								armor = hitstats->shield;
7815 								armornum = 4;
7816 								break;
7817 							case 5:
7818 								armor = hitstats->cloak;
7819 								armornum = 6;
7820 								break;
7821 							default:
7822 								break;
7823 						}
7824 
7825 						if ( armor != NULL && armor->status > BROKEN )
7826 						{
7827 							switch ( armor->type )
7828 							{
7829 								case CRYSTAL_HELM:
7830 								case CRYSTAL_SHIELD:
7831 								case CRYSTAL_BREASTPIECE:
7832 								case CRYSTAL_BOOTS:
7833 								case CRYSTAL_GLOVES:
7834 									isWeakArmor = true;
7835 									break;
7836 								default:
7837 									isWeakArmor = false;
7838 									break;
7839 							}
7840 						}
7841 
7842 						int armorDegradeChance = 25;
7843 						if ( isWeakArmor )
7844 						{
7845 							armorDegradeChance = 15;
7846 							if ( weaponskill == PRO_MACE )
7847 							{
7848 								armorDegradeChance = 5;
7849 							}
7850 						}
7851 						else
7852 						{
7853 							if ( weaponskill == PRO_MACE )
7854 							{
7855 								armorDegradeChance = 10;
7856 							}
7857 						}
7858 
7859 						if ( hitstats->type == GOBLIN )
7860 						{
7861 							armorDegradeChance += 10;
7862 						}
7863 
7864 						if ( hit.entity->behavior == &actPlayer && armornum == 4 )
7865 						{
7866 							armorDegradeChance += (hitstats->PROFICIENCIES[PRO_SHIELD] / 10);
7867 							if ( skillCapstoneUnlocked(hit.entity->skill[2], PRO_SHIELD) )
7868 							{
7869 								armorDegradeChance = 100; // don't break.
7870 							}
7871 						}
7872 
7873 						// crystal golem special attack increase chance for armor to break if hit. (25-33%)
7874 						// special attack only degrades armor if primary target.
7875 						if ( (pose == MONSTER_POSE_GOLEM_SMASH || pose == PLAYER_POSE_GOLEM_SMASH) && target == nullptr )
7876 						{
7877 							if ( isWeakArmor )
7878 							{
7879 								// 66% chance to be deselected from degrading.
7880 								if ( rand() % 3 > 0 )
7881 								{
7882 									armor = NULL;
7883 									armornum = 0;
7884 								}
7885 							}
7886 							else
7887 							{
7888 								// 75% chance to be deselected from degrading.
7889 								if ( rand() % 4 > 0 )
7890 								{
7891 									armor = NULL;
7892 									armornum = 0;
7893 								}
7894 							}
7895 						}
7896 						else
7897 						{
7898 							if ( armorDegradeChance == 100 || (rand() % armorDegradeChance > 0) )
7899 							{
7900 								armor = NULL;
7901 								armornum = 0;
7902 							}
7903 						}
7904 					}
7905 
7906 					// if nothing chosen to degrade, check extra shield chances to degrade
7907 					if ( hitstats->shield != NULL && hitstats->shield->status > BROKEN && armor == NULL
7908 						&& !itemTypeIsQuiver(hitstats->shield->type) && itemCategory(hitstats->shield) != SPELLBOOK
7909 						&& hitstats->shield->type != TOOL_TINKERING_KIT )
7910 					{
7911 						if ( hitstats->shield->type == TOOL_CRYSTALSHARD && hitstats->defending )
7912 						{
7913 							// shards degrade by 1 stage each hit.
7914 							armor = hitstats->shield;
7915 							armornum = 4;
7916 						}
7917 						else if ( hitstats->shield->type == MIRROR_SHIELD && hitstats->defending )
7918 						{
7919 							// mirror shield degrade by 1 stage each hit.
7920 							armor = hitstats->shield;
7921 							armornum = 4;
7922 						}
7923 						else
7924 						{
7925 							// if no armor piece was chosen to break, grant chance to improve shield skill.
7926 							if ( itemCategory(hitstats->shield) == ARMOR )
7927 							{
7928 								if ( (rand() % 15 == 0 && damage > 0) || (damage == 0 && rand() % 8 == 0) )
7929 								{
7930 									hit.entity->increaseSkill(PRO_SHIELD); // increase shield skill
7931 								}
7932 							}
7933 
7934 							// shield still has chance to degrade after raising skill.
7935 							// crystal golem special attack increase chance for shield to break if defended. (33%)
7936 							// special attack only degrades shields if primary target.
7937 							int shieldDegradeChance = 10;
7938 							if ( svFlags & SV_FLAG_HARDCORE )
7939 							{
7940 								shieldDegradeChance = 40;
7941 							}
7942 							if ( hitstats->type == GOBLIN )
7943 							{
7944 								shieldDegradeChance += 10;
7945 							}
7946 							if ( hit.entity->behavior == &actPlayer )
7947 							{
7948 								shieldDegradeChance += (hitstats->PROFICIENCIES[PRO_SHIELD] / 10);
7949 								if ( skillCapstoneUnlocked(hit.entity->skill[2], PRO_SHIELD) )
7950 								{
7951 									shieldDegradeChance = 100; // don't break.
7952 								}
7953 							}
7954 							if ( shieldDegradeChance < 100 && armor == NULL &&
7955 								(	(hitstats->defending && rand() % shieldDegradeChance == 0)
7956 									|| (hitstats->defending && pose == MONSTER_POSE_GOLEM_SMASH && target == nullptr && rand() % 3 == 0)
7957 								)
7958 								)
7959 							{
7960 								armor = hitstats->shield;
7961 								armornum = 4;
7962 							}
7963 						}
7964 					}
7965 
7966 					if ( armor != NULL && armor->status > BROKEN )
7967 					{
7968 						hit.entity->degradeArmor(*hitstats, *armor, armornum);
7969 						if ( armor->status == BROKEN )
7970 						{
7971 							if ( player >= 0 && hit.entity->behavior == &actMonster )
7972 							{
7973 								steamStatisticUpdateClient(player, STEAM_STAT_UNSTOPPABLE_FORCE, STEAM_STAT_INT, 1);
7974 							}
7975 						}
7976 					}
7977 
7978 					bool statusInflicted = false;
7979 					bool paralyzeStatusInflicted = false;
7980 					bool slowStatusInflicted = false;
7981 					bool bleedStatusInflicted = false;
7982 					bool swordExtraDamageInflicted = false;
7983 					bool knockbackInflicted = false;
7984 					bool dyrnwynBurn = false;
7985 
7986 					// special weapon effects
7987 					if ( myStats->weapon && !shapeshifted )
7988 					{
7989 						if ( myStats->weapon->type == ARTIFACT_SWORD )
7990 						{
7991 							real_t amount = 0.0;
7992 							real_t percent = getArtifactWeaponEffectChance(myStats->weapon->type, *myStats, &amount);
7993 							if ( dyrnwynSmite || (rand() % 100 < static_cast<int>(percent)) )
7994 							{
7995 								if ( hit.entity->flags[BURNABLE] )
7996 								{
7997 									if ( hitstats )
7998 									{
7999 										hitstats->burningInflictedBy = static_cast<Sint32>(uid);
8000 									}
8001 
8002 									bool wasBurning = hit.entity->flags[BURNING];
8003 									// Attempt to set the Entity on fire
8004 									hit.entity->SetEntityOnFire();
8005 
8006 									if ( !wasBurning && hit.entity->flags[BURNING] )
8007 									{
8008 										// 6 ticks maximum burning.
8009 										hit.entity->char_fire = std::min(hit.entity->char_fire, static_cast<int>(TICKS_TO_PROCESS_FIRE * (6 + amount)));
8010 										dyrnwynBurn = true;
8011 									}
8012 
8013 									// If a Player was hit, and they are now on fire, tell them what set them on fire
8014 									if ( playerhit > 0 && hit.entity->flags[BURNING] )
8015 									{
8016 										messagePlayer(playerhit, language[683]); // "Dyrnwyn sets you on fire!"
8017 									}
8018 								}
8019 							}
8020 						}
8021 						else if ( myStats->weapon->type == ARTIFACT_AXE && parashuProc )
8022 						{
8023 							int duration = 100; // 2 seconds
8024 							if ( hitstats->HP > 0 && hit.entity->setEffect(EFF_SLOW, true, duration, true) )
8025 							{
8026 								slowStatusInflicted = true;
8027 								playSoundEntity(hit.entity, 396 + rand() % 3, 64);
8028 								spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 171);
8029 							}
8030 						}
8031 					}
8032 
8033 					if ( (hitstats->EFFECTS[EFF_WEBBED] || pose == PLAYER_POSE_GOLEM_SMASH)
8034 						&& !hitstats->EFFECTS[EFF_KNOCKBACK] && hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) )
8035 					{
8036 						real_t baseMultiplier = 0.7;
8037 						if ( pose == PLAYER_POSE_GOLEM_SMASH )
8038 						{
8039 							baseMultiplier = 0.9;
8040 						}
8041 						real_t pushbackMultiplier = baseMultiplier;
8042 						if ( !hit.entity->isMobile() )
8043 						{
8044 							pushbackMultiplier += 0.3;
8045 						}
8046 						real_t tangent = atan2(hit.entity->y - this->y, hit.entity->x - this->x);
8047 						if ( hit.entity->behavior == &actMonster )
8048 						{
8049 							hit.entity->vel_x = cos(tangent) * pushbackMultiplier;
8050 							hit.entity->vel_y = sin(tangent) * pushbackMultiplier;
8051 							hit.entity->monsterKnockbackVelocity = 0.05;
8052 							hit.entity->monsterKnockbackUID = this->getUID();
8053 							hit.entity->monsterKnockbackTangentDir = tangent;
8054 							hit.entity->lookAtEntity(*this);
8055 							if ( !(backstab || flanking) )
8056 							{
8057 								if ( hit.entity->monsterAttack == 0 )
8058 								{
8059 									hit.entity->monsterHitTime = std::max(HITRATE - 12, hit.entity->monsterHitTime);
8060 								}
8061 							}
8062 						}
8063 						else if ( hit.entity->behavior == &actPlayer )
8064 						{
8065 							// normalize tangent
8066 							while ( tangent < 0 )
8067 							{
8068 								tangent += 2 * PI;
8069 							}
8070 							while ( tangent > 2 * PI )
8071 							{
8072 								tangent -= 2 * PI;
8073 							}
8074 							if ( hit.entity->skill[2] != clientnum )
8075 							{
8076 								hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
8077 								hit.entity->monsterKnockbackTangentDir = tangent;
8078 								serverUpdateEntityFSkill(hit.entity, 11);
8079 								serverUpdateEntityFSkill(hit.entity, 9);
8080 							}
8081 							else
8082 							{
8083 								hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
8084 								hit.entity->monsterKnockbackTangentDir = tangent;
8085 							}
8086 						}
8087 						knockbackInflicted = true;
8088 					}
8089 
8090 					// player weapon skills
8091 					if ( damage > 0 && weaponskill == PRO_UNARMED && behavior == &actPlayer && (charge >= MAXCHARGE - 3) )
8092 					{
8093 						int chance = 0;
8094 						bool inflictParalyze = false;
8095 						switch ( myStats->PROFICIENCIES[PRO_UNARMED] / 20 )
8096 						{
8097 							case 0:
8098 								break;
8099 							case 1:
8100 								break;
8101 							case 2:
8102 								break;
8103 							case 3:
8104 								break;
8105 							case 4:
8106 								break;
8107 							case 5:
8108 								chance = 1;
8109 								break;
8110 							default:
8111 								break;
8112 						}
8113 						if ( chance > 0 && backstab && !hitstats->EFFECTS[EFF_PARALYZED] && hitstats->HP > 0 )
8114 						{
8115 							int duration = 50;
8116 							if ( hitstats->HP > 0 && hit.entity->setEffect(EFF_PARALYZED, true, duration, true) )
8117 							{
8118 								paralyzeStatusInflicted = true;
8119 								playSoundEntity(hit.entity, 172, 64); //TODO: Paralyze spell sound.
8120 								spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 170);
8121 								if ( behavior == &actPlayer ) // redundant; but if this code ever changes...
8122 								{
8123 									steamAchievementClient(skill[2], "BARONY_ACH_ONE_PUNCH_MAN");
8124 								}
8125 							}
8126 							hit.entity->modHP(-5); // do extra damage.
8127 						}
8128 						if ( !knockbackInflicted && hasMeleeGloves
8129 							&& hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) )
8130 						{
8131 							real_t baseMultiplier = 0.5;
8132 							if ( myStats->gloves )
8133 							{
8134 								switch ( myStats->gloves->type )
8135 								{
8136 									case BRASS_KNUCKLES:
8137 										baseMultiplier = 0.25;
8138 										break;
8139 									case IRON_KNUCKLES:
8140 										baseMultiplier = 0.35;
8141 										break;
8142 									case SPIKED_GAUNTLETS:
8143 										baseMultiplier = 0.5;
8144 										break;
8145 									default:
8146 										break;
8147 								}
8148 							}
8149 							real_t pushbackMultiplier = baseMultiplier + 0.1 * (myStats->PROFICIENCIES[PRO_UNARMED] / 20);
8150 							/*if ( myStats->shield && hasMeleeGloves )
8151 							{
8152 								pushbackMultiplier /= 2;
8153 							}*/
8154 							if ( !hit.entity->isMobile() )
8155 							{
8156 								pushbackMultiplier += 0.3;
8157 							}
8158 							real_t tangent = atan2(hit.entity->y - this->y, hit.entity->x - this->x);
8159 							if ( hit.entity->behavior == &actMonster )
8160 							{
8161 								hit.entity->vel_x = cos(tangent) * pushbackMultiplier;
8162 								hit.entity->vel_y = sin(tangent) * pushbackMultiplier;
8163 								hit.entity->monsterKnockbackVelocity = 0.05;
8164 								hit.entity->monsterKnockbackUID = this->getUID();
8165 								hit.entity->monsterKnockbackTangentDir = tangent;
8166 								hit.entity->lookAtEntity(*this);
8167 								if ( !(backstab || flanking) )
8168 								{
8169 									if ( hit.entity->monsterAttack == 0 )
8170 									{
8171 										hit.entity->monsterHitTime = std::max(HITRATE - 12, hit.entity->monsterHitTime);
8172 									}
8173 								}
8174 							}
8175 							else if ( hit.entity->behavior == &actPlayer )
8176 							{
8177 								// normalize tangent
8178 								while ( tangent < 0 )
8179 								{
8180 									tangent += 2 * PI;
8181 								}
8182 								while ( tangent > 2 * PI )
8183 								{
8184 									tangent -= 2 * PI;
8185 								}
8186 								if ( hit.entity->skill[2] != clientnum )
8187 								{
8188 									hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
8189 									hit.entity->monsterKnockbackTangentDir = tangent;
8190 									serverUpdateEntityFSkill(hit.entity, 11);
8191 									serverUpdateEntityFSkill(hit.entity, 9);
8192 								}
8193 								else
8194 								{
8195 									hit.entity->monsterKnockbackVelocity = pushbackMultiplier;
8196 									hit.entity->monsterKnockbackTangentDir = tangent;
8197 								}
8198 							}
8199 							knockbackInflicted = true;
8200 						}
8201 					}
8202 					else if ( damage > 0 && behavior == &actPlayer && weaponskill >= PRO_SWORD && weaponskill <= PRO_POLEARM && (charge >= MAXCHARGE - 3) )
8203 					{
8204 						// special weapon effects.
8205 						int capstoneDamage = 5;
8206 						if ( weaponskill == PRO_AXE )
8207 						{
8208 							capstoneDamage = 10;
8209 						}
8210 						int chance = 0;
8211 						switch ( myStats->PROFICIENCIES[weaponskill] / 20 )
8212 						{
8213 							case 0:
8214 							case 1:
8215 							case 2:
8216 							case 3:
8217 							case 4:
8218 								break;
8219 							case 5:
8220 								chance = 4;
8221 								break;
8222 							default:
8223 								break;
8224 						}
8225 
8226 						if ( weaponskill == PRO_POLEARM )
8227 						{
8228 							// knockback.
8229 							if ( chance > 0 )
8230 							{
8231 								if ( hit.entity->behavior == &actMonster && hit.entity->setEffect(EFF_KNOCKBACK, true, 20, false) )
8232 								{
8233 									real_t pushbackMultiplier = 0.3 + 0.075 * (myStats->PROFICIENCIES[PRO_POLEARM] / 20);
8234 									if ( !hit.entity->isMobile() )
8235 									{
8236 										pushbackMultiplier += 0.3;
8237 									}
8238 									real_t tangent = atan2(hit.entity->y - this->y, hit.entity->x - this->x);
8239 									hit.entity->vel_x = cos(tangent) * pushbackMultiplier;
8240 									hit.entity->vel_y = sin(tangent) * pushbackMultiplier;
8241 									hit.entity->monsterKnockbackVelocity = 0.05;
8242 									hit.entity->monsterKnockbackUID = this->getUID();
8243 									hit.entity->monsterKnockbackTangentDir = tangent;
8244 									hit.entity->lookAtEntity(*this);
8245 									if ( !(backstab || flanking) )
8246 									{
8247 										if ( hit.entity->monsterAttack == 0 )
8248 										{
8249 											hit.entity->monsterHitTime = std::max(HITRATE - 12, hit.entity->monsterHitTime);
8250 										}
8251 									}
8252 									knockbackInflicted = true;
8253 								}
8254 								hit.entity->modHP(-capstoneDamage); // do the damage
8255 							}
8256 						}
8257 						else if ( weaponskill == PRO_MACE && hitstats->HP > 0 )
8258 						{
8259 							// paralyze.
8260 							if ( chance > 0 ) // chance based paralyze
8261 							{
8262 								if ( rand() % chance == 0 && !hitstats->EFFECTS[EFF_PARALYZED] )
8263 								{
8264 									int duration = 75; // 1.5 seconds
8265 									if ( hitstats->HP > 0 && hit.entity->setEffect(EFF_PARALYZED, true, duration, true) )
8266 									{
8267 										paralyzeStatusInflicted = true;
8268 										playSoundEntity(hit.entity, 172, 64); //TODO: Paralyze spell sound.
8269 										spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 170);
8270 									}
8271 								}
8272 								hit.entity->modHP(-capstoneDamage); // do the damage
8273 							}
8274 						}
8275 						else if ( weaponskill == PRO_AXE && hitstats->HP > 0 )
8276 						{
8277 							// slow.
8278 							if ( chance > 0 ) // always
8279 							{
8280 								int duration = 150; // 3 seconds
8281 								if ( hitstats->HP > 0 && hit.entity->setEffect(EFF_SLOW, true, duration, true) && !slowStatusInflicted )
8282 								{
8283 									slowStatusInflicted = true;
8284 									playSoundEntity(hit.entity, 396 + rand() % 3, 64);
8285 									spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 171);
8286 								}
8287 								hit.entity->modHP(-capstoneDamage); // do the damage
8288 								// don't re-notify if already inflicted slow from Parashu.
8289 							}
8290 						}
8291 						else if ( weaponskill == PRO_SWORD && hitstats->HP > 0 )
8292 						{
8293 							// bleed.
8294 							if ( chance > 0 ) // always
8295 							{
8296 								spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 643);
8297 								playSoundEntity(hit.entity, 173, 128);
8298 								if ( gibtype[hitstats->type] > 0 )
8299 								{
8300 									bleedStatusInflicted = true;
8301 									for ( int gibs = 0; gibs < 10; ++gibs )
8302 									{
8303 										Entity* gib = spawnGib(hit.entity);
8304 										serverSpawnGibForClient(gib);
8305 									}
8306 									hit.entity->modHP(-capstoneDamage); // do the damage
8307 								}
8308 								else
8309 								{
8310 									swordExtraDamageInflicted = true;
8311 									int extraDamage = 5;
8312 									hit.entity->modHP(-(extraDamage + capstoneDamage)); // do the damage
8313 								}
8314 							}
8315 						}
8316 					}
8317 
8318 					bool playerPoisonedTarget = false;
8319 
8320 					// special monster effects
8321 					if ( myStats->type == CRYSTALGOLEM && pose == MONSTER_POSE_GOLEM_SMASH )
8322 					{
8323 						if ( damage >= 150 && playerhit >= 0 )
8324 						{
8325 							if ( hitstats && hitstats->HP > 0 )
8326 							{
8327 								steamAchievementClient(playerhit, "BARONY_ACH_SPONGE");
8328 							}
8329 						}
8330 						if ( multiplayer != CLIENT )
8331 						{
8332 							createParticleRock(hit.entity);
8333 							if ( multiplayer == SERVER )
8334 							{
8335 								serverSpawnMiscParticles(hit.entity, PARTICLE_EFFECT_ABILITY_ROCK, 0);
8336 							}
8337 							if ( target == nullptr )
8338 							{
8339 								// only play sound once on primary target.
8340 								playSoundEntity(hit.entity, 181, 64);
8341 							}
8342 						}
8343 					}
8344 					else if ( (damage > 0 || hitstats->EFFECTS[EFF_PACIFY] || hitstats->EFFECTS[EFF_FEAR]) && rand() % 4 == 0 )
8345 					{
8346 						int armornum = 0;
8347 						Item* armor = nullptr;
8348 						int armorstolen = rand() % 9;
8349 						switch ( myStats->type )
8350 						{
8351 							case SCORPION:
8352 								hitstats->EFFECTS[EFF_PARALYZED] = true;
8353 								hitstats->EFFECTS_TIMERS[EFF_PARALYZED] = std::max(50, 150 - hit.entity->getCON() * 5);
8354 								messagePlayer(playerhit, language[684]);
8355 								messagePlayer(playerhit, language[685]);
8356 								serverUpdateEffects(playerhit);
8357 								break;
8358 							case SPIDER:
8359 							{
8360 								bool applyPoison = true;
8361 								if ( behavior == &actPlayer )
8362 								{
8363 									if ( charge >= MAXCHARGE - 3 ) // fully charged strike injects venom.
8364 									{
8365 										applyPoison = true;
8366 									}
8367 									else
8368 									{
8369 										applyPoison = false;
8370 									}
8371 								}
8372 								if ( applyPoison )
8373 								{
8374 									playerPoisonedTarget = true;
8375 									hitstats->EFFECTS[EFF_POISONED] = true;
8376 									hitstats->EFFECTS_TIMERS[EFF_POISONED] = std::max(200, 600 - hit.entity->getCON() * 20);
8377 									messagePlayer(playerhit, language[686]);
8378 									messagePlayer(playerhit, language[687]);
8379 									serverUpdateEffects(playerhit);
8380 									for ( int tmp = 0; tmp < 3; ++tmp )
8381 									{
8382 										Entity* gib = spawnGib(hit.entity, 211);
8383 										serverSpawnGibForClient(gib);
8384 									}
8385 								}
8386 								break;
8387 							}
8388 							case SUCCUBUS:
8389 								switch ( armorstolen )
8390 								{
8391 									case 0:
8392 										armor = hitstats->helmet;
8393 										armornum = 0;
8394 										break;
8395 									case 1:
8396 										armor = hitstats->breastplate;
8397 										armornum = 1;
8398 										break;
8399 									case 2:
8400 										armor = hitstats->gloves;
8401 										armornum = 2;
8402 										break;
8403 									case 3:
8404 										armor = hitstats->shoes;
8405 										armornum = 3;
8406 										break;
8407 									case 4:
8408 										armor = hitstats->shield;
8409 										armornum = 4;
8410 										break;
8411 									case 5:
8412 										armor = hitstats->cloak;
8413 										armornum = 6;
8414 										break;
8415 									case 6:
8416 										armor = hitstats->amulet;
8417 										armornum = 7;
8418 										break;
8419 									case 7:
8420 										armor = hitstats->ring;
8421 										armornum = 8;
8422 										break;
8423 									case 8:
8424 										armor = hitstats->mask;
8425 										armornum = 9;
8426 										break;
8427 									default:
8428 										break;
8429 								}
8430 								if ( behavior == &actPlayer )
8431 								{
8432 									armor = nullptr;
8433 								}
8434 								if ( armor != nullptr )
8435 								{
8436 									if ( playerhit == clientnum || playerhit < 0 )
8437 									{
8438 										if ( armor->count > 1 )
8439 										{
8440 											newItem(armor->type, armor->status, armor->beatitude, armor->count - 1, armor->appearance, armor->identified, &hitstats->inventory);
8441 										}
8442 									}
8443 									armor->count = 1;
8444 									messagePlayer(playerhit, language[688], armor->getName());
8445 									Item* stolenArmor = newItem(armor->type, armor->status, armor->beatitude, armor->count, armor->appearance, armor->identified, &myStats->inventory);
8446 									stolenArmor->ownerUid = hit.entity->getUID();
8447 									Item** slot = itemSlot(hitstats, armor);
8448 									if ( slot )
8449 									{
8450 										*slot = NULL;
8451 									}
8452 									if ( armor->node )
8453 									{
8454 										list_RemoveNode(armor->node);
8455 									}
8456 									else
8457 									{
8458 										free(armor);
8459 									}
8460 									if ( playerhit > 0 && multiplayer == SERVER )
8461 									{
8462 										strcpy((char*)net_packet->data, "STLA");
8463 										net_packet->data[4] = armornum;
8464 										net_packet->address.host = net_clients[playerhit - 1].host;
8465 										net_packet->address.port = net_clients[playerhit - 1].port;
8466 										net_packet->len = 5;
8467 										sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
8468 									}
8469 									teleportRandom();
8470 
8471 									// the succubus loses interest after this
8472 									monsterState = 0;
8473 									monsterTarget = 0;
8474 								}
8475 								break;
8476 							default:
8477 								break;
8478 						}
8479 					}
8480 					else if ( damage == 0 && !(hitstats->defending) )
8481 					{
8482 						// special chance effects when damage is 0.
8483 						if ( rand() % 20 == 0 )
8484 						{
8485 							switch ( myStats->type )
8486 							{
8487 								case SCORPION:
8488 									hitstats->EFFECTS[EFF_PARALYZED] = true;
8489 									hitstats->EFFECTS_TIMERS[EFF_PARALYZED] = std::max(50, 150 - hit.entity->getCON() * 5);
8490 									messagePlayer(playerhit, language[684]);
8491 									messagePlayer(playerhit, language[685]);
8492 									serverUpdateEffects(playerhit);
8493 									statusInflicted = true;
8494 									break;
8495 								case SPIDER:
8496 									if ( behavior != &actPlayer )
8497 									{
8498 										hitstats->EFFECTS[EFF_POISONED] = true;
8499 										hitstats->EFFECTS_TIMERS[EFF_POISONED] = std::max(200, 300 - hit.entity->getCON() * 20);
8500 										messagePlayer(playerhit, language[686]);
8501 										messagePlayer(playerhit, language[687]);
8502 										serverUpdateEffects(playerhit);
8503 										statusInflicted = true;
8504 									}
8505 									break;
8506 								default:
8507 									break;
8508 							}
8509 						}
8510 					}
8511 
8512 					if ( player >= 0 && hit.entity->behavior == &actMonster )
8513 					{
8514 						if ( damage > 0 )
8515 						{
8516 							updateAchievementRhythmOfTheKnight(player, hit.entity, false);
8517 						}
8518 						else
8519 						{
8520 							if ( !achievementStatusRhythmOfTheKnight[player] )
8521 							{
8522 								achievementRhythmOfTheKnightVec[player].clear(); // didn't inflict damage.
8523 							}
8524 						}
8525 					}
8526 
8527 					bool artifactWeaponProc = parashuProc || dyrnwynSmite || dyrnwynBurn || gugnirProc;
8528 
8529 					// send messages
8530 					if ( !strcmp(hitstats->name, "") )
8531 					{
8532 						Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
8533 						Uint32 colorSpecial = color;// SDL_MapRGB(mainsurface->format, 255, 0, 255);
8534 						if ( hitstats->HP > 0 )
8535 						{
8536 							if ( !artifactWeaponProc )
8537 							{
8538 								if ( damage > olddamage )
8539 								{
8540 									// critical hit
8541 									messagePlayerMonsterEvent(player, color, *hitstats, language[689], language[689], MSG_COMBAT);
8542 								}
8543 								else
8544 								{
8545 									// normal hit
8546 									messagePlayerMonsterEvent(player, color, *hitstats, language[690], language[690], MSG_COMBAT);
8547 								}
8548 							}
8549 
8550 							if ( dyrnwynSmite )
8551 							{
8552 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3754], language[3755], MSG_COMBAT);
8553 							}
8554 							else if ( dyrnwynBurn )
8555 							{
8556 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3756], language[3757], MSG_COMBAT);
8557 							}
8558 							else if ( parashuProc )
8559 							{
8560 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3758], language[3759], MSG_COMBAT);
8561 							}
8562 							else if ( gugnirProc )
8563 							{
8564 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3760], language[3761], MSG_COMBAT);
8565 							}
8566 
8567 							if ( damage == 0 )
8568 							{
8569 								// blow bounces off
8570 								messagePlayer(player, language[691]);
8571 							}
8572 							else
8573 							{
8574 								if ( flanking )
8575 								{
8576 									// flank defenses
8577 									messagePlayerMonsterEvent(player, color, *hitstats, language[2545], language[2545], MSG_COMBAT);
8578 								}
8579 								else if ( backstab )
8580 								{
8581 									// backstab on unaware enemy
8582 									messagePlayerMonsterEvent(player, color, *hitstats, language[2543], language[2543], MSG_COMBAT);
8583 									if ( player >= 0 && hitstats->EFFECTS[EFF_SHADOW_TAGGED] && this->creatureShadowTaggedThisUid == hit.entity->getUID() )
8584 									{
8585 										achievementObserver.awardAchievementIfActive(player, this, AchievementObserver::BARONY_ACH_OHAI_MARK);
8586 									}
8587 								}
8588 							}
8589 
8590 							if ( playerPoisonedTarget )
8591 							{
8592 								messagePlayerMonsterEvent(player, color, *hitstats, language[3478], language[3479], MSG_COMBAT);
8593 							}
8594 							if ( paralyzeStatusInflicted )
8595 							{
8596 								messagePlayerMonsterEvent(player, color, *hitstats, language[3206], language[3205], MSG_COMBAT);
8597 							}
8598 							else if ( slowStatusInflicted )
8599 							{
8600 								messagePlayerMonsterEvent(player, color, *hitstats, language[394], language[393], MSG_COMBAT);
8601 							}
8602 							else if ( swordExtraDamageInflicted )
8603 							{
8604 								messagePlayerMonsterEvent(player, color, *hitstats, language[3211], language[3210], MSG_COMBAT);
8605 							}
8606 							else if ( knockbackInflicted )
8607 							{
8608 								messagePlayerMonsterEvent(player, color, *hitstats, language[3215], language[3214], MSG_COMBAT);
8609 							}
8610 						}
8611 						else
8612 						{
8613 							// HP <= 0
8614 							if ( backstab )
8615 							{
8616 								// assassinate monster
8617 								messagePlayerMonsterEvent(player, color, *hitstats, language[2547], language[2547], MSG_COMBAT);
8618 								if ( hitstats->type == COCKATRICE )
8619 								{
8620 									steamAchievementClient(player, "BARONY_ACH_SCALES_IN_FAVOR");
8621 								}
8622 								if ( player >= 0 && stats[player]->type == VAMPIRE && isInvisible() )
8623 								{
8624 									steamStatisticUpdateClient(player, STEAM_STAT_BLOOD_SPORT, STEAM_STAT_INT, 1);
8625 								}
8626 								if ( player >= 0 && hitstats->EFFECTS[EFF_SHADOW_TAGGED] && this->creatureShadowTaggedThisUid == hit.entity->getUID() )
8627 								{
8628 									achievementObserver.awardAchievementIfActive(player, this, AchievementObserver::BARONY_ACH_OHAI_MARK);
8629 								}
8630 							}
8631 							else
8632 							{
8633 								// kill monster
8634 								messagePlayerMonsterEvent(player, color, *hitstats, language[692], language[692], MSG_COMBAT);
8635 								if ( player >= 0 && hit.entity && hit.entity->behavior == &actMonster )
8636 								{
8637 									real_t hitAngle = hit.entity->yawDifferenceFromPlayer(player);
8638 									if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc
8639 									{
8640 										if ( hit.entity->monsterState == MONSTER_STATE_ATTACK && hit.entity->monsterTarget != 0
8641 											&& hit.entity->monsterTarget != getUID() )
8642 										{
8643 											bool angelOfDeath = false;
8644 											// monster is attacking another entity.
8645 											for ( int i = 0; i < MAXPLAYERS; ++i )
8646 											{
8647 												if ( players[i] && players[i]->entity )
8648 												{
8649 													if ( players[i]->entity->getUID() == hit.entity->monsterTarget )
8650 													{
8651 														// monster is attacking another player.
8652 														angelOfDeath = true;
8653 														break;
8654 													}
8655 													Entity* tmpEnt = uidToEntity(hit.entity->monsterTarget);
8656 													if ( tmpEnt )
8657 													{
8658 														Stat* tmpStats = tmpEnt->getStats();
8659 														if ( tmpStats )
8660 														{
8661 															if ( tmpStats->leader_uid == players[i]->entity->getUID() )
8662 															{
8663 																// monster is attacking an allied NPC of a player.
8664 																angelOfDeath = true;
8665 																break;
8666 															}
8667 														}
8668 													}
8669 												}
8670 											}
8671 											if ( angelOfDeath )
8672 											{
8673 												steamAchievementClient(player, "BARONY_ACH_ANGEL_OF_DEATH");
8674 											}
8675 										}
8676 									}
8677 								}
8678 							}
8679 							awardXP(hit.entity, true, true);
8680 							if ( player >= 0 && myStats->weapon && this->checkEnemy(hit.entity) )
8681 							{
8682 								if ( myStats->weapon->ownerUid == hit.entity->getUID() )
8683 								{
8684 									achievementObserver.awardAchievementIfActive(player, hit.entity, AchievementObserver::BARONY_ACH_IRONIC_PUNISHMENT);
8685 								}
8686 								if ( myStats->weapon->type == TOOL_WHIP )
8687 								{
8688 									achievementObserver.awardAchievementIfActive(player, hit.entity, AchievementObserver::BARONY_ACH_COWBOY_FROM_HELL);
8689 								}
8690 								if ( weaponskill == PRO_AXE && client_classes[player] == CLASS_PUNISHER )
8691 								{
8692 									if ( hitstats->EFFECTS[EFF_DISORIENTED] || hitstats->EFFECTS[EFF_PARALYZED]
8693 										|| hitstats->EFFECTS[EFF_SLOW] || hitstats->EFFECTS[EFF_ASLEEP] )
8694 									{
8695 										steamStatisticUpdateClient(player, STEAM_STAT_CHOPPING_BLOCK, STEAM_STAT_INT, 1);
8696 									}
8697 								}
8698 							}
8699 						}
8700 					}
8701 					else
8702 					{
8703 						Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
8704 						Uint32 colorSpecial = color;// SDL_MapRGB(mainsurface->format, 255, 0, 255);
8705 						if ( hitstats->HP > 0 )
8706 						{
8707 							if ( !artifactWeaponProc )
8708 							{
8709 								if ( damage > olddamage )
8710 								{
8711 									// critical hit
8712 									messagePlayerMonsterEvent(player, color, *hitstats, language[689], language[693], MSG_COMBAT);
8713 								}
8714 								else
8715 								{
8716 									// normal hit
8717 									messagePlayerMonsterEvent(player, color, *hitstats, language[690], language[694], MSG_COMBAT);
8718 								}
8719 							}
8720 
8721 							if ( dyrnwynSmite )
8722 							{
8723 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3754], language[3755], MSG_COMBAT);
8724 							}
8725 							else if ( dyrnwynBurn )
8726 							{
8727 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3756], language[3757], MSG_COMBAT);
8728 							}
8729 							else if ( parashuProc )
8730 							{
8731 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3758], language[3759], MSG_COMBAT);
8732 							}
8733 							else if ( gugnirProc )
8734 							{
8735 								messagePlayerMonsterEvent(player, colorSpecial, *hitstats, language[3760], language[3761], MSG_COMBAT);
8736 							}
8737 
8738 							if ( damage == 0 )
8739 							{
8740 								// blow bounces off
8741 								if ( hitstats->sex )
8742 								{
8743 									messagePlayerMonsterEvent(player, 0xFFFFFFFF, *hitstats, language[691], language[695], MSG_COMBAT);
8744 								}
8745 								else
8746 								{
8747 									messagePlayerMonsterEvent(player, 0xFFFFFFFF, *hitstats, language[691], language[696], MSG_COMBAT);
8748 								}
8749 							}
8750 							else
8751 							{
8752 								if ( flanking )
8753 								{
8754 									// flank defenses
8755 									messagePlayerMonsterEvent(player, color, *hitstats, language[2545], language[2546], MSG_COMBAT);
8756 								}
8757 								else if ( backstab )
8758 								{
8759 									// backstab on unaware enemy
8760 									messagePlayerMonsterEvent(player, color, *hitstats, language[2543], language[2544], MSG_COMBAT);
8761 									if ( player >= 0 && hitstats->EFFECTS[EFF_SHADOW_TAGGED] && this->creatureShadowTaggedThisUid == hit.entity->getUID() )
8762 									{
8763 										achievementObserver.awardAchievementIfActive(player, this, AchievementObserver::BARONY_ACH_OHAI_MARK);
8764 									}
8765 								}
8766 							}
8767 
8768 							if ( playerPoisonedTarget )
8769 							{
8770 								messagePlayerMonsterEvent(player, color, *hitstats, language[3478], language[3479], MSG_COMBAT);
8771 							}
8772 							if ( paralyzeStatusInflicted )
8773 							{
8774 								messagePlayerMonsterEvent(player, color, *hitstats, language[3206], language[3205], MSG_COMBAT);
8775 							}
8776 							else if ( slowStatusInflicted )
8777 							{
8778 								messagePlayerMonsterEvent(player, color, *hitstats, language[394], language[393], MSG_COMBAT);
8779 							}
8780 							else if ( swordExtraDamageInflicted )
8781 							{
8782 								messagePlayerMonsterEvent(player, color, *hitstats, language[3211], language[3210], MSG_COMBAT);
8783 							}
8784 							else if ( knockbackInflicted )
8785 							{
8786 								messagePlayerMonsterEvent(player, color, *hitstats, language[3215], language[3214], MSG_COMBAT);
8787 							}
8788 						}
8789 						else
8790 						{
8791 							// HP <= 0
8792 							if ( backstab )
8793 							{
8794 								// assassinate monster
8795 								messagePlayerMonsterEvent(player, color, *hitstats, language[2547], language[2548], MSG_COMBAT);
8796 								if ( hitstats->type == COCKATRICE )
8797 								{
8798 									steamAchievementClient(player, "BARONY_ACH_SCALES_IN_FAVOR");
8799 								}
8800 								if ( player >= 0 && stats[player]->type == VAMPIRE && isInvisible() )
8801 								{
8802 									steamStatisticUpdateClient(player, STEAM_STAT_BLOOD_SPORT, STEAM_STAT_INT, 1);
8803 								}
8804 								if ( player >= 0 && hitstats->EFFECTS[EFF_SHADOW_TAGGED] && this->creatureShadowTaggedThisUid == hit.entity->getUID() )
8805 								{
8806 									achievementObserver.awardAchievementIfActive(player, this, AchievementObserver::BARONY_ACH_OHAI_MARK);
8807 								}
8808 							}
8809 							else
8810 							{
8811 								// kill monster
8812 								messagePlayerMonsterEvent(player, color, *hitstats, language[692], language[697], MSG_COMBAT);
8813 								if ( player >= 0 && hit.entity && hit.entity->behavior == &actMonster )
8814 								{
8815 									real_t hitAngle = hit.entity->yawDifferenceFromPlayer(player);
8816 									if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc
8817 									{
8818 										if ( hit.entity->monsterState == MONSTER_STATE_ATTACK && hit.entity->monsterTarget != 0
8819 											&& hit.entity->monsterTarget != getUID() )
8820 										{
8821 											bool angelOfDeath = false;
8822 											// monster is attacking another entity.
8823 											for ( int i = 0; i < MAXPLAYERS; ++i )
8824 											{
8825 												if ( players[i] && players[i]->entity )
8826 												{
8827 													if ( players[i]->entity->getUID() == hit.entity->monsterTarget )
8828 													{
8829 														// monster is attacking another player.
8830 														angelOfDeath = true;
8831 														break;
8832 													}
8833 													Entity* tmpEnt = uidToEntity(hit.entity->monsterTarget);
8834 													if ( tmpEnt )
8835 													{
8836 														Stat* tmpStats = tmpEnt->getStats();
8837 														if ( tmpStats )
8838 														{
8839 															if ( tmpStats->leader_uid == players[i]->entity->getUID() )
8840 															{
8841 																// monster is attacking an allied NPC of a player.
8842 																angelOfDeath = true;
8843 																break;
8844 															}
8845 														}
8846 													}
8847 												}
8848 											}
8849 											if ( angelOfDeath )
8850 											{
8851 												steamAchievementClient(player, "BARONY_ACH_ANGEL_OF_DEATH");
8852 											}
8853 										}
8854 									}
8855 								}
8856 							}
8857 							awardXP(hit.entity, true, true);
8858 							if ( player >= 0 && myStats->weapon && this->checkEnemy(hit.entity) )
8859 							{
8860 								if ( myStats->weapon->ownerUid == hit.entity->getUID() )
8861 								{
8862 									achievementObserver.awardAchievementIfActive(player, hit.entity, AchievementObserver::BARONY_ACH_IRONIC_PUNISHMENT);
8863 								}
8864 								if ( myStats->weapon->type == TOOL_WHIP )
8865 								{
8866 									achievementObserver.awardAchievementIfActive(player, hit.entity, AchievementObserver::BARONY_ACH_COWBOY_FROM_HELL);
8867 								}
8868 								if ( weaponskill == PRO_AXE && client_classes[player] == CLASS_PUNISHER )
8869 								{
8870 									if ( hitstats->EFFECTS[EFF_DISORIENTED] || hitstats->EFFECTS[EFF_PARALYZED]
8871 										|| hitstats->EFFECTS[EFF_SLOW] || hitstats->EFFECTS[EFF_ASLEEP] )
8872 									{
8873 										steamStatisticUpdateClient(player, STEAM_STAT_CHOPPING_BLOCK, STEAM_STAT_INT, 1);
8874 									}
8875 								}
8876 							}
8877 						}
8878 					}
8879 
8880 					bool disarmed = false;
8881 					if ( hitstats->HP > 0 )
8882 					{
8883 						if ( !whip && hitstats->EFFECTS[EFF_DISORIENTED] )
8884 						{
8885 							hit.entity->setEffect(EFF_DISORIENTED, false, 0, false);
8886 						}
8887 						else if ( whip && (hitstats->EFFECTS[EFF_DISORIENTED]
8888 							|| !hit.entity->isMobile()
8889 							|| (hitstats->EFFECTS[EFF_DRUNK] && rand() % 3 == 0)
8890 							|| (hitstats->EFFECTS[EFF_CONFUSED] && rand() % 3 == 0))
8891 							)
8892 						{
8893 							if ( hit.entity->behavior == &actMonster && !hit.entity->isBossMonster() )
8894 							{
8895 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
8896 								if ( hitstats->weapon
8897 									&& itemCategory(hitstats->weapon) != SPELLBOOK )
8898 								{
8899 									Entity* dropped = dropItemMonster(hitstats->weapon, hit.entity, hitstats);
8900 									if ( dropped )
8901 									{
8902 										if ( hitstats->EFFECTS[EFF_DISORIENTED] && !hitstats->shield )
8903 										{
8904 											hit.entity->setEffect(EFF_DISORIENTED, false, 0, false);
8905 										}
8906 										playSoundEntity(hit.entity, 406, 128);
8907 										dropped->itemDelayMonsterPickingUp = TICKS_PER_SECOND * 5;
8908 										double tangent = atan2(hit.entity->y - y, hit.entity->x - x) + PI;
8909 										dropped->yaw = tangent + PI;
8910 										dropped->vel_x = (1.5 + .025 * (rand() % 11)) * cos(tangent);
8911 										dropped->vel_y = (1.5 + .025 * (rand() % 11)) * sin(tangent);
8912 										dropped->vel_z = (-10 - rand() % 20) * .01;
8913 										dropped->flags[USERFLAG1] = false;
8914 										messagePlayerMonsterEvent(player, color, *hitstats, language[3454], language[3455], MSG_COMBAT);
8915 										disarmed = true;
8916 										dropped->itemOriginalOwner = hit.entity->getUID();
8917 										if ( player >= 0 )
8918 										{
8919 											achievementObserver.addEntityAchievementTimer(hit.entity, AchievementObserver::BARONY_ACH_IRONIC_PUNISHMENT, -1, true, 0);
8920 											achievementObserver.playerAchievements[player].ironicPunishmentTargets.insert(hit.entity->getUID());
8921 										}
8922 									}
8923 								}
8924 								else if ( hitstats->shield )
8925 								{
8926 									Entity* dropped = dropItemMonster(hitstats->shield, hit.entity, hitstats);
8927 									if ( dropped )
8928 									{
8929 										if ( hitstats->EFFECTS[EFF_DISORIENTED] )
8930 										{
8931 											hit.entity->setEffect(EFF_DISORIENTED, false, 0, false);
8932 										}
8933 										playSoundEntity(hit.entity, 406, 128);
8934 										dropped->itemDelayMonsterPickingUp = TICKS_PER_SECOND * 5;
8935 										double tangent = atan2(hit.entity->y - y, hit.entity->x - x) + PI;
8936 										dropped->yaw = tangent;
8937 										dropped->vel_x = (1.5 + .025 * (rand() % 11)) * cos(tangent);
8938 										dropped->vel_y = (1.5 + .025 * (rand() % 11)) * sin(tangent);
8939 										dropped->vel_z = (-10 - rand() % 20) * .01;
8940 										dropped->flags[USERFLAG1] = false;
8941 										messagePlayerMonsterEvent(player, color, *hitstats, language[3456], language[3457], MSG_COMBAT);
8942 										disarmed = true;
8943 										dropped->itemOriginalOwner = hit.entity->getUID();
8944 									}
8945 								}
8946 								else
8947 								{
8948 									if ( hitstats->EFFECTS[EFF_DISORIENTED] )
8949 									{
8950 										hit.entity->setEffect(EFF_DISORIENTED, false, 0, false);
8951 									}
8952 								}
8953 							}
8954 							else
8955 							{
8956 								if ( hitstats->EFFECTS[EFF_DISORIENTED] )
8957 								{
8958 									hit.entity->setEffect(EFF_DISORIENTED, false, 0, false);
8959 								}
8960 							}
8961 						}
8962 					}
8963 
8964 					if ( playerhit > 0 && multiplayer == SERVER )
8965 					{
8966 						if ( pose == MONSTER_POSE_GOLEM_SMASH )
8967 						{
8968 							if ( target == nullptr )
8969 							{
8970 								// primary target
8971 								strcpy((char*)net_packet->data, "SHAK");
8972 								net_packet->data[4] = 20; // turns into .2
8973 								net_packet->data[5] = 20;
8974 								net_packet->address.host = net_clients[playerhit - 1].host;
8975 								net_packet->address.port = net_clients[playerhit - 1].port;
8976 								net_packet->len = 6;
8977 								sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
8978 							}
8979 							else
8980 							{
8981 								// secondary target
8982 								strcpy((char*)net_packet->data, "SHAK");
8983 								net_packet->data[4] = 10; // turns into .1
8984 								net_packet->data[5] = 10;
8985 								net_packet->address.host = net_clients[playerhit - 1].host;
8986 								net_packet->address.port = net_clients[playerhit - 1].port;
8987 								net_packet->len = 6;
8988 								sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
8989 							}
8990 
8991 							strcpy((char*)net_packet->data, "UPHP");
8992 							SDLNet_Write32((Uint32)hitstats->HP, &net_packet->data[4]);
8993 							SDLNet_Write32((Uint32)myStats->type, &net_packet->data[8]);
8994 							net_packet->address.host = net_clients[playerhit - 1].host;
8995 							net_packet->address.port = net_clients[playerhit - 1].port;
8996 							net_packet->len = 12;
8997 							sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
8998 						}
8999 						else
9000 						{
9001 							strcpy((char*)net_packet->data, "SHAK");
9002 							net_packet->data[4] = 10; // turns into .1
9003 							net_packet->data[5] = 10;
9004 							net_packet->address.host = net_clients[playerhit - 1].host;
9005 							net_packet->address.port = net_clients[playerhit - 1].port;
9006 							net_packet->len = 6;
9007 							sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
9008 						}
9009 					}
9010 					else if ( playerhit == 0 || splitscreen )
9011 					{
9012 						if ( pose == MONSTER_POSE_GOLEM_SMASH || pose == PLAYER_POSE_GOLEM_SMASH )
9013 						{
9014 							if ( target == nullptr )
9015 							{
9016 								// primary target
9017 								cameravars[playerhit].shakex += .2;
9018 								cameravars[playerhit].shakey += 20;
9019 							}
9020 							else
9021 							{
9022 								// secondary target
9023 								cameravars[playerhit].shakex += .1;
9024 								cameravars[playerhit].shakey += 10;
9025 							}
9026 						}
9027 						else if ( damage > 0 )
9028 						{
9029 							cameravars[playerhit].shakex += .1;
9030 							cameravars[playerhit].shakey += 10;
9031 						}
9032 						else
9033 						{
9034 							cameravars[playerhit].shakex += .05;
9035 							cameravars[playerhit].shakey += 5;
9036 						}
9037 					}
9038 
9039 					if ( damage > 0 )
9040 					{
9041 						Entity* gib = spawnGib(hit.entity);
9042 						serverSpawnGibForClient(gib);
9043 						Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
9044 						messagePlayerMonsterEvent(playerhit, color, *myStats, language[698], language[699], MSG_ATTACKS);
9045 						if ( playerhit >= 0 )
9046 						{
9047 							if ( !achievementStatusRhythmOfTheKnight[playerhit] )
9048 							{
9049 								achievementRhythmOfTheKnightVec[playerhit].clear();
9050 							}
9051 							if ( !achievementStatusThankTheTank[playerhit] )
9052 							{
9053 								achievementThankTheTankPair[playerhit] = std::make_pair(0, 0);
9054 							}
9055 							if ( behavior == &actMonster )
9056 							{
9057 								updateAchievementBaitAndSwitch(playerhit, false);
9058 							}
9059 							//messagePlayer(0, "took damage!");
9060 							if ( paralyzeStatusInflicted )
9061 							{
9062 								messagePlayerMonsterEvent(playerhit, color, *myStats, language[3208], language[3207], MSG_COMBAT);
9063 							}
9064 							else if ( slowStatusInflicted )
9065 							{
9066 								messagePlayerMonsterEvent(playerhit, color, *myStats, language[395], language[395], MSG_COMBAT);
9067 							}
9068 							else if ( swordExtraDamageInflicted )
9069 							{
9070 								messagePlayerMonsterEvent(playerhit, color, *myStats, language[3213], language[3212], MSG_COMBAT);
9071 							}
9072 							else if ( knockbackInflicted )
9073 							{
9074 								messagePlayerMonsterEvent(playerhit, color, *myStats, language[3216], language[3216], MSG_COMBAT);
9075 							}
9076 						}
9077 					}
9078 					else
9079 					{
9080 						// display 'blow bounces off' message
9081 						//messagePlayer(playerhit, language[700]);
9082 						if ( !statusInflicted )
9083 						{
9084 							messagePlayerMonsterEvent(playerhit, 0xFFFFFFFF, *myStats, language[2457], language[2458], MSG_COMBAT);
9085 						}
9086 						if ( myStats->type == COCKATRICE && hitstats->defending )
9087 						{
9088 							steamAchievementClient(playerhit, "BARONY_ACH_COCK_BLOCK");
9089 						}
9090 						else if ( myStats->type == MINOTAUR && !hitstats->defending )
9091 						{
9092 							steamAchievementClient(playerhit, "BARONY_ACH_ONE_WHO_KNOCKS");
9093 						}
9094 						if ( playerhit >= 0 )
9095 						{
9096 							if ( hitstats->defending )
9097 							{
9098 								updateAchievementRhythmOfTheKnight(playerhit, this, true);
9099 								updateAchievementThankTheTank(playerhit, this, false);
9100 							}
9101 							else if ( !achievementStatusRhythmOfTheKnight[player] )
9102 							{
9103 								achievementRhythmOfTheKnightVec[playerhit].clear();
9104 								//messagePlayer(0, "used AC!");
9105 							}
9106 						}
9107 					}
9108 
9109 					if ( !strncmp(hitstats->name, "inner demon", strlen("inner demon")) )
9110 					{
9111 						hit.entity->modHP(damage); // undo melee damage.
9112 					}
9113 
9114 					// update enemy bar for attacker
9115 					if ( !strcmp(hitstats->name, "") )
9116 					{
9117 						if ( hitstats->type < KOBOLD ) //Original monster count
9118 						{
9119 							updateEnemyBar(this, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
9120 						}
9121 						else if ( hitstats->type >= KOBOLD ) //New monsters
9122 						{
9123 							updateEnemyBar(this, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
9124 						}
9125 					}
9126 					else
9127 					{
9128 						updateEnemyBar(this, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
9129 					}
9130 
9131 					if ( hitstats->type == INCUBUS
9132 						&& !strncmp(hitstats->name, "inner demon", strlen("inner demon")) )
9133 					{
9134 						// conjuration deals damage back to attacker.
9135 						Entity* illusionParent = uidToEntity(hit.entity->parent);
9136 						this->modHP(-(std::max(2, damage / 2)) );
9137 						playSoundEntity(this, 173, 64);
9138 						if ( illusionParent )
9139 						{
9140 							if ( myStats->HP <= 0 )
9141 							{
9142 								illusionParent->awardXP(this, true, true);
9143 								if ( illusionParent->behavior == &actPlayer )
9144 								{
9145 									steamStatisticUpdateClient(illusionParent->skill[2], STEAM_STAT_SELF_FLAGELLATION, STEAM_STAT_INT, 1);
9146 								}
9147 							}
9148 							if ( player > 0 && multiplayer == SERVER )
9149 							{
9150 								strcpy((char*)net_packet->data, "SHAK");
9151 								net_packet->data[4] = 10; // turns into .1
9152 								net_packet->data[5] = 10;
9153 								net_packet->address.host = net_clients[player - 1].host;
9154 								net_packet->address.port = net_clients[player - 1].port;
9155 								net_packet->len = 6;
9156 								sendPacketSafe(net_sock, -1, net_packet, player - 1);
9157 							}
9158 							else if ( player == 0 || splitscreen )
9159 							{
9160 								cameravars[player].shakex += 0.1;
9161 								cameravars[player].shakey += 10;
9162 							}
9163 
9164 							spawnMagicEffectParticles(this->x, this->y, this->z, 983);
9165 							if ( illusionParent->behavior == &actPlayer && illusionParent != this )
9166 							{
9167 								// update enemy bar for attacker
9168 								if ( !strcmp(myStats->name, "") )
9169 								{
9170 									if ( myStats->type < KOBOLD ) //Original monster count
9171 									{
9172 										updateEnemyBar(illusionParent, this, language[90 + myStats->type], myStats->HP, myStats->MAXHP);
9173 									}
9174 									else if ( myStats->type >= KOBOLD ) //New monsters
9175 									{
9176 										updateEnemyBar(illusionParent, this, language[2000 + (myStats->type - KOBOLD)], myStats->HP, myStats->MAXHP);
9177 									}
9178 								}
9179 								else
9180 								{
9181 									updateEnemyBar(illusionParent, this, myStats->name, myStats->HP, myStats->MAXHP);
9182 								}
9183 							}
9184 						}
9185 					}
9186 
9187 					if ( !disarmed )
9188 					{
9189 						if ( whip )
9190 						{
9191 							playSoundEntity(hit.entity, 407 + rand() % 3, 64);
9192 						}
9193 						else
9194 						{
9195 							playSoundEntity(hit.entity, 28, 64);
9196 						}
9197 					}
9198 
9199 					// chance of bleeding
9200 					bool wasBleeding = hitstats->EFFECTS[EFF_BLEEDING]; // check if currently bleeding when this roll occurred.
9201 					if ( gibtype[(int)hitstats->type] > 0 )
9202 					{
9203 						if ( bleedStatusInflicted || (hitstats->HP > 5 && damage > 0) )
9204 						{
9205 							if ( bleedStatusInflicted || (rand() % 20 == 0 && (weaponskill > PRO_SWORD && weaponskill <= PRO_POLEARM) )
9206 								|| (rand() % 10 == 0 && weaponskill == PRO_SWORD)
9207 								|| (whip && ( (flanking && rand() % 5 == 0) || (backstab && rand() % 2 == 0) || disarmed) )
9208 								|| (rand() % 4 == 0 && pose == MONSTER_POSE_GOLEM_SMASH)
9209 								|| (rand() % 4 == 0 && pose == PLAYER_POSE_GOLEM_SMASH)
9210 								|| (rand() % 10 == 0 && myStats->type == VAMPIRE && myStats->weapon == nullptr)
9211 								|| (rand() % 8 == 0 && myStats->EFFECTS[EFF_VAMPIRICAURA] && (myStats->weapon == nullptr || myStats->type == LICH_FIRE))
9212 							)
9213 							{
9214 								bool heavyBleedEffect = false; // heavy bleed will have a greater starting duration, and add to existing duration.
9215 								if ( pose == MONSTER_POSE_GOLEM_SMASH )
9216 								{
9217 									heavyBleedEffect = true;
9218 								}
9219 								else if ( bleedStatusInflicted )
9220 								{
9221 									heavyBleedEffect = false;
9222 								}
9223 								else if ( (myStats->type == VAMPIRE && this->behavior == &actMonster) || myStats->EFFECTS[EFF_VAMPIRICAURA] )
9224 								{
9225 									if ( rand() % 2 == 0 ) // 50% for heavy bleed effect.
9226 									{
9227 										heavyBleedEffect = true;
9228 									}
9229 								}
9230 
9231 								char playerHitMessage[1024] = "";
9232 								char monsterHitMessage[1024] = "";
9233 
9234 								if ( (!wasBleeding && !heavyBleedEffect) || bleedStatusInflicted )
9235 								{
9236 									// normal bleed effect
9237 									if ( bleedStatusInflicted ) // from sword capstone
9238 									{
9239 										// 5 seconds bleeding minimum
9240 										hitstats->EFFECTS_TIMERS[EFF_BLEEDING] = std::max(hitstats->EFFECTS_TIMERS[EFF_BLEEDING], 250);
9241 									}
9242 									else if ( myStats->weapon && myStats->weapon->type == TOOL_WHIP )
9243 									{
9244 										// 5 seconds bleeding minimum
9245 										hitstats->EFFECTS_TIMERS[EFF_BLEEDING] = std::max(hitstats->EFFECTS_TIMERS[EFF_BLEEDING], 250);
9246 										spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 643);
9247 										for ( int gibs = 0; gibs < 5; ++gibs )
9248 										{
9249 											Entity* gib = spawnGib(hit.entity);
9250 											serverSpawnGibForClient(gib);
9251 										}
9252 									}
9253 									else
9254 									{
9255 										hitstats->EFFECTS_TIMERS[EFF_BLEEDING] = std::max(480 + rand() % 360 - hit.entity->getCON() * 100, 120); // 2.4-16.8 seconds
9256 									}
9257 									hitstats->EFFECTS[EFF_BLEEDING] = true;
9258 									strcpy(playerHitMessage, language[701]);
9259 									if ( !strcmp(hitstats->name, "") )
9260 									{
9261 										strcpy(monsterHitMessage, language[702]);
9262 									}
9263 									else
9264 									{
9265 										strcpy(monsterHitMessage, language[703]);
9266 									}
9267 								}
9268 								else if ( heavyBleedEffect )
9269 								{
9270 									if ( !wasBleeding )
9271 									{
9272 										hitstats->EFFECTS_TIMERS[EFF_BLEEDING] = std::max(500 + rand() % 500 - hit.entity->getCON() * 10, 250); // 5-20 seconds
9273 										hitstats->EFFECTS[EFF_BLEEDING] = true;
9274 										strcpy(playerHitMessage, language[2451]);
9275 										if ( !strcmp(hitstats->name, "") )
9276 										{
9277 											strcpy(monsterHitMessage, language[2452]);
9278 										}
9279 										else
9280 										{
9281 											strcpy(monsterHitMessage, language[2453]);
9282 										}
9283 									}
9284 									else
9285 									{
9286 										hitstats->EFFECTS_TIMERS[EFF_BLEEDING] += std::max(rand() % 350 - hit.entity->getCON() * 5, 100); // 2-7 seconds in addition
9287 										hitstats->EFFECTS[EFF_BLEEDING] = true;
9288 										strcpy(playerHitMessage, language[2454]);
9289 										if ( !strcmp(hitstats->name, "") )
9290 										{
9291 											strcpy(monsterHitMessage, language[2455]);
9292 										}
9293 										else
9294 										{
9295 											strcpy(monsterHitMessage, language[2456]);
9296 										}
9297 									}
9298 								}
9299 
9300 								// message player of effect, skip if hit entity was already bleeding.
9301 								if ( hitstats->EFFECTS[EFF_BLEEDING] && (!wasBleeding || heavyBleedEffect) )
9302 								{
9303 									hitstats->bleedInflictedBy = static_cast<Sint32>(this->getUID());
9304 									if ( heavyBleedEffect )
9305 									{
9306 										hitstats->EFFECTS[EFF_SLOW] = true;
9307 										hitstats->EFFECTS_TIMERS[EFF_SLOW] = 60;
9308 									}
9309 									if ( hit.entity->behavior == &actPlayer && multiplayer == SERVER )
9310 									{
9311 										serverUpdateEffects(hit.entity->skill[2]);
9312 									}
9313 
9314 									if ( playerhit >= 0 )
9315 									{
9316 										Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
9317 										messagePlayerColor(playerhit, color, playerHitMessage);
9318 									}
9319 									else
9320 									{
9321 										Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
9322 										if ( !strcmp(hitstats->name, "") )
9323 										{
9324 											messagePlayerColor(player, color, monsterHitMessage, hit.entity->getMonsterLangEntry());
9325 										}
9326 										else
9327 										{
9328 											messagePlayerColor(player, color, monsterHitMessage, hitstats->name);
9329 										}
9330 									}
9331 
9332 									// energize if wearing punisher hood!
9333 									if ( myStats->helmet && myStats->helmet->type == PUNISHER_HOOD )
9334 									{
9335 										this->modMP(1 + rand() % 2);
9336 										Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
9337 										this->setEffect(EFF_MP_REGEN, true, 250, true);
9338 										if ( behavior == &actPlayer )
9339 										{
9340 											messagePlayerColor(player, color, language[3753]);
9341 											steamStatisticUpdateClient(player, STEAM_STAT_ITS_A_LIVING, STEAM_STAT_INT, 1);
9342 										}
9343 										playSoundEntity(this, 168, 128);
9344 									}
9345 								}
9346 							}
9347 						}
9348 					}
9349 					// apply AoE attack
9350 					list_t* aoeTargets = nullptr;
9351 					list_t* shakeTargets = nullptr;
9352 					Entity* tmpEntity = nullptr;
9353 					if ( pose == MONSTER_POSE_GOLEM_SMASH && target == nullptr )
9354 					{
9355 						getTargetsAroundEntity(this, hit.entity, STRIKERANGE, PI / 3, MONSTER_TARGET_ENEMY, &aoeTargets);
9356 						if ( aoeTargets )
9357 						{
9358 							for ( node = aoeTargets->first; node != NULL; node = node->next )
9359 							{
9360 								tmpEntity = (Entity*)node->element;
9361 								if ( tmpEntity != nullptr )
9362 								{
9363 									this->attack(MONSTER_POSE_GOLEM_SMASH, charge, tmpEntity);
9364 								}
9365 							}
9366 							//Free the list.
9367 							list_FreeAll(aoeTargets);
9368 							free(aoeTargets);
9369 						}
9370 						getTargetsAroundEntity(this, hit.entity, STRIKERANGE, PI, MONSTER_TARGET_PLAYER, &shakeTargets);
9371 						if ( shakeTargets )
9372 						{
9373 							// shake nearby players that were not the primary target.
9374 							for ( node = shakeTargets->first; node != NULL; node = node->next )
9375 							{
9376 								tmpEntity = (Entity*)node->element;
9377 								playerhit = tmpEntity->skill[2];
9378 								if ( playerhit > 0 && multiplayer == SERVER )
9379 								{
9380 									strcpy((char*)net_packet->data, "SHAK");
9381 									net_packet->data[4] = 10; // turns into .1
9382 									net_packet->data[5] = 10;
9383 									net_packet->address.host = net_clients[playerhit - 1].host;
9384 									net_packet->address.port = net_clients[playerhit - 1].port;
9385 									net_packet->len = 6;
9386 									sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
9387 								}
9388 								else if ( playerhit == 0 )
9389 								{
9390 									cameravars[playerhit].shakex += 0.1;
9391 									cameravars[playerhit].shakey += 10;
9392 								}
9393 							}
9394 							//Free the list.
9395 							list_FreeAll(shakeTargets);
9396 							free(shakeTargets);
9397 						}
9398 					}
9399 					else if ( pose == MONSTER_POSE_AUTOMATON_MALFUNCTION )
9400 					{
9401 						getTargetsAroundEntity(this, this, 24, PI, MONSTER_TARGET_ALL, &aoeTargets);
9402 						if ( aoeTargets )
9403 						{
9404 							for ( node = aoeTargets->first; node != NULL; node = node->next )
9405 							{
9406 								tmpEntity = (Entity*)node->element;
9407 								if ( tmpEntity != nullptr )
9408 								{
9409 									spawnExplosion(tmpEntity->x, tmpEntity->y, tmpEntity->z);
9410 									Stat* tmpStats = tmpEntity->getStats();
9411 									if ( tmpStats )
9412 									{
9413 										int explodeDmg = (40 + myStats->HP) * tmpEntity->getDamageTableMultiplier(*tmpStats, DAMAGE_TABLE_MAGIC); // check base magic damage resist.
9414 										Entity* gib = spawnGib(tmpEntity);
9415 										serverSpawnGibForClient(gib);
9416 										playerhit = tmpEntity->skill[2];
9417 										if ( playerhit > 0 && multiplayer == SERVER )
9418 										{
9419 											strcpy((char*)net_packet->data, "SHAK");
9420 											net_packet->data[4] = 20; // turns into .1
9421 											net_packet->data[5] = 20;
9422 											net_packet->address.host = net_clients[playerhit - 1].host;
9423 											net_packet->address.port = net_clients[playerhit - 1].port;
9424 											net_packet->len = 6;
9425 											sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
9426 										}
9427 										else if ( playerhit == 0 )
9428 										{
9429 											cameravars[playerhit].shakex += 0.2;
9430 											cameravars[playerhit].shakey += 20;
9431 										}
9432 										tmpEntity->modHP(-explodeDmg);
9433 										if ( playerhit >= 0 )
9434 										{
9435 											Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
9436 											messagePlayerColor(playerhit, color, language[2523]);
9437 										}
9438 									}
9439 								}
9440 							}
9441 							//Free the list.
9442 							list_FreeAll(aoeTargets);
9443 							free(aoeTargets);
9444 						}
9445 					}
9446 					// lifesteal
9447 					bool tryLifesteal = false;
9448 					bool forceLifesteal = false;
9449 					int lifeStealAmount = damage;
9450 					if ( damage > 0 )
9451 					{
9452 						if ( behavior == &actPlayer )
9453 						{
9454 							if ( myStats->weapon == nullptr || shapeshifted )
9455 							{
9456 								if ( myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] == -2 )
9457 								{
9458 									if ( backstab || flanking )
9459 									{
9460 										if ( hitstats->HP <= 0 )
9461 										{
9462 											forceLifesteal = true;
9463 										}
9464 									}
9465 								}
9466 								else if ( myStats->EFFECTS[EFF_VAMPIRICAURA] && myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] > 0 )
9467 								{
9468 									tryLifesteal = true;
9469 									if ( backstab || flanking )
9470 									{
9471 										if ( hitstats->HP <= 0 )
9472 										{
9473 											forceLifesteal = true;
9474 										}
9475 									}
9476 								}
9477 								lifeStealAmount = std::max(0, hitstats->OLDHP - hitstats->HP);
9478 								lifeStealAmount /= 4;
9479 								lifeStealAmount = std::max(3, lifeStealAmount);
9480 							}
9481 						}
9482 						else if ( (myStats->EFFECTS[EFF_VAMPIRICAURA] && (myStats->weapon == nullptr || myStats->type == LICH_FIRE)) )
9483 						{
9484 							tryLifesteal = true;
9485 						}
9486 						else if ( myStats->type == VAMPIRE && behavior == &actMonster )
9487 						{
9488 							tryLifesteal = true;
9489 						}
9490 
9491 						// special strike spell animation
9492 						if ( pose == PLAYER_POSE_GOLEM_SMASH )
9493 						{
9494 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 643);
9495 							for ( int gibs = 0; gibs < 10; ++gibs )
9496 							{
9497 								Entity* gib = spawnGib(hit.entity);
9498 								serverSpawnGibForClient(gib);
9499 							}
9500 							playSoundEntity(hit.entity, 181, 128);
9501 						}
9502 					}
9503 
9504 					if ( tryLifesteal || forceLifesteal )
9505 					{
9506 						bool lifestealSuccess = false;
9507 						if ( forceLifesteal )
9508 						{
9509 							this->modHP(lifeStealAmount);
9510 							spawnMagicEffectParticles(x, y, z, 169);
9511 							playSoundEntity(this, 168, 128);
9512 							lifestealSuccess = true;
9513 						}
9514 						else if ( !wasBleeding && hitstats->EFFECTS[EFF_BLEEDING] )
9515 						{
9516 							// attack caused the target to bleed, trigger lifesteal tick
9517 							this->modHP(lifeStealAmount);
9518 							spawnMagicEffectParticles(x, y, z, 169);
9519 							playSoundEntity(this, 168, 128);
9520 							lifestealSuccess = true;
9521 						}
9522 						else if ( (rand() % 4 == 0) && (myStats->type == VAMPIRE && behavior == &actMonster && myStats->EFFECTS[EFF_VAMPIRICAURA]) )
9523 						{
9524 							// vampires under aura have higher chance.
9525 							this->modHP(lifeStealAmount);
9526 							spawnMagicEffectParticles(x, y, z, 169);
9527 							playSoundEntity(this, 168, 128);
9528 							lifestealSuccess = true;
9529 						}
9530 						else if ( rand() % 8 == 0 )
9531 						{
9532 							// else low chance for lifesteal.
9533 							this->modHP(lifeStealAmount);
9534 							spawnMagicEffectParticles(x, y, z, 169);
9535 							playSoundEntity(this, 168, 128);
9536 							lifestealSuccess = true;
9537 						}
9538 
9539 						if ( lifestealSuccess )
9540 						{
9541 							if ( player >= 0 )
9542 							{
9543 								myStats->HUNGER = std::min(1499, myStats->HUNGER + 100);
9544 								serverUpdateHunger(player);
9545 								if ( stats[player]->type == VAMPIRE )
9546 								{
9547 									steamStatisticUpdateClient(player, STEAM_STAT_BAD_BLOOD, STEAM_STAT_INT, lifeStealAmount);
9548 								}
9549 							}
9550 							if ( playerhit >= 0 )
9551 							{
9552 								Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
9553 								messagePlayerColor(playerhit, color, language[2441]);
9554 							}
9555 							else
9556 							{
9557 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
9558 								if ( !strcmp(hitstats->name, "") )
9559 								{
9560 									messagePlayerColor(player, color, language[2440], hit.entity->getMonsterLangEntry());
9561 								}
9562 								else
9563 								{
9564 									messagePlayerColor(player, color, language[2439], hitstats->name);
9565 								}
9566 							}
9567 						}
9568 					}
9569 					// vampire blood drops.
9570 					bool tryBloodVial = false;
9571 					if ( hitstats->HP <= 0 && hit.entity->behavior == &actMonster
9572 						&& (gibtype[hitstats->type] == 1 || gibtype[hitstats->type] == 2) )
9573 					{
9574 						for ( c = 0; c < MAXPLAYERS; ++c )
9575 						{
9576 							if ( players[c] && players[c]->entity )
9577 							{
9578 								if ( players[c]->entity->playerRequiresBloodToSustain() )
9579 								{
9580 									tryBloodVial = true;
9581 									break;
9582 								}
9583 							}
9584 						}
9585 					}
9586 					if ( tryBloodVial )
9587 					{
9588 						bool spawnBloodVial = false;
9589 						bool spawnSecondVial = false;
9590 						if ( (backstab || flanking) && hitstats->HP <= 0 )
9591 						{
9592 							spawnBloodVial = true;
9593 						}
9594 						else if ( hitstats->EFFECTS[EFF_BLEEDING] || myStats->EFFECTS[EFF_VAMPIRICAURA] )
9595 						{
9596 							if ( hitstats->EFFECTS_TIMERS[EFF_BLEEDING] >= 250 )
9597 							{
9598 								spawnBloodVial = (rand() % 2 == 0);
9599 							}
9600 							else if ( hitstats->EFFECTS_TIMERS[EFF_BLEEDING] >= 150 )
9601 							{
9602 								spawnBloodVial = (rand() % 4 == 0);
9603 							}
9604 							else
9605 							{
9606 								spawnBloodVial = (rand() % 8 == 0);
9607 							}
9608 
9609 							if ( rand() % 5 == 0 )
9610 							{
9611 								spawnSecondVial = true;
9612 							}
9613 						}
9614 						else
9615 						{
9616 							spawnBloodVial = (rand() % 10 == 0);
9617 						}
9618 
9619 						if ( spawnBloodVial )
9620 						{
9621 							Item* blood = newItem(FOOD_BLOOD, EXCELLENT, 0, 1, gibtype[hitstats->type] - 1, true, &hitstats->inventory);
9622 							if ( spawnSecondVial )
9623 							{
9624 								blood = newItem(FOOD_BLOOD, EXCELLENT, 0, 1, gibtype[hitstats->type] - 1, true, &hitstats->inventory);
9625 							}
9626 						}
9627 					}
9628 				}
9629 			}
9630 		}
9631 		else
9632 		{
9633 			if ( (dist != STRIKERANGE && !whip) || (dist != STRIKERANGE * 1.5 && whip) )
9634 			{
9635 				// hit a wall
9636 				if ( pose == PLAYER_POSE_GOLEM_SMASH )
9637 				{
9638 					if ( hit.mapx >= 1 && hit.mapx < map.width - 1 && hit.mapy >= 1 && hit.mapy < map.height - 1 )
9639 					{
9640 						magicDig(this, nullptr, 0, 0);
9641 						playSoundPos(hit.x, hit.y, 67, 128); // bust wall
9642 						if ( player >= 0 && myStats->type == TROLL )
9643 						{
9644 							serverUpdatePlayerGameplayStats(player, STATISTICS_FORUM_TROLL, AchievementObserver::FORUM_TROLL_BREAK_WALL);
9645 						}
9646 						for ( int c = 0; c < 5; c++ )
9647 						{
9648 							Entity* entity = newEntity(78, 1, map.entities, nullptr); //Particle entity.
9649 							entity->sizex = 1;
9650 							entity->sizey = 1;
9651 							entity->x = hit.x + (-4 + rand() % 9);
9652 							entity->y = hit.y + (-4 + rand() % 9);
9653 							entity->z = 7.5;
9654 							entity->yaw = c * 2 * PI / 5;//(rand() % 360) * PI / 180.0;
9655 							entity->roll = (rand() % 360) * PI / 180.0;
9656 
9657 							entity->vel_x = 0.2 * cos(entity->yaw);
9658 							entity->vel_y = 0.2 * sin(entity->yaw);
9659 							entity->vel_z = 3;// 0.25 - (rand() % 5) / 10.0;
9660 
9661 							entity->skill[0] = 50; // particle life
9662 							entity->skill[1] = 0; // particle direction, 0 = upwards, 1 = downwards.
9663 
9664 							entity->behavior = &actParticleRock;
9665 							entity->flags[PASSABLE] = true;
9666 							entity->flags[NOUPDATE] = true;
9667 							entity->flags[UNCLICKABLE] = true;
9668 							if ( multiplayer != CLIENT )
9669 							{
9670 								entity_uids--;
9671 							}
9672 							entity->setUID(-3);
9673 						}
9674 					}
9675 					else
9676 					{
9677 						messagePlayer(player, language[706]);
9678 					}
9679 				}
9680 				else if ( myStats->weapon != NULL && !shapeshifted )
9681 				{
9682 					if ( myStats->weapon->type == TOOL_PICKAXE )
9683 					{
9684 						if ( hit.mapx >= 1 && hit.mapx < map.width - 1 && hit.mapy >= 1 && hit.mapy < map.height - 1 )
9685 						{
9686 							bool degradePickaxe = true;
9687 							if ( this->behavior == &actPlayer && MFLAG_DISABLEDIGGING )
9688 							{
9689 								Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
9690 								messagePlayerColor(this->skill[2], color, language[2380]); // disabled digging.
9691 								playSoundPos(hit.x, hit.y, 66, 128); // strike wall
9692 								// bang
9693 								spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0);
9694 							}
9695 							else if ( swimmingtiles[map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height]]
9696 								|| lavatiles[map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height]] )
9697 							{
9698 								// no effect for lava/water tiles.
9699 								degradePickaxe = false;
9700 							}
9701 							else
9702 							{
9703 								playSoundPos(hit.x, hit.y, 67, 128); // bust wall
9704 								// spawn several rock items
9705 								i = 8 + rand() % 4;
9706 								for ( c = 0; c < i; c++ )
9707 								{
9708 									Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock/item entity.
9709 									entity->flags[INVISIBLE] = true;
9710 									entity->flags[UPDATENEEDED] = true;
9711 									entity->x = hit.mapx * 16 + 4 + rand() % 8;
9712 									entity->y = hit.mapy * 16 + 4 + rand() % 8;
9713 									entity->z = -6 + rand() % 12;
9714 									entity->sizex = 4;
9715 									entity->sizey = 4;
9716 									entity->yaw = rand() % 360 * PI / 180;
9717 									entity->vel_x = (rand() % 20 - 10) / 10.0;
9718 									entity->vel_y = (rand() % 20 - 10) / 10.0;
9719 									entity->vel_z = -.25 - (rand() % 5) / 10.0;
9720 									entity->flags[PASSABLE] = true;
9721 									entity->behavior = &actItem;
9722 									entity->flags[USERFLAG1] = true; // no collision: helps performance
9723 									entity->skill[10] = GEM_ROCK;    // type
9724 									entity->skill[11] = WORN;        // status
9725 									entity->skill[12] = 0;           // beatitude
9726 									entity->skill[13] = 1;           // count
9727 									entity->skill[14] = 0;           // appearance
9728 									entity->skill[15] = 1;			 // identified
9729 								}
9730 
9731 								if ( map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height] >= 41
9732 									&& map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height] <= 49 )
9733 								{
9734 									steamAchievementClient(player, "BARONY_ACH_BAD_REVIEW");
9735 								}
9736 
9737 								map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height] = 0;
9738 								// send wall destroy info to clients
9739 								if ( multiplayer == SERVER )
9740 								{
9741 									for ( c = 1; c < MAXPLAYERS; c++ )
9742 									{
9743 										if ( client_disconnected[c] == true )
9744 										{
9745 											continue;
9746 										}
9747 										strcpy((char*)net_packet->data, "WALD");
9748 										SDLNet_Write16((Uint16)hit.mapx, &net_packet->data[4]);
9749 										SDLNet_Write16((Uint16)hit.mapy, &net_packet->data[6]);
9750 										net_packet->address.host = net_clients[c - 1].host;
9751 										net_packet->address.port = net_clients[c - 1].port;
9752 										net_packet->len = 8;
9753 										sendPacketSafe(net_sock, -1, net_packet, c - 1);
9754 									}
9755 								}
9756 								// Update the paths so that monsters know they can walk through it
9757 								generatePathMaps();
9758 							}
9759 							int chance = 2 + (myStats->type == GOBLIN ? 2 : 0);
9760 							if ( rand() % chance && degradePickaxe )
9761 							{
9762 								myStats->weapon->status = static_cast<Status>(myStats->weapon->status - 1);
9763 								if ( myStats->weapon->status == BROKEN )
9764 								{
9765 									messagePlayer(player, language[704]);
9766 									playSoundEntity(this, 76, 64);
9767 								}
9768 								else
9769 								{
9770 									messagePlayer(player, language[705]);
9771 								}
9772 								if ( player > 0 && multiplayer == SERVER )
9773 								{
9774 									strcpy((char*)net_packet->data, "ARMR");
9775 									net_packet->data[4] = 5;
9776 									net_packet->data[5] = myStats->weapon->status;
9777 									net_packet->address.host = net_clients[player - 1].host;
9778 									net_packet->address.port = net_clients[player - 1].port;
9779 									net_packet->len = 6;
9780 									sendPacketSafe(net_sock, -1, net_packet, player - 1);
9781 								}
9782 							}
9783 
9784 						}
9785 						else
9786 						{
9787 							spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0);
9788 							messagePlayer(player, language[706]);
9789 						}
9790 					}
9791 					else
9792 					{
9793 						// bang
9794 						spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0);
9795 					}
9796 				}
9797 				else
9798 				{
9799 					// bang
9800 					//spawnBang(hit.x - cos(my->yaw)*2,hit.y - sin(my->yaw)*2,0);
9801 					playSoundPos(hit.x, hit.y, 183, 64);
9802 				}
9803 			}
9804 
9805 			// apply AoE shake effect
9806 			if ( (pose == MONSTER_POSE_GOLEM_SMASH || pose == PLAYER_POSE_GOLEM_SMASH) && target == nullptr )
9807 			{
9808 				list_t* shakeTargets = nullptr;
9809 				Entity* tmpEntity = nullptr;
9810 				getTargetsAroundEntity(this, hit.entity, STRIKERANGE, PI, MONSTER_TARGET_PLAYER, &shakeTargets);
9811 				if ( shakeTargets )
9812 				{
9813 					// shake nearby players that were not the primary target.
9814 					for ( node = shakeTargets->first; node != NULL; node = node->next )
9815 					{
9816 						tmpEntity = (Entity*)node->element;
9817 						playerhit = tmpEntity->skill[2];
9818 						if ( playerhit > 0 && multiplayer == SERVER )
9819 						{
9820 							strcpy((char*)net_packet->data, "SHAK");
9821 							net_packet->data[4] = 10; // turns into .1
9822 							net_packet->data[5] = 10;
9823 							net_packet->address.host = net_clients[playerhit - 1].host;
9824 							net_packet->address.port = net_clients[playerhit - 1].port;
9825 							net_packet->len = 6;
9826 							sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
9827 						}
9828 						else if ( playerhit == 0 )
9829 						{
9830 							cameravars[playerhit].shakex += .1;
9831 							cameravars[playerhit].shakey += 10;
9832 						}
9833 					}
9834 					//Free the list.
9835 					list_FreeAll(shakeTargets);
9836 					free(shakeTargets);
9837 				}
9838 			}
9839 		}
9840 	}
9841 	else
9842 	{
9843 		if ( player == -1 )
9844 		{
9845 			return;    // clients are NOT supposed to invoke monster attacks in the gamestate!
9846 		}
9847 		strcpy((char*)net_packet->data, "ATAK");
9848 		net_packet->data[4] = player;
9849 		net_packet->data[5] = pose;
9850 		net_packet->data[6] = charge;
9851 		net_packet->address.host = net_server.host;
9852 		net_packet->address.port = net_server.port;
9853 		net_packet->len = 7;
9854 		sendPacketSafe(net_sock, -1, net_packet, 0);
9855 	}
9856 }
9857 
9858 /*-------------------------------------------------------------------------------
9859 
9860 AC
9861 
9862 Returns armor class value from a Stat instance
9863 
9864 -------------------------------------------------------------------------------*/
9865 
AC(Stat * stat)9866 int AC(Stat* stat)
9867 {
9868 	if ( !stat )
9869 	{
9870 		return 0;
9871 	}
9872 
9873 	Entity* playerEntity = nullptr;
9874 	for ( int i = 0; i < MAXPLAYERS; ++i )
9875 	{
9876 		if ( stat && stats[i] == stat )
9877 		{
9878 			if ( players[i] && players[i]->entity )
9879 			{
9880 				playerEntity = players[i]->entity;
9881 				break;
9882 			}
9883 		}
9884 	}
9885 	int armor = statGetCON(stat, playerEntity);
9886 
9887 	if ( stat->helmet )
9888 	{
9889 		armor += stat->helmet->armorGetAC(stat);
9890 	}
9891 	if ( stat->breastplate )
9892 	{
9893 		armor += stat->breastplate->armorGetAC(stat);
9894 	}
9895 	if ( stat->gloves )
9896 	{
9897 		armor += stat->gloves->armorGetAC(stat);
9898 	}
9899 	if ( stat->shoes )
9900 	{
9901 		armor += stat->shoes->armorGetAC(stat);
9902 	}
9903 	if ( stat->shield )
9904 	{
9905 		armor += stat->shield->armorGetAC(stat);
9906 	}
9907 	if ( stat->cloak )
9908 	{
9909 		armor += stat->cloak->armorGetAC(stat);
9910 	}
9911 	if ( stat->ring )
9912 	{
9913 		armor += stat->ring->armorGetAC(stat);
9914 	}
9915 
9916 	if ( stat->type == TROLL || stat->type == RAT || stat->type == SPIDER || stat->type == CREATURE_IMP )
9917 	{
9918 		for ( int i = 0; i < MAXPLAYERS; ++i )
9919 		{
9920 			if ( stat == stats[i] ) // is a player stat pointer.
9921 			{
9922 				return armor; // shapeshifted players do not benefit from shield defense/proficiency.
9923 			}
9924 		}
9925 	}
9926 
9927 	if ( stat->shield )
9928 	{
9929 		int shieldskill = stat->PROFICIENCIES[PRO_SHIELD] / 25;
9930 		armor += shieldskill;
9931 		if ( stat->defending )
9932 		{
9933 			//messagePlayer(0, "shield up! +%d", 5 + stat->PROFICIENCIES[PRO_SHIELD] / 5);
9934 			armor += 5 + stat->PROFICIENCIES[PRO_SHIELD] / 5;
9935 		}
9936 	}
9937 
9938 	return armor;
9939 }
9940 
9941 /*-------------------------------------------------------------------------------
9942 
9943 Entity::teleport
9944 
9945 Teleports the given entity to the given (x, y) location on the map,
9946 in map coordinates. Will not teleport if the destination is an obstacle.
9947 
9948 -------------------------------------------------------------------------------*/
9949 
teleport(int tele_x,int tele_y)9950 bool Entity::teleport(int tele_x, int tele_y)
9951 {
9952 	int player = -1;
9953 
9954 	if ( behavior == &actPlayer )
9955 	{
9956 		player = skill[2];
9957 		if ( MFLAG_DISABLETELEPORT )
9958 		{
9959 			Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
9960 			// play sound effect
9961 			playSoundEntity(this, 77, 64);
9962 			messagePlayerColor(player, color, language[2381]);
9963 			return false;
9964 		}
9965 	}
9966 
9967 	if ( strstr(map.name, "Minotaur") || checkObstacle((tele_x << 4) + 8, (tele_y << 4) + 8, this, NULL) )
9968 	{
9969 		messagePlayer(player, language[707]);
9970 		return false;
9971 	}
9972 
9973 	// play sound effect
9974 	playSoundEntity(this, 77, 64);
9975 
9976 	// relocate entity
9977 	double oldx = x;
9978 	double oldy = y;
9979 	x = (tele_x << 4) + 8;
9980 	y = (tele_y << 4) + 8;
9981 	if ( entityInsideSomething(this) && getRace() != LICH_FIRE && getRace() != LICH_ICE )
9982 	{
9983 		x = oldx;
9984 		y = oldy;
9985 		if ( multiplayer == SERVER && player > 0 )
9986 		{
9987 			messagePlayer(player, language[707]);
9988 		}
9989 		return false;
9990 	}
9991 	updateAchievementBaitAndSwitch(player, true);
9992 	if ( multiplayer != CLIENT )
9993 	{
9994 		TileEntityList.updateEntity(*this);
9995 	}
9996 	if ( player > 0 && multiplayer == SERVER )
9997 	{
9998 		strcpy((char*)net_packet->data, "TELE");
9999 		net_packet->data[4] = tele_x;
10000 		net_packet->data[5] = tele_y;
10001 		SDLNet_Write16(static_cast<Sint16>(this->yaw * 180 / PI), &net_packet->data[6]);
10002 		net_packet->address.host = net_clients[player - 1].host;
10003 		net_packet->address.port = net_clients[player - 1].port;
10004 		net_packet->len = 8;
10005 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
10006 	}
10007 
10008 
10009 	if ( behavior == actMonster )
10010 	{
10011 		if ( getRace() != LICH && getRace() != DEVIL && getRace() != LICH_FIRE && getRace() != LICH_ICE )
10012 		{
10013 			//messagePlayer(0, "Resetting monster's path after teleport.");
10014 			monsterState = MONSTER_STATE_PATH;
10015 			/*if ( children.first != nullptr )
10016 			{
10017 				list_RemoveNode(children.first);
10018 			}*/
10019 		}
10020 	}
10021 
10022 	// play second sound effect
10023 	playSoundEntity(this, 77, 64);
10024 
10025 	if ( behavior == &actMonster )
10026 	{
10027 		achievementObserver.addEntityAchievementTimer(this, AchievementObserver::BARONY_ACH_TELEFRAG, 50, true, 0);
10028 		achievementObserver.addEntityAchievementTimer(this, AchievementObserver::BARONY_ACH_COWBOY_FROM_HELL, 150, true, 0);
10029 	}
10030 
10031 	return true;
10032 }
10033 
10034 /*-------------------------------------------------------------------------------
10035 
10036 Entity::teleportRandom
10037 
10038 Teleports the given entity to a random location on the map.
10039 
10040 -------------------------------------------------------------------------------*/
10041 
teleportRandom()10042 bool Entity::teleportRandom()
10043 {
10044 	int numlocations = 0;
10045 	int pickedlocation;
10046 	int player = -1;
10047 	if ( behavior == &actPlayer )
10048 	{
10049 		player = skill[2];
10050 		if ( MFLAG_DISABLETELEPORT )
10051 		{
10052 			Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
10053 			// play sound effect
10054 			playSoundEntity(this, 77, 64);
10055 			messagePlayerColor(player, color, language[2381]);
10056 			return false;
10057 		}
10058 
10059 	}
10060 	for ( int iy = 1; iy < map.height; ++iy )
10061 	{
10062 		for ( int ix = 1; ix < map.width; ++ix )
10063 		{
10064 			if ( !checkObstacle((ix << 4) + 8, (iy << 4) + 8, this, NULL) )
10065 			{
10066 				numlocations++;
10067 			}
10068 		}
10069 	}
10070 	if ( numlocations == 0 )
10071 	{
10072 		messagePlayer(player, language[708]);
10073 		return false;
10074 	}
10075 	pickedlocation = rand() % numlocations;
10076 	numlocations = 0;
10077 	for ( int iy = 1; iy < map.height; iy++ )
10078 	{
10079 		for ( int ix = 1; ix < map.width; ix++ )
10080 		{
10081 			if ( !checkObstacle((ix << 4) + 8, (iy << 4) + 8, this, NULL) )
10082 			{
10083 				if ( numlocations == pickedlocation )
10084 				{
10085 					teleport(ix, iy);
10086 					return true;
10087 				}
10088 				numlocations++;
10089 			}
10090 		}
10091 	}
10092 	return false;
10093 }
10094 
10095 /*-------------------------------------------------------------------------------
10096 
10097 Entity::teleportAroundEntity
10098 
10099 Teleports the given entity within a radius of a target entity.
10100 
10101 -------------------------------------------------------------------------------*/
10102 
teleportAroundEntity(Entity * target,int dist,int effectType)10103 bool Entity::teleportAroundEntity(Entity* target, int dist, int effectType)
10104 {
10105 	int numlocations = 0;
10106 	int pickedlocation;
10107 	int player = -1;
10108 	if ( !target )
10109 	{
10110 		return false;
10111 	}
10112 	int ty = static_cast<int>(std::floor(target->y)) >> 4;
10113 	int tx = static_cast<int>(std::floor(target->x)) >> 4;
10114 
10115 	if ( behavior == &actPlayer )
10116 	{
10117 		player = skill[2];
10118 		if ( MFLAG_DISABLETELEPORT )
10119 		{
10120 			Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
10121 			// play sound effect
10122 			playSoundEntity(this, 77, 64);
10123 			messagePlayerColor(player, color, language[2381]);
10124 			return false;
10125 		}
10126 	}
10127 
10128 	std::vector<std::pair<int, int>> goodspots;
10129 	std::vector<std::pair<int, int>> spotsBehindMonster;
10130 	bool forceSpot = false;
10131 	for ( int iy = std::max(1, ty - dist); !forceSpot && iy < std::min(ty + dist, static_cast<int>(map.height)); ++iy )
10132 	{
10133 		for ( int ix = std::max(1, tx - dist); !forceSpot && ix < std::min(tx + dist, static_cast<int>(map.width)); ++ix )
10134 		{
10135 			if ( !checkObstacle((ix << 4) + 8, (iy << 4) + 8, this, NULL) )
10136 			{
10137 				if ( behavior == &actPlayer && target->behavior == &actMonster )
10138 				{
10139 					// check LOS
10140 					Entity* ohit = hit.entity;
10141 
10142 					// pretend player has teleported, get the angle needed.
10143 					real_t tmpx = x;
10144 					real_t tmpy = y;
10145 					x = (ix << 4) + 8;
10146 					y = (iy << 4) + 8;
10147 					TileEntityList.updateEntity(*this); // important - lineTrace needs the TileEntityListUpdated.
10148 
10149 					real_t tangent = atan2(target->y - this->y, target->x - this->x);
10150 					lineTraceTarget(this, this->x, this->y, tangent, 64 * dist, 0, false, target);
10151 					if ( hit.entity == target && !entityInsideSomething(this) )
10152 					{
10153 						numlocations++;
10154 						real_t targetYaw = target->yaw;
10155 						while ( targetYaw >= 2 * PI )
10156 						{
10157 							targetYaw -= PI * 2;
10158 						}
10159 						while ( targetYaw < 0 )
10160 						{
10161 							targetYaw += PI * 2;
10162 						}
10163 						real_t yawDifference = (PI - abs(abs(tangent - targetYaw) - PI)) * 2;
10164 						if ( yawDifference >= 0 && yawDifference <= PI ) // 180 degree arc
10165 						{
10166 							spotsBehindMonster.push_back(std::make_pair(ix, iy));
10167 						}
10168 						else
10169 						{
10170 							goodspots.push_back(std::make_pair(ix, iy));
10171 						}
10172 					}
10173 					// restore coordinates.
10174 					x = tmpx;
10175 					y = tmpy;
10176 					TileEntityList.updateEntity(*this); // important - lineTrace needs the TileEntityListUpdated.
10177 					hit.entity = ohit;
10178 				}
10179 				else
10180 				{
10181 					if ( target->behavior == &actBomb && target->skill[22] == 1 && ix == tx && iy == ty ) // teleport receiver.
10182 					{
10183 						// directly on top, let's go there.
10184 						real_t tmpx = x;
10185 						real_t tmpy = y;
10186 						x = (ix << 4) + 8;
10187 						y = (iy << 4) + 8;
10188 						if ( !entityInsideSomething(this) )
10189 						{
10190 							forceSpot = true;
10191 							goodspots.clear();
10192 							goodspots.push_back(std::make_pair(ix, iy));
10193 							numlocations = 1;
10194 							// restore coordinates.
10195 							x = tmpx;
10196 							y = tmpy;
10197 							break;
10198 						}
10199 						// restore coordinates.
10200 						x = tmpx;
10201 						y = tmpy;
10202 					}
10203 					else
10204 					{
10205 						real_t tmpx = x;
10206 						real_t tmpy = y;
10207 						x = (ix << 4) + 8;
10208 						y = (iy << 4) + 8;
10209 						if ( !entityInsideSomething(this) )
10210 						{
10211 							goodspots.push_back(std::make_pair(ix, iy));
10212 							numlocations++;
10213 						}
10214 						// restore coordinates.
10215 						x = tmpx;
10216 						y = tmpy;
10217 					}
10218 				}
10219 			}
10220 		}
10221 	}
10222 	//messagePlayer(0, "locations: %d", numlocations);
10223 	if ( numlocations == 0 )
10224 	{
10225 		messagePlayer(player, language[708]);
10226 		return false;
10227 	}
10228 	std::pair<int, int> tmpPair;
10229 	if ( behavior == &actMonster || spotsBehindMonster.empty() )
10230 	{
10231 		tmpPair = goodspots[rand() % goodspots.size()];
10232 	}
10233 	else
10234 	{
10235 		tmpPair = spotsBehindMonster[rand() % spotsBehindMonster.size()];
10236 	}
10237 	tx = tmpPair.first;
10238 	ty = tmpPair.second;
10239 	if ( behavior == &actPlayer )
10240 	{
10241 		// pretend player has teleported, get the angle needed.
10242 		real_t tmpx = x;
10243 		real_t tmpy = y;
10244 		x = (tx << 4) + 8;
10245 		y = (ty << 4) + 8;
10246 		real_t tangent = atan2(target->y - this->y, target->x - this->x);
10247 		// restore coordinates.
10248 		x = tmpx;
10249 		y = tmpy;
10250 		this->yaw = tangent;
10251 		if ( target->behavior == &actMonster && target->monsterTarget == getUID() )
10252 		{
10253 			target->monsterReleaseAttackTarget();
10254 		}
10255 		if ( teleport(tx, ty) )
10256 		{
10257 			return true;
10258 		}
10259 		return false;
10260 	}
10261 
10262 	return teleport(tx, ty);
10263 }
10264 
10265 /*-------------------------------------------------------------------------------
10266 
10267 Entity::teleporterMove
10268 
10269 Teleports the given entity to the given (x, y) location on the map,
10270 in map coordinates. Will not teleport if the destination is an obstacle.
10271 
10272 -------------------------------------------------------------------------------*/
10273 
teleporterMove(int tele_x,int tele_y,int type)10274 bool Entity::teleporterMove(int tele_x, int tele_y, int type)
10275 {
10276 	int player = -1;
10277 
10278 	if ( behavior == &actPlayer )
10279 	{
10280 		player = skill[2];
10281 	}
10282 	// Can be inside entities?
10283 	//if ( strstr(map.name, "Minotaur") || checkObstacle((tele_x << 4) + 8, (tele_y << 4) + 8, this, NULL) )
10284 	//{
10285 	//	messagePlayer(player, language[707]);
10286 	//	return false;
10287 	//}
10288 
10289 	// relocate entity
10290 	double oldx = x;
10291 	double oldy = y;
10292 	x = (tele_x << 4) + 8;
10293 	y = (tele_y << 4) + 8;
10294 	/*if ( entityInsideSomething(this) )
10295 	{
10296 		x = oldx;
10297 		y = oldy;
10298 		if ( multiplayer == SERVER && player > 0 )
10299 		{
10300 			messagePlayer(player, language[707]);
10301 		}
10302 		return false;
10303 	}*/
10304 	if ( multiplayer != CLIENT )
10305 	{
10306 		TileEntityList.updateEntity(*this);
10307 	}
10308 	if ( player > 0 && multiplayer == SERVER )
10309 	{
10310 		strcpy((char*)net_packet->data, "TELM");
10311 		net_packet->data[4] = tele_x;
10312 		net_packet->data[5] = tele_y;
10313 		net_packet->data[6] = type;
10314 		net_packet->address.host = net_clients[player - 1].host;
10315 		net_packet->address.port = net_clients[player - 1].port;
10316 		net_packet->len = 7;
10317 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
10318 	}
10319 
10320 	// play sound effect
10321 	if ( type == 0 || type == 1 )
10322 	{
10323 		playSoundEntityLocal(this, 96, 64);
10324 	}
10325 	else if ( type == 2 )
10326 	{
10327 		playSoundEntityLocal(this, 154, 64);
10328 	}
10329 	return true;
10330 }
10331 
10332 /*-------------------------------------------------------------------------------
10333 
10334 Entity::awardXP
10335 
10336 Awards XP to the dest (ie killer) entity from the src (ie killed) entity
10337 
10338 -------------------------------------------------------------------------------*/
10339 
awardXP(Entity * src,bool share,bool root)10340 void Entity::awardXP(Entity* src, bool share, bool root)
10341 {
10342 	if ( !src )
10343 	{
10344 		return;
10345 	}
10346 
10347 	Stat* destStats = getStats();
10348 	Stat* srcStats = src->getStats();
10349 
10350 	if ( !destStats || !srcStats )
10351 	{
10352 		return;
10353 	}
10354 
10355 	if ( src->behavior == &actMonster
10356 		&& (src->monsterAllySummonRank != 0
10357 			|| src->monsterIsTinkeringCreation()) )
10358 	{
10359 		return; // summoned monster, no XP!
10360 	}
10361 	if ( srcStats->type == INCUBUS && !strncmp(srcStats->name, "inner demon", strlen("inner demon")) )
10362 	{
10363 		return;
10364 	}
10365 
10366 	int player = -1;
10367 	if ( behavior == &actPlayer )
10368 	{
10369 		player = skill[2];
10370 	}
10371 
10372 	// calculate XP gain
10373 	int baseXp = 10;
10374 	int xpGain = baseXp + rand() % std::max(1, baseXp) + std::max(0, srcStats->LVL - destStats->LVL) * baseXp;
10375 	if ( srcStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] > 0 )
10376 	{
10377 		int value = srcStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] - 1; // offset by 1 since 0 is nothing
10378 		double percent = value / 100.f;
10379 		xpGain = percent * xpGain;
10380 	}
10381 	if ( gameplayCustomManager.inUse() )
10382 	{
10383 		xpGain = (gameplayCustomManager.globalXPPercent / 100.f) * xpGain;
10384 	}
10385 
10386 	// save hit struct
10387 	hit_t tempHit;
10388 	tempHit.entity = hit.entity;
10389 	tempHit.mapx = hit.mapx;
10390 	tempHit.mapy = hit.mapy;
10391 	tempHit.side = hit.side;
10392 	tempHit.x = hit.x;
10393 	tempHit.y = hit.y;
10394 
10395 	int shareRange = gameplayCustomManager.inUse() ? gameplayCustomManager.xpShareRange : XPSHARERANGE;
10396 
10397 	// divide shares
10398 	if ( player >= 0 )
10399 	{
10400 		int numshares = 0;
10401 		Entity* shares[MAXPLAYERS];
10402 		int c;
10403 
10404 		for ( c = 0; c < MAXPLAYERS; ++c )
10405 		{
10406 			shares[c] = nullptr;
10407 		}
10408 
10409 		// find other players to divide shares with
10410 		node_t* node;
10411 		for ( node = map.creatures->first; node != nullptr; node = node->next ) //Since only looking at players, this should just iterate over players[]
10412 		{
10413 			Entity* entity = (Entity*)node->element;
10414 			if ( entity == this )
10415 			{
10416 				continue;
10417 			}
10418 			if ( entity && entity->behavior == &actPlayer )
10419 			{
10420 				if ( entityDist(this, entity) < shareRange )
10421 				{
10422 					++numshares;
10423 					shares[numshares] = entity;
10424 					if ( numshares == MAXPLAYERS - 1 )
10425 					{
10426 						break;
10427 					}
10428 				}
10429 			}
10430 		}
10431 
10432 		// divide value of each share
10433 		if ( numshares )
10434 		{
10435 			xpGain /= numshares;
10436 		}
10437 
10438 		// award XP to everyone else in the group
10439 		if ( share )
10440 		{
10441 			for ( c = 0; c < MAXPLAYERS; c++ )
10442 			{
10443 				if ( shares[c] )
10444 				{
10445 					shares[c]->awardXP(src, false, false);
10446 				}
10447 			}
10448 
10449 			if ( this->behavior == &actPlayer )
10450 			{
10451 				if ( stats[this->skill[2]] )
10452 				{
10453 					// award XP to player's followers.
10454 					int numFollowers = list_Size(&stats[this->skill[2]]->FOLLOWERS);
10455 					for ( node = stats[this->skill[2]]->FOLLOWERS.first; node != nullptr; node = node->next )
10456 					{
10457 						Entity* follower = nullptr;
10458 						if ( (Uint32*)node->element )
10459 						{
10460 							follower = uidToEntity(*((Uint32*)node->element));
10461 						}
10462 						if ( follower && entityDist(this, follower) < shareRange && follower != src )
10463 						{
10464 							if ( follower->monsterIsTinkeringCreation() )
10465 							{
10466 								--numFollowers; // tinkering creation don't penalise XP.
10467 								continue;
10468 							}
10469 							Stat* followerStats = follower->getStats();
10470 							if ( followerStats )
10471 							{
10472 								//int xpDivide = std::min(std::max(1, numFollowers), 4); // 1 - 4 depending on followers.
10473 								if ( follower->monsterAllySummonRank != 0 && numshares > 0 )
10474 								{
10475 									followerStats->EXP += (xpGain * numshares); // summoned monsters aren't penalised XP.
10476 								}
10477 								else
10478 								{
10479 									followerStats->EXP += (xpGain);
10480 								}
10481 								//messagePlayer(0, "monster got %d xp", xpGain);
10482 							}
10483 						}
10484 					}
10485 				}
10486 			}
10487 		}
10488 
10489 	}
10490 
10491 	// award XP to main victor
10492 	if ( !this->monsterIsTinkeringCreation() )
10493 	{
10494 		destStats->EXP += xpGain;
10495 	}
10496 
10497 	if ( (srcStats->type == LICH || srcStats->type == LICH_FIRE || srcStats->type == LICH_ICE) && root )
10498 	{
10499 		if ( destStats->type == CREATURE_IMP
10500 			|| destStats->type == DEMON
10501 			|| (destStats->type == AUTOMATON && !strcmp(destStats->name, "corrupted automaton")) )
10502 		{
10503 			if ( !flags[USERFLAG2] )
10504 			{
10505 				for ( int c = 0; c < MAXPLAYERS; c++ )
10506 				{
10507 					steamAchievementClient(c, "BARONY_ACH_OWN_WORST_ENEMY");
10508 				}
10509 			}
10510 		}
10511 	}
10512 
10513 	if ( root ) // global stats
10514 	{
10515 		if ( src->behavior == &actPlayer && this->behavior == &actMonster )
10516 		{
10517 			achievementObserver.updateGlobalStat(getIndexForDeathType(destStats->type));
10518 		}
10519 		else if ( src->behavior == &actMonster && this->behavior == &actPlayer )
10520 		{
10521 			if ( srcStats->type == LICH )
10522 			{
10523 				achievementObserver.updateGlobalStat(STEAM_GSTAT_HERX_SLAIN);
10524 			}
10525 			else if ( srcStats->type == LICH_FIRE )
10526 			{
10527 				achievementObserver.updateGlobalStat(STEAM_GSTAT_TWINSFIRE_SLAIN);
10528 			}
10529 			else if ( srcStats->type == LICH_ICE )
10530 			{
10531 				achievementObserver.updateGlobalStat(STEAM_GSTAT_TWINSICE_SLAIN);
10532 			}
10533 			else if ( srcStats->type == DEVIL )
10534 			{
10535 				achievementObserver.updateGlobalStat(STEAM_GSTAT_BAPHOMET_SLAIN);
10536 			}
10537 			else if ( srcStats->type == MINOTAUR )
10538 			{
10539 				achievementObserver.updateGlobalStat(STEAM_GSTAT_MINOTAURS_SLAIN);
10540 			}
10541 			else if ( srcStats->type == SHOPKEEPER )
10542 			{
10543 				achievementObserver.updateGlobalStat(STEAM_GSTAT_SHOPKEEPERS_SLAIN);
10544 			}
10545 		}
10546 	}
10547 
10548 
10549 	// award bonus XP and update kill counters
10550 	if ( player >= 0 )
10551 	{
10552 		if ( root == false )
10553 		{
10554 			updateAchievementThankTheTank(player, src, true);
10555 		}
10556 		if ( currentlevel >= 25 && srcStats->type == MINOTAUR )
10557 		{
10558 			for ( int c = 0; c < MAXPLAYERS; c++ )
10559 			{
10560 				steamAchievementClient(c, "BARONY_ACH_REUNITED");
10561 			}
10562 		}
10563 		if ( srcStats->type == SHADOW && root )
10564 		{
10565 			std::string name = "Shadow of ";
10566 			name += stats[player]->name;
10567 			if ( name.compare(srcStats->name) == 0 )
10568 			{
10569 				steamAchievementClient(player, "BARONY_ACH_KNOW_THYSELF");
10570 			}
10571 		}
10572 		if ( srcStats->LVL >= 25 && root
10573 			&& destStats->HP <= 5 && checkEnemy(src) )
10574 		{
10575 			steamAchievementClient(player, "BARONY_ACH_BUT_A_SCRATCH");
10576 		}
10577 		if ( srcStats->EFFECTS[EFF_PARALYZED] )
10578 		{
10579 			serverUpdatePlayerGameplayStats(player, STATISTICS_SITTING_DUCK, 1);
10580 		}
10581 		if ( root )
10582 		{
10583 			achievementObserver.awardAchievementIfActive(player, src, AchievementObserver::BARONY_ACH_TELEFRAG);
10584 			if ( stats[player]->playerRace == RACE_INCUBUS && stats[player]->appearance == 0 )
10585 			{
10586 				achievementObserver.playerAchievements[player].checkTraditionKill(this, src);
10587 			}
10588 			if ( stats[player]->type == SPIDER && srcStats->EFFECTS[EFF_WEBBED] )
10589 			{
10590 				steamStatisticUpdateClient(player, STEAM_STAT_MANY_PEDI_PALP, STEAM_STAT_INT, 1);
10591 			}
10592 
10593 			bool guerillaRadio = false;
10594 			if ( src->monsterTarget != 0 )
10595 			{
10596 				Entity* wasTargeting = uidToEntity(src->monsterTarget);
10597 				if ( wasTargeting )
10598 				{
10599 					if ( src->monsterState == MONSTER_STATE_ATTACK && wasTargeting->getMonsterTypeFromSprite() == DUMMYBOT )
10600 					{
10601 						steamStatisticUpdateClient(player, STEAM_STAT_RAGE_AGAINST, STEAM_STAT_INT, 1);
10602 					}
10603 					else if ( wasTargeting->behavior == &actDecoyBox )
10604 					{
10605 						guerillaRadio = true;
10606 					}
10607 				}
10608 			}
10609 			if ( !guerillaRadio )
10610 			{
10611 				Entity* noisemaker = uidToEntity(src->monsterLastDistractedByNoisemaker);
10612 				if ( noisemaker && noisemaker->behavior == &actDecoyBox
10613 					&& entityDist(noisemaker, src) < TOUCHRANGE )
10614 				{
10615 					guerillaRadio = true;
10616 				}
10617 			}
10618 
10619 			if ( guerillaRadio )
10620 			{
10621 				steamStatisticUpdateClient(player, STEAM_STAT_GUERILLA_RADIO, STEAM_STAT_INT, 1);
10622 				if ( rand() % 5 == 0 || (uidToEntity(src->monsterTarget) != this && rand() % 3 == 0) )
10623 				{
10624 					this->increaseSkill(PRO_LOCKPICKING);
10625 				}
10626 			}
10627 		}
10628 
10629 		if ( player == 0 )
10630 		{
10631 			if ( srcStats->type == LICH )
10632 			{
10633 				kills[LICH] = 1;
10634 			}
10635 			else if ( srcStats->type == LICH_FIRE )
10636 			{
10637 				kills[LICH]++;
10638 			}
10639 			else if ( srcStats->type == LICH_ICE )
10640 			{
10641 				kills[LICH]++;
10642 			}
10643 			else
10644 			{
10645 				kills[srcStats->type]++;
10646 			}
10647 		}
10648 		else if ( multiplayer == SERVER && player > 0 )
10649 		{
10650 			// inform client of kill
10651 			strcpy((char*)net_packet->data, "MKIL");
10652 			if ( srcStats->type == LICH_FIRE || srcStats->type == LICH_ICE )
10653 			{
10654 				net_packet->data[4] = LICH;
10655 			}
10656 			else
10657 			{
10658 				net_packet->data[4] = srcStats->type;
10659 			}
10660 			net_packet->address.host = net_clients[player - 1].host;
10661 			net_packet->address.port = net_clients[player - 1].port;
10662 			net_packet->len = 5;
10663 			sendPacketSafe(net_sock, -1, net_packet, player - 1);
10664 
10665 			// update client attributes
10666 			strcpy((char*)net_packet->data, "ATTR");
10667 			net_packet->data[4] = clientnum;
10668 			net_packet->data[5] = (Sint8)destStats->STR;
10669 			net_packet->data[6] = (Sint8)destStats->DEX;
10670 			net_packet->data[7] = (Sint8)destStats->CON;
10671 			net_packet->data[8] = (Sint8)destStats->INT;
10672 			net_packet->data[9] = (Sint8)destStats->PER;
10673 			net_packet->data[10] = (Sint8)destStats->CHR;
10674 			net_packet->data[11] = (Sint8)destStats->EXP;
10675 			net_packet->data[12] = (Sint8)destStats->LVL;
10676 			SDLNet_Write16((Sint16)destStats->HP, &net_packet->data[13]);
10677 			SDLNet_Write16((Sint16)destStats->MAXHP, &net_packet->data[15]);
10678 			SDLNet_Write16((Sint16)destStats->MP, &net_packet->data[17]);
10679 			SDLNet_Write16((Sint16)destStats->MAXMP, &net_packet->data[19]);
10680 			net_packet->address.host = net_clients[player - 1].host;
10681 			net_packet->address.port = net_clients[player - 1].port;
10682 			net_packet->len = 21;
10683 			sendPacketSafe(net_sock, -1, net_packet, player - 1);
10684 		}
10685 	}
10686 	else
10687 	{
10688 		Entity* leader = nullptr;
10689 
10690 		// NPCs with leaders award equal XP to their master (so NPCs don't steal XP gainz)
10691 
10692 		if ( (leader = uidToEntity(destStats->leader_uid)) != NULL )
10693 		{
10694 			if ( this->monsterIsTinkeringCreation() )
10695 			{
10696 				if ( rand() % 10 == 0 )
10697 				{
10698 					leader->increaseSkill(PRO_LOCKPICKING);
10699 				}
10700 				if ( root && leader->behavior == &actPlayer && srcStats->type == MINOTAUR )
10701 				{
10702 					steamAchievementClient(leader->skill[2], "BARONY_ACH_TIME_TO_PLAN");
10703 				}
10704 			}
10705 			else
10706 			{
10707 				leader->increaseSkill(PRO_LEADERSHIP);
10708 			}
10709 			leader->awardXP(src, true, false);
10710 
10711 			if ( leader->behavior == &actPlayer )
10712 			{
10713 				if ( destStats->monsterIsCharmed == 1 )
10714 				{
10715 					// charmed follower killed something.
10716 					steamStatisticUpdateClient(leader->skill[2], STEAM_STAT_KILL_COMMAND, STEAM_STAT_INT, 1);
10717 				}
10718 				if ( destStats->type == INSECTOID )
10719 				{
10720 					if ( leader->getStats()->playerRace == RACE_INSECTOID && leader->getStats()->appearance == 0 )
10721 					{
10722 						steamStatisticUpdateClient(leader->skill[2], STEAM_STAT_MONARCH, STEAM_STAT_INT, 1);
10723 					}
10724 				}
10725 			}
10726 		}
10727 	}
10728 
10729 	// restore hit struct
10730 	if ( root )
10731 	{
10732 		hit.entity = tempHit.entity;
10733 		hit.mapx = tempHit.mapx;
10734 		hit.mapy = tempHit.mapy;
10735 		hit.side = tempHit.side;
10736 		hit.x = tempHit.x;
10737 		hit.y = tempHit.y;
10738 	}
10739 }
10740 
10741 /*-------------------------------------------------------------------------------
10742 
10743 Entity::checkEnemy
10744 
10745 Returns true if my and your are enemies, otherwise returns false
10746 
10747 -------------------------------------------------------------------------------*/
10748 
checkEnemy(Entity * your)10749 bool Entity::checkEnemy(Entity* your)
10750 {
10751 	if ( !your )
10752 	{
10753 		return false;
10754 	}
10755 
10756 	bool result;
10757 
10758 	Stat* myStats = getStats();
10759 	Stat* yourStats = your->getStats();
10760 
10761 	if ( !myStats || !yourStats )
10762 	{
10763 		return false;
10764 	}
10765 	if ( everybodyfriendly )   // friendly monsters mode
10766 	{
10767 		return false;
10768 	}
10769 
10770 	if ( (your->behavior == &actPlayer || your->behavior == &actPlayerLimb) && (behavior == &actPlayer || behavior == &actPlayerLimb) )
10771 	{
10772 		return false;
10773 	}
10774 
10775 	if ( behavior == &actPlayer && your->behavior == &actMonster && yourStats->monsterForceAllegiance != Stat::MONSTER_FORCE_ALLEGIANCE_NONE )
10776 	{
10777 		if ( yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ALLY || yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
10778 		{
10779 			return false;
10780 		}
10781 		else if ( yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
10782 		{
10783 			return true;
10784 		}
10785 	}
10786 	else if ( your->behavior == &actPlayer && behavior == &actMonster && myStats->monsterForceAllegiance != Stat::MONSTER_FORCE_ALLEGIANCE_NONE )
10787 	{
10788 		if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ALLY || myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
10789 		{
10790 			return false;
10791 		}
10792 		else if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
10793 		{
10794 			return true;
10795 		}
10796 	}
10797 
10798 	if ( myStats->type == GYROBOT )
10799 	{
10800 		return false;
10801 	}
10802 
10803 	if ( (myStats->type == SHOPKEEPER && myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0)
10804 		|| (yourStats->type == SHOPKEEPER && yourStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0) )
10805 	{
10806 		return false;
10807 	}
10808 
10809 	if ( myStats->type == HUMAN && (yourStats->type == AUTOMATON && !strncmp(yourStats->name, "corrupted automaton", 19)) )
10810 	{
10811 		return true;
10812 	}
10813 	else if ( (yourStats->type == HUMAN || your->behavior == &actPlayer) && (myStats->type == AUTOMATON && !strncmp(myStats->name, "corrupted automaton", 19)) )
10814 	{
10815 		return true;
10816 	}
10817 	else if ( your->behavior == &actPlayer && myStats->type == CREATURE_IMP
10818 		&& (!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
10819 	{
10820 		if ( this->monsterAllyGetPlayerLeader() )
10821 		{
10822 			return false;
10823 		}
10824 		return true;
10825 	}
10826 	else if ( behavior == &actPlayer && yourStats->type == CREATURE_IMP
10827 		&& (!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
10828 	{
10829 		if ( your->monsterAllyGetPlayerLeader() )
10830 		{
10831 			return false;
10832 		}
10833 		return true;
10834 	}
10835 	else if ( your->behavior == &actPlayer && myStats->type == VAMPIRE && !strncmp(myStats->name, "Bram Kindly", 11) )
10836 	{
10837 		return true;
10838 	}
10839 	else if ( behavior == &actPlayer && yourStats->type == VAMPIRE && !strncmp(yourStats->name, "Bram Kindly", 11) )
10840 	{
10841 		return true;
10842 	}
10843 	else if ( behavior == &actMonster && myStats->type == INCUBUS && !strncmp(myStats->name, "inner demon", strlen("inner demon")) )
10844 	{
10845 		Entity* parentEntity = uidToEntity(this->parent);
10846 		if ( parentEntity != your )
10847 		{
10848 			return true;
10849 		}
10850 		else
10851 		{
10852 			return false;
10853 		}
10854 	}
10855 	else if ( behavior == &actPlayer && yourStats->type == INCUBUS && !strncmp(yourStats->name, "inner demon", strlen("inner demon")) )
10856 	{
10857 		Entity* parentEntity = uidToEntity(your->parent);
10858 		if ( parentEntity != this )
10859 		{
10860 			return true;
10861 		}
10862 		else
10863 		{
10864 			return false;
10865 		}
10866 	}
10867 	else if ( behavior == &actMonster && your->behavior == &actMonster && yourStats->type == INCUBUS && !strncmp(yourStats->name, "inner demon", strlen("inner demon")) )
10868 	{
10869 		Entity* illusionTauntingThisEntity = uidToEntity(static_cast<Uint32>(your->monsterIllusionTauntingThisUid));
10870 		if ( illusionTauntingThisEntity == this )
10871 		{
10872 			return true;
10873 		}
10874 	}
10875 
10876 	// if you have a leader, check whether we are enemies instead
10877 	Entity* yourLeader = NULL;
10878 	if ( yourStats->leader_uid )
10879 	{
10880 		yourLeader = uidToEntity(yourStats->leader_uid);
10881 	}
10882 	if ( yourLeader )
10883 	{
10884 		Stat* yourLeaderStats = yourLeader->getStats();
10885 		if ( yourLeaderStats )
10886 		{
10887 			if ( yourLeader == this )
10888 			{
10889 				return false;
10890 			}
10891 			else
10892 			{
10893 				return checkEnemy(yourLeader);
10894 			}
10895 		}
10896 	}
10897 
10898 	// first find out if I have a leader
10899 	Entity* myLeader = NULL;
10900 	if ( myStats->leader_uid )
10901 	{
10902 		myLeader = uidToEntity(myStats->leader_uid);
10903 	}
10904 	if ( myLeader )
10905 	{
10906 		Stat* myLeaderStats = myLeader->getStats();
10907 		if ( myLeaderStats )
10908 		{
10909 			if ( myLeader == your )
10910 			{
10911 				result = false;
10912 			}
10913 			else
10914 			{
10915 				return myLeader->checkEnemy(your);
10916 			}
10917 		}
10918 		else
10919 		{
10920 			// invalid leader, default to allegiance table
10921 			result = swornenemies[myStats->type][yourStats->type];
10922 		}
10923 	}
10924 	else
10925 	{
10926 		node_t* t_node;
10927 		bool foundFollower = false;
10928 		for ( t_node = myStats->FOLLOWERS.first; t_node != NULL; t_node = t_node->next )
10929 		{
10930 			Uint32* uid = (Uint32*)t_node->element;
10931 			if ( *uid == your->uid )
10932 			{
10933 				foundFollower = true;
10934 				result = false;
10935 				break;
10936 			}
10937 		}
10938 		if ( !foundFollower )
10939 		{
10940 			// no leader, default to allegiance table
10941 			result = swornenemies[myStats->type][yourStats->type];
10942 
10943 			// player exceptions to table go here.
10944 			if ( behavior == &actPlayer && myStats->type != HUMAN )
10945 			{
10946 				result = swornenemies[HUMAN][yourStats->type];
10947 				if ( (yourStats->type == HUMAN || yourStats->type == SHOPKEEPER) && myStats->type != AUTOMATON )
10948 				{
10949 					// enemies.
10950 					result = true;
10951 				}
10952 				else
10953 				{
10954 					switch ( myStats->type )
10955 					{
10956 						case SKELETON:
10957 							if ( yourStats->type == GHOUL )
10958 							{
10959 								result = false;
10960 							}
10961 							break;
10962 						case RAT:
10963 							if ( yourStats->type == RAT )
10964 							{
10965 								result = false;
10966 							}
10967 							break;
10968 						case SPIDER:
10969 							if ( yourStats->type == SPIDER
10970 								|| yourStats->type == SCARAB || yourStats->type == SCORPION )
10971 							{
10972 								result = false;
10973 							}
10974 							break;
10975 						case TROLL:
10976 							if ( yourStats->type == TROLL )
10977 							{
10978 								result = false;
10979 							}
10980 							break;
10981 						case CREATURE_IMP:
10982 							if ( yourStats->type == CREATURE_IMP )
10983 							{
10984 								result = false;
10985 							}
10986 							break;
10987 						case GOBLIN:
10988 							if ( yourStats->type == GOBLIN )
10989 							{
10990 								result = false;
10991 							}
10992 							break;
10993 						case GOATMAN:
10994 							if ( yourStats->type == GOATMAN )
10995 							{
10996 								result = false;
10997 							}
10998 							break;
10999 						case INCUBUS:
11000 						case SUCCUBUS:
11001 							if ( yourStats->type == SUCCUBUS || yourStats->type == INCUBUS )
11002 							{
11003 								result = false;
11004 							}
11005 							break;
11006 						case INSECTOID:
11007 							if ( yourStats->type == SCARAB || yourStats->type == INSECTOID || yourStats->type == SCORPION )
11008 							{
11009 								result = false;
11010 							}
11011 							break;
11012 						case VAMPIRE:
11013 							if ( yourStats->type == VAMPIRE )
11014 							{
11015 								result = false;
11016 							}
11017 							break;
11018 						case AUTOMATON:
11019 							if ( yourStats->type == INCUBUS || yourStats->type == SUCCUBUS )
11020 							{
11021 								result = false;
11022 							}
11023 							if ( yourStats->type == SHOPKEEPER )
11024 							{
11025 								result = swornenemies[SHOPKEEPER][AUTOMATON];
11026 							}
11027 							break;
11028 						default:
11029 							break;
11030 					}
11031 				}
11032 			}
11033 			else if ( behavior == &actMonster && your->behavior == &actPlayer && yourStats->type != HUMAN )
11034 			{
11035 				result = swornenemies[myStats->type][HUMAN];
11036 				if ( (myStats->type == HUMAN || myStats->type == SHOPKEEPER) && yourStats->type != AUTOMATON )
11037 				{
11038 					// enemies.
11039 					result = true;
11040 				}
11041 				else
11042 				{
11043 					switch ( yourStats->type )
11044 					{
11045 						case SKELETON:
11046 							if ( myStats->type == GHOUL )
11047 							{
11048 								result = false;
11049 							}
11050 							break;
11051 						case RAT:
11052 							if ( myStats->type == RAT )
11053 							{
11054 								result = false;
11055 							}
11056 							break;
11057 						case SPIDER:
11058 							if ( myStats->type == SPIDER
11059 								|| myStats->type == SCARAB || myStats->type == SCORPION )
11060 							{
11061 								result = false;
11062 							}
11063 							break;
11064 						case TROLL:
11065 							if ( myStats->type == TROLL )
11066 							{
11067 								result = false;
11068 							}
11069 							break;
11070 						case CREATURE_IMP:
11071 							if ( myStats->type == CREATURE_IMP )
11072 							{
11073 								result = false;
11074 							}
11075 							break;
11076 						case GOBLIN:
11077 							if ( myStats->type == GOBLIN )
11078 							{
11079 								result = false;
11080 							}
11081 							break;
11082 						case GOATMAN:
11083 							if ( myStats->type == GOATMAN )
11084 							{
11085 								result = false;
11086 							}
11087 							break;
11088 						case INCUBUS:
11089 						case SUCCUBUS:
11090 							if ( myStats->type == SUCCUBUS || myStats->type == INCUBUS )
11091 							{
11092 								result = false;
11093 							}
11094 							break;
11095 						case INSECTOID:
11096 							if ( myStats->type == SCARAB
11097 								|| myStats->type == INSECTOID || myStats->type == SCORPION )
11098 							{
11099 								result = false;
11100 							}
11101 							break;
11102 						case VAMPIRE:
11103 							if ( myStats->type == VAMPIRE )
11104 							{
11105 								result = false;
11106 							}
11107 							break;
11108 						case AUTOMATON:
11109 							if ( myStats->type == INCUBUS || myStats->type == SUCCUBUS )
11110 							{
11111 								result = false;
11112 							}
11113 							if ( myStats->type == SHOPKEEPER )
11114 							{
11115 								result = swornenemies[SHOPKEEPER][AUTOMATON];
11116 							}
11117 							break;
11118 						default:
11119 							break;
11120 					}
11121 				}
11122 			}
11123 		}
11124 	}
11125 
11126 	// confused monsters mistake their allegiances
11127 	if ( myStats->EFFECTS[EFF_CONFUSED] )
11128 	{
11129 		if ( myStats->type == AUTOMATON && yourStats->type == AUTOMATON
11130 			&& !strncmp(myStats->name, "corrupted automaton", strlen("corrupted automaton")) )
11131 		{
11132 			// these guys ignore themselves when confused..
11133 		}
11134 		else
11135 		{
11136 			result = (result == false);
11137 		}
11138 	}
11139 
11140 	return result;
11141 }
11142 
11143 /*-------------------------------------------------------------------------------
11144 
11145 Entity::checkFriend
11146 
11147 Returns true if my and your are friends, otherwise returns false
11148 
11149 -------------------------------------------------------------------------------*/
11150 
checkFriend(Entity * your)11151 bool Entity::checkFriend(Entity* your)
11152 {
11153 	bool result = false;
11154 
11155 	if ( !your )
11156 	{
11157 		return false;    //Equivalent to if (!myStats || !yourStats)
11158 	}
11159 
11160 	Stat* myStats = getStats();
11161 	Stat* yourStats = your->getStats();
11162 
11163 	if ( !myStats || !yourStats )
11164 	{
11165 		return false;
11166 	}
11167 
11168 	if ( (your->behavior == &actPlayer || your->behavior == &actPlayerLimb) && (behavior == &actPlayer || behavior == &actPlayerLimb) )
11169 	{
11170 		return true;
11171 	}
11172 
11173 	if ( behavior == &actPlayer && your->behavior == &actMonster && yourStats->monsterForceAllegiance != Stat::MONSTER_FORCE_ALLEGIANCE_NONE )
11174 	{
11175 		if ( yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ALLY || yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
11176 		{
11177 			return true;
11178 		}
11179 		else if ( yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
11180 		{
11181 			return false;
11182 		}
11183 	}
11184 	else if ( your->behavior == &actPlayer && behavior == &actMonster && myStats->monsterForceAllegiance != Stat::MONSTER_FORCE_ALLEGIANCE_NONE )
11185 	{
11186 		if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ALLY || myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
11187 		{
11188 			return true;
11189 		}
11190 		else if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
11191 		{
11192 			return false;
11193 		}
11194 	}
11195 
11196 
11197 	if ( (myStats->type == SHOPKEEPER && myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0)
11198 		|| (yourStats->type == SHOPKEEPER && yourStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0) )
11199 	{
11200 		return false;
11201 	}
11202 
11203 	if ( myStats->type == GYROBOT )
11204 	{
11205 		return true;
11206 	}
11207 
11208 	if ( (myStats->type == HUMAN || behavior == &actPlayer) && (yourStats->type == AUTOMATON && !strncmp(yourStats->name, "corrupted automaton", 19)) )
11209 	{
11210 		return false;
11211 	}
11212 	else if ( (yourStats->type == HUMAN || your->behavior == &actPlayer) && (myStats->type == AUTOMATON && !strncmp(myStats->name, "corrupted automaton", 19)) )
11213 	{
11214 		return false;
11215 	}
11216 	else if ( your->behavior == &actPlayer && myStats->type == CREATURE_IMP
11217 		&& (!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
11218 	{
11219 		if ( this->monsterAllyGetPlayerLeader() )
11220 		{
11221 			return true;
11222 		}
11223 		return false;
11224 	}
11225 	else if ( behavior == &actPlayer && yourStats->type == CREATURE_IMP
11226 		&& (!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
11227 	{
11228 		if ( your->monsterAllyGetPlayerLeader() )
11229 		{
11230 			return true;
11231 		}
11232 		return false;
11233 	}
11234 	else if ( your->behavior == &actPlayer && myStats->type == VAMPIRE && !strncmp(myStats->name, "Bram Kindly", 11) )
11235 	{
11236 		return false;
11237 	}
11238 	else if ( behavior == &actPlayer && yourStats->type == VAMPIRE && !strncmp(yourStats->name, "Bram Kindly", 11) )
11239 	{
11240 		return false;
11241 	}
11242 	else if ( behavior == &actMonster && myStats->type == INCUBUS && !strncmp(myStats->name, "inner demon", strlen("inner demon")) )
11243 	{
11244 		Entity* parentEntity = uidToEntity(this->parent);
11245 		if ( parentEntity == your )
11246 		{
11247 			return true;
11248 		}
11249 		else
11250 		{
11251 			return false;
11252 		}
11253 	}
11254 	else if ( behavior == &actPlayer && your->behavior == &actMonster && yourStats->type == INCUBUS && !strncmp(yourStats->name, "inner demon", strlen("inner demon")) )
11255 	{
11256 		Entity* parentEntity = uidToEntity(your->parent);
11257 		if ( parentEntity == this )
11258 		{
11259 			return true;
11260 		}
11261 		else
11262 		{
11263 			return false;
11264 		}
11265 	}
11266 	else if ( behavior == &actMonster && your->behavior == &actMonster && yourStats->type == INCUBUS && !strncmp(yourStats->name, "inner demon", strlen("inner demon")) )
11267 	{
11268 		Entity* illusionTauntingThisEntity = uidToEntity(static_cast<Uint32>(your->monsterIllusionTauntingThisUid));
11269 		if ( illusionTauntingThisEntity == this )
11270 		{
11271 			return false;
11272 		}
11273 	}
11274 
11275 	// if you have a leader, check whether we are friends instead
11276 	Entity* yourLeader = NULL;
11277 	if ( yourStats->leader_uid )
11278 	{
11279 		yourLeader = uidToEntity(yourStats->leader_uid);
11280 	}
11281 	if ( yourLeader )
11282 	{
11283 		Stat* yourLeaderStats = yourLeader->getStats();
11284 		if ( yourLeaderStats )
11285 		{
11286 			if ( yourLeader == this )
11287 			{
11288 				return true;
11289 			}
11290 			else
11291 			{
11292 				return checkFriend(yourLeader);
11293 			}
11294 		}
11295 	}
11296 
11297 	// first find out if I have a leader
11298 	Entity* myLeader = NULL;
11299 	if ( myStats->leader_uid )
11300 	{
11301 		myLeader = uidToEntity(myStats->leader_uid);
11302 	}
11303 	if ( myLeader )
11304 	{
11305 		Stat* myLeaderStats = myLeader->getStats();
11306 		if ( myLeaderStats )
11307 		{
11308 			if ( myLeader == your )
11309 			{
11310 				result = true;
11311 			}
11312 			else
11313 			{
11314 				return myLeader->checkFriend(your);
11315 			}
11316 		}
11317 		else
11318 		{
11319 			// invalid leader, default to allegiance table
11320 			result = monsterally[myStats->type][yourStats->type];
11321 		}
11322 	}
11323 	else
11324 	{
11325 		node_t* t_node;
11326 		bool foundFollower = false;
11327 		for ( t_node = myStats->FOLLOWERS.first; t_node != NULL; t_node = t_node->next )
11328 		{
11329 			Uint32* uid = (Uint32*)t_node->element;
11330 			if ( *uid == your->uid )
11331 			{
11332 				foundFollower = true;
11333 				result = true;
11334 				break;
11335 			}
11336 		}
11337 		if ( !foundFollower )
11338 		{
11339 			// no leader, default to allegiance table
11340 			result = monsterally[myStats->type][yourStats->type];
11341 
11342 			// player exceptions to table go here.
11343 			if ( behavior == &actPlayer && myStats->type != HUMAN )
11344 			{
11345 				result = monsterally[HUMAN][yourStats->type];
11346 				if ( (yourStats->type == HUMAN || yourStats->type == SHOPKEEPER) && myStats->type != AUTOMATON )
11347 				{
11348 					result = false;
11349 				}
11350 				else
11351 				{
11352 					result = false;
11353 					switch ( myStats->type )
11354 					{
11355 						case SKELETON:
11356 							if ( yourStats->type == GHOUL )
11357 							{
11358 								result = true;
11359 							}
11360 							break;
11361 						case RAT:
11362 							if ( yourStats->type == RAT )
11363 							{
11364 								result = true;
11365 							}
11366 							break;
11367 						case SPIDER:
11368 							if ( yourStats->type == SPIDER || yourStats->type == SCARAB || yourStats->type == SCORPION )
11369 							{
11370 								result = true;
11371 							}
11372 							break;
11373 						case TROLL:
11374 							if ( yourStats->type == TROLL )
11375 							{
11376 								result = true;
11377 							}
11378 							break;
11379 						case CREATURE_IMP:
11380 							if ( yourStats->type == CREATURE_IMP )
11381 							{
11382 								result = true;
11383 							}
11384 							break;
11385 						case GOBLIN:
11386 							if ( yourStats->type == GOBLIN )
11387 							{
11388 								result = true;
11389 							}
11390 							break;
11391 						case GOATMAN:
11392 							if ( yourStats->type == GOATMAN )
11393 							{
11394 								result = true;
11395 							}
11396 							break;
11397 						case INCUBUS:
11398 						case SUCCUBUS:
11399 							if ( yourStats->type == SUCCUBUS || yourStats->type == INCUBUS )
11400 							{
11401 								result = true;
11402 							}
11403 							break;
11404 						case INSECTOID:
11405 							if ( yourStats->type == SCARAB
11406 								|| yourStats->type == INSECTOID || yourStats->type == SCORPION )
11407 							{
11408 								result = true;
11409 							}
11410 							break;
11411 						case VAMPIRE:
11412 							if ( yourStats->type == VAMPIRE )
11413 							{
11414 								result = true;
11415 							}
11416 							break;
11417 						case AUTOMATON:
11418 							if ( yourStats->type == SHOPKEEPER )
11419 							{
11420 								result = monsterally[SHOPKEEPER][AUTOMATON];
11421 							}
11422 							else if ( yourStats->type == HUMAN )
11423 							{
11424 								result = true;
11425 							}
11426 							break;
11427 						default:
11428 							break;
11429 					}
11430 				}
11431 			}
11432 			else if ( behavior == &actMonster && your->behavior == &actPlayer && yourStats->type != HUMAN )
11433 			{
11434 				result = monsterally[myStats->type][HUMAN];
11435 				if ( (myStats->type == HUMAN || myStats->type == SHOPKEEPER) && yourStats->type != AUTOMATON )
11436 				{
11437 					result = false;
11438 				}
11439 				else
11440 				{
11441 					switch ( yourStats->type )
11442 					{
11443 						case SKELETON:
11444 							if ( myStats->type == GHOUL )
11445 							{
11446 								result = true;
11447 							}
11448 							break;
11449 						case RAT:
11450 							if ( myStats->type == RAT )
11451 							{
11452 								result = true;
11453 							}
11454 							break;
11455 						case SPIDER:
11456 							if ( myStats->type == SPIDER
11457 								|| myStats->type == SCARAB || myStats->type == SCORPION )
11458 							{
11459 								result = true;
11460 							}
11461 							break;
11462 						case TROLL:
11463 							if ( myStats->type == TROLL )
11464 							{
11465 								result = true;
11466 							}
11467 							break;
11468 						case CREATURE_IMP:
11469 							if ( myStats->type == CREATURE_IMP )
11470 							{
11471 								result = true;
11472 							}
11473 							break;
11474 						case GOBLIN:
11475 							if ( myStats->type == GOBLIN )
11476 							{
11477 								result = true;
11478 							}
11479 							break;
11480 						case GOATMAN:
11481 							if ( myStats->type == GOATMAN )
11482 							{
11483 								result = true;
11484 							}
11485 							break;
11486 						case INCUBUS:
11487 						case SUCCUBUS:
11488 							if ( myStats->type == SUCCUBUS || myStats->type == INCUBUS )
11489 							{
11490 								result = true;
11491 							}
11492 							break;
11493 						case INSECTOID:
11494 							if ( myStats->type == SCARAB
11495 								|| myStats->type == INSECTOID || myStats->type == SCORPION )
11496 							{
11497 								result = true;
11498 							}
11499 							break;
11500 						case VAMPIRE:
11501 							if ( myStats->type == VAMPIRE )
11502 							{
11503 								result = true;
11504 							}
11505 							break;
11506 						case AUTOMATON:
11507 							if ( myStats->type == SHOPKEEPER )
11508 							{
11509 								result = monsterally[SHOPKEEPER][AUTOMATON];
11510 							}
11511 							else if ( myStats->type == HUMAN )
11512 							{
11513 								result = true;
11514 							}
11515 							break;
11516 						default:
11517 							break;
11518 					}
11519 				}
11520 			}
11521 		}
11522 	}
11523 
11524 	return result;
11525 }
11526 
11527 
createMonsterEquipment(Stat * stats)11528 void createMonsterEquipment(Stat* stats)
11529 {
11530 	int itemIndex = 0;
11531 	ItemType itemId;
11532 	Status itemStatus;
11533 	int itemBless;
11534 	int itemAppearance = rand();
11535 	int itemCount;
11536 	int chance = 1;
11537 	int category = 0;
11538 	bool itemIdentified;
11539 	if ( stats != nullptr )
11540 	{
11541 		for ( itemIndex = 0; itemIndex < 10; ++itemIndex )
11542 		{
11543 			bool generateItem = true;
11544 			category = stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + ITEM_SLOT_CATEGORY];
11545 			if ( category > 0 && stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES] == 1 )
11546 			{
11547 				if ( category > 0 && category <= 13 )
11548 				{
11549 					itemId = itemLevelCurve(static_cast<Category>(category - 1), 0, currentlevel);
11550 				}
11551 				else
11552 				{
11553 					int randType = 0;
11554 					if ( category == 14 )
11555 					{
11556 						// equipment
11557 						randType = rand() % 2;
11558 						if ( randType == 0 )
11559 						{
11560 							itemId = itemLevelCurve(static_cast<Category>(WEAPON), 0, currentlevel);
11561 						}
11562 						else if ( randType == 1 )
11563 						{
11564 							itemId = itemLevelCurve(static_cast<Category>(ARMOR), 0, currentlevel);
11565 						}
11566 					}
11567 					else if ( category == 15 )
11568 					{
11569 						// jewelry
11570 						randType = rand() % 2;
11571 						if ( randType == 0 )
11572 						{
11573 							itemId = itemLevelCurve(static_cast<Category>(AMULET), 0, currentlevel);
11574 						}
11575 						else
11576 						{
11577 							itemId = itemLevelCurve(static_cast<Category>(RING), 0, currentlevel);
11578 						}
11579 					}
11580 					else if ( category == 16 )
11581 					{
11582 						// magical
11583 						randType = rand() % 3;
11584 						if ( randType == 0 )
11585 						{
11586 							itemId = itemLevelCurve(static_cast<Category>(SCROLL), 0, currentlevel);
11587 						}
11588 						else if ( randType == 1 )
11589 						{
11590 							itemId = itemLevelCurve(static_cast<Category>(MAGICSTAFF), 0, currentlevel);
11591 						}
11592 						else
11593 						{
11594 							itemId = itemLevelCurve(static_cast<Category>(SPELLBOOK), 0, currentlevel);
11595 						}
11596 					}
11597 				}
11598 			}
11599 			else
11600 			{
11601 				if ( static_cast<ItemType>(stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES] - 2) >= 0 )
11602 				{
11603 					itemId = static_cast<ItemType>(stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES] - 2);
11604 				}
11605 				else
11606 				{
11607 					itemId = ItemType::WOODEN_SHIELD;
11608 					generateItem = false;
11609 				}
11610 			}
11611 
11612 			if ( itemId >= 0 && generateItem )
11613 			{
11614 				itemStatus = static_cast<Status>(stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 1]);
11615 				if ( itemStatus == 0 )
11616 				{
11617 					itemStatus = static_cast<Status>(DECREPIT + rand() % 4);
11618 				}
11619 				else if ( itemStatus > BROKEN )
11620 				{
11621 					itemStatus = static_cast<Status>(itemStatus - 1); // reserved '0' for random, so '1' is decrepit... etc to '5' being excellent.
11622 				}
11623 				itemBless = stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 2];
11624 				if ( itemBless == 10 )
11625 				{
11626 					itemBless = -2 + rand() % 5;
11627 				}
11628 				itemCount = stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 3];
11629 				if ( stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 4] == 1 )
11630 				{
11631 					itemIdentified = false;
11632 				}
11633 				else if ( stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 4] == 2 )
11634 				{
11635 					itemIdentified = true;
11636 				}
11637 				else
11638 				{
11639 					itemIdentified = rand() % 2;
11640 				}
11641 				itemAppearance = rand();
11642 				chance = stats->EDITOR_ITEMS[itemIndex * ITEM_SLOT_NUMPROPERTIES + 5];
11643 
11644 				if ( rand() % 100 < chance )
11645 				{
11646 					switch ( itemIndex ) {
11647 						case 0:
11648 							stats->helmet = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11649 							break;
11650 						case 1:
11651 							stats->weapon = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11652 							break;
11653 						case 2:
11654 							stats->shield = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11655 							break;
11656 						case 3:
11657 							stats->breastplate = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11658 							break;
11659 						case 4:
11660 							stats->shoes = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11661 							break;
11662 						case 5:
11663 							stats->ring = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11664 							break;
11665 						case 6:
11666 							stats->amulet = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11667 							break;
11668 						case 7:
11669 							stats->cloak = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11670 							break;
11671 						case 8:
11672 							stats->mask = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11673 							break;
11674 						case 9:
11675 							stats->gloves = newItem(itemId, itemStatus, itemBless, itemCount, itemAppearance, itemIdentified, NULL);
11676 							break;
11677 						default:
11678 							break;
11679 					}
11680 				}
11681 			}
11682 		}
11683 	}
11684 }
11685 
countCustomItems(Stat * stats)11686 int countCustomItems(Stat* stats)
11687 {
11688 	int x = 0;
11689 	int customItemSlotCount = 0;
11690 
11691 	for ( x = ITEM_SLOT_INV_1; x <= ITEM_SLOT_INV_6; x = x + ITEM_SLOT_NUMPROPERTIES )
11692 	{
11693 		if ( stats->EDITOR_ITEMS[x] != 1 || (stats->EDITOR_ITEMS[x] == 1 && stats->EDITOR_ITEMS[x + ITEM_SLOT_CATEGORY] != 0) )
11694 		{
11695 			++customItemSlotCount; //found a custom item in inventory
11696 		}
11697 	}
11698 
11699 	return customItemSlotCount; //use custom items from editor instead of default generation
11700 }
11701 
countDefaultItems(Stat * stats)11702 int countDefaultItems(Stat* stats)
11703 {
11704 	int x = 0;
11705 	int defaultItemSlotCount = 0;
11706 
11707 	for ( x = ITEM_SLOT_INV_1; x <= ITEM_SLOT_INV_6; x = x + ITEM_SLOT_NUMPROPERTIES )
11708 	{
11709 		if ( stats->EDITOR_ITEMS[x] == 1 && stats->EDITOR_ITEMS[x + ITEM_SLOT_CATEGORY] == 0 )
11710 		{
11711 			defaultItemSlotCount++; //found a default item in inventory
11712 		}
11713 	}
11714 
11715 	return defaultItemSlotCount;
11716 }
11717 
setRandomMonsterStats(Stat * stats)11718 void setRandomMonsterStats(Stat* stats)
11719 {
11720 	if ( stats != nullptr )
11721 	{
11722 		//**************************************
11723 		// HEALTH
11724 		//**************************************
11725 
11726 		if ( stats->MAXHP == stats->HP )
11727 		{
11728 			stats->MAXHP += rand() % (stats->RANDOM_MAXHP + 1);
11729 
11730 			if ( stats->RANDOM_MAXHP == stats->RANDOM_HP )
11731 			{
11732 				// if the max hp and normal hp range is the same, hp follows the roll of maxhp.
11733 				stats->HP = stats->MAXHP;
11734 			}
11735 			else
11736 			{
11737 				// roll the current hp
11738 				stats->HP += rand() % (stats->RANDOM_HP + 1);
11739 			}
11740 		}
11741 		else
11742 		{
11743 			// roll both ranges independently
11744 			stats->MAXHP += rand() % (stats->RANDOM_MAXHP + 1);
11745 			stats->HP += rand() % (stats->RANDOM_HP + 1);
11746 		}
11747 
11748 		if ( stats->HP > stats->MAXHP )
11749 		{
11750 			// check if hp exceeds maximums
11751 			stats->HP = stats->MAXHP;
11752 		}
11753 		stats->OLDHP = stats->HP;
11754 
11755 		//**************************************
11756 		// MANA
11757 		//**************************************
11758 
11759 		if ( stats->MAXMP == stats->MP )
11760 		{
11761 			stats->MAXMP += rand() % (stats->RANDOM_MAXMP + 1);
11762 
11763 			if ( stats->RANDOM_MAXMP == stats->RANDOM_MP )
11764 			{
11765 				// if the max mp and normal mp range is the same, mp follows the roll of maxmp.
11766 				stats->MP = stats->MAXMP;
11767 			}
11768 			else
11769 			{
11770 				// roll the current mp
11771 				stats->MP += rand() % (stats->RANDOM_MP + 1);
11772 			}
11773 		}
11774 		else
11775 		{
11776 			// roll both ranges independently
11777 			stats->MAXMP += rand() % (stats->RANDOM_MAXMP + 1);
11778 			stats->MP += rand() % (stats->RANDOM_MP + 1);
11779 		}
11780 
11781 		if ( stats->MP > stats->MAXMP )
11782 		{
11783 			// check if mp exceeds maximums
11784 			stats->MP = stats->MAXMP;
11785 		}
11786 
11787 		//**************************************
11788 		// REST OF STATS
11789 		//**************************************
11790 
11791 		stats->STR += rand() % (stats->RANDOM_STR + 1);
11792 		stats->DEX += rand() % (stats->RANDOM_DEX + 1);
11793 		stats->CON += rand() % (stats->RANDOM_CON + 1);
11794 		stats->INT += rand() % (stats->RANDOM_INT + 1);
11795 		stats->PER += rand() % (stats->RANDOM_PER + 1);
11796 		stats->CHR += rand() % (stats->RANDOM_CHR + 1);
11797 
11798 		stats->LVL += rand() % (stats->RANDOM_LVL + 1);
11799 		stats->GOLD += rand() % (stats->RANDOM_GOLD + 1);
11800 	}
11801 
11802 	// debug print out each monster spawned
11803 
11804 	/*messagePlayer(0, "Set stats to: ");
11805 	messagePlayer(0, "MAXHP: %d", stats->MAXHP);
11806 	messagePlayer(0, "HP: %d", stats->HP);
11807 	messagePlayer(0, "MAXMP: %d", stats->MAXMP);
11808 	messagePlayer(0, "MP: %d", stats->MP);
11809 	messagePlayer(0, "Str: %d", stats->STR);
11810 	messagePlayer(0, "Dex: %d", stats->DEX);
11811 	messagePlayer(0, "Con: %d", stats->CON);
11812 	messagePlayer(0, "Int: %d", stats->INT);
11813 	messagePlayer(0, "Per: %d", stats->PER);
11814 	messagePlayer(0, "Chr: %d", stats->CHR);
11815 	messagePlayer(0, "LVL: %d", stats->LVL);
11816 	messagePlayer(0, "GOLD: %d", stats->GOLD);*/
11817 
11818 
11819 	return;
11820 }
11821 
11822 
checkEquipType(const Item * item)11823 int checkEquipType(const Item *item)
11824 {
11825 	if ( !item )
11826 	{
11827 		return TYPE_NONE;
11828 	}
11829 	if ( itemTypeIsQuiver(item->type) )
11830 	{
11831 		return TYPE_OFFHAND;
11832 	}
11833 	switch ( item->type ) {
11834 
11835 		case LEATHER_BOOTS:
11836 		case LEATHER_BOOTS_SPEED:
11837 		case IRON_BOOTS:
11838 		case IRON_BOOTS_WATERWALKING:
11839 		case STEEL_BOOTS:
11840 		case STEEL_BOOTS_LEVITATION:
11841 		case STEEL_BOOTS_FEATHER:
11842 		case CRYSTAL_BOOTS:
11843 		case ARTIFACT_BOOTS:
11844 		case SUEDE_BOOTS:
11845 			return TYPE_BOOTS;
11846 			break;
11847 
11848 		case LEATHER_HELM:
11849 		case IRON_HELM:
11850 		case STEEL_HELM:
11851 		case CRYSTAL_HELM:
11852 		case ARTIFACT_HELM:
11853 			return TYPE_HELM;
11854 			break;
11855 
11856 		case LEATHER_BREASTPIECE:
11857 		case IRON_BREASTPIECE:
11858 		case STEEL_BREASTPIECE:
11859 		case CRYSTAL_BREASTPIECE:
11860 		case WIZARD_DOUBLET:
11861 		case HEALER_DOUBLET:
11862 		case VAMPIRE_DOUBLET:
11863 		case ARTIFACT_BREASTPIECE:
11864 			return TYPE_BREASTPIECE;
11865 			break;
11866 
11867 		case CRYSTAL_SHIELD:
11868 		case WOODEN_SHIELD:
11869 		case BRONZE_SHIELD:
11870 		case IRON_SHIELD:
11871 		case STEEL_SHIELD:
11872 		case STEEL_SHIELD_RESISTANCE:
11873 		case MIRROR_SHIELD:
11874 			return TYPE_SHIELD;
11875 			break;
11876 
11877 		case TOOL_TORCH:
11878 		case TOOL_LANTERN:
11879 		case TOOL_CRYSTALSHARD:
11880 			return TYPE_OFFHAND;
11881 			break;
11882 
11883 		case CLOAK:
11884 		case CLOAK_MAGICREFLECTION:
11885 		case CLOAK_INVISIBILITY:
11886 		case CLOAK_PROTECTION:
11887 		case ARTIFACT_CLOAK:
11888 		case CLOAK_BLACK:
11889 		case CLOAK_BACKPACK:
11890 		case CLOAK_SILVER:
11891 			return TYPE_CLOAK;
11892 			break;
11893 
11894 		case GLOVES:
11895 		case GLOVES_DEXTERITY:
11896 		case GAUNTLETS:
11897 		case GAUNTLETS_STRENGTH:
11898 		case BRACERS:
11899 		case BRACERS_CONSTITUTION:
11900 		case CRYSTAL_GLOVES:
11901 		case ARTIFACT_GLOVES:
11902 		case SPIKED_GAUNTLETS:
11903 		case IRON_KNUCKLES:
11904 		case BRASS_KNUCKLES:
11905 		case SUEDE_GLOVES:
11906 			return TYPE_GLOVES;
11907 			break;
11908 
11909 		case HAT_HOOD:
11910 		case HAT_JESTER:
11911 		case HAT_PHRYGIAN:
11912 		case HAT_WIZARD:
11913 		case HAT_FEZ:
11914 		case HAT_HOOD_RED:
11915 		case MASK_SHAMAN:
11916 		case PUNISHER_HOOD:
11917 			return TYPE_HAT;
11918 			break;
11919 
11920 		default:
11921 			break;
11922 	}
11923 
11924 	return TYPE_NONE;
11925 }
11926 
setGloveSprite(Stat * myStats,Entity * ent,int spriteOffset)11927 int setGloveSprite(Stat* myStats, Entity* ent, int spriteOffset)
11928 {
11929 	if ( myStats == nullptr )
11930 	{
11931 		return 0;
11932 	}
11933 	if ( myStats->gloves == nullptr )
11934 	{
11935 		return 0;
11936 	}
11937 
11938 	if ( myStats->gloves->type == GLOVES || myStats->gloves->type == GLOVES_DEXTERITY ) {
11939 		ent->sprite = 132 + myStats->sex + spriteOffset;
11940 	}
11941 	else if ( myStats->gloves->type == BRACERS || myStats->gloves->type == BRACERS_CONSTITUTION ) {
11942 		ent->sprite = 323 + myStats->sex + spriteOffset;
11943 	}
11944 	else if ( myStats->gloves->type == GAUNTLETS || myStats->gloves->type == GAUNTLETS_STRENGTH ) {
11945 		ent->sprite = 140 + myStats->sex + spriteOffset;
11946 	}
11947 	else if ( myStats->gloves->type == CRYSTAL_GLOVES )
11948 	{
11949 		ent->sprite = 491 + myStats->sex + spriteOffset;
11950 	}
11951 	else if ( myStats->gloves->type == ARTIFACT_GLOVES )
11952 	{
11953 		ent->sprite = 513 + myStats->sex + spriteOffset;
11954 	}
11955 	else if ( myStats->gloves->type == BRASS_KNUCKLES )
11956 	{
11957 		ent->sprite = 531 + myStats->sex + spriteOffset;
11958 	}
11959 	else if ( myStats->gloves->type == IRON_KNUCKLES )
11960 	{
11961 		ent->sprite = 539 + myStats->sex + spriteOffset;
11962 	}
11963 	else if ( myStats->gloves->type == SPIKED_GAUNTLETS )
11964 	{
11965 		ent->sprite = 547 + myStats->sex + spriteOffset;
11966 	}
11967 	else if ( myStats->gloves->type == SUEDE_GLOVES )
11968 	{
11969 		ent->sprite = 804 + (spriteOffset > 0 ? 1 : 0);
11970 	}
11971 	else
11972 	{
11973 		return 0;
11974 	}
11975 	return 1;
11976 }
11977 
setBootSprite(Entity * leg,int spriteOffset)11978 bool Entity::setBootSprite(Entity* leg, int spriteOffset)
11979 {
11980 	if ( multiplayer == CLIENT )
11981 	{
11982 		return false;
11983 	}
11984 
11985 	Stat* myStats;
11986 
11987 	if ( this->behavior == &actPlayer )
11988 	{
11989 		myStats = stats[this->skill[2]]; // skill[2] contains the player number.
11990 	}
11991 	else
11992 	{
11993 		myStats = this->getStats();
11994 	}
11995 
11996 	if ( myStats == nullptr )
11997 	{
11998 		return false;
11999 	}
12000 	if ( myStats->shoes == nullptr )
12001 	{
12002 		return false;
12003 	}
12004 
12005 	switch ( myStats->type )
12006 	{
12007 		case HUMAN:
12008 			if ( myStats->shoes->type == LEATHER_BOOTS || myStats->shoes->type == LEATHER_BOOTS_SPEED )
12009 			{
12010 				leg->sprite = 148 + myStats->sex + spriteOffset;
12011 			}
12012 			else if ( myStats->shoes->type == IRON_BOOTS || myStats->shoes->type == IRON_BOOTS_WATERWALKING )
12013 			{
12014 				leg->sprite = 152 + myStats->sex + spriteOffset;
12015 			}
12016 			else if ( myStats->shoes->type >= STEEL_BOOTS && myStats->shoes->type <= STEEL_BOOTS_FEATHER )
12017 			{
12018 				leg->sprite = 156 + myStats->sex + spriteOffset;
12019 			}
12020 			else if ( myStats->shoes->type == CRYSTAL_BOOTS )
12021 			{
12022 				leg->sprite = 499 + myStats->sex + spriteOffset;
12023 			}
12024 			else if ( myStats->shoes->type == ARTIFACT_BOOTS )
12025 			{
12026 				leg->sprite = 521 + myStats->sex + spriteOffset;
12027 			}
12028 			else if ( myStats->shoes->type == SUEDE_BOOTS )
12029 			{
12030 				leg->sprite = 808 + (spriteOffset > 0 ? 1 : 0);
12031 			}
12032 			else
12033 			{
12034 				return false;
12035 			}
12036 			break;
12037 			// fall throughs below
12038 		case AUTOMATON:
12039 		case GOATMAN:
12040 		case INSECTOID:
12041 		case KOBOLD:
12042 		case GOBLIN:
12043 		case SKELETON:
12044 		case GNOME:
12045 		case SHADOW:
12046 		case INCUBUS:
12047 		case VAMPIRE:
12048 		case SUCCUBUS:
12049 		case SHOPKEEPER:
12050 			if ( myStats->shoes->type == LEATHER_BOOTS || myStats->shoes->type == LEATHER_BOOTS_SPEED )
12051 			{
12052 				leg->sprite = 148 + spriteOffset;
12053 			}
12054 			else if ( myStats->shoes->type == IRON_BOOTS || myStats->shoes->type == IRON_BOOTS_WATERWALKING )
12055 			{
12056 				leg->sprite = 152 + spriteOffset;
12057 			}
12058 			else if ( myStats->shoes->type >= STEEL_BOOTS && myStats->shoes->type <= STEEL_BOOTS_FEATHER )
12059 			{
12060 				leg->sprite = 156 + spriteOffset;
12061 			}
12062 			else if ( myStats->shoes->type == CRYSTAL_BOOTS )
12063 			{
12064 				leg->sprite = 499 + spriteOffset;
12065 			}
12066 			else if ( myStats->shoes->type == ARTIFACT_BOOTS )
12067 			{
12068 				leg->sprite = 521 + spriteOffset;
12069 			}
12070 			else if ( myStats->shoes->type == SUEDE_BOOTS )
12071 			{
12072 				leg->sprite = 808 + (spriteOffset > 0 ? 1 : 0);
12073 			}
12074 			else
12075 			{
12076 				return false;
12077 			}
12078 			break;
12079 		default:
12080 			break;
12081 	}
12082 
12083 	return true;
12084 }
12085 
12086 
12087 /*-------------------------------------------------------------------------------
12088 
12089 sLevitating
12090 
12091 returns true if the given entity is levitating, or false if it cannot
12092 
12093 -------------------------------------------------------------------------------*/
12094 
isLevitating(Stat * mystats)12095 bool isLevitating(Stat* mystats)
12096 {
12097 	if ( mystats == nullptr )
12098 	{
12099 		return false;
12100 	}
12101 
12102 	// check levitating value
12103 	bool levitating = false;
12104 	if ( MFLAG_DISABLELEVITATION )
12105 	{
12106 		for ( int i = 0; i < MAXPLAYERS; ++i )
12107 		{
12108 			if ( client_disconnected[i] )
12109 			{
12110 				continue;
12111 			}
12112 			// check if mystats is a player, and levitation flag is disabled.
12113 			if ( players[i] && players[i]->entity )
12114 			{
12115 				if ( players[i]->entity->getStats() == mystats )
12116 				{
12117 					if ( mystats->type == CREATURE_IMP )
12118 					{
12119 						return true;
12120 					}
12121 					return false;
12122 				}
12123 			}
12124 		}
12125 	}
12126 	for ( int i = 0; i < MAXPLAYERS; ++i )
12127 	{
12128 		if ( players[i] && players[i]->entity )
12129 		{
12130 			if ( players[i]->entity->getStats() == mystats )
12131 			{
12132 				if ( players[i]->entity->effectShapeshift == CREATURE_IMP )
12133 				{
12134 					return true;
12135 				}
12136 				break;
12137 			}
12138 		}
12139 	}
12140 
12141 	if ( mystats->EFFECTS[EFF_LEVITATING] == true )
12142 	{
12143 		return true;
12144 	}
12145 	else if ( mystats->EFFECTS[EFF_FLUTTER] )
12146 	{
12147 		return true;
12148 	}
12149 	if ( mystats->ring != NULL )
12150 	{
12151 		if ( mystats->ring->type == RING_LEVITATION )
12152 		{
12153 			return true;
12154 		}
12155 	}
12156 	if ( mystats->shoes != NULL )
12157 	{
12158 		if ( mystats->shoes->type == STEEL_BOOTS_LEVITATION )
12159 		{
12160 			return true;
12161 		}
12162 	}
12163 	if ( mystats->cloak != NULL )
12164 	{
12165 		if ( mystats->cloak->type == ARTIFACT_CLOAK )
12166 		{
12167 			return true;
12168 		}
12169 	}
12170 
12171 	return false;
12172 }
12173 
12174 /*-------------------------------------------------------------------------------
12175 
12176 getWeaponSkill
12177 
12178 returns the proficiency for the weapon equipped.
12179 
12180 -------------------------------------------------------------------------------*/
12181 
getWeaponSkill(Item * weapon)12182 int getWeaponSkill(Item* weapon)
12183 {
12184 	if ( weapon == NULL )
12185 	{
12186 		return PRO_UNARMED;
12187 	}
12188 
12189 	if ( weapon->type == QUARTERSTAFF || weapon->type == IRON_SPEAR || weapon->type == STEEL_HALBERD || weapon->type == ARTIFACT_SPEAR || weapon->type == CRYSTAL_SPEAR )
12190 	{
12191 		return PRO_POLEARM;
12192 	}
12193 	if ( weapon->type == BRONZE_SWORD || weapon->type == IRON_SWORD || weapon->type == STEEL_SWORD || weapon->type == ARTIFACT_SWORD || weapon->type == CRYSTAL_SWORD )
12194 	{
12195 		return PRO_SWORD;
12196 	}
12197 	if ( weapon->type == BRONZE_MACE || weapon->type == IRON_MACE || weapon->type == STEEL_MACE || weapon->type == ARTIFACT_MACE || weapon->type == CRYSTAL_MACE )
12198 	{
12199 		return PRO_MACE;
12200 	}
12201 	if ( weapon->type == BRONZE_AXE || weapon->type == IRON_AXE || weapon->type == STEEL_AXE || weapon->type == ARTIFACT_AXE || weapon->type == CRYSTAL_BATTLEAXE )
12202 	{
12203 		return PRO_AXE;
12204 	}
12205 	if ( isRangedWeapon(*weapon) )
12206 	{
12207 		return PRO_RANGED;
12208 	}
12209 	if ( itemCategory(weapon) == THROWN || itemCategory(weapon) == POTION || itemCategory(weapon) == GEM )
12210 	{
12211 		return PRO_RANGED;
12212 	}
12213 	if ( weapon->type == TOOL_WHIP )
12214 	{
12215 		return PRO_RANGED;
12216 	}
12217 	return -1;
12218 }
12219 
12220 /*-------------------------------------------------------------------------------
12221 
12222 getStatForProficiency
12223 
12224 returns the stat associated with the given proficiency.
12225 
12226 -------------------------------------------------------------------------------*/
12227 
getStatForProficiency(int skill)12228 int getStatForProficiency(int skill)
12229 {
12230 	int statForProficiency = -1;
12231 
12232 	switch ( skill )
12233 	{
12234 		case PRO_SWORD:			// base attribute: str
12235 		case PRO_MACE:			// base attribute: str
12236 		case PRO_AXE:			// base attribute: str
12237 		case PRO_POLEARM:		// base attribute: str
12238 		case PRO_UNARMED:
12239 			statForProficiency = STAT_STR;
12240 			break;
12241 		case PRO_LOCKPICKING:	// base attribute: dex
12242 		case PRO_STEALTH:		// base attribute: dex
12243 		case PRO_RANGED:        // base attribute: dex
12244 			statForProficiency = STAT_DEX;
12245 			break;
12246 		case PRO_SWIMMING:      // base attribute: con
12247 		case PRO_SHIELD:		// base attribute: con
12248 			statForProficiency = STAT_CON;
12249 			break;
12250 		case PRO_SPELLCASTING:  // base attribute: int
12251 		case PRO_MAGIC:         // base attribute: int
12252 		case PRO_ALCHEMY:       // base attribute: int
12253 			statForProficiency = STAT_INT;
12254 			break;
12255 		case PRO_APPRAISAL:		// base attribute: per
12256 			statForProficiency = STAT_PER;
12257 			break;
12258 		case PRO_TRADING:       // base attribute: chr
12259 		case PRO_LEADERSHIP:    // base attribute: chr
12260 			statForProficiency = STAT_CHR;
12261 			break;
12262 		default:
12263 			statForProficiency = -1;
12264 			break;
12265 	}
12266 
12267 	return statForProficiency;
12268 }
12269 
12270 
isEntityPlayer() const12271 int Entity::isEntityPlayer() const
12272 {
12273 	for ( int i = 0; i < MAXPLAYERS; ++i )
12274 	{
12275 		if ( players[i] && this == players[i]->entity )
12276 		{
12277 			return i;
12278 		}
12279 	}
12280 
12281 	return -1;
12282 }
12283 
getReflection() const12284 int Entity::getReflection() const
12285 {
12286 	Stat *stats = getStats();
12287 	if ( !stats )
12288 	{
12289 		return 0;
12290 	}
12291 
12292 	if ( stats->EFFECTS[EFF_MAGICREFLECT] )
12293 	{
12294 		return 3;
12295 	}
12296 
12297 	if ( stats->shield )
12298 	{
12299 		if ( stats->shield->type == MIRROR_SHIELD && stats->defending )
12300 		{
12301 			return 3;
12302 		}
12303 	}
12304 	if ( stats->amulet )
12305 	{
12306 		if ( stats->amulet->type == AMULET_MAGICREFLECTION )
12307 		{
12308 			return 2;
12309 		}
12310 	}
12311 	if ( stats->cloak )
12312 	{
12313 		if ( stats->cloak->type == CLOAK_MAGICREFLECTION )
12314 		{
12315 			return 1;
12316 		}
12317 	}
12318 	return 0;
12319 }
12320 
getAttackPose() const12321 int Entity::getAttackPose() const
12322 {
12323 	Stat *myStats = getStats();
12324 	if ( !myStats )
12325 	{
12326 		return -1;
12327 	}
12328 
12329 	int pose = 0;
12330 
12331 	if ( myStats->weapon != nullptr )
12332 	{
12333 		if ( myStats->type == LICH_FIRE )
12334 		{
12335 			switch ( monsterLichFireMeleeSeq )
12336 			{
12337 				case LICH_ATK_VERTICAL_SINGLE:
12338 					pose = MONSTER_POSE_MELEE_WINDUP1;
12339 					break;
12340 				case LICH_ATK_HORIZONTAL_SINGLE:
12341 					pose = MONSTER_POSE_MELEE_WINDUP2;
12342 					break;
12343 				case LICH_ATK_RISING_RAIN:
12344 					pose = MONSTER_POSE_SPECIAL_WINDUP1;
12345 					break;
12346 				case LICH_ATK_BASICSPELL_SINGLE:
12347 					pose = MONSTER_POSE_MAGIC_WINDUP1;
12348 					break;
12349 				case LICH_ATK_RISING_SINGLE:
12350 					pose = MONSTER_POSE_MELEE_WINDUP3;
12351 					break;
12352 				case LICH_ATK_VERTICAL_QUICK:
12353 					pose = MONSTER_POSE_MELEE_WINDUP1;
12354 					break;
12355 				case LICH_ATK_HORIZONTAL_RETURN:
12356 					pose = MONSTER_POSE_MELEE_WINDUP2;
12357 					break;
12358 				case LICH_ATK_HORIZONTAL_QUICK:
12359 					pose = MONSTER_POSE_MELEE_WINDUP2;
12360 					break;
12361 				case LICH_ATK_SUMMON:
12362 					pose = MONSTER_POSE_MAGIC_WINDUP3;
12363 					break;
12364 				default:
12365 					break;
12366 			}
12367 		}
12368 		else if ( myStats->type == LICH_ICE )
12369 		{
12370 			switch ( monsterLichIceCastSeq )
12371 			{
12372 				case LICH_ATK_VERTICAL_SINGLE:
12373 					pose = MONSTER_POSE_MELEE_WINDUP1;
12374 					break;
12375 				case LICH_ATK_HORIZONTAL_SINGLE:
12376 					pose = MONSTER_POSE_MELEE_WINDUP2;
12377 					break;
12378 				case LICH_ATK_RISING_RAIN:
12379 					pose = MONSTER_POSE_SPECIAL_WINDUP1;
12380 					break;
12381 				case LICH_ATK_BASICSPELL_SINGLE:
12382 					pose = MONSTER_POSE_MAGIC_WINDUP1;
12383 					break;
12384 				case LICH_ATK_RISING_SINGLE:
12385 					pose = MONSTER_POSE_MELEE_WINDUP1;
12386 					break;
12387 				case LICH_ATK_VERTICAL_QUICK:
12388 					pose = MONSTER_POSE_MELEE_WINDUP1;
12389 					break;
12390 				case LICH_ATK_HORIZONTAL_RETURN:
12391 					pose = MONSTER_POSE_MELEE_WINDUP2;
12392 					break;
12393 				case LICH_ATK_HORIZONTAL_QUICK:
12394 					pose = MONSTER_POSE_MELEE_WINDUP2;
12395 					break;
12396 				case LICH_ATK_CHARGE_AOE:
12397 					pose = MONSTER_POSE_SPECIAL_WINDUP2;
12398 					break;
12399 				case LICH_ATK_FALLING_DIAGONAL:
12400 					pose = MONSTER_POSE_SPECIAL_WINDUP3;
12401 					break;
12402 				case LICH_ATK_SUMMON:
12403 					pose = MONSTER_POSE_MAGIC_WINDUP3;
12404 					break;
12405 				default:
12406 					break;
12407 			}
12408 		}
12409 		else if ( myStats->type == SENTRYBOT )
12410 		{
12411 			pose = MONSTER_POSE_RANGED_WINDUP1;
12412 		}
12413 		else if ( myStats->type == SPELLBOT )
12414 		{
12415 			pose = MONSTER_POSE_MAGIC_WINDUP1;
12416 		}
12417 		else if ( itemCategory(myStats->weapon) == MAGICSTAFF )
12418 		{
12419 			if ( myStats->type == KOBOLD || myStats->type == AUTOMATON
12420 				|| myStats->type == GOATMAN || myStats->type == INSECTOID
12421 				|| myStats->type == INCUBUS || myStats->type == VAMPIRE
12422 				|| myStats->type == HUMAN || myStats->type == GOBLIN
12423 				|| myStats->type == SKELETON || myStats->type == GNOME
12424 				|| myStats->type == SUCCUBUS || myStats->type == SHOPKEEPER
12425 				|| myStats->type == SHADOW )
12426 			{
12427 				pose = MONSTER_POSE_MELEE_WINDUP1;
12428 			}
12429 			else
12430 			{
12431 				pose = 3;  // jab
12432 			}
12433 		}
12434 		else if ( itemCategory(myStats->weapon) == SPELLBOOK )
12435 		{
12436 			if ( myStats->type == INSECTOID && this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_INSECTOID_ACID )
12437 			{
12438 				pose = MONSTER_POSE_MAGIC_WINDUP3;
12439 			}
12440 			else if ( myStats->type == INCUBUS && this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_INCUBUS_STEAL )
12441 			{
12442 				pose = MONSTER_POSE_MAGIC_WINDUP3;
12443 			}
12444 			else if ( myStats->type == COCKATRICE && this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_COCKATRICE_STONE )
12445 			{
12446 				pose = MONSTER_POSE_MAGIC_WINDUP2;
12447 			}
12448 			else if ( myStats->type == VAMPIRE )
12449 			{
12450 				if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_VAMPIRE_DRAIN )
12451 				{
12452 					pose = MONSTER_POSE_VAMPIRE_DRAIN;
12453 				}
12454 				else if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_VAMPIRE_AURA )
12455 				{
12456 					pose = MONSTER_POSE_VAMPIRE_AURA_CHARGE;
12457 				}
12458 				else
12459 				{
12460 					pose = MONSTER_POSE_MAGIC_WINDUP1;
12461 				}
12462 			}
12463 			else if ( myStats->type == KOBOLD || myStats->type == AUTOMATON
12464 				|| myStats->type == GOATMAN || myStats->type == INSECTOID
12465 				|| myStats->type == COCKATRICE || myStats->type == INCUBUS
12466 				|| myStats->type == VAMPIRE || myStats->type == HUMAN
12467 				|| myStats->type == GOBLIN || myStats->type == SKELETON
12468 				|| myStats->type == GNOME || myStats->type == SUCCUBUS
12469 				|| myStats->type == SHOPKEEPER || myStats->type == SHADOW )
12470 			{
12471 				pose = MONSTER_POSE_MAGIC_WINDUP1;
12472 			}
12473 			else if ( myStats->type == DEMON || myStats->type == CREATURE_IMP || myStats->type == GHOUL )
12474 			{
12475 				pose = MONSTER_POSE_MELEE_WINDUP1;
12476 			}
12477 			else
12478 			{
12479 				pose = 1;  // vertical swing
12480 			}
12481 		}
12482 		else if ( itemCategory(myStats->weapon) == POTION )
12483 		{
12484 			if ( myStats->type == GOATMAN )
12485 			{
12486 				/*if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_GOATMAN_DRINK )
12487 				{
12488 					pose = MONSTER_POSE_RANGED_WINDUP3;
12489 				}
12490 				else if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_GOATMAN_THROW )
12491 				{
12492 					pose = MONSTER_POSE_MELEE_WINDUP1;
12493 				}*/
12494 				if ( monsterSpecialState == GOATMAN_POTION )
12495 				{
12496 					pose = MONSTER_POSE_RANGED_WINDUP3;
12497 				}
12498 
12499 			}
12500 			else if ( myStats->type == INCUBUS )
12501 			{
12502 				if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_INCUBUS_CONFUSION )
12503 				{
12504 					pose = MONSTER_POSE_SPECIAL_WINDUP1;
12505 				}
12506 			}
12507 			else
12508 			{
12509 				pose = MONSTER_POSE_MELEE_WINDUP1;
12510 			}
12511 		}
12512 		else if ( this->hasRangedWeapon() )
12513 		{
12514 			if ( myStats->type == KOBOLD || myStats->type == AUTOMATON
12515 				|| myStats->type == GOATMAN || myStats->type == INSECTOID
12516 				|| myStats->type == INCUBUS || myStats->type == VAMPIRE
12517 				|| myStats->type == HUMAN || myStats->type == GOBLIN
12518 				|| myStats->type == SKELETON || myStats->type == GNOME
12519 				|| myStats->type == SUCCUBUS || myStats->type == SHOPKEEPER
12520 				|| myStats->type == SHADOW )
12521 			{
12522 				if ( myStats->weapon->type == CROSSBOW || myStats->weapon->type == HEAVY_CROSSBOW )
12523 				{
12524 					pose = MONSTER_POSE_RANGED_WINDUP1;
12525 				}
12526 				else if ( itemCategory(myStats->weapon) == THROWN )
12527 				{
12528 					if ( myStats->type == INSECTOID )
12529 					{
12530 						if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_INSECTOID_THROW )
12531 						{
12532 							pose = MONSTER_POSE_RANGED_WINDUP3;
12533 						}
12534 						else
12535 						{
12536 							pose = MONSTER_POSE_MELEE_WINDUP1;
12537 						}
12538 					}
12539 					else
12540 					{
12541 						pose = MONSTER_POSE_MELEE_WINDUP1;
12542 					}
12543 				}
12544 				else
12545 				{
12546 					pose = MONSTER_POSE_RANGED_WINDUP2;
12547 				}
12548 			}
12549 			else
12550 			{
12551 				pose = 0;
12552 			}
12553 		}
12554 		else
12555 		{
12556 			if ( myStats->type == KOBOLD || myStats->type == AUTOMATON
12557 				|| myStats->type == GOATMAN || myStats->type == INSECTOID
12558 				|| myStats->type == INCUBUS || myStats->type == VAMPIRE
12559 				|| myStats->type == HUMAN || myStats->type == GOBLIN
12560 				|| myStats->type == SKELETON || myStats->type == GNOME
12561 				|| myStats->type == SUCCUBUS || myStats->type == SHOPKEEPER
12562 				|| myStats->type == SHADOW )
12563 			{
12564 				if ( getWeaponSkill(myStats->weapon) == PRO_AXE || getWeaponSkill(myStats->weapon) == PRO_MACE
12565 					|| myStats->weapon->type == TOOL_WHIP )
12566 				{
12567 					// axes and maces don't stab
12568 					pose = MONSTER_POSE_MELEE_WINDUP1 + rand() % 2;
12569 				}
12570 				else
12571 				{
12572 					pose = MONSTER_POSE_MELEE_WINDUP1 + rand() % 3;
12573 				}
12574 			}
12575 			else
12576 			{
12577 				pose = rand() % 3 + 1;
12578 			}
12579 		}
12580 	}
12581 	// fists
12582 	else
12583 	{
12584 		if ( myStats->type == KOBOLD || myStats->type == AUTOMATON
12585 			|| myStats->type == GOATMAN || myStats->type == INSECTOID
12586 			|| myStats->type == INCUBUS || myStats->type == VAMPIRE
12587 			|| myStats->type == HUMAN || myStats->type == GOBLIN
12588 			|| myStats->type == GHOUL || myStats->type == SKELETON
12589 			|| myStats->type == GNOME || myStats->type == DEMON
12590 			|| myStats->type == CREATURE_IMP || myStats->type == SUCCUBUS
12591 			|| myStats->type == SHOPKEEPER || myStats->type == MINOTAUR
12592 			|| myStats->type == SHADOW )
12593 		{
12594 			pose = MONSTER_POSE_MELEE_WINDUP1;
12595 		}
12596 		else if ( myStats->type == CRYSTALGOLEM )
12597 		{
12598 			if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_GOLEM )
12599 			{
12600 				pose = MONSTER_POSE_MELEE_WINDUP3;
12601 			}
12602 			else
12603 			{
12604 				pose = MONSTER_POSE_MELEE_WINDUP1 + rand() % 2;
12605 			}
12606 		}
12607 		else if ( myStats->type == COCKATRICE )
12608 		{
12609 			if ( this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK )
12610 			{
12611 				pose = MONSTER_POSE_MELEE_WINDUP3;
12612 			}
12613 			else
12614 			{
12615 				pose = MONSTER_POSE_MELEE_WINDUP1 + rand() % 2;
12616 			}
12617 		}
12618 		else if ( myStats->type == TROLL )
12619 		{
12620 			pose = MONSTER_POSE_MELEE_WINDUP1;
12621 		}
12622 		else
12623 		{
12624 			pose = 1;
12625 		}
12626 	}
12627 
12628 	return pose;
12629 }
12630 
hasRangedWeapon() const12631 bool Entity::hasRangedWeapon() const
12632 {
12633 	Stat *myStats = getStats();
12634 	if ( myStats == nullptr || myStats->weapon == nullptr )
12635 	{
12636 		return false;
12637 	}
12638 
12639 	if ( isRangedWeapon(*myStats->weapon) )
12640 	{
12641 		return true;
12642 	}
12643 	else if ( itemCategory(myStats->weapon) == MAGICSTAFF )
12644 	{
12645 		return true;
12646 	}
12647 	else if ( itemCategory(myStats->weapon) == SPELLBOOK )
12648 	{
12649 		return true;
12650 	}
12651 	else if ( itemCategory(myStats->weapon) == THROWN )
12652 	{
12653 		return true;
12654 	}
12655 	else if ( itemCategory(myStats->weapon) == GEM )
12656 	{
12657 		return true;
12658 	}
12659 	else if ( itemCategory(myStats->weapon) == POTION )
12660 	{
12661 		return true;
12662 	}
12663 
12664 	return false;
12665 }
12666 
12667 /*void Entity::returnWeaponarmToNeutral(Entity* weaponarm, Entity* rightbody)
12668 {
12669 weaponarm->skill[0] = rightbody->skill[0];
12670 monsterWeaponYaw = 0;
12671 weaponarm->pitch = rightbody->pitch;
12672 weaponarm->roll = 0;
12673 monsterArmbended = 0;
12674 monsterAttack = 0;
12675 }*/
12676 
handleWeaponArmAttack(Entity * weaponarm)12677 void Entity::handleWeaponArmAttack(Entity* weaponarm)
12678 {
12679 	if ( weaponarm == nullptr )
12680 	{
12681 		return;
12682 	}
12683 
12684 	Entity* rightbody = nullptr;
12685 	// set rightbody to left leg.
12686 	node_t* rightbodyNode = list_Node(&this->children, LIMB_HUMANOID_LEFTLEG);
12687 	if ( rightbodyNode )
12688 	{
12689 		rightbody = (Entity*)rightbodyNode->element;
12690 	}
12691 	else
12692 	{
12693 		return;
12694 	}
12695 
12696 	// vertical chop windup
12697 	if ( monsterAttack == MONSTER_POSE_MELEE_WINDUP1 )
12698 	{
12699 		if ( monsterAttackTime == 0 )
12700 		{
12701 			// init rotations
12702 			weaponarm->pitch = 0;
12703 			this->monsterArmbended = 0;
12704 			this->monsterWeaponYaw = 0;
12705 			weaponarm->roll = 0;
12706 			weaponarm->skill[1] = 0;
12707 		}
12708 
12709 		limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
12710 
12711 		if ( monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
12712 		{
12713 			if ( multiplayer != CLIENT )
12714 			{
12715 				this->attack(1, 0, nullptr);
12716 			}
12717 		}
12718 	}
12719 	// vertical chop attack
12720 	else if ( monsterAttack == 1 )
12721 	{
12722 		if ( weaponarm->pitch >= 3 * PI / 2 )
12723 		{
12724 			this->monsterArmbended = 1;
12725 		}
12726 
12727 		if ( weaponarm->skill[1] == 0 )
12728 		{
12729 			// chop forwards
12730 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.4, PI / 3, false, 0.0) )
12731 			{
12732 				weaponarm->skill[1] = 1;
12733 			}
12734 		}
12735 		else if ( weaponarm->skill[1] >= 1 )
12736 		{
12737 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, 7 * PI / 4, false, 0.0) )
12738 			{
12739 				weaponarm->skill[0] = rightbody->skill[0];
12740 				this->monsterWeaponYaw = 0;
12741 				weaponarm->pitch = rightbody->pitch;
12742 				weaponarm->roll = 0;
12743 				this->monsterArmbended = 0;
12744 				monsterAttack = 0;
12745 				//returnWeaponarmToNeutral(weaponarm, rightbody);
12746 			}
12747 		}
12748 	}
12749 	// horizontal chop windup
12750 	else if ( monsterAttack == MONSTER_POSE_MELEE_WINDUP2 )
12751 	{
12752 		if ( monsterAttackTime == 0 )
12753 		{
12754 			// init rotations
12755 			weaponarm->pitch = PI / 4;
12756 			weaponarm->roll = 0;
12757 			this->monsterArmbended = 1;
12758 			weaponarm->skill[1] = 0;
12759 			this->monsterWeaponYaw = 6 * PI / 4;
12760 		}
12761 
12762 		limbAnimateToLimit(weaponarm, ANIMATE_ROLL, -0.2, 3 * PI / 2, false, 0.0);
12763 		limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 0, false, 0.0);
12764 
12765 
12766 		if ( monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
12767 		{
12768 			if ( multiplayer != CLIENT )
12769 			{
12770 				this->attack(2, 0, nullptr);
12771 			}
12772 		}
12773 	}
12774 	// horizontal chop attack
12775 	else if ( monsterAttack == 2 )
12776 	{
12777 		if ( weaponarm->skill[1] == 0 )
12778 		{
12779 			// swing
12780 			// this->weaponyaw is OK to change for clients, as server doesn't update it for them.
12781 			if ( limbAnimateToLimit(this, ANIMATE_WEAPON_YAW, 0.3, 2 * PI / 8, false, 0.0) )
12782 			{
12783 				weaponarm->skill[1] = 1;
12784 			}
12785 		}
12786 		else if ( weaponarm->skill[1] >= 1 )
12787 		{
12788 			// post-swing return to normal weapon yaw
12789 			if ( limbAnimateToLimit(this, ANIMATE_WEAPON_YAW, -0.5, 0, false, 0.0) )
12790 			{
12791 				// restore pitch and roll after yaw is set
12792 				if ( limbAnimateToLimit(weaponarm, ANIMATE_ROLL, 0.4, 0, false, 0.0)
12793 					&& limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.4, 7 * PI / 4, false, 0.0) )
12794 				{
12795 					weaponarm->skill[0] = rightbody->skill[0];
12796 					this->monsterWeaponYaw = 0;
12797 					weaponarm->pitch = rightbody->pitch;
12798 					weaponarm->roll = 0;
12799 					this->monsterArmbended = 0;
12800 					monsterAttack = 0;
12801 				}
12802 			}
12803 		}
12804 	}
12805 	// stab windup
12806 	else if ( monsterAttack == MONSTER_POSE_MELEE_WINDUP3 )
12807 	{
12808 		if ( monsterAttackTime == 0 )
12809 		{
12810 			// init rotations
12811 			this->monsterArmbended = 0;
12812 			this->monsterWeaponYaw = 0;
12813 			weaponarm->roll = 0;
12814 			weaponarm->pitch = 0;
12815 			weaponarm->skill[1] = 0;
12816 		}
12817 
12818 		limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.5, 2 * PI / 3, false, 0.0);
12819 
12820 		if ( monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
12821 		{
12822 			if ( multiplayer != CLIENT )
12823 			{
12824 				this->attack(3, 0, nullptr);
12825 			}
12826 		}
12827 	}
12828 	// stab attack - refer to weapon limb code for additional animation
12829 	else if ( monsterAttack == 3 )
12830 	{
12831 		if ( weaponarm->skill[1] == 0 )
12832 		{
12833 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.3, 0, false, 0.0) )
12834 			{
12835 				weaponarm->skill[1] = 1;
12836 			}
12837 		}
12838 		else if ( weaponarm->skill[1] == 1 )
12839 		{
12840 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.3, 2 * PI / 3, false, 0.0) )
12841 			{
12842 				weaponarm->skill[1] = 2;
12843 			}
12844 		}
12845 		else if ( weaponarm->skill[1] >= 2 )
12846 		{
12847 			// return to neutral
12848 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 0, false, 0.0) )
12849 			{
12850 				weaponarm->skill[0] = rightbody->skill[0];
12851 				this->monsterWeaponYaw = 0;
12852 				weaponarm->pitch = rightbody->pitch;
12853 				weaponarm->roll = 0;
12854 				this->monsterArmbended = 0;
12855 				monsterAttack = 0;
12856 			}
12857 		}
12858 	}
12859 	// ranged weapons
12860 	else if ( monsterAttack == MONSTER_POSE_RANGED_WINDUP1 )
12861 	{
12862 		// crossbow
12863 		if ( monsterAttackTime == 0 )
12864 		{
12865 			// init rotations
12866 			this->monsterArmbended = 0;
12867 			this->monsterWeaponYaw = 0;
12868 			weaponarm->roll = 0;
12869 			weaponarm->skill[1] = 0;
12870 		}
12871 
12872 		// draw the crossbow level... slowly
12873 		if ( weaponarm->pitch > PI || weaponarm->pitch < 0 )
12874 		{
12875 			limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.1, 0, false, 0.0);
12876 		}
12877 		else
12878 		{
12879 			limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.1, 0, false, 0.0);
12880 		}
12881 
12882 		if ( monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
12883 		{
12884 			if ( multiplayer != CLIENT )
12885 			{
12886 				this->attack(MONSTER_POSE_RANGED_SHOOT1, 0, nullptr);
12887 			}
12888 		}
12889 	}
12890 	// shoot crossbow
12891 	else if ( monsterAttack == MONSTER_POSE_RANGED_SHOOT1 )
12892 	{
12893 		// recoil upwards
12894 		if ( weaponarm->skill[1] == 0 )
12895 		{
12896 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 15 * PI / 8, false, 0.0) )
12897 			{
12898 				weaponarm->skill[1] = 1;
12899 			}
12900 		}
12901 		// recoil downwards
12902 		else if ( weaponarm->skill[1] == 1 )
12903 		{
12904 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.1, PI / 3, false, 0.0) )
12905 			{
12906 				weaponarm->skill[1] = 2;
12907 			}
12908 		}
12909 		else if ( weaponarm->skill[1] >= 2 )
12910 		{
12911 			// limbAngleWithinRange cuts off animation early so it doesn't snap too far back to position.
12912 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 0, false, 0.0) || limbAngleWithinRange(weaponarm->pitch, -0.2, rightbody->pitch) )
12913 			{
12914 				weaponarm->skill[0] = rightbody->skill[0];
12915 				this->monsterWeaponYaw = 0;
12916 				//if ( this->hasRangedWeapon() && this->monsterState == MONSTER_STATE_ATTACK )
12917 				//{
12918 				//	// don't move ranged weapons so far if ready to attack
12919 				//	weaponarm->pitch = rightbody->pitch * 0.25;
12920 				//}
12921 				//else
12922 				//{
12923 				//	weaponarm->pitch = rightbody->pitch;
12924 				//}
12925 				weaponarm->roll = 0;
12926 				this->monsterArmbended = 0;
12927 				monsterAttack = 0;
12928 			}
12929 		}
12930 	}
12931 	// shortbow/sling
12932 	else if ( monsterAttack == MONSTER_POSE_RANGED_WINDUP2 )
12933 	{
12934 		if ( monsterAttackTime == 0 )
12935 		{
12936 			// init rotations
12937 			this->monsterArmbended = 0;
12938 			this->monsterWeaponYaw = 0;
12939 			weaponarm->roll = 0;
12940 			weaponarm->skill[1] = 0;
12941 		}
12942 
12943 		// draw the weapon level... slowly and shake
12944 		if ( weaponarm->pitch > PI || weaponarm->pitch < 0 )
12945 		{
12946 			limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.1, 0, true, 0.1);
12947 		}
12948 		else
12949 		{
12950 			limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.1, 0, true, 0.1);
12951 		}
12952 
12953 		if ( monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
12954 		{
12955 			if ( multiplayer != CLIENT )
12956 			{
12957 				this->attack(MONSTER_POSE_RANGED_SHOOT2, 0, nullptr);
12958 			}
12959 		}
12960 	}
12961 	// shoot shortbow/sling
12962 	else if ( monsterAttack == MONSTER_POSE_RANGED_SHOOT2 )
12963 	{
12964 		// recoil upwards
12965 		if ( weaponarm->skill[1] == 0 )
12966 		{
12967 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 14 * PI / 8, false, 0.0) )
12968 			{
12969 				weaponarm->skill[1] = 1;
12970 			}
12971 		}
12972 		// recoil downwards
12973 		else if ( weaponarm->skill[1] == 1 )
12974 		{
12975 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.1, 1 * PI / 3, false, 0.0) )
12976 			{
12977 				weaponarm->skill[1] = 2;
12978 			}
12979 		}
12980 		else if ( weaponarm->skill[1] >= 2 )
12981 		{
12982 			// limbAngleWithinRange cuts off animation early so it doesn't snap too far back to position.
12983 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.2, 0, false, 0.0) || limbAngleWithinRange(weaponarm->pitch, -0.2, rightbody->pitch) )
12984 			{
12985 				weaponarm->skill[0] = rightbody->skill[0];
12986 				this->monsterWeaponYaw = 0;
12987 				weaponarm->pitch = rightbody->pitch;
12988 				weaponarm->roll = 0;
12989 				this->monsterArmbended = 0;
12990 				monsterAttack = 0;
12991 				// play draw arrow sound
12992 				playSoundEntityLocal(this, 246, 16);
12993 			}
12994 		}
12995 	}
12996 	else if ( monsterAttack == MONSTER_POSE_MAGIC_WINDUP1 )
12997 	{
12998 		// magic wiggle hands
12999 		if ( monsterAttackTime == 0 )
13000 		{
13001 			// init rotations
13002 			this->monsterArmbended = 0;
13003 			this->monsterWeaponYaw = 0;
13004 			weaponarm->roll = 0;
13005 			weaponarm->pitch = 0;
13006 			weaponarm->yaw = this->yaw;
13007 			weaponarm->skill[1] = 0;
13008 			// casting particles
13009 			createParticleDot(this);
13010 			// play casting sound
13011 			playSoundEntityLocal(this, 170, 32);
13012 		}
13013 
13014 		double animationYawSetpoint = 0.f;
13015 		double animationYawEndpoint = 0.f;
13016 		double armSwingRate = 0.f;
13017 		double animationPitchSetpoint = 0.f;
13018 		double animationPitchEndpoint = 0.f;
13019 
13020 		switch ( this->monsterSpellAnimation )
13021 		{
13022 			case MONSTER_SPELLCAST_NONE:
13023 				break;
13024 			case MONSTER_SPELLCAST_SMALL_HUMANOID:
13025 				// smaller models so arms can wave in a larger radius and faster.
13026 				animationYawSetpoint = normaliseAngle2PI(this->yaw + 2 * PI / 8);
13027 				animationYawEndpoint = normaliseAngle2PI(this->yaw - 2 * PI / 8);
13028 				animationPitchSetpoint = 2 * PI / 8;
13029 				animationPitchEndpoint = 14 * PI / 8;
13030 				armSwingRate = 0.3;
13031 				if ( monsterAttackTime == 0 )
13032 				{
13033 					weaponarm->yaw = this->yaw - PI / 8;
13034 				}
13035 				break;
13036 			case MONSTER_SPELLCAST_HUMANOID:
13037 				animationYawSetpoint = normaliseAngle2PI(this->yaw + 1 * PI / 8);
13038 				animationYawEndpoint = normaliseAngle2PI(this->yaw - 1 * PI / 8);
13039 				animationPitchSetpoint = 1 * PI / 8;
13040 				animationPitchEndpoint = 15 * PI / 8;
13041 				armSwingRate = 0.15;
13042 				break;
13043 			default:
13044 				break;
13045 		}
13046 
13047 		if ( weaponarm->skill[1] == 0 )
13048 		{
13049 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, armSwingRate, animationPitchSetpoint, false, 0.0) )
13050 			{
13051 				if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, armSwingRate, animationYawSetpoint, false, 0.0) )
13052 				{
13053 					weaponarm->skill[1] = 1;
13054 				}
13055 			}
13056 		}
13057 		else
13058 		{
13059 			if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -armSwingRate, animationPitchEndpoint, false, 0.0) )
13060 			{
13061 				if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, -armSwingRate, animationYawEndpoint, false, 0.0) )
13062 				{
13063 					weaponarm->skill[1] = 0;
13064 				}
13065 			}
13066 		}
13067 
13068 		if ( monsterAttackTime >= 2 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
13069 		{
13070 			if ( multiplayer != CLIENT )
13071 			{
13072 				// swing the arm after we prepped the spell
13073 				this->attack(MONSTER_POSE_MAGIC_WINDUP2, 0, nullptr);
13074 			}
13075 		}
13076 	}
13077 	// swing arm to cast spell
13078 	else if ( monsterAttack == MONSTER_POSE_MAGIC_WINDUP2 )
13079 	{
13080 		if ( monsterAttackTime == 0 )
13081 		{
13082 			// init rotations
13083 			weaponarm->pitch = 0;
13084 			this->monsterArmbended = 0;
13085 			this->monsterWeaponYaw = 0;
13086 			weaponarm->roll = 0;
13087 			weaponarm->skill[1] = 0;
13088 		}
13089 
13090 		if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.3, 5 * PI / 4, false, 0.0) )
13091 		{
13092 			if ( multiplayer != CLIENT )
13093 			{
13094 				Stat* stats = this->getStats();
13095 				if ( stats && stats->type == SHADOW )
13096 				{
13097 					this->attack(MONSTER_POSE_MAGIC_CAST1, 0, nullptr);
13098 				}
13099 				else
13100 				{
13101 					this->attack(1, 0, nullptr);
13102 				}
13103 			}
13104 		}
13105 	}
13106 
13107 	return;
13108 }
13109 
humanoidAnimateWalk(Entity * limb,node_t * bodypartNode,int bodypart,double walkSpeed,double dist,double distForFootstepSound)13110 void Entity::humanoidAnimateWalk(Entity* limb, node_t* bodypartNode, int bodypart, double walkSpeed, double dist, double distForFootstepSound)
13111 {
13112 	if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
13113 	{
13114 		Entity* rightbody = nullptr;
13115 		// set rightbody to left leg.
13116 		node_t* rightbodyNode = list_Node(&this->children, LIMB_HUMANOID_LEFTLEG);
13117 		if ( rightbodyNode )
13118 		{
13119 			rightbody = (Entity*)rightbodyNode->element;
13120 		}
13121 		else
13122 		{
13123 			return;
13124 		}
13125 
13126 		node_t* shieldNode = list_Node(&this->children, 8);
13127 		if ( shieldNode )
13128 		{
13129 			Entity* shield = (Entity*)shieldNode->element;
13130 			if ( dist > 0.1 && (bodypart != LIMB_HUMANOID_LEFTARM || shield->sprite == 0) )
13131 			{
13132 				// walking to destination
13133 				if ( !rightbody->skill[0] )
13134 				{
13135 					limb->pitch -= dist * walkSpeed;
13136 					if ( limb->pitch < -PI / 4.0 )
13137 					{
13138 						limb->pitch = -PI / 4.0;
13139 						if ( bodypart == LIMB_HUMANOID_RIGHTLEG )
13140 						{
13141 							if ( dist > distForFootstepSound )
13142 							{
13143 								if ( limb->skill[0] == 0 ) // fix for waking up on sleep to reduce repeated sound bytes in race condition.
13144 								{
13145 									if ( this->monsterFootstepType == MONSTER_FOOTSTEP_USE_BOOTS )
13146 									{
13147 										node_t* tempNode = list_Node(&this->children, 3);
13148 										if ( tempNode )
13149 										{
13150 											Entity* foot = (Entity*)tempNode->element;
13151 											playSoundEntityLocal(this, getMonsterFootstepSound(this->monsterFootstepType, foot->sprite), 32);
13152 										}
13153 									}
13154 									else
13155 									{
13156 										playSoundEntityLocal(this, getMonsterFootstepSound(this->monsterFootstepType, 0), 32);
13157 									}
13158 									limb->skill[0] = 1;
13159 								}
13160 							}
13161 						}
13162 					}
13163 				}
13164 				else
13165 				{
13166 					limb->pitch += dist * walkSpeed;
13167 					if ( limb->pitch > PI / 4.0 )
13168 					{
13169 						limb->pitch = PI / 4.0;
13170 						if ( bodypart == LIMB_HUMANOID_RIGHTLEG )
13171 						{
13172 							if ( dist > distForFootstepSound )
13173 							{
13174 								if ( limb->skill[0] == 1 ) // fix for waking up on sleep to reduce repeated sound bytes in race condition.
13175 								{
13176 									if ( this->monsterFootstepType == MONSTER_FOOTSTEP_USE_BOOTS )
13177 									{
13178 										node_t* tempNode = list_Node(&this->children, 3);
13179 										if ( tempNode )
13180 										{
13181 											Entity* foot = (Entity*)tempNode->element;
13182 											playSoundEntityLocal(this, getMonsterFootstepSound(this->monsterFootstepType, foot->sprite), 32);
13183 										}
13184 									}
13185 									else
13186 									{
13187 										playSoundEntityLocal(this, getMonsterFootstepSound(this->monsterFootstepType, 0), 32);
13188 									}
13189 									limb->skill[0] = 0;
13190 								}
13191 							}
13192 						}
13193 					}
13194 				}
13195 			}
13196 			else
13197 			{
13198 				// coming to a stop
13199 				if ( limb->pitch < 0 || (limb->pitch > PI && limb->pitch < 2 * PI) )
13200 				{
13201 					limb->pitch += 1 / fmax(dist * .1, 10.0);
13202 					if ( limb->pitch > 0 )
13203 					{
13204 						limb->pitch = 0;
13205 					}
13206 				}
13207 				else if ( limb->pitch > 0 )
13208 				{
13209 					limb->pitch -= 1 / fmax(dist * .1, 10.0);
13210 					if ( limb->pitch < 0 )
13211 					{
13212 						limb->pitch = 0;
13213 					}
13214 				}
13215 			}
13216 		}
13217 	}
13218 	else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM || bodypart == LIMB_HUMANOID_CLOAK )
13219 	{
13220 		if ( bodypart != LIMB_HUMANOID_RIGHTARM || (this->monsterAttack == 0 && this->monsterAttackTime == 0) )
13221 		{
13222 			if ( dist > 0.1 )
13223 			{
13224 				double armMoveSpeed = 1.0;
13225 				if ( bodypart == LIMB_HUMANOID_RIGHTARM && this->hasRangedWeapon() && this->monsterState == MONSTER_STATE_ATTACK )
13226 				{
13227 					// don't move ranged weapons so far if ready to attack
13228 					armMoveSpeed = 0.5;
13229 				}
13230 
13231 				if ( limb->skill[0] )
13232 				{
13233 					limb->pitch -= dist * walkSpeed * armMoveSpeed;
13234 					if ( limb->pitch < -PI * armMoveSpeed / 4.0 )
13235 					{
13236 						limb->skill[0] = 0;
13237 						limb->pitch = -PI * armMoveSpeed / 4.0;
13238 					}
13239 				}
13240 				else
13241 				{
13242 					limb->pitch += dist * walkSpeed * armMoveSpeed;
13243 					if ( limb->pitch > PI * armMoveSpeed / 4.0 )
13244 					{
13245 						limb->skill[0] = 1;
13246 						limb->pitch = PI * armMoveSpeed / 4.0;
13247 					}
13248 				}
13249 			}
13250 			else
13251 			{
13252 				if ( limb->pitch < 0 )
13253 				{
13254 					limb->pitch += 1 / fmax(dist * .1, 10.0);
13255 					if ( limb->pitch > 0 )
13256 					{
13257 						limb->pitch = 0;
13258 					}
13259 				}
13260 				else if ( limb->pitch > 0 )
13261 				{
13262 					limb->pitch -= 1 / fmax(dist * .1, 10.0);
13263 					if ( limb->pitch < 0 )
13264 					{
13265 						limb->pitch = 0;
13266 					}
13267 				}
13268 			}
13269 		}
13270 	}
13271 
13272 	return;
13273 }
13274 
getMonsterFootstepSound(int footstepType,int bootSprite)13275 Uint32 Entity::getMonsterFootstepSound(int footstepType, int bootSprite)
13276 {
13277 	int sound = -1;
13278 
13279 	switch ( footstepType )
13280 	{
13281 		case MONSTER_FOOTSTEP_SKELETON:
13282 			sound = 95;
13283 			break;
13284 		case MONSTER_FOOTSTEP_STOMP:
13285 			sound = 115;
13286 			break;
13287 		case MONSTER_FOOTSTEP_LEATHER:
13288 			sound = rand() % 7;
13289 			break;
13290 		case MONSTER_FOOTSTEP_USE_BOOTS:
13291 			if ( bootSprite >= 152 && bootSprite <= 155 ) // iron boots
13292 			{
13293 				sound = 7 + rand() % 7;
13294 			}
13295 			else if ( bootSprite >= 156 && bootSprite <= 159 ) // steel boots
13296 			{
13297 				sound = 14 + rand() % 7;
13298 			}
13299 			else if ( bootSprite >= 499 && bootSprite <= 502 ) // crystal boots
13300 			{
13301 				sound = 14 + rand() % 7;
13302 			}
13303 			else if ( bootSprite >= 521 && bootSprite <= 524 ) // artifact boots
13304 			{
13305 				sound = 14 + rand() % 7;
13306 			}
13307 			else
13308 			{
13309 				sound = rand() % 7;
13310 			}
13311 			break;
13312 		case MONSTER_FOOTSTEP_NONE:
13313 		default:
13314 			break;
13315 	}
13316 	return static_cast<Uint32>(sound);
13317 }
13318 
handleHumanoidWeaponLimb(Entity * weaponLimb,Entity * weaponArmLimb)13319 void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb)
13320 {
13321 	if ( weaponLimb == nullptr || weaponArmLimb == nullptr )
13322 	{
13323 		return;
13324 	}
13325 
13326 	int monsterType = this->getMonsterTypeFromSprite();
13327 	int myAttack = this->monsterAttack;
13328 	bool isPlayer = this->behavior == &actPlayer;
13329 	if ( isPlayer )
13330 	{
13331 		myAttack = this->skill[9];
13332 	}
13333 
13334 	if ( weaponLimb->flags[INVISIBLE] == false ) //TODO: isInvisible()?
13335 	{
13336 		if ( weaponLimb->sprite == items[SHORTBOW].index )
13337 		{
13338 			weaponLimb->x = weaponArmLimb->x - .5 * cos(weaponArmLimb->yaw);
13339 			weaponLimb->y = weaponArmLimb->y - .5 * sin(weaponArmLimb->yaw);
13340 			weaponLimb->z = weaponArmLimb->z + 1;
13341 			weaponLimb->pitch = weaponArmLimb->pitch + .25;
13342 		}
13343 		else if ( weaponLimb->sprite == items[ARTIFACT_BOW].index
13344 			|| weaponLimb->sprite == items[LONGBOW].index
13345 			|| weaponLimb->sprite == items[COMPOUND_BOW].index )
13346 		{
13347 			if ( isPlayer && monsterType == HUMAN )
13348 			{
13349 				weaponLimb->x = weaponArmLimb->x - .5 * cos(weaponArmLimb->yaw);
13350 				weaponLimb->y = weaponArmLimb->y - .5 * sin(weaponArmLimb->yaw);
13351 				weaponLimb->z = weaponArmLimb->z + 1;
13352 				weaponLimb->pitch = weaponArmLimb->pitch + .25;
13353 			}
13354 			else
13355 			{
13356 				weaponLimb->x = weaponArmLimb->x - .5 * cos(weaponArmLimb->yaw);
13357 				weaponLimb->y = weaponArmLimb->y - .5 * sin(weaponArmLimb->yaw);
13358 				weaponLimb->z = weaponArmLimb->z + 1;
13359 				weaponLimb->pitch = weaponArmLimb->pitch + .25;
13360 			}
13361 
13362 			if ( weaponLimb->sprite == items[LONGBOW].index )
13363 			{
13364 				weaponLimb->x -= .5 * cos(weaponArmLimb->yaw);
13365 				weaponLimb->y -= .5 * sin(weaponArmLimb->yaw);
13366 			}
13367 			else if ( weaponLimb->sprite == items[COMPOUND_BOW].index )
13368 			{
13369 				weaponLimb->x += .5 * cos(weaponArmLimb->yaw);
13370 				weaponLimb->y += .5 * sin(weaponArmLimb->yaw);
13371 			}
13372 		}
13373 		else if ( weaponLimb->sprite == items[CROSSBOW].index || weaponLimb->sprite == items[HEAVY_CROSSBOW].index )
13374 		{
13375 			weaponLimb->x = weaponArmLimb->x;
13376 			weaponLimb->y = weaponArmLimb->y;
13377 			weaponLimb->z = weaponArmLimb->z + 1;
13378 			weaponLimb->pitch = weaponArmLimb->pitch;
13379 		}
13380 		else if ( weaponLimb->sprite == items[TOOL_LOCKPICK].index )
13381 		{
13382 			weaponLimb->x = weaponArmLimb->x + 1.5 * cos(weaponArmLimb->yaw);
13383 			weaponLimb->y = weaponArmLimb->y + 1.5 * sin(weaponArmLimb->yaw);
13384 			weaponLimb->z = weaponArmLimb->z + 1.5;
13385 			weaponLimb->pitch = weaponArmLimb->pitch + .25;
13386 		}
13387 		else
13388 		{
13389 			/*weaponLimb->focalx = limbs[monsterType][6][0];
13390 			weaponLimb->focalz = limbs[monsterType][6][2];*/
13391 			if ( myAttack == 3 && !isPlayer )
13392 			{
13393 				// poking animation, weapon pointing straight ahead.
13394 				if ( weaponArmLimb->skill[1] < 2 && weaponArmLimb->pitch < PI / 2 )
13395 				{
13396 					// cos(weaponArmLimb->pitch)) * cos(weaponArmLimb->yaw) allows forward/back motion dependent on the arm rotation.
13397 					weaponLimb->x = weaponArmLimb->x + (3 * cos(weaponArmLimb->pitch)) * cos(weaponArmLimb->yaw);
13398 					weaponLimb->y = weaponArmLimb->y + (3 * cos(weaponArmLimb->pitch)) * sin(weaponArmLimb->yaw);
13399 
13400 					if ( weaponArmLimb->pitch < PI / 3 )
13401 					{
13402 						// adjust the z point halfway through swing.
13403 						weaponLimb->z = weaponArmLimb->z + 1.5 - 2 * cos(weaponArmLimb->pitch / 2);
13404 						if ( monsterType == INCUBUS || monsterType == SUCCUBUS )
13405 						{
13406 							weaponLimb->z += 2;
13407 						}
13408 					}
13409 					else
13410 					{
13411 						weaponLimb->z = weaponArmLimb->z - .5 * (myAttack == 0);
13412 						if ( weaponLimb->pitch > PI / 2 )
13413 						{
13414 							limbAnimateToLimit(weaponLimb, ANIMATE_PITCH, -0.5, PI * 0.5, false, 0);
13415 						}
13416 						else
13417 						{
13418 							limbAnimateToLimit(weaponLimb, ANIMATE_PITCH, 0.5, PI * 0.5, false, 0);
13419 						}
13420 						if ( monsterType == INCUBUS || monsterType == SUCCUBUS )
13421 						{
13422 							weaponLimb->z += 1.25;
13423 						}
13424 					}
13425 				}
13426 				// hold sword with pitch aligned to arm rotation.
13427 				else
13428 				{
13429 					weaponLimb->x = weaponArmLimb->x + .5 * cos(weaponArmLimb->yaw) * (myAttack == 0);
13430 					weaponLimb->y = weaponArmLimb->y + .5 * sin(weaponArmLimb->yaw) * (myAttack == 0);
13431 					weaponLimb->z = weaponArmLimb->z - .5;
13432 					weaponLimb->pitch = weaponArmLimb->pitch + .25 * (myAttack == 0);
13433 					if ( monsterType == INCUBUS || monsterType == SUCCUBUS )
13434 					{
13435 						weaponLimb->z += 1;
13436 					}
13437 				}
13438 			}
13439 			else
13440 			{
13441 				weaponLimb->x = weaponArmLimb->x + .5 * cos(weaponArmLimb->yaw) * (myAttack == 0);
13442 				weaponLimb->y = weaponArmLimb->y + .5 * sin(weaponArmLimb->yaw) * (myAttack == 0);
13443 				weaponLimb->z = weaponArmLimb->z - .5 * (myAttack == 0);
13444 				weaponLimb->pitch = weaponArmLimb->pitch + .25 * (myAttack == 0);
13445 			}
13446 		}
13447 	}
13448 
13449 	weaponLimb->yaw = weaponArmLimb->yaw;
13450 	bool isPotion = false;
13451 	if ( myAttack == MONSTER_POSE_RANGED_WINDUP3 && monsterType == GOATMAN && !isPlayer )
13452 	{
13453 		// specific for potion throwing goatmen.
13454 		limbAnimateToLimit(weaponLimb, ANIMATE_ROLL, 0.25, 1 * PI / 4, false, 0.0);
13455 	}
13456 	else
13457 	{
13458 		weaponLimb->roll = weaponArmLimb->roll;
13459 		if ( isPlayer )
13460 		{
13461 			if ( (weaponLimb->sprite >= 50 && weaponLimb->sprite < 58)
13462 				|| weaponLimb->sprite == 795 )
13463 			{
13464 				weaponLimb->roll += (PI / 2); // potion sprites rotated
13465 				isPotion = true;
13466 			}
13467 			else if ( weaponLimb->sprite == items[BOOMERANG].index )
13468 			{
13469 				weaponLimb->roll += (PI / 2); // sprite rotated
13470 				weaponLimb->pitch -= PI / 8;
13471 				weaponLimb->pitch += .25 * (myAttack != 0); // add 0.25 if attacking
13472 			}
13473 			else if ( weaponLimb->sprite == items[FOOD_CREAMPIE].index )
13474 			{
13475 				weaponLimb->roll += (PI / 2); // sprite rotated
13476 			}
13477 		}
13478 	}
13479 
13480 	bool armBended = (!isPlayer && this->monsterArmbended) || (isPlayer && this->skill[11]);
13481 	weaponLimb->scalex = 1.f;
13482 	weaponLimb->scaley = 1.f;
13483 	weaponLimb->scalez = 1.f;
13484 	if ( weaponLimb->sprite == items[TOOL_WHIP].index || weaponLimb->sprite == items[TOOL_WHIP].index + 1 )
13485 	{
13486 		if ( myAttack != 2 )
13487 		{
13488 			weaponLimb->pitch -= PI / 8;
13489 			if ( weaponLimb->sprite == items[TOOL_WHIP].index + 1 )
13490 			{
13491 				weaponLimb->pitch -= PI / 8;
13492 			}
13493 		}
13494 		if ( myAttack == 1 )
13495 		{
13496 			if ( weaponArmLimb->skill[1] == 1 && armBended )
13497 			{
13498 				if ( weaponArmLimb->pitch >= 3 * PI / 2 )
13499 				{
13500 					if ( weaponLimb->sprite == items[TOOL_WHIP].index )
13501 					{
13502 						weaponLimb->sprite += 1;
13503 					}
13504 				}
13505 				else if ( weaponArmLimb->pitch >= PI / 10 )
13506 				{
13507 					if ( weaponLimb->sprite == items[TOOL_WHIP].index + 1 )
13508 					{
13509 						weaponLimb->sprite = items[TOOL_WHIP].index;
13510 					}
13511 				}
13512 			}
13513 			else
13514 			{
13515 				weaponLimb->sprite = items[TOOL_WHIP].index;
13516 			}
13517 		}
13518 		else
13519 		{
13520 			weaponLimb->sprite = items[TOOL_WHIP].index;
13521 		}
13522 	}
13523 	else if ( weaponLimb->sprite == items[TOOL_DECOY].index || weaponLimb->sprite == items[TOOL_DUMMYBOT].index )
13524 	{
13525 		weaponLimb->scalex = 0.8;
13526 		weaponLimb->scaley = 0.8;
13527 		weaponLimb->scalez = 0.8;
13528 	}
13529 
13530 	if ( isPlayer && monsterType == CREATURE_IMP )
13531 	{
13532 		weaponLimb->focalx = limbs[monsterType][9][0];
13533 		weaponLimb->focaly = limbs[monsterType][9][1];
13534 		weaponLimb->focalz = limbs[monsterType][9][2];
13535 		weaponLimb->pitch += .5 + limbs[monsterType][10][0];
13536 	}
13537 	else if ( !armBended )
13538 	{
13539 		weaponLimb->focalx = limbs[monsterType][6][0]; // 2.5
13540 		weaponLimb->focaly = limbs[monsterType][6][1]; // 0
13541 		if ( weaponLimb->sprite == items[CROSSBOW].index || weaponLimb->sprite == items[HEAVY_CROSSBOW].index )
13542 		{
13543 			weaponLimb->focalx += 2.1;
13544 			weaponLimb->focaly -= 0.1;
13545 		}
13546 		weaponLimb->focalz = limbs[monsterType][6][2]; // -.5
13547 		if ( isPlayer && isPotion )
13548 		{
13549 			weaponLimb->focalz += 1;
13550 			if ( monsterType == INCUBUS || monsterType == SUCCUBUS )
13551 			{
13552 				weaponLimb->focaly += 1;
13553 				weaponLimb->focalz -= 1.5;
13554 			}
13555 		}
13556 		else if ( weaponLimb->sprite == items[BOOMERANG].index )
13557 		{
13558 			weaponLimb->focalx += 2;
13559 			weaponLimb->focaly += 0.25;
13560 			weaponLimb->focalz += 0;
13561 			weaponLimb->x += -1.2 * cos(weaponArmLimb->yaw + PI / 2) + -.6 * cos(weaponArmLimb->yaw);
13562 			weaponLimb->y += -1.2 * sin(weaponArmLimb->yaw + PI / 2) + -.6 * sin(weaponArmLimb->yaw);
13563 			weaponLimb->z += 0.25;
13564 			switch ( monsterType )
13565 			{
13566 				case SKELETON:
13567 				case AUTOMATON:
13568 				case GOATMAN:
13569 				case INSECTOID:
13570 				case GOBLIN:
13571 					weaponLimb->x += 0.5 * cos(weaponArmLimb->yaw + PI / 2);
13572 					weaponLimb->y += 0.5 * sin(weaponArmLimb->yaw + PI / 2);
13573 					break;
13574 				case INCUBUS:
13575 				case SUCCUBUS:
13576 					weaponLimb->x += 1.75 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13577 					weaponLimb->y += 1.75 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13578 					weaponLimb->z += 0;
13579 
13580 					weaponLimb->focalx += -0.75;
13581 					weaponLimb->focaly += 1;
13582 					weaponLimb->focalz += 0;
13583 					break;
13584 				default:
13585 					break;
13586 			}
13587 		}
13588 		else if ( weaponLimb->sprite == items[TOOL_WHIP].index || weaponLimb->sprite == items[TOOL_WHIP].index + 1 )
13589 		{
13590 			weaponLimb->focalx += 1;
13591 			weaponLimb->focalz += 1.5;
13592 		}
13593 		else if ( weaponLimb->sprite == items[TOOL_GYROBOT].index )
13594 		{
13595 			weaponLimb->focalz += 1;
13596 		}
13597 		else if ( weaponLimb->sprite >= items[TOOL_BOMB].index && weaponLimb->sprite <= items[TOOL_TELEPORT_BOMB].index )
13598 		{
13599 			weaponLimb->focalz += 2;
13600 			if ( monsterType == SKELETON )
13601 			{
13602 				weaponLimb->focalx -= 0.25;
13603 				weaponLimb->focaly += 0.1;
13604 			}
13605 			else if ( monsterType == AUTOMATON )
13606 			{
13607 				weaponLimb->focaly += 0.5;
13608 				weaponLimb->focalz -= 1;
13609 			}
13610 		}
13611 		else if ( weaponLimb->sprite == items[SHORTBOW].index || weaponLimb->sprite == items[ARTIFACT_BOW].index
13612 			|| weaponLimb->sprite == items[LONGBOW].index || weaponLimb->sprite == items[COMPOUND_BOW].index )
13613 		{
13614 			if ( weaponLimb->sprite == items[SHORTBOW].index )
13615 			{
13616 				switch ( monsterType )
13617 				{
13618 					case HUMAN:
13619 					case VAMPIRE:
13620 					case SHOPKEEPER:
13621 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13622 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13623 						weaponLimb->z += -1.25;
13624 						weaponLimb->focalx += -0.5;
13625 						weaponLimb->focaly += 0;
13626 						weaponLimb->focalz += 1.75;
13627 						break;
13628 					case GOBLIN:
13629 					case GOATMAN:
13630 					case INSECTOID:
13631 					case SUCCUBUS:
13632 					case INCUBUS:
13633 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13634 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13635 						weaponLimb->z += -1;
13636 						weaponLimb->focalx += 0;
13637 						weaponLimb->focaly += 0;
13638 						weaponLimb->focalz += 1.25;
13639 						break;
13640 					case SKELETON:
13641 					case AUTOMATON:
13642 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.5 * cos(weaponArmLimb->yaw);
13643 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.5 * sin(weaponArmLimb->yaw);
13644 						weaponLimb->z += -1;
13645 						weaponLimb->focalx += -0.5;
13646 						weaponLimb->focaly += 0;
13647 						weaponLimb->focalz += 1;
13648 						break;
13649 					default:
13650 						break;
13651 				}
13652 			}
13653 			else if ( weaponLimb->sprite == items[ARTIFACT_BOW].index
13654 				|| weaponLimb->sprite == items[LONGBOW].index )
13655 			{
13656 				switch ( monsterType )
13657 				{
13658 					case HUMAN:
13659 					case VAMPIRE:
13660 					case SHOPKEEPER:
13661 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.75 * cos(weaponArmLimb->yaw);
13662 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.75 * sin(weaponArmLimb->yaw);
13663 						weaponLimb->z += -1.25;
13664 						weaponLimb->focalx += -0.5;
13665 						weaponLimb->focaly += 0;
13666 						weaponLimb->focalz += 1.75;
13667 						if ( weaponLimb->sprite == items[LONGBOW].index )
13668 						{
13669 							weaponLimb->x += -0.25 * cos(weaponArmLimb->yaw);
13670 							weaponLimb->y += -0.25 * sin(weaponArmLimb->yaw);
13671 							weaponLimb->z += 0.25;
13672 							weaponLimb->focalx += 0.5;
13673 							weaponLimb->focaly += 0;
13674 							weaponLimb->focalz += -0.5;
13675 						}
13676 						break;
13677 					case GOBLIN:
13678 					case GOATMAN:
13679 					case INSECTOID:
13680 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.5 * cos(weaponArmLimb->yaw);
13681 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.5 * sin(weaponArmLimb->yaw);
13682 						weaponLimb->z += -1;
13683 						weaponLimb->focalx += 0;
13684 						weaponLimb->focaly += 0;
13685 						weaponLimb->focalz += 1.25;
13686 						break;
13687 					case SUCCUBUS:
13688 					case INCUBUS:
13689 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13690 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13691 						weaponLimb->z += -1;
13692 						weaponLimb->focalx += 0;
13693 						weaponLimb->focaly += 0;
13694 						weaponLimb->focalz += 1.25;
13695 						break;
13696 					case SKELETON:
13697 					case AUTOMATON:
13698 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13699 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13700 						weaponLimb->z += -1.25;
13701 						weaponLimb->focalx += 0;
13702 						weaponLimb->focaly += 0;
13703 						weaponLimb->focalz += 1.25;
13704 						break;
13705 					default:
13706 						break;
13707 				}
13708 				if ( weaponLimb->sprite == items[LONGBOW].index )
13709 				{
13710 					// this applies to all offsets for all monsters.
13711 					weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.75 * cos(weaponArmLimb->yaw);
13712 					weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.75 * sin(weaponArmLimb->yaw);
13713 					weaponLimb->z += 0.25;
13714 					weaponLimb->focalx += -.75;
13715 					weaponLimb->focaly += 0;
13716 					weaponLimb->focalz += 0;
13717 				}
13718 			}
13719 			else if ( weaponLimb->sprite == items[COMPOUND_BOW].index )
13720 			{
13721 				switch ( monsterType )
13722 				{
13723 					case HUMAN:
13724 					case VAMPIRE:
13725 					case SHOPKEEPER:
13726 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.f * cos(weaponArmLimb->yaw);
13727 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.f * sin(weaponArmLimb->yaw);
13728 						weaponLimb->z += -1.25;
13729 						weaponLimb->focalx += 0;
13730 						weaponLimb->focaly += 0;
13731 						weaponLimb->focalz += 1.75;
13732 						break;
13733 					case GOBLIN:
13734 					case GOATMAN:
13735 					case INSECTOID:
13736 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.5 * cos(weaponArmLimb->yaw);
13737 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.5 * sin(weaponArmLimb->yaw);
13738 						weaponLimb->z += -1;
13739 						weaponLimb->focalx += 0;
13740 						weaponLimb->focaly += 0;
13741 						weaponLimb->focalz += 1.25;
13742 						break;
13743 					case SUCCUBUS:
13744 					case INCUBUS:
13745 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13746 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13747 						weaponLimb->z += -1;
13748 						weaponLimb->focalx += 0;
13749 						weaponLimb->focaly += 0;
13750 						weaponLimb->focalz += 1.25;
13751 						break;
13752 					case SKELETON:
13753 					case AUTOMATON:
13754 						weaponLimb->x += -.1 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13755 						weaponLimb->y += -.1 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13756 						weaponLimb->z += -1.25;
13757 						weaponLimb->focalx += 0;
13758 						weaponLimb->focaly += 0;
13759 						weaponLimb->focalz += 1.25;
13760 						break;
13761 					default:
13762 						break;
13763 				}
13764 			}
13765 			/*weaponLimb->x += limbs[HUMAN][12][0] * cos(weaponArmLimb->yaw + PI / 2) + limbs[HUMAN][12][1] * cos(weaponArmLimb->yaw);
13766 			weaponLimb->y += limbs[HUMAN][12][0] * sin(weaponArmLimb->yaw + PI / 2) + limbs[HUMAN][12][1] * sin(weaponArmLimb->yaw);
13767 			weaponLimb->z += limbs[HUMAN][12][2];
13768 			weaponLimb->focalx += limbs[HUMAN][11][0];
13769 			weaponLimb->focaly += limbs[HUMAN][11][1];
13770 			weaponLimb->focalz += limbs[HUMAN][11][2];*/
13771 		}
13772 		else
13773 		{
13774 			switch ( monsterType )
13775 			{
13776 				case SUCCUBUS:
13777 				case INCUBUS:
13778 				case HUMAN:
13779 				case VAMPIRE:
13780 				case AUTOMATON:
13781 				case INSECTOID:
13782 				case GOBLIN:
13783 					weaponLimb->focaly -= 0.05; // minor z-fighting fix.
13784 					break;
13785 				default:
13786 					break;
13787 			}
13788 		}
13789 	}
13790 	else
13791 	{
13792 		if ( monsterType == INCUBUS || monsterType == SUCCUBUS )
13793 		{
13794 			weaponLimb->focalx = limbs[monsterType][6][0] + 2; // 3.5
13795 			weaponLimb->focaly = limbs[monsterType][6][1]; // 0
13796 			weaponLimb->focalz = limbs[monsterType][6][2] - 3.5; // -2.5
13797 			if ( isPlayer && isPotion )
13798 			{
13799 				weaponLimb->focalz += 4.5;
13800 			}
13801 		}
13802 		else if ( isPlayer && monsterType == HUMAN )
13803 		{
13804 			weaponLimb->focalx = limbs[monsterType][6][0] + 1.5;
13805 			weaponLimb->focaly = limbs[monsterType][6][1];
13806 			weaponLimb->focalz = limbs[monsterType][6][2] - 2;
13807 			if ( isPlayer && isPotion )
13808 			{
13809 				weaponLimb->focalz += 3;
13810 			}
13811 		}
13812 		else
13813 		{
13814 			weaponLimb->focalx = limbs[monsterType][6][0] + 1; // 3.5
13815 			weaponLimb->focaly = limbs[monsterType][6][1]; // 0
13816 			weaponLimb->focalz = limbs[monsterType][6][2] - 2; // -2.5
13817 			if ( isPlayer && isPotion )
13818 			{
13819 				weaponLimb->focalz += 3;
13820 			}
13821 		}
13822 
13823 		if ( weaponLimb->sprite == items[BOOMERANG].index )
13824 		{
13825 			weaponLimb->focalx += 2;
13826 			weaponLimb->focaly += 0.25;
13827 			weaponLimb->focalz += 2;
13828 			weaponLimb->x += -1.2 * cos(weaponArmLimb->yaw + PI / 2) + -.1 * cos(weaponArmLimb->yaw);
13829 			weaponLimb->y += -1.2 * sin(weaponArmLimb->yaw + PI / 2) + -.1 * sin(weaponArmLimb->yaw);
13830 			weaponLimb->z += 0.25;
13831 			switch ( monsterType )
13832 			{
13833 				case SKELETON:
13834 				case AUTOMATON:
13835 				case GOATMAN:
13836 				case INSECTOID:
13837 				case GOBLIN:
13838 					weaponLimb->x += 0.5 * cos(weaponArmLimb->yaw + PI / 2);
13839 					weaponLimb->y += 0.5 * sin(weaponArmLimb->yaw + PI / 2);
13840 					break;
13841 				case INCUBUS:
13842 				case SUCCUBUS:
13843 					weaponLimb->x += 1.75 * cos(weaponArmLimb->yaw + PI / 2) + 0.25 * cos(weaponArmLimb->yaw);
13844 					weaponLimb->y += 1.75 * sin(weaponArmLimb->yaw + PI / 2) + 0.25 * sin(weaponArmLimb->yaw);
13845 					weaponLimb->z += 0;
13846 
13847 					weaponLimb->focalx += -0.75;
13848 					weaponLimb->focaly += 1;
13849 					weaponLimb->focalz += 1.5;
13850 					break;
13851 				default:
13852 					break;
13853 			}
13854 		}
13855 		else if ( weaponLimb->sprite == items[TOOL_WHIP].index + 1 )
13856 		{
13857 			weaponLimb->focalx += 5.5;
13858 			weaponLimb->focalz += 3.5;
13859 		}
13860 		else if ( weaponLimb->sprite == items[TOOL_WHIP].index )
13861 		{
13862 			weaponLimb->focalx += 1.5;
13863 			weaponLimb->focalz += 2.5;
13864 		}
13865 		else if ( weaponLimb->sprite == items[TOOL_GYROBOT].index )
13866 		{
13867 			weaponLimb->focalz += 1;
13868 		}
13869 		else if ( weaponLimb->sprite >= items[TOOL_BOMB].index && weaponLimb->sprite <= items[TOOL_TELEPORT_BOMB].index )
13870 		{
13871 			weaponLimb->focalz += 2;
13872 		}
13873 
13874 		weaponLimb->yaw -= sin(weaponArmLimb->roll) * PI / 2;
13875 		weaponLimb->pitch += cos(weaponArmLimb->roll) * PI / 2;
13876 	}
13877 
13878 	return;
13879 }
13880 
lookAtEntity(Entity & target)13881 void Entity::lookAtEntity(Entity& target)
13882 {
13883 	double tangent = atan2(target.y - y, target.x - x);
13884 	monsterLookTime = 1;
13885 	monsterMoveTime = rand() % 10 + 1;
13886 	monsterLookDir = tangent;
13887 }
13888 
getActiveMagicEffect(int spellID)13889 spell_t* Entity::getActiveMagicEffect(int spellID)
13890 {
13891 	Stat* myStats = getStats();
13892 	if ( !myStats )
13893 	{
13894 		return nullptr;
13895 	}
13896 
13897 	spell_t* spell = nullptr;
13898 	spell_t* searchSpell = nullptr;
13899 
13900 	for ( node_t *node = myStats->magic_effects.first; node; node = node->next )
13901 	{
13902 		searchSpell = (node->element ? static_cast<spell_t*>(node->element) : nullptr);
13903 		if ( searchSpell && searchSpell->ID == spellID )
13904 		{
13905 			spell = searchSpell;
13906 			break;
13907 		}
13908 	}
13909 
13910 	return spell;
13911 }
13912 
actAmbientParticleEffectIdle(Entity * my)13913 void actAmbientParticleEffectIdle(Entity* my)
13914 {
13915 	if ( !my )
13916 	{
13917 		return;
13918 	}
13919 
13920 	if ( my->particleDuration < 0 )
13921 	{
13922 		list_RemoveNode(my->mynode);
13923 		return;
13924 	}
13925 	else
13926 	{
13927 		if ( my->particleShrink == 1 )
13928 		{
13929 			// shrink the particle.
13930 			my->scalex *= 0.95;
13931 			my->scaley *= 0.95;
13932 			my->scalez *= 0.95;
13933 		}
13934 		--my->particleDuration;
13935 		my->z += my->vel_z;
13936 		my->yaw += 0.1;
13937 		if ( my->yaw > 2 * PI )
13938 		{
13939 			my->yaw = 0;
13940 		}
13941 	}
13942 
13943 	return;
13944 }
13945 
spawnAmbientParticles(int chance,int particleSprite,int duration,double particleScale,bool shrink)13946 void Entity::spawnAmbientParticles(int chance, int particleSprite, int duration, double particleScale, bool shrink)
13947 {
13948 	if ( rand() % chance == 0 )
13949 	{
13950 		Entity* spawnParticle = newEntity(particleSprite, 1, map.entities, nullptr); //Particle entity.
13951 		spawnParticle->sizex = 1;
13952 		spawnParticle->sizey = 1;
13953 		spawnParticle->x = x + (-2 + rand() % 5);
13954 		spawnParticle->y = y + (-2 + rand() % 5);
13955 		spawnParticle->z = 7.5;
13956 		spawnParticle->scalex *= particleScale;
13957 		spawnParticle->scaley *= particleScale;
13958 		spawnParticle->scalez *= particleScale;
13959 		spawnParticle->vel_z = -1;
13960 		spawnParticle->particleDuration = duration;
13961 		if ( shrink )
13962 		{
13963 			spawnParticle->particleShrink = 1;
13964 		}
13965 		else
13966 		{
13967 			spawnParticle->particleShrink = 0;
13968 		}
13969 		spawnParticle->behavior = &actAmbientParticleEffectIdle;
13970 		spawnParticle->flags[PASSABLE] = true;
13971 		spawnParticle->setUID(-3);
13972 	}
13973 }
13974 
handleEffectsClient()13975 void Entity::handleEffectsClient()
13976 {
13977 	Stat* myStats = getStats();
13978 
13979 	if ( !myStats )
13980 	{
13981 		return;
13982 	}
13983 
13984 	if ( myStats->EFFECTS[EFF_MAGICREFLECT] )
13985 	{
13986 		spawnAmbientParticles(80, 579, 10 + rand() % 40, 1.0, false);
13987 	}
13988 
13989 	if ( myStats->EFFECTS[EFF_FEAR] )
13990 	{
13991 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
13992 		{
13993 			spawnAmbientParticles(1, 864, 20 + rand() % 10, 0.5, true);
13994 		}
13995 	}
13996 
13997 	if ( myStats->EFFECTS[EFF_TROLLS_BLOOD] )
13998 	{
13999 		spawnAmbientParticles(80, 169, 20 + rand() % 10, 0.5, true);
14000 	}
14001 
14002 	if ( myStats->EFFECTS[EFF_VAMPIRICAURA] )
14003 	{
14004 		spawnAmbientParticles(30, 600, 20 + rand() % 30, 0.5, true);
14005 	}
14006 
14007 	if ( myStats->EFFECTS[EFF_PACIFY] )
14008 	{
14009 		spawnAmbientParticles(30, 685, 20 + rand() % 30, 0.5, true);
14010 	}
14011 
14012 	if ( myStats->EFFECTS[EFF_SHADOW_TAGGED] )
14013 	{
14014 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
14015 		{
14016 			spawnAmbientParticles(1, 871, 20 + rand() % 10, 0.5, true);
14017 		}
14018 	}
14019 
14020 	if ( myStats->EFFECTS[EFF_POLYMORPH] )
14021 	{
14022 		if ( ticks % 25 == 0 || ticks % 40 == 0 )
14023 		{
14024 			spawnAmbientParticles(1, 593, 20 + rand() % 10, 0.5, true);
14025 		}
14026 	}
14027 
14028 	if ( myStats->EFFECTS[EFF_INVISIBLE] && getMonsterTypeFromSprite() == SHADOW )
14029 	{
14030 		spawnAmbientParticles(20, 175, 20 + rand() % 30, 0.5, true);
14031 	}
14032 }
14033 
serverUpdateEffectsForEntity(bool guarantee)14034 void Entity::serverUpdateEffectsForEntity(bool guarantee)
14035 {
14036 	if ( multiplayer != SERVER )
14037 	{
14038 		return;
14039 	}
14040 
14041 	Stat* myStats = getStats();
14042 
14043 	if ( !myStats )
14044 	{
14045 		return;
14046 	}
14047 
14048 	for ( int player = 1; player < MAXPLAYERS; ++player )
14049 	{
14050 		if ( client_disconnected[player] )
14051 		{
14052 			continue;
14053 		}
14054 
14055 		/*
14056 		* Packet breakdown:
14057 		* [0][1][2][3]: "EFFE"
14058 		* [4][5][6][7]: Entity's UID.
14059 		* [8][9][10][11]: Entity's effects.
14060 		*/
14061 
14062 		strcpy((char*)net_packet->data, "EFFE");
14063 		SDLNet_Write32(static_cast<Uint32>(getUID()), &net_packet->data[4]);
14064 		net_packet->data[8] = 0;
14065 		net_packet->data[9] = 0;
14066 		net_packet->data[10] = 0;
14067 		net_packet->data[11] = 0;
14068 		net_packet->data[12] = 0;
14069 		for ( int i = 0; i < NUMEFFECTS; ++i )
14070 		{
14071 			if ( myStats->EFFECTS[i] )
14072 			{
14073 				net_packet->data[8 + i / 8] |= power(2, i - (i / 8) * 8);
14074 			}
14075 		}
14076 		net_packet->address.host = net_clients[player - 1].host;
14077 		net_packet->address.port = net_clients[player - 1].port;
14078 		net_packet->len = 13;
14079 		if ( guarantee )
14080 		{
14081 			sendPacketSafe(net_sock, -1, net_packet, player - 1);
14082 		}
14083 		else
14084 		{
14085 			sendPacket(net_sock, -1, net_packet, player - 1);
14086 		}
14087 		clientsHaveItsStats = true;
14088 	}
14089 }
14090 
setEffect(int effect,bool value,int duration,bool updateClients,bool guarantee)14091 bool Entity::setEffect(int effect, bool value, int duration, bool updateClients, bool guarantee)
14092 {
14093 	Stat* myStats = getStats();
14094 
14095 	if ( !myStats )
14096 	{
14097 		return false;
14098 	}
14099 
14100 	if ( value == true )
14101 	{
14102 		switch ( effect )
14103 		{
14104 			case EFF_ASLEEP:
14105 			case EFF_PARALYZED:
14106 			case EFF_PACIFY:
14107 			case EFF_KNOCKBACK:
14108 			case EFF_BLIND:
14109 			case EFF_WEBBED:
14110 				if ( (myStats->type >= LICH && myStats->type < KOBOLD)
14111 					|| myStats->type == COCKATRICE || myStats->type == LICH_FIRE || myStats->type == LICH_ICE )
14112 				{
14113 					if ( !(effect == EFF_PACIFY && myStats->type == SHOPKEEPER) &&
14114 						!(effect == EFF_KNOCKBACK && myStats->type == COCKATRICE) &&
14115 						!(effect == EFF_WEBBED && myStats->type == COCKATRICE) &&
14116 						!(effect == EFF_BLIND && myStats->type == COCKATRICE) &&
14117 						!(effect == EFF_BLIND && myStats->type == SHOPKEEPER) )
14118 					{
14119 						return false;
14120 					}
14121 				}
14122 				break;
14123 			case EFF_DISORIENTED:
14124 				if ( myStats->type == LICH || myStats->type == DEVIL
14125 					|| myStats->type == LICH_FIRE || myStats->type == LICH_ICE
14126 					|| myStats->type == SHADOW || myStats->type == SHOPKEEPER )
14127 				{
14128 					return false;
14129 				}
14130 				break;
14131 			case EFF_FEAR:
14132 				if ( myStats->type == LICH || myStats->type == DEVIL
14133 					|| myStats->type == LICH_FIRE || myStats->type == LICH_ICE
14134 					|| myStats->type == SHADOW )
14135 				{
14136 					return false;
14137 				}
14138 				break;
14139 			case EFF_POLYMORPH:
14140 				//if ( myStats->EFFECTS[EFF_POLYMORPH] || effectPolymorph != 0 )
14141 				//{
14142 				//	return false;
14143 				//}
14144 				break;
14145 			case EFF_BLEEDING:
14146 				if ( gibtype[(int)myStats->type] == 0 )
14147 				{
14148 					return false;
14149 				}
14150 				break;
14151 			default:
14152 				break;
14153 		}
14154 	}
14155 	myStats->EFFECTS[effect] = value;
14156 	myStats->EFFECTS_TIMERS[effect] = duration;
14157 
14158 	int player = -1;
14159 	for ( int i = 0; i < MAXPLAYERS; ++i )
14160 	{
14161 		if ( players[i] && players[i]->entity == this )
14162 		{
14163 			player = i;
14164 			break;
14165 		}
14166 	}
14167 
14168 	if ( multiplayer == SERVER && player > 0 )
14169 	{
14170 		serverUpdateEffects(player);
14171 	}
14172 
14173 	if ( updateClients )
14174 	{
14175 		serverUpdateEffectsForEntity(guarantee);
14176 	}
14177 	return true;
14178 }
14179 
giveClientStats()14180 void Entity::giveClientStats()
14181 {
14182 	if ( !clientStats )
14183 	{
14184 		clientStats = new Stat(0);
14185 	}
14186 }
14187 
monsterAcquireAttackTarget(const Entity & target,Sint32 state,bool monsterWasHit)14188 void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool monsterWasHit)
14189 {
14190 	Stat* myStats = getStats();
14191 	if ( !myStats )
14192 	{
14193 		return;
14194 	}
14195 
14196 	bool hadOldTarget = (uidToEntity(monsterTarget) != nullptr);
14197 
14198 	if ( target.getRace() == GYROBOT )
14199 	{
14200 		return;
14201 	}
14202 	else if ( myStats->type == GYROBOT )
14203 	{
14204 		if ( state == MONSTER_STATE_ATTACK )
14205 		{
14206 			return;
14207 		}
14208 		else
14209 		{
14210 			if ( target.behavior == &actMonster )
14211 			{
14212 				return;
14213 			}
14214 		}
14215 	}
14216 	else if ( monsterIsImmobileTurret(this, myStats) )
14217 	{
14218 		if ( monsterAllyIndex >= 0 && target.behavior == &actPlayer )
14219 		{
14220 			return;
14221 		}
14222 		if ( monsterIsImmobileTurret(nullptr, target.getStats()) && state == MONSTER_STATE_PATH )
14223 		{
14224 			return;
14225 		}
14226 		if ( monsterState == MONSTER_STATE_WAIT )
14227 		{
14228 			if ( myStats->LVL >= 10 && monsterHitTime < HITRATE )
14229 			{
14230 				monsterHitTime = HITRATE;
14231 			}
14232 		}
14233 	}
14234 
14235 	if ( &target != uidToEntity(monsterTarget) && !monsterReleaseAttackTarget() )
14236 	{
14237 		//messagePlayer(clientnum, "Entity failed to acquire target!");
14238 		return;
14239 	}
14240 
14241 	/*if ( &target != uidToEntity(monsterTarget) )
14242 	{
14243 		messagePlayer(clientnum, "Entity acquired new target!");
14244 	}*/
14245 
14246 	if ( myStats->type == LICH_ICE ) // make sure automatons don't attack the leader and vice versa...
14247 	{
14248 		Stat* targetStats = target.getStats();
14249 		if ( targetStats )
14250 		{
14251 			if ( targetStats->type == AUTOMATON && !strncmp(targetStats->name, "corrupted automaton", 19) )
14252 			{
14253 				return;
14254 			}
14255 		}
14256 	}
14257 	else if ( myStats->type == AUTOMATON && !strncmp(myStats->name, "corrupted automaton", 19) )
14258 	{
14259 		if ( target.getRace() == LICH_ICE )
14260 		{
14261 			return;
14262 		}
14263 	}
14264 	else if ( myStats->type == INCUBUS && !strncmp(myStats->name, "inner demon", strlen("inner demon")) )
14265 	{
14266 		if ( monsterState == MONSTER_STATE_WAIT )
14267 		{
14268 			return;
14269 		}
14270 	}
14271 
14272 	if ( target.getRace() == INCUBUS )
14273 	{
14274 		Stat* targetStats = target.getStats();
14275 		if ( targetStats && !strncmp(targetStats->name, "inner demon", strlen("inner demon")) )
14276 		{
14277 			Entity* illusionTauntingThisEntity = uidToEntity(static_cast<Uint32>(target.monsterIllusionTauntingThisUid));
14278 			if ( illusionTauntingThisEntity != this )
14279 			{
14280 				return;
14281 			}
14282 		}
14283 	}
14284 
14285 	if ( myStats->EFFECTS[EFF_DISORIENTED] )
14286 	{
14287 		return;
14288 	}
14289 
14290 	if ( monsterState != MONSTER_STATE_ATTACK && !hadOldTarget )
14291 	{
14292 		if ( myStats->type != LICH_FIRE
14293 			&& myStats->type != LICH_ICE
14294 			&& (myStats->type < LICH || myStats->type > DEVIL)
14295 			)
14296 		{
14297 			// check to see if holding ranged weapon, set hittime to be ready to attack.
14298 			// set melee hittime close to max in hardcore mode...
14299 			if ( ((svFlags & SV_FLAG_HARDCORE) || hasRangedWeapon()) && monsterSpecialTimer <= 0 )
14300 			{
14301 				if ( hasRangedWeapon() )
14302 				{
14303 					if ( myStats->weapon && itemCategory(myStats->weapon) == MAGICSTAFF )
14304 					{
14305 						monsterHitTime = HITRATE - 6; // 120 ms reaction time
14306 					}
14307 					else
14308 					{
14309 						monsterHitTime = 2 * HITRATE - 2;
14310 					}
14311 				}
14312 				else if ( svFlags & SV_FLAG_HARDCORE )
14313 				{
14314 					if ( monsterWasHit ) // retaliating to an attack
14315 					{
14316 						monsterHitTime = HITRATE - 12; // 240 ms reaction time
14317 					}
14318 					else // monster find enemy in line of sight
14319 					{
14320 						monsterHitTime = HITRATE - 30; // 600 ms reaction time
14321 					}
14322 				}
14323 			}
14324 		}
14325 	}
14326 
14327 	if ( (myStats->type == LICH_FIRE || myStats->type == LICH_ICE)
14328 		&& (monsterState == MONSTER_STATE_LICHFIRE_TELEPORT_STATIONARY
14329 			|| monsterState == MONSTER_STATE_LICHICE_TELEPORT_STATIONARY
14330 			|| monsterState == MONSTER_STATE_LICH_CASTSPELLS
14331 			|| monsterState == MONSTER_STATE_LICH_TELEPORT_ROAMING
14332 			|| monsterState == MONSTER_STATE_LICHFIRE_DIE
14333 			|| monsterState == MONSTER_STATE_LICHICE_DIE) )
14334 	{
14335 
14336 	}
14337 	else
14338 	{
14339 		monsterState = state;
14340 	}
14341 
14342 	if ( myStats->type == SHOPKEEPER && monsterTarget != target.getUID() && target.behavior == &actPlayer )
14343 	{
14344 		Stat* targetStats = target.getStats();
14345 		if ( targetStats )
14346 		{
14347 			char namesays[32];
14348 			if ( !strcmp(myStats->name, "") )
14349 			{
14350 				snprintf(namesays, 31, language[513], SHOPKEEPER);
14351 			}
14352 			else
14353 			{
14354 				snprintf(namesays, 31, language[1302], myStats->name);
14355 			}
14356 			if ( targetStats->type != HUMAN )
14357 			{
14358 				if ( targetStats->type < KOBOLD ) //Original monster count
14359 				{
14360 					messagePlayer(target.skill[2], language[3243],
14361 						namesays, language[90 + targetStats->type]);
14362 				}
14363 				else if ( targetStats->type >= KOBOLD ) //New monsters
14364 				{
14365 					messagePlayer(target.skill[2], language[3243], namesays,
14366 						language[2000 + (targetStats->type - KOBOLD)]);
14367 				}
14368 				steamAchievementClient(target.skill[2], "BARONY_ACH_RIGHT_TO_REFUSE");
14369 			}
14370 			else
14371 			{
14372 				messagePlayer(target.skill[2], language[516 + rand() % 4], namesays);
14373 			}
14374 		}
14375 	}
14376 
14377 	monsterTarget = target.getUID();
14378 	monsterTargetX = target.x;
14379 	monsterTargetY = target.y;
14380 
14381 	if ( target.getStats() != nullptr )
14382 	{
14383 		if ( monsterState != MONSTER_STATE_ATTACK && state == MONSTER_STATE_PATH )
14384 		{
14385 			if ( myStats->type != LICH_FIRE && myStats->type != LICH_ICE && myStats->type != LICH && myStats->type != DEVIL )
14386 			{
14387 				real_t distance = pow(x - target.x, 2) + pow(y - target.y, 2);
14388 				if ( distance < STRIKERANGE * STRIKERANGE )
14389 				{
14390 					monsterState = MONSTER_STATE_ATTACK;
14391 				}
14392 			}
14393 		}
14394 	}
14395 
14396 
14397 	if ( monsterAllyIndex > 0 && monsterAllyIndex < MAXPLAYERS )
14398 	{
14399 		serverUpdateEntitySkill(this, 1); // update monsterTarget for player leaders.
14400 	}
14401 
14402 	if ( !hadOldTarget && myStats->type == SHADOW )
14403 	{
14404 		//messagePlayer(clientnum, "TODO: Shadow got new target.");
14405 		//Activate special ability initially for Shadow.
14406 		monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_TELEMIMICINVISI_ATTACK;
14407 		//pose = MONSTER_POSE_MAGIC_WINDUP1;
14408 		monsterShadowInitialMimic = 1; //true!
14409 		attack(MONSTER_POSE_MAGIC_WINDUP3, 0, nullptr);
14410 	}
14411 }
14412 
monsterReleaseAttackTarget(bool force)14413 bool Entity::monsterReleaseAttackTarget(bool force)
14414 {
14415 	if ( !monsterTarget )
14416 	{
14417 		return true;
14418 	}
14419 
14420 	Stat* myStats = getStats();
14421 	if ( !myStats )
14422 	{
14423 		return false;
14424 	}
14425 
14426 	if ( !force && myStats->type == SHADOW && monsterTarget && uidToEntity(monsterTarget) )
14427 	{
14428 		//messagePlayer(clientnum, "Shadow cannot lose target until it's dead!");
14429 		return false; //Shadow cannot lose its target.
14430 	}
14431 
14432 	/*if ( myStats->type == SHADOW )
14433 	{
14434 		messagePlayer(0, "DEBUG: Shadow: Entity::monsterReleaseAttackTarget().");
14435 	}*/
14436 
14437 	monsterTarget = 0;
14438 
14439 	if ( monsterAllyIndex > 0 && monsterAllyIndex < MAXPLAYERS )
14440 	{
14441 		serverUpdateEntitySkill(this, 1); // update monsterTarget for player leaders.
14442 	}
14443 
14444 	return true;
14445 }
14446 
checkGroundForItems()14447 void Entity::checkGroundForItems()
14448 {
14449 	Stat* myStats = getStats();
14450 	if ( myStats == nullptr )
14451 	{
14452 		return;
14453 	}
14454 	if ( monsterAllyPickupItems == ALLY_PICKUP_NONE && monsterAllyIndex >= 0 )
14455 	{
14456 		return; // set to ignore ground items.
14457 	}
14458 
14459 	// Calls the function for a monster to pick up an item, if it's a monster that picks up items, only if they are not Asleep
14460 	if ( myStats->EFFECTS[EFF_ASLEEP] == false )
14461 	{
14462 		switch ( myStats->type )
14463 		{
14464 			case GOBLIN:
14465 			case HUMAN:
14466 				if ( !strcmp(myStats->name, "") )
14467 				{
14468 					//checkBetterEquipment(myStats);
14469 					monsterAddNearbyItemToInventory(myStats, 16, 9);
14470 				}
14471 				break;
14472 			case GOATMAN:
14473 				//Goatman boss picks up items too.
14474 				monsterAddNearbyItemToInventory(myStats, 16, 9); //Replaces checkBetterEquipment(), because more better. Adds items to inventory, and swaps out current equipped with better stuff on the ground.
14475 																 //checkBetterEquipment(myStats);
14476 				break;
14477 			case AUTOMATON:
14478 				monsterAddNearbyItemToInventory(myStats, 16, 5);
14479 				break;
14480 			case SHOPKEEPER:
14481 				if ( myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 )
14482 				{
14483 					monsterAddNearbyItemToInventory(myStats, 16, 99);
14484 				}
14485 				break;
14486 			default:
14487 				return;
14488 		}
14489 	}
14490 }
14491 
canWieldItem(const Item & item) const14492 bool Entity::canWieldItem(const Item& item) const
14493 {
14494 	Stat* myStats = getStats();
14495 	if ( !myStats )
14496 	{
14497 		return false;
14498 	}
14499 
14500 	switch ( myStats->type )
14501 	{
14502 		case GOBLIN:
14503 			return goblinCanWieldItem(item);
14504 		case HUMAN:
14505 			return humanCanWieldItem(item);
14506 		case GOATMAN:
14507 			return goatmanCanWieldItem(item);
14508 		case AUTOMATON:
14509 			return automatonCanWieldItem(item);
14510 		case SHADOW:
14511 			return shadowCanWieldItem(item);
14512 		default:
14513 			return false;
14514 	}
14515 }
14516 
monsterAddNearbyItemToInventory(Stat * myStats,int rangeToFind,int maxInventoryItems,Entity * forcePickupItem)14517 bool Entity::monsterAddNearbyItemToInventory(Stat* myStats, int rangeToFind, int maxInventoryItems, Entity* forcePickupItem)
14518 {
14519 	//TODO: Any networking/multiplayer needs?
14520 	if ( !myStats )
14521 	{
14522 		return false; //Can't continue without these.
14523 	}
14524 
14525 	if ( list_Size(&myStats->inventory) >= maxInventoryItems + 1 )
14526 	{
14527 		return false;
14528 	}
14529 
14530 	list_t* itemsList = nullptr;
14531 	//X and Y in terms of tiles.
14532 	if ( forcePickupItem != nullptr && forcePickupItem->behavior == &actItem )
14533 	{
14534 		if ( !FollowerMenu.allowedInteractItems(myStats->type) )
14535 		{
14536 			return false;
14537 		}
14538 
14539 		//If this is the first item found, the list needs to be created.
14540 		if ( !(itemsList) )
14541 		{
14542 			itemsList = (list_t*)malloc(sizeof(list_t));
14543 			(itemsList)->first = NULL;
14544 			(itemsList)->last = NULL;
14545 		}
14546 
14547 		//Add the current entity to it.
14548 		node_t* node2 = list_AddNodeLast(itemsList);
14549 		node2->element = forcePickupItem;
14550 		node2->deconstructor = &emptyDeconstructor;
14551 	}
14552 	else
14553 	{
14554 		int tx = x / 16;
14555 		int ty = y / 16;
14556 		getItemsOnTile(tx, ty, &itemsList); //Check the tile the monster is on for items.
14557 		getItemsOnTile(tx - 1, ty, &itemsList); //Check tile to the left.
14558 		getItemsOnTile(tx + 1, ty, &itemsList); //Check tile to the right.
14559 		getItemsOnTile(tx, ty - 1, &itemsList); //Check tile up.
14560 		getItemsOnTile(tx, ty + 1, &itemsList); //Check tile down.
14561 		getItemsOnTile(tx - 1, ty - 1, &itemsList); //Check tile diagonal up left.
14562 		getItemsOnTile(tx + 1, ty - 1, &itemsList); //Check tile diagonal up right.
14563 		getItemsOnTile(tx - 1, ty + 1, &itemsList); //Check tile diagonal down left.
14564 		getItemsOnTile(tx + 1, ty + 1, &itemsList); //Check tile diagonal down right.
14565 	}
14566 	node_t* node = nullptr;
14567 	bool pickedUpItemReturnValue = false;
14568 
14569 	if ( itemsList )
14570 	{
14571 		/*
14572 		* Rundown of the function:
14573 		* Loop through all items.
14574 		* Add item to inventory.
14575 		*/
14576 
14577 		for ( node = itemsList->first; node != nullptr; node = node->next )
14578 		{
14579 			//Turn the entity into an item.
14580 			if ( node->element )
14581 			{
14582 				if ( list_Size(&myStats->inventory) >= maxInventoryItems + 1 )
14583 				{
14584 					break;
14585 				}
14586 
14587 				Entity* entity = (Entity*)node->element;
14588 				if ( entity->flags[INVISIBLE] )
14589 				{
14590 					continue; // ignore invisible items like Sokoban gloves or other scripted events.
14591 				}
14592 
14593 				Item* item = nullptr;
14594 				if ( entity != nullptr )
14595 				{
14596 					item = newItemFromEntity(entity);
14597 					if ( forcePickupItem )
14598 					{
14599 						item->forcedPickupByPlayer = true;
14600 					}
14601 				}
14602 				if ( !item )
14603 				{
14604 					continue;
14605 				}
14606 
14607 				double dist = sqrt(pow(this->x - entity->x, 2) + pow(this->y - entity->y, 2));
14608 				if ( std::floor(dist) > rangeToFind )
14609 				{
14610 					// item was too far away, continue.
14611 					if ( item != nullptr )
14612 					{
14613 						free(item);
14614 					}
14615 					continue;
14616 				}
14617 
14618 				if ( !entity->itemNotMoving && entity->parent && entity->parent != uid )
14619 				{
14620 					if ( itemCategory(item) == THROWN && entity->parent && entity->parent == uid )
14621 					{
14622 						//It's good. Can pick this one up, it's your THROWN now.
14623 					}
14624 					else
14625 					{
14626 						//Don't pick up non-THROWN items that are moving, or owned THROWN that are moving.
14627 						if ( item != nullptr )
14628 						{
14629 							free(item);
14630 						}
14631 						continue; //Item still in motion, don't pick it up.
14632 					}
14633 				}
14634 
14635 				Item** shouldWield = nullptr;
14636 				node_t* replaceInventoryItem = nullptr;
14637 				if ( !monsterWantsItem(*item, shouldWield, replaceInventoryItem) )
14638 				{
14639 					if ( item && item->interactNPCUid == getUID() && forcePickupItem )
14640 					{
14641 						// I don't want this.
14642 						handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_NOUSE);
14643 					}
14644 					if ( item != nullptr )
14645 					{
14646 						free(item);
14647 					}
14648 					continue;
14649 				}
14650 
14651 				int playerOwned = -1;
14652 				if ( entity->itemOriginalOwner != 0 )
14653 				{
14654 					for ( int c = 0; c < MAXPLAYERS; ++c )
14655 					{
14656 						if ( players[c] && players[c]->entity )
14657 						{
14658 							if ( players[c]->entity->getUID() == entity->itemOriginalOwner )
14659 							{
14660 								if ( players[c]->entity->checkFriend(this) )
14661 								{
14662 									// player owned.
14663 									playerOwned = c;
14664 								}
14665 								break;
14666 							}
14667 						}
14668 					}
14669 					if ( item->interactNPCUid == getUID() )
14670 					{
14671 						// item being interacted with, can interact with item.
14672 					}
14673 					else if ( (playerOwned >= 0
14674 						&& (entity->ticks < 5 * TICKS_PER_SECOND
14675 							|| (monsterAllyPickupItems != ALLY_PICKUP_ALL && monsterAllyIndex >= 0))
14676 							)
14677 						)
14678 					{
14679 						// player item too new on ground, or monster is set to not pickup player items.
14680 						continue;
14681 					}
14682 				}
14683 				if ( entity->itemDelayMonsterPickingUp > 0 && entity->ticks < entity->itemDelayMonsterPickingUp )
14684 				{
14685 					// dropped from a disarm skill, don't pick up item until timer is up.
14686 					continue;
14687 				}
14688 
14689 				if ( myStats->type == SHOPKEEPER && myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 )
14690 				{
14691 					// pickup the item always.
14692 					Entity* owner = uidToEntity(item->ownerUid);
14693 					if ( owner && owner->behavior == &actPlayer )
14694 					{
14695 						switch ( item->type )
14696 						{
14697 							case ARTIFACT_ORB_BLUE:
14698 								messagePlayer(owner->skill[2], language[3889], myStats->name);
14699 								break;
14700 							case ARTIFACT_ORB_RED:
14701 								messagePlayer(owner->skill[2], language[3890], myStats->name);
14702 								break;
14703 							case ARTIFACT_ORB_GREEN:
14704 								messagePlayer(owner->skill[2], language[3888], myStats->name);
14705 								break;
14706 							default:
14707 								break;
14708 						}
14709 					}
14710 					playSoundEntity(this, 35 + rand() % 3, 64);
14711 					addItemToMonsterInventory(item);
14712 					item = nullptr;
14713 					list_RemoveNode(entity->mynode);
14714 					pickedUpItemReturnValue = true;
14715 				}
14716 				else if ( myStats->type == SLIME )
14717 				{
14718 					if ( item->identified )
14719 					{
14720 						messagePlayer(monsterAllyIndex, language[3145], items[item->type].name_identified);
14721 					}
14722 					else
14723 					{
14724 						messagePlayer(monsterAllyIndex, language[3145], items[item->type].name_unidentified);
14725 					}
14726 					list_RemoveNode(entity->mynode); // slimes eat the item up.
14727 					pickedUpItemReturnValue = true;
14728 				}
14729 				else if ( shouldWield )
14730 				{
14731 					if ( (*shouldWield) && (*shouldWield)->beatitude < 0 && myStats->type != AUTOMATON )
14732 					{
14733 						if ( item && item->interactNPCUid == getUID() && forcePickupItem )
14734 						{
14735 							// held item is cursed!
14736 							handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_CURSED);
14737 						}
14738 						if ( item != nullptr )
14739 						{
14740 							free(item);
14741 						}
14742 						continue;
14743 					}
14744 
14745 					if ( myStats->type == AUTOMATON && list_Size(&myStats->inventory) < maxInventoryItems
14746 						&& !(item->interactNPCUid == getUID() && forcePickupItem) )
14747 					{
14748 						addItemToMonsterInventory(*shouldWield); // Automatons are hoarders, except if commanded.
14749 					}
14750 					else
14751 					{
14752 						Entity* dropped = dropItemMonster((*shouldWield), this, myStats); //And I threw it on the ground!
14753 						if ( dropped && item && item->interactNPCUid == getUID() )
14754 						{
14755 							if ( monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS )
14756 							{
14757 								if ( players[monsterAllyIndex] && players[monsterAllyIndex]->entity )
14758 								{
14759 									dropped->itemOriginalOwner = players[monsterAllyIndex]->entity->getUID();
14760 								}
14761 							}
14762 						}
14763 					}
14764 
14765 					if ( playerOwned >= 0 && checkFriend(players[playerOwned]->entity)
14766 						&& (item->type >= ARTIFACT_SWORD && item->type <= ARTIFACT_GLOVES) )
14767 					{
14768 						steamAchievementClient(playerOwned, "BARONY_ACH_EARN_THIS");
14769 					}
14770 
14771 					if ( item && item->interactNPCUid == getUID() && forcePickupItem )
14772 					{
14773 						if ( itemCategory(item) == AMULET || itemCategory(item) == RING )
14774 						{
14775 							playSoundEntity(this, 33 + rand() % 2, 64);
14776 						}
14777 						else if ( itemCategory(item) == WEAPON || itemCategory(item) == THROWN )
14778 						{
14779 							playSoundEntity(this, 40 + rand() % 4, 64);
14780 						}
14781 						else if ( itemCategory(item) == ARMOR )
14782 						{
14783 							playSoundEntity(this, 44 + rand() % 3, 64);
14784 						}
14785 						else if ( item->type == TOOL_TORCH || item->type == TOOL_LANTERN || item->type == TOOL_CRYSTALSHARD )
14786 						{
14787 							playSoundEntity(this, 134, 64);
14788 						}
14789 					}
14790 
14791 					(*shouldWield) = item;
14792 					item = nullptr;
14793 					list_RemoveNode(entity->mynode);
14794 					pickedUpItemReturnValue = true;
14795 				}
14796 				else if ( replaceInventoryItem )
14797 				{
14798 					//Drop that item out of the monster's inventory, and add this item to the monster's inventory.
14799 					Item* itemToDrop = static_cast<Item*>(replaceInventoryItem->element);
14800 					if ( itemToDrop )
14801 					{
14802 						if ( !(myStats->type == AUTOMATON && list_Size(&myStats->inventory) < maxInventoryItems) )
14803 						{
14804 							// Automatons are hoarders when swapping. Everything else will drop the weapon.
14805 							dropItemMonster(itemToDrop, this, myStats, itemToDrop->count);
14806 						}
14807 						//list_RemoveNode(replaceInventoryItem);
14808 					}
14809 
14810 					if ( list_Size(&myStats->inventory) < maxInventoryItems )
14811 					{
14812 						addItemToMonsterInventory(item);
14813 					}
14814 					item = nullptr;
14815 					list_RemoveNode(entity->mynode);
14816 					pickedUpItemReturnValue = true;
14817 				}
14818 				else if ( list_Size(&myStats->inventory) < maxInventoryItems )
14819 				{
14820 					bool addItem = true;
14821 					if ( myStats->type == GYROBOT && list_Size(&myStats->inventory) >= 1 )
14822 					{
14823 						node_t* inv = myStats->inventory.first;
14824 						Item* toStack = (Item*)inv->element;
14825 						if ( toStack )
14826 						{
14827 							if ( toStack->type >= TOOL_BOMB && toStack->type <= TOOL_TELEPORT_BOMB )
14828 							{
14829 								if ( !itemCompare(toStack, item, false) )
14830 								{
14831 									// stack the items.
14832 									toStack->count += item->count;
14833 									item = nullptr;
14834 									list_RemoveNode(entity->mynode);
14835 									pickedUpItemReturnValue = true;
14836 									addItem = false;
14837 								}
14838 							}
14839 						}
14840 					}
14841 
14842 					if ( addItem )
14843 					{
14844 						addItemToMonsterInventory(item);
14845 						item = nullptr;
14846 						list_RemoveNode(entity->mynode);
14847 						pickedUpItemReturnValue = true;
14848 					}
14849 				}
14850 
14851 				if ( item != nullptr )
14852 				{
14853 					free(item);
14854 				}
14855 			}
14856 		}
14857 		list_FreeAll(itemsList);
14858 		free(itemsList);
14859 	}
14860 
14861 	return pickedUpItemReturnValue;
14862 }
14863 
addItemToMonsterInventory(Item * item)14864 node_t* Entity::addItemToMonsterInventory(Item* item)
14865 {
14866 	//TODO: Sort into inventory...that is, if an item of this type already exists and they can stack, stack 'em instead of creating a new node.
14867 	if ( !item )
14868 	{
14869 		return nullptr;
14870 	}
14871 
14872 	Stat* myStats = getStats();
14873 	if ( !myStats )
14874 	{
14875 		return nullptr;
14876 	}
14877 
14878 	item->node = list_AddNodeLast(&myStats->inventory);
14879 	if ( !item->node )
14880 	{
14881 		return nullptr;
14882 	}
14883 	item->node->element = item;
14884 	item->node->deconstructor = &defaultDeconstructor;
14885 	item->node->size = sizeof(Item);
14886 
14887 	return item->node;
14888 }
14889 
shouldMonsterEquipThisWeapon(const Item & itemToEquip) const14890 bool Entity::shouldMonsterEquipThisWeapon(const Item& itemToEquip) const
14891 {
14892 	Stat* myStats = getStats();
14893 	if ( !myStats )
14894 	{
14895 		return false;
14896 	}
14897 
14898 	if ( myStats->weapon == nullptr )
14899 	{
14900 		return true; //Something is better than nothing.
14901 	}
14902 
14903 	if ( itemToEquip.interactNPCUid == getUID() )
14904 	{
14905 		return true;
14906 	}
14907 
14908 	if ( myStats->weapon->beatitude < 0 )
14909 	{
14910 		//If monster already holding an item, can't swap it out if it's cursed.
14911 		return false;
14912 	}
14913 
14914 	if ( myStats->weapon->forcedPickupByPlayer == true )
14915 	{
14916 		return false;
14917 	}
14918 
14919 	//Monster is already holding a weapon.
14920 	if ( monsterAllyIndex >= 0 )
14921 	{
14922 		if ( monsterAllyClass == ALLY_CLASS_RANGED && isRangedWeapon(itemToEquip)
14923 			&& !isRangedWeapon(*(myStats->weapon)) )
14924 		{
14925 			// drop what you're holding and pickup that new bow!
14926 			return true;
14927 		}
14928 		else if ( monsterAllyClass == ALLY_CLASS_MELEE && !isRangedWeapon(itemToEquip)
14929 			&& isRangedWeapon(*(myStats->weapon)) )
14930 		{
14931 			// drop what you're holding and pickup that new non-bow!
14932 			return true;
14933 		}
14934 	}
14935 
14936 	if ( !Item::isThisABetterWeapon(itemToEquip, myStats->weapon) )
14937 	{
14938 		return false; //Don't want junk.
14939 	}
14940 
14941 	if ( itemCategory(myStats->weapon) == MAGICSTAFF || itemCategory(myStats->weapon) == POTION || itemCategory(myStats->weapon) == THROWN || itemCategory(myStats->weapon) == GEM )
14942 	{
14943 		//If current hand item is not cursed, but it's a certain item, don't want to equip this new one.
14944 		return false;
14945 	}
14946 
14947 	if ( !isRangedWeapon(itemToEquip) && isRangedWeapon(*(myStats->weapon)) && rangedWeaponUseQuiverOnAttack(myStats) )
14948 	{
14949 		// have ranged weapon and quiver, don't pickup non-ranged weapon.
14950 		return false;
14951 	}
14952 
14953 	return true;
14954 }
14955 
monsterWantsItem(const Item & item,Item ** & shouldEquip,node_t * & replaceInventoryItem) const14956 bool Entity::monsterWantsItem(const Item& item, Item**& shouldEquip, node_t*& replaceInventoryItem) const
14957 {
14958 	Stat* myStats = getStats();
14959 	if ( !myStats )
14960 	{
14961 		return false;
14962 	}
14963 
14964 	if ( myStats->type == GYROBOT && item.interactNPCUid == getUID() )
14965 	{
14966 		return true;
14967 	}
14968 	if ( item.status == BROKEN )
14969 	{
14970 		return false; // no want broken.
14971 	}
14972 
14973 	switch ( myStats->type )
14974 	{
14975 		case GOBLIN:
14976 			if ( !goblinCanWieldItem(item) )
14977 			{
14978 				return false;
14979 			}
14980 			break;
14981 		case HUMAN:
14982 			if ( !humanCanWieldItem(item) )
14983 			{
14984 				return false;
14985 			}
14986 			break;
14987 		case GOATMAN:
14988 			if ( !goatmanCanWieldItem(item) )
14989 			{
14990 				return false;
14991 			}
14992 			break;
14993 		case AUTOMATON:
14994 			if ( !automatonCanWieldItem(item) )
14995 			{
14996 				if ( item.interactNPCUid == getUID() )
14997 				{
14998 					// item is being interacted with but we won't auto pick up on interact.
14999 					return false;
15000 				}
15001 				else
15002 				{
15003 					return true; //Can pick up all items automaton can't equip, because recycler.
15004 				}
15005 			}
15006 			break;
15007 		case GNOME:
15008 		case KOBOLD:
15009 		case INCUBUS:
15010 		case INSECTOID:
15011 		case SKELETON:
15012 		case VAMPIRE:
15013 			if ( !monsterAllyEquipmentInClass(item) )
15014 			{
15015 				return false;
15016 			}
15017 			break;
15018 		case SLIME:
15019 			return true; // noms on all items.
15020 			break;
15021 		case SHOPKEEPER:
15022 			if ( myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 )
15023 			{
15024 				if ( item.type == ARTIFACT_ORB_BLUE
15025 					|| item.type == ARTIFACT_ORB_GREEN
15026 					|| item.type == ARTIFACT_ORB_RED )
15027 				{
15028 					return true;
15029 				}
15030 			}
15031 			return false;
15032 			break;
15033 		default:
15034 			return false;
15035 			break;
15036 	}
15037 
15038 	switch ( itemCategory(&item) )
15039 	{
15040 		case WEAPON:
15041 			if ( !myStats->weapon )
15042 			{
15043 				shouldEquip = &myStats->weapon;
15044 			}
15045 
15046 			if ( myStats->weapon && itemCategory(myStats->weapon) == WEAPON && shouldMonsterEquipThisWeapon(item) )
15047 			{
15048 				shouldEquip = &myStats->weapon;
15049 				return true;
15050 			}
15051 			else
15052 			{
15053 				if ( myStats->weapon && itemCategory(myStats->weapon) == WEAPON )
15054 				{
15055 					//Weapon ain't better than weapon already holding. Don't want it.
15056 					if ( myStats->type == AUTOMATON ) // Automatons are hoarders.
15057 					{
15058 						return true;
15059 					}
15060 					return false;
15061 				}
15062 
15063 				if ( monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS )
15064 				{
15065 					if ( myStats->weapon && item.interactNPCUid == getUID() )
15066 					{
15067 						shouldEquip = &myStats->weapon;
15068 						return true;
15069 					}
15070 					if ( myStats->weapon && myStats->weapon->forcedPickupByPlayer )
15071 					{
15072 						return false;
15073 					}
15074 				}
15075 
15076 				//Not holding a weapon. Make sure don't already have a weapon in the inventory. If doesn't have a weapon at all, then add it into the inventory since something is better than nothing.
15077 				node_t* weaponNode = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON);
15078 				if ( !weaponNode )
15079 				{
15080 					//If no weapons found in inventory, then yes, the goatman wants it, and it should be added to the inventory.
15081 					return true; //Want this item.
15082 				}
15083 
15084 				//Search inventory and replace weapon if this one is better.
15085 				if ( Item::isThisABetterWeapon(item, static_cast<Item*>(weaponNode->element)) )
15086 				{
15087 					replaceInventoryItem = weaponNode;
15088 					return true;
15089 				}
15090 				return false; //Don't want your junk.
15091 			}
15092 		case ARMOR:
15093 			if ( myStats->type == AUTOMATON ) // Automatons are hoarders.
15094 			{
15095 				shouldEquip = shouldMonsterEquipThisArmor(item);
15096 				return true;
15097 			}
15098 
15099 			return (shouldEquip = shouldMonsterEquipThisArmor(item));
15100 		case THROWN:
15101 			if ( myStats->weapon == nullptr )
15102 			{
15103 				shouldEquip = &myStats->weapon;
15104 				return true;
15105 			}
15106 			else
15107 			{
15108 				return true; //Store in inventory.
15109 			}
15110 		case MAGICSTAFF:
15111 			if ( item.interactNPCUid == getUID() )
15112 			{
15113 				shouldEquip = &myStats->weapon;
15114 			}
15115 			return true;
15116 			break;
15117 		case RING:
15118 			if ( item.interactNPCUid == getUID() )
15119 			{
15120 				shouldEquip = &myStats->ring;
15121 			}
15122 			return true;
15123 			break;
15124 		case AMULET:
15125 			if ( item.interactNPCUid == getUID() )
15126 			{
15127 				shouldEquip = &myStats->amulet;
15128 			}
15129 			return true;
15130 			break;
15131 		case TOOL:
15132 			if ( itemTypeIsQuiver(item.type) )
15133 			{
15134 				return (shouldEquip = shouldMonsterEquipThisArmor(item));
15135 			}
15136 			if ( item.interactNPCUid == getUID() )
15137 			{
15138 				if ( item.type == TOOL_TORCH || item.type == TOOL_LANTERN || item.type == TOOL_CRYSTALSHARD )
15139 				{
15140 					shouldEquip = &myStats->shield;
15141 					return true;
15142 				}
15143 			}
15144 			break;
15145 		default:
15146 			return true; //Already checked if monster likes this specific item in the racial calls.
15147 	}
15148 
15149 	return false;
15150 }
15151 
shouldMonsterEquipThisArmor(const Item & item) const15152 Item** Entity::shouldMonsterEquipThisArmor(const Item& item) const
15153 {
15154 	Stat* myStats = getStats();
15155 	if ( !myStats )
15156 	{
15157 		return nullptr;
15158 	}
15159 
15160 	switch ( checkEquipType(&item) )
15161 	{
15162 		case TYPE_HAT:
15163 			if ( item.interactNPCUid == getUID() && myStats->helmet )
15164 			{
15165 				return &myStats->helmet;
15166 			}
15167 			if ( myStats->helmet && (myStats->helmet->beatitude < 0 || myStats->helmet->forcedPickupByPlayer == true) )
15168 			{
15169 				return nullptr; //No can has hats : (
15170 			}
15171 
15172 			return Item::isThisABetterArmor(item, myStats->helmet) ? &myStats->helmet : nullptr;
15173 		case TYPE_HELM:
15174 			if ( item.interactNPCUid == getUID() && myStats->helmet )
15175 			{
15176 				return &myStats->helmet;
15177 			}
15178 
15179 			if ( myStats->helmet && (myStats->helmet->beatitude < 0 || myStats->helmet->forcedPickupByPlayer == true) )
15180 			{
15181 				return nullptr; //Can't swap out armor piece if current one is cursed!
15182 			}
15183 
15184 			if ( myStats->type == GOBLIN && myStats->helmet && checkEquipType(myStats->helmet) == TYPE_HAT )
15185 			{
15186 				return nullptr; //Goblins love hats.
15187 			}
15188 
15189 			return Item::isThisABetterArmor(item, myStats->helmet) ? &myStats->helmet : nullptr;
15190 			break;
15191 		case TYPE_SHIELD:
15192 		case TYPE_OFFHAND:
15193 			if ( item.interactNPCUid == getUID() && myStats->shield )
15194 			{
15195 				return &myStats->shield;
15196 			}
15197 
15198 			if ( myStats->shield && (myStats->shield->beatitude < 0 || myStats->shield->forcedPickupByPlayer == true) )
15199 			{
15200 				return nullptr; //Can't swap out armor piece if current one is cursed!
15201 			}
15202 
15203 			return Item::isThisABetterArmor(item, myStats->shield) ? &myStats->shield : nullptr;
15204 		case TYPE_BREASTPIECE:
15205 			if ( item.interactNPCUid == getUID() && myStats->breastplate )
15206 			{
15207 				return &myStats->breastplate;
15208 			}
15209 
15210 			if ( myStats->breastplate && (myStats->breastplate->beatitude < 0 || myStats->breastplate->forcedPickupByPlayer == true) )
15211 			{
15212 				return nullptr; //Can't swap out armor piece if current one is cursed!
15213 			}
15214 
15215 			return Item::isThisABetterArmor(item, myStats->breastplate) ? &myStats->breastplate : nullptr;
15216 		case TYPE_CLOAK:
15217 			if ( item.interactNPCUid == getUID() && myStats->cloak )
15218 			{
15219 				return &myStats->cloak;
15220 			}
15221 
15222 			if ( myStats->cloak && (myStats->cloak->beatitude < 0 || myStats->cloak->forcedPickupByPlayer == true) )
15223 			{
15224 				return nullptr; //Can't swap out armor piece if current one is cursed!
15225 			}
15226 
15227 			return Item::isThisABetterArmor(item, myStats->cloak) ? &myStats->cloak : nullptr;
15228 		case TYPE_BOOTS:
15229 			if ( item.interactNPCUid == getUID() && myStats->shoes )
15230 			{
15231 				return &myStats->shoes;
15232 			}
15233 
15234 			if ( myStats->shoes && (myStats->shoes->beatitude < 0 || myStats->shoes->forcedPickupByPlayer == true) )
15235 			{
15236 				return nullptr; //Can't swap out armor piece if current one is cursed!
15237 			}
15238 
15239 			return Item::isThisABetterArmor(item, myStats->shoes) ? &myStats->shoes : nullptr;
15240 		case TYPE_GLOVES:
15241 			if ( item.interactNPCUid == getUID() && myStats->gloves )
15242 			{
15243 				return &myStats->gloves;
15244 			}
15245 
15246 			if ( myStats->gloves && (myStats->gloves->beatitude < 0 || myStats->gloves->forcedPickupByPlayer == true) )
15247 			{
15248 				return nullptr; //Can't swap out armor piece if current one is cursed!
15249 			}
15250 
15251 			return Item::isThisABetterArmor(item, myStats->gloves) ? &myStats->gloves : nullptr;
15252 			break;
15253 		default:
15254 			return nullptr;
15255 	}
15256 }
15257 
monsterRotate()15258 double Entity::monsterRotate()
15259 {
15260 	double dir = yaw - monsterLookDir;
15261 	while ( dir >= PI )
15262 	{
15263 		dir -= PI * 2;
15264 	}
15265 	while ( dir < -PI )
15266 	{
15267 		dir += PI * 2;
15268 	}
15269 	int race = getMonsterTypeFromSprite();
15270 	if ( race == SENTRYBOT || race == SPELLBOT )
15271 	{
15272 		Stat* myStats = getStats();
15273 		int ratio = 64;
15274 		if ( myStats )
15275 		{
15276 			if ( myStats->LVL >= 15 )
15277 			{
15278 				ratio = 2;
15279 			}
15280 			else if ( myStats->LVL >= 10 )
15281 			{
15282 				ratio = 4;
15283 			}
15284 			else if ( myStats->LVL >= 5 )
15285 			{
15286 				ratio = 16;
15287 			}
15288 			else if ( myStats->LVL >= 3 )
15289 			{
15290 				ratio = 64;
15291 			}
15292 		}
15293 		yaw -= dir / ratio;
15294 	}
15295 	else if ( race == DUMMYBOT )
15296 	{
15297 		yaw -= dir / 4;
15298 	}
15299 	else
15300 	{
15301 		yaw -= dir / 2;
15302 	}
15303 	while ( yaw < 0 )
15304 	{
15305 		yaw += 2 * PI;
15306 	}
15307 	while ( yaw >= 2 * PI )
15308 	{
15309 		yaw -= 2 * PI;
15310 	}
15311 
15312 	return dir;
15313 }
15314 
getBestMeleeWeaponIHave() const15315 Item* Entity::getBestMeleeWeaponIHave() const
15316 {
15317 	Stat* myStats = getStats();
15318 	if ( !myStats )
15319 	{
15320 		return nullptr;
15321 	}
15322 
15323 	Item* currentBest = nullptr;
15324 	if ( myStats->weapon && isMeleeWeapon(*myStats->weapon) )
15325 	{
15326 		currentBest = myStats->weapon;
15327 	}
15328 
15329 	//Loop through the creature's inventory & find the best item. //TODO: Make it work on multiplayer clients?
15330 	for ( node_t* node = myStats->inventory.first; node; node = node->next )
15331 	{
15332 		Item* item = static_cast<Item*>(node->element);
15333 		if ( item )
15334 		{
15335 			if ( isMeleeWeapon(*item) && Item::isThisABetterWeapon(*item, currentBest) )
15336 			{
15337 				currentBest = item;
15338 			}
15339 		}
15340 	}
15341 
15342 	if ( currentBest && itemIsThrowableTinkerTool(currentBest) )
15343 	{
15344 		currentBest = nullptr;
15345 	}
15346 	/*if ( currentBest )
15347 	{
15348 		messagePlayer(clientnum, "Found best melee weapon: \"%s\"", currentBest->description());
15349 	}*/
15350 
15351 	return currentBest;
15352 }
15353 
getBestShieldIHave() const15354 Item* Entity::getBestShieldIHave() const
15355 {
15356 	Stat* myStats = getStats();
15357 	if ( !myStats )
15358 	{
15359 		return nullptr;
15360 	}
15361 
15362 	Item* currentBest = nullptr;
15363 	if ( myStats->shield && myStats->shield->isShield() )
15364 	{
15365 		currentBest = myStats->shield;
15366 	}
15367 
15368 	//Loop through the creature's inventory & find the best item. //TODO: Make it work on multiplayer clients?
15369 	for ( node_t* node = myStats->inventory.first; node; node = node->next )
15370 	{
15371 		Item* item = static_cast<Item*>(node->element);
15372 		if ( item )
15373 		{
15374 			if ( item->isShield() && Item::isThisABetterArmor(*item, currentBest) )
15375 			{
15376 				currentBest = item;
15377 			}
15378 		}
15379 	}
15380 
15381 	/*if ( currentBest )
15382 	{
15383 		messagePlayer(clientnum, "Found best shield: \"%s\"", currentBest->description());
15384 	}*/
15385 
15386 	return currentBest;
15387 }
15388 
degradeArmor(Stat & hitstats,Item & armor,int armornum)15389 void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum)
15390 {
15391 	if ( hitstats.type == SHADOW )
15392 	{
15393 		return; //Shadows' armor and shields don't break.
15394 	}
15395 
15396 	if ( hitstats.type == SKELETON && behavior == &actMonster && monsterAllySummonRank > 0 )
15397 	{
15398 		return; // conjured skeleton armor doesn't break.
15399 	}
15400 
15401 	if ( armor.type == ARTIFACT_BOOTS
15402 		|| armor.type == ARTIFACT_HELM
15403 		|| armor.type == ARTIFACT_CLOAK
15404 		|| armor.type == ARTIFACT_GLOVES
15405 		|| armor.type == ARTIFACT_BREASTPIECE )
15406 	{
15407 		return;
15408 	}
15409 
15410 	if ( itemTypeIsQuiver(armor.type) )
15411 	{
15412 		// quivers don't break.
15413 		return;
15414 	}
15415 
15416 	int playerhit = -1;
15417 
15418 	if ( this->behavior == &actPlayer )
15419 	{
15420 		playerhit = this->skill[2];
15421 	}
15422 
15423 	if ( playerhit == clientnum || playerhit < 0 )
15424 	{
15425 		if ( armor.count > 1 )
15426 		{
15427 			newItem(armor.type, armor.status, armor.beatitude, armor.count - 1, armor.appearance, armor.identified, &hitstats.inventory);
15428 		}
15429 	}
15430 	armor.count = 1;
15431 	armor.status = static_cast<Status>(std::max(static_cast<int>(BROKEN), armor.status - 1));
15432 	if ( armor.status > BROKEN )
15433 	{
15434 		if ( armor.type == TOOL_CRYSTALSHARD )
15435 		{
15436 			messagePlayer(playerhit, language[2350], armor.getName());
15437 		}
15438 		else
15439 		{
15440 			messagePlayer(playerhit, language[681], armor.getName());
15441 		}
15442 	}
15443 	else
15444 	{
15445 		if ( armor.type == TOOL_CRYSTALSHARD )
15446 		{
15447 			playSoundEntity(this, 162, 64);
15448 			messagePlayer(playerhit, language[2351], armor.getName());
15449 		}
15450 		else if ( itemCategory(&armor) == SPELLBOOK )
15451 		{
15452 			playSoundEntity(this, 414, 64);
15453 			messagePlayer(playerhit, language[3459], armor.getName());
15454 		}
15455 		else
15456 		{
15457 			playSoundEntity(this, 76, 64);
15458 			messagePlayer(playerhit, language[682], armor.getName());
15459 		}
15460 	}
15461 	if ( playerhit > 0 && multiplayer == SERVER )
15462 	{
15463 		strcpy((char*)net_packet->data, "ARMR");
15464 		net_packet->data[4] = armornum;
15465 		net_packet->data[5] = armor.status;
15466 		net_packet->address.host = net_clients[playerhit - 1].host;
15467 		net_packet->address.port = net_clients[playerhit - 1].port;
15468 		net_packet->len = 6;
15469 		sendPacketSafe(net_sock, -1, net_packet, playerhit - 1);
15470 	}
15471 }
15472 
removeLightField()15473 void Entity::removeLightField()
15474 {
15475 	if ( this->light != nullptr )
15476 	{
15477 		list_RemoveNode(this->light->node);
15478 		this->light = nullptr;
15479 	}
15480 }
15481 
shouldRetreat(Stat & myStats)15482 bool Entity::shouldRetreat(Stat& myStats)
15483 {
15484 	// monsters that retreat based on CHR
15485 	// gnomes, spiders, humans (50%)
15486 	// kobolds, scarabs, suc/incubi, goatmen, rats
15487 
15488 	// excluded golems, shadows, cockatrice, skeletons, demons, imps
15489 	// scorpions, slimes, ghouls, vampires, shopkeeps
15490 
15491 	// retreating monsters will not try path when losing sight of target
15492 
15493 	if ( myStats.EFFECTS[EFF_PACIFY] || myStats.EFFECTS[EFF_FEAR] )
15494 	{
15495 		return true;
15496 	}
15497 	if ( myStats.EFFECTS[EFF_KNOCKBACK] )
15498 	{
15499 		return true;
15500 	}
15501 	if ( (myStats.EFFECTS[EFF_DASH] || (myStats.weapon && myStats.weapon->type == SPELLBOOK_DASH)) && behavior == &actMonster )
15502 	{
15503 		return false;
15504 	}
15505 	if ( myStats.type == VAMPIRE )
15506 	{
15507 		return false;
15508 	}
15509 	else if ( myStats.type == SHADOW )
15510 	{
15511 		return false;
15512 	}
15513 	else if ( myStats.type == SHOPKEEPER )
15514 	{
15515 		return false;
15516 	}
15517 	else if ( myStats.type == LICH_FIRE )
15518 	{
15519 		if ( monsterLichFireMeleeSeq == LICH_ATK_BASICSPELL_SINGLE )
15520 		{
15521 			return true;
15522 		}
15523 		else
15524 		{
15525 			return false;
15526 		}
15527 	}
15528 	else if ( monsterIsImmobileTurret(this, &myStats) )
15529 	{
15530 		return false;
15531 	}
15532 	if ( monsterAllySummonRank != 0 )
15533 	{
15534 		return false;
15535 	}
15536 	if ( myStats.type == LICH_ICE )
15537 	{
15538 		return false;
15539 	}
15540 	if ( myStats.type == INCUBUS && !strncmp(myStats.name, "inner demon", strlen("inner demon")) )
15541 	{
15542 		return false;
15543 	}
15544 	if ( monsterTarget != 0 && myStats.monsterDemonHasBeenExorcised != 0 )
15545 	{
15546 		Entity* target = uidToEntity(monsterTarget);
15547 		if ( target )
15548 		{
15549 			Stat* targetStats = target->getStats();
15550 			if ( targetStats && targetStats->type == INCUBUS && !strncmp(targetStats->name, "inner demon", strlen("inner demon")) )
15551 			{
15552 				return false;
15553 			}
15554 		}
15555 	}
15556 
15557 	Entity* leader = monsterAllyGetPlayerLeader();
15558 	if ( leader )
15559 	{
15560 		// do not retreat for brave leader!
15561 		return false;
15562 	}
15563 
15564 	if ( myStats.MAXHP >= 100 )
15565 	{
15566 		if ( myStats.HP <= myStats.MAXHP / 8 && this->getCHR() >= -2 )
15567 		{
15568 			return true;
15569 		}
15570 	}
15571 	else if ( myStats.HP <= myStats.MAXHP / 4 && this->getCHR() >= -2 )
15572 	{
15573 		return true;
15574 	}
15575 
15576 	return false;
15577 }
15578 
backupWithRangedWeapon(Stat & myStats,int dist,int hasrangedweapon)15579 bool Entity::backupWithRangedWeapon(Stat& myStats, int dist, int hasrangedweapon)
15580 {
15581 	int distanceLimit = 100;
15582 	if ( hasrangedweapon && myStats.weapon )
15583 	{
15584 		if ( distanceLimit >= getMonsterEffectiveDistanceOfRangedWeapon(myStats.weapon) )
15585 		{
15586 			distanceLimit = getMonsterEffectiveDistanceOfRangedWeapon(myStats.weapon) - 20;
15587 		}
15588 	}
15589 	if ( dist >= distanceLimit || !hasrangedweapon )
15590 	{
15591 		return false;
15592 	}
15593 
15594 	if ( (myStats.EFFECTS[EFF_DASH] || (myStats.weapon && myStats.weapon->type == SPELLBOOK_DASH)) && behavior == &actMonster )
15595 	{
15596 		return false;
15597 	}
15598 	if ( myStats.type == INSECTOID && monsterSpecialState > 0 )
15599 	{
15600 		return false;
15601 	}
15602 	if ( monsterIsImmobileTurret(this, &myStats) )
15603 	{
15604 		return false;
15605 	}
15606 	if ( myStats.type == VAMPIRE && (monsterSpecialState > 0 || !strncmp(myStats.name, "Bram Kindly", 11)) )
15607 	{
15608 		return false;
15609 	}
15610 
15611 	return true;
15612 }
15613 
monsterEquipItem(Item & item,Item ** slot)15614 void Entity::monsterEquipItem(Item& item, Item** slot)
15615 {
15616 	if ( !slot )
15617 	{
15618 		return;
15619 	}
15620 
15621 	Stat *myStats = getStats();
15622 	if ( !myStats )
15623 	{
15624 		return;
15625 	}
15626 
15627 	dropItemMonster((*slot), this, myStats);
15628 
15629 	*slot = &item;
15630 }
15631 
monsterHasSpellbook(int spellbookType)15632 bool Entity::monsterHasSpellbook(int spellbookType)
15633 {
15634 	if (spellbookType == SPELL_NONE )
15635 	{
15636 		//messagePlayer(clientnum, "[DEBUG: Entity::monsterHasSpellbook()] skipping SPELL_NONE");
15637 		return false;
15638 	}
15639 
15640 	Stat* myStats = getStats();
15641 	if ( !myStats )
15642 	{
15643 		return false;
15644 	}
15645 
15646 	if ( myStats->weapon && getSpellIDFromSpellbook(myStats->weapon->type) == spellbookType )
15647 	{
15648 		spell_t *spell = getSpellFromID(getSpellIDFromSpellbook(myStats->weapon->type));
15649 		//messagePlayer(clientnum, "DEBUG: Monster has spell %s.", spell->name);
15650 		return true;
15651 	}
15652 
15653 	for ( node_t* node = myStats->inventory.first; node; node = node->next )
15654 	{
15655 		Item* item = static_cast<Item*>(node->element);
15656 		if ( !item )
15657 		{
15658 			continue;
15659 		}
15660 
15661 		if ( getSpellIDFromSpellbook(item->type) == spellbookType )
15662 		{
15663 			spell_t *spell = getSpellFromID(getSpellIDFromSpellbook(item->type));
15664 			//messagePlayer(clientnum, "DEBUG: Monster HAS spell %s.", spell->name);
15665 			return true;
15666 		}
15667 	}
15668 
15669 	return false;
15670 }
15671 
isSpellcasterBeginner()15672 bool Entity::isSpellcasterBeginner()
15673 {
15674 	Stat* myStats = getStats();
15675 	if ( !myStats )
15676 	{
15677 		return false;
15678 	}
15679 	else if ( behavior == &actMonster )
15680 	{
15681 		return false;
15682 	}
15683 	else if ( myStats->PROFICIENCIES[PRO_SPELLCASTING] < SPELLCASTING_BEGINNER )
15684 	{
15685 		return true; //The caster has lower spellcasting skill. Cue happy fun times.
15686 	}
15687 	return false;
15688 }
15689 
getMonsterLangEntry()15690 char* Entity::getMonsterLangEntry()
15691 {
15692 	Stat* myStats = getStats();
15693 	if ( !myStats )
15694 	{
15695 		return nullptr;
15696 	}
15697 	if ( !strcmp(myStats->name, "") )
15698 	{
15699 		if ( myStats->type < KOBOLD ) //Original monster count
15700 		{
15701 			return language[90 + myStats->type];
15702 		}
15703 		else if ( myStats->type >= KOBOLD ) //New monsters
15704 		{
15705 			return language[2000 + (myStats->type - KOBOLD)];
15706 		}
15707 	}
15708 	else
15709 	{
15710 		return myStats->name;
15711 	}
15712 	return nullptr;
15713 }
15714 
playerStatIncrease(int playerClass,int chosenStats[3])15715 void Entity::playerStatIncrease(int playerClass, int chosenStats[3])
15716 {
15717 	std::mt19937 seed(rand()); // seed of distribution.
15718 
15719 	std::vector<int> statWeights = classStatGrowth[playerClass];
15720 
15721 	// debug to print which vector values are being used.
15722 	//for ( std::vector<int>::const_iterator i = statWeights.begin(); i != statWeights.end(); ++i )
15723 	//{
15724 	//	messagePlayer(0, "%2d, ", *i);
15725 	//}
15726 	if ( behavior == &actPlayer && playerClass == CLASS_SHAMAN && stats[skill[2]] )
15727 	{
15728 		if ( stats[skill[2]]->type == RAT )
15729 		{
15730 			//	            STR	DEX	CON	INT	PER	CHR
15731 			statWeights = { 1,	6,	1,	3,	1,	1 };
15732 		}
15733 		else if ( stats[skill[2]]->type == SPIDER )
15734 		{
15735 			//	            STR	DEX	CON	INT	PER	CHR
15736 			statWeights = { 1,	1,	3,	1,	6,	1 };
15737 		}
15738 		else if ( stats[skill[2]]->type == TROLL )
15739 		{
15740 			//	            STR	DEX	CON	INT	PER	CHR
15741 			statWeights = { 6,	1,	3,	1,	1,	1 };
15742 		}
15743 		else if ( stats[skill[2]]->type == CREATURE_IMP )
15744 		{
15745 			//	            STR	DEX	CON	INT	PER	CHR
15746 			statWeights = { 1,	3,	1,	6,	1,	1 };
15747 		}
15748 	}
15749 
15750 	chosenStats[0] = rand() % 6; // get first stat randomly.
15751 	statWeights[chosenStats[0]] = 0; // remove the chance of the local stat vector.
15752 
15753 	std::discrete_distribution<> distr2(statWeights.begin(), statWeights.end()); // regen the distribution with new weights.
15754 	chosenStats[1] = distr2(seed); // get second stat.
15755 	statWeights[chosenStats[1]] = 0; // remove the chance in the local stat vector.
15756 
15757 	std::discrete_distribution<> distr3(statWeights.begin(), statWeights.end()); // regen the distribution with new weights.
15758 	chosenStats[2] = distr3(seed); // get third stat.
15759 
15760 	if ( chosenStats[0] == chosenStats[1] || chosenStats[0] == chosenStats[2] || chosenStats[1] == chosenStats[2] )
15761 	{
15762 		printlog("Err: duplicate stat index chosen on level up of player with class %d!\n", playerClass);
15763 	}
15764 
15765 	return;
15766 }
15767 
createPathBoundariesNPC(int maxTileDistance)15768 void Entity::createPathBoundariesNPC(int maxTileDistance)
15769 {
15770 	Stat* myStats = this->getStats();
15771 
15772 	if ( !myStats )
15773 	{
15774 		return;
15775 	}
15776 
15777 	if ( myStats->MISC_FLAGS[STAT_FLAG_NPC] != 0
15778 		|| myStats->type == SHOPKEEPER
15779 		|| monsterAllyState == ALLY_STATE_DEFEND )
15780 	{
15781 		// is NPC, find the bounds which movement is restricted to by finding the "box" it spawned in.
15782 		int i, j;
15783 		int numTiles = 0;
15784 		monsterPathBoundaryXStart = x / 16;
15785 		monsterPathBoundaryXEnd = x / 16;
15786 		monsterPathBoundaryYStart = y / 16;
15787 		monsterPathBoundaryYEnd = y / 16;
15788 		for ( i = x; i >= 0; i -= 16 )
15789 		{
15790 			if ( !checkObstacle(i, y, this, nullptr) )
15791 			{
15792 				monsterPathBoundaryXStart = i;
15793 			}
15794 			else
15795 			{
15796 				if ( monsterAllyState == ALLY_STATE_DEFEND )
15797 				{
15798 					// don't use players to block boundaries.
15799 					bool foundplayer = false;
15800 					for ( int player = 0; player < MAXPLAYERS; ++player )
15801 					{
15802 						if ( players[player] && players[player]->entity )
15803 						{
15804 							int playerx = static_cast<int>(players[player]->entity->x);
15805 							int playery = static_cast<int>(players[player]->entity->y);
15806 							if ( playerx == i && playery == y )
15807 							{
15808 								monsterPathBoundaryXStart = i;
15809 								foundplayer = true;
15810 							}
15811 						}
15812 					}
15813 					if ( !foundplayer )
15814 					{
15815 						break;
15816 					}
15817 				}
15818 				else
15819 				{
15820 					break;
15821 				}
15822 			}
15823 			if ( maxTileDistance > 0 )
15824 			{
15825 				++numTiles;
15826 				if ( numTiles > maxTileDistance )
15827 				{
15828 					break;
15829 				}
15830 			}
15831 		}
15832 		numTiles = 0;
15833 		for ( i = x; i < map.width << 4; i += 16 )
15834 		{
15835 			if ( !checkObstacle(i, y, this, nullptr) )
15836 			{
15837 				monsterPathBoundaryXEnd = i;
15838 			}
15839 			else
15840 			{
15841 				if ( monsterAllyState == ALLY_STATE_DEFEND )
15842 				{
15843 					// don't use players to block boundaries.
15844 					bool foundplayer = false;
15845 					for ( int player = 0; player < MAXPLAYERS; ++player )
15846 					{
15847 						if ( players[player] && players[player]->entity )
15848 						{
15849 							int playerx = static_cast<int>(players[player]->entity->x);
15850 							int playery = static_cast<int>(players[player]->entity->y);
15851 							if ( playerx == i && playery == y )
15852 							{
15853 								monsterPathBoundaryXEnd = i;
15854 								foundplayer = true;
15855 							}
15856 						}
15857 					}
15858 					if ( !foundplayer )
15859 					{
15860 						break;
15861 					}
15862 				}
15863 				else
15864 				{
15865 					break;
15866 				}
15867 			}
15868 			if ( maxTileDistance > 0 )
15869 			{
15870 				++numTiles;
15871 				if ( numTiles > maxTileDistance )
15872 				{
15873 					break;
15874 				}
15875 			}
15876 		}
15877 		numTiles = 0;
15878 		for ( j = y; j >= 0; j -= 16 )
15879 		{
15880 			if ( !checkObstacle(x, j, this, nullptr) )
15881 			{
15882 				monsterPathBoundaryYStart = j;
15883 			}
15884 			else
15885 			{
15886 				if ( monsterAllyState == ALLY_STATE_DEFEND )
15887 				{
15888 					// don't use players to block boundaries.
15889 					bool foundplayer = false;
15890 					for ( int player = 0; player < MAXPLAYERS; ++player )
15891 					{
15892 						if ( players[player] && players[player]->entity )
15893 						{
15894 							int playerx = static_cast<int>(players[player]->entity->x);
15895 							int playery = static_cast<int>(players[player]->entity->y);
15896 							if ( playerx == x && playery == j )
15897 							{
15898 								monsterPathBoundaryYStart = j;
15899 								foundplayer = true;
15900 							}
15901 						}
15902 					}
15903 					if ( !foundplayer )
15904 					{
15905 						break;
15906 					}
15907 				}
15908 				else
15909 				{
15910 					break;
15911 				}
15912 			}
15913 			if ( maxTileDistance > 0 )
15914 			{
15915 				++numTiles;
15916 				if ( numTiles > maxTileDistance )
15917 				{
15918 					break;
15919 				}
15920 			}
15921 		}
15922 		numTiles = 0;
15923 		for ( j = y; j < map.height << 4; j += 16 )
15924 		{
15925 			if ( !checkObstacle(x, j, this, nullptr) )
15926 			{
15927 				monsterPathBoundaryYEnd = j;
15928 			}
15929 			else
15930 			{
15931 				if ( monsterAllyState == ALLY_STATE_DEFEND )
15932 				{
15933 					// don't use players to block boundaries.
15934 					bool foundplayer = false;
15935 					for ( int player = 0; player < MAXPLAYERS; ++player )
15936 					{
15937 						if ( players[player] && players[player]->entity )
15938 						{
15939 							int playerx = static_cast<int>(players[player]->entity->x);
15940 							int playery = static_cast<int>(players[player]->entity->y);
15941 							if ( playerx == x && playery == j )
15942 							{
15943 								monsterPathBoundaryYEnd = j;
15944 								foundplayer = true;
15945 							}
15946 						}
15947 					}
15948 					if ( !foundplayer )
15949 					{
15950 						break;
15951 					}
15952 				}
15953 				else
15954 				{
15955 					break;
15956 				}
15957 			}
15958 			if ( maxTileDistance > 0 )
15959 			{
15960 				++numTiles;
15961 				if ( numTiles > maxTileDistance )
15962 				{
15963 					break;
15964 				}
15965 			}
15966 		}
15967 		numTiles = 0;
15968 		//messagePlayer(0, "restricted to (%d, %d), (%d, %d)", monsterPathBoundaryXStart >> 4, monsterPathBoundaryYStart >> 4, monsterPathBoundaryXEnd >> 4, monsterPathBoundaryYEnd >> 4);
15969 	}
15970 }
15971 
chooseAttackSpellbookFromInventory()15972 node_t* Entity::chooseAttackSpellbookFromInventory()
15973 {
15974 	Stat* myStats = getStats();
15975 	if (!myStats )
15976 	{
15977 		return nullptr;
15978 	}
15979 
15980 	node_t* spellbook = nullptr;
15981 	std::vector<int> spellbooks;
15982 
15983 	//Ok, first, compile a list of all spells it has on it.
15984 	//Then choose one and return it.
15985 	for ( int i = 1; i < NUM_SPELLS; ++i ) //Skip 0, which = SPELL_NONE.
15986 	{
15987 		if ( monsterHasSpellbook(i) )
15988 		{
15989 			if ( myStats->type == SHADOW ) //TODO: Replace this if-else block with an "isAttackSpell() && monsterCanUseSpell()"
15990 			{
15991 				if ( shadowCanMimickSpell(i) )
15992 				{
15993 					//messagePlayer(clientnum, "I can mimic spell %d!", i);
15994 					spellbooks.push_back(i);
15995 				}
15996 				else
15997 				{
15998 					//messagePlayer(clientnum, "I no can does spell %d", i);
15999 				}
16000 			}
16001 			else
16002 			{
16003 				//messagePlayer(clientnum, "TODO: Only shadow has CanCastSpell() checking implemented! Need to update other relevant monsters.");
16004 			}
16005 		}
16006 	}
16007 
16008 	if ( spellbooks.size() == 0 )
16009 	{
16010 		//messagePlayer(clientnum, "[DEBUG:Entity::chooseAttackSpellbookFromInventory()] No applicable spellbooks on me!");
16011 		return nullptr;
16012 	}
16013 
16014 	spellbook = spellbookNodeInInventory(myStats, spellbooks[rand()%spellbooks.size()]); //Choose a random spell and return it.
16015 	if (!spellbook )
16016 	{
16017 		//messagePlayer(clientnum, "[DEBUG:Entity::chooseAttackSpellbookFromInventory()] Error: Failed to choose a spellbook!");
16018 	}
16019 	return spellbook;
16020 }
16021 
getManaRegenInterval(Stat & myStats)16022 int Entity::getManaRegenInterval(Stat& myStats)
16023 {
16024 	int regenTime = getBaseManaRegen(myStats);
16025 	int manaring = 0;
16026 	bool shapeshifted = false;
16027 	if ( behavior == &actPlayer && myStats.type != HUMAN )
16028 	{
16029 		if ( myStats.type == SKELETON )
16030 		{
16031 			manaring = -1; // 0.25x regen speed.
16032 		}
16033 		if ( effectShapeshift != NOTHING )
16034 		{
16035 			shapeshifted = true;
16036 		}
16037 	}
16038 	bool cursedItemIsBuff = false;
16039 	if ( behavior == &actPlayer )
16040 	{
16041 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(&myStats);
16042 	}
16043 	if ( myStats.breastplate != nullptr )
16044 	{
16045 		if ( myStats.breastplate->type == VAMPIRE_DOUBLET )
16046 		{
16047 			if ( myStats.breastplate->beatitude >= 0 || cursedItemIsBuff )
16048 			{
16049 				manaring++;
16050 			}
16051 			else
16052 			{
16053 				manaring--;
16054 			}
16055 		}
16056 	}
16057 	if ( myStats.cloak != nullptr )
16058 	{
16059 		if ( myStats.cloak->type == ARTIFACT_CLOAK )
16060 		{
16061 			if ( myStats.cloak->beatitude >= 0 || cursedItemIsBuff )
16062 			{
16063 				manaring++;
16064 			}
16065 			else
16066 			{
16067 				manaring--;
16068 			}
16069 		}
16070 	}
16071 	if ( myStats.mask != nullptr )
16072 	{
16073 		if ( myStats.mask->type == MASK_SHAMAN && shapeshifted )
16074 		{
16075 			if ( myStats.mask->beatitude >= 0 || cursedItemIsBuff )
16076 			{
16077 				manaring++;
16078 			}
16079 			else
16080 			{
16081 				manaring--;
16082 			}
16083 		}
16084 	}
16085 
16086 	if ( manaring >= 2 && ticks % TICKS_PER_SECOND == 0 )
16087 	{
16088 		steamAchievementEntity(this, "BARONY_ACH_ARCANE_LINK");
16089 	}
16090 
16091 	if ( myStats.EFFECTS[EFF_MP_REGEN] && myStats.type != AUTOMATON )
16092 	{
16093 		manaring += 2;
16094 		if ( manaring > 3 )
16095 		{
16096 			manaring = 3;
16097 		}
16098 	}
16099 
16100 	if ( behavior == &actPlayer && myStats.type == AUTOMATON && myStats.HUNGER < 300 )
16101 	{
16102 		float floatRegenTime = (60 * regenTime) / (std::max(myStats.MAXMP, 1));
16103 		if ( manaring > 0 )
16104 		{
16105 			return floatRegenTime * (manaring * 2); // lose 1 MP each 12 base seconds - good!
16106 		}
16107 		else if ( manaring < 0 )
16108 		{
16109 			return floatRegenTime / (abs(manaring) * 2); // lose 1 MP each 3 base seconds - bad!
16110 		}
16111 		else if ( manaring == 0 )
16112 		{
16113 			return floatRegenTime;
16114 		}
16115 	}
16116 	else if ( behavior == &actPlayer && myStats.playerRace == RACE_INSECTOID && myStats.appearance == 0 )
16117 	{
16118 		if ( !(svFlags & SV_FLAG_HUNGER) )
16119 		{
16120 			return -1;
16121 		}
16122 
16123 		// how many hunger ticks in seconds from max of 1000.
16124 		float floatRegenTime = (1000.f * 30 / static_cast<float>(TICKS_PER_SECOND));
16125 
16126 		floatRegenTime /= (std::max(myStats.MAXMP, 1)); // time for 1 mana in seconds
16127 		floatRegenTime *= TICKS_PER_SECOND; // game ticks for 1 mana
16128 
16129 		if ( manaring > 0 )
16130 		{
16131 			return floatRegenTime * (manaring * 2); // lose 1 MP each 2x base seconds - good!
16132 		}
16133 		else if ( manaring < 0 )
16134 		{
16135 			return floatRegenTime / (abs(manaring) * 2); // lose 1 MP each 0.5x base seconds - bad!
16136 		}
16137 		else if ( manaring == 0 )
16138 		{
16139 			return floatRegenTime;
16140 		}
16141 
16142 		return floatRegenTime;
16143 	}
16144 
16145 	if ( manaring > 0 )
16146 	{
16147 		return regenTime / (manaring * 2); // 1 MP each 6 seconds base
16148 	}
16149 	else if ( manaring < 0 )
16150 	{
16151 		return regenTime * abs(manaring) * 4; // 1 MP each 24 seconds if negative regen
16152 	}
16153 	else if ( manaring == 0 )
16154 	{
16155 		return regenTime;
16156 	}
16157 	return MAGIC_REGEN_TIME;
16158 }
16159 
getHealthRegenInterval(Stat & myStats)16160 int Entity::getHealthRegenInterval(Stat& myStats)
16161 {
16162 	if ( !(svFlags & SV_FLAG_HUNGER) )
16163 	{
16164 		return -1;
16165 	}
16166 	if ( myStats.EFFECTS[EFF_VAMPIRICAURA] )
16167 	{
16168 		if ( behavior == &actPlayer && myStats.EFFECTS_TIMERS[EFF_VAMPIRICAURA] > 0 )
16169 		{
16170 			return -1;
16171 		}
16172 	}
16173 	if ( myStats.HP <= 0 )
16174 	{
16175 		return -1;
16176 	}
16177 	bool cursedItemIsBuff = false;
16178 	if ( behavior == &actPlayer )
16179 	{
16180 		cursedItemIsBuff = shouldInvertEquipmentBeatitude(&myStats);
16181 	}
16182 	if ( myStats.breastplate && myStats.breastplate->type == VAMPIRE_DOUBLET )
16183 	{
16184 		return -1;
16185 	}
16186 	double healring = 0;
16187 	if ( behavior == &actPlayer && myStats.type != HUMAN )
16188 	{
16189 		if ( myStats.type == SKELETON )
16190 		{
16191 			healring = -1; // 0.25x regen speed.
16192 		}
16193 	}
16194 	if ( myStats.ring != nullptr )
16195 	{
16196 		if ( myStats.ring->type == RING_REGENERATION )
16197 		{
16198 			if ( myStats.ring->beatitude >= 0  || cursedItemIsBuff )
16199 			{
16200 				healring++;
16201 				if ( cursedItemIsBuff )
16202 				{
16203 					healring += std::min(static_cast<int>(abs(myStats.ring->beatitude)), 1);
16204 				}
16205 				else
16206 				{
16207 					healring += std::min(static_cast<int>(myStats.ring->beatitude), 1);
16208 				}
16209 			}
16210 			else
16211 			{
16212 				healring--;
16213 			}
16214 		}
16215 	}
16216 	if ( myStats.breastplate != nullptr )
16217 	{
16218 		if ( myStats.breastplate->type == ARTIFACT_BREASTPIECE )
16219 		{
16220 			if ( myStats.breastplate->beatitude >= 0 || cursedItemIsBuff )
16221 			{
16222 				healring++;
16223 			}
16224 			else
16225 			{
16226 				healring--;
16227 			}
16228 		}
16229 	}
16230 
16231 	if ( myStats.EFFECTS[EFF_TROLLS_BLOOD] )
16232 	{
16233 		healring += 1;
16234 	}
16235 
16236 	if ( healring >= 2 && ticks % TICKS_PER_SECOND == 0 )
16237 	{
16238 		steamAchievementEntity(this, "BARONY_ACH_TROLLS_BLOOD");
16239 	}
16240 
16241 	if ( myStats.EFFECTS[EFF_HP_REGEN] )
16242 	{
16243 		if ( monsterAllyGetPlayerLeader() && monsterAllySpecial == ALLY_SPECIAL_CMD_REST && myStats.EFFECTS[EFF_ASLEEP] )
16244 		{
16245 			healring += 1;
16246 		}
16247 		else
16248 		{
16249 			healring += 2;
16250 		}
16251 		if ( healring > 3 )
16252 		{
16253 			healring = 3;
16254 		}
16255 	}
16256 
16257 	if ( !strncmp(map.name, "Mages Guild", 11) && myStats.type == SHOPKEEPER )
16258 	{
16259 		healring = 25; // these guys like regenerating
16260 	}
16261 
16262 	if ( healring > 0 )
16263 	{
16264 		return (HEAL_TIME / (healring * 6)); // 1 HP each 12 sec base
16265 	}
16266 	else if ( healring < 0 )
16267 	{
16268 		return (abs(healring) * HEAL_TIME * 4); // 1 HP each 48 sec if negative regen
16269 	}
16270 	else if ( healring == 0 )
16271 	{
16272 		return HEAL_TIME;
16273 	}
16274 	return HEAL_TIME;
16275 }
16276 
getBaseManaRegen(Stat & myStats)16277 int Entity::getBaseManaRegen(Stat& myStats)
16278 {
16279 	// reduced time from intelligence and spellcasting ability, 0-200 ticks of 300.
16280 	int profMultiplier = (myStats.PROFICIENCIES[PRO_SPELLCASTING] / 20) + 1; // 2 to 7
16281 	int statMultiplier = std::max(getINT(), 0); // get intelligence
16282 	if ( myStats.type == AUTOMATON )
16283 	{
16284 		return MAGIC_REGEN_TIME;
16285 	}
16286 
16287 	int multipliedTotal = profMultiplier * statMultiplier;
16288 
16289 	if ( myStats.weapon && myStats.weapon->type == ARTIFACT_MACE )
16290 	{
16291 		real_t amount = 0.0;
16292 		getArtifactWeaponEffectChance(myStats.weapon->type, myStats, &amount);
16293 		multipliedTotal += amount;
16294 	}
16295 
16296 	if ( behavior == &actPlayer && myStats.playerRace == INSECTOID && myStats.appearance == 0 )
16297 	{
16298 		int base = MAGIC_REGEN_TIME / 3;
16299 		if ( myStats.HUNGER < 50 )
16300 		{
16301 			base = MAGIC_REGEN_TIME * 3;
16302 		}
16303 		else if ( myStats.HUNGER < 250 )
16304 		{
16305 			base = MAGIC_REGEN_TIME;
16306 		}
16307 		return (base - static_cast<int>(std::min(multipliedTotal, 100))); // return 100-33 ticks, 2-0.67 seconds.
16308 	}
16309 
16310 	return (MAGIC_REGEN_TIME - static_cast<int>(std::min(multipliedTotal, 200))); // return 300-100 ticks, 6-2 seconds.
16311 }
16312 
setRangedProjectileAttack(Entity & marksman,Stat & myStats,int optionalOverrideForArrowType)16313 void Entity::setRangedProjectileAttack(Entity& marksman, Stat& myStats, int optionalOverrideForArrowType)
16314 {
16315 	this->arrowSpeed = 7;
16316 	this->arrowShotByWeapon = 0;
16317 	this->arrowQuiverType = 0;
16318 
16319 	// get arrow effects.
16320 	if ( myStats.weapon )
16321 	{
16322 		this->arrowShotByWeapon = myStats.weapon->type;
16323 
16324 		// no longer poisons!
16325 		//if ( myStats.weapon->type == ARTIFACT_BOW )
16326 		//{
16327 		//	// poison arrow
16328 		//	//this->arrowPoisonTime = 540;    // 9 seconds of poison
16329 		//}
16330 
16331 		if ( myStats.weapon->type != SLING )
16332 		{
16333 			// get armor pierce chance.
16334 			int statChance = std::min(std::max(marksman.getPER() / 2, 0), 50); // 0 to 50 value.
16335 			if ( myStats.weapon->type == HEAVY_CROSSBOW )
16336 			{
16337 				statChance += 50;
16338 			}
16339 			int chance = rand() % 100;
16340 			if ( chance < statChance )
16341 			{
16342 				this->arrowArmorPierce = 1; // pierce half of armor in damage calc.
16343 			}
16344 			else
16345 			{
16346 				this->arrowArmorPierce = 0;
16347 			}
16348 		}
16349 
16350 		if ( marksman.behavior == &actPlayer )
16351 		{
16352 			this->setArrowProjectileProperties(this->arrowShotByWeapon);
16353 			this->arrowShotByParent = ARROW_SHOT_BY_PLAYER;
16354 		}
16355 		else if ( marksman.behavior == &actMonster )
16356 		{
16357 			this->arrowSpeed = 7;
16358 			if ( myStats.type == SENTRYBOT )
16359 			{
16360 				this->arrowShotByParent = ARROW_SHOT_BY_TRAP;
16361 			}
16362 			else
16363 			{
16364 				this->setArrowProjectileProperties(this->arrowShotByWeapon);
16365 				this->arrowShotByParent = ARROW_SHOT_BY_MONSTER;
16366 			}
16367 		}
16368 		if ( multiplayer == SERVER )
16369 		{
16370 			skill[2] = -(1000 + arrowShotByWeapon); // invokes actArrow for clients.
16371 		}
16372 	}
16373 
16374 	int attack = 0;
16375 
16376 	if ( (myStats.shield && rangedWeaponUseQuiverOnAttack(&myStats)) || optionalOverrideForArrowType != WOODEN_SHIELD )
16377 	{
16378 		if ( optionalOverrideForArrowType != WOODEN_SHIELD )
16379 		{
16380 			this->arrowQuiverType = optionalOverrideForArrowType;
16381 			if ( myStats.weapon )
16382 			{
16383 				ItemType oldType = myStats.weapon->type;
16384 				myStats.weapon->type = static_cast<ItemType>(optionalOverrideForArrowType);
16385 				attack += myStats.weapon->weaponGetAttack(&myStats);
16386 				myStats.weapon->type = oldType;
16387 			}
16388 		}
16389 		else
16390 		{
16391 			this->arrowQuiverType = myStats.shield->type;
16392 			attack += myStats.shield->weaponGetAttack(&myStats);
16393 		}
16394 		switch ( arrowQuiverType )
16395 		{
16396 			case QUIVER_SILVER:
16397 				sprite = 924;
16398 				break;
16399 			case QUIVER_PIERCE:
16400 				arrowArmorPierce = 2;
16401 				sprite = 925;
16402 				break;
16403 			case QUIVER_LIGHTWEIGHT:
16404 				sprite = 926;
16405 				break;
16406 			case QUIVER_FIRE:
16407 				sprite = 927;
16408 				break;
16409 			case QUIVER_KNOCKBACK:
16410 				sprite = 928;
16411 				break;
16412 			case QUIVER_CRYSTAL:
16413 				sprite = 929;
16414 				break;
16415 			case QUIVER_HUNTING:
16416 				sprite = 930;
16417 				break;
16418 			default:
16419 				break;
16420 		}
16421 	}
16422 
16423 	// get arrow power.
16424 	attack += marksman.getRangedAttack();
16425 	int chance = (attack / 2) * (100 - myStats.PROFICIENCIES[PRO_RANGED]) / 100.f;
16426 	if ( chance > 0 )
16427 	{
16428 		attack = (attack - chance) + (rand() % chance) + 1;
16429 	}
16430 	this->arrowPower = attack;
16431 }
16432 
setArrowProjectileProperties(int weaponType)16433 bool Entity::setArrowProjectileProperties(int weaponType)
16434 {
16435 	if ( weaponType == WOODEN_SHIELD )
16436 	{
16437 		return false;
16438 	}
16439 	if ( multiplayer == CLIENT && weaponType == TOOL_SENTRYBOT )
16440 	{
16441 		// hack for arrow traps.
16442 		this->arrowSpeed = 7;
16443 		this->vel_x = cos(this->yaw) * this->arrowSpeed;
16444 		this->vel_y = sin(this->yaw) * this->arrowSpeed;
16445 		return true;
16446 	}
16447 
16448 	if ( weaponType == CROSSBOW || weaponType == SLING || weaponType == HEAVY_CROSSBOW )
16449 	{
16450 		this->vel_z = -0.2;
16451 		this->arrowSpeed = 6;
16452 		this->pitch = -PI / 32;
16453 		this->arrowFallSpeed = 0.1;
16454 		this->arrowBoltDropOffRange = 5; // ticks before projectile starts falling.
16455 
16456 		this->vel_x = cos(this->yaw) * this->arrowSpeed;
16457 		this->vel_y = sin(this->yaw) * this->arrowSpeed;
16458 		return true;
16459 	}
16460 	else
16461 	{
16462 		this->vel_z = -0.6;
16463 		this->arrowFallSpeed = 0.08;
16464 		if ( weaponType == SHORTBOW || weaponType == COMPOUND_BOW || weaponType == ARTIFACT_BOW )
16465 		{
16466 			this->arrowSpeed = 7;
16467 			this->vel_z = -0.6;
16468 			this->arrowFallSpeed = 0.08;
16469 		}
16470 		else if ( weaponType == LONGBOW )
16471 		{
16472 			this->arrowSpeed = 8;
16473 			this->vel_z = -0.4;
16474 			this->arrowFallSpeed = 0.04;
16475 		}
16476 		this->pitch = -PI / 32;
16477 		this->arrowBoltDropOffRange = 0;
16478 		this->vel_x = cos(this->yaw) * this->arrowSpeed;
16479 		this->vel_y = sin(this->yaw) * this->arrowSpeed;
16480 		return true;
16481 	}
16482 	return false;
16483 }
16484 
16485 /* SetEntityOnFire
16486  * Attempts to set the Entity on fire. Entities that are not Burnable or are already on fire will return before any processing
16487  * Entities that do not have Stats (such as furniture) will return after setting the fire time and chance to stop at max
16488  * Entities with Stats will have their fire time (char_fire) and chance to stop being on fire (chanceToPutOutFire) reduced by their CON
16489  * Calculations for reductions is outlined in this function
16490  */
SetEntityOnFire(Entity * sourceOfFire)16491 void Entity::SetEntityOnFire(Entity* sourceOfFire)
16492 {
16493 	// Check if the Entity can be set on fire
16494 	if ( this->flags[BURNABLE] )
16495 	{
16496 		if ( this->behavior == &actPlayer )
16497 		{
16498 			Stat* myStats = this->getStats();
16499 			if ( myStats )
16500 			{
16501 				if ( myStats->type == SKELETON )
16502 				{
16503 					return;
16504 				}
16505 				if ( myStats->type == AUTOMATON )
16506 				{
16507 					return;
16508 				}
16509 				if ( myStats->breastplate && myStats->breastplate->type == MACHINIST_APRON )
16510 				{
16511 					return;
16512 				}
16513 			}
16514 		}
16515 		// Check if the Entity is already on fire
16516 		if ( !(this->flags[BURNING]) )
16517 		{
16518 			this->flags[BURNING] = true;
16519 			serverUpdateEntityFlag(this, BURNING);
16520 
16521 			/* Set the time the Entity will be on fire, based off their CON
16522 			 * |\_ MAX_TICKS_ON_FIRE is reduced by every 2 points in CON
16523 			 * |
16524 			 * |\_ Fire has a minimum of 4 cycles (120 ticks), and a maximum of 20 cycles (600 ticks), cycles are based off of TICKS_TO_PROCESS_FIRE
16525 			 * |  \_ Constants are defined in entity.hpp: MIN_TICKS_ON_FIRE and MAX_TICKS_ON_FIRE
16526 			 * |
16527 			 *  \_ For every 5 points of CON, the chance to stop being on fire is increased
16528 			 *    \_ The chance to stop being on fire has a minimum of 1 in 10, and a maximum of 1 in 5
16529 			 *      \_ Constants are defined in entity.hpp: MIN_CHANCE_STOP_FIRE and MAX_CHANCE_STOP_FIRE
16530 			 */
16531 
16532 			// Set the default time on fire
16533 			this->char_fire = MAX_TICKS_ON_FIRE;
16534 			// Set the default chance of putting out fire
16535 			this->chanceToPutOutFire = MAX_CHANCE_STOP_FIRE;
16536 
16537 			// If the Entity is not a Monster, it wont have Stats, end here
16538 			if ( this->getStats() == nullptr )
16539 			{
16540 				return; // The Entity was set on fire, it does not have Stats, so it is on fire for maximum duration
16541 			}
16542 
16543 			// Determine decrease in time on fire based on the Entity's CON
16544 			const Sint32 entityCON = this->getStats()->CON;
16545 
16546 			// If the Entity's CON is <= 1 then their time is just MAX_TICKS_ON_FIRE
16547 			if ( entityCON <= 1 )
16548 			{
16549 				return; // The Entity was set on fire, with maximum duration and chance
16550 			}
16551 
16552 			// If the Entity's CON is <= 4 then their chance is just MAX_CHANCE_STOP_FIRE
16553 			if ( entityCON <= 4 )
16554 			{
16555 				this->chanceToPutOutFire = MAX_CHANCE_STOP_FIRE;
16556 			}
16557 			else if ( entityCON >= MAX_CON_FOR_STOP_FIRE ) // If the Entity has MAX_CON_FOR_STOP_FIRE (25) or greater CON, then the reduction is equal to or less than MIN_CHANCE_STOP_FIRE
16558 			{
16559 				this->chanceToPutOutFire = MIN_CHANCE_STOP_FIRE;
16560 			}
16561 			else
16562 			{
16563 				this->chanceToPutOutFire -= static_cast<Sint32>(floor(entityCON * 0.2));
16564 			}
16565 
16566 			// If the Entity has MAX_CON_FOR_FIRE_TIME (32) or greater CON, then the reduction is equal or less than MIN_TICKS_ON_FIRE
16567 			if ( entityCON >= MAX_CON_FOR_FIRE_TIME )
16568 			{
16569 				this->char_fire = MIN_TICKS_ON_FIRE;
16570 			}
16571 			else
16572 			{
16573 				this->char_fire -= static_cast<Sint32>(floor((entityCON * 0.5) * TICKS_TO_PROCESS_FIRE));
16574 			}
16575 
16576 			if ( sourceOfFire && sourceOfFire->behavior == &actArrow )
16577 			{
16578 				if ( behavior == &actMonster )
16579 				{
16580 					// monsters shot with arrow burn less, harder for players.
16581 					this->char_fire = std::min(this->char_fire, TICKS_TO_PROCESS_FIRE * 6);
16582 				}
16583 			}
16584 
16585 			return; // The Entity was set on fire, with a reduced duration
16586 		}
16587 	}
16588 
16589 	return; // The Entity can/should not be set on fire
16590 }
16591 
16592 /*-------------------------------------------------------------------------------
16593 
16594 messagePlayerMonsterEvent
16595 handles text for monster interaction/damage/obituaries
16596 
16597 -------------------------------------------------------------------------------*/
16598 
messagePlayerMonsterEvent(int player,Uint32 color,Stat & monsterStats,char * msgGeneric,char * msgNamed,int detailType,Entity * optionalEntity)16599 void messagePlayerMonsterEvent(int player, Uint32 color, Stat& monsterStats, char* msgGeneric, char* msgNamed, int detailType, Entity* optionalEntity)
16600 {
16601 	if ( player < 0 || player >= MAXPLAYERS )
16602 	{
16603 		return;
16604 	}
16605 
16606 	// If true, pretend the monster doesn't have a name and use the generic message "You hit the lesser skeleton!"
16607 	bool namedMonsterAsGeneric = monsterNameIsGeneric(monsterStats);
16608 	int monsterType = monsterStats.type;
16609 	if ( optionalEntity != nullptr )
16610 	{
16611 		if ( optionalEntity->behavior == &actPlayer )
16612 		{
16613 			monsterType = optionalEntity->getMonsterTypeFromSprite();
16614 		}
16615 	}
16616 
16617 	//char str[256] = { 0 };
16618 	if ( !strcmp(monsterStats.name, "") )
16619 	{
16620 		// use generic racial name and grammar. "You hit the skeleton"
16621 		if ( detailType == MSG_OBITUARY )
16622 		{
16623 			for ( int c = 0; c < MAXPLAYERS; ++c )
16624 			{
16625 				if ( client_disconnected[c] )
16626 				{
16627 					continue;
16628 				}
16629 				if ( c == player )
16630 				{
16631 					if ( monsterType < KOBOLD ) // Original monster count
16632 					{
16633 						messagePlayerColor(c, color, msgNamed, language[90 + monsterType], monsterStats.obituary);
16634 					}
16635 					else if ( monsterType >= KOBOLD ) //New monsters
16636 					{
16637 						messagePlayerColor(c, color, msgNamed, language[2000 + (monsterType - KOBOLD)], monsterStats.obituary);
16638 					}
16639 				}
16640 				else
16641 				{
16642 					if ( monsterType < KOBOLD ) // Original monster count
16643 					{
16644 						messagePlayerColor(c, color, msgGeneric, stats[player]->name, language[90 + monsterType], monsterStats.obituary);
16645 					}
16646 					else if ( monsterType >= KOBOLD ) //New monsters
16647 					{
16648 						messagePlayerColor(c, color, msgGeneric, stats[player]->name, language[2000 + (monsterType - KOBOLD)], monsterStats.obituary);
16649 					}
16650 				}
16651 			}
16652 		}
16653 		else if ( detailType == MSG_ATTACKS )
16654 		{
16655 			if ( monsterType < KOBOLD ) // Original monster count
16656 			{
16657 				messagePlayerColor(player, color, msgGeneric, language[90 + monsterType], language[132 + monsterType]);
16658 			}
16659 			else if ( monsterType >= KOBOLD ) //New monsters
16660 			{
16661 				messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)], language[2100 + (monsterType - KOBOLD)]);
16662 			}
16663 		}
16664 		else if ( detailType == MSG_STEAL_WEAPON )
16665 		{
16666 			if ( monsterStats.weapon )
16667 			{
16668 				if ( monsterType < KOBOLD ) // Original monster count
16669 				{
16670 					messagePlayerColor(player, color, msgGeneric, language[90 + monsterType], monsterStats.weapon->getName());
16671 				}
16672 				else if ( monsterType >= KOBOLD ) //New monsters
16673 				{
16674 					messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)], monsterStats.weapon->getName());
16675 				}
16676 			}
16677 		}
16678 		else if ( detailType == MSG_TOOL_BOMB )
16679 		{
16680 			int itemType = WOODEN_SHIELD;
16681 			if ( optionalEntity && optionalEntity->behavior == &actBomb )
16682 			{
16683 				itemType = optionalEntity->skill[21];
16684 				if ( monsterType < KOBOLD ) // Original monster count
16685 				{
16686 					messagePlayerColor(player, color, msgGeneric, language[90 + monsterType], items[itemType].name_identified);
16687 				}
16688 				else if ( monsterType >= KOBOLD ) //New monsters
16689 				{
16690 					messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)], items[itemType].name_identified);
16691 				}
16692 			}
16693 		}
16694 		else
16695 		{
16696 			if ( monsterType < KOBOLD ) // Original monster count
16697 			{
16698 				messagePlayerColor(player, color, msgGeneric, language[90 + monsterType]);
16699 			}
16700 			else if ( monsterType >= KOBOLD ) //New monsters
16701 			{
16702 				messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)]);
16703 			}
16704 		}
16705 	}
16706 	else
16707 	{
16708 		// use monster's "name" and pronoun grammar. "You hit Funny Bones!"
16709 		if ( detailType == MSG_DESCRIPTION )
16710 		{
16711 			if ( namedMonsterAsGeneric )
16712 			{
16713 				messagePlayerColor(player, color, msgGeneric, monsterStats.name);
16714 			}
16715 			else if ( monsterType < KOBOLD ) //Original monster count
16716 			{
16717 				messagePlayerColor(player, color, msgNamed, language[90 + monsterType], monsterStats.name);
16718 			}
16719 			else if ( monsterType >= KOBOLD ) //New monsters
16720 			{
16721 				messagePlayerColor(player, color, msgNamed, language[2000 + (monsterType - KOBOLD)], monsterStats.name);
16722 			}
16723 		}
16724 		else if ( detailType == MSG_COMBAT )
16725 		{
16726 			if ( namedMonsterAsGeneric )
16727 			{
16728 				messagePlayerColor(player, color, msgGeneric, monsterStats.name);
16729 			}
16730 			else if ( monsterType < KOBOLD ) //Original monster count
16731 			{
16732 				messagePlayerColor(player, color, msgNamed, monsterStats.name);
16733 			}
16734 			else if ( monsterType >= KOBOLD ) //New monsters
16735 			{
16736 				messagePlayerColor(player, color, msgNamed, monsterStats.name);
16737 			}
16738 		}
16739 		else if ( detailType == MSG_OBITUARY )
16740 		{
16741 			for ( int c = 0; c < MAXPLAYERS; ++c )
16742 			{
16743 				if ( client_disconnected[c] )
16744 				{
16745 					continue;
16746 				}
16747 				if ( namedMonsterAsGeneric )
16748 				{
16749 					if ( c == player )
16750 					{
16751 						messagePlayerColor(c, color, msgNamed, monsterStats.name, monsterStats.obituary);
16752 					}
16753 					else
16754 					{
16755 						messagePlayerColor(c, color, msgGeneric, stats[player]->name, monsterStats.name, monsterStats.obituary);
16756 					}
16757 				}
16758 				else
16759 				{
16760 					messagePlayerColor(c, color, "%s %s", monsterStats.name, monsterStats.obituary);
16761 				}
16762 			}
16763 		}
16764 		else if ( detailType == MSG_GENERIC )
16765 		{
16766 			if ( namedMonsterAsGeneric || monsterType == HUMAN || (optionalEntity && optionalEntity->behavior == &actPlayer) )
16767 			{
16768 				messagePlayerColor(player, color, msgGeneric, monsterStats.name);
16769 			}
16770 			else if ( monsterType < KOBOLD ) // Original monster count
16771 			{
16772 				messagePlayerColor(player, color, msgGeneric, language[90 + monsterType]);
16773 			}
16774 			else if ( monsterType >= KOBOLD ) //New monsters
16775 			{
16776 				messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)]);
16777 			}
16778 		}
16779 		else if ( detailType == MSG_ATTACKS )
16780 		{
16781 			if ( namedMonsterAsGeneric )
16782 			{
16783 				if ( monsterType < KOBOLD ) // Original monster count
16784 				{
16785 					messagePlayerColor(player, color, msgGeneric, monsterStats.name, language[132 + monsterType]);
16786 				}
16787 				else if ( monsterType >= KOBOLD ) //New monsters
16788 				{
16789 					messagePlayerColor(player, color, msgGeneric, monsterStats.name, language[2100 + (monsterType - KOBOLD)]);
16790 				}
16791 			}
16792 			else if ( monsterType < KOBOLD ) // Original monster count
16793 			{
16794 				messagePlayerColor(player, color, msgNamed, monsterStats.name, language[132 + monsterType]);
16795 			}
16796 			else if ( monsterType >= KOBOLD ) //New monsters
16797 			{
16798 				messagePlayerColor(player, color, msgNamed, monsterStats.name, language[2100 + (monsterType - KOBOLD)]);
16799 			}
16800 		}
16801 		else if ( detailType == MSG_STEAL_WEAPON )
16802 		{
16803 			if ( monsterStats.weapon )
16804 			{
16805 				if ( namedMonsterAsGeneric )
16806 				{
16807 					messagePlayerColor(player, color, msgGeneric, monsterStats.name, monsterStats.weapon->getName());
16808 				}
16809 				else if ( monsterType < KOBOLD ) //Original monster count
16810 				{
16811 					messagePlayerColor(player, color, msgNamed, monsterStats.name, monsterStats.weapon->getName());
16812 				}
16813 				else if ( monsterType >= KOBOLD ) //New monsters
16814 				{
16815 					messagePlayerColor(player, color, msgNamed, monsterStats.name, monsterStats.weapon->getName());
16816 				}
16817 			}
16818 		}
16819 		else if ( detailType == MSG_TOOL_BOMB )
16820 		{
16821 			int itemType = WOODEN_SHIELD;
16822 			if ( optionalEntity && optionalEntity->behavior == &actBomb )
16823 			{
16824 				itemType = optionalEntity->skill[21];
16825 				if ( namedMonsterAsGeneric || monsterType == HUMAN )
16826 				{
16827 					messagePlayerColor(player, color, msgGeneric, monsterStats.name, items[itemType].name_identified);
16828 				}
16829 				else if ( monsterType < KOBOLD ) // Original monster count
16830 				{
16831 					messagePlayerColor(player, color, msgGeneric, language[90 + monsterType], items[itemType].name_identified);
16832 				}
16833 				else if ( monsterType >= KOBOLD ) //New monsters
16834 				{
16835 					messagePlayerColor(player, color, msgGeneric, language[2000 + (monsterType - KOBOLD)], items[itemType].name_identified);
16836 				}
16837 			}
16838 		}
16839 	}
16840 }
16841 
16842 /*-------------------------------------------------------------------------------
16843 
16844 playerClassLangEntry
16845 get text string for the different player chosen classes.
16846 
16847 -------------------------------------------------------------------------------*/
16848 
playerClassLangEntry(int classnum,int playernum)16849 char const * playerClassLangEntry(int classnum, int playernum)
16850 {
16851 	if ( classnum >= CLASS_BARBARIAN && classnum <= CLASS_JOKER )
16852 	{
16853 		return language[1900 + classnum];
16854 	}
16855 	else if ( classnum >= CLASS_CONJURER )
16856 	{
16857 		return language[3223 + classnum - CLASS_CONJURER];
16858 	}
16859 	else if ( classnum >= CLASS_SEXTON && classnum <= CLASS_MONK )
16860 	{
16861 		return language[2550 + classnum - CLASS_SEXTON];
16862 	}
16863 	else
16864 	{
16865 		return "undefined classname";
16866 	}
16867 }
16868 
16869 /*-------------------------------------------------------------------------------
16870 
16871 playerClassDescription
16872 get text string for the description of player chosen classes.
16873 
16874 -------------------------------------------------------------------------------*/
16875 
playerClassDescription(int classnum,int playernum)16876 char const * playerClassDescription(int classnum, int playernum)
16877 {
16878 	if ( classnum >= CLASS_BARBARIAN && classnum <= CLASS_JOKER )
16879 	{
16880 		return language[10 + classnum];
16881 	}
16882 	else if ( classnum >= CLASS_CONJURER )
16883 	{
16884 		return language[3231 + classnum - CLASS_CONJURER];
16885 	}
16886 	else if ( classnum >= CLASS_SEXTON && classnum <= CLASS_MONK )
16887 	{
16888 		return language[2560 + classnum - CLASS_SEXTON];
16889 	}
16890 	else
16891 	{
16892 		return "undefined description";
16893 	}
16894 }
16895 
16896 /*-------------------------------------------------------------------------------
16897 
16898 setHelmetLimbOffset
16899 Adjusts helmet offsets for all monsters, depending on the type of headwear.
16900 
16901 -------------------------------------------------------------------------------*/
16902 
setHelmetLimbOffset(Entity * helm)16903 void Entity::setHelmetLimbOffset(Entity* helm)
16904 {
16905 	helm->scalex = 1.01;
16906 	helm->scaley = 1.01;
16907 	helm->scalez = 1.01;
16908 	// for non-armor helmets, they are rotated so focaly acts as up/down postion.
16909 	int monster = getMonsterTypeFromSprite();
16910 	if ( helm->sprite == items[HAT_PHRYGIAN].index )
16911 	{
16912 		switch ( monster )
16913 		{
16914 			case AUTOMATON:
16915 			case SKELETON:
16916 				helm->focalx = limbs[monster][9][0] - .5;
16917 				helm->focaly = limbs[monster][9][1] - 3.25;
16918 				helm->focalz = limbs[monster][9][2] + 2.25;
16919 				break;
16920 			case HUMAN:
16921 			case SHOPKEEPER:
16922 			case VAMPIRE:
16923 				helm->focalx = limbs[monster][9][0] - .5;
16924 				helm->focaly = limbs[monster][9][1] - 3.25;
16925 				helm->focalz = limbs[monster][9][2] + 2.25;
16926 				break;
16927 			case INSECTOID:
16928 				helm->focalx = limbs[monster][9][0] - .5;
16929 				helm->focaly = limbs[monster][9][1] - 3.05;
16930 				helm->focalz = limbs[monster][9][2] + 2.25;
16931 				break;
16932 			case GOBLIN:
16933 			case SHADOW:
16934 				helm->focalx = limbs[monster][9][0] - .5;
16935 				helm->focaly = limbs[monster][9][1] - 3.55;
16936 				helm->focalz = limbs[monster][9][2] + 2.5;
16937 				break;
16938 			case GOATMAN:
16939 				helm->focalx = limbs[monster][9][0] - .5;
16940 				helm->focaly = limbs[monster][9][1] - 3.55;
16941 				helm->focalz = limbs[monster][9][2] + 2.75;
16942 				break;
16943 			case INCUBUS:
16944 			case SUCCUBUS:
16945 				helm->focalx = limbs[monster][9][0] - .5;
16946 				helm->focaly = limbs[monster][9][1] - 3.2;
16947 				helm->focalz = limbs[monster][9][2] + 2.5;
16948 				break;
16949 			default:
16950 				break;
16951 		}
16952 		helm->roll = PI / 2;
16953 	}
16954 	else if ( (helm->sprite >= items[HAT_HOOD].index && helm->sprite < items[HAT_HOOD].index + items[HAT_HOOD].variations)
16955 		|| helm->sprite == items[HAT_HOOD_RED].index || helm->sprite == items[HAT_HOOD_SILVER].index
16956 		|| helm->sprite == items[PUNISHER_HOOD].index )
16957 	{
16958 		switch ( monster )
16959 		{
16960 			case AUTOMATON:
16961 			case SKELETON:
16962 				helm->focalx = limbs[monster][9][0] - .5;
16963 				helm->focaly = limbs[monster][9][1] - 2.5;
16964 				helm->focalz = limbs[monster][9][2] + 2.25;
16965 				if ( helm->sprite == (items[HAT_HOOD].index + 2) )
16966 				{
16967 					helm->focaly += 0.5; // black hood
16968 				}
16969 				else if ( helm->sprite == (items[HAT_HOOD].index + 3) )
16970 				{
16971 					helm->focaly -= 0.5; // purple hood
16972 				}
16973 				else if ( helm->sprite == items[PUNISHER_HOOD].index )
16974 				{
16975 					helm->focalx += 0.25;
16976 					helm->focaly += 0.5;
16977 				}
16978 				break;
16979 			case INCUBUS:
16980 			case SUCCUBUS:
16981 				helm->focalx = limbs[monster][9][0] - .5;
16982 				helm->focaly = limbs[monster][9][1] - 2.5;
16983 				helm->focalz = limbs[monster][9][2] + 2.5;
16984 				if ( helm->sprite == (items[HAT_HOOD].index + 3) )
16985 				{
16986 					helm->focaly -= 0.5; // purple hood
16987 				}
16988 				else if ( helm->sprite == items[PUNISHER_HOOD].index )
16989 				{
16990 					if ( monster == INCUBUS )
16991 					{
16992 						helm->focalx += 0.25;
16993 						helm->focaly += 0.25;
16994 					}
16995 				}
16996 				break;
16997 			case VAMPIRE:
16998 			case SHOPKEEPER:
16999 			case HUMAN:
17000 				helm->focalx = limbs[monster][9][0] - .5;
17001 				helm->focaly = limbs[monster][9][1] - 2.5;
17002 				helm->focalz = limbs[monster][9][2] + 2.25;
17003 				if ( helm->sprite == items[PUNISHER_HOOD].index )
17004 				{
17005 					helm->focaly += 0.25;
17006 				}
17007 				break;
17008 			case GOATMAN:
17009 				helm->focalx = limbs[monster][9][0] - .5;
17010 				helm->focaly = limbs[monster][9][1] - 2.75;
17011 				helm->focalz = limbs[monster][9][2] + 2.75;
17012 				if ( helm->sprite == (items[HAT_HOOD].index + 2) )
17013 				{
17014 					helm->focaly -= 0.25; // black hood
17015 				}
17016 				else if ( helm->sprite == (items[HAT_HOOD].index + 3) )
17017 				{
17018 					helm->focaly -= 0.5; // purple hood
17019 				}
17020 				break;
17021 			case INSECTOID:
17022 				helm->focalx = limbs[monster][9][0] - .5;
17023 				helm->focaly = limbs[monster][9][1] - 2.15;
17024 				helm->focalz = limbs[monster][9][2] + 2.25;
17025 				if ( helm->sprite == (items[HAT_HOOD].index + 2) )
17026 				{
17027 					helm->focaly += 0.25; // black hood
17028 				}
17029 				else if ( helm->sprite == (items[HAT_HOOD].index + 3) )
17030 				{
17031 					helm->focaly -= 0.5; // purple hood
17032 				}
17033 				else if ( helm->sprite == items[PUNISHER_HOOD].index )
17034 				{
17035 					helm->focalx += 0.5;
17036 					helm->focaly += 0.15;
17037 				}
17038 				break;
17039 			case GOBLIN:
17040 			case SHADOW:
17041 				helm->focalx = limbs[monster][9][0] - .5;
17042 				helm->focaly = limbs[monster][9][1] - 2.75;
17043 				helm->focalz = limbs[monster][9][2] + 2.5;
17044 				if ( monster == GOBLIN && this->sprite == 752 ) // special female offset.
17045 				{
17046 					if ( helm->sprite == (items[HAT_HOOD].index + 3) )
17047 					{
17048 						helm->focaly -= 0.5; // purple hood
17049 					}
17050 				}
17051 				if ( helm->sprite == items[PUNISHER_HOOD].index )
17052 				{
17053 					helm->focaly += 0.25;
17054 				}
17055 				break;
17056 			default:
17057 				break;
17058 		}
17059 		/*helm->focalx += limbs[HUMAN][12][0];
17060 		helm->focaly += limbs[HUMAN][12][1];
17061 		helm->focalz += limbs[HUMAN][12][2];*/
17062 		helm->roll = PI / 2;
17063 	}
17064 	else if ( helm->sprite == items[HAT_WIZARD].index || helm->sprite == items[HAT_JESTER].index )
17065 	{
17066 		switch ( monster )
17067 		{
17068 			case AUTOMATON:
17069 			case SKELETON:
17070 				helm->focalx = limbs[monster][9][0];
17071 				helm->focaly = limbs[monster][9][1] - 4.5;
17072 				helm->focalz = limbs[monster][9][2] + 2.25;
17073 				break;
17074 			case INCUBUS:
17075 			case SUCCUBUS:
17076 				helm->focalx = limbs[monster][9][0];
17077 				helm->focaly = limbs[monster][9][1] - 4.75;
17078 				helm->focalz = limbs[monster][9][2] + 2.5;
17079 				break;
17080 			case VAMPIRE:
17081 			case SHOPKEEPER:
17082 			case HUMAN:
17083 				helm->focalx = limbs[monster][9][0];
17084 				helm->focaly = limbs[monster][9][1] - 4.75;
17085 				helm->focalz = limbs[monster][9][2] + 2.25;
17086 				break;
17087 			case GOATMAN:
17088 				helm->focalx = limbs[monster][9][0];
17089 				helm->focaly = limbs[monster][9][1] - 5.1;
17090 				helm->focalz = limbs[monster][9][2] + 2.75;
17091 				break;
17092 			case INSECTOID:
17093 				helm->focalx = limbs[monster][9][0];
17094 				helm->focaly = limbs[monster][9][1] - 4.75;
17095 				helm->focalz = limbs[monster][9][2] + 2.25;
17096 				break;
17097 			case GOBLIN:
17098 			case SHADOW:
17099 				helm->focalx = limbs[monster][9][0];
17100 				helm->focaly = limbs[monster][9][1] - 5;
17101 				helm->focalz = limbs[monster][9][2] + 2.5;
17102 				break;
17103 			default:
17104 				break;
17105 		}
17106 		helm->roll = PI / 2;
17107 	}
17108 	else if ( helm->sprite == items[HAT_FEZ].index )
17109 	{
17110 		switch ( monster )
17111 		{
17112 			case AUTOMATON:
17113 			case SKELETON:
17114 				helm->focalx = limbs[monster][9][0];
17115 				helm->focaly = limbs[monster][9][1] - 4.f;
17116 				helm->focalz = limbs[monster][9][2] + 2.25;
17117 				break;
17118 			case INCUBUS:
17119 			case SUCCUBUS:
17120 				helm->focalx = limbs[monster][9][0];
17121 				helm->focaly = limbs[monster][9][1] - 4.0;
17122 				helm->focalz = limbs[monster][9][2] + 2.5;
17123 				break;
17124 			case VAMPIRE:
17125 			case SHOPKEEPER:
17126 			case HUMAN:
17127 				helm->focalx = limbs[monster][9][0];
17128 				helm->focaly = limbs[monster][9][1] - 4.35;
17129 				helm->focalz = limbs[monster][9][2] + 2.25;
17130 				break;
17131 			case GOATMAN:
17132 				helm->focalx = limbs[monster][9][0];
17133 				helm->focaly = limbs[monster][9][1] - 4.5;
17134 				helm->focalz = limbs[monster][9][2] + 2.75;
17135 				break;
17136 			case INSECTOID:
17137 				helm->focalx = limbs[monster][9][0];
17138 				helm->focaly = limbs[monster][9][1] - 4;
17139 				helm->focalz = limbs[monster][9][2] + 2.25;
17140 				break;
17141 			case GOBLIN:
17142 			case SHADOW:
17143 				helm->focalx = limbs[monster][9][0];
17144 				helm->focaly = limbs[monster][9][1] - 4.5;
17145 				helm->focalz = limbs[monster][9][2] + 2.5;
17146 				if ( monster == GOBLIN && this->sprite == 752 ) // special female offset.
17147 				{
17148 					helm->focaly -= 0.25;
17149 				}
17150 				break;
17151 			default:
17152 				break;
17153 		}
17154 		helm->roll = PI / 2;
17155 	}
17156 	else if ( helm->sprite == items[MASK_SHAMAN].index )
17157 	{
17158 		switch ( monster )
17159 		{
17160 			case AUTOMATON:
17161 				helm->focalx = limbs[monster][10][0] + 1.f;
17162 				helm->focaly = limbs[monster][10][1] - 0.5;
17163 				helm->focalz = limbs[monster][10][2] - 1.5;
17164 				break;
17165 			case SKELETON:
17166 				helm->focalx = limbs[monster][10][0] + 0.5;
17167 				helm->focaly = limbs[monster][10][1] - 0.5;
17168 				helm->focalz = limbs[monster][10][2] - 1.7;
17169 				break;
17170 			case INCUBUS:
17171 				helm->focalx = limbs[monster][10][0] + 0.5;
17172 				helm->focaly = limbs[monster][10][1] - 0.25;
17173 				helm->focalz = limbs[monster][10][2] - 2;
17174 				break;
17175 			case SUCCUBUS:
17176 				helm->focalx = limbs[monster][10][0] + 0.5;
17177 				helm->focaly = limbs[monster][10][1] - 0;
17178 				helm->focalz = limbs[monster][10][2] - 2.25;
17179 				break;
17180 			case VAMPIRE:
17181 			case SHOPKEEPER:
17182 			case HUMAN:
17183 				helm->focalx = limbs[monster][10][0] + 0.75;
17184 				helm->focaly = limbs[monster][10][1] - 0;
17185 				helm->focalz = limbs[monster][10][2] - 2;
17186 				break;
17187 			case GOATMAN:
17188 				helm->focalx = limbs[monster][10][0] + 0.7;
17189 				helm->focaly = limbs[monster][10][1] + 0.25;
17190 				helm->focalz = limbs[monster][10][2] - 2.55;
17191 				break;
17192 			case INSECTOID:
17193 				helm->focalx = limbs[monster][10][0] + 1.03;
17194 				helm->focaly = limbs[monster][10][1] - 0.25;
17195 				helm->focalz = limbs[monster][10][2] - 1.5;
17196 				break;
17197 			case GOBLIN:
17198 				helm->focalx = limbs[monster][10][0] + 0.7;
17199 				helm->focaly = limbs[monster][10][1] + 0;
17200 				helm->focalz = limbs[monster][10][2] - 2.25;
17201 				//if ( monster == GOBLIN && this->sprite == 752 ) // special female offset.
17202 				//{
17203 				//	helm->focaly -= 0.25;
17204 				//}
17205 				break;
17206 			case SHADOW:
17207 			default:
17208 				break;
17209 		}
17210 	}
17211 	else
17212 	{
17213 		if ( monster == GOBLIN && this->sprite == 752 ) // special female offset.
17214 		{
17215 			helm->focalz = limbs[monster][9][2] - 0.25; // all non-hat helms
17216 		}
17217 	}
17218 }
17219 
yawDifferenceFromPlayer(int player)17220 real_t Entity::yawDifferenceFromPlayer(int player)
17221 {
17222 	if ( player >= 0 && players[player] && players[player]->entity )
17223 	{
17224 		real_t targetYaw = this->yaw;
17225 		while ( targetYaw >= 2 * PI )
17226 		{
17227 			targetYaw -= PI * 2;
17228 		}
17229 		while ( targetYaw < 0 )
17230 		{
17231 			targetYaw += PI * 2;
17232 		}
17233 		return (PI - abs(abs(players[player]->entity->yaw - targetYaw) - PI)) * 2;
17234 	}
17235 	return 0.f;
17236 }
17237 
summonChest(long x,long y)17238 Entity* summonChest(long x, long y)
17239 {
17240 	Entity* entity = newEntity(21, 1, map.entities, nullptr); //Chest entity.
17241 	if ( !entity )
17242 	{
17243 		return nullptr;
17244 	}
17245 	setSpriteAttributes(entity, nullptr, nullptr);
17246 	entity->chestLocked = -1;
17247 
17248 	// Find a free tile next to the source and then spawn it there.
17249 	if ( multiplayer != CLIENT )
17250 	{
17251 		if ( entityInsideSomething(entity) )
17252 		{
17253 			do
17254 			{
17255 				entity->x = x;
17256 				entity->y = y - 16;
17257 				if (!entityInsideSomething(entity))
17258 				{
17259 					break;    // north
17260 				}
17261 				entity->x = x;
17262 				entity->y = y + 16;
17263 				if (!entityInsideSomething(entity))
17264 				{
17265 					break;    // south
17266 				}
17267 				entity->x = x - 16;
17268 				entity->y = y;
17269 				if (!entityInsideSomething(entity))
17270 				{
17271 					break;    // west
17272 				}
17273 				entity->x = x + 16;
17274 				entity->y = y;
17275 				if (!entityInsideSomething(entity))
17276 				{
17277 					break;    // east
17278 				}
17279 				entity->x = x + 16;
17280 				entity->y = y - 16;
17281 				if (!entityInsideSomething(entity))
17282 				{
17283 					break;    // northeast
17284 				}
17285 				entity->x = x + 16;
17286 				entity->y = y + 16;
17287 				if (!entityInsideSomething(entity))
17288 				{
17289 					break;    // southeast
17290 				}
17291 				entity->x = x - 16;
17292 				entity->y = y - 16;
17293 				if (!entityInsideSomething(entity))
17294 				{
17295 					break;    // northwest
17296 				}
17297 				entity->x = x - 16;
17298 				entity->y = y + 16;
17299 				if (!entityInsideSomething(entity))
17300 				{
17301 					break;    // southwest
17302 				}
17303 
17304 				// we can't have monsters in walls...
17305 				list_RemoveNode(entity->mynode);
17306 				entity = nullptr;
17307 				break;
17308 			}
17309 			while (1);
17310 		}
17311 	}
17312 
17313 	entity->sizex = 3;
17314 	entity->sizey = 2;
17315 	entity->x = x;
17316 	entity->y = y;
17317 	entity->x += 8;
17318 	entity->y += 8;
17319 	entity->z = 5.5;
17320 	entity->yaw = entity->yaw * (PI / 2); //set to 0 by default in editor, can be set 0-3
17321 	entity->behavior = &actChest;
17322 	entity->sprite = 188;
17323 	//entity->skill[9] = -1; //Set default chest as random category < 0
17324 
17325 	Entity* childEntity = newEntity(216, 0, map.entities, nullptr); //Sort-of limb entity.
17326 	if ( !childEntity )
17327 	{
17328 		return nullptr;
17329 	}
17330 	childEntity->parent = entity->getUID();
17331 	entity->parent = childEntity->getUID();
17332 	if ( entity->yaw == 0 ) //EAST FACING
17333 	{
17334 		childEntity->x = entity->x - 3;
17335 		childEntity->y = entity->y;
17336 	}
17337 	else if ( entity->yaw == PI / 2 ) //SOUTH FACING
17338 	{
17339 		childEntity->x = entity->x;
17340 		childEntity->y = entity->y - 3;
17341 	}
17342 	else if ( entity->yaw == PI ) //WEST FACING
17343 	{
17344 		childEntity->x = entity->x + 3;
17345 		childEntity->y = entity->y;
17346 	}
17347 	else if (entity->yaw == 3 * PI/2 ) //NORTH FACING
17348 	{
17349 		childEntity->x = entity->x;
17350 		childEntity->y = entity->y + 3;
17351 	}
17352 	else
17353 	{
17354 		childEntity->x = entity->x;
17355 		childEntity->y = entity->y - 3;
17356 	}
17357 	//printlog("29 Generated entity. Sprite: %d Uid: %d X: %.2f Y: %.2f\n",childEntity->sprite,childEntity->getUID(),childEntity->x,childEntity->y);
17358 	childEntity->z = entity->z - 2.75;
17359 	childEntity->focalx = 3;
17360 	childEntity->focalz = -.75;
17361 	childEntity->yaw = entity->yaw;
17362 	childEntity->sizex = 2;
17363 	childEntity->sizey = 2;
17364 	childEntity->behavior = &actChestLid;
17365 	childEntity->flags[PASSABLE] = true;
17366 
17367 	//Chest inventory.
17368 	node_t* tempNode = list_AddNodeFirst(&entity->children);
17369 	tempNode->element = nullptr;
17370 	tempNode->deconstructor = &emptyDeconstructor;
17371 
17372 	return entity;
17373 }
17374 
addToCreatureList(list_t * list)17375 void Entity::addToCreatureList(list_t *list)
17376 {
17377 	//printlog("*ATTEMPTING* to add Dennis to creature list.");
17378 	if ( list )
17379 	{
17380 		if ( myCreatureListNode )
17381 		{
17382 			list_RemoveNode(myCreatureListNode);
17383 			myCreatureListNode = nullptr;
17384 		}
17385 		myCreatureListNode = list_AddNodeLast(list);
17386 		myCreatureListNode->element = this;
17387 		myCreatureListNode->deconstructor = &emptyDeconstructor;
17388 		myCreatureListNode->size = sizeof(Entity);
17389 		//printlog("Added dennis to creature list.");
17390 	}
17391 }
17392 
getMagicResistance()17393 int Entity::getMagicResistance()
17394 {
17395 	int resistance = 0;
17396 	Stat* myStats = getStats();
17397 	if ( myStats )
17398 	{
17399 		if ( myStats->shield )
17400 		{
17401 			if ( myStats->shield->type == STEEL_SHIELD_RESISTANCE )
17402 			{
17403 				if ( myStats->defending )
17404 				{
17405 					resistance += 2;
17406 				}
17407 				else
17408 				{
17409 					resistance += 1;
17410 				}
17411 			}
17412 		}
17413 		if ( myStats->ring )
17414 		{
17415 			if ( myStats->ring->type == RING_MAGICRESISTANCE )
17416 			{
17417 				resistance += 1;
17418 			}
17419 		}
17420 		if ( myStats->gloves )
17421 		{
17422 			if ( myStats->gloves->type == ARTIFACT_GLOVES )
17423 			{
17424 				resistance += 1;
17425 			}
17426 		}
17427 		if ( myStats->EFFECTS[EFF_MAGICRESIST] )
17428 		{
17429 			resistance += 1;
17430 		}
17431 		if ( myStats->EFFECTS[EFF_SHRINE_BLUE_BUFF] )
17432 		{
17433 			resistance += 1;
17434 		}
17435 	}
17436 	else
17437 	{
17438 		return 0;
17439 	}
17440 	return resistance;
17441 }
17442 
setHardcoreStats(Stat & stats)17443 void Entity::setHardcoreStats(Stat& stats)
17444 {
17445 	if ( (svFlags & SV_FLAG_HARDCORE) && stats.MISC_FLAGS[STAT_FLAG_MONSTER_DISABLE_HC_SCALING] == 0 )
17446 	{
17447 		// spice up some stats...
17448 		int statIncrease = ((abs(stats.HP) / 20 + 1) * 20); // each 20 HP add 20 random HP
17449 		stats.HP += statIncrease - (rand() % (std::max(statIncrease / 5, 1))); // 80%-100% of increased value
17450 		stats.MAXHP = stats.HP;
17451 		stats.OLDHP = stats.HP;
17452 
17453 		statIncrease = (abs(stats.STR) / 5 + 1) * 5; // each 5 STR add 5 more STR.
17454 		stats.STR += (statIncrease - (rand() % (std::max(statIncrease / 4, 1)))); // 75%-100% of increased value.
17455 
17456 		statIncrease = (abs(stats.PER) / 5 + 1) * 5; // each 5 PER add 5 more PER.
17457 		stats.PER += (statIncrease - (rand() % (std::max(statIncrease / 4, 1)))); // 75%-100% of increased value.
17458 
17459 		statIncrease = std::min((abs(stats.DEX) / 4 + 1) * 1, 8); // each 4 DEX add 1 more DEX, capped at 8.
17460 		stats.DEX += (statIncrease - (rand() % (std::max(statIncrease / 2, 1)))); // 50%-100% of increased value.
17461 
17462 		statIncrease = (abs(stats.CON) / 5 + 1) * 1; // each 5 CON add 1 more CON.
17463 		stats.CON += (statIncrease - (rand() % (std::max(statIncrease / 2, 1)))); // 50%-100% of increased value.
17464 
17465 		statIncrease = (abs(stats.INT) / 5 + 1) * 5; // each 5 INT add 5 more INT.
17466 		stats.INT += (statIncrease - (rand() % (std::max(statIncrease / 2, 1)))); // 50%-100% of increased value.
17467 
17468 		int lvlIncrease = rand() % 4;
17469 		lvlIncrease = std::max(0, lvlIncrease - 1);
17470 		stats.LVL += std::max(0, lvlIncrease - 1); // increase by 1 or 2 50%, else stay same.
17471 	}
17472 	//messagePlayer(0, "Set stats to: ");
17473 	//messagePlayer(0, "MAXHP: %d", stats.MAXHP);
17474 	//messagePlayer(0, "HP: %d", stats.HP);
17475 	//messagePlayer(0, "MAXMP: %d", stats.MAXMP);
17476 	//messagePlayer(0, "MP: %d", stats.MP);
17477 	//messagePlayer(0, "Str: %d", stats.STR);
17478 	//messagePlayer(0, "Dex: %d", stats.DEX);
17479 	//messagePlayer(0, "Con: %d", stats.CON);
17480 	//messagePlayer(0, "Int: %d", stats.INT);
17481 	//messagePlayer(0, "Per: %d", stats.PER);
17482 	//messagePlayer(0, "Chr: %d", stats.CHR);
17483 	//messagePlayer(0, "LVL: %d", stats.LVL);
17484 	//messagePlayer(0, "GOLD: %d", stats.GOLD);
17485 }
17486 
playerEntityMatchesUid(Uint32 uid)17487 int playerEntityMatchesUid(Uint32 uid)
17488 {
17489 	for ( int i = 0; i < MAXPLAYERS; ++i )
17490 	{
17491 		if ( players[i] && players[i]->entity && players[i]->entity->getUID() == uid )
17492 		{
17493 			return i;
17494 		}
17495 	}
17496 
17497 	return -1;
17498 }
17499 
monsterNameIsGeneric(Stat & monsterStats)17500 bool monsterNameIsGeneric(Stat& monsterStats)
17501 {
17502 	if ( monsterStats.MISC_FLAGS[STAT_FLAG_MONSTER_NAME_GENERIC] == 1 )
17503 	{
17504 		return true;
17505 	}
17506 	if ( strstr(monsterStats.name, "lesser")
17507 		|| strstr(monsterStats.name, "young")
17508 		|| strstr(monsterStats.name, "enslaved")
17509 		|| strstr(monsterStats.name, "damaged")
17510 		|| strstr(monsterStats.name, "corrupted")
17511 		|| strstr(monsterStats.name, "cultist")
17512 		|| strstr(monsterStats.name, "knight")
17513 		|| strstr(monsterStats.name, "sentinel")
17514 		|| strstr(monsterStats.name, "mage")
17515 		|| strstr(monsterStats.name, "inner")
17516 		|| strstr(monsterStats.name, "training")
17517 		|| strstr(monsterStats.name, "Training")
17518 		|| strstr(monsterStats.name, "Mysterious") )
17519 	{
17520 		// If true, pretend the monster doesn't have a name and use the generic message "You hit the lesser skeleton!"
17521 		return true;
17522 	}
17523 	return false;
17524 }
17525 
addEntity(Entity & entity)17526 node_t* TileEntityListHandler::addEntity(Entity& entity)
17527 {
17528 	if ( entity.myTileListNode )
17529 	{
17530 		return nullptr;
17531 	}
17532 
17533 	if ( entity.getUID() == -3 )
17534 	{
17535 		return nullptr;
17536 	}
17537 
17538 	int x = (static_cast<int>(entity.x) >> 4);
17539 	int y = (static_cast<int>(entity.y) >> 4);
17540 	if ( x >= 0 && x < kMaxMapDimension && y >= 0 && y < kMaxMapDimension )
17541 	{
17542 		//messagePlayer(0, "added at %d, %d", x, y);
17543 		entity.myTileListNode = list_AddNodeLast(&TileEntityList.gridEntities[x][y]);
17544 		entity.myTileListNode->element = &entity;
17545 		entity.myTileListNode->deconstructor = &emptyDeconstructor;
17546 		entity.myTileListNode->size = sizeof(Entity);
17547 		return entity.myTileListNode;
17548 	}
17549 
17550 	return nullptr;
17551 }
17552 
updateEntity(Entity & entity)17553 node_t* TileEntityListHandler::updateEntity(Entity& entity)
17554 {
17555 	if ( !entity.myTileListNode )
17556 	{
17557 		return nullptr;
17558 	}
17559 
17560 	int x = (static_cast<int>(entity.x) >> 4);
17561 	int y = (static_cast<int>(entity.y) >> 4);
17562 	if ( x >= 0 && x < kMaxMapDimension && y >= 0 && y < kMaxMapDimension )
17563 	{
17564 		list_RemoveNode(entity.myTileListNode);
17565 		entity.myTileListNode = list_AddNodeLast(&TileEntityList.gridEntities[x][y]);
17566 		entity.myTileListNode->element = &entity;
17567 		entity.myTileListNode->deconstructor = &emptyDeconstructor;
17568 		entity.myTileListNode->size = sizeof(Entity);
17569 		return entity.myTileListNode;
17570 	}
17571 
17572 	return nullptr;
17573 }
17574 
clearTile(int x,int y)17575 void TileEntityListHandler::clearTile(int x, int y)
17576 {
17577 	list_FreeAll(&gridEntities[x][y]);
17578 }
17579 
emptyGridEntities()17580 void TileEntityListHandler::emptyGridEntities()
17581 {
17582 	for ( int i = 0; i < kMaxMapDimension; ++i )
17583 	{
17584 		for ( int j = 0; j < kMaxMapDimension; ++j )
17585 		{
17586 			clearTile(i, j);
17587 		}
17588 	}
17589 }
17590 
getTileList(int x,int y)17591 list_t* TileEntityListHandler::getTileList(int x, int y)
17592 {
17593 	if ( x >= 0 && x < kMaxMapDimension && y >= 0 && y < kMaxMapDimension )
17594 	{
17595 		return &gridEntities[x][y];
17596 	}
17597 	return nullptr;
17598 }
17599 
17600 /* returns list of entities within a radius, e.g 1 radius is a 3x3 area around given center. */
getEntitiesWithinRadius(int u,int v,int radius)17601 std::vector<list_t*> TileEntityListHandler::getEntitiesWithinRadius(int u, int v, int radius)
17602 {
17603 	std::vector<list_t*> return_val;
17604 
17605 	for ( int i = u - radius; i <= u + radius; ++i )
17606 	{
17607 		for ( int j = v - radius; j <= v + radius; ++j )
17608 		{
17609 			list_t* list = getTileList(i, j);
17610 			if ( list )
17611 			{
17612 				return_val.push_back(list);
17613 			}
17614 		}
17615 	}
17616 
17617 	return return_val;
17618 }
17619 
17620 /* returns list of entities within a radius around entity, e.g 1 radius is a 3x3 area around entity. */
getEntitiesWithinRadiusAroundEntity(Entity * entity,int radius)17621 std::vector<list_t*> TileEntityListHandler::getEntitiesWithinRadiusAroundEntity(Entity* entity, int radius)
17622 {
17623 	int u = static_cast<int>(entity->x) >> 4;
17624 	int v = static_cast<int>(entity->y) >> 4;
17625 	return getEntitiesWithinRadius(u, v, radius);
17626 }
17627 
setHumanoidLimbOffset(Entity * limb,Monster race,int limbType)17628 void Entity::setHumanoidLimbOffset(Entity* limb, Monster race, int limbType)
17629 {
17630 	if ( !limb )
17631 	{
17632 		return;
17633 	}
17634 	if ( limbType == LIMB_HUMANOID_TORSO )
17635 	{
17636 		limb->scalez = 1.f; // reset this scale incase something modifies this.
17637 	}
17638 	switch ( race )
17639 	{
17640 		case CREATURE_IMP:
17641 			if ( limbType == LIMB_HUMANOID_TORSO )
17642 			{
17643 				limb->x -= 2 * cos(this->yaw);
17644 				limb->y -= 2 * sin(this->yaw);
17645 				limb->z += 2.75;
17646 				limb->focalz -= 0.25;
17647 			}
17648 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17649 			{
17650 				limb->x += 1 * cos(this->yaw + PI / 2);
17651 				limb->y += 1 * sin(this->yaw + PI / 2);
17652 				limb->z += 6;
17653 			}
17654 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17655 			{
17656 				limb->x -= 1 * cos(this->yaw + PI / 2);
17657 				limb->y -= 1 * sin(this->yaw + PI / 2);
17658 				limb->z += 6;
17659 			}
17660 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
17661 			{
17662 				limb->x += 3 * cos(this->yaw + PI / 2) - 1 * cos(this->yaw);
17663 				limb->y += 3 * sin(this->yaw + PI / 2) - 1 * sin(this->yaw);
17664 				limb->z += 1;
17665 			}
17666 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
17667 			{
17668 				limb->x -= 3 * cos(this->yaw + PI / 2) + 1 * cos(this->yaw);
17669 				limb->y -= 3 * sin(this->yaw + PI / 2) + 1 * sin(this->yaw);
17670 				limb->z += 1;
17671 			}
17672 			break;
17673 		case HUMAN:
17674 		case VAMPIRE:
17675 			if ( limbType == LIMB_HUMANOID_TORSO )
17676 			{
17677 				limb->x -= .25 * cos(this->yaw);
17678 				limb->y -= .25 * sin(this->yaw);
17679 				limb->z += 2.5;
17680 			}
17681 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17682 			{
17683 				limb->x += 1 * cos(this->yaw + PI / 2) + .25 * cos(this->yaw);
17684 				limb->y += 1 * sin(this->yaw + PI / 2) + .25 * sin(this->yaw);
17685 				limb->z += 5;
17686 				if ( this->z >= 1.4 && this->z <= 1.6 )
17687 				{
17688 					limb->yaw += PI / 8;
17689 					limb->pitch = -PI / 2;
17690 				}
17691 			}
17692 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17693 			{
17694 				limb->x -= 1 * cos(this->yaw + PI / 2) - .25 * cos(this->yaw);
17695 				limb->y -= 1 * sin(this->yaw + PI / 2) - .25 * sin(this->yaw);
17696 				limb->z += 5;
17697 				if ( this->z >= 1.4 && this->z <= 1.6 )
17698 				{
17699 					limb->yaw -= PI / 8;
17700 					limb->pitch = -PI / 2;
17701 				}
17702 			}
17703 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
17704 			{
17705 				limb->x += 2.5 * cos(this->yaw + PI / 2) - .20 * cos(this->yaw);
17706 				limb->y += 2.5 * sin(this->yaw + PI / 2) - .20 * sin(this->yaw);
17707 				limb->z += 1.5;
17708 				if ( this->z >= 1.4 && this->z <= 1.6 )
17709 				{
17710 					limb->pitch = 0;
17711 				}
17712 			}
17713 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
17714 			{
17715 				limb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
17716 				limb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
17717 				limb->z += 1.5;
17718 				if ( this->z >= 1.4 && this->z <= 1.6 )
17719 				{
17720 					limb->pitch = 0;
17721 				}
17722 			}
17723 			break;
17724 		case TROLL:
17725 			if ( limbType == LIMB_HUMANOID_TORSO )
17726 			{
17727 				limb->x -= .5 * cos(this->yaw);
17728 				limb->y -= .5 * sin(this->yaw);
17729 				limb->z += 2.25;
17730 			}
17731 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17732 			{
17733 				limb->x += 2 * cos(this->yaw + PI / 2) - 0.75 * cos(this->yaw);
17734 				limb->y += 2 * sin(this->yaw + PI / 2) - 0.75 * sin(this->yaw);
17735 				limb->z += 5;
17736 				if ( this->z >= 1.4 && this->z <= 1.6 )
17737 				{
17738 					limb->yaw += PI / 8;
17739 					limb->pitch = -PI / 2;
17740 				}
17741 				else if ( limb->pitch <= -PI / 3 )
17742 				{
17743 					limb->pitch = 0;
17744 				}
17745 			}
17746 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17747 			{
17748 				limb->x -= 2 * cos(this->yaw + PI / 2) + 0.75 * cos(this->yaw);
17749 				limb->y -= 2 * sin(this->yaw + PI / 2) + 0.75 * sin(this->yaw);
17750 				limb->z += 5;
17751 				if ( this->z >= 1.4 && this->z <= 1.6 )
17752 				{
17753 					limb->yaw -= PI / 8;
17754 					limb->pitch = -PI / 2;
17755 				}
17756 				else if ( limb->pitch <= -PI / 3 )
17757 				{
17758 					limb->pitch = 0;
17759 				}
17760 			}
17761 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
17762 			{
17763 				limb->x += 3.5 * cos(this->yaw + PI / 2) - 1 * cos(this->yaw);
17764 				limb->y += 3.5 * sin(this->yaw + PI / 2) - 1 * sin(this->yaw);
17765 				limb->z += .1;
17766 				//limb->yaw += MONSTER_WEAPONYAW;
17767 				if ( this->z >= 1.4 && this->z <= 1.6 )
17768 				{
17769 					limb->pitch = 0;
17770 				}
17771 			}
17772 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
17773 			{
17774 				limb->x -= 3.5 * cos(this->yaw + PI / 2) + 1 * cos(this->yaw);
17775 				limb->y -= 3.5 * sin(this->yaw + PI / 2) + 1 * sin(this->yaw);
17776 				limb->z += .1;
17777 				if ( this->z >= 1.4 && this->z <= 1.6 )
17778 				{
17779 					limb->pitch = 0;
17780 				}
17781 			}
17782 			break;
17783 		case SKELETON:
17784 		case AUTOMATON:
17785 			if ( limbType == LIMB_HUMANOID_TORSO )
17786 			{
17787 				limb->x -= .25 * cos(this->yaw);
17788 				limb->y -= .25 * sin(this->yaw);
17789 				limb->z += 2;
17790 				if ( limb->sprite == items[WIZARD_DOUBLET].index
17791 					|| limb->sprite == items[HEALER_DOUBLET].index
17792 					|| limb->sprite == items[TUNIC].index
17793 					|| limb->sprite == items[TUNIC].index + 1 )
17794 				{
17795 					limb->z += 0.15;
17796 					limb->scalez = 0.9;
17797 					limb->x += .1 * cos(this->yaw);
17798 					limb->y += .1 * sin(this->yaw);
17799 				}
17800 				else
17801 				{
17802 					limb->scalez = 1.f;
17803 				}
17804 			}
17805 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17806 			{
17807 				limb->x += 1 * cos(this->yaw + PI / 2) + .25 * cos(this->yaw);
17808 				limb->y += 1 * sin(this->yaw + PI / 2) + .25 * sin(this->yaw);
17809 				limb->z += 4;
17810 				if ( this->z >= 1.9 && this->z <= 2.1 )
17811 				{
17812 					limb->yaw += PI / 8;
17813 					limb->pitch = -PI / 2;
17814 				}
17815 				else if ( limb->pitch <= -PI / 3 )
17816 				{
17817 					limb->pitch = 0;
17818 				}
17819 			}
17820 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17821 			{
17822 				limb->x -= 1 * cos(this->yaw + PI / 2) - .25 * cos(this->yaw);
17823 				limb->y -= 1 * sin(this->yaw + PI / 2) - .25 * sin(this->yaw);
17824 				limb->z += 4;
17825 				if ( this->z >= 1.9 && this->z <= 2.1 )
17826 				{
17827 					limb->yaw -= PI / 8;
17828 					limb->pitch = -PI / 2;
17829 				}
17830 				else if ( limb->pitch <= -PI / 3 )
17831 				{
17832 					limb->pitch = 0;
17833 				}
17834 			}
17835 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
17836 			{
17837 				if ( limb->sprite != 689 && limb->sprite != 691
17838 					&& limb->sprite != 233 && limb->sprite != 234
17839 					&& limb->sprite != 745 && limb->sprite != 747
17840 					&& limb->sprite != 471 && limb->sprite != 472 )
17841 				{
17842 					// wearing gloves (not default arms), position tighter to body.
17843 					limb->x += 1.75 * cos(this->yaw + PI / 2) - .20 * cos(this->yaw);
17844 					limb->y += 1.75 * sin(this->yaw + PI / 2) - .20 * sin(this->yaw);
17845 				}
17846 				else
17847 				{
17848 					limb->x += 2.f * cos(this->yaw + PI / 2) - .20 * cos(this->yaw);
17849 					limb->y += 2.f * sin(this->yaw + PI / 2) - .20 * sin(this->yaw);
17850 				}
17851 				limb->z += .6;
17852 				if ( this->z >= 1.9 && this->z <= 2.1 )
17853 				{
17854 					limb->pitch = 0;
17855 				}
17856 			}
17857 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
17858 			{
17859 				if ( limb->sprite != 688 && limb->sprite != 690
17860 					&& limb->sprite != 231 && limb->sprite != 232
17861 					&& limb->sprite != 744 && limb->sprite != 746
17862 					&& limb->sprite != 469 && limb->sprite != 470 )
17863 				{
17864 					// wearing gloves (not default arms), position tighter to body.
17865 					limb->x -= 1.75 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
17866 					limb->y -= 1.75 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
17867 				}
17868 				else
17869 				{
17870 					limb->x -= 2.f * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
17871 					limb->y -= 2.f * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
17872 				}
17873 				limb->z += .6;
17874 				if ( this->z >= 1.9 && this->z <= 2.1 )
17875 				{
17876 					limb->pitch = 0;
17877 				}
17878 			}
17879 			break;
17880 		case GOBLIN:
17881 		case GOATMAN:
17882 		case INSECTOID:
17883 			if ( limbType == LIMB_HUMANOID_TORSO )
17884 			{
17885 				limb->x -= .25 * cos(this->yaw);
17886 				limb->y -= .25 * sin(this->yaw);
17887 				limb->z += 2;
17888 				if ( race == INSECTOID )
17889 				{
17890 					if ( limb->sprite != 727 && limb->sprite != 458 && limb->sprite != 761 )
17891 					{
17892 						// wearing armor, offset by 1.
17893 						limb->z -= 1;
17894 					}
17895 				}
17896 
17897 				/*if ( limb->sprite == items[WIZARD_DOUBLET].index
17898 					|| limb->sprite == items[HEALER_DOUBLET].index
17899 					|| limb->sprite == items[TUNIC].index
17900 					|| limb->sprite == items[TUNIC].index + 1 )
17901 				{
17902 					limb->z += 0.25;
17903 				}*/
17904 			}
17905 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17906 			{
17907 				limb->x += 1 * cos(this->yaw + PI / 2) + .25 * cos(this->yaw);
17908 				limb->y += 1 * sin(this->yaw + PI / 2) + .25 * sin(this->yaw);
17909 				limb->z += 4;
17910 				if ( this->z >= 2.4 && this->z <= 2.6 )
17911 				{
17912 					limb->yaw += PI / 8;
17913 					limb->pitch = -PI / 2;
17914 				}
17915 				else if ( limb->pitch <= -PI / 3 )
17916 				{
17917 					limb->pitch = 0;
17918 				}
17919 			}
17920 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17921 			{
17922 				limb->x -= 1 * cos(this->yaw + PI / 2) - .25 * cos(this->yaw);
17923 				limb->y -= 1 * sin(this->yaw + PI / 2) - .25 * sin(this->yaw);
17924 				limb->z += 4;
17925 				if ( this->z >= 2.4 && this->z <= 2.6 )
17926 				{
17927 					limb->yaw -= PI / 8;
17928 					limb->pitch = -PI / 2;
17929 				}
17930 				else if ( limb->pitch <= -PI / 3 )
17931 				{
17932 					limb->pitch = 0;
17933 				}
17934 			}
17935 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
17936 			{
17937 				limb->x += 2.5 * cos(this->yaw + PI / 2) - .20 * cos(this->yaw);
17938 				limb->y += 2.5 * sin(this->yaw + PI / 2) - .20 * sin(this->yaw);
17939 				limb->z += .5;
17940 				if ( this->z >= 2.4 && this->z <= 2.6 )
17941 				{
17942 					limb->pitch = 0;
17943 				}
17944 			}
17945 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
17946 			{
17947 				limb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
17948 				limb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
17949 				limb->z += .5;
17950 				if ( this->z >= 2.4 && this->z <= 2.6 )
17951 				{
17952 					limb->pitch = 0;
17953 				}
17954 			}
17955 			break;
17956 		case INCUBUS:
17957 		case SUCCUBUS:
17958 			if ( limbType == LIMB_HUMANOID_TORSO )
17959 			{
17960 				limb->x -= .5 * cos(this->yaw);
17961 				limb->y -= .5 * sin(this->yaw);
17962 				limb->z += 2.5;
17963 
17964 				if ( limb->sprite == items[WIZARD_DOUBLET].index
17965 					|| limb->sprite == items[HEALER_DOUBLET].index
17966 					|| limb->sprite == items[TUNIC].index
17967 					|| limb->sprite == items[TUNIC].index + 1 )
17968 				{
17969 					limb->z += 0.5;
17970 				}
17971 			}
17972 			else if ( limbType == LIMB_HUMANOID_RIGHTLEG )
17973 			{
17974 				limb->x += 1 * cos(this->yaw + PI / 2) - .75 * cos(this->yaw);
17975 				limb->y += 1 * sin(this->yaw + PI / 2) - .75 * sin(this->yaw);
17976 				limb->z += 5;
17977 				if ( this->z >= 1.4 && this->z <= 1.6 )
17978 				{
17979 					limb->yaw += PI / 8;
17980 					limb->pitch = -PI / 2;
17981 				}
17982 				else if ( limb->pitch <= -PI / 3 )
17983 				{
17984 					limb->pitch = 0;
17985 				}
17986 			}
17987 			else if ( limbType == LIMB_HUMANOID_LEFTLEG )
17988 			{
17989 				limb->x -= 1 * cos(this->yaw + PI / 2) + .75 * cos(this->yaw);
17990 				limb->y -= 1 * sin(this->yaw + PI / 2) + .75 * sin(this->yaw);
17991 				limb->z += 5;
17992 				if ( this->z >= 1.4 && this->z <= 1.6 )
17993 				{
17994 					limb->yaw -= PI / 8;
17995 					limb->pitch = -PI / 2;
17996 				}
17997 				else if ( limb->pitch <= -PI / 3 )
17998 				{
17999 					limb->pitch = 0;
18000 				}
18001 			}
18002 			else if ( limbType == LIMB_HUMANOID_RIGHTARM )
18003 			{
18004 				limb->x += 2.5 * cos(this->yaw + PI / 2) - .20 * cos(this->yaw);
18005 				limb->y += 2.5 * sin(this->yaw + PI / 2) - .20 * sin(this->yaw);
18006 				limb->z += .5;
18007 				if ( this->z >= 1.4 && this->z <= 1.6 )
18008 				{
18009 					limb->pitch = 0;
18010 				}
18011 			}
18012 			else if ( limbType == LIMB_HUMANOID_LEFTARM )
18013 			{
18014 				limb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
18015 				limb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
18016 				limb->z += .5;
18017 				if ( this->z >= 1.4 && this->z <= 1.6 )
18018 				{
18019 					limb->pitch = 0;
18020 				}
18021 			}
18022 			break;
18023 		default:
18024 			break;
18025 	}
18026 }
18027 
handleHumanoidShieldLimb(Entity * shieldLimb,Entity * shieldArmLimb)18028 void Entity::handleHumanoidShieldLimb(Entity* shieldLimb, Entity* shieldArmLimb)
18029 {
18030 	if ( !shieldLimb || !shieldArmLimb )
18031 	{
18032 		return;
18033 	}
18034 
18035 	int race = this->getMonsterTypeFromSprite();
18036 	int player = -1;
18037 	if ( this->behavior == &actPlayer )
18038 	{
18039 		player = this->skill[2];
18040 	}
18041 	Entity* flameEntity = nullptr;
18042 
18043 	shieldLimb->focalx = limbs[race][7][0];
18044 	shieldLimb->focaly = limbs[race][7][1];
18045 	shieldLimb->focalz = limbs[race][7][2];
18046 
18047 	shieldLimb->scalex = 1.f;
18048 	shieldLimb->scaley = 1.f;
18049 	shieldLimb->scalez = 1.f;
18050 
18051 	switch ( race )
18052 	{
18053 		case CREATURE_IMP:
18054 			shieldLimb->focalx = limbs[race][8][0];
18055 			shieldLimb->focaly = limbs[race][8][1];
18056 			shieldLimb->focalz = limbs[race][8][2];
18057 
18058 			shieldLimb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
18059 			shieldLimb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
18060 			shieldLimb->z += 2.5;
18061 			shieldLimb->yaw = shieldArmLimb->yaw;
18062 			shieldLimb->roll = 0;
18063 			shieldLimb->pitch = 0;
18064 
18065 			if ( shieldLimb->sprite >= items[SPELLBOOK_LIGHT].index
18066 				&& shieldLimb->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) )
18067 			{
18068 				shieldLimb->pitch = shieldArmLimb->pitch - .35 + 3 * PI / 2;
18069 				shieldLimb->yaw += PI / 6;
18070 				shieldLimb->focalx -= 4;
18071 				shieldLimb->focalz += .5;
18072 				shieldLimb->x += 0.5 * cos(this->yaw + PI / 2) - 1 * cos(this->yaw);
18073 				shieldLimb->y += 0.5 * sin(this->yaw + PI / 2) - 1 * sin(this->yaw);
18074 				shieldLimb->z -= 0.5;
18075 				shieldLimb->scalex = 0.8;
18076 				shieldLimb->scaley = 0.8;
18077 				shieldLimb->scalez = 0.8;
18078 			}
18079 			break;
18080 		case HUMAN:
18081 		case VAMPIRE:
18082 			shieldLimb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
18083 			shieldLimb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
18084 			shieldLimb->z += 2.5;
18085 			shieldLimb->yaw = shieldArmLimb->yaw;
18086 			shieldLimb->roll = 0;
18087 			shieldLimb->pitch = 0;
18088 
18089 			if ( shieldLimb->sprite == items[TOOL_TORCH].index )
18090 			{
18091 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18092 				flameEntity->x += 2 * cos(shieldArmLimb->yaw);
18093 				flameEntity->y += 2 * sin(shieldArmLimb->yaw);
18094 				flameEntity->z -= 2;
18095 			}
18096 			else if ( shieldLimb->sprite == items[TOOL_CRYSTALSHARD].index )
18097 			{
18098 				flameEntity = spawnFlame(shieldLimb, SPRITE_CRYSTALFLAME);
18099 				flameEntity->x += 2 * cos(shieldArmLimb->yaw);
18100 				flameEntity->y += 2 * sin(shieldArmLimb->yaw);
18101 				flameEntity->z -= 2;
18102 			}
18103 			else if ( shieldLimb->sprite == items[TOOL_LANTERN].index )
18104 			{
18105 				shieldLimb->z += 2;
18106 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18107 				flameEntity->x += 2 * cos(shieldArmLimb->yaw);
18108 				flameEntity->y += 2 * sin(shieldArmLimb->yaw);
18109 				flameEntity->z += 1;
18110 			}
18111 			else if ( itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) )
18112 			{
18113 				shieldLimb->focalz += 3;
18114 				shieldLimb->scalex = 1.05;
18115 				shieldLimb->x -= -0.25 * cos(this->yaw + PI / 2) + 1.25 * cos(this->yaw);
18116 				shieldLimb->y -= -0.25 * sin(this->yaw + PI / 2) + 1.25 * sin(this->yaw);
18117 				shieldLimb->z += -1.28;
18118 			}
18119 			else if ( shieldLimb->sprite >= items[SPELLBOOK_LIGHT].index
18120 				&& shieldLimb->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) )
18121 			{
18122 				shieldLimb->pitch = shieldArmLimb->pitch - .25 + 3 * PI / 2;
18123 				shieldLimb->yaw += PI / 6;
18124 				shieldLimb->focalx -= 4;
18125 				shieldLimb->focalz += .5;
18126 				shieldLimb->x += 0.5 * cos(this->yaw + PI / 2) + .5 * cos(this->yaw);
18127 				shieldLimb->y += 0.5 * sin(this->yaw + PI / 2) + .5 * sin(this->yaw);
18128 				shieldLimb->z -= 1;
18129 				shieldLimb->scalex = 0.8;
18130 				shieldLimb->scaley = 0.8;
18131 				shieldLimb->scalez = 0.8;
18132 			}
18133 
18134 			if ( this->fskill[8] > PI / 32 ) //MONSTER_SHIELDYAW and PLAYER_SHIELDYAW defending animation
18135 			{
18136 				if ( shieldLimb->sprite != items[TOOL_TORCH].index
18137 					&& shieldLimb->sprite != items[TOOL_LANTERN].index
18138 					&& shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index )
18139 				{
18140 					// shield, so rotate a little.
18141 					shieldLimb->roll += PI / 64;
18142 				}
18143 				else
18144 				{
18145 					shieldLimb->x += 0.25 * cos(this->yaw);
18146 					shieldLimb->y += 0.25 * sin(this->yaw);
18147 					shieldLimb->pitch += PI / 16;
18148 					if ( flameEntity )
18149 					{
18150 						flameEntity->x += 0.75 * cos(shieldArmLimb->yaw);
18151 						flameEntity->y += 0.75 * sin(shieldArmLimb->yaw);
18152 					}
18153 				}
18154 			}
18155 			break;
18156 		case SKELETON:
18157 		case AUTOMATON:
18158 			shieldLimb->x -= 3.f * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
18159 			shieldLimb->y -= 3.f * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
18160 			shieldLimb->z += 2.5;
18161 			shieldLimb->yaw = shieldArmLimb->yaw;
18162 			shieldLimb->roll = 0;
18163 			shieldLimb->pitch = 0;
18164 
18165 			if ( shieldLimb->sprite != items[TOOL_TORCH].index && shieldLimb->sprite != items[TOOL_LANTERN].index && shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index )
18166 			{
18167 				shieldLimb->focalx = limbs[race][7][0] - 0.65;
18168 				shieldLimb->focaly = limbs[race][7][1];
18169 				shieldLimb->focalz = limbs[race][7][2];
18170 			}
18171 			if ( itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) )
18172 			{
18173 				shieldLimb->focalz += 3;
18174 				shieldLimb->z += -1.78;
18175 				shieldLimb->scalex = 1.05;
18176 				if ( race == SKELETON )
18177 				{
18178 					shieldLimb->x -= -0.2 * cos(this->yaw + PI / 2) + 1.6 * cos(this->yaw);
18179 					shieldLimb->y -= -0.2 * sin(this->yaw + PI / 2) + 1.6 * sin(this->yaw);
18180 					if ( behavior == &actMonster )
18181 					{
18182 						// additional offsets for skellie monsters since the player offsets slightly different
18183 						shieldLimb->x += -0.25 * cos(this->yaw);
18184 						shieldLimb->y += -0.25 * sin(this->yaw);
18185 						shieldLimb->z += -0.25;
18186 					}
18187 				}
18188 				else if ( race == AUTOMATON )
18189 				{
18190 					shieldLimb->x -= 0.1 * cos(this->yaw + PI / 2) + 2.1 * cos(this->yaw);
18191 					shieldLimb->y -= 0.1 * sin(this->yaw + PI / 2) + 2.1 * sin(this->yaw);
18192 					shieldLimb->z += 0.1;
18193 
18194 					// originally shieldLimb->x/y was -1.1 * cos or sin but it fell outside the player bounding box and was visible in hud
18195 					// so shieldLimb->x/y is -0.1 and -1 is added to focaly.
18196 					shieldLimb->focaly -= 1;
18197 				}
18198 			}
18199 
18200 			if ( shieldLimb->sprite == items[TOOL_TORCH].index )
18201 			{
18202 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18203 				flameEntity->x += 2.5 * cos(shieldLimb->yaw + PI / 16);
18204 				flameEntity->y += 2.5 * sin(shieldLimb->yaw + PI / 16);
18205 				flameEntity->z -= 2;
18206 			}
18207 			else if ( shieldLimb->sprite == items[TOOL_CRYSTALSHARD].index )
18208 			{
18209 				flameEntity = spawnFlame(shieldLimb, SPRITE_CRYSTALFLAME);
18210 				flameEntity->x += 2.5 * cos(shieldLimb->yaw + PI / 16);
18211 				flameEntity->y += 2.5 * sin(shieldLimb->yaw + PI / 16);
18212 				flameEntity->z -= 2;
18213 			}
18214 			else if ( shieldLimb->sprite == items[TOOL_LANTERN].index )
18215 			{
18216 				shieldLimb->z += 2;
18217 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18218 				flameEntity->x += 2.5 * cos(shieldLimb->yaw);
18219 				flameEntity->y += 2.5 * sin(shieldLimb->yaw);
18220 				flameEntity->z += 1;
18221 			}
18222 			else if ( shieldLimb->sprite >= items[SPELLBOOK_LIGHT].index
18223 				&& shieldLimb->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) )
18224 			{
18225 				shieldLimb->pitch = shieldArmLimb->pitch - .25 + 3 * PI / 2;
18226 				shieldLimb->yaw += PI / 16;
18227 				shieldLimb->focalx -= 4;
18228 				if ( race == SKELETON )
18229 				{
18230 					shieldLimb->focaly += .5;
18231 				}
18232 				else if ( race == AUTOMATON )
18233 				{
18234 					shieldLimb->focaly += .5;
18235 					shieldLimb->focalz -= .5;
18236 				}
18237 				shieldLimb->focalz += .5;
18238 				shieldLimb->x += 0.5 * cos(this->yaw + PI / 2) + .5 * cos(this->yaw);
18239 				shieldLimb->y += 0.5 * sin(this->yaw + PI / 2) + .5 * sin(this->yaw);
18240 				shieldLimb->z -= 1;
18241 				shieldLimb->scalex = 0.8;
18242 				shieldLimb->scaley = 0.8;
18243 				shieldLimb->scalez = 0.8;
18244 			}
18245 			else
18246 			{
18247 				if ( shieldLimb->sprite != items[TOOL_TINKERING_KIT].index )
18248 				{
18249 					// automaton arm clips through shield. extend shield out more.
18250 					if ( race == AUTOMATON )
18251 					{
18252 						shieldLimb->focalx += 0.75;
18253 						shieldLimb->focaly += 1.25;
18254 					}
18255 				}
18256 			}
18257 
18258 			if ( this->fskill[8] > PI / 32 ) //MONSTER_SHIELDYAW and PLAYER_SHIELDYAW defending animation
18259 			{
18260 				if ( shieldLimb->sprite != items[TOOL_TORCH].index
18261 					&& shieldLimb->sprite != items[TOOL_LANTERN].index
18262 					&& shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index )
18263 				{
18264 					// shield, so rotate a little.
18265 					shieldLimb->roll += PI / 64;
18266 				}
18267 				else
18268 				{
18269 					shieldLimb->x += 0.25 * cos(this->yaw);
18270 					shieldLimb->y += 0.25 * sin(this->yaw);
18271 					shieldLimb->pitch += PI / 16;
18272 					if ( flameEntity )
18273 					{
18274 						flameEntity->x += 0.75 * cos(shieldArmLimb->yaw);
18275 						flameEntity->y += 0.75 * sin(shieldArmLimb->yaw);
18276 					}
18277 				}
18278 			}
18279 			else
18280 			{
18281 				if ( !shieldLimb->flags[INVISIBLE] )
18282 				{
18283 					if ( !itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) )
18284 					{
18285 						shieldArmLimb->yaw -= PI / 8;
18286 					}
18287 				}
18288 			}
18289 			break;
18290 		case GOBLIN:
18291 		case GOATMAN:
18292 		case INSECTOID:
18293 		case INCUBUS:
18294 		case SUCCUBUS:
18295 			shieldLimb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw);
18296 			shieldLimb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw);
18297 			shieldLimb->z += 2.5;
18298 			shieldLimb->yaw = shieldArmLimb->yaw;
18299 			shieldLimb->roll = 0;
18300 			shieldLimb->pitch = 0;
18301 
18302 			/*if ( shieldLimb->sprite != items[TOOL_TORCH].index && shieldLimb->sprite != items[TOOL_LANTERN].index && shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index )
18303 			{
18304 				shieldLimb->focalx = limbs[race][7][0];
18305 				shieldLimb->focaly = limbs[race][7][1];
18306 				shieldLimb->focalz = limbs[race][7][2];
18307 			}
18308 			else
18309 			{
18310 				shieldLimb->focalx = limbs[race][7][0] - 0.5;
18311 				shieldLimb->focaly = limbs[race][7][1] - 1;
18312 				shieldLimb->focalz = limbs[race][7][2];
18313 			}*/
18314 
18315 			if ( shieldLimb->sprite == items[TOOL_TORCH].index )
18316 			{
18317 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18318 				flameEntity->x += 2 * cos(shieldLimb->yaw);
18319 				flameEntity->y += 2 * sin(shieldLimb->yaw);
18320 				flameEntity->z -= 2;
18321 			}
18322 			else if ( shieldLimb->sprite == items[TOOL_CRYSTALSHARD].index )
18323 			{
18324 				flameEntity = spawnFlame(shieldLimb, SPRITE_CRYSTALFLAME);
18325 				flameEntity->x += 2 * cos(shieldLimb->yaw);
18326 				flameEntity->y += 2 * sin(shieldLimb->yaw);
18327 				flameEntity->z -= 2;
18328 			}
18329 			else if ( shieldLimb->sprite == items[TOOL_LANTERN].index )
18330 			{
18331 				shieldLimb->z += 2;
18332 				flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME);
18333 				flameEntity->x += 2 * cos(shieldLimb->yaw);
18334 				flameEntity->y += 2 * sin(shieldLimb->yaw);
18335 				flameEntity->z += 1;
18336 			}
18337 			else if ( shieldLimb->sprite >= items[SPELLBOOK_LIGHT].index
18338 				&& shieldLimb->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) )
18339 			{
18340 				shieldLimb->pitch = shieldArmLimb->pitch - .25 + 3 * PI / 2;
18341 				shieldLimb->yaw += PI / 6;
18342 				shieldLimb->focalx -= 4;
18343 				shieldLimb->focalz += .5;
18344 				if ( race == INCUBUS || race == SUCCUBUS )
18345 				{
18346 					shieldLimb->focalz -= 1.5;
18347 				}
18348 				shieldLimb->x += 0.5 * cos(this->yaw + PI / 2) + .5 * cos(this->yaw);
18349 				shieldLimb->y += 0.5 * sin(this->yaw + PI / 2) + .5 * sin(this->yaw);
18350 				shieldLimb->z -= 1;
18351 				shieldLimb->scalex = 0.8;
18352 				shieldLimb->scaley = 0.8;
18353 				shieldLimb->scalez = 0.8;
18354 			}
18355 			else if ( itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) )
18356 			{
18357 				shieldLimb->scalex = 1.05;
18358 				//shieldLimb->scalez = 1.05;
18359 				shieldLimb->focalz += 3;
18360 				if ( race == INCUBUS || race == SUCCUBUS )
18361 				{
18362 					if ( race == SUCCUBUS )
18363 					{
18364 						shieldLimb->x -= 0.05 * cos(this->yaw + PI / 2) + (1.75) * cos(this->yaw);
18365 						shieldLimb->y -= 0.05 * sin(this->yaw + PI / 2) + (1.75) * sin(this->yaw);
18366 					}
18367 					else
18368 					{
18369 						shieldLimb->x -= 0.05 * cos(this->yaw + PI / 2) + (1.5) * cos(this->yaw);
18370 						shieldLimb->y -= 0.05 * sin(this->yaw + PI / 2) + (1.5) * sin(this->yaw);
18371 					}
18372 					shieldLimb->z += -2.28;
18373 				}
18374 				else
18375 				{
18376 					if ( race == GOATMAN )
18377 					{
18378 						shieldLimb->x -= -0.25 * cos(this->yaw + PI / 2) + 1.5 * cos(this->yaw);
18379 						shieldLimb->y -= -0.25 * sin(this->yaw + PI / 2) + 1.5 * sin(this->yaw);
18380 					}
18381 					else if ( race == GOBLIN )
18382 					{
18383 						shieldLimb->x -= -0.25 * cos(this->yaw + PI / 2) + 1.25 * cos(this->yaw);
18384 						shieldLimb->y -= -0.25 * sin(this->yaw + PI / 2) + 1.25 * sin(this->yaw);
18385 					}
18386 					else
18387 					{
18388 						shieldLimb->x -= -0.25 * cos(this->yaw + PI / 2) + 1.0 * cos(this->yaw);
18389 						shieldLimb->y -= -0.25 * sin(this->yaw + PI / 2) + 1.0 * sin(this->yaw);
18390 					}
18391 					shieldLimb->z += -1.78;
18392 				}
18393 			}
18394 
18395 			if ( this->fskill[8] > PI / 32 ) //MONSTER_SHIELDYAW and PLAYER_SHIELDYAW defending animation
18396 			{
18397 				if ( shieldLimb->sprite != items[TOOL_TORCH].index
18398 					&& shieldLimb->sprite != items[TOOL_LANTERN].index
18399 					&& shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index )
18400 				{
18401 					// shield, so rotate a little.
18402 					shieldLimb->roll += PI / 64;
18403 				}
18404 				else
18405 				{
18406 					shieldLimb->x += 0.25 * cos(this->yaw);
18407 					shieldLimb->y += 0.25 * sin(this->yaw);
18408 					shieldLimb->pitch += PI / 16;
18409 					if ( flameEntity )
18410 					{
18411 						flameEntity->x += 0.75 * cos(shieldArmLimb->yaw);
18412 						flameEntity->y += 0.75 * sin(shieldArmLimb->yaw);
18413 					}
18414 				}
18415 			}
18416 			break;
18417 		default:
18418 			break;
18419 	}
18420 
18421 	if ( shieldLimb->sprite == items[TOOL_TINKERING_KIT].index )
18422 	{
18423 		//shieldLimb->pitch = 0;
18424 		shieldLimb->yaw += PI / 6;
18425 		shieldLimb->focalx -= .5;
18426 		shieldLimb->focaly += .25;
18427 		shieldLimb->focalz += 2.25;
18428 		if ( race == INCUBUS || race == SUCCUBUS )
18429 		{
18430 			shieldLimb->focalx -= .5;
18431 			shieldLimb->focaly -= 0.7;
18432 		}
18433 		else if ( race == GOATMAN || race == GOBLIN || race == INSECTOID )
18434 		{
18435 			shieldLimb->focalz -= 0.5;
18436 		}
18437 		else if ( race == AUTOMATON || race == SKELETON )
18438 		{
18439 			shieldLimb->focalz -= 1;
18440 			if ( race == SKELETON )
18441 			{
18442 				shieldLimb->focalx -= 0.5;
18443 			}
18444 			if ( this->fskill[8] > PI / 32 ) //MONSTER_SHIELDYAW and PLAYER_SHIELDYAW defending animation
18445 			{
18446 				shieldLimb->focalx += 2;
18447 			}
18448 			else
18449 			{
18450 				shieldLimb->focalx += 1;
18451 				shieldLimb->focaly -= 0.8;
18452 			}
18453 		}
18454 	}
18455 	else if ( itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) )
18456 	{
18457 		shieldLimb->yaw += PI / 6;
18458 	}
18459 
18460 	if ( flameEntity && player >= 0 )
18461 	{
18462 		if ( player == clientnum )
18463 		{
18464 			flameEntity->flags[GENIUS] = true;
18465 			flameEntity->setUID(-4);
18466 		}
18467 		else
18468 		{
18469 			flameEntity->setUID(-3);
18470 		}
18471 	}
18472 }
18473 
isBossMonster()18474 bool Entity::isBossMonster()
18475 {
18476 	Stat* myStats = getStats();
18477 	if ( myStats )
18478 	{
18479 		if ( myStats->type == MINOTAUR
18480 			|| myStats->type == SHOPKEEPER
18481 			|| myStats->type == SHADOW
18482 			|| myStats->type == LICH
18483 			|| myStats->type == LICH_FIRE
18484 			|| myStats->type == LICH_ICE
18485 			|| myStats->type == DEVIL
18486 			|| (myStats->type == VAMPIRE && !strncmp(myStats->name, "Bram Kindly", 11))
18487 			|| (myStats->type == COCKATRICE && !strncmp(map.name, "Cockatrice Lair", 15))
18488 			)
18489 		{
18490 			return true;
18491 		}
18492 		else
18493 		{
18494 			return false;
18495 		}
18496 	}
18497 	return false;
18498 }
18499 
handleKnockbackDamage(Stat & myStats,Entity * knockedInto)18500 void Entity::handleKnockbackDamage(Stat& myStats, Entity* knockedInto)
18501 {
18502 	if ( knockedInto != NULL && myStats.EFFECTS[EFF_KNOCKBACK] && myStats.HP > 0 )
18503 	{
18504 		int damageOnHit = 5 + rand() % 6;
18505 		if ( knockedInto->behavior == &actDoor )
18506 		{
18507 			playSoundEntity(this, 28, 64);
18508 			this->modHP(-damageOnHit);
18509 			Entity* whoKnockedMe = uidToEntity(this->monsterKnockbackUID);
18510 			if ( myStats.HP <= 0 )
18511 			{
18512 				if ( whoKnockedMe )
18513 				{
18514 					whoKnockedMe->awardXP(this, true, true);
18515 				}
18516 			}
18517 			if ( whoKnockedMe && whoKnockedMe->behavior == &actPlayer )
18518 			{
18519 				steamStatisticUpdateClient(whoKnockedMe->skill[2], STEAM_STAT_TAKE_THIS_OUTSIDE, STEAM_STAT_INT, 1);
18520 			}
18521 			knockedInto->doorHealth = 0;    // smash doors instantly
18522 			playSoundEntity(knockedInto, 28, 64);
18523 			if ( knockedInto->doorHealth <= 0 )
18524 			{
18525 				// set direction of splinters
18526 				if ( !knockedInto->doorDir )
18527 				{
18528 					knockedInto->doorSmacked = (this->x > knockedInto->x);
18529 				}
18530 				else
18531 				{
18532 					knockedInto->doorSmacked = (this->y < knockedInto->y);
18533 				}
18534 			}
18535 		}
18536 		else if ( knockedInto->behavior == &::actFurniture )
18537 		{
18538 			// break it down!
18539 			playSoundEntity(this, 28, 64);
18540 			this->modHP(-damageOnHit);
18541 			if ( myStats.HP <= 0 )
18542 			{
18543 				Entity* whoKnockedMe = uidToEntity(this->monsterKnockbackUID);
18544 				if ( whoKnockedMe )
18545 				{
18546 					whoKnockedMe->awardXP(this, true, true);
18547 				}
18548 			}
18549 			knockedInto->furnitureHealth = 0;    // smash furniture instantly
18550 			playSoundEntity(knockedInto, 28, 64);
18551 		}
18552 	}
18553 }
18554 
setHelmetLimbOffsetWithMask(Entity * helm,Entity * mask)18555 void Entity::setHelmetLimbOffsetWithMask(Entity* helm, Entity* mask)
18556 {
18557 	if ( !helm || !mask )
18558 	{
18559 		return;
18560 	}
18561 
18562 	if ( !mask->flags[INVISIBLE] && !helm->flags[INVISIBLE] )
18563 	{
18564 		helm->scalex = 1.01;
18565 		helm->scaley = 1.01;
18566 		helm->scalez = 1.01;
18567 	}
18568 	else
18569 	{
18570 		mask->scalex = 1.01;
18571 		mask->scaley = 1.01;
18572 		mask->scalez = 1.01;
18573 		return;
18574 	}
18575 
18576 	if ( helm->sprite == items[LEATHER_HELM].index
18577 		|| helm->sprite == items[IRON_HELM].index
18578 		|| (helm->sprite >= items[HAT_HOOD].index && helm->sprite < items[HAT_HOOD].index + items[HAT_HOOD].variations)
18579 		|| helm->sprite == items[HAT_HOOD_RED].index
18580 		|| helm->sprite == items[HAT_HOOD_SILVER].index
18581 		|| helm->sprite == items[PUNISHER_HOOD].index )
18582 	{
18583 		helm->scalex = 1.05;
18584 		helm->scaley = 1.05;
18585 		helm->scalez = 1.05;
18586 		if ( helm->sprite == items[PUNISHER_HOOD].index )
18587 		{
18588 			/*helm->scalex += limbs[HUMAN][11][0];
18589 			helm->scaley += limbs[HUMAN][11][1];
18590 			helm->scalez += limbs[HUMAN][11][2];*/
18591 		}
18592 	}
18593 
18594 	mask->scalex = 1.01;
18595 	mask->scaley = 1.01;
18596 	mask->scalez = 1.01;
18597 
18598 	int monster = getMonsterTypeFromSprite();
18599 	switch ( monster )
18600 	{
18601 		case HUMAN:
18602 		case VAMPIRE:
18603 		case SHOPKEEPER:
18604 			if ( helm->sprite == items[LEATHER_HELM].index )
18605 			{
18606 				helm->focalz -= 0.2;
18607 			}
18608 			break;
18609 		case GOBLIN:
18610 			if ( helm->sprite == items[LEATHER_HELM].index
18611 				|| helm->sprite == items[IRON_HELM].index )
18612 			{
18613 				helm->focalz -= 0.2;
18614 			}
18615 			else if ( helm->sprite == (items[HAT_HOOD].index + 2) )
18616 			{
18617 				// black hood
18618 				helm->focalx += 0.25;
18619 			}
18620 			break;
18621 		case GOATMAN:
18622 			if ( helm->sprite == items[LEATHER_HELM].index
18623 				|| helm->sprite == items[IRON_HELM].index )
18624 			{
18625 				helm->focalz -= 0.25;
18626 			}
18627 			else if ( helm->sprite == items[PUNISHER_HOOD].index )
18628 			{
18629 				helm->scaley += 0.05;
18630 			}
18631 			break;
18632 		case INCUBUS:
18633 			if ( helm->sprite == items[LEATHER_HELM].index
18634 				|| helm->sprite == items[IRON_HELM].index )
18635 			{
18636 				helm->focalx += 0.4;
18637 				helm->focalz -= 0.2;
18638 			}
18639 			else if ( helm->sprite == items[PUNISHER_HOOD].index )
18640 			{
18641 				mask->focalx -= 0.1;
18642 			}
18643 			break;
18644 		case SUCCUBUS:
18645 			if ( helm->sprite == items[LEATHER_HELM].index
18646 				|| helm->sprite == items[IRON_HELM].index )
18647 			{
18648 				helm->focalz -= 0.2;
18649 			}
18650 			break;
18651 		case AUTOMATON:
18652 			if ( helm->sprite == items[LEATHER_HELM].index
18653 				|| helm->sprite == items[IRON_HELM].index )
18654 			{
18655 				mask->focalx -= 0.2;
18656 			}
18657 			else if ( helm->sprite == items[PUNISHER_HOOD].index )
18658 			{
18659 				mask->focalx -= 0.2;
18660 			}
18661 			break;
18662 		case INSECTOID:
18663 			if ( helm->sprite == items[LEATHER_HELM].index )
18664 			{
18665 				helm->focalz -= 0.2;
18666 			}
18667 			else if ( helm->sprite == (items[HAT_HOOD].index + 2) )
18668 			{
18669 				// black hood
18670 				helm->focalx += 0.25;
18671 				helm->focaly += 0.06;
18672 			}
18673 			else if ( helm->sprite == items[PUNISHER_HOOD].index )
18674 			{
18675 				mask->focalx -= 0.2;
18676 			}
18677 			break;
18678 		case SKELETON:
18679 			break;
18680 		default:
18681 			break;
18682 	}
18683 }
18684 
monsterIsImmobileTurret(Entity * my,Stat * myStats)18685 bool monsterIsImmobileTurret(Entity* my, Stat* myStats)
18686 {
18687 	if ( myStats )
18688 	{
18689 		if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT || myStats->type == DUMMYBOT )
18690 		{
18691 			return true;
18692 		}
18693 	}
18694 	else if ( my )
18695 	{
18696 		int race = my->getMonsterTypeFromSprite();
18697 		if ( race == SENTRYBOT || race == SPELLBOT || race == DUMMYBOT )
18698 		{
18699 			return true;
18700 		}
18701 	}
18702 
18703 	return false;
18704 }
18705 
monsterChangesColorWhenAlly(Stat * myStats,Entity * entity)18706 bool monsterChangesColorWhenAlly(Stat* myStats, Entity* entity)
18707 {
18708 	int race = NOTHING;
18709 	if ( !myStats )
18710 	{
18711 		if ( !entity )
18712 		{
18713 			return true;
18714 		}
18715 		race = entity->getMonsterTypeFromSprite();
18716 	}
18717 	else
18718 	{
18719 		race = myStats->type;
18720 	}
18721 
18722 	if ( race == HUMAN || race == SENTRYBOT
18723 		|| race == SPELLBOT || race == AUTOMATON || race == GYROBOT || race == DUMMYBOT )
18724 	{
18725 		return false;
18726 	}
18727 	return true;
18728 }
18729 
monsterTinkeringConvertHPToAppearance(Stat * myStats)18730 int monsterTinkeringConvertHPToAppearance(Stat* myStats)
18731 {
18732 	if ( myStats )
18733 	{
18734 		if ( myStats->MAXHP == 0 || myStats->HP == 0 )
18735 		{
18736 			return 0;
18737 		}
18738 		if ( myStats->MAXHP == myStats->HP )
18739 		{
18740 			return ITEM_TINKERING_APPEARANCE;
18741 		}
18742 		real_t ratio = (1.0 * myStats->HP) / (myStats->MAXHP);
18743 		if ( ratio >= 0.74 )
18744 		{
18745 			return 3;
18746 		}
18747 		else if ( ratio >= 0.49 )
18748 		{
18749 			return 2;
18750 		}
18751 		else if ( ratio >= 0.24 )
18752 		{
18753 			return 1;
18754 		}
18755 		else
18756 		{
18757 			return 0;
18758 		}
18759 	}
18760 	return 0;
18761 }
18762 
monsterTinkeringConvertAppearanceToHP(Stat * myStats,int appearance)18763 int monsterTinkeringConvertAppearanceToHP(Stat* myStats, int appearance)
18764 {
18765 	if ( myStats )
18766 	{
18767 		if ( appearance == ITEM_TINKERING_APPEARANCE || (appearance > 0 && appearance % 10 == 0) )
18768 		{
18769 			return myStats->MAXHP;
18770 		}
18771 		int randomHP = std::max(1, myStats->MAXHP / 8);
18772 		randomHP = randomHP + rand() % randomHP;
18773 		int convertedAppearance = appearance % 10;
18774 		return std::min(myStats->MAXHP, ((convertedAppearance * myStats->HP) / 4) + randomHP);
18775 	}
18776 	return 0;
18777 }
18778 
handleQuiverThirdPersonModel(Stat & myStats)18779 void Entity::handleQuiverThirdPersonModel(Stat& myStats)
18780 {
18781 	if ( multiplayer == CLIENT )
18782 	{
18783 		return;
18784 	}
18785 	if ( !myStats.breastplate )
18786 	{
18787 		switch ( myStats.type )
18788 		{
18789 			case SKELETON:
18790 			case AUTOMATON:
18791 				sprite += 2; // short strap
18792 				break;
18793 			case KOBOLD:
18794 			case GNOME:
18795 				// no strap.
18796 				break;
18797 			default:
18798 				sprite += 1; // normal strap
18799 				break;
18800 		}
18801 	}
18802 	else
18803 	{
18804 		switch ( myStats.type )
18805 		{
18806 			case SKELETON:
18807 			case AUTOMATON:
18808 				sprite += 2; // short strap
18809 				break;
18810 			default:
18811 				sprite += 3; // shoulderpad-less.
18812 				break;
18813 		}
18814 	}
18815 }
18816 
playerInsectoidExpectedManaFromHunger(Stat & myStats)18817 Sint32 Entity::playerInsectoidExpectedManaFromHunger(Stat& myStats)
18818 {
18819 	real_t manaPercentFromHunger = myStats.HUNGER / 1000.f;
18820 	real_t expectedManaValue = std::floor(myStats.MAXMP * manaPercentFromHunger);
18821 	if ( myStats.HUNGER > 0 )
18822 	{
18823 		// add extra expected mana point here.
18824 		// i.e 950 hunger is still full mana to avoid always having 1 short.
18825 		// skip 0 hunger as it will be 0 expected.
18826 		return (static_cast<Sint32>(expectedManaValue) + 1);
18827 	}
18828 	return static_cast<Sint32>(expectedManaValue);
18829 }
18830 
playerInsectoidHungerValueOfManaPoint(Stat & myStats)18831 Sint32 Entity::playerInsectoidHungerValueOfManaPoint(Stat& myStats)
18832 {
18833 	float manaPointPercentage = 1 / static_cast<float>(myStats.MAXMP);
18834 	return static_cast<Sint32>(1000 * manaPointPercentage);
18835 }
18836 
getDamageTableMultiplier(Stat & myStats,DamageTableType damageType)18837 real_t Entity::getDamageTableMultiplier(Stat& myStats, DamageTableType damageType)
18838 {
18839 	real_t damageMultiplier = damagetables[myStats.type][damageType];
18840 	if ( myStats.EFFECTS[EFF_SHADOW_TAGGED] )
18841 	{
18842 		if ( myStats.type == LICH || myStats.type == LICH_FIRE || myStats.type == LICH_ICE
18843 			|| myStats.type == DEVIL )
18844 		{
18845 			return 1.5; // rough average.
18846 		}
18847 		else
18848 		{
18849 			return 1.0;
18850 		}
18851 	}
18852 	//messagePlayer(0, "%f", damageMultiplier);
18853 	return damageMultiplier;
18854 }
18855