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