1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: actmonster.cpp
5 Desc: behavior function for monsters
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 "messages.hpp"
16 #include "entity.hpp"
17 #include "items.hpp"
18 #include "monster.hpp"
19 #include "sound.hpp"
20 #include "shops.hpp"
21 #include "interface/interface.hpp"
22 #include "magic/magic.hpp"
23 #include "net.hpp"
24 #include "paths.hpp"
25 #include "collision.hpp"
26 #include "player.hpp"
27 #include "colors.hpp"
28 #include "scores.hpp"
29 #include "mod_tools.hpp"
30
31 float limbs[NUMMONSTERS][20][3];
32
33 // determines which monsters fight which
34 bool swornenemies[NUMMONSTERS][NUMMONSTERS] =
35 {
36 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
37 { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // HUMAN
38 { 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // RAT
39 { 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GOBLIN
40 { 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // SLIME
41 { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // TROLL
42 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // OCTOPUS
43 { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SPIDER
44 { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // GHOUL
45 { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SKELETON
46 { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCORPION
47 { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // IMP
48 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // BUGBEAR
49 { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GNOME
50 { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 }, // DEMON
51 { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SUCCUBUS
52 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // MIMIC
53 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // LICH
54 { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // MINOTAUR
55 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // DEVIL
56 { 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
57 { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // KOBOLD
58 { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCARAB
59 { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // CRYSTALGOLEM
60 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // INCUBUS
61 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // VAMPIRE
62 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // SHADOW
63 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // COCKATRICE
64 { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // INSECTOID
65 { 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GOATMAN
66 { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // AUTOMATON
67 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_ICE
68 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_FIRE
69 { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // SENTRYBOT
70 { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // SPELLBOT
71 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT
72 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // DUMMYBOT
73 };
74
75 // determines which monsters come to the aid of other monsters
76 bool monsterally[NUMMONSTERS][NUMMONSTERS] =
77 {
78 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
79 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // HUMAN
80 { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT
81 { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // GOBLIN
82 { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // SLIME
83 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL
84 { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // OCTOPUS
85 { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER
86 { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, // GHOUL
87 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // SKELETON
88 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION
89 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // IMP
90 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BUGBEAR
91 { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME
92 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // DEMON
93 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // SUCCUBUS
94 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC
95 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH
96 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR
97 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0 }, // DEVIL
98 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
99 { 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD
100 { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB
101 { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM
102 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // INCUBUS
103 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // VAMPIRE
104 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // SHADOW
105 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE
106 { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID
107 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // GOATMAN
108 { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // AUTOMATON
109 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH_ICE
110 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH_FIRE
111 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // SENTRYBOT
112 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // SPELLBOT
113 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GYROBOT
114 { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 } // DUMMYBOT
115 };
116
117 // monster sight ranges
118 double sightranges[NUMMONSTERS] =
119 {
120 256, // NOTHING
121 256, // HUMAN
122 128, // RAT
123 256, // GOBLIN
124 80, // SLIME
125 32, // TROLL
126 0, // OCTOPUS
127 96, // SPIDER
128 128, // GHOUL
129 192, // SKELETON
130 96, // SCORPION
131 256, // IMP
132 0, // BUGBEAR
133 128, // GNOME
134 256, // DEMON
135 256, // SUCCUBUS
136 0, // MIMIC
137 512, // LICH
138 512, // MINOTAUR
139 1024, // DEVIL
140 256, // SHOPKEEPER
141 192, // KOBOLD
142 512, // SCARAB
143 192, // CRYSTALGOLEM
144 256, // INCUBUS
145 192, // VAMPIRE
146 768, // SHADOW
147 256, // COCKATRICE
148 256, // INSECTOID
149 256, // GOATMAN
150 192, // AUTOMATON
151 512, // LICH_ICE
152 512, // LICH_FIRE
153 256, // SENTRYBOT
154 192, // SPELLBOT
155 256, // GYROBOT
156 32 // DUMMYBOT
157 };
158
159 int monsterGlobalAnimationMultiplier = 10;
160 int monsterGlobalAttackTimeMultiplier = 1;
161
162 /*-------------------------------------------------------------------------------
163
164 summonMonster
165
166 summons a monster near (but not at) the given location
167
168 -------------------------------------------------------------------------------*/
169
summonMonsterClient(Monster creature,long x,long y,Uint32 uid)170 void summonMonsterClient(Monster creature, long x, long y, Uint32 uid)
171 {
172 Entity* entity = summonMonster(creature, x, y);
173 entity->flags[INVISIBLE] = false;
174 entity->setUID(uid);
175 }
176
summonMonster(Monster creature,long x,long y,bool forceLocation)177 Entity* summonMonster(Monster creature, long x, long y, bool forceLocation)
178 {
179 Entity* entity = newEntity(-1, 1, map.entities, map.creatures); //Monster entity.
180 //Set the monster's variables.
181 entity->sizex = 4;
182 entity->sizey = 4;
183 entity->x = x;
184 entity->y = y;
185 entity->z = 6;
186 entity->yaw = (rand() % 360) * PI / 180.0;
187 entity->behavior = &actMonster;
188 entity->flags[UPDATENEEDED] = true;
189 entity->flags[INVISIBLE] = true;
190 entity->ranbehavior = true;
191 entity->skill[5] = nummonsters;
192
193 Stat* myStats = nullptr;
194 if ( multiplayer != CLIENT )
195 {
196 // Need to give the entity its list stuff.
197 // create an empty first node for traversal purposes
198 node_t* node = nullptr;
199 node = list_AddNodeFirst(&entity->children);
200 node->element = nullptr;
201 node->deconstructor = &emptyDeconstructor;
202
203 myStats = new Stat(creature + 1000);
204 node = list_AddNodeLast(&entity->children); //ASSUMING THIS ALREADY EXISTS WHEN THIS FUNCTION IS CALLED.
205 node->element = myStats;
206 node->size = sizeof(myStats);
207 //node->deconstructor = myStats->~Stat;
208 if ( entity->parent )
209 {
210 myStats->leader_uid = entity->parent;
211 entity->parent = 0;
212 }
213 myStats->type = creature;
214 }
215 else
216 {
217 //Give dummy stats.
218 entity->clientStats = new Stat(creature + 1000);
219 }
220
221 // Find a free tile next to the source and then spawn it there.
222 if ( multiplayer != CLIENT && !forceLocation )
223 {
224 if ( entityInsideSomething(entity) )
225 {
226 do
227 {
228 entity->x = x;
229 entity->y = y - 16;
230 if (!entityInsideSomething(entity))
231 {
232 break; // north
233 }
234 entity->x = x;
235 entity->y = y + 16;
236 if (!entityInsideSomething(entity))
237 {
238 break; // south
239 }
240 entity->x = x - 16;
241 entity->y = y;
242 if (!entityInsideSomething(entity))
243 {
244 break; // west
245 }
246 entity->x = x + 16;
247 entity->y = y;
248 if (!entityInsideSomething(entity))
249 {
250 break; // east
251 }
252 entity->x = x + 16;
253 entity->y = y - 16;
254 if (!entityInsideSomething(entity))
255 {
256 break; // northeast
257 }
258 entity->x = x + 16;
259 entity->y = y + 16;
260 if (!entityInsideSomething(entity))
261 {
262 break; // southeast
263 }
264 entity->x = x - 16;
265 entity->y = y - 16;
266 if (!entityInsideSomething(entity))
267 {
268 break; // northwest
269 }
270 entity->x = x - 16;
271 entity->y = y + 16;
272 if (!entityInsideSomething(entity))
273 {
274 break; // southwest
275 }
276
277 // we can't have monsters in walls...
278 list_RemoveNode(entity->mynode);
279 entity = nullptr;
280 break;
281 }
282 while ( 1 );
283 }
284 }
285
286 if ( entity )
287 {
288 switch ( creature )
289 {
290 case RAT:
291 entity->focalx = limbs[RAT][0][0]; // 0
292 entity->focaly = limbs[RAT][0][1]; // 0
293 entity->focalz = limbs[RAT][0][2]; // 0
294 break;
295 case SCORPION:
296 entity->focalx = limbs[SCORPION][0][0]; // 0
297 entity->focaly = limbs[SCORPION][0][1]; // 0
298 entity->focalz = limbs[SCORPION][0][2]; // 0
299 break;
300 case HUMAN:
301 entity->z = -1;
302 entity->focalx = limbs[HUMAN][0][0]; // 0
303 entity->focaly = limbs[HUMAN][0][1]; // 0
304 entity->focalz = limbs[HUMAN][0][2]; // -1.5
305 break;
306 case GOBLIN:
307 entity->z = 0;
308 entity->focalx = limbs[GOBLIN][0][0]; // 0
309 entity->focaly = limbs[GOBLIN][0][1]; // 0
310 entity->focalz = limbs[GOBLIN][0][2]; // -1.75
311 break;
312 case SLIME:
313 if ( multiplayer != CLIENT )
314 {
315 myStats->LVL = 7;
316 }
317 break;
318 case SUCCUBUS:
319 entity->z = -1;
320 entity->focalx = limbs[SUCCUBUS][0][0]; // 0
321 entity->focaly = limbs[SUCCUBUS][0][1]; // 0
322 entity->focalz = limbs[SUCCUBUS][0][2]; // -1.5
323 break;
324 case TROLL:
325 entity->z = -1.5;
326 entity->focalx = limbs[TROLL][0][0]; // 1
327 entity->focaly = limbs[TROLL][0][1]; // 0
328 entity->focalz = limbs[TROLL][0][2]; // -2
329 break;
330 case SHOPKEEPER:
331 entity->z = -1;
332 entity->focalx = limbs[SHOPKEEPER][0][0]; // 0
333 entity->focaly = limbs[SHOPKEEPER][0][1]; // 0
334 entity->focalz = limbs[SHOPKEEPER][0][2]; // -1.5
335 break;
336 case SKELETON:
337 entity->z = -.5;
338 entity->focalx = limbs[SKELETON][0][0]; // 0
339 entity->focaly = limbs[SKELETON][0][1]; // 0
340 entity->focalz = limbs[SKELETON][0][2]; // -1.5
341 break;
342 case MINOTAUR:
343 entity->z = -6;
344 entity->focalx = limbs[MINOTAUR][0][0]; // 0
345 entity->focaly = limbs[MINOTAUR][0][1]; // 0
346 entity->focalz = limbs[MINOTAUR][0][2]; // 0
347 break;
348 case GHOUL:
349 entity->z = -.25;
350 entity->focalx = limbs[GHOUL][0][0]; // 0
351 entity->focaly = limbs[GHOUL][0][1]; // 0
352 entity->focalz = limbs[GHOUL][0][2]; // -1.5
353 break;
354 case DEMON:
355 entity->z = -8.5;
356 entity->focalx = limbs[DEMON][0][0]; // -1
357 entity->focaly = limbs[DEMON][0][1]; // 0
358 entity->focalz = limbs[DEMON][0][2]; // -1.25
359 break;
360 case SPIDER:
361 entity->z = 4.5;
362 entity->focalx = limbs[SPIDER][0][0]; // -3
363 entity->focaly = limbs[SPIDER][0][1]; // 0
364 entity->focalz = limbs[SPIDER][0][2]; // -1
365 break;
366 case LICH:
367 entity->focalx = limbs[LICH][0][0]; // -0.75
368 entity->focaly = limbs[LICH][0][1]; // 0
369 entity->focalz = limbs[LICH][0][2]; // 0
370 entity->z = -2;
371 entity->yaw = PI;
372 entity->sprite = 274;
373 entity->skill[29] = 120;
374 break;
375 case CREATURE_IMP:
376 entity->z = -4.5;
377 entity->focalx = limbs[CREATURE_IMP][0][0]; // 0
378 entity->focaly = limbs[CREATURE_IMP][0][1]; // 0
379 entity->focalz = limbs[CREATURE_IMP][0][2]; // -1.75
380 break;
381 case GNOME:
382 entity->z = 2.25;
383 entity->focalx = limbs[GNOME][0][0]; // 0
384 entity->focaly = limbs[GNOME][0][1]; // 0
385 entity->focalz = limbs[GNOME][0][2]; // -2
386 break;
387 case DEVIL:
388 entity->focalx = limbs[DEVIL][0][0]; // 0
389 entity->focaly = limbs[DEVIL][0][1]; // 0
390 entity->focalz = limbs[DEVIL][0][2]; // 0
391 entity->z = -4;
392 entity->sizex = 20;
393 entity->sizey = 20;
394 entity->yaw = PI;
395 break;
396 case KOBOLD:
397 entity->z = 2.25;
398 entity->focalx = limbs[KOBOLD][0][0]; // 0
399 entity->focaly = limbs[KOBOLD][0][1]; // 0
400 entity->focalz = limbs[KOBOLD][0][2]; // -2
401 break;
402 case SCARAB:
403 entity->focalx = limbs[SCARAB][0][0]; // 0
404 entity->focaly = limbs[SCARAB][0][1]; // 0
405 entity->focalz = limbs[SCARAB][0][2]; // 0
406 break;
407 case CRYSTALGOLEM:
408 entity->z = -1.5;
409 entity->focalx = limbs[CRYSTALGOLEM][0][0]; // 1
410 entity->focaly = limbs[CRYSTALGOLEM][0][1]; // 0
411 entity->focalz = limbs[CRYSTALGOLEM][0][2]; // -2
412 break;
413 case INCUBUS:
414 entity->z = -1;
415 entity->focalx = limbs[INCUBUS][0][0]; // 0
416 entity->focaly = limbs[INCUBUS][0][1]; // 0
417 entity->focalz = limbs[INCUBUS][0][2]; // -1.5
418 break;
419 case VAMPIRE:
420 entity->z = -1;
421 entity->focalx = limbs[HUMAN][0][0]; // 0
422 entity->focaly = limbs[HUMAN][0][1]; // 0
423 entity->focalz = limbs[HUMAN][0][2]; // -1.5
424 break;
425 case SHADOW:
426 entity->z = -1;
427 entity->focalx = limbs[SHADOW][0][0]; // 0
428 entity->focaly = limbs[SHADOW][0][1]; // 0
429 entity->focalz = limbs[SHADOW][0][2]; // -1.75
430 break;
431 case COCKATRICE:
432 entity->z = -4.5;
433 entity->focalx = limbs[COCKATRICE][0][0]; // 0
434 entity->focaly = limbs[COCKATRICE][0][1]; // 0
435 entity->focalz = limbs[COCKATRICE][0][2]; // -1.75
436 break;
437 case INSECTOID:
438 entity->z = 0;
439 entity->focalx = limbs[INSECTOID][0][0]; // 0
440 entity->focaly = limbs[INSECTOID][0][1]; // 0
441 entity->focalz = limbs[INSECTOID][0][2]; // -1.75
442 if ( multiplayer != CLIENT )
443 {
444 if ( !strncmp(map.name, "Sokoban", 7) || !strncmp(map.name, "The Labyrinth", 13) )
445 {
446 strcpy(myStats->name, "lesser insectoid");
447 }
448 }
449 break;
450 case GOATMAN:
451 entity->z = 0;
452 entity->focalx = limbs[GOATMAN][0][0]; // 0
453 entity->focaly = limbs[GOATMAN][0][1]; // 0
454 entity->focalz = limbs[GOATMAN][0][2]; // -1.75
455 break;
456 case AUTOMATON:
457 entity->z = -.5;
458 entity->focalx = limbs[AUTOMATON][0][0]; // 0
459 entity->focaly = limbs[AUTOMATON][0][1]; // 0
460 entity->focalz = limbs[AUTOMATON][0][2]; // -1.5
461 break;
462 case LICH_ICE:
463 entity->focalx = limbs[LICH_ICE][0][0]; // -0.75
464 entity->focaly = limbs[LICH_ICE][0][1]; // 0
465 entity->focalz = limbs[LICH_ICE][0][2]; // 0
466 entity->z = -1.2;
467 entity->yaw = PI;
468 entity->sprite = 650;
469 break;
470 case LICH_FIRE:
471 entity->focalx = limbs[LICH_FIRE][0][0]; // -0.75
472 entity->focaly = limbs[LICH_FIRE][0][1]; // 0
473 entity->focalz = limbs[LICH_FIRE][0][2]; // 0
474 entity->z = -1.2;
475 entity->yaw = PI;
476 entity->sprite = 646;
477 break;
478 case SENTRYBOT:
479 entity->z = 0;
480 entity->focalx = limbs[SENTRYBOT][0][0];
481 entity->focaly = limbs[SENTRYBOT][0][1];
482 entity->focalz = limbs[SENTRYBOT][0][2];
483 break;
484 case SPELLBOT:
485 entity->z = 0;
486 entity->focalx = limbs[SPELLBOT][0][0];
487 entity->focaly = limbs[SPELLBOT][0][1];
488 entity->focalz = limbs[SPELLBOT][0][2];
489 break;
490 case GYROBOT:
491 entity->z = 5;
492 entity->focalx = limbs[GYROBOT][0][0];
493 entity->focaly = limbs[GYROBOT][0][1];
494 entity->focalz = limbs[GYROBOT][0][2];
495 break;
496 case DUMMYBOT:
497 entity->z = 0;
498 entity->focalx = limbs[DUMMYBOT][0][0];
499 entity->focaly = limbs[DUMMYBOT][0][1];
500 entity->focalz = limbs[DUMMYBOT][0][2];
501 break;
502 default:
503 //Spawn a potato.
504 list_RemoveNode(entity->mynode);
505 return nullptr;
506 break;
507 }
508 if ( entity )
509 {
510 nummonsters++;
511 }
512 if ( multiplayer == SERVER )
513 {
514 strcpy((char*)net_packet->data, "SUMM");
515 SDLNet_Write32((Uint32)creature, &net_packet->data[4]);
516 SDLNet_Write32((Uint32)entity->x, &net_packet->data[8]);
517 SDLNet_Write32((Uint32)entity->y, &net_packet->data[12]);
518 SDLNet_Write32(entity->getUID(), &net_packet->data[16]);
519 net_packet->len = 20;
520
521 for ( int c = 1; c < MAXPLAYERS; c++ )
522 {
523 if ( client_disconnected[c] )
524 {
525 continue;
526 }
527 net_packet->address.host = net_clients[c - 1].host;
528 net_packet->address.port = net_clients[c - 1].port;
529 sendPacketSafe(net_sock, -1, net_packet, c - 1);
530 }
531 }
532 return entity;
533 }
534 return nullptr;
535 }
536
summonManyMonster(Monster creature)537 void summonManyMonster(Monster creature)
538 {
539 for ( long x = 1; x < map.width - 1; ++x )
540 {
541 for ( long y = 1; y < map.height - 1; ++y )
542 {
543 summonMonster(creature, (x * 16) + 8, (y * 16) + 8);
544 }
545 }
546 }
547
548 /*-------------------------------------------------------------------------------
549
550 monsterMoveAside
551
552 Causes the monster given in *entity to move aside from the entity given
553 in *my
554
555 -------------------------------------------------------------------------------*/
556
monsterMoveAside(Entity * my,Entity * entity)557 bool monsterMoveAside(Entity* my, Entity* entity)
558 {
559 if ( !my || !entity )
560 {
561 return false;
562 }
563
564 if ( my->monsterState != 0 )
565 {
566 return false;
567 }
568
569 int x = 0, y = 0;
570 if ( cos(entity->yaw) > .4 )
571 {
572 y += 16;
573 if ( checkObstacle(my->x, my->y + y, my, NULL) )
574 {
575 y -= 32;
576 if ( checkObstacle(my->x, my->y + y, my, NULL) )
577 {
578 y = 0;
579 x += 16;
580 }
581 }
582 }
583 else if ( cos(entity->yaw) < -.4 )
584 {
585 y -= 16;
586 if ( checkObstacle(my->x, my->y + y, my, NULL) )
587 {
588 y += 32;
589 if ( checkObstacle(my->x, my->y + y, my, NULL) )
590 {
591 y = 0;
592 x -= 16;
593 }
594 }
595 }
596 if ( sin(entity->yaw) > .4 )
597 {
598 x -= 16;
599 if ( checkObstacle(my->x + x, my->y, my, NULL) )
600 {
601 x += 32;
602 if ( checkObstacle(my->x + x, my->y, my, NULL) )
603 {
604 x = 0;
605 y += 16;
606 }
607 }
608 }
609 else if ( sin(entity->yaw) < -.4 )
610 {
611 x += 16;
612 if ( checkObstacle(my->x + x, my->y, my, NULL) )
613 {
614 x -= 32;
615 if ( checkObstacle(my->x + x, my->y, my, NULL) )
616 {
617 x = 0;
618 y -= 16;
619 }
620 }
621 }
622
623 // move away
624 if ( x != 0 || y != 0 )
625 {
626 my->monsterState = MONSTER_STATE_PATH;
627 my->monsterReleaseAttackTarget();
628 my->monsterTargetX = my->x + x;
629 my->monsterTargetY = my->y + y;
630 serverUpdateEntitySkill(my, 0);
631 return true;
632 }
633 return false;
634 }
635
636 /*-------------------------------------------------------------------------------
637
638 act*
639
640 The following function describes an entity behavior. The function
641 takes a pointer to the entity that uses it as an argument.
642
643 -------------------------------------------------------------------------------*/
644
645 int devilstate = 0;
646 int devilacted = 0;
647 int devilroar = 0;
648 int devilsummonedtimes = 0;
649
makeFollower(int monsterclicked,bool ringconflict,char namesays[64],Entity * my,Stat * myStats)650 bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], Entity* my, Stat* myStats)
651 {
652 if ( !myStats )
653 {
654 return false;
655 }
656 if ( ringconflict )
657 {
658 //Instant fail if ring of conflict is in effect. You have no allies!
659 return false;
660 }
661
662 if ( !players[monsterclicked] || !players[monsterclicked]->entity || !stats[monsterclicked] )
663 {
664 return false;
665 }
666
667 Monster race = my->getRace();
668
669 if ( myStats->leader_uid != 0 )
670 {
671 //Handle the "I have a leader!" situation.
672 if ( myStats->leader_uid == players[monsterclicked]->entity->getUID() )
673 {
674 //Follows this player already!
675 if ( my->getINT() > -2 && race == HUMAN )
676 {
677 messagePlayer(monsterclicked, language[535], namesays, stats[monsterclicked]->name);
678 }
679 else
680 {
681 messagePlayer(monsterclicked, language[534], namesays);
682 }
683 }
684 else
685 {
686 //Follows somebody else.
687 if ( my->getINT() > -2 && race == HUMAN )
688 {
689 messagePlayer(monsterclicked, language[536], namesays, stats[monsterclicked]->name);
690 }
691 else
692 {
693 messagePlayer(monsterclicked, language[534], namesays);
694 }
695
696 if ( my->checkFriend(players[monsterclicked]->entity) )
697 {
698 //If friendly, move aside.
699 monsterMoveAside(my, players[monsterclicked]->entity);
700 }
701 }
702
703 return false;
704 }
705
706 bool canAlly = false;
707 if ( skillCapstoneUnlocked(monsterclicked, PRO_LEADERSHIP) )
708 {
709 int allowedFollowers = 8;
710 int numFollowers = 0;
711 for ( node_t* node = stats[monsterclicked]->FOLLOWERS.first; node; node = node->next )
712 {
713 Entity* follower = nullptr;
714 if ( (Uint32*)node->element )
715 {
716 follower = uidToEntity(*((Uint32*)node->element));
717 }
718 if ( follower )
719 {
720 Stat* followerStats = follower->getStats();
721 if ( followerStats )
722 {
723 if ( !(followerStats->type == SENTRYBOT || followerStats->type == GYROBOT
724 || followerStats->type == SPELLBOT || followerStats->type == DUMMYBOT) )
725 {
726 ++numFollowers;
727 }
728 }
729 }
730 }
731 if ( allowedFollowers > numFollowers )
732 {
733 //Can control humans & goblins & goatmen & insectoids.
734 //TODO: Control humanoids in general? Or otherwise something from each tileset.
735 if ( (stats[monsterclicked]->type == HUMAN && race == HUMAN)
736 || race == GOBLIN
737 || race == AUTOMATON
738 || race == GOATMAN
739 || race == INSECTOID
740 || race == GYROBOT )
741 {
742 canAlly = true;
743 }
744
745 //TODO: If enemies (e.g. goblin or an angry human), require the player to be unseen by this creature to gain control of it.
746
747 if ( stats[monsterclicked]->type == SKELETON )
748 {
749 if ( race == GHOUL )
750 {
751 canAlly = true;
752 }
753 }
754 else if ( stats[monsterclicked]->type == VAMPIRE )
755 {
756 if ( race == VAMPIRE && strncmp(myStats->name, "Bram Kindly", 11) )
757 {
758 canAlly = true;
759 }
760 }
761 else if ( stats[monsterclicked]->type == INCUBUS || stats[monsterclicked]->type == SUCCUBUS )
762 {
763 if ( race == INCUBUS || race == SUCCUBUS )
764 {
765 canAlly = true;
766 }
767 else if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED])
768 && stats[monsterclicked]->type != INCUBUS )
769 {
770 canAlly = true;
771 if ( myStats->EFFECTS[EFF_CONFUSED] )
772 {
773 my->setEffect(EFF_CONFUSED, false, 0, false);
774 }
775 }
776 }
777 else if ( stats[monsterclicked]->type == GOATMAN )
778 {
779 if ( race == GOATMAN )
780 {
781 canAlly = true;
782 }
783 }
784 else if ( stats[monsterclicked]->type == GOBLIN )
785 {
786 if ( race == GOBLIN )
787 {
788 canAlly = true;
789 }
790 }
791 else if ( stats[monsterclicked]->type == AUTOMATON )
792 {
793 if ( race == AUTOMATON || race == HUMAN )
794 {
795 canAlly = true;
796 }
797 }
798 else if ( stats[monsterclicked]->type == RAT )
799 {
800 if ( race == RAT )
801 {
802 canAlly = true;
803 }
804 }
805 else if ( stats[monsterclicked]->type == SPIDER )
806 {
807 if ( race == SPIDER || race == SCARAB || race == SCORPION )
808 {
809 canAlly = true;
810 }
811 }
812 else if ( stats[monsterclicked]->type == INSECTOID )
813 {
814 if ( race == INSECTOID || race == SCARAB || race == SCORPION )
815 {
816 canAlly = true;
817 }
818 }
819 else if ( stats[monsterclicked]->type == TROLL )
820 {
821 if ( race == TROLL )
822 {
823 canAlly = true;
824 }
825 }
826 else if ( stats[monsterclicked]->type == CREATURE_IMP )
827 {
828 if ( race == CREATURE_IMP && !(!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
829 {
830 canAlly = true; // non-boss imps
831 }
832 }
833 if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
834 {
835 canAlly = true;
836 }
837 }
838 else
839 {
840 messagePlayer(monsterclicked, language[3482]);
841 }
842 }
843 else
844 {
845 bool tryAlly = my->checkFriend(players[monsterclicked]->entity);
846 if ( stats[monsterclicked]->type == SUCCUBUS )
847 {
848 if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED]) )
849 {
850 tryAlly = true;
851 }
852 }
853 if ( strcmp(myStats->name, "") && !monsterNameIsGeneric(*myStats)
854 && ((stats[monsterclicked]->PROFICIENCIES[PRO_LEADERSHIP] + stats[monsterclicked]->CHR) < 60) )
855 {
856 if ( race != HUMAN )
857 {
858 tryAlly = false;
859 messagePlayer(monsterclicked, language[3481], myStats->name);
860 }
861 }
862 if ( tryAlly )
863 {
864 if ( myStats->leader_uid == 0 )
865 {
866 int allowedFollowers = std::min(8, std::max(4, 2 * (stats[monsterclicked]->PROFICIENCIES[PRO_LEADERSHIP] / 20)));
867 int numFollowers = 0;
868 for ( node_t* node = stats[monsterclicked]->FOLLOWERS.first; node; node = node->next )
869 {
870 Entity* follower = nullptr;
871 if ( (Uint32*)node->element )
872 {
873 follower = uidToEntity(*((Uint32*)node->element));
874 }
875 if ( follower )
876 {
877 Stat* followerStats = follower->getStats();
878 if ( followerStats )
879 {
880 if ( !(followerStats->type == SENTRYBOT || followerStats->type == GYROBOT
881 || followerStats->type == SPELLBOT || followerStats->type == DUMMYBOT) )
882 {
883 ++numFollowers;
884 }
885 }
886 }
887 }
888 if ( allowedFollowers > numFollowers )
889 {
890 if ( race == AUTOMATON || race == GYROBOT )
891 {
892 canAlly = true;
893 }
894 if ( stats[monsterclicked]->type == HUMAN )
895 {
896 canAlly = true;
897 }
898 else if ( stats[monsterclicked]->type == SKELETON )
899 {
900 if ( race == GHOUL )
901 {
902 canAlly = true;
903 }
904 }
905 else if ( stats[monsterclicked]->type == VAMPIRE )
906 {
907 if ( race == VAMPIRE && strncmp(myStats->name, "Bram Kindly", 11) )
908 {
909 canAlly = true;
910 }
911 }
912 else if ( stats[monsterclicked]->type == SUCCUBUS || stats[monsterclicked]->type == INCUBUS )
913 {
914 if ( race == INCUBUS || race == SUCCUBUS )
915 {
916 canAlly = true;
917 }
918 else if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED])
919 && stats[monsterclicked]->type != INCUBUS )
920 {
921 canAlly = true;
922 if ( stats[monsterclicked]->type == SUCCUBUS )
923 {
924 steamAchievementClient(monsterclicked, "BARONY_ACH_TEMPTRESS");
925 }
926 if ( myStats->EFFECTS[EFF_CONFUSED] )
927 {
928 my->setEffect(EFF_CONFUSED, false, 0, false);
929 }
930 }
931 }
932 else if ( stats[monsterclicked]->type == GOATMAN )
933 {
934 if ( race == GOATMAN )
935 {
936 canAlly = true;
937 }
938 }
939 else if ( stats[monsterclicked]->type == GOBLIN )
940 {
941 if ( race == GOBLIN )
942 {
943 canAlly = true;
944 }
945 }
946 else if ( stats[monsterclicked]->type == RAT )
947 {
948 if ( race == RAT )
949 {
950 canAlly = true;
951 }
952 }
953 else if ( stats[monsterclicked]->type == AUTOMATON )
954 {
955 if ( race == AUTOMATON || race == HUMAN )
956 {
957 canAlly = true;
958 }
959 }
960 else if ( stats[monsterclicked]->type == SPIDER )
961 {
962 if ( race == SPIDER || race == SCARAB || race == SCORPION )
963 {
964 canAlly = true;
965 }
966 }
967 else if ( stats[monsterclicked]->type == INSECTOID )
968 {
969 if ( race == INSECTOID || race == SCARAB || race == SCORPION )
970 {
971 canAlly = true;
972 }
973 }
974 else if ( stats[monsterclicked]->type == TROLL )
975 {
976 if ( race == TROLL )
977 {
978 canAlly = true;
979 }
980 }
981 else if ( stats[monsterclicked]->type == CREATURE_IMP )
982 {
983 if ( race == CREATURE_IMP && !(!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
984 {
985 canAlly = true; // non-boss imps
986 }
987 }
988 if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
989 {
990 canAlly = true;
991 }
992 }
993 else
994 {
995 if ( numFollowers >= 8 )
996 {
997 messagePlayer(monsterclicked, language[3482]);
998 }
999 else
1000 {
1001 messagePlayer(monsterclicked, language[3480]);
1002 }
1003 }
1004 }
1005 }
1006 }
1007
1008 if ( !canAlly )
1009 {
1010 //This one does not want to join your ever-enlarging cult.
1011 if ( my->getINT() > -2 && race == HUMAN )
1012 {
1013 //Human tells off the player.
1014 messagePlayer(monsterclicked, language[530 + rand() % 4], namesays);
1015 // move aside
1016 monsterMoveAside(my, players[monsterclicked]->entity);
1017 }
1018 else
1019 {
1020 messagePlayer(monsterclicked, language[534], namesays);
1021 }
1022
1023 return false;
1024 }
1025
1026 node_t* newNode = list_AddNodeLast(&stats[monsterclicked]->FOLLOWERS);
1027 newNode->deconstructor = &defaultDeconstructor;
1028 Uint32* myuid = (Uint32*) (malloc(sizeof(Uint32)));
1029 newNode->element = myuid;
1030 *myuid = my->getUID();
1031
1032 if ( my->getINT() > -2 && race == HUMAN )
1033 {
1034 messagePlayer(monsterclicked, language[525 + rand() % 4], namesays, stats[monsterclicked]->name);
1035 }
1036 else
1037 {
1038 //This one can't speak, so generic "The %s decides to follow you!" message.
1039 messagePlayerMonsterEvent(monsterclicked, 0xFFFFFFFF, *myStats, language[529], language[529], MSG_COMBAT);
1040 }
1041 spawnMagicEffectParticles(my->x, my->y, my->z, 685);
1042 monsterMoveAside(my, players[monsterclicked]->entity);
1043 players[monsterclicked]->entity->increaseSkill(PRO_LEADERSHIP);
1044 my->monsterState = MONSTER_STATE_WAIT; // be ready to follow
1045 myStats->leader_uid = players[monsterclicked]->entity->getUID();
1046 my->monsterAllyIndex = monsterclicked;
1047 if ( multiplayer == SERVER )
1048 {
1049 serverUpdateEntitySkill(my, 42); // update monsterAllyIndex for clients.
1050 }
1051 if ( monsterclicked > 0 && multiplayer == SERVER )
1052 {
1053 //Tell the client he suckered somebody into his cult.
1054 strcpy((char*) (net_packet->data), "LEAD");
1055 SDLNet_Write32((Uint32 )my->getUID(), &net_packet->data[4]);
1056 strcpy((char*)(&net_packet->data[8]), myStats->name);
1057 net_packet->data[8 + strlen(myStats->name)] = 0;
1058 net_packet->address.host = net_clients[monsterclicked - 1].host;
1059 net_packet->address.port = net_clients[monsterclicked - 1].port;
1060 net_packet->len = 8 + strlen(myStats->name) + 1;
1061 sendPacketSafe(net_sock, -1, net_packet, monsterclicked - 1);
1062
1063 serverUpdateAllyStat(monsterclicked, my->getUID(), myStats->LVL, myStats->HP, myStats->MAXHP, myStats->type);
1064 }
1065
1066 // update flags for colors.
1067 my->flags[USERFLAG2] = true;
1068 serverUpdateEntityFlag(my, USERFLAG2);
1069 if ( monsterChangesColorWhenAlly(myStats) )
1070 {
1071 int bodypart = 0;
1072 for ( node_t* node = my->children.first; node != nullptr; node = node->next )
1073 {
1074 if ( bodypart >= LIMB_HUMANOID_TORSO )
1075 {
1076 Entity* tmp = (Entity*)node->element;
1077 if ( tmp )
1078 {
1079 tmp->flags[USERFLAG2] = true;
1080 //serverUpdateEntityFlag(tmp, USERFLAG2);
1081 }
1082 }
1083 ++bodypart;
1084 }
1085 }
1086
1087 for ( node_t* node = stats[monsterclicked]->FOLLOWERS.first; node != nullptr; node = node->next )
1088 {
1089 Uint32* c = (Uint32*)node->element;
1090 Entity* entity = nullptr;
1091 if ( c )
1092 {
1093 entity = uidToEntity(*c);
1094 }
1095 if ( entity && entity->monsterTarget == *myuid )
1096 {
1097 entity->monsterReleaseAttackTarget(); // followers stop punching the new target.
1098 }
1099 }
1100
1101 if ( !FollowerMenu.recentEntity && monsterclicked == clientnum )
1102 {
1103 FollowerMenu.recentEntity = my;
1104 }
1105
1106 if ( (stats[monsterclicked]->type != HUMAN && stats[monsterclicked]->type != AUTOMATON) && myStats->type == HUMAN )
1107 {
1108 steamAchievementClient(monsterclicked, "BARONY_ACH_PITY_FRIEND");
1109 }
1110 if ( stats[monsterclicked]->type == VAMPIRE && myStats->type == VAMPIRE )
1111 {
1112 if ( !strncmp(myStats->name, "young vampire", strlen("young vampire")) )
1113 {
1114 steamAchievementClient(monsterclicked, "BARONY_ACH_YOUNG_BLOOD");
1115 }
1116 }
1117 if ( myStats->type == HUMAN && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->appearance == 0
1118 && stats[monsterclicked]->playerRace == RACE_AUTOMATON )
1119 {
1120 achievementObserver.updatePlayerAchievement(monsterclicked, AchievementObserver::Achievement::BARONY_ACH_REAL_BOY,
1121 AchievementObserver::AchievementEvent::REAL_BOY_HUMAN_RECRUIT);
1122 }
1123 if ( stats[monsterclicked]->type == TROLL && myStats->type == TROLL )
1124 {
1125 serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_FORUM_TROLL, AchievementObserver::FORUM_TROLL_RECRUIT_TROLL);
1126 }
1127 if ( stats[monsterclicked]->appearance == 0
1128 && (stats[monsterclicked]->playerRace == RACE_INCUBUS || stats[monsterclicked]->playerRace == RACE_SUCCUBUS) )
1129 {
1130 if ( myStats->type == HUMAN )
1131 {
1132 if ( stats[monsterclicked]->type == INCUBUS || stats[monsterclicked]->type == SUCCUBUS )
1133 {
1134 serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_PIMPING_AINT_EASY, 1);
1135 }
1136 }
1137 else if ( myStats->type == INCUBUS || myStats->type == SUCCUBUS )
1138 {
1139 serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_PIMPING_AINT_EASY, 1);
1140 }
1141 }
1142 if ( stats[monsterclicked]->appearance == 0
1143 && (stats[monsterclicked]->playerRace == RACE_GOBLIN) )
1144 {
1145 if ( myStats->type == GOBLIN )
1146 {
1147 serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_TRIBE_SUBSCRIBE, 1);
1148 }
1149 }
1150 if ( client_classes[monsterclicked] == CLASS_SHAMAN )
1151 {
1152 if ( players[monsterclicked]->entity->effectPolymorph != 0 || players[monsterclicked]->entity->effectShapeshift != 0 )
1153 {
1154 achievementObserver.playerAchievements[monsterclicked].socialButterfly++;
1155 }
1156 }
1157
1158 return true;
1159 }
1160
sentrybotPickSpotNoise(Entity * my,Stat * myStats)1161 void sentrybotPickSpotNoise(Entity* my, Stat* myStats)
1162 {
1163 if ( !my || !myStats )
1164 {
1165 return;
1166 }
1167 bool doSpecialNoise = false;
1168 switch ( my->getUID() % 3 )
1169 {
1170 case 0:
1171 if ( my->ticks % 60 < 20 )
1172 {
1173 doSpecialNoise = true;
1174 }
1175 case 1:
1176 if ( my->ticks % 60 >= 20 && my->ticks % 60 < 40 )
1177 {
1178 doSpecialNoise = true;
1179 }
1180 case 2:
1181 if ( my->ticks % 60 >= 40 )
1182 {
1183 doSpecialNoise = true;
1184 }
1185 break;
1186 default:
1187 break;
1188 }
1189 if ( doSpecialNoise && rand() % 3 == 0 )
1190 {
1191 MONSTER_SOUND = playSoundEntity(my, 466 + rand() % 3, 64);
1192 }
1193 else
1194 {
1195 MONSTER_SOUND = playSoundEntity(my, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128);
1196 }
1197 }
1198
actMonster(Entity * my)1199 void actMonster(Entity* my)
1200 {
1201 if (!my)
1202 {
1203 return;
1204 }
1205
1206 int x, y, c, i;
1207 double dist, dist2;
1208 list_t* path;
1209 node_t* node, *node2;
1210 pathnode_t* pathnode;
1211 double dir;
1212 double tangent;
1213 Stat* myStats;
1214 Entity* entity;
1215 Stat* hitstats = NULL;
1216 bool hasrangedweapon = false;
1217 bool myReflex;
1218 Sint32 previousMonsterState = my->monsterState;
1219
1220 // deactivate in menu
1221 if ( intro )
1222 {
1223 return;
1224 }
1225
1226 // this is mostly a SERVER function.
1227 // however, there is a small part for clients:
1228 if ( multiplayer == CLIENT )
1229 {
1230 if ( !MONSTER_INIT && my->sprite >= 100 && !(my->sprite >= 163 && my->sprite <= 166) )
1231 {
1232 MONSTER_INIT = 1;
1233
1234 // make two empty nodes
1235 node = list_AddNodeLast(&my->children);
1236 node->element = nullptr;
1237 node->deconstructor = &emptyDeconstructor;
1238 node->size = 0;
1239 node = list_AddNodeLast(&my->children);
1240 node->element = nullptr;
1241 node->deconstructor = &emptyDeconstructor;
1242 node->size = 0;
1243 if ( my->isPlayerHeadSprite() ) // human heads
1244 {
1245 initHuman(my, nullptr);
1246 }
1247 else if ( my->sprite == 131 || my->sprite == 265 ) // rat
1248 {
1249 initRat(my, nullptr);
1250 }
1251 else if ( my->sprite == 180 ) // goblin head
1252 {
1253 initGoblin(my, nullptr);
1254 }
1255 else if ( my->sprite == 196 || my->sprite == 266 ) // scorpion body
1256 {
1257 initScorpion(my, nullptr);
1258 }
1259 else if ( my->sprite == 190 ) // succubus head
1260 {
1261 initSuccubus(my, nullptr);
1262 }
1263 else if ( my->sprite == 204 ) // troll head
1264 {
1265 initTroll(my, nullptr);
1266 }
1267 else if ( my->sprite == 217 ) // shopkeeper head
1268 {
1269 initShopkeeper(my, nullptr);
1270 }
1271 else if ( my->sprite == 229 ) // skeleton head
1272 {
1273 initSkeleton(my, nullptr);
1274 }
1275 else if ( my->sprite == 239 ) // minotaur waist
1276 {
1277 initMinotaur(my, nullptr);
1278 }
1279 else if ( my->sprite == 246 ) // ghoul head
1280 {
1281 initGhoul(my, nullptr);
1282 }
1283 else if ( my->sprite == 258 ) // demon head
1284 {
1285 initDemon(my, nullptr);
1286 }
1287 else if ( my->sprite == 267 ) // spider body
1288 {
1289 initSpider(my, nullptr);
1290 }
1291 else if ( my->sprite == 274 ) // lich body
1292 {
1293 initLich(my, nullptr);
1294 }
1295 else if ( my->sprite == 289 ) // imp head
1296 {
1297 initImp(my, nullptr);
1298 }
1299 else if ( my->sprite == 295 ) // gnome head
1300 {
1301 initGnome(my, nullptr);
1302 }
1303 else if ( my->sprite == 304 ) // devil torso
1304 {
1305 initDevil(my, nullptr);
1306 }
1307 else if ( my->sprite == 475 ) // crystal golem head
1308 {
1309 initCrystalgolem(my, nullptr);
1310 }
1311 else if ( my->sprite == 413 ) // cockatrice head
1312 {
1313 initCockatrice(my, nullptr);
1314 }
1315 else if ( my->sprite == 467 ) // automaton torso
1316 {
1317 initAutomaton(my, NULL);
1318 }
1319 else if ( my->sprite == 429 || my->sprite == 430 ) // scarab
1320 {
1321 initScarab(my, nullptr);
1322 }
1323 else if ( my->sprite == 421 ) // kobold head
1324 {
1325 initKobold(my, nullptr);
1326 }
1327 else if ( my->sprite == 481 ) // shadow head
1328 {
1329 initShadow(my, nullptr);
1330 }
1331 else if ( my->sprite == 437 ) // vampire head
1332 {
1333 initVampire(my, nullptr);
1334 }
1335 else if ( my->sprite == 445 ) // incubus head
1336 {
1337 initIncubus(my, nullptr);
1338 }
1339 else if ( my->sprite == 455 ) // insectoid head
1340 {
1341 initInsectoid(my, nullptr);
1342 }
1343 else if ( my->sprite == 463 ) // goatman head
1344 {
1345 initGoatman(my, nullptr);
1346 }
1347 else if ( my->sprite == 646 ) // lich body
1348 {
1349 initLichFire(my, nullptr);
1350 }
1351 else if ( my->sprite == 650 ) // lich body
1352 {
1353 initLichIce(my, nullptr);
1354 }
1355 else if ( my->sprite == 872 || my->sprite == 885 ) // sentrybot head
1356 {
1357 initSentryBot(my, nullptr);
1358 }
1359 else if ( my->sprite == 886 ) // gyrobot head
1360 {
1361 initGyroBot(my, nullptr);
1362 }
1363 else if ( my->sprite == 889 ) // dummybot head
1364 {
1365 initDummyBot(my, nullptr);
1366 }
1367 }
1368 else
1369 {
1370 my->flags[BURNABLE] = true;
1371 if ( my->isPlayerHeadSprite() ) // human heads
1372 {
1373 humanMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1374 }
1375 else if ( my->sprite == 131 || my->sprite == 265 ) // rat
1376 {
1377 ratAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1378 }
1379 else if ( my->sprite == 180 ) // goblin head
1380 {
1381 goblinMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1382 }
1383 else if ( my->sprite == 196 || my->sprite == 266 ) // scorpion body
1384 {
1385 scorpionAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1386 }
1387 else if ( my->sprite == 190 ) // succubus head
1388 {
1389 succubusMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1390 }
1391 else if ( my->sprite == 204 ) // troll head
1392 {
1393 trollMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1394 }
1395 else if ( my->sprite == 217 ) // shopkeeper head
1396 {
1397 shopkeeperMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1398 }
1399 else if ( my->sprite == 229 ) // skeleton head
1400 {
1401 my->flags[BURNABLE] = false;
1402 skeletonMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1403 }
1404 else if ( my->sprite == 239 ) // minotaur waist
1405 {
1406 minotaurMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1407 actMinotaurCeilingBuster(my);
1408 }
1409 else if ( my->sprite == 246 ) // ghoul head
1410 {
1411 ghoulMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1412 }
1413 else if ( my->sprite == 258 ) // demon head
1414 {
1415 my->flags[BURNABLE] = false;
1416 demonMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1417 actDemonCeilingBuster(my);
1418 }
1419 else if ( my->sprite == 267 ) // spider body
1420 {
1421 spiderMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1422 }
1423 else if ( my->sprite == 274 ) // lich body
1424 {
1425 my->flags[BURNABLE] = false;
1426 lichAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1427 }
1428 else if ( my->sprite == 289 ) // imp head
1429 {
1430 my->flags[BURNABLE] = false;
1431 impMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1432 }
1433 else if ( my->sprite == 295 ) // gnome head
1434 {
1435 gnomeMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1436 }
1437 else if ( my->sprite == 304 ) // devil torso
1438 {
1439 my->flags[BURNABLE] = false;
1440 devilMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1441 }
1442 else if ( my->sprite == 421 ) // kobold head
1443 {
1444 koboldMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1445 }
1446 else if ( my->sprite == 429 || my->sprite == 430 ) // scarab
1447 {
1448 scarabAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1449 }
1450 else if ( my->sprite == 475 ) // crystal golem head
1451 {
1452 crystalgolemMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1453 }
1454 else if ( my->sprite == 445 ) // incubus head
1455 {
1456 incubusMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1457 }
1458 else if ( my->sprite == 437 ) // vampire head
1459 {
1460 vampireMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1461 }
1462 else if ( my->sprite == 481 ) // shadow head
1463 {
1464 shadowMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1465 }
1466 else if ( my->sprite == 413 ) // cockatrice head
1467 {
1468 cockatriceMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1469 }
1470 else if ( my->sprite == 455 ) // insectoid head
1471 {
1472 insectoidMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1473 }
1474 else if ( my->sprite == 463 ) // goatman head
1475 {
1476 goatmanMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1477 }
1478 else if ( my->sprite == 467 ) // automaton head
1479 {
1480 automatonMoveBodyparts(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1481 }
1482 else if ( my->sprite == 646 ) // lich body
1483 {
1484 my->flags[BURNABLE] = false;
1485 lichFireAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1486 }
1487 else if ( my->sprite == 650 ) // lich body
1488 {
1489 my->flags[BURNABLE] = false;
1490 lichIceAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1491 }
1492 else if ( my->sprite == 872 ) // sentrybot head
1493 {
1494 my->flags[BURNABLE] = false;
1495 sentryBotAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1496 }
1497 else if ( my->sprite == 885 ) // sentrybot head
1498 {
1499 my->flags[BURNABLE] = false;
1500 sentryBotAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1501 }
1502 else if ( my->sprite == 886 ) // gyrobot head
1503 {
1504 my->flags[BURNABLE] = false;
1505 gyroBotAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1506 }
1507 else if ( my->sprite == 889 ) // dummybot head
1508 {
1509 dummyBotAnimate(my, NULL, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
1510 }
1511 else
1512 {
1513 my->flags[BURNABLE] = false;
1514 }
1515
1516 if ( !intro )
1517 {
1518 my->handleEffectsClient();
1519 }
1520
1521 // request entity update (check if I've been deleted)
1522 if ( ticks % (TICKS_PER_SECOND * 5) == my->getUID() % (TICKS_PER_SECOND * 5) )
1523 {
1524 strcpy((char*)net_packet->data, "ENTE");
1525 net_packet->data[4] = clientnum;
1526 SDLNet_Write32(my->getUID(), &net_packet->data[5]);
1527 net_packet->address.host = net_server.host;
1528 net_packet->address.port = net_server.port;
1529 net_packet->len = 9;
1530 sendPacketSafe(net_sock, -1, net_packet, 0);
1531 }
1532 }
1533 return;
1534 }
1535
1536 if ( ticks % (TICKS_PER_SECOND) == my->getUID() % (TICKS_PER_SECOND / 2) )
1537 {
1538 myReflex = true;
1539 }
1540 else
1541 {
1542 myReflex = false;
1543 }
1544
1545 // init
1546 if ( MONSTER_INIT < 2 ) // 0 means no initialization, 1 means stats are initialized
1547 {
1548 my->skill[2] = -4; // tells clients to set this entity behavior to actMonster
1549 myStats = my->getStats();
1550 if (myStats)
1551 {
1552 myStats->monster_sound = NULL;
1553 my->flags[BURNABLE] = true;
1554 switch ( myStats->type )
1555 {
1556 case HUMAN:
1557 initHuman(my, myStats);
1558 break;
1559 case RAT:
1560 initRat(my, myStats);
1561 break;
1562 case GOBLIN:
1563 initGoblin(my, myStats);
1564 break;
1565 case SLIME:
1566 my->flags[BURNABLE] = false;
1567 initSlime(my, myStats);
1568 break;
1569 case SCORPION:
1570 initScorpion(my, myStats);
1571 break;
1572 case SUCCUBUS:
1573 initSuccubus(my, myStats);
1574 break;
1575 case TROLL:
1576 initTroll(my, myStats);
1577 break;
1578 case SHOPKEEPER:
1579 initShopkeeper(my, myStats);
1580 break;
1581 case SKELETON:
1582 my->flags[BURNABLE] = false;
1583 initSkeleton(my, myStats);
1584 break;
1585 case MINOTAUR:
1586 initMinotaur(my, myStats);
1587 break;
1588 case GHOUL:
1589 initGhoul(my, myStats);
1590 break;
1591 case DEMON:
1592 my->flags[BURNABLE] = false;
1593 initDemon(my, myStats);
1594 break;
1595 case SPIDER:
1596 initSpider(my, myStats);
1597 break;
1598 case LICH:
1599 my->flags[BURNABLE] = false;
1600 initLich(my, myStats);
1601 break;
1602 case CREATURE_IMP:
1603 my->flags[BURNABLE] = false;
1604 initImp(my, myStats);
1605 break;
1606 case GNOME:
1607 initGnome(my, myStats);
1608 break;
1609 case DEVIL:
1610 my->flags[BURNABLE] = false;
1611 devilstate = 0;
1612 devilacted = 0;
1613 devilroar = 0;
1614 devilsummonedtimes = 0;
1615 initDevil(my, myStats);
1616 break;
1617 case KOBOLD:
1618 initKobold (my, myStats);
1619 break;
1620 case SCARAB:
1621 initScarab (my, myStats);
1622 break;
1623 case CRYSTALGOLEM:
1624 initCrystalgolem (my, myStats);
1625 break;
1626 case INCUBUS:
1627 initIncubus (my, myStats);
1628 break;
1629 case VAMPIRE:
1630 initVampire (my, myStats);
1631 break;
1632 case SHADOW:
1633 initShadow (my, myStats);
1634 break;
1635 case COCKATRICE:
1636 initCockatrice (my, myStats);
1637 break;
1638 case INSECTOID:
1639 initInsectoid (my, myStats);
1640 break;
1641 case GOATMAN:
1642 initGoatman (my, myStats);
1643 break;
1644 case AUTOMATON:
1645 my->flags[BURNABLE] = false;
1646 initAutomaton (my, myStats);
1647 break;
1648 case LICH_ICE:
1649 my->flags[BURNABLE] = false;
1650 initLichIce (my, myStats);
1651 my->monsterLichBattleState = LICH_BATTLE_IMMOBILE;
1652 break;
1653 case LICH_FIRE:
1654 my->flags[BURNABLE] = false;
1655 initLichFire (my, myStats);
1656 my->monsterLichBattleState = LICH_BATTLE_IMMOBILE;
1657 break;
1658 case SENTRYBOT:
1659 my->sprite = 872;
1660 my->flags[BURNABLE] = false;
1661 initSentryBot(my, myStats);
1662 break;
1663 case SPELLBOT:
1664 my->sprite = 885;
1665 my->flags[BURNABLE] = false;
1666 initSentryBot(my, myStats);
1667 break;
1668 case GYROBOT:
1669 my->flags[BURNABLE] = false;
1670 initGyroBot(my, myStats);
1671 break;
1672 case DUMMYBOT:
1673 initDummyBot(my, myStats);
1674 break;
1675 default:
1676 break; //This should never be reached.
1677 }
1678 }
1679
1680 MONSTER_INIT = 2;
1681 if ( myStats->type != LICH && myStats->type != DEVIL )
1682 {
1683 my->monsterLookDir = (rand() % 360) * PI / 180;
1684 }
1685 else
1686 {
1687 my->monsterLookDir = PI;
1688 }
1689 my->monsterLookTime = rand() % 120;
1690 my->monsterMoveTime = rand() % 10;
1691 MONSTER_SOUND = NULL;
1692 if ( MONSTER_NUMBER == -1 )
1693 {
1694 MONSTER_NUMBER = nummonsters;
1695 nummonsters++;
1696 }
1697 /*if( rand()%20==0 ) { // 20% chance
1698 MONSTER_STATE = 2; // start hunting the player immediately
1699 MONSTER_TARGET = rand()%numplayers;
1700 MONSTER_TARGETX = players[MONSTER_TARGET]->x;
1701 MONSTER_TARGETY = players[MONSTER_TARGET]->y;
1702 } else {
1703 MONSTER_TARGET = -1;
1704 }*/
1705
1706 if ( uidToEntity(my->monsterTarget) == nullptr )
1707 {
1708 my->monsterTarget = 0;
1709 }
1710
1711 /*// create an empty first node for traversal purposes //GOING TO ASSUME THIS ALREADY EXISTS WHEN THIS FUNCTION IS CALLED.
1712 node = list_AddNodeFirst(my->children);
1713 node->element = NULL;
1714 node->deconstructor = &emptyDeconstructor;*/
1715
1716 // assign stats to the monster
1717 //myStats = (Stat *) malloc(sizeof(Stat)); //GOING TO ASSUME THIS ALREADY EXISTS WHEN THIS FUNCTION IS CALLED.
1718 //myStats->type = RAT; //GOING TO ASSUME THIS IS ALREADY PROPERLY SET WHEN THE FUNCTION IS CALLED.
1719 //TODO: Move the rest of this into the monster specific init functions.
1720 /*node = list_AddNodeLast(my->children); //ASSUMING THIS ALREADY EXISTS WHEN THIS FUNCTION IS CALLED.
1721 node->element = myStats;
1722 node->deconstructor = &defaultDeconstructor;*/
1723
1724 return;
1725 }
1726
1727 myStats = my->getStats();
1728 if ( myStats == NULL )
1729 {
1730 printlog("ERROR: monster entity at %p has no stats struct!", my);
1731 return;
1732 }
1733 myStats->defending = false;
1734 myStats->sneaking = 0;
1735
1736 // levitation
1737 bool levitating = isLevitating(myStats);
1738
1739 if ( myStats->type == MINOTAUR )
1740 {
1741 int c;
1742 for ( c = 0; c < MAXPLAYERS; c++ )
1743 {
1744 assailant[c] = true; // as long as this is active, combat music doesn't turn off
1745 assailantTimer[c] = COMBAT_MUSIC_COOLDOWN;
1746 }
1747 }
1748
1749 if ( myStats->type == SHADOW && my->monsterTarget != 0 )
1750 {
1751 for ( int c = 0; c < MAXPLAYERS; ++c )
1752 {
1753 if ( players[c] && players[c]->entity && players[c]->entity->getUID() == my->monsterTarget )
1754 {
1755 assailant[c] = true; //Keeps combat music on as long as a shadow is hunting you down down down!
1756 assailantTimer[c] = COMBAT_MUSIC_COOLDOWN;
1757 break;
1758 }
1759 }
1760 }
1761
1762 if ( my->ticks == 120 + MONSTER_NUMBER )
1763 {
1764 serverUpdateBodypartIDs(my);
1765 }
1766
1767 // some special herx behavior
1768 if ( myStats->type == LICH )
1769 {
1770 // destroying room lights
1771 if ( myStats->HP <= myStats->MAXHP / 2 )
1772 {
1773 node_t* node, *nextnode;
1774 bool foundlights = false;
1775 for ( node = map.entities->first; node != nullptr; node = nextnode )
1776 {
1777 nextnode = node->next;
1778 Entity* tempEntity = (Entity*)node->element;
1779
1780 if ( tempEntity->behavior == &actTorch || tempEntity->behavior == &actCampfire )
1781 {
1782 foundlights = true;
1783 if ( tempEntity->light )
1784 {
1785 list_RemoveNode(tempEntity->light->node);
1786 tempEntity->light = nullptr;
1787 }
1788 list_RemoveNode(tempEntity->mynode);
1789 }
1790 }
1791 if ( foundlights )
1792 {
1793 #ifdef USE_FMOD
1794 if ( MONSTER_SOUND )
1795 {
1796 FMOD_Channel_Stop(MONSTER_SOUND);
1797 }
1798 #elif defined USE_OPENAL
1799 if ( MONSTER_SOUND )
1800 {
1801 OPENAL_Channel_Stop(MONSTER_SOUND);
1802 }
1803 #endif
1804 int c;
1805 for ( c = 0; c < MAXPLAYERS; c++ )
1806 {
1807 MONSTER_SOUND = playSoundPlayer(c, 179, 128);
1808 playSoundPlayer(c, 166, 128);
1809 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
1810 messagePlayerColor(c, color, language[512]);
1811 }
1812 }
1813 }
1814 // dodging away
1815 if ( ( ( rand() % 4 == 0 && my->monsterState != 6 ) || ( rand() % 10 == 0 && my->monsterState == MONSTER_STATE_LICH_SUMMON) ) && myStats->OLDHP != myStats->HP )
1816 {
1817 playSoundEntity(my, 180, 128);
1818 my->monsterState = MONSTER_STATE_LICH_DODGE; // dodge state
1819 double dir = my->yaw - (PI / 2) + PI * (rand() % 2);
1820 MONSTER_VELX = cos(dir) * 5;
1821 MONSTER_VELY = sin(dir) * 5;
1822 my->monsterSpecialTimer = 0;
1823 }
1824 }
1825
1826 if ( (myStats->type == LICH_FIRE && my->monsterState != MONSTER_STATE_LICHFIRE_DIE)
1827 || (myStats->type == LICH_ICE && my->monsterState != MONSTER_STATE_LICHICE_DIE )
1828 && myStats->HP > 0 )
1829 {
1830 //messagePlayer(0, "state: %d", my->monsterState);
1831 if ( my->monsterLichBattleState >= LICH_BATTLE_READY )
1832 {
1833 for ( int c = 0; c < MAXPLAYERS; c++ )
1834 {
1835 assailant[c] = true; // as long as this is active, combat music doesn't turn off
1836 assailantTimer[c] = COMBAT_MUSIC_COOLDOWN;
1837 }
1838 }
1839 if ( my->monsterSpecialTimer > 0 )
1840 {
1841 --my->monsterSpecialTimer;
1842 }
1843 else
1844 {
1845 my->monsterSpecialTimer = 0;
1846 if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS )
1847 {
1848 my->monsterState = MONSTER_STATE_LICH_TELEPORT_ROAMING;
1849 my->monsterSpecialTimer = 60;
1850 if ( myStats->type == LICH_FIRE )
1851 {
1852 my->lichFireTeleport();
1853 }
1854 else
1855 {
1856 my->lichIceTeleport();
1857 }
1858 }
1859 }
1860
1861 if ( my->monsterState != MONSTER_STATE_ATTACK && my->monsterState <= MONSTER_STATE_HUNT )
1862 {
1863 my->monsterHitTime = HITRATE * 2;
1864 }
1865 //messagePlayer(0, "Ally state: %d", my->monsterLichAllyStatus);
1866 Entity* lichAlly = nullptr;
1867 if ( my->ticks > (TICKS_PER_SECOND)
1868 && my->monsterLichAllyStatus == LICH_ALLY_ALIVE
1869 && ticks % (TICKS_PER_SECOND * 2) == 0 )
1870 {
1871 if ( myStats->type == LICH_ICE )
1872 {
1873 numMonsterTypeAliveOnMap(LICH_FIRE, lichAlly);
1874 if ( lichAlly == nullptr )
1875 {
1876 //messagePlayer(0, "DEAD");
1877 my->monsterLichAllyStatus = LICH_ALLY_DEAD;
1878 my->monsterLichAllyUID = 0;
1879 for ( int c = 0; c < MAXPLAYERS; c++ )
1880 {
1881 playSoundPlayer(c, 392, 128);
1882 messagePlayerColor(c, uint32ColorBaronyBlue(*mainsurface), language[2647]);
1883 }
1884 }
1885 else if ( lichAlly && my->monsterLichAllyUID == 0 )
1886 {
1887 my->monsterLichAllyUID = lichAlly->getUID();
1888 }
1889 }
1890 else
1891 {
1892 numMonsterTypeAliveOnMap(LICH_ICE, lichAlly);
1893 if ( lichAlly == nullptr )
1894 {
1895 //messagePlayer(0, "DEAD");
1896 my->monsterLichAllyStatus = LICH_ALLY_DEAD;
1897 my->monsterLichAllyUID = 0;
1898 for ( int c = 0; c < MAXPLAYERS; c++ )
1899 {
1900 playSoundPlayer(c, 391, 128);
1901 messagePlayerColor(c, uint32ColorOrange(*mainsurface), language[2649]);
1902 }
1903 }
1904 else if ( lichAlly && my->monsterLichAllyUID == 0 )
1905 {
1906 my->monsterLichAllyUID = lichAlly->getUID();
1907 }
1908 }
1909 }
1910 real_t lichDist = 0.f;
1911 Entity* target = uidToEntity(my->monsterTarget);
1912
1913 if ( myStats->OLDHP != myStats->HP && myStats->HP > 0 )
1914 {
1915 if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS
1916 && my->monsterSpecialTimer < 250 )
1917 {
1918 if ( rand() % 8 == 0 )
1919 {
1920 my->monsterState = MONSTER_STATE_LICH_TELEPORT_ROAMING;
1921 my->lichFireTeleport();
1922 my->monsterSpecialTimer = 60;
1923 }
1924 }
1925 if ( my->monsterState <= MONSTER_STATE_HUNT )
1926 {
1927 switch ( my->monsterLichBattleState )
1928 {
1929 // track when a teleport can happen, battleState needs to be odd numbered to allow stationary teleport
1930 case 0:
1931 if ( myStats->HP <= myStats->MAXHP * 0.9 )
1932 {
1933 my->monsterLichBattleState = 1;
1934 }
1935 break;
1936 case 2:
1937 if ( myStats->HP <= myStats->MAXHP * 0.7 )
1938 {
1939 my->monsterLichBattleState = 3;
1940 }
1941 break;
1942 case 4:
1943 if ( myStats->HP <= myStats->MAXHP * 0.5 )
1944 {
1945 my->monsterLichBattleState = 5;
1946 }
1947 break;
1948 case 6:
1949 if ( myStats->HP <= myStats->MAXHP * 0.3 )
1950 {
1951 my->monsterLichBattleState = 7;
1952 }
1953 break;
1954 case 8:
1955 if ( myStats->HP <= myStats->MAXHP * 0.1 )
1956 {
1957 my->monsterLichBattleState = 9;
1958 }
1959 break;
1960 default:
1961 break;
1962 }
1963 if ( my->monsterLichBattleState % 2 == 1
1964 && (rand() % 5 == 0
1965 || (rand() % 4 == 0 && my->monsterLichTeleportTimer > 0)
1966 || (rand() % 2 == 0 && my->monsterLichAllyStatus == LICH_ALLY_DEAD))
1967 )
1968 {
1969 // chance to change state to teleport after being hit.
1970 if ( my->monsterLichAllyUID != 0 )
1971 {
1972 lichAlly = uidToEntity(my->monsterLichAllyUID);
1973 }
1974 if ( myStats->type == LICH_FIRE )
1975 {
1976 if ( !myStats->EFFECTS[EFF_VAMPIRICAURA] )
1977 {
1978 if ( (lichAlly && lichAlly->monsterState != MONSTER_STATE_LICH_CASTSPELLS)
1979 || my->monsterLichAllyStatus == LICH_ALLY_DEAD
1980 || multiplayer != SINGLE )
1981 {
1982 // don't teleport if ally is casting spells. unless multiplayer, then go nuts!
1983 my->monsterState = MONSTER_STATE_LICHFIRE_TELEPORT_STATIONARY;
1984 my->lichFireTeleport();
1985 my->monsterSpecialTimer = 80;
1986 ++my->monsterLichBattleState;
1987 }
1988 }
1989 }
1990 else if ( myStats->type == LICH_ICE )
1991 {
1992 if ( (lichAlly && lichAlly->monsterState != MONSTER_STATE_LICH_CASTSPELLS)
1993 || my->monsterLichAllyStatus == LICH_ALLY_DEAD
1994 || multiplayer != SINGLE )
1995 {
1996 // don't teleport if ally is casting spells. unless multiplayer, then go nuts!
1997 my->monsterState = MONSTER_STATE_LICHICE_TELEPORT_STATIONARY;
1998 my->lichIceTeleport();
1999 my->monsterSpecialTimer = 80;
2000 ++my->monsterLichBattleState;
2001 }
2002 }
2003 }
2004 }
2005 }
2006 if ( my->monsterSpecialTimer == 0 && my->monsterAttack == 0 )
2007 {
2008 if ( my->monsterState <= MONSTER_STATE_HUNT && my->monsterTarget )
2009 {
2010 if ( target )
2011 {
2012 lichDist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
2013 }
2014 if ( ticks % 30 == 0 )
2015 {
2016 // check tiles around the monster.
2017 int sides = 0;
2018 int my_x = static_cast<int>(my->x) >> 4;
2019 int my_y = static_cast<int>(my->y) >> 4;
2020 int mapIndex = (my_y) * MAPLAYERS + (my_x + 1) * MAPLAYERS * map.height;
2021 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
2022 {
2023 ++sides;
2024 }
2025 mapIndex = (my_y) * MAPLAYERS + (my_x - 1) * MAPLAYERS * map.height;
2026 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
2027 {
2028 ++sides;
2029 }
2030 mapIndex = (my_y + 1) * MAPLAYERS + (my_x) * MAPLAYERS * map.height;
2031 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
2032 {
2033 ++sides;
2034 }
2035 mapIndex = (my_y - 1) * MAPLAYERS + (my_x) * MAPLAYERS * map.height;
2036 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
2037 {
2038 ++sides;
2039 }
2040 //messagePlayer(0, "sides: %d, timer %d", sides, my->monsterLichTeleportTimer);
2041 if ( sides == 0 )
2042 {
2043 my->monsterLichTeleportTimer = 0;
2044 }
2045 else
2046 {
2047 if ( sides >= 2 )
2048 {
2049 my->monsterLichTeleportTimer++;
2050 }
2051 else
2052 {
2053 if ( rand() % 3 == 0 )
2054 {
2055 my->monsterLichTeleportTimer++;
2056 }
2057 }
2058 if ( my->monsterLichTeleportTimer >= 3 )
2059 {
2060 // let's teleport, reset the counter inside the teleport functions.
2061 if ( myStats->type == LICH_FIRE )
2062 {
2063 my->lichFireTeleport();
2064 }
2065 else
2066 {
2067 my->lichIceTeleport();
2068 }
2069 my->monsterSpecialTimer = 40;
2070 }
2071 }
2072 }
2073 if ( myStats->type == LICH_FIRE )
2074 {
2075 if ( ( my->monsterLichFireMeleePrev == LICH_ATK_RISING_SINGLE
2076 || my->monsterLichFireMeleePrev == LICH_ATK_HORIZONTAL_RETURN)
2077 && rand() % 4 == 0
2078 && ticks % 10 == 0
2079 )
2080 {
2081 // chance to dodge immediately after the above 2 attacks
2082 playSoundEntity(my, 180, 128);
2083 dir = my->yaw - (PI / 2) + PI * (rand() % 2);
2084 MONSTER_VELX = cos(dir) * 3;
2085 MONSTER_VELY = sin(dir) * 3;
2086 my->monsterState = MONSTER_STATE_LICHFIRE_DODGE;
2087 my->monsterSpecialTimer = 20;
2088 my->monsterLichFireMeleePrev = 0;
2089 my->monsterLichFireMeleeSeq = LICH_ATK_BASICSPELL_SINGLE;
2090 }
2091 else if ( myStats->OLDHP != myStats->HP )
2092 {
2093 if ( rand() % 4 == 0 )
2094 {
2095 // chance to dodge on hp loss
2096 playSoundEntity(my, 180, 128);
2097 dir = my->yaw - (PI / 2) + PI * (rand() % 2);
2098 MONSTER_VELX = cos(dir) * 3;
2099 MONSTER_VELY = sin(dir) * 3;
2100 my->monsterState = MONSTER_STATE_LICHFIRE_DODGE;
2101 my->monsterSpecialTimer = 20;
2102 }
2103 }
2104 else if ( lichDist > 64 )
2105 {
2106 if ( target && rand() % 100 == 0 )
2107 {
2108 // chance to dodge towards the target if distance is great enough.
2109 playSoundEntity(my, 180, 128);
2110 tangent = atan2(target->y - my->y, target->x - my->x);
2111 dir = tangent;
2112 while ( dir < 0 )
2113 {
2114 dir += 2 * PI;
2115 }
2116 while ( dir > 2 * PI )
2117 {
2118 dir -= 2 * PI;
2119 }
2120 MONSTER_VELX = cos(dir) * 3;
2121 MONSTER_VELY = sin(dir) * 3;
2122 my->monsterState = MONSTER_STATE_LICHFIRE_DODGE;
2123 my->monsterSpecialTimer = 50;
2124 }
2125 }
2126 }
2127 else if ( myStats->type == LICH_ICE )
2128 {
2129 int enemiesInMelee = 0;
2130 if ( ticks % 50 == 0 )
2131 {
2132 // grab enemies around the lich once per second to determine threat level.
2133 enemiesInMelee = numTargetsAroundEntity(my, 32.0, PI, MONSTER_TARGET_ENEMY);
2134 }
2135 if ( myStats->OLDHP != myStats->HP )
2136 {
2137 if ( rand() % 3 == 0 )
2138 {
2139 // chance to dodge on hp loss
2140 playSoundEntity(my, 180, 128);
2141 dir = my->yaw - (PI / 2) + PI * (rand() % 2);
2142 MONSTER_VELX = cos(dir) * 3;
2143 MONSTER_VELY = sin(dir) * 3;
2144 my->monsterState = MONSTER_STATE_LICHICE_DODGE;
2145 my->monsterSpecialTimer = 30;
2146 if ( rand() % 2 == 0 )
2147 {
2148 // prepare off-hand spell after dodging
2149 my->monsterLichIceCastPrev = 0;
2150 my->monsterLichIceCastSeq = LICH_ATK_BASICSPELL_SINGLE;
2151 }
2152 }
2153 }
2154 else if ( (lichDist < 32) || (enemiesInMelee > 1) )
2155 {
2156 if ( target && ticks % 10 == 0 && rand() % 100 == 0 || (enemiesInMelee > 1 && rand() % 8 == 0) )
2157 {
2158 // chance to dodge away from target if distance is low enough.
2159 playSoundEntity(my, 180, 128);
2160 tangent = atan2(target->y - my->y, target->x - my->x);
2161 dir = tangent + PI;
2162 while ( dir < 0 )
2163 {
2164 dir += 2 * PI;
2165 }
2166 while ( dir > 2 * PI )
2167 {
2168 dir -= 2 * PI;
2169 }
2170 MONSTER_VELX = cos(dir) * 3;
2171 MONSTER_VELY = sin(dir) * 3;
2172 my->monsterState = MONSTER_STATE_LICHICE_DODGE;
2173 my->monsterSpecialTimer = 20;
2174 if ( rand() % 2 == 0 )
2175 {
2176 // prepare off-hand spell after dodging
2177 my->monsterLichIceCastPrev = 0;
2178 my->monsterLichIceCastSeq = LICH_ATK_BASICSPELL_SINGLE;
2179 }
2180 }
2181 else if ( (ticks % 50 == 0 && rand() % 10 == 0) || (enemiesInMelee > 1 && rand() % 4 == 0) )
2182 {
2183 my->monsterSpecialTimer = 100;
2184 my->monsterLichIceCastPrev = 0;
2185 my->monsterLichIceCastSeq = LICH_ATK_CHARGE_AOE;
2186 }
2187 }
2188 else if ( lichDist > 64 )
2189 {
2190 // chance to dodge towards the target if distance is great enough.
2191 if ( rand() % 100 == 0 )
2192 {
2193 if ( my->monsterLichAllyUID != 0 )
2194 {
2195 lichAlly = uidToEntity(my->monsterLichAllyUID);
2196 }
2197 if ( lichAlly && target )
2198 {
2199 // if ally is close to your target, then don't dodge towards.
2200 if ( entityDist(lichAlly, target) > 48.0 )
2201 {
2202 playSoundEntity(my, 180, 128);
2203 tangent = atan2(target->y - my->y, target->x - my->x);
2204 dir = tangent;
2205 while ( dir < 0 )
2206 {
2207 dir += 2 * PI;
2208 }
2209 while ( dir > 2 * PI )
2210 {
2211 dir -= 2 * PI;
2212 }
2213 MONSTER_VELX = cos(dir) * 3;
2214 MONSTER_VELY = sin(dir) * 3;
2215 my->monsterState = MONSTER_STATE_LICHICE_DODGE;
2216 my->monsterSpecialTimer = 50;
2217 }
2218 }
2219 if ( my->monsterState != MONSTER_STATE_LICHICE_DODGE )
2220 {
2221 // chance to dodge sideways if not set above
2222 playSoundEntity(my, 180, 128);
2223 dir = my->yaw - (PI / 2) + PI * (rand() % 2);
2224 MONSTER_VELX = cos(dir) * 3;
2225 MONSTER_VELY = sin(dir) * 3;
2226 my->monsterState = MONSTER_STATE_LICHICE_DODGE;
2227 my->monsterSpecialTimer = 30;
2228 if ( rand() % 10 == 0 )
2229 {
2230 // prepare off-hand spell after dodging
2231 my->monsterLichIceCastPrev = 0;
2232 my->monsterLichIceCastSeq = LICH_ATK_BASICSPELL_SINGLE;
2233 }
2234 }
2235 }
2236 }
2237 else if ( my->monsterLichMeleeSwingCount > 3 )
2238 {
2239 // reached x successive normal attacks, either move/teleport/dodge around the map
2240 my->monsterLichMeleeSwingCount = 0;
2241 if ( rand() % 10 > 0 )
2242 {
2243 if ( rand() % 2 == 0 )
2244 {
2245 my->monsterTarget = 0;
2246 my->monsterTargetX = my->x - 50 + rand() % 100;
2247 my->monsterTargetY = my->y - 50 + rand() % 100;
2248 my->monsterState = MONSTER_STATE_PATH; // path state
2249 }
2250 else
2251 {
2252 // chance to dodge
2253 playSoundEntity(my, 180, 128);
2254 dir = my->yaw - (PI / 2) + PI * (rand() % 2);
2255 MONSTER_VELX = cos(dir) * 3;
2256 MONSTER_VELY = sin(dir) * 3;
2257 my->monsterState = MONSTER_STATE_LICHICE_DODGE;
2258 my->monsterSpecialTimer = 30;
2259 if ( rand() % 2 == 0 )
2260 {
2261 // prepare off-hand spell after dodging
2262 my->monsterLichIceCastPrev = 0;
2263 my->monsterLichIceCastSeq = LICH_ATK_BASICSPELL_SINGLE;
2264 }
2265 else
2266 {
2267 my->monsterLichIceCastPrev = 0;
2268 my->monsterLichIceCastSeq = LICH_ATK_FALLING_DIAGONAL;
2269 }
2270 }
2271 }
2272 }
2273 }
2274 }
2275 else if ( my->monsterState == MONSTER_STATE_LICHFIRE_TELEPORT_STATIONARY
2276 || my->monsterState == MONSTER_STATE_LICHICE_TELEPORT_STATIONARY )
2277 {
2278 my->monsterState = MONSTER_STATE_LICH_CASTSPELLS;
2279 my->monsterSpecialTimer = 500; // cast spells for 10 seconds.
2280 my->monsterHitTime = 0;
2281 my->monsterLichMagicCastCount = 0;
2282 my->monsterLichFireMeleeSeq = 0;
2283 // acquire a new target.
2284 lichDist = 1024;
2285 for ( node = map.creatures->first; node != nullptr; node = node->next ) //Only creatures need to be targetted.
2286 {
2287 Entity* tempEntity = (Entity*)node->element;
2288 if ( tempEntity->behavior == &actPlayer
2289 && (sqrt(pow(my->x - tempEntity->x, 2) + pow(my->y - tempEntity->y, 2)) < lichDist)
2290 )
2291 {
2292 lichDist = sqrt(pow(my->x - tempEntity->x, 2) + pow(my->y - tempEntity->y, 2));
2293 target = tempEntity;
2294 }
2295 }
2296 if ( target )
2297 {
2298 my->monsterAcquireAttackTarget(*target, MONSTER_STATE_LICH_CASTSPELLS);
2299 }
2300 my->castOrbitingMagicMissile(SPELL_BLEED, 16.0, 0.0, 500);
2301 my->castOrbitingMagicMissile(SPELL_BLEED, 16.0, 2 * PI / 5, 500);
2302 my->castOrbitingMagicMissile(SPELL_BLEED, 16.0, 4 * PI / 5, 500);
2303 my->castOrbitingMagicMissile(SPELL_BLEED, 16.0, 6 * PI / 5, 500);
2304 my->castOrbitingMagicMissile(SPELL_BLEED, 16.0, 8 * PI / 5, 500);
2305 }
2306 else if ( my->monsterState == MONSTER_STATE_LICH_TELEPORT_ROAMING )
2307 {
2308 my->monsterHitTime = 0;
2309 my->monsterLichMagicCastCount = 0;
2310 my->monsterLichFireMeleeSeq = 0;
2311 // acquire a new target.
2312 lichDist = 1024;
2313 for ( node = map.creatures->first; node != nullptr; node = node->next ) //Only creatures need to be targetted.
2314 {
2315 Entity* tempEntity = (Entity*)node->element;
2316 if ( tempEntity && tempEntity->behavior == &actPlayer
2317 && (sqrt(pow(my->x - tempEntity->x, 2) + pow(my->y - tempEntity->y, 2)) < lichDist)
2318 )
2319 {
2320 lichDist = sqrt(pow(my->x - tempEntity->x, 2) + pow(my->y - tempEntity->y, 2));
2321 target = tempEntity;
2322 }
2323 }
2324 if ( target )
2325 {
2326 my->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH);
2327 my->monsterState = MONSTER_STATE_PATH;
2328 }
2329 else
2330 {
2331 my->monsterState = MONSTER_STATE_WAIT;
2332 }
2333 }
2334 }
2335 }
2336
2337 // hunger, regaining hp/mp, poison, etc.
2338 if ( !intro )
2339 {
2340 my->handleEffects(myStats);
2341 }
2342 if ( myStats->HP <= 0
2343 && my->monsterState != MONSTER_STATE_LICH_DEATH
2344 && my->monsterState != MONSTER_STATE_DEVIL_DEATH
2345 && my->monsterState != MONSTER_STATE_LICHFIRE_DIE
2346 && my->monsterState != MONSTER_STATE_LICHICE_DIE )
2347 {
2348 //TODO: Refactor die function.
2349 // drop all equipment
2350 entity = dropItemMonster(myStats->helmet, my, myStats);
2351 if ( entity )
2352 {
2353 entity->flags[USERFLAG1] = true;
2354 }
2355 myStats->helmet = NULL;
2356 entity = dropItemMonster(myStats->breastplate, my, myStats);
2357 if ( entity )
2358 {
2359 entity->flags[USERFLAG1] = true;
2360 }
2361 myStats->breastplate = NULL;
2362 entity = dropItemMonster(myStats->gloves, my, myStats);
2363 if ( entity )
2364 {
2365 entity->flags[USERFLAG1] = true;
2366 }
2367 myStats->gloves = NULL;
2368 entity = dropItemMonster(myStats->shoes, my, myStats);
2369 if ( entity )
2370 {
2371 entity->flags[USERFLAG1] = true;
2372 }
2373 myStats->shoes = NULL;
2374 entity = dropItemMonster(myStats->shield, my, myStats);
2375 if ( entity )
2376 {
2377 entity->flags[USERFLAG1] = true;
2378 }
2379 myStats->shield = NULL;
2380 if ( myStats->weapon )
2381 {
2382 if ( itemCategory(myStats->weapon) != SPELLBOOK )
2383 {
2384 entity = dropItemMonster(myStats->weapon, my, myStats);
2385 if ( entity )
2386 {
2387 entity->flags[USERFLAG1] = true;
2388 }
2389 }
2390 else
2391 {
2392 // spellbooks are not dropped
2393 if ( myStats->weapon->node )
2394 {
2395 list_RemoveNode(myStats->weapon->node);
2396 }
2397 else
2398 {
2399 free(myStats->weapon);
2400 }
2401 }
2402 myStats->weapon = NULL;
2403 }
2404 entity = dropItemMonster(myStats->cloak, my, myStats);
2405 if ( entity )
2406 {
2407 entity->flags[USERFLAG1] = true;
2408 }
2409 myStats->cloak = NULL;
2410 entity = dropItemMonster(myStats->amulet, my, myStats);
2411 if ( entity )
2412 {
2413 entity->flags[USERFLAG1] = true;
2414 }
2415 myStats->amulet = NULL;
2416 entity = dropItemMonster(myStats->ring, my, myStats);
2417 if ( entity )
2418 {
2419 entity->flags[USERFLAG1] = true;
2420 }
2421 myStats->ring = NULL;
2422 entity = dropItemMonster(myStats->mask, my, myStats);
2423 if ( entity )
2424 {
2425 entity->flags[USERFLAG1] = true;
2426 }
2427 myStats->mask = NULL;
2428 node_t* nextnode = NULL;
2429
2430 for ( node = myStats->inventory.first; node != NULL; node = nextnode )
2431 {
2432 nextnode = node->next;
2433 Item* item = (Item*)node->element;
2434 for ( c = item->count; c > 0; c-- )
2435 {
2436 bool wasQuiver = itemTypeIsQuiver(item->type);
2437 entity = dropItemMonster(item, my, myStats);
2438 if ( entity )
2439 {
2440 entity->flags[USERFLAG1] = true; // makes items passable, improves performance
2441 if ( wasQuiver )
2442 {
2443 break; // always drop the whole stack.
2444 }
2445 }
2446 }
2447 }
2448
2449 // broadcast my player allies about my death
2450 int playerFollower = MAXPLAYERS;
2451 for (c = 0; c < MAXPLAYERS; c++)
2452 {
2453 if (players[c] && players[c]->entity)
2454 {
2455 if (myStats->leader_uid == players[c]->entity->getUID())
2456 {
2457 playerFollower = c;
2458 if ( stats[c] )
2459 {
2460 for ( node_t* allyNode = stats[c]->FOLLOWERS.first; allyNode != nullptr; allyNode = allyNode->next )
2461 {
2462 if ( *((Uint32*)allyNode->element) == my->getUID() )
2463 {
2464 list_RemoveNode(allyNode);
2465 if ( myStats->monsterIsCharmed == 1 && client_classes[c] == CLASS_MESMER )
2466 {
2467 steamStatisticUpdateClient(c, STEAM_STAT_SURROGATES, STEAM_STAT_INT, 1);
2468 }
2469 if ( c != clientnum )
2470 {
2471 serverRemoveClientFollower(c, my->getUID());
2472 }
2473 else
2474 {
2475 if ( FollowerMenu.recentEntity && (FollowerMenu.recentEntity->getUID() == 0
2476 || FollowerMenu.recentEntity->getUID() == my->getUID()) )
2477 {
2478 FollowerMenu.recentEntity = nullptr;
2479 }
2480 if ( FollowerMenu.followerToCommand == my )
2481 {
2482 FollowerMenu.closeFollowerMenuGUI();
2483 }
2484 }
2485 break;
2486 }
2487 }
2488 }
2489 break;
2490 }
2491 }
2492 }
2493
2494 bool skipObituary = false;
2495 if ( my->monsterAllySummonRank != 0 && myStats->MP > 0 )
2496 {
2497 skipObituary = true;
2498 }
2499
2500 if ( playerFollower < MAXPLAYERS && !skipObituary )
2501 {
2502 messagePlayerMonsterEvent(c, 0xFFFFFFFF, *myStats, language[1499], language[2589], MSG_OBITUARY);
2503 }
2504
2505 // drop gold
2506 if ( gameplayCustomManager.inUse() )
2507 {
2508 int numGold = myStats->GOLD * (gameplayCustomManager.globalGoldPercent / 100.f);
2509 myStats->GOLD = numGold;
2510 }
2511 if ( myStats->GOLD > 0 && myStats->monsterNoDropItems == 0 )
2512 {
2513 int x = std::min<int>(std::max(0, (int)(my->x / 16)), map.width - 1);
2514 int y = std::min<int>(std::max(0, (int)(my->y / 16)), map.height - 1);
2515
2516 // check for floor to drop gold...
2517 if ( map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] )
2518 {
2519 entity = newEntity(130, 0, map.entities, nullptr); // 130 = goldbag model
2520 entity->sizex = 4;
2521 entity->sizey = 4;
2522 entity->x = my->x;
2523 entity->y = my->y;
2524 entity->z = 6;
2525 entity->yaw = (rand() % 360) * PI / 180.0;
2526 entity->flags[PASSABLE] = true;
2527 entity->flags[UPDATENEEDED] = true;
2528 entity->behavior = &actGoldBag;
2529 entity->skill[0] = myStats->GOLD; // amount
2530 }
2531 }
2532
2533 // die
2534 #ifdef USE_FMOD
2535 if ( MONSTER_SOUND )
2536 {
2537 FMOD_Channel_Stop(MONSTER_SOUND);
2538 }
2539 #elif defined USE_OPENAL
2540 if ( MONSTER_SOUND )
2541 {
2542 OPENAL_Channel_Stop(MONSTER_SOUND);
2543 }
2544 #endif
2545 myStats = my->getStats();
2546 switch ( myStats->type )
2547 {
2548 case HUMAN:
2549 humanDie(my);
2550 break;
2551 case RAT:
2552 ratDie(my);
2553 break;
2554 case GOBLIN:
2555 goblinDie(my);
2556 break;
2557 case SLIME:
2558 slimeDie(my);
2559 break;
2560 case SCORPION:
2561 scorpionDie(my);
2562 break;
2563 case SUCCUBUS:
2564 succubusDie(my);
2565 break;
2566 case TROLL:
2567 trollDie(my);
2568 break;
2569 case SHOPKEEPER:
2570 shopkeeperDie(my);
2571 break;
2572 case SKELETON:
2573 skeletonDie(my);
2574 break;
2575 case MINOTAUR:
2576 minotaurDie(my);
2577 break;
2578 case GHOUL:
2579 ghoulDie(my);
2580 break;
2581 case DEMON:
2582 demonDie(my);
2583 break;
2584 case SPIDER:
2585 spiderDie(my);
2586 break;
2587 case LICH:
2588 my->flags[PASSABLE] = true; // so I can't take any more hits
2589 my->monsterState = MONSTER_STATE_LICH_DEATH; // lich death state
2590 my->monsterSpecialTimer = 0;
2591 MONSTER_ATTACK = 0;
2592 MONSTER_ATTACKTIME = 0;
2593 serverUpdateEntitySkill(my, 8);
2594 serverUpdateEntitySkill(my, 9);
2595 for ( c = 0; c < NUMEFFECTS; ++c )
2596 {
2597 myStats->EFFECTS[c] = false;
2598 myStats->EFFECTS_TIMERS[c] = 0;
2599 }
2600 break;
2601 case CREATURE_IMP:
2602 impDie(my);
2603 break;
2604 case GNOME:
2605 gnomeDie(my);
2606 break;
2607 case DEVIL:
2608 my->flags[PASSABLE] = true; // so I can't take any more hits
2609 my->monsterState = MONSTER_STATE_DEVIL_DEATH; // devil death state
2610 my->monsterSpecialTimer = 0;
2611 MONSTER_ATTACK = 0;
2612 MONSTER_ATTACKTIME = 0;
2613 MONSTER_ARMBENDED = 0;
2614 serverUpdateEntitySkill(my, 8);
2615 serverUpdateEntitySkill(my, 9);
2616 serverUpdateEntitySkill(my, 10);
2617 for ( c = 0; c < NUMEFFECTS; ++c )
2618 {
2619 myStats->EFFECTS[c] = false;
2620 myStats->EFFECTS_TIMERS[c] = 0;
2621 }
2622 break;
2623 case AUTOMATON:
2624 automatonDie(my);
2625 break;
2626 case COCKATRICE:
2627 cockatriceDie(my);
2628 break;
2629 case CRYSTALGOLEM:
2630 crystalgolemDie(my);
2631 break;
2632 case SCARAB:
2633 scarabDie(my);
2634 break;
2635 case KOBOLD:
2636 koboldDie(my);
2637 break;
2638 case SHADOW:
2639 shadowDie(my);
2640 break;
2641 case VAMPIRE:
2642 vampireDie(my);
2643 break;
2644 case INCUBUS:
2645 incubusDie(my);
2646 break;
2647 case INSECTOID:
2648 insectoidDie(my);
2649 break;
2650 case GOATMAN:
2651 goatmanDie(my);
2652 break;
2653 case LICH_FIRE:
2654 my->flags[PASSABLE] = true; // so I can't take any more hits
2655 my->monsterState = MONSTER_STATE_LICHFIRE_DIE; // lich death state
2656 my->monsterSpecialTimer = 180;
2657 my->monsterAttack = 0;
2658 my->monsterAttackTime = 0;
2659 serverUpdateEntitySkill(my, 8);
2660 serverUpdateEntitySkill(my, 9);
2661 serverUpdateEntitySkill(my, 0);
2662 for ( c = 0; c < NUMEFFECTS; ++c )
2663 {
2664 myStats->EFFECTS[c] = false;
2665 myStats->EFFECTS_TIMERS[c] = 0;
2666 }
2667 break;
2668 case LICH_ICE:
2669 my->flags[PASSABLE] = true; // so I can't take any more hits
2670 my->monsterState = MONSTER_STATE_LICHICE_DIE; // lich death state
2671 my->monsterSpecialTimer = 180;
2672 my->monsterAttack = 0;
2673 my->monsterAttackTime = 0;
2674 serverUpdateEntitySkill(my, 8);
2675 serverUpdateEntitySkill(my, 9);
2676 serverUpdateEntitySkill(my, 0);
2677 for ( c = 0; c < NUMEFFECTS; ++c )
2678 {
2679 myStats->EFFECTS[c] = false;
2680 myStats->EFFECTS_TIMERS[c] = 0;
2681 }
2682 break;
2683 case SENTRYBOT:
2684 case SPELLBOT:
2685 sentryBotDie(my);
2686 break;
2687 case GYROBOT:
2688 gyroBotDie(my);
2689 break;
2690 case DUMMYBOT:
2691 dummyBotDie(my);
2692 break;
2693 default:
2694 break; //This should never be reached.
2695 }
2696 return;
2697 }
2698
2699 if ( multiplayer != CLIENT )
2700 {
2701 my->effectTimes();
2702 }
2703
2704 if ( ticks % TICKS_PER_SECOND == 0 )
2705 {
2706 my->checkGroundForItems();
2707 }
2708
2709 // check to see if monster can scream again
2710 if ( MONSTER_SOUND != NULL )
2711 {
2712 #ifdef USE_FMOD
2713 FMOD_BOOL playing;
2714 FMOD_Channel_IsPlaying(MONSTER_SOUND, &playing);
2715 if (!playing)
2716 {
2717 MONSTER_SOUND = NULL;
2718 }
2719 else
2720 {
2721 for ( c = 0; c < numsounds; c++ )
2722 {
2723 /*if( sounds[c] == Mix_GetChunk(MONSTER_SOUND) && ( c<MONSTER_SPOTSND || c>=MONSTER_SPOTSND+MONSTER_SPOTVAR ) ) { //TODO: Is this necessary? If so, port it to FMOD or find a workaround.
2724 MONSTER_SOUND = -1;
2725 break;
2726 }*/
2727 FMOD_BOOL playing = true;
2728 FMOD_Channel_IsPlaying(MONSTER_SOUND, &playing);
2729 if (!playing)
2730 {
2731 MONSTER_SOUND = NULL;
2732 break;
2733 }
2734 }
2735 }
2736 #elif defined USE_OPENAL
2737 ALboolean playing;
2738 OPENAL_Channel_IsPlaying(MONSTER_SOUND, &playing);
2739 if (!playing)
2740 {
2741 MONSTER_SOUND = NULL;
2742 }
2743 else
2744 {
2745 for ( c = 0; c < numsounds; c++ )
2746 {
2747 ALboolean playing = true;
2748 OPENAL_Channel_IsPlaying(MONSTER_SOUND, &playing);
2749 if (!playing)
2750 {
2751 MONSTER_SOUND = NULL;
2752 break;
2753 }
2754 }
2755 }
2756 #endif
2757 }
2758
2759 // remove broken equipment
2760 if ( myStats->helmet != NULL )
2761 {
2762 if ( myStats->helmet->status == BROKEN )
2763 {
2764 free(myStats->helmet);
2765 myStats->helmet = NULL;
2766 }
2767 }
2768 if ( myStats->breastplate != NULL )
2769 {
2770 if ( myStats->breastplate->status == BROKEN )
2771 {
2772 free(myStats->breastplate);
2773 myStats->breastplate = NULL;
2774 }
2775 }
2776 if ( myStats->gloves != NULL )
2777 {
2778 if ( myStats->gloves->status == BROKEN )
2779 {
2780 free(myStats->gloves);
2781 myStats->gloves = NULL;
2782 }
2783 }
2784 if ( myStats->shoes != NULL )
2785 {
2786 if ( myStats->shoes->status == BROKEN )
2787 {
2788 free(myStats->shoes);
2789 myStats->shoes = NULL;
2790 }
2791 }
2792 if ( myStats->shield != NULL )
2793 {
2794 if ( myStats->shield->status == BROKEN )
2795 {
2796 free(myStats->shield);
2797 myStats->shield = NULL;
2798 }
2799 }
2800 if ( myStats->weapon != NULL )
2801 {
2802 if ( myStats->weapon->status == BROKEN )
2803 {
2804 free(myStats->weapon);
2805 myStats->weapon = NULL;
2806 }
2807 }
2808 if ( myStats->cloak != NULL )
2809 {
2810 if ( myStats->cloak->status == BROKEN )
2811 {
2812 free(myStats->cloak);
2813 myStats->cloak = NULL;
2814 }
2815 }
2816 if ( myStats->amulet != NULL )
2817 {
2818 if ( myStats->amulet->status == BROKEN )
2819 {
2820 free(myStats->amulet);
2821 myStats->amulet = NULL;
2822 }
2823 }
2824 if ( myStats->ring != NULL )
2825 {
2826 if ( myStats->ring->status == BROKEN )
2827 {
2828 free(myStats->ring);
2829 myStats->ring = NULL;
2830 }
2831 }
2832 if ( myStats->mask != NULL )
2833 {
2834 if ( myStats->mask->status == BROKEN )
2835 {
2836 free(myStats->mask);
2837 myStats->mask = NULL;
2838 }
2839 }
2840
2841 // calculate weight
2842 Sint32 weight = 0;
2843 if ( myStats->helmet != NULL )
2844 {
2845 weight += items[myStats->helmet->type].weight * myStats->helmet->count;
2846 }
2847 if ( myStats->breastplate != NULL )
2848 {
2849 weight += items[myStats->breastplate->type].weight * myStats->breastplate->count;
2850 }
2851 if ( myStats->gloves != NULL )
2852 {
2853 weight += items[myStats->gloves->type].weight * myStats->gloves->count;
2854 }
2855 if ( myStats->shoes != NULL )
2856 {
2857 weight += items[myStats->shoes->type].weight * myStats->shoes->count;
2858 }
2859 if ( myStats->shield != NULL )
2860 {
2861 if ( itemTypeIsQuiver(myStats->shield->type) )
2862 {
2863 weight += std::max(1, items[myStats->shield->type].weight * myStats->shield->count / 5);
2864 }
2865 else
2866 {
2867 weight += items[myStats->shield->type].weight * myStats->shield->count;
2868 }
2869 }
2870 if ( myStats->weapon != NULL )
2871 {
2872 weight += items[myStats->weapon->type].weight * myStats->weapon->count;
2873 }
2874 if ( myStats->cloak != NULL )
2875 {
2876 weight += items[myStats->cloak->type].weight * myStats->cloak->count;
2877 }
2878 if ( myStats->amulet != NULL )
2879 {
2880 weight += items[myStats->amulet->type].weight * myStats->amulet->count;
2881 }
2882 if ( myStats->ring != NULL )
2883 {
2884 weight += items[myStats->ring->type].weight * myStats->ring->count;
2885 }
2886 if ( myStats->mask != NULL )
2887 {
2888 weight += items[myStats->mask->type].weight * myStats->mask->count;
2889 }
2890 weight += myStats->GOLD / 100;
2891 weight /= 2; // on monsters weight shouldn't matter so much
2892 double weightratio = (1000 + my->getSTR() * 100 - weight) / (double)(1000 + my->getSTR() * 100);
2893 weightratio = fmin(fmax(0, weightratio), 1);
2894 // determine if I have a ranged weapon or not
2895 hasrangedweapon = my->hasRangedWeapon();
2896
2897 // effect of a ring of conflict
2898 bool ringconflict = false;
2899 Entity* ringConflictHolder = nullptr;
2900 if ( myStats->type != LICH_ICE && myStats->type != LICH_FIRE )
2901 {
2902 for ( node = map.creatures->first; node != nullptr; node = node->next ) //Only creatures can wear rings, so don't search map.entities.
2903 {
2904 Entity* tempentity = (Entity*)node->element;
2905 if ( tempentity != nullptr && tempentity != my )
2906 {
2907 Stat* tempstats = tempentity->getStats();
2908 if ( tempstats && tempstats->ring && tempstats->ring->type == RING_CONFLICT )
2909 {
2910 int conflictRange = 5 * TOUCHRANGE;
2911 if ( sqrt(pow(my->x - tempentity->x, 2) + pow(my->y - tempentity->y, 2)) < conflictRange )
2912 {
2913 tangent = atan2(tempentity->y - my->y, tempentity->x - my->x);
2914 lineTrace(my, my->x, my->y, tangent, conflictRange, 0, false);
2915 ringconflict = true;
2916 if ( hit.entity == tempentity )
2917 {
2918 ringConflictHolder = tempentity;
2919 }
2920 break;
2921 }
2922 }
2923 }
2924 }
2925 }
2926
2927 // invisibility
2928 bool handleinvisible = true;
2929 switch ( myStats->type )
2930 {
2931 case HUMAN:
2932 case GOBLIN:
2933 case SKELETON:
2934 case GNOME:
2935 case KOBOLD:
2936 case AUTOMATON:
2937 case INSECTOID:
2938 case GOATMAN:
2939 case INCUBUS:
2940 case SHADOW:
2941 case VAMPIRE:
2942 case SUCCUBUS:
2943 case SHOPKEEPER:
2944 case LICH_FIRE:
2945 case LICH_ICE:
2946 case SENTRYBOT:
2947 case SPELLBOT:
2948 case GYROBOT:
2949 case DUMMYBOT:
2950 handleinvisible = false;
2951 break;
2952 default:
2953 break;
2954 }
2955 if ( handleinvisible )
2956 {
2957 //TODO: Should this use isInvisible()?
2958 if ( myStats->EFFECTS[EFF_INVISIBLE] )
2959 {
2960 my->flags[INVISIBLE] = true;
2961 for ( node = list_Node(&my->children, 2); node != NULL; node = node->next )
2962 {
2963 Entity* entity = (Entity*)node->element;
2964 entity->flags[INVISIBLE] = true;
2965 }
2966 }
2967 else
2968 {
2969 my->flags[INVISIBLE] = false;
2970 for ( node = list_Node(&my->children, 2); node != NULL; node = node->next )
2971 {
2972 Entity* entity = (Entity*)node->element;
2973 entity->flags[INVISIBLE] = false;
2974 }
2975 }
2976 }
2977
2978 // chatting
2979 char namesays[64];
2980 if ( !strcmp(myStats->name, "") || monsterNameIsGeneric(*myStats) )
2981 {
2982 if ( monsterNameIsGeneric(*myStats) )
2983 {
2984 snprintf(namesays, 63, language[1302], myStats->name);
2985 }
2986 else if ( myStats->type < KOBOLD ) //Original monster count
2987 {
2988 snprintf(namesays, 63, language[513], language[90 + myStats->type]);
2989 }
2990 else if ( myStats->type >= KOBOLD ) //New monsters
2991 {
2992 snprintf(namesays, 63, language[513], language[2000 + myStats->type - KOBOLD]);
2993 }
2994 }
2995 else
2996 {
2997 snprintf(namesays, 63, language[1302], myStats->name);
2998 }
2999 int monsterclicked = -1;
3000 for (i = 0; i < MAXPLAYERS; i++)
3001 {
3002 if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
3003 {
3004 if (inrange[i])
3005 {
3006 monsterclicked = i;
3007 }
3008 }
3009 }
3010 if ( MONSTER_CLICKED )
3011 {
3012 monsterclicked = MONSTER_CLICKED - 1;
3013 MONSTER_CLICKED = 0;
3014 }
3015 if ( monsterclicked >= 0 && monsterclicked < MAXPLAYERS )
3016 {
3017 if ( !my->isMobile() )
3018 {
3019 // message the player, "the %s doesn't respond"
3020 messagePlayerMonsterEvent(monsterclicked, 0xFFFFFFFF, *myStats, language[514], language[515], MSG_COMBAT);
3021 }
3022 else
3023 {
3024 if (my->monsterTarget == players[monsterclicked]->entity->getUID() && my->monsterState != 4)
3025 {
3026 // angry at the player, "En Guarde!"
3027 switch (myStats->type)
3028 {
3029 case HUMAN:
3030 messagePlayer(monsterclicked, language[516 + rand() % 4], namesays);
3031 break;
3032 case SHOPKEEPER:
3033 if ( stats[monsterclicked] )
3034 {
3035 if ( stats[monsterclicked]->type != HUMAN )
3036 {
3037 if ( stats[monsterclicked]->type < KOBOLD ) //Original monster count
3038 {
3039 messagePlayer(monsterclicked, language[3243],
3040 namesays, language[90 + stats[monsterclicked]->type]);
3041 }
3042 else if ( stats[monsterclicked]->type >= KOBOLD ) //New monsters
3043 {
3044 messagePlayer(monsterclicked, language[3243], namesays,
3045 language[2000 + (stats[monsterclicked]->type - KOBOLD)]);
3046 }
3047 }
3048 else
3049 {
3050 messagePlayer(monsterclicked, language[516 + rand() % 4], namesays);
3051 }
3052 }
3053 else
3054 {
3055 messagePlayer(monsterclicked, language[516 + rand() % 4], namesays);
3056 }
3057 break;
3058 default:
3059 break;
3060 }
3061 }
3062 else if (my->monsterState == MONSTER_STATE_TALK)
3063 {
3064 // for shopkeepers trading with a player, "I am somewhat busy now."
3065 if (my->monsterTarget != players[monsterclicked]->entity->getUID())
3066 {
3067 switch (myStats->type)
3068 {
3069 case SHOPKEEPER:
3070 case HUMAN:
3071 messagePlayer(monsterclicked, language[520 + rand() % 4], namesays);
3072 break;
3073 default:
3074 messagePlayer(monsterclicked, language[524], namesays);
3075 break;
3076 }
3077 }
3078 }
3079 else
3080 {
3081 // handle followers/trading
3082 if ( myStats->type != SHOPKEEPER )
3083 {
3084 if ( myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 )
3085 {
3086 makeFollower(monsterclicked, ringconflict, namesays, my, myStats);
3087 }
3088 else
3089 {
3090 handleMonsterChatter(monsterclicked, ringconflict, namesays, my, myStats);
3091 }
3092 my->lookAtEntity(*players[monsterclicked]->entity);
3093 }
3094 else
3095 {
3096 if ( myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 ) // mysterious merchant
3097 {
3098 bool hasOrb = false;
3099 for ( node_t* node = myStats->inventory.first; node; node = node->next )
3100 {
3101 Item* item = (Item*)node->element;
3102 if ( item && (item->type == ARTIFACT_ORB_BLUE
3103 || item->type == ARTIFACT_ORB_GREEN
3104 || item->type == ARTIFACT_ORB_RED)
3105 )
3106 {
3107 hasOrb = true;
3108 break;
3109 }
3110 }
3111 if ( !hasOrb )
3112 {
3113 handleMonsterChatter(monsterclicked, ringconflict, namesays, my, myStats);
3114 my->lookAtEntity(*players[monsterclicked]->entity);
3115 }
3116 else
3117 {
3118 // shopkeepers start trading
3119 startTradingServer(my, monsterclicked);
3120 }
3121 }
3122 else if ( players[monsterclicked] && players[monsterclicked]->entity )
3123 {
3124 if ( !my->checkEnemy(players[monsterclicked]->entity) )
3125 {
3126 // shopkeepers start trading
3127 startTradingServer(my, monsterclicked);
3128 if ( stats[monsterclicked] && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->appearance == 0
3129 && stats[monsterclicked]->playerRace == RACE_AUTOMATON )
3130 {
3131 achievementObserver.updatePlayerAchievement(monsterclicked, AchievementObserver::Achievement::BARONY_ACH_REAL_BOY,
3132 AchievementObserver::AchievementEvent::REAL_BOY_SHOP);
3133 }
3134 }
3135 }
3136 }
3137 }
3138 }
3139 }
3140
3141 bool isIllusionTaunt = false;
3142 if ( myStats->type == INCUBUS && !strncmp(myStats->name, "inner demon", strlen("inner demon")) )
3143 {
3144 isIllusionTaunt = true;
3145 hasrangedweapon = false;
3146 Entity* myTarget = uidToEntity(static_cast<Uint32>(my->monsterIllusionTauntingThisUid));
3147 if ( myTarget )
3148 {
3149 if ( my->ticks % 50 == 0 )
3150 {
3151 if ( myTarget->monsterTarget != my->getUID() )
3152 {
3153 switch ( myTarget->getRace() )
3154 {
3155 case LICH:
3156 case DEVIL:
3157 case LICH_FIRE:
3158 case LICH_ICE:
3159 case MINOTAUR:
3160 break;
3161 default:
3162 myTarget->monsterAcquireAttackTarget(*my, MONSTER_STATE_PATH);
3163 break;
3164 }
3165 }
3166 }
3167 if ( my->isMobile() && my->ticks > 10 )
3168 {
3169 if ( (my->monsterState != MONSTER_STATE_WAIT && my->monsterHitTime >= 30 && my->monsterHitTime <= 40)
3170 || (my->ticks >= 100 && my->monsterAttack == 0) )
3171 {
3172 my->monsterReleaseAttackTarget();
3173 my->attack(MONSTER_POSE_INCUBUS_TAUNT, 0, nullptr);
3174 }
3175 else if ( my->monsterState == MONSTER_STATE_WAIT )
3176 {
3177 my->monsterHitTime = HITRATE - 3;
3178 if ( entityDist(my, myTarget) > STRIKERANGE * 1.5 )
3179 {
3180 my->monsterState = MONSTER_STATE_PATH;
3181 my->monsterTarget = myTarget->getUID();
3182 my->monsterTargetX = myTarget->x;
3183 my->monsterTargetY = myTarget->y;
3184 }
3185 else
3186 {
3187 my->monsterState = MONSTER_STATE_ATTACK;
3188 my->monsterTarget = myTarget->getUID();
3189 my->monsterTargetX = myTarget->x;
3190 my->monsterTargetY = myTarget->y;
3191 }
3192 }
3193 }
3194 }
3195 else
3196 {
3197 my->modHP(-9999);
3198 }
3199 }
3200
3201 if ( my->isMobile() )
3202 {
3203 // ghouls rise out of the dirt :O
3204 if ( myStats->type == GHOUL )
3205 {
3206 if ( my->z > -.25 )
3207 {
3208 my->z -= .25;
3209 if ( my->z < -.25 )
3210 {
3211 my->z = -.25;
3212 }
3213 ghoulMoveBodyparts(my, myStats, 0);
3214 return;
3215 }
3216 }
3217
3218 // being bumped by someone friendly
3219 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
3220 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
3221 {
3222 list_t* currentList = *it;
3223 for ( node2 = currentList->first; node2 != nullptr; node2 = node2->next ) //Can't convert to map.creatures because of doorframes.
3224 {
3225 entity = (Entity*)node2->element;
3226 if ( entity == my )
3227 {
3228 continue;
3229 }
3230 if ( entity->behavior != &actMonster && entity->behavior != &actPlayer && entity->behavior != &actDoorFrame )
3231 {
3232 continue;
3233 }
3234 if ( entityInsideEntity(my, entity) && entity->getRace() != GYROBOT )
3235 {
3236 if ( entity->behavior != &actDoorFrame )
3237 {
3238 double tangent = atan2(my->y - entity->y, my->x - entity->x);
3239 MONSTER_VELX = cos(tangent) * .1;
3240 MONSTER_VELY = sin(tangent) * .1;
3241 }
3242 else
3243 {
3244 if ( entity->yaw >= -0.1 && entity->yaw <= 0.1 )
3245 {
3246 // east/west doorway
3247 if ( my->y < floor(my->y / 16) * 16 + 8 )
3248 {
3249 // slide south
3250 MONSTER_VELX = 0;
3251 MONSTER_VELY = .25;
3252 }
3253 else
3254 {
3255 // slide north
3256 MONSTER_VELX = 0;
3257 MONSTER_VELY = -.25;
3258 }
3259 }
3260 else
3261 {
3262 // north/south doorway
3263 if ( my->x < floor(my->x / 16) * 16 + 8 )
3264 {
3265 // slide east
3266 MONSTER_VELX = .25;
3267 MONSTER_VELY = 0;
3268 }
3269 else
3270 {
3271 // slide west
3272 MONSTER_VELX = -.25;
3273 MONSTER_VELY = 0;
3274 }
3275 }
3276 //messagePlayer(0, "path: %d", my->monsterPathCount);
3277 ++my->monsterPathCount;
3278 if ( my->monsterPathCount > 50 )
3279 {
3280 my->monsterPathCount = 0;
3281 monsterMoveAside(my, my);
3282 }
3283 }
3284
3285
3286 if ( (entity->sprite == 274 || entity->sprite == 646
3287 || entity->sprite == 650 || entity->sprite == 304)
3288 && entity->flags[PASSABLE] == true )
3289 {
3290 // LICH/LICH_FIRE/LICH_ICE/DEVIL
3291 // If these guys are PASSABLE then they're either dying or some other animation
3292 // Move the monster inside the boss, but don't set PASSABLE to false again.
3293 clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
3294 }
3295 else
3296 {
3297 entity->flags[PASSABLE] = true;
3298 clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
3299 entity->flags[PASSABLE] = false;
3300 }
3301 }
3302 }
3303 }
3304
3305 if ( myStats->type != LICH
3306 && myStats->type != DEVIL
3307 && myStats->type != LICH_ICE
3308 && myStats->type != LICH_FIRE
3309 && my->monsterSpecialTimer > 0 )
3310 {
3311 --my->monsterSpecialTimer;
3312 }
3313
3314 if ( my->monsterAllySpecialCooldown > 0 )
3315 {
3316 --my->monsterAllySpecialCooldown;
3317 }
3318
3319 if ( myStats->type == AUTOMATON )
3320 {
3321 my->automatonRecycleItem();
3322 }
3323
3324 if ( myStats->EFFECTS[EFF_PACIFY] || myStats->EFFECTS[EFF_FEAR] )
3325 {
3326 my->monsterHitTime = HITRATE / 2; // stop this incrementing to HITRATE but leave monster ready to strike shortly after.
3327 }
3328
3329 if ( my->monsterDefend != MONSTER_DEFEND_NONE )
3330 {
3331 if ( my->monsterState != MONSTER_STATE_ATTACK
3332 || myStats->shield == nullptr )
3333 {
3334 myStats->defending = false;
3335 my->monsterDefend = 0;
3336 serverUpdateEntitySkill(my, 47);
3337 }
3338 else if ( my->monsterAttack == 0 )
3339 {
3340 myStats->defending = true;
3341 }
3342 }
3343 else
3344 {
3345 myStats->defending = false;
3346 }
3347
3348 /*if ( myStats->defending )
3349 {
3350 messagePlayer(0, "defending!");
3351 }*/
3352
3353 //if ( myStats->type == DEVIL )
3354 //{
3355 // std::string state_string;
3356
3357 // switch(my->monsterState)
3358 // {
3359 // case MONSTER_STATE_WAIT:
3360 // state_string = "WAIT";
3361 // break;
3362 // case MONSTER_STATE_ATTACK:
3363 // state_string = "CHARGE";
3364 // break;
3365 // case MONSTER_STATE_PATH:
3366 // state_string = "PATH";
3367 // break;
3368 // case MONSTER_STATE_HUNT:
3369 // state_string = "HUNT";
3370 // break;
3371 // case MONSTER_STATE_TALK:
3372 // state_string = "TALK";
3373 // break;
3374 // default:
3375 // state_string = std::to_string(my->monsterState);
3376 // //state_string = "Unknown state";
3377 // break;
3378 // }
3379
3380 // messagePlayer(0, "%s, ATK: %d hittime:%d, atktime:%d, (%d|%d), timer:%d",
3381 // state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message.
3382 //}
3383
3384 //Begin state machine
3385 if ( my->monsterState == MONSTER_STATE_WAIT ) //Begin wait state
3386 {
3387 //my->monsterTarget = -1; //TODO: Setting it to -1 = Bug? -1 may not work properly for cases such as: if ( !my->monsterTarget )
3388 my->monsterReleaseAttackTarget();
3389 if ( !myStats->EFFECTS[EFF_KNOCKBACK] )
3390 {
3391 MONSTER_VELX = 0;
3392 MONSTER_VELY = 0;
3393 }
3394 else
3395 {
3396 // do knockback movement
3397 my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir, weightratio);
3398 if ( abs(MONSTER_VELX) > 0.01 || abs(MONSTER_VELY) > 0.01 )
3399 {
3400 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
3401 my->handleKnockbackDamage(*myStats, hit.entity);
3402 }
3403 }
3404 if ( myReflex && !myStats->EFFECTS[EFF_DISORIENTED] && !isIllusionTaunt )
3405 {
3406 if ( myStats->EFFECTS[EFF_FEAR] && my->monsterFearfulOfUid != 0 )
3407 {
3408 Entity* scaryEntity = uidToEntity(my->monsterFearfulOfUid);
3409 if ( scaryEntity )
3410 {
3411 my->monsterAcquireAttackTarget(*scaryEntity, MONSTER_STATE_PATH);
3412 my->lookAtEntity(*scaryEntity);
3413 if ( previousMonsterState != my->monsterState )
3414 {
3415 serverUpdateEntitySkill(my, 0);
3416 }
3417 return;
3418 }
3419 }
3420
3421 for ( node2 = map.creatures->first; node2 != nullptr; node2 = node2->next ) //So my concern is that this never explicitly checks for actMonster or actPlayer, instead it relies on there being stats. Now, only monsters and players have stats, so that's not a problem, except...actPlayerLimb can still return a stat from getStat()! D: Meh, if you can find the player's hand, you can find the actual player too, so it shouldn't be an issue.
3422 {
3423 entity = (Entity*)node2->element;
3424 if ( entity == my || entity->flags[PASSABLE] )
3425 {
3426 continue;
3427 }
3428 hitstats = entity->getStats();
3429 if ( hitstats != nullptr )
3430 {
3431 if ( (my->checkEnemy(entity) || my->monsterTarget == entity->getUID() || ringconflict) )
3432 {
3433 tangent = atan2( entity->y - my->y, entity->x - my->x );
3434 dir = my->yaw - tangent;
3435 while ( dir >= PI )
3436 {
3437 dir -= PI * 2;
3438 }
3439 while ( dir < -PI )
3440 {
3441 dir += PI * 2;
3442 }
3443
3444 // skip if light level is too low and distance is too high
3445 int light = entity->entityLightAfterReductions(*hitstats, my);
3446 if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
3447 {
3448 //See invisible.
3449 light = 1000;
3450 }
3451 else if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
3452 {
3453 light += 150;
3454 }
3455 double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
3456
3457 real_t monsterVisionRange = sightranges[myStats->type];
3458 if ( hitstats->type == DUMMYBOT )
3459 {
3460 monsterVisionRange = std::min(monsterVisionRange, 96.0);
3461 }
3462
3463 if ( targetdist > monsterVisionRange )
3464 {
3465 continue;
3466 }
3467 if ( targetdist > TOUCHRANGE && targetdist > light )
3468 {
3469 if ( !levitating )
3470 {
3471 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, true);
3472 }
3473 else
3474 {
3475 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
3476 }
3477 if ( hit.entity == entity )
3478 if ( rand() % 100 == 0 )
3479 {
3480 entity->increaseSkill(PRO_STEALTH);
3481 }
3482 continue;
3483 }
3484 bool visiontest = false;
3485 if ( hitstats->type == DUMMYBOT || myStats->type == SENTRYBOT || myStats->type == SPELLBOT
3486 || (ringConflictHolder && ringConflictHolder == entity) )
3487 {
3488 if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
3489 {
3490 visiontest = true;
3491 }
3492 }
3493 else if ( myStats->type != SPIDER )
3494 {
3495 if ( dir >= -7 * PI / 16 && dir <= 7 * PI / 16 )
3496 {
3497 visiontest = true;
3498 }
3499 }
3500 else
3501 {
3502 if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
3503 {
3504 visiontest = true;
3505 }
3506 }
3507
3508 if ( visiontest ) // vision cone
3509 {
3510 if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
3511 {
3512 //See invisible
3513 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
3514 }
3515 else
3516 {
3517 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, IGNORE_ENTITIES, false);
3518 }
3519 if ( !hit.entity )
3520 {
3521 lineTrace(my, my->x, my->y, tangent, TOUCHRANGE, 0, false);
3522 }
3523 if ( hit.entity == entity )
3524 {
3525 // charge state
3526 Entity& attackTarget = *hit.entity;
3527 my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
3528
3529 if ( MONSTER_SOUND == nullptr )
3530 {
3531 if ( myStats->type != MINOTAUR )
3532 {
3533 if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
3534 {
3535 sentrybotPickSpotNoise(my, myStats);
3536 }
3537 else
3538 {
3539 MONSTER_SOUND = playSoundEntity(my, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128);
3540 }
3541 }
3542 else
3543 {
3544 int c;
3545 for ( c = 0; c < MAXPLAYERS; ++c )
3546 {
3547 if ( c == 0 )
3548 {
3549 MONSTER_SOUND = playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
3550 }
3551 else
3552 {
3553 playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
3554 }
3555 }
3556 }
3557 }
3558
3559 if ( entity != nullptr )
3560 {
3561 if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT )
3562 {
3563 assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
3564 assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
3565 }
3566 }
3567
3568 // alert other monsters of this enemy's presence //TODO: Refactor into its own function.
3569 for ( node = map.creatures->first; node != nullptr; node = node->next )
3570 {
3571 entity = (Entity*)node->element;
3572 if ( entity->behavior == &actMonster )
3573 {
3574 hitstats = entity->getStats();
3575 if ( hitstats != nullptr )
3576 {
3577 if ( entity->checkFriend(my) )
3578 {
3579 if ( entity->skill[0] == MONSTER_STATE_WAIT ) // monster is waiting
3580 {
3581 tangent = atan2( entity->y - my->y, entity->x - my->x );
3582 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
3583 if ( hit.entity == entity )
3584 {
3585 entity->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_PATH);
3586 }
3587 }
3588 }
3589 }
3590 }
3591 }
3592 break;
3593 }
3594 }
3595 }
3596 }
3597 }
3598 }
3599
3600 // minotaurs and liches chase players relentlessly.
3601 if (myReflex)
3602 {
3603 if (myStats->type == MINOTAUR
3604 || myStats->type == LICH
3605 || myStats->type == LICH_FIRE
3606 || myStats->type == LICH_ICE
3607 || (myStats->type == CREATURE_IMP && strstr(map.name, "Boss") && !my->monsterAllyGetPlayerLeader())
3608 || (myStats->type == AUTOMATON && strstr(myStats->name, "corrupted automaton"))
3609 || (myStats->type == SHADOW && !strncmp(map.name, "Hell Boss", 9) && uidToEntity(my->parent) && uidToEntity(my->parent)->getRace() == DEVIL) )
3610 {
3611 double distToPlayer = 0;
3612 int c, playerToChase = -1;
3613 for (c = 0; c < MAXPLAYERS; c++)
3614 {
3615 if (players[c] && players[c]->entity)
3616 {
3617 list_t* playerPath = generatePath((int)floor(my->x / 16), (int)floor(my->y / 16),
3618 (int)floor(players[c]->entity->x / 16), (int)floor(players[c]->entity->y / 16), my, players[c]->entity);
3619 if ( playerPath == NULL )
3620 {
3621 continue;
3622 }
3623 else
3624 {
3625 list_FreeAll(playerPath);
3626 free(playerPath);
3627 }
3628 if (!distToPlayer)
3629 {
3630 distToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
3631 playerToChase = c;
3632 }
3633 else
3634 {
3635 double newDistToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
3636 if (newDistToPlayer < distToPlayer)
3637 {
3638 distToPlayer = newDistToPlayer;
3639 playerToChase = c;
3640 }
3641 }
3642 }
3643 }
3644 if ( playerToChase >= 0 && players[playerToChase] && players[playerToChase]->entity )
3645 {
3646 if ( myStats->type == SHADOW )
3647 {
3648 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_ATTACK);
3649 }
3650 else
3651 {
3652 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_PATH);
3653 }
3654 if ( previousMonsterState != my->monsterState )
3655 {
3656 serverUpdateEntitySkill(my, 0);
3657 }
3658 return;
3659 }
3660 }
3661 else if ( myStats->type == SHADOW && my->monsterTarget && my->monsterState != MONSTER_STATE_ATTACK )
3662 {
3663 //Fix shadow state.
3664 my->monsterState = MONSTER_STATE_PATH;
3665 //my->monsterTargetX = my->monsterTarget.x;
3666 //my->monsterTargetY = my->monsterTarget.y;
3667 serverUpdateEntitySkill(my, 0); //Update monster state because it changed.
3668 return;
3669 }
3670 }
3671
3672 // follow the leader :)
3673 if ( myStats->leader_uid != 0
3674 && my->monsterAllyState == ALLY_STATE_DEFAULT
3675 && my->getUID() % TICKS_PER_SECOND == ticks % TICKS_PER_SECOND
3676 && !myStats->EFFECTS[EFF_FEAR]
3677 && !myStats->EFFECTS[EFF_DISORIENTED]
3678 && !isIllusionTaunt
3679 && !monsterIsImmobileTurret(my, myStats) )
3680 {
3681 Entity* leader = uidToEntity(myStats->leader_uid);
3682 if ( leader )
3683 {
3684 real_t followx = leader->x;
3685 real_t followy = leader->y;
3686 if ( myStats->type == GYROBOT )
3687 {
3688 // follow ahead of the leader.
3689 real_t startx = leader->x;
3690 real_t starty = leader->y;
3691 // draw line from the leaders direction until we hit a wall or 48 dist
3692 real_t previousx = startx;
3693 real_t previousy = starty;
3694 for ( int iterations = 0; iterations < 16; ++iterations)
3695 {
3696 startx += 4 * cos(leader->yaw);
3697 starty += 4 * sin(leader->yaw);
3698 int index = (static_cast<int>(starty + 16 * sin(leader->yaw)) >> 4) * MAPLAYERS + (static_cast<int>(startx + 16 * cos(leader->yaw)) >> 4) * MAPLAYERS * map.height;
3699 if ( !map.tiles[OBSTACLELAYER + index] )
3700 {
3701 // store the last known good coordinate
3702 previousx = startx;
3703 previousy = starty;
3704 }
3705 else if ( map.tiles[OBSTACLELAYER + index] )
3706 {
3707 // hit a wall.
3708 break;
3709 }
3710 if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > WAIT_FOLLOWDIST )
3711 {
3712 break;
3713 }
3714 }
3715 followx = previousx;
3716 followy = previousy;
3717 // createParticleFollowerCommand(previousx, previousy, 0, 174); debug particle
3718 }
3719 double dist = sqrt(pow(my->x - followx, 2) + pow(my->y - followy, 2));
3720
3721 if ( dist > WAIT_FOLLOWDIST )
3722 {
3723 bool doFollow = true;
3724 if ( my->monsterTarget != 0 )
3725 {
3726 doFollow = my->isFollowerFreeToPathToPlayer(myStats);
3727 }
3728
3729 if ( doFollow )
3730 {
3731 my->monsterReleaseAttackTarget();
3732 if ( my->monsterSetPathToLocation(static_cast<int>(followx) / 16, static_cast<int>(followy) / 16, 2) )
3733 {
3734 my->monsterState = MONSTER_STATE_HUNT; // hunt state
3735 }
3736 if ( previousMonsterState != my->monsterState )
3737 {
3738 serverUpdateEntitySkill(my, 0);
3739 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
3740 {
3741 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
3742 }
3743 }
3744 return;
3745 }
3746 }
3747 else if ( myStats->type != GYROBOT )
3748 {
3749 tangent = atan2( leader->y - my->y, leader->x - my->x );
3750 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, true);
3751 if ( hit.entity != leader )
3752 {
3753 bool doFollow = true;
3754 if ( my->monsterTarget != 0 )
3755 {
3756 doFollow = my->isFollowerFreeToPathToPlayer(myStats);
3757 }
3758 if ( doFollow )
3759 {
3760 my->monsterReleaseAttackTarget();
3761 if ( my->monsterSetPathToLocation(static_cast<int>(leader->x) / 16, static_cast<int>(leader->y) / 16, 1) )
3762 {
3763 my->monsterState = MONSTER_STATE_HUNT; // hunt state
3764 }
3765 if ( previousMonsterState != my->monsterState )
3766 {
3767 serverUpdateEntitySkill(my, 0);
3768 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
3769 {
3770 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
3771 }
3772 }
3773 return;
3774 }
3775 }
3776 }
3777 }
3778 }
3779
3780 // look
3781 my->monsterLookTime++;
3782 if ( my->monsterLookTime >= 120
3783 && myStats->type != LICH
3784 && myStats->type != DEVIL
3785 && myStats->type != LICH_FIRE
3786 && myStats->type != LICH_ICE )
3787 {
3788 my->monsterLookTime = 0;
3789 my->monsterMoveTime--;
3790 if ( myStats->type != GHOUL && (myStats->type != SPIDER || (myStats->type == SPIDER && my->monsterAllyGetPlayerLeader()))
3791 && !myStats->EFFECTS[EFF_FEAR] && !isIllusionTaunt )
3792 {
3793 if ( monsterIsImmobileTurret(my, myStats) )
3794 {
3795 if ( abs(my->monsterSentrybotLookDir) > 0.001 )
3796 {
3797 my->monsterLookDir = my->monsterSentrybotLookDir + (-30 + rand() % 61) * PI / 180;
3798 }
3799 else
3800 {
3801 my->monsterLookDir = (rand() % 360) * PI / 180;
3802 }
3803 }
3804 else
3805 {
3806 my->monsterLookDir = (rand() % 360) * PI / 180;
3807 }
3808 }
3809 if ( !myStats->EFFECTS[EFF_FEAR] && my->monsterTarget == 0 && my->monsterState == MONSTER_STATE_WAIT && my->monsterAllyGetPlayerLeader() )
3810 {
3811 // allies should try intelligently scan for enemies in radius.
3812 if ( monsterIsImmobileTurret(my, myStats) && myStats->LVL < 5 )
3813 {
3814 // don't scan cause dumb robot.
3815 }
3816 else
3817 {
3818 real_t dist = sightranges[myStats->type];
3819 for ( node = map.creatures->first; node != nullptr; node = node->next )
3820 {
3821 Entity* target = (Entity*)node->element;
3822 if ( target->behavior == &actMonster && my->checkEnemy(target) )
3823 {
3824 real_t oldDist = dist;
3825 dist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
3826 if ( dist < sightranges[myStats->type] && dist <= oldDist )
3827 {
3828 double tangent = atan2(target->y - my->y, target->x - my->x);
3829 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
3830 if ( hit.entity == target )
3831 {
3832 //my->monsterLookTime = 1;
3833 //my->monsterMoveTime = rand() % 10 + 1;
3834 my->monsterLookDir = tangent;
3835 if ( monsterIsImmobileTurret(my, myStats) )
3836 {
3837 if ( myStats->LVL >= 10 )
3838 {
3839 my->monsterHitTime = HITRATE * 2 - 20;
3840 }
3841 }
3842 break;
3843 }
3844 }
3845 }
3846 }
3847 }
3848 }
3849 if ( rand() % 3 == 0 && !isIllusionTaunt )
3850 {
3851 if ( !MONSTER_SOUND )
3852 {
3853 if ( myStats->type != MINOTAUR )
3854 {
3855 if ( !my->monsterAllyGetPlayerLeader() || (my->monsterAllyGetPlayerLeader() && rand() % 3 == 0) || myStats->type == DUMMYBOT )
3856 {
3857 // idle sounds. if player follower, reduce noise frequency by 66%.
3858 MONSTER_SOUND = playSoundEntity(my, MONSTER_IDLESND + (rand() % MONSTER_IDLEVAR), 128);
3859 }
3860 }
3861 else
3862 {
3863 int c;
3864 for ( c = 0; c < MAXPLAYERS; c++ )
3865 {
3866 if ( c == 0 )
3867 {
3868 MONSTER_SOUND = playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
3869 }
3870 else
3871 {
3872 playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
3873 }
3874 }
3875 }
3876 }
3877 }
3878 }
3879 if ( my->monsterMoveTime == 0
3880 && (uidToEntity(myStats->leader_uid) == NULL || my->monsterAllyState == ALLY_STATE_DEFEND)
3881 && !myStats->EFFECTS[EFF_FEAR]
3882 && !myStats->EFFECTS[EFF_DISORIENTED]
3883 && !isIllusionTaunt
3884 && !(monsterIsImmobileTurret(my, myStats))
3885 && myStats->type != DEVIL )
3886 {
3887 std::vector<std::pair<int, int>> possibleCoordinates;
3888 my->monsterMoveTime = rand() % 30;
3889 int goodspots = 0;
3890 int centerX = static_cast<int>(my->x / 16); // grab the coordinates in small form.
3891 int centerY = static_cast<int>(my->y / 16); // grab the coordinates in small form.
3892 int lowerX = std::max<int>(0, centerX - (map.width / 2)); // assigned upper/lower x coords from entity start position.
3893 int upperX = std::min<int>(centerX + (map.width / 2), map.width);
3894
3895 int lowerY = std::max<int>(0, centerY - (map.height / 2)); // assigned upper/lower y coords from entity start position.
3896 int upperY = std::min<int>(centerY + (map.height / 2), map.height);
3897 //messagePlayer(0, "my x: %d, my y: %d, rangex: (%d-%d), rangey: (%d-%d)", centerX, centerY, lowerX, upperX, lowerY, upperY);
3898
3899 if ( myStats->type != SHOPKEEPER && (myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 && my->monsterAllyState == ALLY_STATE_DEFAULT) )
3900 {
3901 for ( x = lowerX; x < upperX; x++ )
3902 {
3903 for ( y = lowerY; y < upperY; y++ )
3904 {
3905 if ( !checkObstacle(x << 4, y << 4, my, NULL) )
3906 {
3907 goodspots++;
3908 possibleCoordinates.push_back(std::make_pair(x, y));
3909 }
3910 }
3911 }
3912 }
3913 else
3914 {
3915 for ( x = 0; x < map.width; x++ )
3916 {
3917 for ( y = 0; y < map.height; y++ )
3918 {
3919 if ( x << 4 >= my->monsterPathBoundaryXStart && x << 4 <= my->monsterPathBoundaryXEnd
3920 && y << 4 >= my->monsterPathBoundaryYStart && y << 4 <= my->monsterPathBoundaryYEnd )
3921 if ( !checkObstacle(x << 4, y << 4, my, NULL) )
3922 {
3923 goodspots++;
3924 possibleCoordinates.push_back(std::make_pair(x, y));
3925 }
3926 }
3927 }
3928 }
3929 if ( goodspots )
3930 {
3931 int chosenspot = rand() % goodspots;
3932 int currentspot = 0;
3933 bool foundit = false;
3934 x = possibleCoordinates.at(chosenspot).first;
3935 y = possibleCoordinates.at(chosenspot).second;
3936 //messagePlayer(0, "Chose distance: %.1fpercent", 100 * (sqrt(pow(my->x / 16 - (x), 2) + pow(my->y / 16 - (y), 2))) / sqrt(pow(map.height, 2) + pow(map.width, 2)));
3937 /*for ( x = 0; x < map.width; x++ )
3938 {
3939 for ( y = 0; y < map.height; y++ )
3940 {
3941 if ( !checkObstacle(x << 4, y << 4, my, NULL) )
3942 {
3943 if ( currentspot == chosenspot )
3944 {
3945 foundit = true;
3946 break;
3947 }
3948 else
3949 {
3950 currentspot++;
3951 }
3952 }
3953 }
3954 if ( foundit )
3955 {
3956 break;
3957 }
3958 }*/
3959 path = generatePath( (int)floor(my->x / 16), (int)floor(my->y / 16), x, y, my, NULL );
3960 if ( my->children.first != NULL )
3961 {
3962 list_RemoveNode(my->children.first);
3963 }
3964 node = list_AddNodeFirst(&my->children);
3965 node->element = path;
3966 node->deconstructor = &listDeconstructor;
3967 my->monsterState = MONSTER_STATE_HUNT; // hunt state
3968 }
3969 }
3970
3971 // rotate monster
3972 dir = my->monsterRotate();
3973
3974 if ( myStats->type == SHADOW && !uidToEntity(my->monsterTarget) && my->monsterSpecialTimer == 0 && my->monsterSpecialState == 0 && ticks%500 == 0 && rand()%5 == 0 )
3975 {
3976 //Random chance for a shadow to teleport around the map if it has nothing better to do.
3977 //messagePlayer(0, "Shadow idle telepotty.");
3978 my->monsterSpecialState = SHADOW_TELEPORT_ONLY;
3979 my->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_PASIVE_TELEPORT;
3980 my->shadowTeleportToTarget(nullptr, 3); // teleport in closer range
3981 my->monsterState = MONSTER_STATE_WAIT;
3982 }
3983 } //End wait state
3984 else if ( my->monsterState == MONSTER_STATE_ATTACK ) //Begin charge state
3985 {
3986 entity = uidToEntity(my->monsterTarget);
3987 if ( entity == nullptr )
3988 {
3989 my->monsterState = MONSTER_STATE_WAIT;
3990 if ( previousMonsterState != my->monsterState )
3991 {
3992 serverUpdateEntitySkill(my, 0);
3993 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
3994 {
3995 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
3996 }
3997 }
3998 if ( myStats->type == SHADOW )
3999 {
4000 //messagePlayer(0, "DEBUG: Shadow lost entity.");
4001 my->monsterReleaseAttackTarget(true);
4002 my->monsterState = MONSTER_STATE_WAIT;
4003 serverUpdateEntitySkill(my, 0); //Update state.
4004 }
4005 return;
4006 }
4007 if ( entity != nullptr )
4008 {
4009 if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT )
4010 {
4011 assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
4012 assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
4013 }
4014 }
4015 my->monsterTargetX = entity->x;
4016 my->monsterTargetY = entity->y;
4017 hitstats = entity->getStats();
4018
4019 if ( myStats->type == SHOPKEEPER && strncmp(map.name, "Mages Guild", 11) )
4020 {
4021 // shopkeepers hold a grudge against players
4022 for ( c = 0; c < MAXPLAYERS; ++c )
4023 {
4024 if ( players[c] && players[c]->entity )
4025 {
4026 if ( my->monsterTarget == players[c]->entity->getUID() )
4027 {
4028 if ( stats[c] && stats[c]->type == HUMAN && !stats[c]->EFFECTS[EFF_POLYMORPH] )
4029 {
4030 swornenemies[SHOPKEEPER][HUMAN] = true;
4031 monsterally[SHOPKEEPER][HUMAN] = false;
4032 }
4033 else if ( stats[c] && stats[c]->type == AUTOMATON && !stats[c]->EFFECTS[EFF_POLYMORPH] )
4034 {
4035 swornenemies[SHOPKEEPER][AUTOMATON] = true;
4036 monsterally[SHOPKEEPER][AUTOMATON] = false;
4037 }
4038 break;
4039 }
4040 }
4041 }
4042 }
4043
4044 if ( myStats->type != DEVIL )
4045 {
4046 // skip if light level is too low and distance is too high
4047 int light = entity->entityLightAfterReductions(*hitstats, my);
4048 if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
4049 {
4050 //See invisible.
4051 light = 1000;
4052 }
4053 else if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
4054 {
4055 light += 150;
4056 }
4057 double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
4058
4059 real_t monsterVisionRange = sightranges[myStats->type];
4060 if ( hitstats && hitstats->type == DUMMYBOT )
4061 {
4062 monsterVisionRange = std::min(monsterVisionRange, 96.0);
4063 }
4064 if ( myStats->EFFECTS[EFF_FEAR] )
4065 {
4066 targetdist = 0.0; // so we can always see our scary target.
4067 }
4068
4069 if ( targetdist > monsterVisionRange )
4070 {
4071 // if target has left my sight, decide whether or not to path or retreat (stay put).
4072 if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
4073 {
4074 my->monsterMoveTime = 0;
4075 my->monsterState = MONSTER_STATE_WAIT; // wait state
4076 }
4077 else
4078 {
4079 my->monsterState = MONSTER_STATE_PATH; // path state
4080 }
4081 }
4082 else
4083 {
4084 if ( targetdist > TOUCHRANGE && targetdist > light && myReflex )
4085 {
4086 tangent = atan2( my->monsterTargetY - my->y, my->monsterTargetX - my->x );
4087 if ( !levitating )
4088 {
4089 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, true);
4090 }
4091 else
4092 {
4093 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
4094 }
4095 if ( hit.entity == entity )
4096 {
4097 if ( rand() % 100 == 0 )
4098 {
4099 entity->increaseSkill(PRO_STEALTH);
4100 }
4101 }
4102 // if target is within sight range but light level is too low and out of melee range.
4103 // decide whether or not to path or retreat (stay put).
4104 if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
4105 {
4106 my->monsterMoveTime = 0;
4107 my->monsterState = MONSTER_STATE_WAIT; // wait state
4108 }
4109 else
4110 {
4111 my->monsterState = MONSTER_STATE_PATH; // path state
4112 }
4113 }
4114 else
4115 {
4116 if ( myStats->EFFECTS[EFF_FEAR] )
4117 {
4118 myReflex = false; // don't determine if you lost sight of the scary monster.
4119 }
4120
4121 if ( myReflex )
4122 {
4123 tangent = atan2( my->monsterTargetY - my->y, my->monsterTargetX - my->x );
4124
4125 if ( myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] > 0 && !hasrangedweapon )
4126 {
4127 if ( rand() % 10 == 0 )
4128 {
4129 node_t* node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
4130 if ( node != nullptr )
4131 {
4132 bool swapped = swapMonsterWeaponWithInventoryItem(my, myStats, node, true, true);
4133 if ( swapped )
4134 {
4135 my->monsterSpecialState = MONSTER_SPELLCAST_GENERIC;
4136 int timer = (myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] >> 4) & 0xFFFF;
4137 my->monsterSpecialTimer = timer > 0 ? timer : 250;
4138 hasrangedweapon = true;
4139 }
4140 }
4141 }
4142 }
4143
4144 if ( !levitating )
4145 {
4146 if ( hasrangedweapon )
4147 {
4148 dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
4149 }
4150 else
4151 {
4152 dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, true);
4153 }
4154 }
4155 else
4156 {
4157 dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
4158 }
4159 }
4160 else
4161 {
4162 dist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
4163 }
4164
4165 if ( hit.entity != entity && myReflex )
4166 {
4167 // if I currently lost sight of my target in a straight line in front of me
4168 // decide whether or not to path or retreat (stay put).
4169 if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
4170 {
4171 my->monsterMoveTime = 0;
4172 my->monsterState = MONSTER_STATE_WAIT; // wait state
4173 }
4174 else
4175 {
4176 my->monsterState = MONSTER_STATE_PATH; // path state
4177 }
4178 }
4179 else
4180 {
4181 // chaaaarge
4182 tangent = atan2( entity->y - my->y, entity->x - my->x );
4183 double tangent2 = tangent;
4184
4185 // get movement dir
4186 int goAgain = 0;
4187 timeToGoAgain:
4188 if ( targetdist > TOUCHRANGE * 1.5 && !hasrangedweapon && !my->shouldRetreat(*myStats) && my->getINT() > -2 )
4189 {
4190 if ( MONSTER_FLIPPEDANGLE < 5 )
4191 {
4192 if ( (my->ticks + my->getUID()) % (TICKS_PER_SECOND * 4) > TICKS_PER_SECOND * 2 )
4193 {
4194 tangent2 += PI / 6;
4195 }
4196 else
4197 {
4198 tangent2 -= PI / 6;
4199 }
4200 }
4201 else
4202 {
4203 if ( (my->ticks + my->getUID()) % (TICKS_PER_SECOND * 4) > TICKS_PER_SECOND * 2 )
4204 {
4205 tangent2 += PI / 6;
4206 }
4207 else
4208 {
4209 tangent2 -= PI / 6;
4210 }
4211 }
4212
4213 Entity* tempHitEntity = hit.entity;
4214 if ( lineTrace(my, my->x, my->x, tangent2, TOUCHRANGE, 1, false) < TOUCHRANGE )
4215 {
4216 MONSTER_FLIPPEDANGLE = (MONSTER_FLIPPEDANGLE < 5) * 10;
4217 goAgain++;
4218 if ( goAgain < 2 )
4219 {
4220 hit.entity = tempHitEntity;
4221 goto timeToGoAgain;
4222 }
4223 else
4224 {
4225 tangent2 = tangent;
4226 }
4227 }
4228 hit.entity = tempHitEntity;
4229 }
4230 else
4231 {
4232 tangent2 = tangent;
4233 }
4234
4235 int myDex = my->monsterGetDexterityForMovement();
4236 real_t maxVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio;
4237 real_t maxVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio;
4238 if ( !myStats->EFFECTS[EFF_KNOCKBACK] )
4239 {
4240 MONSTER_VELX = maxVelX;
4241 MONSTER_VELY = maxVelY;
4242 }
4243
4244 int rangedWeaponDistance = 160;
4245 if ( hasrangedweapon )
4246 {
4247 int effectiveDistance = my->getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon);
4248 if ( effectiveDistance < rangedWeaponDistance )
4249 {
4250 // shorter range xbows etc should advance at a little less than the extremity.
4251 rangedWeaponDistance = effectiveDistance - 10;
4252 }
4253 if ( myStats->weapon && myStats->weapon->type == SPELLBOOK_DASH )
4254 {
4255 rangedWeaponDistance = TOUCHRANGE;
4256 }
4257 }
4258
4259 if ( monsterIsImmobileTurret(my, myStats) )
4260 {
4261 // this is just so that the monster rotates. it doesn't actually move
4262 MONSTER_VELX = maxVelX * 0.01;
4263 MONSTER_VELY = maxVelY * 0.01;
4264 }
4265 else if ( !myStats->EFFECTS[EFF_KNOCKBACK] &&
4266 ((dist > 16 && !hasrangedweapon && !my->shouldRetreat(*myStats))
4267 || (hasrangedweapon && dist > rangedWeaponDistance)) )
4268 {
4269 if ( my->shouldRetreat(*myStats) )
4270 {
4271 MONSTER_VELX *= -.5;
4272 MONSTER_VELY *= -.5;
4273 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
4274 }
4275 else
4276 {
4277 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
4278 }
4279 if ( hit.entity != NULL )
4280 {
4281 if ( hit.entity->behavior == &actDoor )
4282 {
4283 // opens the door if unlocked and monster can do it
4284 if ( !hit.entity->doorLocked && my->getINT() > -2 )
4285 {
4286 if ( !hit.entity->doorDir && !hit.entity->doorStatus )
4287 {
4288 hit.entity->doorStatus = 1 + (my->x > hit.entity->x);
4289 playSoundEntity(hit.entity, 21, 96);
4290 }
4291 else if ( hit.entity->doorDir && !hit.entity->doorStatus )
4292 {
4293 hit.entity->doorStatus = 1 + (my->y < hit.entity->y);
4294 playSoundEntity(hit.entity, 21, 96);
4295 }
4296 }
4297 else
4298 {
4299 // can't open door, so break it down
4300 my->monsterHitTime++;
4301 if ( my->monsterHitTime >= HITRATE )
4302 {
4303 my->monsterAttack = my->getAttackPose(); // random attack motion
4304 my->monsterHitTime = 0;
4305 hit.entity->doorHealth--; // decrease door health
4306 if ( myStats->STR > 20 )
4307 {
4308 hit.entity->doorHealth -= static_cast<int>(std::max((myStats->STR - 20), 0) / 3); // decrease door health
4309 hit.entity->doorHealth = std::max(hit.entity->doorHealth, 0);
4310 }
4311 if ( myStats->type == MINOTAUR )
4312 {
4313 hit.entity->doorHealth = 0; // minotaurs smash doors instantly
4314 }
4315 playSoundEntity(hit.entity, 28, 64);
4316 if ( hit.entity->doorHealth <= 0 )
4317 {
4318 // set direction of splinters
4319 if ( !hit.entity->doorDir )
4320 {
4321 hit.entity->doorSmacked = (my->x > hit.entity->x);
4322 }
4323 else
4324 {
4325 hit.entity->doorSmacked = (my->y < hit.entity->y);
4326 }
4327 }
4328 }
4329 }
4330 }
4331 else if ( hit.entity->behavior == &actFurniture )
4332 {
4333 // break it down!
4334 my->monsterHitTime++;
4335 if ( my->monsterHitTime >= HITRATE )
4336 {
4337 my->monsterAttack = my->getAttackPose(); // random attack motion
4338 my->monsterHitTime = HITRATE / 4;
4339 hit.entity->furnitureHealth--; // decrease door health
4340 if ( myStats->STR > 20 )
4341 {
4342 hit.entity->furnitureHealth -= static_cast<int>(std::max((myStats->STR - 20), 0) / 3); // decrease door health
4343 hit.entity->furnitureHealth = std::max(hit.entity->furnitureHealth, 0);
4344 }
4345 if ( myStats->type == MINOTAUR )
4346 {
4347 hit.entity->furnitureHealth = 0; // minotaurs smash furniture instantly
4348 }
4349 playSoundEntity(hit.entity, 28, 64);
4350 }
4351 }
4352 else
4353 {
4354 if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
4355 {
4356 my->monsterMoveTime = 0;
4357 my->monsterState = MONSTER_STATE_WAIT; // wait state
4358 }
4359 else
4360 {
4361 my->monsterState = MONSTER_STATE_PATH; // path state
4362 }
4363 }
4364 }
4365 else
4366 {
4367 if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
4368 {
4369 my->monsterMoveTime = 0;
4370 my->monsterState = MONSTER_STATE_WAIT; // wait state
4371 }
4372 else if ( dist2 <= 0.1 && myStats->HP > myStats->MAXHP / 3 )
4373 {
4374 my->monsterState = MONSTER_STATE_PATH; // path state
4375 }
4376 }
4377 }
4378 else
4379 {
4380 if ( my->backupWithRangedWeapon(*myStats, dist, hasrangedweapon) || my->shouldRetreat(*myStats) )
4381 {
4382 // injured monsters or monsters with ranged weapons back up
4383 if ( myStats->type == LICH_ICE )
4384 {
4385 double strafeTangent = tangent2;
4386 //messagePlayer(0, "strafe: %d", my->monsterStrafeDirection);
4387 if ( ticks % 10 == 0 && my->monsterStrafeDirection != 0 && rand() % 10 == 0 )
4388 {
4389 Entity* lichAlly = nullptr;
4390 if ( my->monsterLichAllyUID != 0 )
4391 {
4392 lichAlly = uidToEntity(my->monsterLichAllyUID);
4393 }
4394 Entity* tmpEntity = hit.entity;
4395 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
4396 if ( hit.entity != tmpEntity )
4397 {
4398 // sight is blocked, keep strafing.
4399 }
4400 else
4401 {
4402 my->monsterStrafeDirection = 0;
4403 }
4404 hit.entity = tmpEntity;
4405 }
4406 if ( dist < 64 )
4407 {
4408 // move diagonally
4409 strafeTangent -= ((PI / 4) * my->monsterStrafeDirection);
4410 }
4411 else
4412 {
4413 // move sideways (dist between 64 and 100 from backupWithRangedWeapon)
4414 strafeTangent -= ((PI / 2) * my->monsterStrafeDirection);
4415 }
4416 MONSTER_VELX = cos(strafeTangent) * .045 * (my->getDEX() + 10) * weightratio * -.5;
4417 MONSTER_VELY = sin(strafeTangent) * .045 * (my->getDEX() + 10) * weightratio * -.5;
4418 }
4419 else
4420 {
4421 int myDex = my->monsterGetDexterityForMovement();
4422 real_t maxVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
4423 real_t maxVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
4424 if ( myStats->EFFECTS[EFF_KNOCKBACK] )
4425 {
4426 my->monsterHandleKnockbackVelocity(tangent2, weightratio);
4427 }
4428 else
4429 {
4430 MONSTER_VELX = maxVelX;
4431 MONSTER_VELY = maxVelY;
4432 }
4433 }
4434 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
4435 my->handleKnockbackDamage(*myStats, hit.entity);
4436 }
4437 else
4438 {
4439 // this is just so that the monster rotates. it doesn't actually move
4440 int myDex = my->monsterGetDexterityForMovement();
4441 MONSTER_VELX = cos(tangent) * .02 * .045 * (myDex + 10) * weightratio;
4442 MONSTER_VELY = sin(tangent) * .02 * .045 * (myDex + 10) * weightratio;
4443 }
4444 }
4445
4446 my->handleMonsterAttack(myStats, entity, dist);
4447
4448 // bust ceilings
4449 /*if( myStats->type == MINOTAUR ) {
4450 if( my->x>=0 && my->y>=0 && my->x<map.width<<4 && my->y<map.height<<4 ) {
4451 if( map.tiles[MAPLAYERS+(int)(my->y/16)*MAPLAYERS+(int)(my->x/16)*MAPLAYERS*map.height] )
4452 map.tiles[MAPLAYERS+(int)(my->y/16)*MAPLAYERS+(int)(my->x/16)*MAPLAYERS*map.height] = 0;
4453 }
4454 }*/
4455
4456 // rotate monster
4457 if ( my->backupWithRangedWeapon(*myStats, dist, hasrangedweapon) || my->shouldRetreat(*myStats) )
4458 {
4459 int myDex = my->getDEX();
4460 if ( my->monsterAllyGetPlayerLeader() )
4461 {
4462 myDex = std::min(myDex, MONSTER_ALLY_DEXTERITY_SPEED_CAP);
4463 }
4464 real_t tempVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
4465 real_t tempVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
4466 if ( myStats->type == LICH_ICE )
4467 {
4468 // override if we're strafing, keep facing the target
4469 dir = my->yaw - atan2(-tempVelY, -tempVelX);
4470 }
4471 else if ( myStats->EFFECTS[EFF_KNOCKBACK] )
4472 {
4473 // in knockback, the velocitys change sign from negative/positive or positive/negative.
4474 // this makes monsters moonwalk if the direction to rotate is assumed the same.
4475 // so we compare goal velocity direction (sin or cos(tangent)) and see if we've reached that sign.
4476
4477 int multX = 1;
4478 int multY = 1;
4479 if ( cos(tangent2) >= 0 == MONSTER_VELX > 0 )
4480 {
4481 // same sign.
4482 multX = 1;
4483 }
4484 else
4485 {
4486 // opposite signed.
4487 multX = -1;
4488 }
4489 if ( sin(tangent2) >= 0 == MONSTER_VELY > 0 )
4490 {
4491 // same sign.
4492 multY = 1;
4493 }
4494 else
4495 {
4496 // opposite signed.
4497 multY = -1;
4498 }
4499 dir = my->yaw - atan2(multY * MONSTER_VELY, multX * MONSTER_VELX);
4500 }
4501 else
4502 {
4503 dir = my->yaw - atan2( -MONSTER_VELY, -MONSTER_VELX );
4504 }
4505 }
4506 else
4507 {
4508 dir = my->yaw - atan2( MONSTER_VELY, MONSTER_VELX );
4509 }
4510 while ( dir >= PI )
4511 {
4512 dir -= PI * 2;
4513 }
4514 while ( dir < -PI )
4515 {
4516 dir += PI * 2;
4517 }
4518 my->yaw -= dir / 2;
4519 while ( my->yaw < 0 )
4520 {
4521 my->yaw += 2 * PI;
4522 }
4523 while ( my->yaw >= 2 * PI )
4524 {
4525 my->yaw -= 2 * PI;
4526 }
4527 }
4528 }
4529 }
4530 }
4531 else
4532 {
4533 // devil specific code
4534 if ( !MONSTER_ATTACK || MONSTER_ATTACK == 4 )
4535 {
4536 my->monsterSpecialTimer++;
4537 int difficulty = 40;
4538 int numPlayers = 0;
4539
4540 for ( int c = 0; c < MAXPLAYERS; ++c )
4541 {
4542 if ( players[c] && players[c]->entity )
4543 {
4544 ++numPlayers;
4545 }
4546 }
4547 if ( numPlayers > 0 )
4548 {
4549 difficulty /= numPlayers; // 40/20/13/10 - basically how long you get to wail on Baphy. Shorter is harder.
4550 }
4551
4552 if ( my->monsterSpecialTimer > 60 || (devilstate == 72 && my->monsterSpecialTimer > difficulty))
4553 {
4554 if ( !devilstate ) // devilstate is 0 at the start of the fight and doesn't return to 0.
4555 {
4556 if ( !MONSTER_ATTACK )
4557 {
4558 int c;
4559 for ( c = 0; c < MAXPLAYERS; c++ )
4560 {
4561 playSoundPlayer(c, 204, 64);
4562 }
4563 playSoundEntity(my, 204, 128);
4564 MONSTER_ATTACK = 4;
4565 MONSTER_ATTACKTIME = 0;
4566 MONSTER_ARMBENDED = 1;
4567 serverUpdateEntitySkill(my, 8);
4568 serverUpdateEntitySkill(my, 9);
4569 serverUpdateEntitySkill(my, 10);
4570 for ( int c = 0; c < MAXPLAYERS; ++c )
4571 {
4572 if ( players[c] && players[c]->entity )
4573 {
4574 my->devilSummonMonster(nullptr, SHADOW, 5, c);
4575 }
4576 }
4577 my->devilSummonMonster(nullptr, DEMON, 5);
4578 }
4579 else if ( MONSTER_ATTACKTIME > 90 )
4580 {
4581 my->monsterState = MONSTER_STATE_DEVIL_TELEPORT; // devil teleport state
4582 }
4583 }
4584 else
4585 {
4586 if ( !devilacted )
4587 {
4588 switch ( devilstate )
4589 {
4590 case 72:
4591 my->monsterState = MONSTER_STATE_DEVIL_SUMMON; // devil summoning state
4592 break;
4593 case 73:
4594 MONSTER_ATTACK = 5 + rand() % 2; // fireballs
4595 break;
4596 case 74:
4597 my->monsterState = MONSTER_STATE_DEVIL_BOULDER; // devil boulder drop
4598 break;
4599 }
4600 devilacted = 1;
4601 }
4602 else
4603 {
4604 if ( rand() % 2 && devilstate == 73 )
4605 {
4606 MONSTER_ATTACK = 5 + rand() % 2; // more fireballs
4607 }
4608 else
4609 {
4610 my->monsterState = MONSTER_STATE_DEVIL_TELEPORT; // devil teleport state
4611 }
4612 }
4613 }
4614 my->monsterSpecialTimer = 0;
4615 }
4616 }
4617 else if ( MONSTER_ATTACK == 5 || MONSTER_ATTACK == 6 )
4618 {
4619 // throw fireballs
4620 my->yaw = my->yaw + MONSTER_WEAPONYAW;
4621 castSpell(my->getUID(), &spell_fireball, true, false);
4622 my->yaw = my->yaw - MONSTER_WEAPONYAW;
4623
4624 // let's throw one specifically aimed at our players to be mean.
4625 if ( MONSTER_ATTACKTIME == 10 )
4626 {
4627 real_t oldYaw = my->yaw;
4628 tangent = atan2(entity->y - my->y, entity->x - my->x);
4629 my->yaw = tangent;
4630 Entity* fireball = castSpell(my->getUID(), &spell_fireball, true, false);
4631 for ( c = 0; c < MAXPLAYERS; ++c )
4632 {
4633 if ( players[c] && players[c]->entity && entity != players[c]->entity )
4634 {
4635 tangent = atan2(entity->y - my->y, entity->x - my->x);
4636 real_t dir = oldYaw - tangent;
4637 while ( dir >= PI )
4638 {
4639 dir -= PI * 2;
4640 }
4641 while ( dir < -PI )
4642 {
4643 dir += PI * 2;
4644 }
4645 if ( dir >= -7 * PI / 16 && dir <= 7 * PI / 16 )
4646 {
4647 my->yaw = tangent;
4648 Entity* fireball = castSpell(my->getUID(), &spell_fireball, true, false);
4649 }
4650 }
4651 }
4652 my->yaw = oldYaw;
4653 }
4654 }
4655
4656 // rotate monster
4657 tangent = atan2( entity->y - my->y, entity->x - my->x );
4658 MONSTER_VELX = cos(tangent);
4659 MONSTER_VELY = sin(tangent);
4660 dir = my->yaw - atan2( MONSTER_VELY, MONSTER_VELX );
4661 while ( dir >= PI )
4662 {
4663 dir -= PI * 2;
4664 }
4665 while ( dir < -PI )
4666 {
4667 dir += PI * 2;
4668 }
4669 my->yaw -= dir / 2;
4670 while ( my->yaw < 0 )
4671 {
4672 my->yaw += 2 * PI;
4673 }
4674 while ( my->yaw >= 2 * PI )
4675 {
4676 my->yaw -= 2 * PI;
4677 }
4678 }
4679 } //End charge state
4680 else if ( my->monsterState == MONSTER_STATE_PATH ) //Begin path state
4681 {
4682 if ( myStats->type == DEVIL )
4683 {
4684 my->monsterState = MONSTER_STATE_ATTACK;
4685 if ( previousMonsterState != my->monsterState )
4686 {
4687 serverUpdateEntitySkill(my, 0);
4688 }
4689 return;
4690 }
4691 else if ( myStats->type == DUMMYBOT )
4692 {
4693 my->monsterState = MONSTER_STATE_WAIT;
4694 my->monsterMoveTime = 0;
4695 return;
4696 }
4697 else if ( monsterIsImmobileTurret(my, myStats) )
4698 {
4699 my->monsterState = MONSTER_STATE_WAIT;
4700 if ( previousMonsterState != my->monsterState )
4701 {
4702 serverUpdateEntitySkill(my, 0);
4703 }
4704 return;
4705 }
4706
4707 //Don't path if your target dieded!
4708 if ( uidToEntity(my->monsterTarget) == nullptr && my->monsterTarget != 0 )
4709 {
4710 my->monsterReleaseAttackTarget(true);
4711 my->monsterState = MONSTER_STATE_WAIT; // wait state
4712 if ( previousMonsterState != my->monsterState )
4713 {
4714 serverUpdateEntitySkill(my, 0);
4715 }
4716 return;
4717 }
4718
4719 entity = uidToEntity(my->monsterTarget);
4720 if ( entity != nullptr )
4721 {
4722 if ( entity->behavior == &actPlayer )
4723 {
4724 assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
4725 assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
4726 }
4727 my->monsterTargetX = entity->x;
4728 my->monsterTargetY = entity->y;
4729 }
4730 x = ((int)floor(my->monsterTargetX)) >> 4;
4731 y = ((int)floor(my->monsterTargetY)) >> 4;
4732 path = generatePath( (int)floor(my->x / 16), (int)floor(my->y / 16), x, y, my, uidToEntity(my->monsterTarget) );
4733 if ( my->children.first != nullptr )
4734 {
4735 list_RemoveNode(my->children.first);
4736 }
4737 node = list_AddNodeFirst(&my->children);
4738 node->element = path;
4739 node->deconstructor = &listDeconstructor;
4740 my->monsterState = MONSTER_STATE_HUNT; // hunt state
4741 /*if ( myStats->type == SHADOW && entity )
4742 {
4743 if ( path == nullptr )
4744 {
4745 messagePlayer(0, "Warning: Shadow failed to generate a path to its target.");
4746 }
4747 }*/
4748 } //End path state.
4749 else if ( my->monsterState == MONSTER_STATE_HUNT ) //Begin hunt state
4750 {
4751 if ( myStats->type == SHADOW && my->monsterSpecialState == SHADOW_TELEPORT_ONLY )
4752 {
4753 //messagePlayer(0, "Shadow in special state teleport only! Aborting hunt state.");
4754 my->monsterState = MONSTER_STATE_WAIT;
4755 return; //Don't do anything, yer casting a spell!
4756 }
4757 //Do the shadow's passive teleport to catch up to their target..
4758 if ( myStats->type == SHADOW && my->monsterSpecialTimer == 0 && my->monsterTarget )
4759 {
4760 Entity* target = uidToEntity(my->monsterTarget);
4761 if ( !target )
4762 {
4763 my->monsterReleaseAttackTarget(true);
4764 my->monsterState = MONSTER_STATE_WAIT;
4765 serverUpdateEntitySkill(my, 0); //Update state.
4766 return;
4767 }
4768
4769 //If shadow has no path to target, then should do the passive teleport.
4770 bool passiveTeleport = false;
4771 if ( my->children.first ) //First child is the path.
4772 {
4773 if ( !my->children.first->element )
4774 {
4775 //messagePlayer(0, "No path for shadow!");
4776 passiveTeleport = true;
4777 }
4778 }
4779 else
4780 {
4781 //messagePlayer(0, "No path for shadow!");
4782 passiveTeleport = true; //Path is saved as first child. If no first child, no path!
4783 }
4784
4785 //Shadow has path to target, but still passive teleport if far enough away.
4786 if ( !passiveTeleport )
4787 {
4788 int specialRoll = rand() % 50;
4789 //messagePlayer(0, "roll %d", specialRoll);
4790 double targetdist = sqrt(pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2));
4791 if ( specialRoll <= (2 + (targetdist > 80 ? 4 : 0)) )
4792 {
4793 passiveTeleport = true;
4794 }
4795 }
4796
4797 if ( passiveTeleport )
4798 {
4799 //messagePlayer(0, "Shadow is doing a passive tele.");
4800 my->monsterSpecialState = SHADOW_TELEPORT_ONLY;
4801 my->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_PASIVE_TELEPORT;
4802 my->shadowTeleportToTarget(target, 3); // teleport in closer range
4803 my->monsterState = MONSTER_STATE_WAIT;
4804 if ( target && target->behavior == actPlayer )
4805 {
4806 messagePlayer(target->skill[2], language[2518]);
4807 }
4808 return;
4809 }
4810 }
4811
4812 if ( myReflex && (myStats->type != LICH || my->monsterSpecialTimer <= 0) )
4813 {
4814 for ( node2 = map.creatures->first; node2 != nullptr; node2 = node2->next ) //Stats only exist on a creature, so don't iterate all map.entities.
4815 {
4816 entity = (Entity*)node2->element;
4817 if ( entity == my || entity->flags[PASSABLE] )
4818 {
4819 continue;
4820 }
4821 hitstats = entity->getStats();
4822 if ( hitstats != nullptr )
4823 {
4824 if ( (my->checkEnemy(entity) || my->monsterTarget == entity->getUID() || ringconflict) )
4825 {
4826 tangent = atan2( entity->y - my->y, entity->x - my->x );
4827 dir = my->yaw - tangent;
4828 while ( dir >= PI )
4829 {
4830 dir -= PI * 2;
4831 }
4832 while ( dir < -PI )
4833 {
4834 dir += PI * 2;
4835 }
4836
4837 // skip if light level is too low and distance is too high
4838 int light = entity->entityLightAfterReductions(*hitstats, my);
4839 if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
4840 {
4841 //See invisible.
4842 light = 1000;
4843 }
4844 double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
4845
4846 real_t monsterVisionRange = sightranges[myStats->type];
4847 if ( hitstats->type == DUMMYBOT )
4848 {
4849 monsterVisionRange = std::min(monsterVisionRange, 96.0);
4850 }
4851
4852 if ( targetdist > monsterVisionRange )
4853 {
4854 continue;
4855 }
4856 if ( targetdist > TOUCHRANGE && targetdist > light )
4857 {
4858 if ( !levitating )
4859 {
4860 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, true);
4861 }
4862 else
4863 {
4864 lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
4865 }
4866 if ( hit.entity == entity )
4867 {
4868 if ( rand() % 100 == 0 )
4869 {
4870 entity->increaseSkill(PRO_STEALTH);
4871 }
4872 }
4873 continue;
4874 }
4875 bool visiontest = false;
4876 if ( hitstats->type == DUMMYBOT || myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
4877 {
4878 if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
4879 {
4880 visiontest = true;
4881 }
4882 }
4883 else if ( myStats->type != SPIDER )
4884 {
4885 if ( my->monsterAllyGetPlayerLeader() )
4886 {
4887 if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
4888 {
4889 visiontest = true; // increase ally vision when hunting.
4890 }
4891 }
4892 else
4893 {
4894 if ( dir >= -7 * PI / 16 && dir <= 7 * PI / 16 )
4895 {
4896 visiontest = true;
4897 }
4898 }
4899 }
4900 else
4901 {
4902 if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
4903 {
4904 visiontest = true;
4905 }
4906 }
4907 if ( visiontest ) // vision cone
4908 {
4909 lineTrace(my, my->x + 1, my->y, tangent, monsterVisionRange, 0, (levitating == false));
4910 if ( hit.entity == entity )
4911 {
4912 lineTrace(my, my->x - 1, my->y, tangent, monsterVisionRange, 0, (levitating == false));
4913 if ( hit.entity == entity )
4914 {
4915 lineTrace(my, my->x, my->y + 1, tangent, monsterVisionRange, 0, (levitating == false));
4916 if ( hit.entity == entity )
4917 {
4918 lineTrace(my, my->x, my->y - 1, tangent, monsterVisionRange, 0, (levitating == false));
4919 if ( hit.entity == entity )
4920 {
4921 Entity& attackTarget = *hit.entity;
4922 // charge state
4923 if ( my->monsterTarget == entity->getUID() )
4924 {
4925 // this is when a monster is chasing it's known target.
4926 // let's to be ready to strike.
4927 // otherwise, we bumped into a new unexpected target, don't modify hitTime
4928 if ( hasrangedweapon )
4929 {
4930 // 120 ms reaction time
4931 if ( my->monsterHitTime < HITRATE )
4932 {
4933 if ( myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK )
4934 {
4935 my->monsterHitTime = std::max(HITRATE, my->monsterHitTime);
4936 }
4937 else
4938 {
4939 my->monsterHitTime = std::max(HITRATE - 6, my->monsterHitTime);
4940 }
4941 }
4942 else
4943 {
4944 // bows have 2x hitrate time compared to standard weapons.
4945 my->monsterHitTime = std::max(2 * HITRATE - 6, my->monsterHitTime);
4946 }
4947 }
4948 else
4949 {
4950 // melee 240ms
4951 my->monsterHitTime = std::max(HITRATE - 12, my->monsterHitTime);
4952 }
4953 }
4954 //messagePlayer(0, "hunt -> attack, %d", my->monsterHitTime);
4955 my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
4956
4957 if ( MONSTER_SOUND == NULL )
4958 {
4959 if ( myStats->type != MINOTAUR )
4960 {
4961 if ( myStats->type != LICH || rand() % 3 == 0 )
4962 {
4963 if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
4964 {
4965 sentrybotPickSpotNoise(my, myStats);
4966 }
4967 else
4968 {
4969 MONSTER_SOUND = playSoundEntity(my, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128);
4970 }
4971 }
4972 }
4973 else
4974 {
4975 int c;
4976 for ( c = 0; c < MAXPLAYERS; c++ )
4977 {
4978 if ( c == 0 )
4979 {
4980 MONSTER_SOUND = playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
4981 }
4982 else
4983 {
4984 playSoundPlayer( c, MONSTER_SPOTSND + rand() % MONSTER_SPOTVAR, 128 );
4985 }
4986 }
4987 }
4988 }
4989
4990 if ( entity != nullptr )
4991 {
4992 if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT )
4993 {
4994 assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
4995 assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
4996 }
4997 }
4998 break;
4999 }
5000 }
5001 }
5002 }
5003 }
5004 }
5005 }
5006 }
5007 }
5008
5009 // minotaurs and liches chase players relentlessly.
5010 if ( myStats->type == MINOTAUR
5011 || (myStats->type == LICH && my->monsterSpecialTimer <= 0)
5012 || ((myStats->type == LICH_FIRE || myStats->type == LICH_ICE) && my->monsterSpecialTimer <= 0 )
5013 || (myStats->type == CREATURE_IMP && strstr(map.name, "Boss") && !my->monsterAllyGetPlayerLeader())
5014 || (myStats->type == AUTOMATON && strstr(myStats->name, "corrupted automaton")) )
5015 {
5016 bool shouldHuntPlayer = false;
5017 Entity* playerOrNot = uidToEntity(my->monsterTarget);
5018 if (playerOrNot)
5019 {
5020 if (ticks % 180 == 0 && playerOrNot->behavior == &actPlayer)
5021 {
5022 shouldHuntPlayer = true;
5023 }
5024 }
5025 else if (ticks % 180 == 0)
5026 {
5027 shouldHuntPlayer = true;
5028 }
5029 if (shouldHuntPlayer)
5030 {
5031 double distToPlayer = 0;
5032 int c, playerToChase = -1;
5033 for (c = 0; c < MAXPLAYERS; c++)
5034 {
5035 if (players[c] && players[c]->entity)
5036 {
5037 list_t* playerPath = generatePath((int)floor(my->x / 16), (int)floor(my->y / 16),
5038 (int)floor(players[c]->entity->x / 16), (int)floor(players[c]->entity->y / 16), my, players[c]->entity);
5039 if ( playerPath == NULL )
5040 {
5041 continue;
5042 }
5043 else
5044 {
5045 list_FreeAll(playerPath);
5046 free(playerPath);
5047 }
5048 if (!distToPlayer)
5049 {
5050 distToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
5051 playerToChase = c;
5052 }
5053 else
5054 {
5055 double newDistToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
5056 if (newDistToPlayer < distToPlayer)
5057 {
5058 distToPlayer = newDistToPlayer;
5059 playerToChase = c;
5060 }
5061 }
5062 }
5063 }
5064 if (playerToChase >= 0)
5065 {
5066 // path state
5067 if ( players[playerToChase] && players[playerToChase]->entity )
5068 {
5069 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_PATH);
5070 }
5071 if ( previousMonsterState != my->monsterState )
5072 {
5073 serverUpdateEntitySkill(my, 0);
5074 }
5075 return;
5076 }
5077 }
5078 }
5079 else if ( myStats->type == SHADOW && my->monsterTarget && (ticks % 180 == 0) )
5080 {
5081 if ( !uidToEntity(my->monsterTarget) )
5082 {
5083 my->monsterReleaseAttackTarget(true);
5084 my->monsterState = MONSTER_STATE_WAIT;
5085 serverUpdateEntitySkill(my, 0); //Update state.
5086 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
5087 {
5088 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
5089 }
5090 return;
5091 }
5092 my->monsterState = MONSTER_STATE_PATH;
5093 serverUpdateEntitySkill(my, 0); //Update state.
5094 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
5095 {
5096 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
5097 }
5098 return;
5099 }
5100
5101 // lich cooldown
5102 if ( myStats->type == LICH )
5103 {
5104 if ( my->monsterSpecialTimer > 0 )
5105 {
5106 my->monsterSpecialTimer--;
5107 }
5108 }
5109
5110 // follow the leader :)
5111 if ( uidToEntity(my->monsterTarget) == nullptr
5112 && myStats->leader_uid != 0 && my->monsterAllyState == ALLY_STATE_DEFAULT && my->getUID() % TICKS_PER_SECOND == ticks % TICKS_PER_SECOND
5113 && !monsterIsImmobileTurret(my, myStats) )
5114 {
5115 Entity* leader = uidToEntity(myStats->leader_uid);
5116 if ( leader )
5117 {
5118 real_t followx = leader->x;
5119 real_t followy = leader->y;
5120 if ( myStats->type == GYROBOT )
5121 {
5122 // follow ahead of the leader.
5123 real_t startx = leader->x;
5124 real_t starty = leader->y;
5125 // draw line from the leaders direction until we hit a wall or 48 dist
5126 real_t previousx = startx;
5127 real_t previousy = starty;
5128 real_t leadDistance = HUNT_FOLLOWDIST;
5129 for ( int iterations = 0; iterations < 16; ++iterations )
5130 {
5131 startx += 4 * cos(leader->yaw);
5132 starty += 4 * sin(leader->yaw);
5133 int index = (static_cast<int>(starty + 16 * sin(leader->yaw)) >> 4) * MAPLAYERS + (static_cast<int>(startx + 16 * cos(leader->yaw)) >> 4) * MAPLAYERS * map.height;
5134 if ( !map.tiles[OBSTACLELAYER + index] )
5135 {
5136 // store the last known good coordinate
5137 previousx = startx;
5138 previousy = starty;
5139 }
5140 else if ( map.tiles[OBSTACLELAYER + index] )
5141 {
5142 break;
5143 }
5144 if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > HUNT_FOLLOWDIST )
5145 {
5146 break;
5147 }
5148 }
5149 followx = previousx;
5150 followy = previousy;
5151 //createParticleFollowerCommand(previousx, previousy, 0, 175); debug particle
5152 }
5153 double dist = sqrt(pow(my->x - followx, 2) + pow(my->y - followy, 2));
5154 if ( dist > HUNT_FOLLOWDIST )
5155 {
5156 bool doFollow = true;
5157 if ( my->monsterTarget != 0 )
5158 {
5159 doFollow = my->isFollowerFreeToPathToPlayer(myStats);
5160 }
5161
5162 if ( doFollow )
5163 {
5164 x = ((int)floor(followx)) >> 4;
5165 y = ((int)floor(followy)) >> 4;
5166 int u, v;
5167 bool foundplace = false;
5168 for ( u = x - 1; u <= x + 1; u++ )
5169 {
5170 for ( v = y - 1; v <= y + 1; v++ )
5171 {
5172 if ( !checkObstacle((u << 4) + 8, (v << 4) + 8, my, leader) )
5173 {
5174 x = u;
5175 y = v;
5176 foundplace = true;
5177 break;
5178 }
5179 }
5180 if ( foundplace )
5181 {
5182 break;
5183 }
5184 }
5185 path = generatePath( (int)floor(my->x / 16), (int)floor(my->y / 16), x, y, my, leader );
5186 if ( my->children.first != NULL )
5187 {
5188 list_RemoveNode(my->children.first);
5189 }
5190 node = list_AddNodeFirst(&my->children);
5191 node->element = path;
5192 node->deconstructor = &listDeconstructor;
5193 my->monsterState = MONSTER_STATE_HUNT; // hunt state
5194 if ( previousMonsterState != my->monsterState )
5195 {
5196 serverUpdateEntitySkill(my, 0);
5197 }
5198 return;
5199 }
5200 }
5201 else if ( myStats->type != GYROBOT )
5202 {
5203 bool doFollow = true;
5204 if ( my->monsterTarget != 0 )
5205 {
5206 doFollow = my->isFollowerFreeToPathToPlayer(myStats);
5207 }
5208
5209 if ( doFollow )
5210 {
5211 double tangent = atan2( leader->y - my->y, leader->x - my->x );
5212 Entity* ohitentity = hit.entity;
5213 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, true);
5214 if ( hit.entity != leader )
5215 {
5216 my->monsterReleaseAttackTarget();
5217 int x = ((int)floor(leader->x)) >> 4;
5218 int y = ((int)floor(leader->y)) >> 4;
5219 int u, v;
5220 bool foundplace = false;
5221 for ( u = x - 1; u <= x + 1; u++ )
5222 {
5223 for ( v = y - 1; v <= y + 1; v++ )
5224 {
5225 if ( !checkObstacle((u << 4) + 8, (v << 4) + 8, my, leader) )
5226 {
5227 x = u;
5228 y = v;
5229 foundplace = true;
5230 break;
5231 }
5232 }
5233 if ( foundplace )
5234 {
5235 break;
5236 }
5237 }
5238 path = generatePath( (int)floor(my->x / 16), (int)floor(my->y / 16), x, y, my, leader );
5239 if ( my->children.first != NULL )
5240 {
5241 list_RemoveNode(my->children.first);
5242 }
5243 node = list_AddNodeFirst(&my->children);
5244 node->element = path;
5245 node->deconstructor = &listDeconstructor;
5246 my->monsterState = MONSTER_STATE_HUNT; // hunt state
5247 if ( previousMonsterState != my->monsterState )
5248 {
5249 serverUpdateEntitySkill(my, 0);
5250 }
5251 return;
5252 }
5253 hit.entity = ohitentity;
5254 }
5255 }
5256 }
5257 }
5258
5259 entity = uidToEntity(my->monsterTarget);
5260 if ( entity != NULL )
5261 {
5262 if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT )
5263 {
5264 assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
5265 assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
5266 }
5267 }
5268 if ( my->children.first != NULL )
5269 {
5270 if ( my->children.first->element != NULL )
5271 {
5272 path = (list_t*)my->children.first->element;
5273 if ( path->first != NULL )
5274 {
5275 pathnode = (pathnode_t*)path->first->element;
5276 dist = sqrt( pow(pathnode->y * 16 + 8 - my->y, 2) + pow(pathnode->x * 16 + 8 - my->x, 2) );
5277 if ( dist <= 2 )
5278 {
5279 list_RemoveNode(pathnode->node);
5280 if ( rand() % 8 == 0 )
5281 {
5282 if ( !MONSTER_SOUND )
5283 {
5284 if ( myStats->type != MINOTAUR )
5285 {
5286 if ( !my->monsterAllyGetPlayerLeader() || (my->monsterAllyGetPlayerLeader() && rand() % 3 == 0) )
5287 {
5288 MONSTER_SOUND = playSoundEntity(my, MONSTER_IDLESND + (rand() % MONSTER_IDLEVAR), 128);
5289 }
5290 }
5291 else
5292 {
5293 int c;
5294 for ( c = 0; c < MAXPLAYERS; c++ )
5295 {
5296 if ( c == 0 )
5297 {
5298 MONSTER_SOUND = playSoundPlayer( c, MONSTER_IDLESND + (rand() % MONSTER_IDLEVAR), 128 );
5299 }
5300 else
5301 {
5302 playSoundPlayer( c, MONSTER_IDLESND + (rand() % MONSTER_IDLEVAR), 128 );
5303 }
5304 }
5305 }
5306 }
5307 }
5308 }
5309 else
5310 {
5311 // move monster
5312 tangent = atan2( pathnode->y * 16 + 8 - my->y, pathnode->x * 16 + 8 - my->x );
5313 int myDex = my->getDEX();
5314 if ( my->monsterAllyGetPlayerLeader() )
5315 {
5316 myDex = std::min(myDex, MONSTER_ALLY_DEXTERITY_SPEED_CAP);
5317 }
5318 real_t maxVelX = cos(tangent) * .045 * (myDex + 10) * weightratio;
5319 real_t maxVelY = sin(tangent) * .045 * (myDex + 10) * weightratio;
5320 if ( myStats->EFFECTS[EFF_KNOCKBACK] )
5321 {
5322 my->monsterHandleKnockbackVelocity(tangent, weightratio);
5323 }
5324 else
5325 {
5326 MONSTER_VELX = maxVelX;
5327 MONSTER_VELY = maxVelY;
5328 }
5329 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
5330 my->handleKnockbackDamage(*myStats, hit.entity);
5331 if ( hit.entity != NULL )
5332 {
5333 if ( hit.entity->behavior == &actDoor )
5334 {
5335 // opens the door if unlocked and monster can do it
5336 if ( !hit.entity->doorLocked && my->getINT() > -2 )
5337 {
5338 if ( !hit.entity->doorDir && !hit.entity->doorStatus )
5339 {
5340 hit.entity->doorStatus = 1 + (my->x > hit.entity->x);
5341 playSoundEntity(hit.entity, 21, 96);
5342 }
5343 else if ( hit.entity->doorDir && !hit.entity->doorStatus )
5344 {
5345 hit.entity->doorStatus = 1 + (my->y < hit.entity->y);
5346 playSoundEntity(hit.entity, 21, 96);
5347 }
5348 }
5349 else
5350 {
5351 // can't open door, so break it down
5352 my->monsterHitTime++;
5353 if ( my->monsterHitTime >= HITRATE )
5354 {
5355 my->monsterAttack = my->getAttackPose(); // random attack motion
5356 my->monsterHitTime = 0;
5357 hit.entity->doorHealth--; // decrease door health
5358 if ( myStats->STR > 20 )
5359 {
5360 hit.entity->doorHealth -= static_cast<int>(std::max((myStats->STR - 20), 0) / 3); // decrease door health
5361 hit.entity->doorHealth = std::max(hit.entity->doorHealth, 0);
5362 }
5363 if ( myStats->type == MINOTAUR )
5364 {
5365 hit.entity->doorHealth = 0; // minotaurs smash doors instantly
5366 }
5367 playSoundEntity(hit.entity, 28, 64);
5368 if ( hit.entity->doorHealth <= 0 )
5369 {
5370 // set direction of splinters
5371 if ( !hit.entity->doorDir )
5372 {
5373 hit.entity->doorSmacked = (my->x > hit.entity->x);
5374 }
5375 else
5376 {
5377 hit.entity->doorSmacked = (my->y < hit.entity->y);
5378 }
5379 }
5380 }
5381 }
5382 }
5383 else if ( hit.entity->behavior == &actFurniture )
5384 {
5385 // break it down!
5386 my->monsterHitTime++;
5387 if ( my->monsterHitTime >= HITRATE )
5388 {
5389 my->monsterAttack = my->getAttackPose(); // random attack motion
5390 my->monsterHitTime = HITRATE / 4;
5391 hit.entity->furnitureHealth--; // decrease door health
5392 if ( myStats->STR > 20 )
5393 {
5394 hit.entity->furnitureHealth -= static_cast<int>(std::max((myStats->STR - 20), 0) / 3); // decrease door health
5395 hit.entity->furnitureHealth = std::max(hit.entity->furnitureHealth, 0);
5396 }
5397 if ( myStats->type == MINOTAUR )
5398 {
5399 hit.entity->furnitureHealth = 0; // minotaurs smash furniture instantly
5400 }
5401 playSoundEntity(hit.entity, 28, 64);
5402 }
5403 }
5404 else if ( hit.entity->behavior == &actMonster )
5405 {
5406 Stat* yourStats = hit.entity->getStats();
5407 if ( hit.entity->getUID() == my->monsterTarget )
5408 {
5409 //TODO: Refactor with setMonsterStateAttack().
5410 my->monsterState = MONSTER_STATE_ATTACK; // charge state
5411
5412 // this is when a monster is bumps into it's known target.
5413 // let's to be ready to strike.
5414 // otherwise, we bumped into a new unexpected target, don't modify hitTime
5415 if ( hasrangedweapon )
5416 {
5417 // 120 ms reaction time
5418 if ( my->monsterHitTime < HITRATE )
5419 {
5420 if ( myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK )
5421 {
5422 my->monsterHitTime = std::max(HITRATE, my->monsterHitTime);
5423 }
5424 else
5425 {
5426 my->monsterHitTime = std::max(HITRATE - 6, my->monsterHitTime);
5427 }
5428 }
5429 else
5430 {
5431 // bows have 2x hitrate time compared to standard weapons.
5432 my->monsterHitTime = std::max(2 * HITRATE - 6, my->monsterHitTime);
5433 }
5434 }
5435 else
5436 {
5437 // melee 240ms
5438 my->monsterHitTime = std::max(HITRATE - 12, my->monsterHitTime);
5439 }
5440 //messagePlayer(0, "bump1 -> attack, %d", my->monsterHitTime);
5441 }
5442 else if ( yourStats )
5443 {
5444 if ( !my->checkEnemy(hit.entity) )
5445 {
5446 // would you kindly move out of the way, sir?
5447 if ( !monsterMoveAside(hit.entity, my) )
5448 {
5449 my->monsterState = MONSTER_STATE_PATH; // try something else and remake path
5450 }
5451 ++my->monsterPathCount;
5452 if ( my->monsterPathCount > 100 )
5453 {
5454 my->monsterPathCount = 0;
5455 //messagePlayer(0, "running into monster like a fool!");
5456 my->monsterMoveBackwardsAndPath();
5457 }
5458 }
5459 else if ( my->checkEnemy(hit.entity) )
5460 {
5461 // charge state
5462 Entity& attackTarget = *hit.entity;
5463 my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
5464 }
5465 }
5466 }
5467 else if ( hit.entity->behavior == &actPlayer )
5468 {
5469 if ( my->checkEnemy(hit.entity) )
5470 {
5471 // charge state
5472 Entity& attackTarget = *hit.entity;
5473 if ( my->monsterTarget == hit.entity->getUID() )
5474 {
5475 // this is when a monster is bumps into it's known target.
5476 // let's to be ready to strike.
5477 // otherwise, we bumped into a new unexpected target, don't modify hitTime
5478 if ( hasrangedweapon )
5479 {
5480 // 120 ms reaction time
5481 if ( my->monsterHitTime < HITRATE )
5482 {
5483 if ( myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK )
5484 {
5485 my->monsterHitTime = std::max(HITRATE, my->monsterHitTime);
5486 }
5487 else
5488 {
5489 my->monsterHitTime = std::max(HITRATE - 6, my->monsterHitTime);
5490 }
5491 }
5492 else
5493 {
5494 // bows have 2x hitrate time compared to standard weapons.
5495 my->monsterHitTime = std::max(2 * HITRATE - 6, my->monsterHitTime);
5496 }
5497 }
5498 else
5499 {
5500 // melee 240ms
5501 my->monsterHitTime = std::max(HITRATE - 12, my->monsterHitTime);
5502 }
5503 }
5504 //messagePlayer(0, "bump2 -> attack, %d", my->monsterHitTime);
5505 my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
5506 }
5507 else
5508 {
5509 my->monsterState = MONSTER_STATE_PATH; // try something else and remake path
5510 }
5511 }
5512 else
5513 {
5514 my->monsterState = MONSTER_STATE_PATH; // remake path
5515 if ( myStats->type != LICH_FIRE && myStats->type != LICH_ICE )
5516 {
5517 if ( hit.entity->behavior == &actGate || hit.entity->behavior == &actBoulder
5518 )
5519 {
5520 ++my->monsterPathCount;
5521 if ( hit.entity->behavior == &actBoulder )
5522 {
5523 my->monsterPathCount += 5;
5524 }
5525 if ( my->monsterPathCount > 100 )
5526 {
5527 my->monsterPathCount = 0;
5528 //messagePlayer(0, "remaking path!");
5529 my->monsterMoveBackwardsAndPath();
5530 }
5531 }
5532 else
5533 {
5534 my->monsterPathCount = 0;
5535 }
5536 }
5537 }
5538 }
5539 else
5540 {
5541 if ( dist2 <= 0.1 )
5542 {
5543 my->monsterState = MONSTER_STATE_PATH; // remake path
5544 }
5545 }
5546
5547 // rotate monster
5548 if ( myStats->EFFECTS[EFF_KNOCKBACK] )
5549 {
5550 // in knockback, the velocitys change sign from negative/positive or positive/negative.
5551 // this makes monsters moonwalk if the direction to rotate is assumed the same.
5552 // so we compare goal velocity direction (sin or cos(tangent)) and see if we've reached that sign.
5553
5554 int multX = 1;
5555 int multY = 1;
5556 if ( cos(tangent) >= 0 == MONSTER_VELX > 0 )
5557 {
5558 // same sign.
5559 multX = 1;
5560 }
5561 else
5562 {
5563 // opposite signed.
5564 multX = -1;
5565 }
5566 if ( sin(tangent) >= 0 == MONSTER_VELY > 0 )
5567 {
5568 // same sign.
5569 multY = 1;
5570 }
5571 else
5572 {
5573 // opposite signed.
5574 multY = -1;
5575 }
5576 dir = my->yaw - atan2(multY * MONSTER_VELY, multX * MONSTER_VELX);
5577 }
5578 else
5579 {
5580 dir = my->yaw - atan2(MONSTER_VELY, MONSTER_VELX);
5581 }
5582 while ( dir >= PI )
5583 {
5584 dir -= PI * 2;
5585 }
5586 while ( dir < -PI )
5587 {
5588 dir += PI * 2;
5589 }
5590 my->yaw -= dir / 2;
5591 while ( my->yaw < 0 )
5592 {
5593 my->yaw += 2 * PI;
5594 }
5595 while ( my->yaw >= 2 * PI )
5596 {
5597 my->yaw -= 2 * PI;
5598 }
5599 }
5600 }
5601 else
5602 {
5603 Entity* target = uidToEntity(my->monsterTarget);
5604 if ( target )
5605 {
5606 my->lookAtEntity(*target);
5607 /*if ( myStats->type == SHADOW )
5608 {
5609 messagePlayer(0, "[SHADOW] No path #1: Resetting to wait state.");
5610 }*/
5611 }
5612 my->monsterState = MONSTER_STATE_WAIT; // no path, return to wait state
5613 if ( my->monsterAllyState == ALLY_STATE_MOVETO )
5614 {
5615 if ( my->monsterAllyInteractTarget != 0 )
5616 {
5617 //messagePlayer(0, "Interacting with a target!");
5618 if ( my->monsterAllySetInteract() )
5619 {
5620 if ( myStats->type == GYROBOT )
5621 {
5622 my->monsterSpecialState = GYRO_INTERACT_LANDING;
5623 my->monsterState = MONSTER_STATE_WAIT;
5624 serverUpdateEntitySkill(my, 33); // for clients to keep track of animation
5625 }
5626 else
5627 {
5628 if ( FollowerMenu.entityToInteractWith && FollowerMenu.entityToInteractWith->behavior == &actItem )
5629 {
5630 //my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM);
5631 }
5632 else
5633 {
5634 my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_OTHER);
5635 }
5636 my->monsterAllyInteractTarget = 0;
5637 my->monsterAllyState = ALLY_STATE_DEFAULT;
5638 }
5639 }
5640 }
5641 else
5642 {
5643 //messagePlayer(0, "Sent a move to command, defending here!");
5644 // scan for enemies after reaching move point.
5645 real_t dist = sightranges[myStats->type];
5646 for ( node = map.creatures->first; node != nullptr; node = node->next )
5647 {
5648 Entity* target = (Entity*)node->element;
5649 if ( target->behavior == &actMonster && my->checkEnemy(target) )
5650 {
5651 real_t oldDist = dist;
5652 dist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
5653 if ( dist < sightranges[myStats->type] && dist <= oldDist )
5654 {
5655 double tangent = atan2(target->y - my->y, target->x - my->x);
5656 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
5657 if ( hit.entity == target )
5658 {
5659 my->monsterLookTime = 1;
5660 my->monsterMoveTime = rand() % 10 + 1;
5661 my->monsterLookDir = tangent;
5662 break;
5663 }
5664 }
5665 }
5666 }
5667 my->monsterAllyState = ALLY_STATE_DEFEND;
5668 my->createPathBoundariesNPC(5);
5669 if ( myStats->type == GYROBOT && my->monsterSpecialState == GYRO_RETURN_PATHING )
5670 {
5671 my->monsterSpecialState = GYRO_RETURN_LANDING;
5672 my->monsterState = MONSTER_STATE_WAIT;
5673 serverUpdateEntitySkill(my, 33); // for clients to keep track of animation
5674 playSoundEntity(my, 449, 128);
5675 }
5676 }
5677 }
5678 else if ( !target && my->monsterAllyGetPlayerLeader() )
5679 {
5680 // scan for enemies after reaching move point.
5681 real_t dist = sightranges[myStats->type];
5682 for ( node = map.creatures->first; node != nullptr; node = node->next )
5683 {
5684 Entity* target = (Entity*)node->element;
5685 if ( target->behavior == &actMonster && my->checkEnemy(target) )
5686 {
5687 real_t oldDist = dist;
5688 dist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
5689 if ( dist < sightranges[myStats->type] && dist <= oldDist )
5690 {
5691 double tangent = atan2(target->y - my->y, target->x - my->x);
5692 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
5693 if ( hit.entity == target )
5694 {
5695 my->monsterLookTime = 1;
5696 my->monsterMoveTime = rand() % 10 + 1;
5697 my->monsterLookDir = tangent;
5698 break;
5699 }
5700 }
5701 }
5702 }
5703 }
5704 }
5705 }
5706 else
5707 {
5708 Entity* target = uidToEntity(my->monsterTarget);
5709 if ( target )
5710 {
5711 double tangent = atan2( target->y - my->y, target->x - my->x );
5712 my->monsterLookTime = 1;
5713 my->monsterMoveTime = rand() % 10 + 1;
5714 my->monsterLookDir = tangent;
5715 /*if ( myStats->type == SHADOW )
5716 {
5717 messagePlayer(0, "[SHADOW] No path #2: Resetting to wait state.");
5718 }*/
5719 }
5720 my->monsterState = MONSTER_STATE_WAIT; // no path, return to wait state
5721 if ( my->monsterAllyState == ALLY_STATE_MOVETO )
5722 {
5723 //messagePlayer(0, "Couldn't reach, retrying.");
5724 if ( target )
5725 {
5726 if ( my->monsterAllySetInteract() )
5727 {
5728 if ( myStats->type == GYROBOT )
5729 {
5730 my->monsterSpecialState = GYRO_INTERACT_LANDING;
5731 my->monsterState = MONSTER_STATE_WAIT;
5732 serverUpdateEntitySkill(my, 33); // for clients to keep track of animation
5733 }
5734 else
5735 {
5736 // we found our interactable within distance.
5737 //messagePlayer(0, "Found my interactable.");
5738 if ( FollowerMenu.entityToInteractWith && FollowerMenu.entityToInteractWith->behavior == &actItem )
5739 {
5740 //my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM);
5741 }
5742 else
5743 {
5744 my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_OTHER);
5745 }
5746 my->monsterAllyInteractTarget = 0;
5747 my->monsterAllyState = ALLY_STATE_DEFAULT;
5748 }
5749 }
5750 else if ( my->monsterSetPathToLocation(static_cast<int>(target->x / 16), static_cast<int>(target->y / 16), 2) )
5751 {
5752 my->monsterState = MONSTER_STATE_HUNT;
5753 my->monsterAllyState = ALLY_STATE_MOVETO;
5754 //messagePlayer(0, "Moving to my interactable!.");
5755 my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_REPATH);
5756 }
5757 else
5758 {
5759 // no path possible, give up.
5760 //messagePlayer(0, "I can't get to my target.");
5761 my->handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_FAIL);
5762 my->monsterAllyInteractTarget = 0;
5763 my->monsterAllyState = ALLY_STATE_DEFAULT;
5764 }
5765 }
5766 else
5767 {
5768 //messagePlayer(0, "Issued move command, defending here.");
5769 // scan for enemies after reaching move point.
5770 real_t dist = sightranges[myStats->type];
5771 for ( node = map.creatures->first; node != nullptr; node = node->next )
5772 {
5773 Entity* target = (Entity*)node->element;
5774 if ( target->behavior == &actMonster && my->checkEnemy(target) )
5775 {
5776 real_t oldDist = dist;
5777 dist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
5778 if ( dist < sightranges[myStats->type] && dist <= oldDist )
5779 {
5780 double tangent = atan2(target->y - my->y, target->x - my->x);
5781 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
5782 if ( hit.entity == target )
5783 {
5784 my->monsterLookTime = 1;
5785 my->monsterMoveTime = rand() % 10 + 1;
5786 my->monsterLookDir = tangent;
5787 break;
5788 }
5789 }
5790 }
5791 }
5792 my->monsterAllyState = ALLY_STATE_DEFEND;
5793 my->createPathBoundariesNPC(5);
5794 }
5795 }
5796 }
5797 }
5798 else
5799 {
5800 Entity* target = uidToEntity(my->monsterTarget);
5801 if ( target )
5802 {
5803 double tangent = atan2( target->y - my->y, target->x - my->x );
5804 my->monsterLookTime = 1;
5805 my->monsterMoveTime = rand() % 10 + 1;
5806 my->monsterLookDir = tangent;
5807 /*if ( myStats->type == SHADOW )
5808 {
5809 messagePlayer(0, "[SHADOW] No path #3: Resetting to wait state.");
5810 }*/
5811 }
5812 my->monsterState = MONSTER_STATE_WAIT; // no path, return to wait state
5813 //TODO: Replace with lookAtEntity();
5814 }
5815 }
5816 else if ( my->monsterState == MONSTER_STATE_TALK ) //Begin talk state
5817 {
5818 MONSTER_VELX = 0;
5819 MONSTER_VELY = 0;
5820
5821 // turn towards target
5822 Entity* target = uidToEntity(my->monsterTarget);
5823 if ( target != NULL )
5824 {
5825 dir = my->yaw - atan2( target->y - my->y, target->x - my->x );
5826 while ( dir >= PI )
5827 {
5828 dir -= PI * 2;
5829 }
5830 while ( dir < -PI )
5831 {
5832 dir += PI * 2;
5833 }
5834 my->yaw -= dir / 2;
5835 while ( my->yaw < 0 )
5836 {
5837 my->yaw += 2 * PI;
5838 }
5839 while ( my->yaw >= 2 * PI )
5840 {
5841 my->yaw -= 2 * PI;
5842 }
5843
5844 // abandon conversation if distance is too great
5845 if ( sqrt( pow(my->x - target->x, 2) + pow(my->y - target->y, 2) ) > TOUCHRANGE )
5846 {
5847 my->monsterState = MONSTER_STATE_WAIT;
5848 my->monsterTarget = 0;
5849 int player = -1;
5850 if ( target->behavior == &actPlayer )
5851 {
5852 player = target->skill[2];
5853 }
5854 if ( player == 0 )
5855 {
5856 closeAllGUIs(CLOSEGUI_ENABLE_SHOOTMODE, CLOSEGUI_CLOSE_ALL);
5857 }
5858 else if ( player > 0 )
5859 {
5860 // inform client of abandonment
5861 strcpy((char*)net_packet->data, "SHPC");
5862 net_packet->address.host = net_clients[player - 1].host;
5863 net_packet->address.port = net_clients[player - 1].port;
5864 net_packet->len = 4;
5865 sendPacketSafe(net_sock, -1, net_packet, player - 1);
5866 }
5867 monsterMoveAside(my, target);
5868 }
5869 }
5870 else
5871 {
5872 // abandon conversation
5873 my->monsterState = MONSTER_STATE_WAIT;
5874 my->monsterTarget = 0;
5875 }
5876 } //End talk state
5877 else if ( my->monsterState == MONSTER_STATE_LICH_DODGE ) // dodge state (herx)
5878 {
5879 double dist = 0;
5880 dist = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
5881 if ( dist != sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY) ) // hit obstacle
5882 {
5883 my->monsterSpecialTimer = 60;
5884 if ( rand() % 2 )
5885 {
5886 my->monsterState = MONSTER_STATE_WAIT; // wait state
5887 }
5888 else
5889 {
5890 my->monsterState = MONSTER_STATE_LICH_SUMMON; // summoning state
5891 }
5892 }
5893 else
5894 {
5895 my->monsterSpecialTimer++;
5896 if ( my->monsterSpecialTimer > 20 )
5897 {
5898 my->monsterSpecialTimer = 60;
5899 if ( rand() % 2 )
5900 {
5901 my->monsterState = MONSTER_STATE_WAIT; // wait state
5902 }
5903 else
5904 {
5905 my->monsterState = MONSTER_STATE_LICH_SUMMON; // summoning state
5906 }
5907 }
5908 }
5909 }
5910 else if ( my->monsterState == MONSTER_STATE_LICH_SUMMON ) // summoning state (herx)
5911 {
5912 MONSTER_ATTACK = 1;
5913 MONSTER_ATTACKTIME = 0;
5914 if ( my->monsterSpecialTimer )
5915 {
5916 my->monsterSpecialTimer--;
5917 }
5918 else
5919 {
5920 my->monsterSpecialTimer = 60;
5921 my->monsterState = MONSTER_STATE_WAIT; // wait state
5922 playSoundEntity(my, 166, 128);
5923
5924 Monster creature = NOTHING;
5925 switch ( rand() % 5 )
5926 {
5927 case 0:
5928 case 1:
5929 creature = CREATURE_IMP;
5930 break;
5931 case 2:
5932 case 3:
5933 case 4:
5934 creature = DEMON;
5935 break;
5936 }
5937 if ( creature != DEMON )
5938 {
5939 summonMonster(creature, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8);
5940 }
5941 summonMonster(creature, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8);
5942 }
5943 }
5944 else if ( my->monsterState == MONSTER_STATE_LICH_DEATH ) // lich death state
5945 {
5946 my->yaw += .5; // rotate
5947 if ( my->yaw >= PI * 2 )
5948 {
5949 my->yaw -= PI * 2;
5950 }
5951 MONSTER_ATTACK = 1;
5952 MONSTER_ATTACKTIME = 0;
5953 if ( my->monsterSpecialTimer == 0 )
5954 {
5955 serverUpdateEntitySkill(my, 8);
5956 serverUpdateEntitySkill(my, 9);
5957 int c;
5958 for ( c = 0; c < MAXPLAYERS; c++ )
5959 {
5960 playSoundPlayer(c, 186, 128);
5961 }
5962 }
5963 if ( my->monsterSpecialTimer % 10 == 0 )
5964 {
5965 spawnExplosion(my->x - 8 + rand() % 16, my->y - 8 + rand() % 16, -4 + rand() % 8);
5966 }
5967 my->monsterSpecialTimer++;
5968 if ( my->monsterSpecialTimer > 180 )
5969 {
5970 lichDie(my);
5971 return;
5972 }
5973 }
5974 else if ( my->monsterState == MONSTER_STATE_LICHFIRE_DIE
5975 || my->monsterState == MONSTER_STATE_LICHICE_DIE ) // lich death state
5976 {
5977 if ( my->monsterSpecialTimer < 100 )
5978 {
5979 my->yaw += .5; // rotate
5980 if ( my->yaw >= PI * 2 )
5981 {
5982 my->yaw -= PI * 2;
5983 }
5984 }
5985 //messagePlayer(0, "timer: %d", my->monsterSpecialTimer);
5986 if ( my->monsterSpecialTimer == 180 )
5987 {
5988 if ( myStats->type == LICH_FIRE )
5989 {
5990 my->monsterAttack = MONSTER_POSE_SPECIAL_WINDUP1;
5991 }
5992 else if ( myStats->type == LICH_ICE )
5993 {
5994 my->monsterAttack = MONSTER_POSE_SPECIAL_WINDUP2;
5995 }
5996 my->monsterAttackTime = 0;
5997 serverUpdateEntitySkill(my, 8);
5998 serverUpdateEntitySkill(my, 9);
5999 for ( int c = 0; c < MAXPLAYERS; c++ )
6000 {
6001 if ( myStats->type == LICH_FIRE )
6002 {
6003 playSoundPlayer(c, 376, 128);
6004 messagePlayerColor(c, uint32ColorOrange(*mainsurface), language[2646]);
6005 }
6006 else if ( myStats->type == LICH_ICE )
6007 {
6008 playSoundPlayer(c, 381, 128);
6009 messagePlayerColor(c, uint32ColorBaronyBlue(*mainsurface), language[2648]);
6010 }
6011 }
6012 }
6013 if ( my->monsterSpecialTimer % 15 == 0 )
6014 {
6015 spawnExplosion(my->x - 8 + rand() % 16, my->y - 8 + rand() % 16, my->z -4 + rand() % 8);
6016 }
6017 --my->monsterSpecialTimer;
6018 if ( my->monsterSpecialTimer <= 0 )
6019 {
6020 if ( myStats->type == LICH_FIRE )
6021 {
6022 lichFireDie(my);
6023 return;
6024 }
6025 else if ( myStats->type == LICH_ICE )
6026 {
6027 lichIceDie(my);
6028 return;
6029 }
6030 }
6031 }
6032 else if ( my->monsterState == MONSTER_STATE_DEVIL_DEATH ) // devil death state
6033 {
6034 my->z += .5; // descend slowly
6035 MONSTER_ATTACK = 4;
6036 MONSTER_ATTACKTIME = 0;
6037 /*if( MONSTER_SPECIAL==0 ) {
6038 int c;
6039 for( c=0; c<MAXPLAYERS; c++ )
6040 playSoundPlayer(c,186,128);
6041 }*/
6042 if ( my->monsterSpecialTimer == 0 )
6043 {
6044 serverUpdateEntitySkill(my, 8);
6045 serverUpdateEntitySkill(my, 9);
6046 my->x += cos(my->yaw + PI / 2) * 2;
6047 my->y += sin(my->yaw + PI / 2) * 2;
6048 }
6049 else if ( my->monsterSpecialTimer % 2 == 0 )
6050 {
6051 my->x += cos(my->yaw + PI / 2) * 4;
6052 my->y += sin(my->yaw + PI / 2) * 4;
6053 }
6054 else
6055 {
6056 my->x -= cos(my->yaw + PI / 2) * 4;
6057 my->y -= sin(my->yaw + PI / 2) * 4;
6058 }
6059 if ( my->monsterSpecialTimer % 10 == 0 )
6060 {
6061 spawnExplosion(my->x - 24 + rand() % 48, my->y - 24 + rand() % 48, -16 + rand() % 32);
6062 }
6063 my->monsterSpecialTimer++;
6064 if ( my->z > 96 )
6065 {
6066 devilDie(my);
6067 return;
6068 }
6069 }
6070 else if ( my->monsterState == MONSTER_STATE_DEVIL_TELEPORT ) // devil teleport state
6071 {
6072 my->flags[PASSABLE] = true;
6073 my->yaw += .1; // rotate
6074 if ( my->yaw >= PI * 2 )
6075 {
6076 my->yaw -= PI * 2;
6077 }
6078 my->z = std::min<int>(my->z + 1, 64); // descend
6079 MONSTER_ATTACK = 4;
6080 MONSTER_ATTACKTIME = 0;
6081 MONSTER_ARMBENDED = 1;
6082 if ( my->monsterSpecialTimer == 0 )
6083 {
6084 serverUpdateEntitySkill(my, 8);
6085 serverUpdateEntitySkill(my, 9);
6086 serverUpdateEntitySkill(my, 10);
6087 }
6088 ++my->monsterSpecialTimer;
6089 if ( my->z >= 64 )
6090 {
6091 node_t* node;
6092 int c = 0;
6093 for ( node = map.entities->first; node != nullptr; node = node->next )
6094 {
6095 Entity* entity = (Entity*)node->element;
6096 if ( entity->behavior == &actDevilTeleport )
6097 {
6098 if ( entity->x == my->x && entity->y == my->y )
6099 {
6100 continue;
6101 }
6102 switch ( entity->sprite )
6103 {
6104 case 72:
6105 if ( devilstate == 74 )
6106 {
6107 c++;
6108 }
6109 continue;
6110 case 73:
6111 if ( devilstate == 0 || devilstate == 72 )
6112 {
6113 c++;
6114 }
6115 continue;
6116 case 74:
6117 if ( devilstate == 73 )
6118 {
6119 c++;
6120 }
6121 continue;
6122 default:
6123 continue;
6124 }
6125 }
6126 }
6127 if ( c )
6128 {
6129 int i = rand() % c;
6130 c = 0;
6131 for ( node = map.entities->first; node != nullptr; node = node->next )
6132 {
6133 Entity* entity = (Entity*)node->element;
6134 if ( entity->behavior == &actDevilTeleport )
6135 {
6136 if ( entity->x == my->x && entity->y == my->y )
6137 {
6138 continue;
6139 }
6140 switch ( entity->sprite )
6141 {
6142 case 72:
6143 if ( devilstate == 74 )
6144 {
6145 if ( c == i )
6146 {
6147 break;
6148 }
6149 else
6150 {
6151 c++;
6152 continue;
6153 }
6154 }
6155 continue;
6156 case 73:
6157 if ( devilstate == 0 || devilstate == 72 )
6158 {
6159 if ( c == i )
6160 {
6161 break;
6162 }
6163 else
6164 {
6165 c++;
6166 continue;
6167 }
6168 }
6169 continue;
6170 case 74:
6171 if ( devilstate == 73 )
6172 {
6173 if ( c == i )
6174 {
6175 break;
6176 }
6177 else
6178 {
6179 c++;
6180 continue;
6181 }
6182 }
6183 continue;
6184 default:
6185 continue;
6186 }
6187 my->x = entity->x;
6188 my->y = entity->y;
6189 devilstate = entity->sprite;
6190 devilacted = 0;
6191 break;
6192 }
6193 }
6194 }
6195 my->monsterSpecialTimer = 30;
6196 my->monsterState = MONSTER_STATE_DEVIL_RISING;
6197 }
6198 }
6199 else if ( my->monsterState == MONSTER_STATE_DEVIL_RISING ) // devil rising state (post-teleport)
6200 {
6201 if ( my->monsterSpecialTimer <= 0 )
6202 {
6203 my->z = std::max<int>(my->z - 1, -4); // ascend
6204 }
6205 else
6206 {
6207 --my->monsterSpecialTimer;
6208 if ( my->monsterSpecialTimer <= 0 )
6209 {
6210 if ( myStats->HP > 0 )
6211 {
6212 my->flags[PASSABLE] = false;
6213 }
6214 node_t* node;
6215 for ( node = map.creatures->first; node != nullptr; node = node->next ) //Since it only looks at entities that have stats, only creatures can have stats; don't iterate map.entities.
6216 {
6217 Entity* entity = (Entity*)node->element;
6218 if ( entity == my )
6219 {
6220 continue;
6221 }
6222 if ( entityInsideEntity(my, entity) )
6223 {
6224 Stat* stats = entity->getStats();
6225 if ( stats )
6226 {
6227 if ( stats->HP > 0 )
6228 {
6229 stats->HP = 0;
6230 }
6231 }
6232 }
6233 }
6234 }
6235 }
6236 if ( !devilroar )
6237 {
6238 if ( my->z <= -4 )
6239 {
6240 int j = rand() % 5;
6241 int c;
6242 for ( c = 0; c < MAXPLAYERS; c++ )
6243 {
6244 playSoundPlayer(c, 204 + j, 64);
6245 }
6246 playSoundEntity(my, 204 + j, 128);
6247 devilroar = 1;
6248 MONSTER_ATTACK = 4;
6249 MONSTER_ATTACKTIME = 0;
6250 MONSTER_ARMBENDED = 1;
6251 serverUpdateEntitySkill(my, 8);
6252 serverUpdateEntitySkill(my, 9);
6253 serverUpdateEntitySkill(my, 10);
6254 }
6255 else
6256 {
6257 my->yaw += .1; // rotate
6258 if ( my->yaw >= PI * 2 )
6259 {
6260 my->yaw -= PI * 2;
6261 }
6262 }
6263 }
6264 else
6265 {
6266 node_t* tempNode;
6267 Entity* playertotrack = nullptr;
6268 for ( tempNode = map.creatures->first; tempNode != nullptr; tempNode = tempNode->next ) //Only inspects players, so don't iterate map.entities.
6269 {
6270 Entity* tempEntity = (Entity*)tempNode->element;
6271 double lowestdist = 5000;
6272 if ( tempEntity->behavior == &actPlayer )
6273 {
6274 double disttoplayer = entityDist(my, tempEntity);
6275 if ( disttoplayer < lowestdist )
6276 {
6277 playertotrack = tempEntity;
6278 }
6279 }
6280 }
6281 if ( playertotrack )
6282 {
6283 my->monsterTarget = playertotrack->getUID();
6284 my->monsterTargetX = playertotrack->x;
6285 my->monsterTargetY = playertotrack->y;
6286 MONSTER_VELX = my->monsterTargetX - my->x;
6287 MONSTER_VELY = my->monsterTargetY - my->y;
6288 }
6289 else
6290 {
6291 MONSTER_VELX = 0;
6292 MONSTER_VELY = 0;
6293 }
6294
6295 // rotate monster
6296 dir = my->yaw - atan2( MONSTER_VELY, MONSTER_VELX );
6297
6298 // To prevent the Entity's position from being updated by dead reckoning on the CLient, set the velocity to 0 after usage
6299 MONSTER_VELX = 0.0;
6300 MONSTER_VELY = 0.0;
6301
6302 while ( dir >= PI )
6303 {
6304 dir -= PI * 2;
6305 }
6306 while ( dir < -PI )
6307 {
6308 dir += PI * 2;
6309 }
6310 my->yaw -= dir / 2;
6311 while ( my->yaw < 0 )
6312 {
6313 my->yaw += 2 * PI;
6314 }
6315 while ( my->yaw >= 2 * PI )
6316 {
6317 my->yaw -= 2 * PI;
6318 }
6319
6320 if ( MONSTER_ATTACKTIME > 60 )
6321 {
6322 my->monsterState = MONSTER_STATE_ATTACK;
6323 MONSTER_ATTACK = 0;
6324 MONSTER_ATTACKTIME = 0;
6325 MONSTER_ARMBENDED = 0;
6326 serverUpdateEntitySkill(my, 8);
6327 serverUpdateEntitySkill(my, 9);
6328 serverUpdateEntitySkill(my, 10);
6329 devilroar = 0;
6330 MONSTER_VELX = 0;
6331 MONSTER_VELY = 0;
6332 }
6333 }
6334 }
6335 else if ( my->monsterState == MONSTER_STATE_DEVIL_SUMMON ) // devil summoning state
6336 {
6337 MONSTER_ATTACK = 4;
6338 MONSTER_ATTACKTIME = 0;
6339 if ( my->monsterSpecialTimer == 0 )
6340 {
6341 serverUpdateEntitySkill(my, 8);
6342 serverUpdateEntitySkill(my, 9);
6343 }
6344 ++my->monsterSpecialTimer;
6345 if ( my->monsterSpecialTimer == 20 ) // start the spawn animations
6346 {
6347 Monster creature = NOTHING;
6348 int numToSpawn = 3;
6349 int numPlayers = 1;
6350 std::vector<int> alivePlayers;
6351 for ( int c = 1; c < MAXPLAYERS; ++c )
6352 {
6353 if ( !client_disconnected[c] )
6354 {
6355 ++numToSpawn;
6356 ++numPlayers;
6357 if ( players[c] && players[c]->entity )
6358 {
6359 alivePlayers.push_back(c);
6360 }
6361 }
6362 }
6363
6364 int spawnedShadows = 0;
6365 while ( numToSpawn > 0 )
6366 {
6367 if ( devilsummonedtimes % 2 == 1 && my->devilGetNumMonstersInArena(SHADOW) + spawnedShadows < numPlayers )
6368 {
6369 // odd numbered spawns.
6370 // let's make some shadows.
6371 if ( !alivePlayers.empty() )
6372 {
6373 int vectorEntry = rand() % alivePlayers.size();
6374 my->devilSummonMonster(nullptr, SHADOW, 5, alivePlayers[vectorEntry]);
6375 alivePlayers.erase(alivePlayers.begin() + vectorEntry);
6376 }
6377 else
6378 {
6379 my->devilSummonMonster(nullptr, SHADOW, 9);
6380 }
6381 ++spawnedShadows;
6382 }
6383 else
6384 {
6385 switch ( rand() % 5 )
6386 {
6387 case 0:
6388 case 1:
6389 my->devilSummonMonster(nullptr, CREATURE_IMP, 9);
6390 break;
6391 case 2:
6392 case 3:
6393 case 4:
6394 my->devilSummonMonster(nullptr, DEMON, 7);
6395 break;
6396 }
6397 }
6398 --numToSpawn;
6399 }
6400 ++devilsummonedtimes;
6401 }
6402 else if ( my->monsterSpecialTimer > 100 ) // end this state.
6403 {
6404 MONSTER_ATTACK = 0;
6405 MONSTER_ATTACKTIME = 0;
6406 serverUpdateEntitySkill(my, 8);
6407 serverUpdateEntitySkill(my, 9);
6408 my->monsterSpecialTimer = 0;
6409 my->monsterState = MONSTER_STATE_ATTACK;
6410 node_t* tempNode;
6411 Entity* playertotrack = nullptr;
6412 for ( tempNode = map.creatures->first; tempNode != nullptr; tempNode = tempNode->next ) //Only inspects players, so don't iterate map.entities. Technically, only needs to iterate through the players[] array, eh?
6413 {
6414 Entity* tempEntity = (Entity*)tempNode->element;
6415 double lowestdist = 5000;
6416 if ( tempEntity->behavior == &actPlayer )
6417 {
6418 double disttoplayer = entityDist(my, tempEntity);
6419 if ( disttoplayer < lowestdist )
6420 {
6421 playertotrack = tempEntity;
6422 }
6423 }
6424 }
6425 if ( playertotrack )
6426 {
6427 my->monsterTarget = playertotrack->getUID();
6428 my->monsterTargetX = playertotrack->x;
6429 my->monsterTargetY = playertotrack->y;
6430 }
6431 }
6432 }
6433 else if ( my->monsterState == MONSTER_STATE_DEVIL_BOULDER ) // devil boulder spawn state
6434 {
6435 int angle = -1;
6436 if ( (int)(my->x / 16) == 14 && (int)(my->y / 16) == 32 )
6437 {
6438 angle = 0;
6439 }
6440 else if ( (int)(my->x / 16) == 32 && (int)(my->y / 16) == 14 )
6441 {
6442 angle = 1;
6443 }
6444 else if ( (int)(my->x / 16) == 50 && (int)(my->y / 16) == 32 )
6445 {
6446 angle = 2;
6447 }
6448 else if ( (int)(my->x / 16) == 32 && (int)(my->y / 16) == 50 )
6449 {
6450 angle = 3;
6451 }
6452 std::unordered_set<int> lavalLocationsXY = { 22,23,24,31,32,33,40,41,42 };
6453 int numLavaBoulders = 0;
6454
6455 my->yaw = angle * PI / 2;
6456 my->monsterSpecialTimer++;
6457 if ( my->monsterSpecialTimer == 10 )
6458 {
6459 MONSTER_ATTACK = 1;
6460 MONSTER_ATTACKTIME = 0;
6461 serverUpdateEntitySkill(my, 8);
6462 serverUpdateEntitySkill(my, 9);
6463
6464 my->castOrbitingMagicMissile(SPELL_BLEED, 32.0, 0.0, 300);
6465 my->castOrbitingMagicMissile(SPELL_BLEED, 32.0, 2 * PI / 5, 300);
6466 my->castOrbitingMagicMissile(SPELL_BLEED, 32.0, 4 * PI / 5, 300);
6467 my->castOrbitingMagicMissile(SPELL_BLEED, 32.0, 6 * PI / 5, 300);
6468 my->castOrbitingMagicMissile(SPELL_BLEED, 32.0, 8 * PI / 5, 300);
6469 }
6470 if ( my->monsterSpecialTimer == 40 )
6471 {
6472 int c;
6473 double oyaw = my->yaw;
6474 for ( c = 0; c < 12; c++ )
6475 {
6476 my->yaw = ((double)c + ((rand() % 100) / 100.f)) * (PI * 2) / 12.f;
6477 castSpell(my->getUID(), &spell_fireball, true, false);
6478 }
6479 my->yaw = oyaw;
6480 for ( c = 0; c < 7; ++c )
6481 {
6482 if ( c == 6 && (angle == 1 || angle == 2) )
6483 {
6484 continue;
6485 }
6486 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
6487 entity->parent = my->getUID();
6488 if ( angle == 0 )
6489 {
6490 entity->x = (20 << 4) + 8;
6491 entity->y = (32 << 4) + 8 + 32 * c;
6492 }
6493 else if ( angle == 1 )
6494 {
6495 entity->x = (20 << 4) + 8 + 32 * c;
6496 entity->y = (20 << 4) + 8;
6497 }
6498 else if ( angle == 2 )
6499 {
6500 entity->x = (44 << 4) + 8;
6501 entity->y = (20 << 4) + 8 + 32 * c;
6502 }
6503 else if ( angle == 3 )
6504 {
6505 entity->x = (32 << 4) + 8 + 32 * c;
6506 entity->y = (44 << 4) + 8;
6507 }
6508
6509 if ( lavalLocationsXY.find(static_cast<int>(entity->x / 16)) != lavalLocationsXY.end()
6510 || lavalLocationsXY.find(static_cast<int>(entity->y / 16)) != lavalLocationsXY.end()
6511 || myStats->HP < myStats->MAXHP * 0.5 )
6512 {
6513 // will roll over lava or Baphy < 50% HP
6514 int chance = 4;
6515 if ( myStats->HP < myStats->MAXHP * 0.25 )
6516 {
6517 chance = 1;
6518 }
6519 else if ( myStats->HP < myStats->MAXHP * 0.5 )
6520 {
6521 chance = 2;
6522 }
6523 else if ( myStats->HP < myStats->MAXHP * 0.75 )
6524 {
6525 chance = 3;
6526 }
6527
6528 if ( rand() % chance == 0 || (numLavaBoulders < 2 && rand() % 2) )
6529 {
6530 entity->sprite = 989; // lava boulder.
6531 ++numLavaBoulders;
6532 }
6533 }
6534
6535 entity->z = -64;
6536 entity->yaw = angle * (PI / 2.f);
6537 entity->sizex = 7;
6538 entity->sizey = 7;
6539 entity->behavior = &actBoulder;
6540 entity->flags[UPDATENEEDED] = true;
6541 entity->flags[PASSABLE] = true;
6542 }
6543 }
6544 if ( my->monsterSpecialTimer == 60 )
6545 {
6546 MONSTER_ATTACK = 2;
6547 MONSTER_ATTACKTIME = 0;
6548 serverUpdateEntitySkill(my, 8);
6549 serverUpdateEntitySkill(my, 9);
6550 }
6551 if ( my->monsterSpecialTimer == 90 )
6552 {
6553 int c;
6554 double oyaw = my->yaw;
6555 for ( c = 0; c < 12; ++c )
6556 {
6557 my->yaw = ((double)c + ((rand() % 100) / 100.f)) * (PI * 2) / 12.f;
6558 castSpell(my->getUID(), &spell_fireball, true, false);
6559 }
6560 for ( c = 0; c < MAXPLAYERS; ++c )
6561 {
6562 my->devilBoulderSummonIfPlayerIsHiding(c);
6563 }
6564 my->yaw = oyaw;
6565
6566 for ( c = 0; c < 7; ++c )
6567 {
6568 if ( c == 6 && (angle == 0 || angle == 3) )
6569 {
6570 continue;
6571 }
6572 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
6573 entity->parent = my->getUID();
6574 if ( angle == 0 )
6575 {
6576 entity->x = (20 << 4) + 8;
6577 entity->y = (20 << 4) + 8 + 32 * c;
6578 }
6579 else if ( angle == 1 )
6580 {
6581 entity->x = (32 << 4) + 8 + 32 * c;
6582 entity->y = (20 << 4) + 8;
6583 }
6584 else if ( angle == 2 )
6585 {
6586 entity->x = (44 << 4) + 8;
6587 entity->y = (32 << 4) + 8 + 32 * c;
6588 }
6589 else if ( angle == 3 )
6590 {
6591 entity->x = (20 << 4) + 8 + 32 * c;
6592 entity->y = (44 << 4) + 8;
6593 }
6594
6595 if ( lavalLocationsXY.find(static_cast<int>(entity->x / 16)) != lavalLocationsXY.end()
6596 || lavalLocationsXY.find(static_cast<int>(entity->y / 16)) != lavalLocationsXY.end()
6597 || myStats->HP < myStats->MAXHP * 0.5 )
6598 {
6599 // will roll over lava or Baphy < 50% HP
6600 int chance = 4;
6601 if ( myStats->HP < myStats->MAXHP * 0.25 )
6602 {
6603 chance = 1;
6604 }
6605 else if ( myStats->HP < myStats->MAXHP * 0.5 )
6606 {
6607 chance = 2;
6608 }
6609 else if ( myStats->HP < myStats->MAXHP * 0.75 )
6610 {
6611 chance = 3;
6612 }
6613
6614 if ( rand() % chance == 0 || (numLavaBoulders < 2 && rand() % 2) )
6615 {
6616 entity->sprite = 989; // lava boulder.
6617 ++numLavaBoulders;
6618 }
6619 }
6620
6621 entity->z = -64;
6622 entity->yaw = angle * (PI / 2.f);
6623 entity->sizex = 7;
6624 entity->sizey = 7;
6625 entity->behavior = &actBoulder;
6626 entity->flags[UPDATENEEDED] = true;
6627 entity->flags[PASSABLE] = true;
6628 }
6629 }
6630 if ( my->monsterSpecialTimer == 180 )
6631 {
6632 MONSTER_ATTACK = 3;
6633 MONSTER_ATTACKTIME = 0;
6634 serverUpdateEntitySkill(my, 8);
6635 serverUpdateEntitySkill(my, 9);
6636 }
6637 if ( my->monsterSpecialTimer == 210 )
6638 {
6639 int c;
6640 double oyaw = my->yaw;
6641 for ( c = 0; c < 12; ++c )
6642 {
6643 my->yaw = ((double)c + ((rand() % 100) / 100.f)) * (PI * 2) / 12.f;
6644 castSpell(my->getUID(), &spell_fireball, true, false);
6645 }
6646 for ( c = 0; c < MAXPLAYERS; ++c )
6647 {
6648 my->devilBoulderSummonIfPlayerIsHiding(c);
6649 }
6650 my->yaw = oyaw;
6651 for ( c = 0; c < 12; ++c )
6652 {
6653 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
6654 entity->parent = my->getUID();
6655 if ( angle == 0 )
6656 {
6657 entity->x = (20 << 4) + 8;
6658 entity->y = (21 << 4) + 8 + 32 * c;
6659 }
6660 else if ( angle == 1 )
6661 {
6662 entity->x = (21 << 4) + 8 + 32 * c;
6663 entity->y = (20 << 4) + 8;
6664 }
6665 else if ( angle == 2 )
6666 {
6667 entity->x = (44 << 4) + 8;
6668 entity->y = (21 << 4) + 8 + 32 * c;
6669 }
6670 else if ( angle == 3 )
6671 {
6672 entity->x = (21 << 4) + 8 + 32 * c;
6673 entity->y = (44 << 4) + 8;
6674 }
6675
6676 if ( lavalLocationsXY.find(static_cast<int>(entity->x / 16)) != lavalLocationsXY.end()
6677 || lavalLocationsXY.find(static_cast<int>(entity->y / 16)) != lavalLocationsXY.end()
6678 || myStats->HP < myStats->MAXHP * 0.5 )
6679 {
6680 // will roll over lava or Baphy < 50% HP
6681 int chance = 4;
6682 if ( myStats->HP < myStats->MAXHP * 0.25 )
6683 {
6684 chance = 1;
6685 }
6686 else if ( myStats->HP < myStats->MAXHP * 0.5 )
6687 {
6688 chance = 2;
6689 }
6690 else if ( myStats->HP < myStats->MAXHP * 0.75 )
6691 {
6692 chance = 3;
6693 }
6694
6695 if ( rand() % chance == 0 || (numLavaBoulders < 3 && rand() % 2) )
6696 {
6697 entity->sprite = 989; // lava boulder.
6698 ++numLavaBoulders;
6699 }
6700 }
6701
6702 entity->z = -64;
6703 entity->yaw = angle * (PI / 2.f);
6704 entity->sizex = 7;
6705 entity->sizey = 7;
6706 entity->behavior = &actBoulder;
6707 entity->flags[UPDATENEEDED] = true;
6708 entity->flags[PASSABLE] = true;
6709 }
6710 }
6711 if ( my->monsterSpecialTimer == 300 ) // 300 blaze it I guess
6712 {
6713 MONSTER_ATTACK = 0;
6714 MONSTER_ATTACKTIME = 0;
6715 serverUpdateEntitySkill(my, 8);
6716 serverUpdateEntitySkill(my, 9);
6717 my->monsterSpecialTimer = 0;
6718 my->monsterState = MONSTER_STATE_ATTACK;
6719 node_t* tempNode;
6720 Entity* playertotrack = nullptr;
6721 for ( tempNode = map.creatures->first; tempNode != nullptr; tempNode = tempNode->next ) //Iterate map.creatures, since only inspecting players, not all entities. Technically should just iterate over players[]?
6722 {
6723 Entity* tempEntity = (Entity*)tempNode->element;
6724 double lowestdist = 5000;
6725 if ( tempEntity->behavior == &actPlayer )
6726 {
6727 double disttoplayer = entityDist(my, tempEntity);
6728 if ( disttoplayer < lowestdist )
6729 {
6730 playertotrack = tempEntity;
6731 }
6732 }
6733 }
6734 if ( playertotrack )
6735 {
6736 my->monsterTarget = playertotrack->getUID();
6737 my->monsterTargetX = playertotrack->x;
6738 my->monsterTargetY = playertotrack->y;
6739 }
6740 }
6741 }
6742 else if ( my->monsterState == MONSTER_STATE_LICHFIRE_DODGE
6743 || my->monsterState == MONSTER_STATE_LICHICE_DODGE )
6744 {
6745 dist = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
6746 Entity* target = uidToEntity(my->monsterTarget);
6747 if ( dist != sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY) )
6748 {
6749 my->monsterSpecialTimer = 0; // hit obstacle
6750 }
6751 if ( target && my->monsterSpecialTimer != 0 && myStats->type == LICH_FIRE )
6752 {
6753 dist = sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2));
6754 if ( dist < STRIKERANGE && rand () % 20 == 0 )
6755 {
6756 my->monsterSpecialTimer = 0; // close enough to target, chance to stop early
6757 }
6758 }
6759 if ( my->monsterSpecialTimer == 0 )
6760 {
6761 my->monsterState = MONSTER_STATE_WAIT;
6762 MONSTER_VELX = 0;
6763 MONSTER_VELY = 0;
6764 if ( target )
6765 {
6766 my->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH);
6767 if ( myStats->type == LICH_FIRE )
6768 {
6769 my->monsterHitTime = HITRATE * 2;
6770 if ( sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2)) < STRIKERANGE )
6771 {
6772 if ( rand() % 2 == 0 )
6773 {
6774 my->monsterLichFireMeleeSeq = LICH_ATK_RISING_RAIN;
6775 my->handleMonsterAttack(myStats, target, 0.f);
6776 }
6777 else
6778 {
6779 my->monsterHitTime = 25;
6780 }
6781 }
6782 }
6783 else if ( myStats->type == LICH_ICE )
6784 {
6785 my->monsterHitTime = HITRATE * 2;
6786 if ( sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2)) < STRIKERANGE * 2 )
6787 {
6788 my->monsterLichIceCastSeq = LICH_ATK_CHARGE_AOE;
6789 my->handleMonsterAttack(myStats, target, 0.f);
6790 }
6791 }
6792 }
6793 }
6794 }
6795 else if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS )
6796 {
6797 ++my->monsterHitTime;
6798 if ( myStats->type == LICH_FIRE )
6799 {
6800 if ( my->monsterLichFireMeleeSeq == 0 )
6801 {
6802 if ( my->monsterHitTime >= 60 || my->monsterLichAllyStatus == LICH_ALLY_DEAD && my->monsterHitTime >= 45 )
6803 {
6804 Entity* target = uidToEntity(my->monsterTarget);
6805 if ( target )
6806 {
6807 tangent = atan2(target->y - my->y, target->x - my->x);
6808 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
6809 /*if ( hit.entity )
6810 {
6811 messagePlayer(0, "sprite: %d", hit.entity->sprite);
6812 }*/
6813 if ( hit.entity == target && rand() % 5 > 0 )
6814 {
6815 switch ( rand() % 3 )
6816 {
6817 case 0:
6818 my->monsterLichFireMeleeSeq = LICH_ATK_BASICSPELL_SINGLE;
6819 my->handleMonsterAttack(myStats, target, entityDist(my, target));
6820 my->monsterLichFireMeleeSeq = 0;
6821 break;
6822 case 1:
6823 my->monsterLichFireMeleeSeq = LICH_ATK_RISING_SINGLE;
6824 break;
6825 case 2:
6826 my->monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_SINGLE;
6827 break;
6828 default:
6829 break;
6830 }
6831 my->monsterLichMagicCastCount = 0;
6832 //my->handleMonsterAttack(myStats, target, entityDist(my, target));
6833 }
6834 else
6835 {
6836 my->monsterLichFireMeleeSeq = LICH_ATK_RISING_RAIN;
6837 my->handleMonsterAttack(myStats, target, entityDist(my, target));
6838 my->monsterLichFireMeleeSeq = 0;
6839 }
6840 my->monsterHitTime = 0;
6841 }
6842 else
6843 {
6844 real_t distToPlayer = 0.f;
6845 int playerToChase = -1;
6846 for ( c = 0; c < MAXPLAYERS; c++ )
6847 {
6848 if ( players[c] && players[c]->entity )
6849 {
6850 if ( !distToPlayer )
6851 {
6852 distToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
6853 playerToChase = c;
6854 }
6855 else
6856 {
6857 double newDistToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
6858 if ( newDistToPlayer < distToPlayer )
6859 {
6860 distToPlayer = newDistToPlayer;
6861 playerToChase = c;
6862 }
6863 }
6864 }
6865 }
6866 if ( playerToChase >= 0 && players[playerToChase] && players[playerToChase]->entity )
6867 {
6868 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_PATH);
6869 }
6870 }
6871 }
6872 }
6873
6874 if ( my->monsterLichFireMeleeSeq != 0
6875 && my->monsterHitTime >= 0
6876 && my->monsterHitTime % 10 == 0 )
6877 {
6878 if ( my->monsterLichFireMeleeSeq == LICH_ATK_RISING_SINGLE )
6879 {
6880 if ( my->monsterLichMagicCastCount < 3 + rand() % 2 )
6881 {
6882 if ( my->monsterLichMagicCastCount == 0 )
6883 {
6884 my->attack(MONSTER_POSE_MELEE_WINDUP3, 0, nullptr);
6885 }
6886 else
6887 {
6888 Entity* spell = castSpell(my->getUID(), getSpellFromID(SPELL_FIREBALL), true, false);
6889 spell->yaw += (PI / 64) * (-1 + rand() % 3);
6890 spell->vel_x = cos(spell->yaw) * 4;
6891 spell->vel_y = sin(spell->yaw) * 4;
6892 }
6893 ++my->monsterLichMagicCastCount;
6894 }
6895 else
6896 {
6897 my->monsterLichFireMeleeSeq = 0;
6898 my->monsterHitTime = 0;
6899 }
6900 }
6901 else if ( my->monsterLichFireMeleeSeq == LICH_ATK_HORIZONTAL_SINGLE )
6902 {
6903 if ( my->monsterLichMagicCastCount < 2 )
6904 {
6905 if ( my->monsterLichMagicCastCount == 0 )
6906 {
6907 my->attack(MONSTER_POSE_MELEE_WINDUP2, 0, nullptr);
6908 }
6909 else
6910 {
6911 Entity* spell = castSpell(my->getUID(), getSpellFromID(SPELL_FIREBALL), true, false);
6912 spell->yaw += PI / 16;
6913 spell->vel_x = cos(spell->yaw) * 4;
6914 spell->vel_y = sin(spell->yaw) * 4;
6915 spell = castSpell(my->getUID(), getSpellFromID(SPELL_FIREBALL), true, false);
6916 spell->yaw -= PI / 16;
6917 spell->vel_x = cos(spell->yaw) * 4;
6918 spell->vel_y = sin(spell->yaw) * 4;
6919 spell = castSpell(my->getUID(), getSpellFromID(SPELL_FIREBALL), true, false);
6920 }
6921 ++my->monsterLichMagicCastCount;
6922 }
6923 else
6924 {
6925 my->monsterLichFireMeleeSeq = 0;
6926 my->monsterHitTime = 0;
6927 }
6928 }
6929 }
6930 }
6931 else if ( myStats->type == LICH_ICE )
6932 {
6933 if ( my->monsterLichIceCastSeq == 0 )
6934 {
6935 if ( my->monsterHitTime >= 60 || (my->monsterLichAllyStatus == LICH_ALLY_DEAD && my->monsterHitTime >= 45) )
6936 {
6937 Entity* target = uidToEntity(my->monsterTarget);
6938 if ( target )
6939 {
6940 tangent = atan2(target->y - my->y, target->x - my->x);
6941 lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
6942 switch ( rand() % 4 )
6943 {
6944 case 0:
6945 case 1:
6946 my->monsterLichIceCastSeq = LICH_ATK_HORIZONTAL_SINGLE;
6947 break;
6948 case 2:
6949 case 3:
6950 if ( my->monsterLichAllyStatus == LICH_ALLY_DEAD )
6951 {
6952 Entity* dummyEntity = nullptr;
6953 if ( numMonsterTypeAliveOnMap(AUTOMATON, dummyEntity) <= 1 )
6954 {
6955 my->monsterLichIceCastSeq = LICH_ATK_SUMMON;
6956 }
6957 else
6958 {
6959 my->monsterLichIceCastSeq = LICH_ATK_RISING_SINGLE;
6960 }
6961 }
6962 else
6963 {
6964 my->monsterLichIceCastSeq = LICH_ATK_RISING_SINGLE;
6965 }
6966 break;
6967 default:
6968 break;
6969 }
6970 my->monsterHitTime = 0;
6971 my->monsterLichMagicCastCount = 0;
6972 }
6973 else
6974 {
6975 real_t distToPlayer = 0.f;
6976 int playerToChase = -1;
6977 for ( c = 0; c < MAXPLAYERS; c++ )
6978 {
6979 if ( players[c] && players[c]->entity )
6980 {
6981 if ( !distToPlayer )
6982 {
6983 distToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
6984 playerToChase = c;
6985 }
6986 else
6987 {
6988 double newDistToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
6989 if ( newDistToPlayer < distToPlayer )
6990 {
6991 distToPlayer = newDistToPlayer;
6992 playerToChase = c;
6993 }
6994 }
6995 }
6996 }
6997 if ( playerToChase >= 0 && players[playerToChase] && players[playerToChase]->entity )
6998 {
6999 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_PATH);
7000 }
7001 }
7002 }
7003 }
7004
7005 if ( my->monsterLichIceCastSeq != 0
7006 && my->monsterHitTime >= 0
7007 && my->monsterHitTime % 10 == 0 )
7008 {
7009 if ( my->monsterLichIceCastSeq == LICH_ATK_RISING_SINGLE
7010 || my->monsterLichIceCastSeq == LICH_ATK_HORIZONTAL_SINGLE
7011 || my->monsterLichIceCastSeq == LICH_ATK_SUMMON )
7012 {
7013 int castLimit = 6;
7014 if ( my->monsterLichIceCastSeq == LICH_ATK_SUMMON )
7015 {
7016 castLimit = 2 + rand() % 2;
7017 }
7018 if ( my->monsterLichMagicCastCount < castLimit )
7019 {
7020 if ( my->monsterLichMagicCastCount == 0 )
7021 {
7022 my->attack(my->getAttackPose(), 0, nullptr);
7023 }
7024 else
7025 {
7026 if ( my->monsterLichIceCastSeq == LICH_ATK_SUMMON )
7027 {
7028 my->lichIceSummonMonster(AUTOMATON);
7029 }
7030 else
7031 {
7032 Entity* spell = castSpell(my->getUID(), getSpellFromID(SPELL_MAGICMISSILE), true, false);
7033 real_t horizontalSpeed = 4.0;
7034 Entity* target = uidToEntity(my->monsterTarget);
7035 if ( target )
7036 {
7037 real_t spellDistance = sqrt(pow(spell->x - target->x, 2) + pow(spell->y - target->y, 2));
7038 spell->vel_z = 22.0 / (spellDistance / horizontalSpeed);
7039 if ( rand() % 3 >= 1 )
7040 {
7041 // spells will track the velocity of the target.
7042 real_t ticksToHit = (spellDistance / horizontalSpeed);
7043 real_t predictx = target->x + (target->vel_x * ticksToHit);
7044 real_t predicty = target->y + (target->vel_y * ticksToHit);
7045 tangent = atan2(predicty - spell->y, predictx - spell->x); // assume target will be here when spell lands.
7046 //messagePlayer(0, "x: %f->%f, y: %f->%f, angle offset: %f", target->x, predictx, target->y, predicty, spell->yaw - tangent);
7047 spell->yaw = tangent;
7048 }
7049 else
7050 {
7051 // do some minor variations in spell angle
7052 spell->yaw += ((PI * (-4 + rand() % 9)) / 100);
7053 }
7054 }
7055 else
7056 {
7057 spell->vel_z = 1.6;
7058 // do some minor variations in spell angle
7059 spell->yaw += ((PI * (-4 + rand() % 9)) / 100);
7060 }
7061 spell->vel_x = horizontalSpeed * cos(spell->yaw);
7062 spell->vel_y = horizontalSpeed * sin(spell->yaw);
7063 spell->actmagicIsVertical = MAGIC_ISVERTICAL_XYZ;
7064 spell->z = -22.0;
7065 spell->pitch = atan2(spell->vel_z, horizontalSpeed);
7066 }
7067 }
7068 ++my->monsterLichMagicCastCount;
7069 }
7070 else
7071 {
7072 if ( my->monsterLichIceCastSeq == LICH_ATK_SUMMON )
7073 {
7074 my->monsterHitTime = 45;
7075 }
7076 else
7077 {
7078 my->monsterHitTime = 0;
7079 }
7080 my->monsterLichIceCastSeq = 0;
7081 }
7082 }
7083 }
7084 }
7085 }
7086 else if ( my->monsterState == MONSTER_STATE_LICHFIRE_TELEPORT_STATIONARY
7087 || my->monsterState == MONSTER_STATE_LICHICE_TELEPORT_STATIONARY )
7088 {
7089 MONSTER_VELX = 0;
7090 MONSTER_VELY = 0;
7091 }
7092 //End state machine.
7093
7094 }
7095 else
7096 {
7097 if ( myStats->EFFECTS[EFF_KNOCKBACK] )
7098 {
7099 my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir, weightratio);
7100 if ( abs(MONSTER_VELX) > 0.01 || abs(MONSTER_VELY) > 0.01 )
7101 {
7102 dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
7103 my->handleKnockbackDamage(*myStats, hit.entity);
7104 }
7105 }
7106 else
7107 {
7108 MONSTER_VELX = 0;
7109 MONSTER_VELY = 0;
7110 }
7111 }
7112
7113 if ( previousMonsterState != my->monsterState )
7114 {
7115 serverUpdateEntitySkill(my, 0);
7116 if ( my->monsterAllyIndex > 0 && my->monsterAllyIndex < MAXPLAYERS )
7117 {
7118 serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
7119 }
7120 }
7121
7122 // move body parts
7123 myStats = my->getStats();
7124 if ( myStats != NULL )
7125 {
7126 if ( myStats->type == HUMAN )
7127 {
7128 humanMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7129 }
7130 else if ( myStats->type == RAT )
7131 {
7132 ratAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7133 }
7134 else if ( myStats->type == GOBLIN )
7135 {
7136 goblinMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7137 }
7138 else if ( myStats->type == SLIME )
7139 {
7140 slimeAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7141 }
7142 else if ( myStats->type == SCORPION )
7143 {
7144 scorpionAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7145 }
7146 else if ( myStats->type == SUCCUBUS )
7147 {
7148 succubusMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7149 }
7150 else if ( myStats->type == TROLL )
7151 {
7152 trollMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7153 }
7154 else if ( myStats->type == SHOPKEEPER )
7155 {
7156 shopkeeperMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7157 }
7158 else if ( myStats->type == SKELETON )
7159 {
7160 skeletonMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7161 }
7162 else if ( myStats->type == MINOTAUR )
7163 {
7164 minotaurMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7165 actMinotaurCeilingBuster(my);
7166 }
7167 else if ( myStats->type == GHOUL )
7168 {
7169 ghoulMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7170 }
7171 else if ( myStats->type == DEMON )
7172 {
7173 demonMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7174 actDemonCeilingBuster(my);
7175 }
7176 else if ( myStats->type == SPIDER )
7177 {
7178 spiderMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7179 }
7180 else if ( myStats->type == LICH )
7181 {
7182 lichAnimate(my, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7183 }
7184 else if ( myStats->type == CREATURE_IMP )
7185 {
7186 impMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7187 }
7188 else if ( myStats->type == GNOME )
7189 {
7190 gnomeMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7191 }
7192 else if ( myStats->type == DEVIL )
7193 {
7194 devilMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7195 }
7196 else if ( myStats->type == COCKATRICE )
7197 {
7198 cockatriceMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7199 }
7200 else if ( myStats->type == AUTOMATON )
7201 {
7202 automatonMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7203 }
7204 else if ( myStats->type == CRYSTALGOLEM )
7205 {
7206 crystalgolemMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7207 }
7208 else if ( myStats->type == SCARAB )
7209 {
7210 scarabAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7211 }
7212 else if ( myStats->type == KOBOLD )
7213 {
7214 koboldMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7215 }
7216 else if ( myStats->type == SHADOW )
7217 {
7218 shadowMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7219 }
7220 else if ( myStats->type == GOATMAN )
7221 {
7222 goatmanMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7223 }
7224 else if ( myStats->type == INSECTOID )
7225 {
7226 insectoidMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7227 }
7228 else if ( myStats->type == INCUBUS )
7229 {
7230 incubusMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7231 }
7232 else if ( myStats->type == VAMPIRE )
7233 {
7234 vampireMoveBodyparts(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7235 }
7236 else if ( myStats->type == LICH_FIRE )
7237 {
7238 lichFireAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7239 }
7240 else if ( myStats->type == LICH_ICE )
7241 {
7242 lichIceAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7243 }
7244 else if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
7245 {
7246 sentryBotAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7247 }
7248 else if ( myStats->type == GYROBOT )
7249 {
7250 gyroBotAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7251 }
7252 else if ( myStats->type == DUMMYBOT )
7253 {
7254 dummyBotAnimate(my, myStats, sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY));
7255 }
7256 }
7257 }
7258
handleMonsterAttack(Stat * myStats,Entity * target,double dist)7259 void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
7260 {
7261 node_t* node = nullptr;
7262 Entity* entity = nullptr;
7263 Stat* hitstats = nullptr;
7264 int charge = 1;
7265
7266 //TODO: I don't like this function getting called every frame. Find a better place to put it.
7267 chooseWeapon(target, dist);
7268 bool hasrangedweapon = this->hasRangedWeapon();
7269 bool lichRangeCheckOverride = false;
7270 if ( myStats->type == LICH_FIRE)
7271 {
7272 if ( monsterLichFireMeleeSeq == LICH_ATK_BASICSPELL_SINGLE )
7273 {
7274 hasrangedweapon = true;
7275 }
7276 if ( monsterState == MONSTER_STATE_LICH_CASTSPELLS )
7277 {
7278 lichRangeCheckOverride = true;
7279 }
7280 }
7281 else if ( myStats->type == LICH_ICE )
7282 {
7283 // lichice todo
7284 if ( monsterLichIceCastSeq == LICH_ATK_BASICSPELL_SINGLE )
7285 {
7286 hasrangedweapon = true;
7287 }
7288 if ( monsterStrafeDirection == 0 && rand() % 10 == 0 && ticks % 10 == 0 )
7289 {
7290 monsterStrafeDirection = -1 + ((rand() % 2 == 0) ? 2 : 0);
7291 }
7292 }
7293 else if ( myStats->type == DUMMYBOT )
7294 {
7295 return;
7296 }
7297 else
7298 {
7299 if ( myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] > 0 )
7300 {
7301 if ( monsterSpecialTimer == 0 && monsterSpecialState == 0 && (this->monsterHitTime >= HITRATE / 2) )
7302 {
7303 if ( rand() % 50 == 0 )
7304 {
7305 node_t* node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
7306 if ( node != nullptr )
7307 {
7308 bool swapped = swapMonsterWeaponWithInventoryItem(this, myStats, node, true, true);
7309 if ( swapped )
7310 {
7311 monsterSpecialState = MONSTER_SPELLCAST_GENERIC;
7312 int timer = (myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] >> 4) & 0xFFFF;
7313 monsterSpecialTimer = timer > 0 ? timer : 250;
7314 hasrangedweapon = true;
7315 }
7316 }
7317 }
7318 }
7319 }
7320 }
7321
7322 // check the range to the target, depending on ranged weapon or melee.
7323 if ( (dist < STRIKERANGE && !hasrangedweapon) || (hasrangedweapon && dist < getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon)) || lichRangeCheckOverride )
7324 {
7325 // increment the hit time, don't attack until this reaches the hitrate of the weapon
7326 this->monsterHitTime++;
7327 real_t bow = 1;
7328 if ( hasrangedweapon && myStats->weapon )
7329 {
7330 if ( (myStats->weapon->type == SLING
7331 || myStats->weapon->type == SHORTBOW
7332 || myStats->weapon->type == ARTIFACT_BOW
7333 || myStats->weapon->type == LONGBOW
7334 || myStats->weapon->type == COMPOUND_BOW) )
7335 {
7336 bow = 2;
7337 if ( myStats->weapon->type == COMPOUND_BOW )
7338 {
7339 bow = 1.5;
7340 }
7341 if ( myStats->shield && itemTypeIsQuiver(myStats->shield->type) )
7342 {
7343 if ( myStats->shield->type == QUIVER_LIGHTWEIGHT )
7344 {
7345 bow -= 0.5;
7346 }
7347 }
7348 }
7349 else if ( myStats->weapon->type == CROSSBOW )
7350 {
7351 if ( myStats->shield && itemTypeIsQuiver(myStats->shield->type) )
7352 {
7353 if ( myStats->shield->type == QUIVER_LIGHTWEIGHT )
7354 {
7355 bow = 0.8;
7356 }
7357 }
7358 }
7359 }
7360 if ( monsterIsImmobileTurret(this, myStats) )
7361 {
7362 bow = 2;
7363 if ( myStats->type == SPELLBOT )
7364 {
7365 if ( myStats->LVL >= 15 )
7366 {
7367 bow = 1.2;
7368 }
7369 else if ( myStats->LVL >= 10 )
7370 {
7371 bow = 1.5;
7372 }
7373 else if ( myStats->LVL >= 5 )
7374 {
7375 bow = 1.8;
7376 }
7377 else
7378 {
7379 bow = 2;
7380 }
7381 }
7382 }
7383 // check if ready to attack
7384 if ( (this->monsterHitTime >= static_cast<int>(HITRATE * monsterGlobalAttackTimeMultiplier * bow)
7385 && (myStats->type != LICH && myStats->type != LICH_ICE))
7386 || (this->monsterHitTime >= 5 && myStats->type == LICH)
7387 || (this->monsterHitTime >= HITRATE * 2 && myStats->type == LICH_ICE)
7388 )
7389 {
7390 bool shouldAttack = this->handleMonsterSpecialAttack(myStats, nullptr, dist);
7391 if ( !shouldAttack )
7392 {
7393 // handleMonsterSpecialAttack processed an action where the monster should not try to attack this frame.
7394 // e.g unequipping/swapping from special weapon, stops punching the air after casting a spell.
7395 return;
7396 }
7397
7398 if ( myStats->type == LICH )
7399 {
7400 this->monsterSpecialTimer++;
7401 if ( this->monsterSpecialTimer >= 5 )
7402 {
7403 this->monsterSpecialTimer = 90;
7404 this->monsterTarget = 0;
7405 this->monsterTargetX = this->x - 50 + rand() % 100;
7406 this->monsterTargetY = this->y - 50 + rand() % 100;
7407 this->monsterState = MONSTER_STATE_PATH; // path state
7408 }
7409 }
7410
7411 // reset the hit timer
7412 this->monsterHitTime = 0;
7413 int tracedist = 0;
7414 if ( lichRangeCheckOverride )
7415 {
7416 tracedist = 1024;
7417 }
7418 else if ( hasrangedweapon )
7419 {
7420 tracedist = 160;
7421 if ( myStats->weapon )
7422 {
7423 tracedist = getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon);
7424 }
7425 }
7426 else
7427 {
7428 tracedist = STRIKERANGE;
7429 }
7430
7431 // check again for the target in attack range. return the result into hit.entity.
7432 double newTangent = atan2(target->y - this->y, target->x - this->x);
7433 if ( lichRangeCheckOverride )
7434 {
7435 hit.entity = uidToEntity(monsterTarget);
7436 }
7437 else
7438 {
7439 lineTrace(this, this->x, this->y, newTangent, tracedist, 0, false);
7440 }
7441 if ( hit.entity != nullptr )
7442 {
7443 // found the target in range
7444 hitstats = hit.entity->getStats();
7445 if ( hit.entity->behavior == &actMonster && !hasrangedweapon )
7446 {
7447 // alert the monster!
7448 if ( hit.entity->skill[0] != MONSTER_STATE_ATTACK )
7449 {
7450 hit.entity->monsterAcquireAttackTarget(*this, MONSTER_STATE_PATH);
7451 }
7452 }
7453 if ( hitstats != nullptr )
7454 {
7455 // prepare attack, set the animation of the attack based on the current weapon.
7456 int pose = this->getAttackPose();
7457
7458 int oldDefend = monsterDefend;
7459 monsterDefend = shouldMonsterDefend(*myStats, *hit.entity, *hitstats, dist, hasrangedweapon);
7460 if ( oldDefend != monsterDefend )
7461 {
7462 serverUpdateEntitySkill(this, 47);
7463 }
7464
7465 // turn to the target, then reset my yaw.
7466 double oYaw = this->yaw;
7467 this->yaw = newTangent;
7468 if ( myStats->type == LICH_FIRE )
7469 {
7470 if ( monsterState != MONSTER_STATE_LICH_CASTSPELLS )
7471 {
7472 lichFireSetNextAttack(*myStats);
7473 //messagePlayer(0, "previous %d, next is %d", monsterLichFireMeleePrev, monsterLichFireMeleeSeq);
7474 }
7475 }
7476 else if ( myStats->type == LICH_ICE )
7477 {
7478 lichIceSetNextAttack(*myStats);
7479 //messagePlayer(0, "previous %d, next is %d", monsterLichIceCastPrev, monsterLichIceCastSeq);
7480 if ( monsterLichIceCastPrev == LICH_ATK_BASICSPELL_SINGLE )
7481 {
7482 monsterHitTime = HITRATE;
7483 }
7484 if ( monsterSpecialState == LICH_ICE_ATTACK_COMBO )
7485 {
7486 monsterHitTime = HITRATE * 2 - 25;
7487 if ( monsterLichMeleeSwingCount > 1 )
7488 {
7489 monsterSpecialState = 0;
7490 monsterSpecialTimer = 100;
7491 }
7492 }
7493 }
7494
7495 if ( monsterDefend == MONSTER_DEFEND_HOLD )
7496 {
7497 // skip attack, continue defending. offset the hit time to allow for timing variation.
7498 monsterHitTime = HITRATE / 4;
7499 }
7500 else
7501 {
7502 this->attack(pose, charge, nullptr); // attacku! D:<
7503 }
7504 this->yaw = oYaw;
7505 }
7506 }
7507 }
7508 }
7509 else
7510 {
7511 if ( ticks % (90 + getUID() % 10) == 0 )
7512 {
7513 if ( !hasrangedweapon && dist > TOUCHRANGE && target && target->hasRangedWeapon() )
7514 {
7515 int oldDefend = monsterDefend;
7516 monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon);
7517 if ( oldDefend != monsterDefend )
7518 {
7519 serverUpdateEntitySkill(this, 47);
7520 }
7521 }
7522 }
7523 }
7524
7525 return;
7526 }
7527
limbAnimateWithOvershoot(Entity * limb,int axis,double setpointRate,double setpoint,double endpointRate,double endpoint,int dir)7528 int limbAnimateWithOvershoot(Entity* limb, int axis, double setpointRate, double setpoint, double endpointRate, double endpoint, int dir)
7529 {
7530 double speedMultiplier = 1.0;
7531
7532 if ( monsterGlobalAnimationMultiplier != 10 )
7533 {
7534 speedMultiplier = monsterGlobalAnimationMultiplier / 10.0;
7535 setpointRate = setpointRate * speedMultiplier;
7536 endpointRate = endpointRate * speedMultiplier;
7537 }
7538
7539 if ( axis == 0 || limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_NONE || dir == ANIMATE_DIR_NONE )
7540 {
7541 if ( axis == ANIMATE_PITCH )
7542 {
7543 limb->pitch = endpoint;
7544 }
7545 else if ( axis == ANIMATE_ROLL )
7546 {
7547 limb->roll = endpoint;
7548 }
7549 else if ( axis == ANIMATE_YAW )
7550 {
7551 limb->yaw = endpoint;
7552 }
7553 // no animation required.
7554 return -1;
7555 }
7556
7557 if ( axis == ANIMATE_PITCH )
7558 {
7559 if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_SETPOINT )
7560 {
7561 limb->pitch += setpointRate * dir;
7562 while ( limb->pitch < 0 )
7563 {
7564 limb->pitch += 2 * PI;
7565 }
7566 while ( limb->pitch >= 2 * PI )
7567 {
7568 limb->pitch -= 2 * PI;
7569 }
7570
7571 if ( limbAngleWithinRange(limb->pitch, setpointRate, setpoint) )
7572 {
7573 limb->pitch = setpoint;
7574 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_ENDPOINT;
7575 return ANIMATE_OVERSHOOT_TO_SETPOINT; //reached setpoint
7576 }
7577 }
7578 else if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_ENDPOINT )
7579 {
7580 limb->pitch -= endpointRate * dir;
7581 while ( limb->pitch < 0 )
7582 {
7583 limb->pitch += 2 * PI;
7584 }
7585 while ( limb->pitch >= 2 * PI )
7586 {
7587 limb->pitch -= 2 * PI;
7588 }
7589
7590 if ( limbAngleWithinRange(limb->pitch, endpointRate, endpoint) )
7591 {
7592 limb->pitch = endpoint;
7593 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_NONE;
7594 return ANIMATE_OVERSHOOT_TO_ENDPOINT; //reached endpoint.
7595 }
7596 }
7597 }
7598 else if ( axis == ANIMATE_ROLL )
7599 {
7600 if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_SETPOINT )
7601 {
7602 limb->roll += setpointRate * dir;
7603 while ( limb->roll < 0 )
7604 {
7605 limb->roll += 2 * PI;
7606 }
7607 while ( limb->roll >= 2 * PI )
7608 {
7609 limb->roll -= 2 * PI;
7610 }
7611
7612 if ( limbAngleWithinRange(limb->roll, setpointRate, setpoint) )
7613 {
7614 limb->roll = setpoint;
7615 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_ENDPOINT;
7616 return ANIMATE_OVERSHOOT_TO_SETPOINT; //reached setpoint
7617 }
7618 }
7619 else if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_ENDPOINT )
7620 {
7621 limb->roll -= endpointRate * dir;
7622 while ( limb->roll < 0 )
7623 {
7624 limb->roll += 2 * PI;
7625 }
7626 while ( limb->roll >= 2 * PI )
7627 {
7628 limb->roll -= 2 * PI;
7629 }
7630
7631 if ( limbAngleWithinRange(limb->roll, endpointRate, endpoint) )
7632 {
7633 limb->roll = endpoint;
7634 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_NONE;
7635 return ANIMATE_OVERSHOOT_TO_ENDPOINT; //reached endpoint.
7636 }
7637 }
7638 }
7639 else if ( axis == ANIMATE_YAW )
7640 {
7641 if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_SETPOINT )
7642 {
7643 limb->yaw += setpointRate * dir;
7644 while ( limb->yaw < 0 )
7645 {
7646 limb->yaw += 2 * PI;
7647 }
7648 while ( limb->yaw >= 2 * PI )
7649 {
7650 limb->yaw -= 2 * PI;
7651 }
7652
7653 if ( limbAngleWithinRange(limb->yaw, setpointRate, setpoint) )
7654 {
7655 limb->yaw = setpoint;
7656 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_ENDPOINT;
7657 return ANIMATE_OVERSHOOT_TO_SETPOINT; //reached setpoint
7658 }
7659 }
7660 else if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_ENDPOINT )
7661 {
7662 limb->yaw -= endpointRate * dir;
7663 while ( limb->yaw < 0 )
7664 {
7665 limb->yaw += 2 * PI;
7666 }
7667 while ( limb->yaw >= 2 * PI )
7668 {
7669 limb->yaw -= 2 * PI;
7670 }
7671
7672 if ( limbAngleWithinRange(limb->yaw, endpointRate, endpoint) )
7673 {
7674 limb->yaw = endpoint;
7675 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_NONE;
7676 return ANIMATE_OVERSHOOT_TO_ENDPOINT; //reached endpoint.
7677 }
7678 }
7679 }
7680 else if ( axis == ANIMATE_Z )
7681 {
7682 if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_SETPOINT )
7683 {
7684 limb->z += setpointRate * dir;
7685
7686 if ( limbAngleWithinRange(limb->z, setpointRate, setpoint) )
7687 {
7688 limb->z = setpoint;
7689 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_ENDPOINT;
7690 return ANIMATE_OVERSHOOT_TO_SETPOINT; //reached setpoint
7691 }
7692 }
7693 else if ( limb->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_TO_ENDPOINT )
7694 {
7695 limb->z -= endpointRate * dir;
7696
7697 if ( limbAngleWithinRange(limb->z, endpointRate, endpoint) )
7698 {
7699 limb->z = endpoint;
7700 limb->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_NONE;
7701 return ANIMATE_OVERSHOOT_TO_ENDPOINT; //reached endpoint.
7702 }
7703 }
7704 }
7705
7706 return -1;
7707 }
7708
limbAnimateToLimit(Entity * limb,int axis,double rate,double setpoint,bool shake,double shakerate)7709 int limbAnimateToLimit(Entity* limb, int axis, double rate, double setpoint, bool shake, double shakerate)
7710 {
7711 if ( axis == 0 )
7712 {
7713 return 0;
7714 }
7715
7716 double speedMultiplier = 1.0;
7717
7718 if ( monsterGlobalAnimationMultiplier != 10 )
7719 {
7720 speedMultiplier = monsterGlobalAnimationMultiplier / 10.0;
7721 rate = rate * speedMultiplier;
7722 shakerate = shakerate * speedMultiplier;
7723 }
7724
7725 if ( axis == ANIMATE_YAW )
7726 {
7727 while ( limb->yaw < 0 )
7728 {
7729 limb->yaw += 2 * PI;
7730 }
7731 while ( limb->yaw >= 2 * PI )
7732 {
7733 limb->yaw -= 2 * PI;
7734 }
7735
7736 if ( limbAngleWithinRange(limb->yaw, rate, setpoint) )
7737 {
7738 limb->yaw = setpoint;
7739 if ( shake )
7740 {
7741 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NONE )
7742 {
7743 // no direction for shake is set.
7744 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7745 }
7746 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_POSITIVE )
7747 {
7748 limb->yaw += shakerate;
7749 limb->monsterAnimationLimbDirection = ANIMATE_DIR_NEGATIVE;
7750 }
7751 else if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NEGATIVE )
7752 {
7753 limb->yaw -= shakerate;
7754 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7755 }
7756 }
7757 return 1; //reached setpoint
7758 }
7759 limb->yaw += rate;
7760 }
7761 else if ( axis == ANIMATE_PITCH )
7762 {
7763 while ( limb->pitch < 0 )
7764 {
7765 limb->pitch += 2 * PI;
7766 }
7767 while ( limb->pitch >= 2 * PI )
7768 {
7769 limb->pitch -= 2 * PI;
7770 }
7771
7772 if ( limbAngleWithinRange(limb->pitch, rate, setpoint) )
7773 {
7774 limb->pitch = setpoint;
7775 if ( shake )
7776 {
7777 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NONE )
7778 {
7779 // no direction for shake is set.
7780 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7781 }
7782 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_POSITIVE )
7783 {
7784 limb->pitch += shakerate;
7785 limb->monsterAnimationLimbDirection = ANIMATE_DIR_NEGATIVE;
7786 }
7787 else if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NEGATIVE )
7788 {
7789 limb->pitch -= shakerate;
7790 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7791 }
7792 }
7793 return 1; //reached setpoint
7794 }
7795 limb->pitch += rate;
7796 }
7797 else if ( axis == ANIMATE_ROLL )
7798 {
7799 while ( limb->roll < 0 )
7800 {
7801 limb->roll += 2 * PI;
7802 }
7803 while ( limb->roll >= 2 * PI )
7804 {
7805 limb->roll -= 2 * PI;
7806 }
7807
7808 if ( limbAngleWithinRange(limb->roll, rate, setpoint) )
7809 {
7810 limb->roll = setpoint;
7811 if ( shake )
7812 {
7813 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NONE )
7814 {
7815 // no direction for shake is set.
7816 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7817 }
7818 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_POSITIVE )
7819 {
7820 limb->roll += shakerate;
7821 limb->monsterAnimationLimbDirection = ANIMATE_DIR_NEGATIVE;
7822 }
7823 else if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NEGATIVE )
7824 {
7825 limb->roll -= shakerate;
7826 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7827 }
7828 }
7829 return 1; //reached setpoint
7830 }
7831 limb->roll += rate;
7832 }
7833 else if ( axis == ANIMATE_Z )
7834 {
7835 if ( limbAngleWithinRange(limb->z, rate, setpoint) )
7836 {
7837 limb->z = setpoint;
7838 if ( shake )
7839 {
7840 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NONE )
7841 {
7842 // no direction for shake is set.
7843 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7844 }
7845 if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_POSITIVE )
7846 {
7847 limb->z += shakerate;
7848 limb->monsterAnimationLimbDirection = ANIMATE_DIR_NEGATIVE;
7849 }
7850 else if ( limb->monsterAnimationLimbDirection == ANIMATE_DIR_NEGATIVE )
7851 {
7852 limb->z -= shakerate;
7853 limb->monsterAnimationLimbDirection = ANIMATE_DIR_POSITIVE;
7854 }
7855 }
7856 return 1; //reached setpoint
7857 }
7858 limb->z += rate;
7859 }
7860 else if ( axis == ANIMATE_WEAPON_YAW )
7861 {
7862 while ( limb->fskill[5] < 0 )
7863 {
7864 limb->fskill[5] += 2 * PI;
7865 }
7866 while ( limb->fskill[5] >= 2 * PI )
7867 {
7868 limb->fskill[5] -= 2 * PI;
7869 }
7870
7871 if ( limbAngleWithinRange(limb->fskill[5], rate, setpoint) )
7872 {
7873 limb->fskill[5] = setpoint;
7874 return 1; //reached setpoint
7875 }
7876 limb->fskill[5] += rate;
7877 }
7878
7879 return 0;
7880 }
7881
limbAngleWithinRange(real_t angle,double rate,double setpoint)7882 int limbAngleWithinRange(real_t angle, double rate, double setpoint)
7883 {
7884 if ( rate > 0 )
7885 {
7886 if ( (angle <= (setpoint + rate)) && (angle >= (setpoint - rate)) )
7887 {
7888 return 1;
7889 }
7890 }
7891 else if ( rate < 0 )
7892 {
7893 if ( (angle >= (setpoint + rate)) && (angle <= (setpoint - rate)) )
7894 {
7895 return 1;
7896 }
7897 }
7898
7899 return 0;
7900 }
7901
normaliseAngle2PI(real_t angle)7902 real_t normaliseAngle2PI(real_t angle)
7903 {
7904 while ( angle >= 2 * PI )
7905 {
7906 angle -= 2 * PI;
7907 }
7908 while ( angle < 0 )
7909 {
7910 angle += 2 * PI;
7911 }
7912
7913 return angle;
7914 }
7915
forceFollower(Entity & leader,Entity & follower)7916 bool forceFollower(Entity& leader, Entity& follower)
7917 {
7918 Stat* leaderStats = leader.getStats();
7919 Stat* followerStats = follower.getStats();
7920 if ( !leaderStats || !followerStats )
7921 {
7922 printlog("[forceFollower] Error: Either leader or follower did not have stats.");
7923 return false;
7924 }
7925
7926 Uint32* myuid = (Uint32*) (malloc(sizeof(Uint32)));
7927 *myuid = follower.getUID();
7928
7929 //Deal with the old leader.
7930 if ( followerStats->leader_uid != 0 )
7931 {
7932 Entity* oldLeader = uidToEntity(followerStats->leader_uid);
7933 if ( oldLeader )
7934 {
7935 Stat* oldLeaderStats = oldLeader->getStats();
7936 if ( oldLeaderStats )
7937 {
7938 if ( leader.behavior == &actPlayer
7939 && oldLeader == &leader )
7940 {
7941 steamAchievementClient(leader.skill[2], "BARONY_ACH_CONFESSOR");
7942 }
7943 list_RemoveNodeWithElement<Uint32>(oldLeaderStats->FOLLOWERS, *myuid);
7944 if ( oldLeader->behavior == &actPlayer )
7945 {
7946 serverRemoveClientFollower(oldLeader->skill[2], *myuid);
7947 }
7948 }
7949 }
7950 }
7951
7952 node_t* newNode = list_AddNodeLast(&leaderStats->FOLLOWERS);
7953 newNode->deconstructor = &defaultDeconstructor;
7954 newNode->element = myuid;
7955
7956 follower.monsterState = 0;
7957 follower.monsterTarget = 0;
7958 followerStats->leader_uid = leader.getUID();
7959
7960 for ( node_t* node = leaderStats->FOLLOWERS.first; node != nullptr; node = node->next )
7961 {
7962 Uint32* c = (Uint32*)node->element;
7963 Entity* entity = nullptr;
7964 if ( c )
7965 {
7966 entity = uidToEntity(*c);
7967 }
7968 if ( entity && entity->monsterTarget == *myuid )
7969 {
7970 entity->monsterReleaseAttackTarget(); // followers stop punching the new target.
7971 }
7972 }
7973
7974 int player = leader.isEntityPlayer();
7975 if ( player > 0 && multiplayer == SERVER )
7976 {
7977 //Tell the client he suckered somebody into his cult.
7978 strcpy((char*) (net_packet->data), "LEAD");
7979 SDLNet_Write32((Uint32 )follower.getUID(), &net_packet->data[4]);
7980 strcpy((char*)(&net_packet->data[8]), followerStats->name);
7981 net_packet->data[8 + strlen(followerStats->name)] = 0;
7982 net_packet->address.host = net_clients[player - 1].host;
7983 net_packet->address.port = net_clients[player - 1].port;
7984 net_packet->len = 8 + strlen(followerStats->name) + 1;
7985 sendPacketSafe(net_sock, -1, net_packet, player - 1);
7986
7987 serverUpdateAllyStat(player, follower.getUID(), followerStats->LVL, followerStats->HP, followerStats->MAXHP, followerStats->type);
7988 }
7989
7990 if ( !FollowerMenu.recentEntity && player == clientnum )
7991 {
7992 FollowerMenu.recentEntity = &follower;
7993 }
7994
7995 if ( player >= 0 )
7996 {
7997 if ( leaderStats->type != HUMAN && followerStats->type == HUMAN )
7998 {
7999 steamAchievementClient(player, "BARONY_ACH_PITY_FRIEND");
8000 }
8001 else if ( leaderStats->type == VAMPIRE && followerStats->type == VAMPIRE )
8002 {
8003 if ( !strncmp(followerStats->name, "young vampire", strlen("young vampire")) )
8004 {
8005 steamAchievementClient(player, "BARONY_ACH_YOUNG_BLOOD");
8006 }
8007 }
8008 }
8009
8010 return true;
8011 }
8012
handleMonsterSpecialAttack(Stat * myStats,Entity * target,double dist)8013 bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double dist)
8014 {
8015 int specialRoll = 0;
8016 node_t* node = nullptr;
8017 int enemiesNearby = 0;
8018 int bonusFromHP = 0;
8019 bool hasrangedweapon = this->hasRangedWeapon();
8020
8021 if ( myStats != nullptr )
8022 {
8023 if ( myStats->type == LICH
8024 || myStats->type == DEVIL
8025 || myStats->type == SHOPKEEPER
8026 || myStats->type == LICH_FIRE
8027 || myStats->type == LICH_ICE )
8028 {
8029 // monster should attack after this function is called.
8030 return true;
8031 }
8032
8033 if ( this->monsterSpecialTimer == 0 )
8034 {
8035 if ( myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] > 0
8036 && (monsterSpecialState == MONSTER_SPELLCAST_GENERIC || monsterSpecialState == MONSTER_SPELLCAST_GENERIC2) )
8037 {
8038 monsterSpecialState = 0;
8039 if ( myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK )
8040 {
8041 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8042 if ( node != nullptr )
8043 {
8044 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8045 return true;
8046 }
8047 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), MAGICSTAFF); // find weapon to re-equip
8048 if ( node != nullptr )
8049 {
8050 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8051 return true;
8052 }
8053 else
8054 {
8055 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8056 }
8057 }
8058 return true;
8059 }
8060
8061 switch ( myStats->type )
8062 {
8063 case KOBOLD:
8064 if ( hasrangedweapon || myStats->weapon == nullptr )
8065 {
8066 specialRoll = rand() % 20;
8067 //messagePlayer(0, "Rolled: %d", specialRoll);
8068 if ( myStats->HP < myStats->MAXHP / 2 )
8069 {
8070 if ( (dist < 40 && specialRoll < 10) || (dist < 100 && specialRoll < 5) ) // 50%/25% chance
8071 {
8072 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
8073 if ( node != nullptr )
8074 {
8075 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8076 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_KOBOLD;
8077 }
8078 }
8079 }
8080 else if ( myStats->HP < (0.8 * myStats->MAXHP) )
8081 {
8082 if ( (dist < 40 && specialRoll < 5) || (dist < 100 && specialRoll < 2) ) // 25%/10% chance
8083 {
8084 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
8085 if ( node != nullptr )
8086 {
8087 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8088 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_KOBOLD;
8089 }
8090 }
8091 }
8092 }
8093 break;
8094 case SUCCUBUS:
8095 if ( monsterSpecialState == SUCCUBUS_CHARM )
8096 {
8097 // special handled in succubusChooseWeapon()
8098 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SUCCUBUS_CHARM;
8099 break;
8100 }
8101 break;
8102 case CRYSTALGOLEM:
8103 specialRoll = rand() % 20;
8104 enemiesNearby = numTargetsAroundEntity(this, STRIKERANGE, PI, MONSTER_TARGET_ENEMY);
8105 if ( enemiesNearby > 1 )
8106 {
8107 enemiesNearby = std::min(enemiesNearby, 4);
8108 if ( specialRoll < enemiesNearby * 2 ) // 10% for each enemy > 1, capped at 40%
8109 {
8110 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8111 break;
8112 }
8113 }
8114
8115 specialRoll = rand() % 20;
8116 if ( myStats->HP > myStats->MAXHP * 0.8 )
8117 {
8118 if ( specialRoll < 2 ) // 10%
8119 {
8120 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8121 }
8122 }
8123 else if ( myStats->HP > myStats->MAXHP * 0.6 )
8124 {
8125 if ( specialRoll < 3 ) // 15%
8126 {
8127 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8128 }
8129 }
8130 else if ( myStats->HP > myStats->MAXHP * 0.4 )
8131 {
8132 if ( specialRoll < 4 ) // 20%
8133 {
8134 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8135 }
8136 }
8137 else if ( myStats->HP > myStats->MAXHP * 0.2 )
8138 {
8139 if ( specialRoll < 5 ) // 25%
8140 {
8141 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8142 }
8143 }
8144 else if ( myStats->HP > myStats->MAXHP * 0.2 )
8145 {
8146 if ( specialRoll < 5 ) // 25%
8147 {
8148 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOLEM;
8149 }
8150 }
8151 break;
8152 case COCKATRICE:
8153 specialRoll = rand() % 20;
8154 //specialRoll = 0;
8155 // check for paralyze first
8156 enemiesNearby = std::min(numTargetsAroundEntity(this, STRIKERANGE * 2, PI, MONSTER_TARGET_ENEMY), 4);
8157
8158 if ( myStats->HP <= myStats->MAXHP * 0.5 )
8159 {
8160 bonusFromHP = 4; // +20% chance if on low health
8161 }
8162 if ( specialRoll < (enemiesNearby * 2 + bonusFromHP) ) // +10% for each enemy, capped at 40%
8163 {
8164 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
8165 if ( node != nullptr )
8166 {
8167 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8168 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_STONE;
8169 }
8170 break;
8171 }
8172
8173 // nothing selected, look for double attack.
8174 specialRoll = rand() % 20;
8175 if ( myStats->HP > myStats->MAXHP * 0.8 )
8176 {
8177 if ( specialRoll < 2 ) // 10%
8178 {
8179 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK;
8180 }
8181 }
8182 else if ( myStats->HP > myStats->MAXHP * 0.6 )
8183 {
8184 if ( specialRoll < 2 ) // 10%
8185 {
8186 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK;
8187 }
8188 }
8189 else if ( myStats->HP > myStats->MAXHP * 0.4 )
8190 {
8191 if ( specialRoll < 3 ) // 15%
8192 {
8193 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK;
8194 }
8195 }
8196 else if ( myStats->HP > myStats->MAXHP * 0.2 )
8197 {
8198 if ( specialRoll < 4 ) // 20%
8199 {
8200 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK;
8201 }
8202 }
8203 else if ( myStats->HP <= myStats->MAXHP * 0.2 )
8204 {
8205 if ( specialRoll < 5 ) // 25%
8206 {
8207 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_COCKATRICE_ATK;
8208 }
8209 }
8210 break;
8211 case INSECTOID:
8212 if ( monsterSpecialState == INSECTOID_DOUBLETHROW_FIRST || monsterSpecialState == INSECTOID_DOUBLETHROW_SECOND )
8213 {
8214 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INSECTOID_THROW;
8215 break;
8216 }
8217
8218 // spray acid
8219 if ( dist < STRIKERANGE * 2 )
8220 {
8221 specialRoll = rand() % 20;
8222 enemiesNearby = std::min(numTargetsAroundEntity(this, STRIKERANGE * 2, PI, MONSTER_TARGET_ENEMY), 4);
8223 //messagePlayer(0, "insectoid roll %d", specialRoll);
8224 if ( myStats->HP <= myStats->MAXHP * 0.8 )
8225 {
8226 bonusFromHP = 4; // +20% chance if on low health
8227 }
8228 if ( specialRoll < (enemiesNearby * 2 + bonusFromHP) ) // +10% for each enemy, capped at 40%
8229 {
8230 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), SPELLBOOK);
8231 if ( node != nullptr )
8232 {
8233 monsterSpecialState = INSECTOID_ACID;
8234 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8235 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INSECTOID_ACID;
8236 serverUpdateEntitySkill(this, 33); // for clients to handle animation
8237 }
8238 else
8239 {
8240 if ( myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK )
8241 {
8242 monsterSpecialState = INSECTOID_ACID;
8243 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INSECTOID_ACID;
8244 serverUpdateEntitySkill(this, 33); // for clients to handle animation
8245 }
8246 }
8247 break;
8248 }
8249 }
8250 // throwing weapon special handled in insectoidChooseWeapon()
8251 break;
8252 case INCUBUS:
8253 if ( monsterSpecialState == INCUBUS_CONFUSION )
8254 {
8255 // throwing weapon special handled in incubusChooseWeapon()
8256 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INCUBUS_CONFUSION;
8257 break;
8258 }
8259 else if ( monsterSpecialState == INCUBUS_STEAL )
8260 {
8261 // special handled in incubusChooseWeapon()
8262 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INCUBUS_STEAL;
8263 break;
8264 }
8265 else if ( monsterSpecialState == INCUBUS_TELEPORT )
8266 {
8267 // special handled in incubusChooseWeapon()
8268 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INCUBUS_TELEPORT_TARGET;
8269 break;
8270 }
8271 else if ( monsterSpecialState == INCUBUS_CHARM )
8272 {
8273 // special handled in incubusChooseWeapon()
8274 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_INCUBUS_CHARM;
8275 break;
8276 }
8277 break;
8278 case VAMPIRE:
8279 if ( monsterSpecialState == VAMPIRE_CAST_AURA )
8280 {
8281 // special handled in vampireChooseWeapon()
8282 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_VAMPIRE_AURA;
8283 }
8284 else if ( monsterSpecialState == VAMPIRE_CAST_DRAIN )
8285 {
8286 // special handled in vampireChooseWeapon()
8287 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_VAMPIRE_DRAIN;
8288 }
8289 break;
8290 case SHADOW:
8291 if ( monsterSpecialState == SHADOW_SPELLCAST )
8292 {
8293 monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_SPELLCAST;
8294 }
8295 else if ( monsterSpecialState == SHADOW_TELEPORT_ONLY )
8296 {
8297 // special handled in shadowChooseWeapon(), teleport code in path state.
8298 this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_TELEPORT;
8299 break;
8300 }
8301 case GOATMAN:
8302 if ( monsterSpecialState == GOATMAN_POTION )
8303 {
8304 monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOATMAN_DRINK;
8305 }
8306 else if ( monsterSpecialState == GOATMAN_THROW )
8307 {
8308 monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_GOATMAN_THROW;
8309 }
8310 break;
8311 default:
8312 break;
8313 }
8314 }
8315 else if ( this->monsterSpecialTimer > 0 )
8316 {
8317 bool shouldAttack = true;
8318
8319 if ( myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] > 0 )
8320 {
8321 if ( monsterSpecialState == MONSTER_SPELLCAST_GENERIC )
8322 {
8323 monsterSpecialState = MONSTER_SPELLCAST_GENERIC2;
8324 return true;
8325 }
8326 else if ( monsterSpecialState == MONSTER_SPELLCAST_GENERIC2 )
8327 {
8328 monsterSpecialState = 0;
8329 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8330 if ( node != nullptr )
8331 {
8332 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8333 return true;
8334 }
8335 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), MAGICSTAFF); // find weapon to re-equip
8336 if ( node != nullptr )
8337 {
8338 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8339 return true;
8340 }
8341 else
8342 {
8343 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8344 }
8345 return true;
8346 }
8347 }
8348
8349 switch ( myStats->type )
8350 {
8351 case KOBOLD:
8352 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8353 if ( node != nullptr )
8354 {
8355 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8356 }
8357 else
8358 {
8359 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8360 }
8361 break;
8362 case SUCCUBUS:
8363 if ( monsterSpecialState == SUCCUBUS_CHARM )
8364 {
8365 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8366 if ( node != nullptr )
8367 {
8368 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8369 }
8370 else
8371 {
8372 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8373 }
8374 shouldAttack = false;
8375 monsterSpecialState = 0;
8376 }
8377 break;
8378 case INSECTOID:
8379 if ( monsterSpecialState == INSECTOID_ACID )
8380 {
8381 monsterSpecialState = 0;
8382 serverUpdateEntitySkill(this, 33); // for clients to handle animation
8383 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8384 if ( node != nullptr )
8385 {
8386 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8387 }
8388 else
8389 {
8390 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8391 }
8392 shouldAttack = false;
8393 }
8394 else if ( monsterSpecialState == INSECTOID_DOUBLETHROW_SECOND )
8395 {
8396 monsterSpecialState = 0;
8397 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8398 if ( node != nullptr )
8399 {
8400 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8401 }
8402 else
8403 {
8404 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, THROWN);
8405 }
8406 shouldAttack = false;
8407 }
8408 break;
8409 case COCKATRICE:
8410 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8411 break;
8412 case INCUBUS:
8413 if ( monsterSpecialState == INCUBUS_CONFUSION )
8414 {
8415 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8416 if ( node != nullptr )
8417 {
8418 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8419 }
8420 else
8421 {
8422 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, POTION);
8423 }
8424 shouldAttack = false;
8425 monsterSpecialState = 0;
8426 }
8427 else if ( monsterSpecialState == INCUBUS_STEAL )
8428 {
8429 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8430 if ( node != nullptr )
8431 {
8432 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8433 }
8434 else
8435 {
8436 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8437 }
8438 shouldAttack = false;
8439 monsterSpecialState = 0;
8440 }
8441 else if ( monsterSpecialState == INCUBUS_TELEPORT_STEAL )
8442 {
8443 // this flag will be cleared in incubusChooseWeapon
8444 }
8445 else if ( monsterSpecialState == INCUBUS_TELEPORT )
8446 {
8447 // this flag will be cleared in incubusChooseWeapon
8448 }
8449 else if ( monsterSpecialState == INCUBUS_CHARM )
8450 {
8451 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8452 if ( node != nullptr )
8453 {
8454 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8455 }
8456 else
8457 {
8458 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8459 }
8460 shouldAttack = false;
8461 monsterSpecialState = 0;
8462 }
8463 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
8464 break;
8465 case VAMPIRE:
8466 if ( monsterSpecialState == VAMPIRE_CAST_AURA )
8467 {
8468 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8469 if ( node != nullptr )
8470 {
8471 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8472 }
8473 else
8474 {
8475 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8476 }
8477 shouldAttack = false;
8478 monsterSpecialState = 0;
8479 }
8480 else if ( monsterSpecialState == VAMPIRE_CAST_DRAIN )
8481 {
8482 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8483 if ( node != nullptr )
8484 {
8485 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8486 }
8487 else
8488 {
8489 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8490 }
8491 shouldAttack = false;
8492 monsterSpecialState = 0;
8493 }
8494 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
8495 break;
8496 case SHADOW:
8497 if ( monsterSpecialState == SHADOW_SPELLCAST ) //TODO: This code is destroying spells?
8498 {
8499 //TODO: Nope, this code isn't destroying spells. Something *before* this code is.
8500 //messagePlayer(clientnum, "[DEBUG: handleMonsterSpecialAttack()] Resolving shadow's spellcast.");
8501 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8502 if ( node != nullptr )
8503 {
8504 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8505 }
8506 else
8507 {
8508 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, SPELLBOOK);
8509 }
8510 /*Item *spellbook = newItem(static_cast<ItemType>(0), static_cast<Status>(0), 0, 1, rand(), 0, &myStats->inventory);
8511 copyItem(spellbook, myStats->weapon);
8512 dropItemMonster(myStats->weapon, this, myStats, 1);*/
8513 shouldAttack = false;
8514 monsterSpecialState = 0;
8515 monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_SPELLCAST;
8516 }
8517 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
8518 break;
8519 case GOATMAN:
8520 if ( monsterSpecialState == GOATMAN_POTION )
8521 {
8522 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8523 if ( node != nullptr )
8524 {
8525 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8526 }
8527 else
8528 {
8529 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, POTION);
8530 }
8531 shouldAttack = false;
8532 monsterSpecialState = 0;
8533 }
8534 else if ( monsterSpecialState == GOATMAN_THROW )
8535 {
8536 node = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON); // find weapon to re-equip
8537 if ( node != nullptr )
8538 {
8539 swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
8540 }
8541 else
8542 {
8543 monsterUnequipSlotFromCategory(myStats, &myStats->weapon, THROWN);
8544 }
8545 shouldAttack = false;
8546 monsterSpecialState = 0;
8547 }
8548 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
8549 break;
8550 default:
8551 break;
8552 }
8553 // Whether monster should attack following the unequip action.
8554 return shouldAttack;
8555 }
8556 }
8557 // monster should attack after this function is called.
8558 return true;
8559 }
8560
getTargetsAroundEntity(Entity * my,Entity * originalTarget,double distToFind,real_t angleToSearch,int searchType,list_t ** list)8561 void getTargetsAroundEntity(Entity* my, Entity* originalTarget, double distToFind, real_t angleToSearch, int searchType, list_t** list)
8562 {
8563 Entity* entity = nullptr;
8564 node_t* node = nullptr;
8565 node_t* node2 = nullptr;
8566
8567 // aoe
8568 for ( node = map.creatures->first; node != nullptr; node = node->next ) //Only looks at monsters and players, don't iterate all entities (map.entities).
8569 {
8570 entity = (Entity*)node->element;
8571 if ( (entity->behavior == &actMonster || entity->behavior == &actPlayer) && entity != originalTarget && entity != my )
8572 {
8573 if ( searchType == MONSTER_TARGET_ENEMY )
8574 {
8575 if ( !my->checkEnemy(entity) )
8576 {
8577 continue;
8578 }
8579 }
8580 else if ( searchType == MONSTER_TARGET_FRIEND )
8581 {
8582 if ( !my->checkFriend(entity) )
8583 {
8584 continue;
8585 }
8586 }
8587 else if ( searchType == MONSTER_TARGET_PLAYER )
8588 {
8589 if ( !(entity->behavior == &actPlayer) )
8590 {
8591 continue;
8592 }
8593 }
8594 else if ( searchType == MONSTER_TARGET_ALL )
8595 {
8596 }
8597
8598 double aoeTangent = atan2(entity->y - my->y, entity->x - my->x);
8599 real_t angle = my->yaw - aoeTangent;
8600 while ( angle >= PI )
8601 {
8602 angle -= PI * 2;
8603 }
8604 while ( angle < -PI )
8605 {
8606 angle += PI * 2;
8607 }
8608 if ( abs(angle) <= angleToSearch ) // searches in 2x the given angle, +/- from yaw.
8609 {
8610 double dist = sqrt(pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2));
8611 if ( dist < distToFind )
8612 {
8613 //If this is the first entity found, the list needs to be created.
8614 if ( !(*list) )
8615 {
8616 *list = (list_t*)malloc(sizeof(list_t));
8617 (*list)->first = nullptr;
8618 (*list)->last = nullptr;
8619 }
8620 node2 = list_AddNodeLast(*list);
8621 node2->element = entity;
8622 node2->deconstructor = &emptyDeconstructor;
8623 node2->size = sizeof(Entity*);
8624 }
8625 }
8626 }
8627 }
8628 return;
8629 }
8630
numTargetsAroundEntity(Entity * my,double distToFind,real_t angleToSearch,int searchType)8631 int numTargetsAroundEntity(Entity* my, double distToFind, real_t angleToSearch, int searchType)
8632 {
8633 list_t* aoeTargets = nullptr;
8634 int count = 0;
8635 getTargetsAroundEntity(my, nullptr, distToFind, angleToSearch, searchType, &aoeTargets);
8636 if ( aoeTargets )
8637 {
8638 count = list_Size(aoeTargets);
8639 //messagePlayer(0, "found %d targets", count);
8640 //Free the list.
8641 list_FreeAll(aoeTargets);
8642 free(aoeTargets);
8643 }
8644 return count;
8645 }
8646
handleMonsterChatter(int monsterclicked,bool ringconflict,char namesays[64],Entity * my,Stat * myStats)8647 bool handleMonsterChatter(int monsterclicked, bool ringconflict, char namesays[64], Entity* my, Stat* myStats)
8648 {
8649 if ( ringconflict || myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 )
8650 {
8651 //Instant fail if ring of conflict is in effect/not NPC
8652 return false;
8653 }
8654
8655 int NPCtype = myStats->MISC_FLAGS[STAT_FLAG_NPC] & 0xFF; // get NPC type, lowest 8 bits.
8656 int NPClastLine = (myStats->MISC_FLAGS[STAT_FLAG_NPC] & 0xFF00) >> 8; // get last line said, next 8 bits.
8657
8658 int numLines = 0;
8659 int startLine = 2700 + (NPCtype - 1) * MONSTER_NPC_DIALOGUE_LINES; // lang line to start from.
8660 int currentLine = startLine + 1;
8661
8662 bool isSequential = false;
8663
8664 if ( !strcmp(language[startLine], "type:seq") )
8665 {
8666 isSequential = true;
8667 }
8668
8669 for ( int i = 1; i < MONSTER_NPC_DIALOGUE_LINES; ++i )
8670 {
8671 // find the next 9 lines if available.
8672 if ( !strcmp(language[currentLine], "") )
8673 {
8674 break;
8675 }
8676 ++currentLine;
8677 ++numLines;
8678 }
8679
8680 // choose a dialogue line.
8681 if ( numLines > 0 )
8682 {
8683 if ( isSequential )
8684 {
8685 // say the next line in series.
8686 if ( NPClastLine != 0 )
8687 {
8688 ++NPClastLine;
8689 if ( (NPClastLine) > numLines )
8690 {
8691 // reset to beginning
8692 NPClastLine = 1;
8693 }
8694 }
8695 else
8696 {
8697 // first line being said, choose the first.
8698 NPClastLine = 1;
8699 }
8700 }
8701 else if ( !isSequential )
8702 {
8703 // choose randomly
8704 NPClastLine = 1 + rand() % numLines;
8705 }
8706 messagePlayer(monsterclicked, language[startLine + NPClastLine], namesays, stats[monsterclicked]->name);
8707 myStats->MISC_FLAGS[STAT_FLAG_NPC] = NPCtype + (NPClastLine << 8);
8708 }
8709 return true;
8710 }
8711
numMonsterTypeAliveOnMap(Monster creature,Entity * & lastMonster)8712 int numMonsterTypeAliveOnMap(Monster creature, Entity*& lastMonster)
8713 {
8714 node_t* node = nullptr;
8715 Entity* entity = nullptr;
8716 int monsterCount = 0;
8717 for ( node = map.creatures->first; node != nullptr; node = node->next )
8718 {
8719 entity = (Entity*)node->element;
8720 if ( entity )
8721 {
8722 if ( entity->getRace() == creature )
8723 {
8724 lastMonster = entity;
8725 ++monsterCount;
8726 }
8727 }
8728 }
8729 return monsterCount;
8730 }
8731
monsterMoveBackwardsAndPath()8732 void Entity::monsterMoveBackwardsAndPath()
8733 {
8734 while ( yaw < 0 )
8735 {
8736 yaw += 2 * PI;
8737 }
8738 while ( yaw >= 2 * PI )
8739 {
8740 yaw -= 2 * PI;
8741 }
8742 int x1 = ((int)floor(x)) >> 4;
8743 int y1 = ((int)floor(y)) >> 4;
8744 int u, v;
8745 std::vector<std::pair<int, int>> areaToTry;
8746 if ( yaw <= PI / 4 || yaw > 7 * PI / 4 )
8747 {
8748 areaToTry.push_back(std::pair<int, int>(x1 - 1, y1));
8749 areaToTry.push_back(std::pair<int, int>(x1 - 2, y1));
8750 areaToTry.push_back(std::pair<int, int>(x1 - 2, y1 + 1));
8751 areaToTry.push_back(std::pair<int, int>(x1 - 2, y1 - 1));
8752 areaToTry.push_back(std::pair<int, int>(x1 - 3, y1));
8753 }
8754 else if ( yaw > PI / 4 && yaw <= 3 * PI / 4 )
8755 {
8756 areaToTry.push_back(std::pair<int, int>(x1, y1 - 1));
8757 areaToTry.push_back(std::pair<int, int>(x1, y1 - 2));
8758 areaToTry.push_back(std::pair<int, int>(x1 - 1, y1 - 2));
8759 areaToTry.push_back(std::pair<int, int>(x1 + 1, y1 - 2));
8760 areaToTry.push_back(std::pair<int, int>(x1, y1 - 3));
8761 }
8762 else if ( yaw > 3 * PI / 4 && yaw <= 5 * PI / 4 )
8763 {
8764 areaToTry.push_back(std::pair<int, int>(x1 + 1, y1));
8765 areaToTry.push_back(std::pair<int, int>(x1 + 2, y1));
8766 areaToTry.push_back(std::pair<int, int>(x1 + 2, y1 + 1));
8767 areaToTry.push_back(std::pair<int, int>(x1 + 2, y1 - 1));
8768 areaToTry.push_back(std::pair<int, int>(x1 + 3, y1));
8769 }
8770 else if ( yaw > 5 * PI / 4 && yaw <= 7 * PI / 4 )
8771 {
8772 areaToTry.push_back(std::pair<int, int>(x1, y1 + 1));
8773 areaToTry.push_back(std::pair<int, int>(x1, y1 + 2));
8774 areaToTry.push_back(std::pair<int, int>(x1 - 1, y1 + 2));
8775 areaToTry.push_back(std::pair<int, int>(x1 + 1, y1 + 2));
8776 areaToTry.push_back(std::pair<int, int>(x1, y1 + 3));
8777 }
8778 bool foundplace = false;
8779 for ( int tries = 0; tries < areaToTry.size() && !foundplace; ++tries )
8780 {
8781 std::pair<int, int> tmpPair = areaToTry[rand() % areaToTry.size()];
8782 u = tmpPair.first;
8783 v = tmpPair.second;
8784 if ( !checkObstacle((u << 4) + 8, (v << 4) + 8, this, nullptr) )
8785 {
8786 x1 = u;
8787 y1 = v;
8788 foundplace = true;
8789 }
8790 }
8791 path = generatePath((int)floor(x / 16), (int)floor(y / 16), x1, y1, this, this);
8792 if ( children.first != NULL )
8793 {
8794 list_RemoveNode(children.first);
8795 }
8796 node_t* node = list_AddNodeFirst(&this->children);
8797 node->element = path;
8798 node->deconstructor = &listDeconstructor;
8799 monsterState = MONSTER_STATE_HUNT; // hunt state
8800 }
8801
monsterHasLeader()8802 bool Entity::monsterHasLeader()
8803 {
8804 Stat* myStats = this->getStats();
8805 if ( myStats )
8806 {
8807 if ( myStats->leader_uid != 0 )
8808 {
8809 return true;
8810 }
8811 }
8812 return false;
8813 }
8814
monsterAllySendCommand(int command,int destX,int destY,Uint32 uid)8815 void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 uid)
8816 {
8817 if ( multiplayer == CLIENT )
8818 {
8819 return;
8820 }
8821 if ( command == -1 || command == ALLY_CMD_CANCEL || monsterAllyIndex == -1 || monsterAllyIndex > MAXPLAYERS )
8822 {
8823 return;
8824 }
8825 if ( !players[monsterAllyIndex] )
8826 {
8827 return;
8828 }
8829 if ( !players[monsterAllyIndex]->entity )
8830 {
8831 return;
8832 }
8833 if ( monsterTarget == players[monsterAllyIndex]->entity->getUID() )
8834 {
8835 // angry at owner.
8836 return;
8837 }
8838
8839 Stat* myStats = getStats();
8840 if ( !myStats )
8841 {
8842 return;
8843 }
8844
8845 if ( !isMobile() )
8846 {
8847 // doesn't respond.
8848 if ( monsterAllySpecial == ALLY_SPECIAL_CMD_REST && myStats->EFFECTS[EFF_ASLEEP]
8849 && (command == ALLY_CMD_MOVETO_CONFIRM || command == ALLY_CMD_ATTACK_CONFIRM
8850 || command == ALLY_CMD_MOVEASIDE) )
8851 {
8852 myStats->EFFECTS[EFF_ASLEEP] = false; // wake up
8853 myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 0;
8854 myStats->EFFECTS[EFF_HP_REGEN] = false; // stop regen
8855 myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = 0;
8856 monsterAllySpecial = ALLY_SPECIAL_CMD_NONE;
8857 }
8858 else
8859 {
8860 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF, *myStats, language[514], language[515], MSG_COMBAT);
8861 }
8862 return;
8863 }
8864
8865 bool isTinkeringFollower = FollowerMenu.isTinkeringFollower(myStats->type);
8866 int tinkeringLVL = 0;
8867 int skillLVL = 0;
8868 if ( stats[monsterAllyIndex] )
8869 {
8870 tinkeringLVL = stats[monsterAllyIndex]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[monsterAllyIndex], players[monsterAllyIndex]->entity);
8871 skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] + statGetCHR(stats[clientnum], players[monsterAllyIndex]->entity);
8872 if ( isTinkeringFollower )
8873 {
8874 skillLVL = tinkeringLVL;
8875 }
8876 }
8877
8878 if ( myStats->type != GYROBOT )
8879 {
8880 if ( FollowerMenu.monsterGyroBotOnlyCommand(command) )
8881 {
8882 return;
8883 }
8884 }
8885 else if ( myStats->type == GYROBOT )
8886 {
8887 if ( FollowerMenu.monsterGyroBotDisallowedCommands(command) )
8888 {
8889 return;
8890 }
8891 }
8892
8893 // do a final check if player can use this command.
8894 /*if ( FollowerMenu.optionDisabledForCreature(skillLVL, myStats->type, command) != 0 )
8895 {
8896 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
8897 *myStats, language[3638], language[3639], MSG_COMBAT);
8898 return;
8899 }*/
8900
8901 switch ( command )
8902 {
8903 case ALLY_CMD_RETURN_SOUL:
8904 if ( monsterAllySummonRank != 0 )
8905 {
8906 float manaToRefund = myStats->MAXMP * (myStats->HP / static_cast<float>(myStats->MAXHP));
8907 setMP(static_cast<int>(manaToRefund));
8908 setHP(0);
8909 if ( stats[monsterAllyIndex] && stats[monsterAllyIndex]->MP == 0 )
8910 {
8911 steamAchievementClient(monsterAllyIndex, "BARONY_ACH_EXTERNAL_BATTERY");
8912 }
8913 }
8914 break;
8915 case ALLY_CMD_ATTACK_CONFIRM:
8916 if ( uid != 0 && uid != getUID() )
8917 {
8918 Entity* target = uidToEntity(uid);
8919 if ( target )
8920 {
8921 if ( target->behavior == &actMonster || target->behavior == &actPlayer )
8922 {
8923 if ( stats[monsterAllyIndex] ) // check owner's proficiency.
8924 {
8925 if ( skillLVL >= SKILL_LEVEL_MASTER || myStats->type != HUMAN )
8926 {
8927 // attack anything except if FF is off + friend.
8928 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
8929 {
8930 if ( !checkFriend(target) )
8931 {
8932 monsterAcquireAttackTarget(*target, MONSTER_STATE_ATTACK);
8933 handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK);
8934 }
8935 else
8936 {
8937 // messagePlayer(0, "Friendly fire is on!");
8938 handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK_FRIENDLY_FIRE);
8939 }
8940 }
8941 else
8942 {
8943 monsterAcquireAttackTarget(*target, MONSTER_STATE_ATTACK);
8944 handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK);
8945 }
8946 }
8947 else if ( skillLVL >= AllyNPCSkillRequirements[ALLY_CMD_ATTACK_CONFIRM] )
8948 {
8949 if ( !checkFriend(target) )
8950 {
8951 monsterAcquireAttackTarget(*target, MONSTER_STATE_ATTACK);
8952 handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK);
8953 }
8954 else
8955 {
8956 handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK_FRIENDLY_FIRE);
8957 }
8958 }
8959 }
8960 monsterAllyInteractTarget = 0;
8961 }
8962 else
8963 {
8964 monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH);
8965 monsterAllyState = ALLY_STATE_MOVETO;
8966 }
8967 }
8968 }
8969 break;
8970 case ALLY_CMD_MOVEASIDE:
8971 monsterMoveAside(this, players[monsterAllyIndex]->entity);
8972 handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVEASIDE);
8973 break;
8974 case ALLY_CMD_DEFEND:
8975 monsterAllyState = ALLY_STATE_DEFEND;
8976 if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
8977 {
8978 monsterSentrybotLookDir = monsterLookDir;
8979 handleNPCInteractDialogue(*myStats, ALLY_EVENT_WAIT);
8980 }
8981 else
8982 {
8983 createPathBoundariesNPC(5);
8984 // stop in your tracks!
8985 handleNPCInteractDialogue(*myStats, ALLY_EVENT_WAIT);
8986 monsterState = MONSTER_STATE_WAIT; // wait state
8987 serverUpdateEntitySkill(this, 0);
8988 }
8989 break;
8990 case ALLY_CMD_MOVETO_SELECT:
8991 break;
8992 case ALLY_CMD_FOLLOW:
8993 monsterAllyState = ALLY_STATE_DEFAULT;
8994 if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
8995 {
8996 monsterSentrybotLookDir = 0.0;
8997 handleNPCInteractDialogue(*myStats, ALLY_EVENT_FOLLOW);
8998 }
8999 else
9000 {
9001 handleNPCInteractDialogue(*myStats, ALLY_EVENT_FOLLOW);
9002 }
9003 break;
9004 case ALLY_CMD_CLASS_TOGGLE:
9005 ++monsterAllyClass;
9006 if ( monsterAllyClass > ALLY_CLASS_RANGED )
9007 {
9008 monsterAllyClass = ALLY_CLASS_MIXED;
9009 }
9010 myStats->allyClass = monsterAllyClass;
9011 serverUpdateEntitySkill(this, 46);
9012 break;
9013 case ALLY_CMD_PICKUP_TOGGLE:
9014 ++monsterAllyPickupItems;
9015 if ( monsterAllyPickupItems > ALLY_PICKUP_ALL )
9016 {
9017 monsterAllyPickupItems = ALLY_PICKUP_NONPLAYER;
9018 }
9019 myStats->allyItemPickup = monsterAllyPickupItems;
9020 serverUpdateEntitySkill(this, 44);
9021 break;
9022 case ALLY_CMD_GYRO_LIGHT_TOGGLE:
9023 ++monsterAllyClass;
9024 if ( monsterAllyClass >= ALLY_GYRO_LIGHT_END )
9025 {
9026 monsterAllyClass = ALLY_GYRO_LIGHT_NONE;
9027 }
9028 myStats->allyClass = monsterAllyClass;
9029 serverUpdateEntitySkill(this, 46);
9030 break;
9031 case ALLY_CMD_GYRO_DETECT_TOGGLE:
9032 {
9033 ++monsterAllyPickupItems;
9034 bool failQuality = false;
9035 bool failSkillRequirement = false;
9036 if ( monsterAllyPickupItems >= ALLY_GYRO_DETECT_END )
9037 {
9038 monsterAllyPickupItems = ALLY_GYRO_DETECT_NONE;
9039 }
9040 else if ( monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_VALUABLE )
9041 {
9042 if ( myStats->LVL < 15 )
9043 {
9044 failQuality = true;
9045 }
9046 if ( skillLVL < SKILL_LEVEL_MASTER )
9047 {
9048 failSkillRequirement = true;
9049 }
9050 }
9051 else if ( monsterAllyPickupItems == ALLY_GYRO_DETECT_MONSTERS )
9052 {
9053 if ( myStats->LVL < 10 )
9054 {
9055 failQuality = true;
9056 }
9057 if ( skillLVL < SKILL_LEVEL_EXPERT )
9058 {
9059 failSkillRequirement = true;
9060 }
9061 }
9062 else if ( monsterAllyPickupItems == ALLY_GYRO_DETECT_TRAPS
9063 || monsterAllyPickupItems == ALLY_GYRO_DETECT_EXITS )
9064 {
9065 if ( myStats->LVL < 5 )
9066 {
9067 failQuality = true;
9068 }
9069 if ( skillLVL < SKILL_LEVEL_SKILLED )
9070 {
9071 failSkillRequirement = true;
9072 }
9073 }
9074 else if ( monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_METAL
9075 || monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_MAGIC )
9076 {
9077 if ( skillLVL < SKILL_LEVEL_BASIC )
9078 {
9079 failSkillRequirement = true;
9080 }
9081 }
9082
9083 if ( failSkillRequirement || failQuality )
9084 {
9085 if ( skillLVL < SKILL_LEVEL_BASIC )
9086 {
9087 messagePlayerColor(monsterAllyIndex, 0xFFFFFFFF, language[3680]);
9088 }
9089 else
9090 {
9091 messagePlayerColor(monsterAllyIndex, 0xFFFFFFFF, language[3679]);
9092 }
9093 monsterAllyPickupItems = ALLY_GYRO_DETECT_NONE;
9094 }
9095 myStats->allyItemPickup = monsterAllyPickupItems;
9096 serverUpdateEntitySkill(this, 44);
9097 break;
9098 }
9099 case ALLY_CMD_DROP_EQUIP:
9100 if ( strcmp(myStats->name, "") && myStats->type == HUMAN )
9101 {
9102 // named humans refuse to drop equipment.
9103 handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_HUMAN_REFUSE);
9104 }
9105 else if ( myStats->type == GYROBOT )
9106 {
9107 bool droppedSomething = false;
9108 node_t* nextnode = nullptr;
9109 for ( node_t* node = myStats->inventory.first; node; node = nextnode )
9110 {
9111 nextnode = node->next;
9112 Item* item = (Item*)node->element;
9113 if ( item )
9114 {
9115 if ( item->type == TOOL_TELEPORT_BOMB || item->type == TOOL_FREEZE_BOMB
9116 || item->type == TOOL_BOMB || item->type == TOOL_SLEEP_BOMB )
9117 {
9118 int count = item->count;
9119 this->monsterEquipItem(*item, &myStats->weapon);
9120 this->attack(0, 0, nullptr);
9121 if ( count > 1 )
9122 {
9123 myStats->weapon = nullptr;
9124 }
9125 droppedSomething = true;
9126 break;
9127 }
9128 else
9129 {
9130 for ( int c = item->count; c > 0; --c )
9131 {
9132 Entity* dropped = dropItemMonster(item, this, myStats, item->count);
9133 if ( dropped )
9134 {
9135 c = 0;
9136 droppedSomething = true;
9137 }
9138 }
9139 }
9140 }
9141 }
9142
9143 if ( !droppedSomething )
9144 {
9145 messagePlayer(monsterAllyIndex, language[3868]);
9146 }
9147 }
9148 else if ( stats[monsterAllyIndex] )
9149 {
9150 Entity* dropped = nullptr;
9151 bool confirmDropped = false;
9152 bool dropWeaponOnly = false;
9153 bool unableToDrop = false;
9154 Uint32 owner = players[monsterAllyIndex]->entity->getUID();
9155 if ( skillLVL >= SKILL_LEVEL_MASTER )
9156 {
9157 if ( myStats->helmet )
9158 {
9159 if ( myStats->helmet->canUnequip(myStats) )
9160 {
9161 dropped = dropItemMonster(myStats->helmet, this, myStats);
9162 }
9163 else
9164 {
9165 unableToDrop = true;
9166 }
9167 }
9168 if ( dropped )
9169 {
9170 confirmDropped = true;
9171 dropped->itemOriginalOwner = owner;
9172 }
9173 if ( myStats->breastplate )
9174 {
9175 if ( myStats->breastplate->canUnequip(myStats) )
9176 {
9177 dropped = dropItemMonster(myStats->breastplate, this, myStats);
9178 }
9179 else
9180 {
9181 unableToDrop = true;
9182 }
9183 }
9184 if ( dropped )
9185 {
9186 confirmDropped = true;
9187 dropped->itemOriginalOwner = owner;
9188 }
9189 if ( myStats->shoes )
9190 {
9191 if ( myStats->shoes->canUnequip(myStats) )
9192 {
9193 dropped = dropItemMonster(myStats->shoes, this, myStats);
9194 }
9195 else
9196 {
9197 unableToDrop = true;
9198 }
9199 }
9200 if ( dropped )
9201 {
9202 confirmDropped = true;
9203 dropped->itemOriginalOwner = owner;
9204 }
9205 if ( myStats->shield )
9206 {
9207 if ( myStats->shield->canUnequip(myStats) )
9208 {
9209 dropped = dropItemMonster(myStats->shield, this, myStats, myStats->shield->count);
9210 }
9211 else
9212 {
9213 unableToDrop = true;
9214 }
9215 }
9216 if ( dropped )
9217 {
9218 confirmDropped = true;
9219 dropped->itemOriginalOwner = owner;
9220 }
9221
9222 if ( skillLVL >= SKILL_LEVEL_LEGENDARY )
9223 {
9224 if ( myStats->ring )
9225 {
9226 if ( myStats->ring->canUnequip(myStats) )
9227 {
9228 dropped = dropItemMonster(myStats->ring, this, myStats);
9229 }
9230 else
9231 {
9232 unableToDrop = true;
9233 }
9234 }
9235 if ( dropped )
9236 {
9237 confirmDropped = true;
9238 dropped->itemOriginalOwner = owner;
9239 }
9240 if ( myStats->amulet )
9241 {
9242 if ( myStats->amulet->canUnequip(myStats) )
9243 {
9244 dropped = dropItemMonster(myStats->amulet, this, myStats);
9245 }
9246 else
9247 {
9248 unableToDrop = true;
9249 }
9250 }
9251 if ( dropped )
9252 {
9253 confirmDropped = true;
9254 dropped->itemOriginalOwner = owner;
9255 }
9256 if ( myStats->cloak )
9257 {
9258 if ( myStats->cloak->canUnequip(myStats) )
9259 {
9260 dropped = dropItemMonster(myStats->cloak, this, myStats);
9261 }
9262 else
9263 {
9264 unableToDrop = true;
9265 }
9266 }
9267 if ( dropped )
9268 {
9269 confirmDropped = true;
9270 dropped->itemOriginalOwner = owner;
9271 }
9272 if ( confirmDropped )
9273 {
9274 handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_ALL);
9275 }
9276 }
9277 else
9278 {
9279 if ( confirmDropped )
9280 {
9281 handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_EQUIP);
9282 }
9283 }
9284 }
9285 else
9286 {
9287 if ( myStats->weapon )
9288 {
9289 dropWeaponOnly = true;
9290 }
9291 }
9292 if ( myStats->weapon )
9293 {
9294 if ( myStats->weapon->canUnequip(myStats) )
9295 {
9296 dropped = dropItemMonster(myStats->weapon, this, myStats, myStats->weapon->count);
9297 }
9298 else
9299 {
9300 unableToDrop = true;
9301 }
9302 }
9303 if ( dropped )
9304 {
9305 dropped->itemOriginalOwner = owner;
9306 if ( dropWeaponOnly )
9307 {
9308 handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_WEAPON);
9309 }
9310 }
9311 /*if ( unableToDrop )
9312 {
9313 handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_FAILED);
9314 }*/
9315 }
9316 break;
9317 case ALLY_CMD_MOVETO_CONFIRM:
9318 {
9319 if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
9320 {
9321 real_t floatx = destX * 16 + 8;
9322 real_t floaty = destY * 16 + 8;
9323 double tangent = atan2(floaty - y, floatx - x);
9324 monsterLookTime = 1;
9325 monsterMoveTime = rand() % 10 + 1;
9326 monsterLookDir = tangent;
9327 monsterSentrybotLookDir = monsterLookDir;
9328 if ( monsterAllyState != ALLY_STATE_DEFEND )
9329 {
9330 handleNPCInteractDialogue(*myStats, ALLY_EVENT_WAIT);
9331 }
9332 monsterAllyState = ALLY_STATE_DEFEND;
9333 }
9334 else if ( monsterSetPathToLocation(destX, destY, 1) )
9335 {
9336 monsterState = MONSTER_STATE_HUNT; // hunt state
9337 monsterAllyState = ALLY_STATE_MOVETO;
9338 serverUpdateEntitySkill(this, 0);
9339 handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_BEGIN);
9340 }
9341 else
9342 {
9343 //messagePlayer(0, "no path to destination");
9344 handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_FAIL);
9345 }
9346 break;
9347 }
9348 case ALLY_CMD_GYRO_RETURN:
9349 {
9350 if ( players[monsterAllyIndex]->entity )
9351 {
9352 destX = static_cast<int>(players[monsterAllyIndex]->entity->x) >> 4;
9353 destY = static_cast<int>(players[monsterAllyIndex]->entity->y) >> 4;
9354 if ( entityDist(this, players[monsterAllyIndex]->entity) < TOUCHRANGE * 2 )
9355 {
9356 monsterSpecialState = GYRO_RETURN_LANDING;
9357 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
9358 playSoundEntity(this, 449, 128);
9359 }
9360 else if ( monsterSetPathToLocation(destX, destY, 2) )
9361 {
9362 monsterState = MONSTER_STATE_HUNT; // hunt state
9363 monsterAllyState = ALLY_STATE_MOVETO;
9364 serverUpdateEntitySkill(this, 0);
9365 handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_BEGIN);
9366 monsterSpecialState = GYRO_RETURN_PATHING;
9367 serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
9368 }
9369 else
9370 {
9371 //messagePlayer(0, "no path to destination");
9372 handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVETO_FAIL);
9373 }
9374 }
9375 break;
9376 }
9377 case ALLY_CMD_SPECIAL:
9378 {
9379 if ( monsterAllySpecialCooldown == 0 )
9380 {
9381 int duration = TICKS_PER_SECOND * (60);
9382 if ( myStats->HP < myStats->MAXHP && setEffect(EFF_ASLEEP, true, duration, false) ) // 60 seconds of sleep.
9383 {
9384 setEffect(EFF_HP_REGEN, true, duration, false);
9385 monsterAllySpecial = ALLY_SPECIAL_CMD_REST;
9386 monsterAllySpecialCooldown = -1; // locked out until next floor.
9387 serverUpdateEntitySkill(this, 49);
9388 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFF, *myStats, language[398], language[397], MSG_COMBAT);
9389 if ( players[monsterAllyIndex] && players[monsterAllyIndex]->entity
9390 && myStats->HP < myStats->MAXHP && rand() % 3 == 0 )
9391 {
9392 players[monsterAllyIndex]->entity->increaseSkill(PRO_LEADERSHIP);
9393 }
9394 }
9395 else
9396 {
9397 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFF, *myStats, language[3880], language[3880], MSG_GENERIC);
9398 }
9399 }
9400 break;
9401 }
9402 case ALLY_CMD_DUMMYBOT_RETURN:
9403 if ( myStats->type == DUMMYBOT )
9404 {
9405 monsterSpecialState = DUMMYBOT_RETURN_FORM;
9406 serverUpdateEntitySkill(this, 33);
9407 }
9408 else if ( myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
9409 {
9410 monsterSpecialState = DUMMYBOT_RETURN_FORM;
9411 serverUpdateEntitySkill(this, 33);
9412 playSoundEntity(this, 469 + rand() % 3, 92);
9413 }
9414 break;
9415 default:
9416 break;
9417 }
9418 serverUpdateEntitySkill(this, 43);
9419 //messagePlayer(0, "received: %d", command);
9420 }
9421
monsterAllySetInteract()9422 bool Entity::monsterAllySetInteract()
9423 {
9424 if ( multiplayer == CLIENT )
9425 {
9426 return false;
9427 }
9428 if ( monsterAllyInteractTarget == 0 )
9429 {
9430 return false;
9431 }
9432 Entity* target = uidToEntity(monsterAllyInteractTarget);
9433 if ( !target )
9434 {
9435 monsterAllyState = ALLY_STATE_DEFAULT;
9436 monsterAllyInteractTarget = 0;
9437 return false;
9438 }
9439 // check distance to interactable.
9440 double range = pow(y - target->y, 2) + pow(x - target->x, 2);
9441 if ( range < 576 ) // 24 squared
9442 {
9443 if ( getMonsterTypeFromSprite() == GYROBOT
9444 && monsterSpecialState != GYRO_INTERACT_LANDING
9445 && z < -0.1 )
9446 {
9447 // don't set interact yet.
9448 return true;
9449 }
9450 else
9451 {
9452 FollowerMenu.entityToInteractWith = target; // set followerInteractedEntity to the mechanism/item/gold etc.
9453 FollowerMenu.entityToInteractWith->interactedByMonster = getUID(); // set the remote entity to this monster's uid to lookup later.
9454 }
9455 }
9456 else
9457 {
9458 return false;
9459 }
9460
9461 return true;
9462 }
9463
isInteractWithMonster()9464 bool Entity::isInteractWithMonster()
9465 {
9466 if ( FollowerMenu.entityToInteractWith == nullptr )
9467 {
9468 return false; // entity is not set to interact with any monster.
9469 }
9470 if ( FollowerMenu.entityToInteractWith->interactedByMonster == 0 )
9471 {
9472 return false; // recent monster is not set to interact.
9473 }
9474 if ( FollowerMenu.entityToInteractWith == this )
9475 {
9476 return true; // a monster is set to interact with myself.
9477 }
9478 return false;
9479 }
9480
clearMonsterInteract()9481 void Entity::clearMonsterInteract()
9482 {
9483 FollowerMenu.entityToInteractWith = nullptr;
9484 interactedByMonster = 0;
9485 }
9486
monsterSetPathToLocation(int destX,int destY,int adjacentTilesToCheck,bool tryRandomSpot)9487 bool Entity::monsterSetPathToLocation(int destX, int destY, int adjacentTilesToCheck, bool tryRandomSpot)
9488 {
9489 int u, v;
9490 bool foundplace = false;
9491 int pathToX = destX;
9492 int pathToY = destY;
9493
9494 if ( static_cast<int>(x / 16) == destX && static_cast<int>(y / 16) == destY )
9495 {
9496 return true; // we're trying to move to the spot we're already at!
9497 }
9498 else if ( !checkObstacle((destX << 4) + 8, (destY << 4) + 8, this, nullptr) )
9499 {
9500 if ( !tryRandomSpot )
9501 {
9502 foundplace = true; // we can path directly to the destination specified.
9503 }
9504 }
9505
9506 std::vector<std::pair<int, std::pair<int, int>>> possibleDestinations; // store distance and the x, y coordinates in each element.
9507
9508 if ( !foundplace )
9509 {
9510 for ( u = destX - adjacentTilesToCheck; u <= destX + adjacentTilesToCheck; u++ )
9511 {
9512 for ( v = destY - adjacentTilesToCheck; v <= destY + adjacentTilesToCheck; v++ )
9513 {
9514 if ( static_cast<int>(x / 16) == u && static_cast<int>(y / 16) == v )
9515 {
9516 // we're trying to move to the spot we're already at!
9517 }
9518 else if ( !checkObstacle((u << 4) + 8, (v << 4) + 8, this, nullptr) )
9519 {
9520 int distance = pow(x / 16 - u, 2) + pow(y / 16 - v, 2);
9521 possibleDestinations.push_back(std::make_pair(distance, std::make_pair(u, v)));
9522 }
9523 }
9524 }
9525 }
9526
9527 if ( !possibleDestinations.empty() )
9528 {
9529 // sort by distance from monster, first result is shortest path.
9530 std::sort(possibleDestinations.begin(), possibleDestinations.end());
9531 pathToX = possibleDestinations.at(0).second.first;
9532 pathToY = possibleDestinations.at(0).second.second;
9533 foundplace = true;
9534 }
9535
9536 path = generatePath(static_cast<int>(floor(x / 16)), static_cast<int>(floor(y / 16)), pathToX, pathToY, this, nullptr);
9537 if ( children.first != NULL )
9538 {
9539 list_RemoveNode(children.first);
9540 }
9541 node_t* node = list_AddNodeFirst(&children);
9542 node->element = path;
9543 node->deconstructor = &listDeconstructor;
9544
9545 if ( path == nullptr || !foundplace )
9546 {
9547 return false;
9548 }
9549 return true;
9550 }
9551
handleNPCInteractDialogue(Stat & myStats,AllyNPCChatter event)9552 void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
9553 {
9554 if ( multiplayer == CLIENT )
9555 {
9556 return;
9557 }
9558 if ( !isMobile() )
9559 {
9560 return;
9561 }
9562 if ( monsterAllyIndex < 0 || monsterAllyIndex >= MAXPLAYERS )
9563 {
9564 return;
9565 }
9566 if ( !stats[monsterAllyIndex] )
9567 {
9568 return;
9569 }
9570
9571 char namesays[32];
9572 if ( !strcmp(myStats.name, "") )
9573 {
9574 if ( myStats.type < KOBOLD ) //Original monster count
9575 {
9576 snprintf(namesays, 31, language[513], language[90 + myStats.type]); // The %s says
9577 }
9578 else if ( myStats.type >= KOBOLD ) //New monsters
9579 {
9580 snprintf(namesays, 31, language[513], language[2000 + myStats.type - KOBOLD]); // The %s says
9581 }
9582 }
9583 else
9584 {
9585 snprintf(namesays, 31, language[1302], myStats.name); // %s says
9586 }
9587
9588 std::string message;
9589
9590 if ( myStats.type == HUMAN )
9591 {
9592 switch ( event )
9593 {
9594 case ALLY_EVENT_MOVEASIDE:
9595 message = language[535];
9596 break;
9597 case ALLY_EVENT_MOVETO_BEGIN:
9598 if ( rand() % 10 == 0 )
9599 {
9600 message = language[3079 + rand() % 2];
9601 }
9602 break;
9603 case ALLY_EVENT_MOVETO_FAIL:
9604 message = language[3077 + rand() % 2];
9605 break;
9606 case ALLY_EVENT_INTERACT_ITEM_CURSED:
9607 if ( FollowerMenu.entityToInteractWith && FollowerMenu.entityToInteractWith->behavior == &actItem )
9608 {
9609 Item* item = newItemFromEntity(FollowerMenu.entityToInteractWith);
9610 if ( item )
9611 {
9612 char fullmsg[256] = "";
9613 switch ( itemCategory(item) )
9614 {
9615 case WEAPON:
9616 case MAGICSTAFF:
9617 snprintf(fullmsg, 63, language[3071], namesays, language[3107]);
9618 break;
9619 case ARMOR:
9620 case TOOL:
9621 switch ( checkEquipType(item) )
9622 {
9623 case TYPE_OFFHAND:
9624 snprintf(fullmsg, 63, language[3071], namesays, language[3112]);
9625 break;
9626 case TYPE_HELM:
9627 case TYPE_HAT:
9628 case TYPE_BREASTPIECE:
9629 case TYPE_BOOTS:
9630 case TYPE_SHIELD:
9631 case TYPE_GLOVES:
9632 case TYPE_CLOAK:
9633 snprintf(fullmsg, 63, language[3071], namesays, language[3107 + checkEquipType(item)]);
9634 break;
9635 default:
9636 snprintf(fullmsg, 63, language[3071], namesays, language[3117]);
9637 break;
9638 }
9639 break;
9640 case RING:
9641 snprintf(fullmsg, 63, language[3071], namesays, language[3107 + TYPE_RING]);
9642 break;
9643 case AMULET:
9644 snprintf(fullmsg, 63, language[3071], namesays, language[3107 + TYPE_AMULET]);
9645 break;
9646 default:
9647 break;
9648 }
9649 if ( strcmp(fullmsg, "") )
9650 {
9651 messagePlayer(monsterAllyIndex, fullmsg);
9652 }
9653 }
9654 if ( item )
9655 {
9656 free(item);
9657 }
9658 }
9659 return;
9660 break;
9661 case ALLY_EVENT_INTERACT_ITEM_NOUSE:
9662 message = language[3074];
9663 break;
9664 case ALLY_EVENT_INTERACT_ITEM_FOOD_BAD:
9665 message = language[3088];
9666 break;
9667 case ALLY_EVENT_INTERACT_ITEM_FOOD_GOOD:
9668 if ( myStats.HUNGER > 800 )
9669 {
9670 message = language[3076];
9671 }
9672 else
9673 {
9674 message = language[3075];
9675 }
9676 break;
9677 case ALLY_EVENT_INTERACT_ITEM_FOOD_ROTTEN:
9678 message = language[3091];
9679 break;
9680 case ALLY_EVENT_INTERACT_ITEM_FOOD_FULL:
9681 message = language[3089 + rand() % 2];
9682 break;
9683 case ALLY_EVENT_INTERACT_OTHER:
9684 break;
9685 case ALLY_EVENT_ATTACK:
9686 case ALLY_EVENT_SPOT_ENEMY:
9687 message = language[516 + rand() % 3];
9688 break;
9689 case ALLY_EVENT_ATTACK_FRIENDLY_FIRE:
9690 message = language[3084 + rand() % 2];
9691 break;
9692 case ALLY_EVENT_DROP_HUMAN_REFUSE:
9693 message = language[3135];
9694 break;
9695 case ALLY_EVENT_DROP_WEAPON:
9696 case ALLY_EVENT_DROP_EQUIP:
9697 case ALLY_EVENT_DROP_ALL:
9698 if ( rand() % 2 )
9699 {
9700 if ( rand() % 2 && event == ALLY_EVENT_DROP_ALL )
9701 {
9702 message = language[3083];
9703 }
9704 else
9705 {
9706 message = language[3072 + rand() % 2];
9707 }
9708 }
9709 else
9710 {
9711 message = language[3081 + rand() % 2];
9712 }
9713 break;
9714 case ALLY_EVENT_WAIT:
9715 message = language[3069 + rand() % 2];
9716 break;
9717 case ALLY_EVENT_FOLLOW:
9718 message = language[526 + rand() % 3];
9719 break;
9720 case ALLY_EVENT_MOVETO_REPATH:
9721 if ( rand() % 20 == 0 )
9722 {
9723 message = language[3086 + rand() % 2];
9724 }
9725 break;
9726 default:
9727 break;
9728 }
9729 }
9730 else
9731 {
9732 char genericStr[128] = "";
9733 char namedStr[128] = "";
9734
9735 switch ( event )
9736 {
9737 case ALLY_EVENT_MOVEASIDE:
9738 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9739 myStats, language[3129], language[3130], MSG_COMBAT);
9740 break;
9741 case ALLY_EVENT_MOVETO_BEGIN:
9742 /*if ( rand() % 10 == 0 )
9743 {
9744 message = language[3079 + rand() % 2];
9745 }*/
9746 break;
9747 case ALLY_EVENT_MOVETO_FAIL:
9748 if ( rand() % 2 == 0 )
9749 {
9750 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9751 myStats, language[3131], language[3132], MSG_COMBAT);
9752 }
9753 else
9754 {
9755 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9756 myStats, language[3133], language[3134], MSG_COMBAT);
9757 }
9758 break;
9759 case ALLY_EVENT_INTERACT_ITEM_CURSED:
9760 if ( FollowerMenu.entityToInteractWith && FollowerMenu.entityToInteractWith->behavior == &actItem )
9761 {
9762 Item* item = newItemFromEntity(FollowerMenu.entityToInteractWith);
9763 if ( item )
9764 {
9765 char fullmsg[256] = "";
9766 switch ( itemCategory(item) )
9767 {
9768 case WEAPON:
9769 case MAGICSTAFF:
9770 snprintf(fullmsg, 63, language[3118], language[3107]);
9771 break;
9772 case ARMOR:
9773 case TOOL:
9774 switch ( checkEquipType(item) )
9775 {
9776 case TYPE_OFFHAND:
9777 snprintf(fullmsg, 63, language[3118], language[3112]);
9778 break;
9779 case TYPE_HELM:
9780 case TYPE_HAT:
9781 case TYPE_BREASTPIECE:
9782 case TYPE_BOOTS:
9783 case TYPE_SHIELD:
9784 case TYPE_GLOVES:
9785 case TYPE_CLOAK:
9786 snprintf(fullmsg, 63, language[3118], language[3107 + checkEquipType(item)]);
9787 break;
9788 default:
9789 snprintf(fullmsg, 63, language[3118], language[3117]);
9790 break;
9791 }
9792 break;
9793 case RING:
9794 snprintf(fullmsg, 63, language[3118], language[3107 + TYPE_RING]);
9795 break;
9796 case AMULET:
9797 snprintf(fullmsg, 63, language[3118], language[3107 + TYPE_AMULET]);
9798 break;
9799 default:
9800 break;
9801 }
9802 if ( strcmp(fullmsg, "") )
9803 {
9804 char genericStr[128];
9805 strcpy(genericStr, language[3119]);
9806 strcat(genericStr, fullmsg);
9807 char namedStr[128];
9808 strcpy(namedStr, language[3120]);
9809 strcat(namedStr, fullmsg);
9810 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9811 myStats, genericStr, namedStr, MSG_COMBAT);
9812 }
9813 }
9814 if ( item )
9815 {
9816 free(item);
9817 }
9818 }
9819 break;
9820 case ALLY_EVENT_INTERACT_ITEM_NOUSE:
9821 case ALLY_EVENT_INTERACT_ITEM_FOOD_FULL:
9822 {
9823 Item* item = newItemFromEntity(FollowerMenu.entityToInteractWith);
9824 if ( item )
9825 {
9826 char itemString[64] = "";
9827 snprintf(itemString, 63, language[3123], items[item->type].name_unidentified);
9828 strcpy(genericStr, language[3121]);
9829 strcat(genericStr, itemString);
9830 strcpy(namedStr, language[3122]);
9831 strcat(namedStr, itemString);
9832 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9833 myStats, genericStr, namedStr, MSG_COMBAT);
9834 free(item);
9835 }
9836 break;
9837 }
9838 case ALLY_EVENT_INTERACT_ITEM_FOOD_BAD:
9839 case ALLY_EVENT_INTERACT_ITEM_FOOD_GOOD:
9840 case ALLY_EVENT_INTERACT_ITEM_FOOD_ROTTEN:
9841 {
9842 Item* item = newItemFromEntity(FollowerMenu.entityToInteractWith);
9843 if ( item )
9844 {
9845 char itemString[64] = "";
9846 snprintf(itemString, 63, language[3124], items[item->type].name_unidentified);
9847 strcpy(genericStr, language[3121]);
9848 strcat(genericStr, itemString);
9849 strcpy(namedStr, language[3122]);
9850 strcat(namedStr, itemString);
9851 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9852 myStats, genericStr, namedStr, MSG_COMBAT);
9853 free(item);
9854 }
9855 break;
9856 }
9857 case ALLY_EVENT_INTERACT_OTHER:
9858 break;
9859 case ALLY_EVENT_ATTACK:
9860 case ALLY_EVENT_SPOT_ENEMY:
9861 //message = language[516 + rand() % 3];
9862 break;
9863 case ALLY_EVENT_ATTACK_FRIENDLY_FIRE:
9864 //message = language[3084 + rand() % 2];
9865 break;
9866 case ALLY_EVENT_DROP_WEAPON:
9867 strcpy(genericStr, language[3121]);
9868 strcat(genericStr, language[3125]);
9869 strcpy(namedStr, language[3122]);
9870 strcat(namedStr, language[3125]);
9871 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9872 myStats, genericStr, namedStr, MSG_COMBAT);
9873 break;
9874 case ALLY_EVENT_DROP_EQUIP:
9875 strcpy(genericStr, language[3121]);
9876 strcat(genericStr, language[3126]);
9877 strcpy(namedStr, language[3122]);
9878 strcat(namedStr, language[3126]);
9879 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9880 myStats, genericStr, namedStr, MSG_COMBAT);
9881 break;
9882 case ALLY_EVENT_DROP_ALL:
9883 strcpy(genericStr, language[3121]);
9884 strcat(genericStr, language[3127]);
9885 strcpy(namedStr, language[3122]);
9886 strcat(namedStr, language[3127]);
9887 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9888 myStats, genericStr, namedStr, MSG_COMBAT);
9889 break;
9890 case ALLY_EVENT_WAIT:
9891 if ( myStats.type == SENTRYBOT || myStats.type == SPELLBOT )
9892 {
9893 messagePlayerColor(monsterAllyIndex, 0xFFFFFFFF, language[3676], monstertypename[myStats.type]);
9894 }
9895 else
9896 {
9897 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9898 myStats, language[3105], language[3106], MSG_COMBAT);
9899 }
9900 break;
9901 case ALLY_EVENT_FOLLOW:
9902 if ( myStats.type == SENTRYBOT || myStats.type == SPELLBOT )
9903 {
9904 messagePlayerColor(monsterAllyIndex, 0xFFFFFFFF, language[3677], monstertypename[myStats.type]);
9905 }
9906 else
9907 {
9908 messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
9909 myStats, language[529], language[3128], MSG_COMBAT);
9910 }
9911 break;
9912 case ALLY_EVENT_MOVETO_REPATH:
9913 /*if ( rand() % 20 == 0 )
9914 {
9915 message = language[3086 + rand() % 2];
9916 }*/
9917 break;
9918 default:
9919 break;
9920 }
9921 return;
9922 }
9923 char fullmsg[256] = "";
9924 strcpy(fullmsg, message.c_str());
9925 if ( strcmp(fullmsg, "") )
9926 {
9927 messagePlayer(monsterAllyIndex, fullmsg, namesays, stats[monsterAllyIndex]->name);
9928 }
9929 }
9930
shouldMonsterDefend(Stat & myStats,const Entity & target,const Stat & targetStats,int targetDist,bool hasrangedweapon)9931 int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat& targetStats, int targetDist, bool hasrangedweapon)
9932 {
9933 if ( behavior != &actMonster )
9934 {
9935 return MONSTER_DEFEND_NONE;
9936 }
9937
9938 if ( !myStats.shield )
9939 {
9940 return MONSTER_DEFEND_NONE;
9941 }
9942
9943 if ( itemTypeIsQuiver(myStats.shield->type) )
9944 {
9945 return MONSTER_DEFEND_NONE;
9946 }
9947
9948 if ( monsterSpecialState > 0 )
9949 {
9950 return MONSTER_DEFEND_NONE;
9951 }
9952
9953 if ( myStats.type == LICH
9954 || myStats.type == DEVIL
9955 || myStats.type == LICH_ICE
9956 || myStats.type == LICH_FIRE
9957 || myStats.type == SHOPKEEPER
9958 )
9959 {
9960 return MONSTER_DEFEND_NONE;
9961 }
9962
9963 bool isPlayerAlly = (monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS);
9964
9965 if ( !(isPlayerAlly || myStats.type == HUMAN) )
9966 {
9967 return MONSTER_DEFEND_NONE;
9968 }
9969
9970 int blockChance = 2; // 10%
9971 bool targetHasRangedWeapon = target.hasRangedWeapon();
9972
9973 if ( isPlayerAlly )
9974 {
9975 if ( stats[monsterAllyIndex] && players[monsterAllyIndex] && players[monsterAllyIndex]->entity )
9976 {
9977 int leaderSkill = std::max(players[monsterAllyIndex]->entity->getCHR(), 0) + stats[monsterAllyIndex]->PROFICIENCIES[PRO_LEADERSHIP];
9978 blockChance += std::max(0, (leaderSkill / 20) * 2); // 0-25% bonus to blockchance.
9979 }
9980 }
9981
9982 if ( myStats.PROFICIENCIES[PRO_SHIELD] > 0 )
9983 {
9984 blockChance += std::min(myStats.PROFICIENCIES[PRO_SHIELD] / 10, 5); // 0-25% bonus to blockchance.
9985 }
9986
9987 bool retreatBonus = false;
9988 if ( backupWithRangedWeapon(myStats, targetDist, hasrangedweapon) )
9989 {
9990 if ( targetHasRangedWeapon )
9991 {
9992 retreatBonus = true;
9993 }
9994 else if ( !targetHasRangedWeapon && targetDist < TOUCHRANGE )
9995 {
9996 retreatBonus = true;
9997 }
9998 }
9999 if ( shouldRetreat(myStats) )
10000 {
10001 retreatBonus = true;
10002 }
10003
10004 if ( retreatBonus )
10005 {
10006 blockChance += 2; // 10% bonus to blockchance.
10007 }
10008
10009 blockChance = std::min(blockChance, 12); // 60% block hard cap.
10010
10011 if ( targetHasRangedWeapon && targetStats.weapon )
10012 {
10013 if ( itemCategory(targetStats.weapon) == MAGICSTAFF )
10014 {
10015 if ( myStats.shield->type != MIRROR_SHIELD )
10016 {
10017 // no point defending!
10018 blockChance = 0;
10019 }
10020 }
10021 }
10022
10023 if ( rand() % 20 < blockChance )
10024 {
10025 if ( isPlayerAlly || myStats.type == HUMAN )
10026 {
10027 if ( rand() % 4 == 0 )
10028 {
10029 return MONSTER_DEFEND_HOLD;
10030 }
10031 else
10032 {
10033 return MONSTER_DEFEND_ALLY;
10034 }
10035 }
10036 else
10037 {
10038 return MONSTER_DEFEND_HOLD;
10039 }
10040 }
10041
10042 return false;
10043 }
10044
monsterConsumeFoodEntity(Entity * food,Stat * myStats)10045 bool Entity::monsterConsumeFoodEntity(Entity* food, Stat* myStats)
10046 {
10047 if ( !myStats )
10048 {
10049 return false;
10050 }
10051
10052 if ( !food )
10053 {
10054 return false;
10055 }
10056
10057 Item* item = newItemFromEntity(food);
10058 if ( !item || item->type == FOOD_TIN )
10059 {
10060 return false;
10061 }
10062
10063 if ( !FollowerMenu.allowedInteractFood(myStats->type) )
10064 {
10065 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_NOUSE);
10066 return false;
10067 }
10068
10069 if ( myStats->HUNGER >= 800 )
10070 {
10071 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_FOOD_FULL);
10072 return false;
10073 }
10074
10075 int buffDuration = item->status * TICKS_PER_SECOND * 2; // (2 - 8 seconds)
10076 bool puking = false;
10077 int pukeChance = 100;
10078 // chance of rottenness
10079 if ( myStats->type == HUMAN )
10080 {
10081 switch ( item->status )
10082 {
10083 case EXCELLENT:
10084 pukeChance = 100;
10085 break;
10086 case SERVICABLE:
10087 pukeChance = 25;
10088 break;
10089 case WORN:
10090 pukeChance = 10;
10091 break;
10092 case DECREPIT:
10093 pukeChance = 4;
10094 break;
10095 default:
10096 pukeChance = 100;
10097 break;
10098 }
10099 }
10100
10101 if ( rand() % pukeChance == 0 && pukeChance < 100 )
10102 {
10103 buffDuration = 0;
10104 this->char_gonnavomit = 40 + rand() % 10;
10105 puking = true;
10106 }
10107 else
10108 {
10109 if ( item->beatitude >= 0 )
10110 {
10111 if ( item->status > WORN )
10112 {
10113 buffDuration -= rand() % ((buffDuration / 2) + 1); // 50-100% duration
10114 }
10115 else
10116 {
10117 buffDuration -= rand() % ((buffDuration / 4) + 1); // 75-100% duration
10118 }
10119 }
10120 else
10121 {
10122 buffDuration = 0;
10123 }
10124 }
10125
10126 if ( puking )
10127 {
10128 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_FOOD_ROTTEN);
10129 }
10130 else if ( item->status <= WORN )
10131 {
10132 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_FOOD_BAD);
10133 }
10134 else
10135 {
10136 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_FOOD_GOOD);
10137 }
10138
10139 int heal = 0;
10140 switch ( item->type )
10141 {
10142 case FOOD_APPLE:
10143 heal = 5 + item->beatitude;
10144 buffDuration = std::min(buffDuration, 2 * TICKS_PER_SECOND);
10145 myStats->HUNGER += 200;
10146 break;
10147 case FOOD_BREAD:
10148 heal = 7 + item->beatitude;
10149 buffDuration = std::min(buffDuration, 6 * TICKS_PER_SECOND);
10150 myStats->HUNGER += 400;
10151 break;
10152 case FOOD_CHEESE:
10153 heal = 3 + item->beatitude;
10154 buffDuration = std::min(buffDuration, 2 * TICKS_PER_SECOND);
10155 myStats->HUNGER += 100;
10156 break;
10157 case FOOD_CREAMPIE:
10158 heal = 7 + item->beatitude;
10159 buffDuration = std::min(buffDuration, 6 * TICKS_PER_SECOND);
10160 myStats->HUNGER += 200;
10161 break;
10162 case FOOD_FISH:
10163 heal = 7 + item->beatitude;
10164 myStats->HUNGER += 500;
10165 break;
10166 case FOOD_MEAT:
10167 heal = 9 + item->beatitude;
10168 myStats->HUNGER += 600;
10169 break;
10170 case FOOD_TOMALLEY:
10171 heal = 7 + item->beatitude;
10172 buffDuration = std::min(buffDuration, 4 * TICKS_PER_SECOND);
10173 myStats->HUNGER += 400;
10174 break;
10175 default:
10176 free(item);
10177 handleNPCInteractDialogue(*myStats, ALLY_EVENT_INTERACT_ITEM_NOUSE);
10178 return false;
10179 break;
10180 }
10181
10182 myStats->HUNGER = std::min(myStats->HUNGER, 1500); // range checking max of 1500 hunger points.
10183
10184 Entity* leader = monsterAllyGetPlayerLeader();
10185 if ( puking )
10186 {
10187 heal -= 5;
10188 if ( rand() % 4 == 0 )
10189 {
10190 // angry at owner.
10191 if ( leader )
10192 {
10193 //monsterAcquireAttackTarget(*leader, MONSTER_STATE_ATTACK);
10194 }
10195 }
10196 }
10197 this->modHP(heal);
10198
10199 if ( !puking && leader && rand() % 2 == 0 )
10200 {
10201 leader->increaseSkill(PRO_LEADERSHIP);
10202 }
10203
10204 if ( buffDuration > 0 )
10205 {
10206 myStats->EFFECTS[EFF_HP_REGEN] = true;
10207 myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = buffDuration;
10208 }
10209
10210 bool foodEntityConsumed = false;
10211
10212 if ( item->count > 1 )
10213 {
10214 --food->skill[13]; // update the entity on ground item count.
10215 }
10216 else
10217 {
10218 food->removeLightField();
10219 list_RemoveNode(food->mynode);
10220 foodEntityConsumed = true;
10221 }
10222 free(item);
10223
10224 // eating sound
10225 playSoundEntity(this, 50 + rand() % 2, 64);
10226
10227 return foodEntityConsumed;
10228 }
10229
monsterAllyGetPlayerLeader()10230 Entity* Entity::monsterAllyGetPlayerLeader()
10231 {
10232 if ( behavior != &actMonster )
10233 {
10234 return nullptr;
10235 }
10236 if ( monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS )
10237 {
10238 if ( players[monsterAllyIndex] )
10239 {
10240 return players[monsterAllyIndex]->entity;
10241 }
10242 }
10243 return nullptr;
10244 }
10245
monsterAllyEquipmentInClass(const Item & item) const10246 bool Entity::monsterAllyEquipmentInClass(const Item& item) const
10247 {
10248 Stat* myStats = getStats();
10249 if ( !myStats )
10250 {
10251 return false;
10252 }
10253
10254 if ( monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS )
10255 {
10256 // player ally.
10257 bool hats = true;
10258 bool helm = true;
10259 bool gloves = false;
10260 bool breastplate = true;
10261 if ( myStats->type == HUMAN || myStats->type == VAMPIRE )
10262 {
10263 gloves = true;
10264 }
10265 if ( myStats->type == GOATMAN || myStats->type == GNOME || myStats->type == INCUBUS
10266 || myStats->type == SUCCUBUS || myStats->type == INSECTOID || myStats->type == VAMPIRE
10267 || myStats->type == KOBOLD )
10268 {
10269 hats = false;
10270 helm = false;
10271 }
10272 if ( myStats->type == VAMPIRE )
10273 {
10274 breastplate = false;
10275 }
10276 if ( myStats->type == AUTOMATON )
10277 {
10278 hats = false;
10279 }
10280
10281 if ( item.interactNPCUid == getUID() )
10282 {
10283 // monster was set to interact with this item, force want it.
10284 switch ( itemCategory(&item) )
10285 {
10286 case ARMOR:
10287 if ( checkEquipType(&item) == TYPE_HAT )
10288 {
10289 if ( myStats->type == KOBOLD && item.type == HAT_HOOD )
10290 {
10291 return true;
10292 }
10293 return hats;
10294 }
10295 else if ( checkEquipType(&item) == TYPE_HELM )
10296 {
10297 return helm;
10298 }
10299 else if ( checkEquipType(&item) == TYPE_BREASTPIECE )
10300 {
10301 return breastplate;
10302 }
10303 else if ( checkEquipType(&item) == TYPE_GLOVES )
10304 {
10305 return gloves;
10306 }
10307 return true;
10308 break;
10309 case WEAPON:
10310 case RING:
10311 case AMULET:
10312 case MAGICSTAFF:
10313 case CLOAK:
10314 return true;
10315 break;
10316 case TOOL:
10317 if ( item.type == TOOL_TORCH || item.type == TOOL_LANTERN || item.type == TOOL_CRYSTALSHARD )
10318 {
10319 return true;
10320 }
10321 else if ( itemTypeIsQuiver(item.type) )
10322 {
10323 return true;
10324 }
10325 else
10326 {
10327 return false;
10328 }
10329 break;
10330 default:
10331 return false;
10332 break;
10333 }
10334 }
10335 else if ( monsterAllyClass == ALLY_CLASS_MIXED )
10336 {
10337 // pick up all default items.
10338 }
10339 else if ( monsterAllyClass == ALLY_CLASS_RANGED )
10340 {
10341 if ( itemTypeIsQuiver(item.type) )
10342 {
10343 return true;
10344 }
10345 switch ( itemCategory(&item) )
10346 {
10347 case WEAPON:
10348 return isRangedWeapon(item);
10349 case ARMOR:
10350 switch ( item.type )
10351 {
10352 case CRYSTAL_BREASTPIECE:
10353 case CRYSTAL_HELM:
10354 case CRYSTAL_SHIELD:
10355 case STEEL_BREASTPIECE:
10356 case STEEL_HELM:
10357 case STEEL_SHIELD:
10358 case IRON_BREASTPIECE:
10359 case IRON_HELM:
10360 case IRON_SHIELD:
10361 return false;
10362 break;
10363 default:
10364 break;
10365 }
10366 if ( checkEquipType(&item) == TYPE_HAT )
10367 {
10368 return hats;
10369 }
10370 else if ( checkEquipType(&item) == TYPE_HELM )
10371 {
10372 return helm;
10373 }
10374 break;
10375 case MAGICSTAFF:
10376 return false;
10377 break;
10378 case THROWN:
10379 if ( myStats->type == GOATMAN )
10380 {
10381 return true;
10382 }
10383 return false;
10384 break;
10385 case POTION:
10386 if ( myStats->type == GOATMAN )
10387 {
10388 switch ( item.type )
10389 {
10390 case POTION_BOOZE:
10391 return true;
10392 case POTION_HEALING:
10393 return true;
10394 default:
10395 return false;
10396 }
10397 }
10398 return false;
10399 break;
10400 case TOOL:
10401 if ( itemTypeIsQuiver(item.type) )
10402 {
10403 return true;
10404 }
10405 break;
10406 default:
10407 return false;
10408 break;
10409 }
10410 }
10411 else if ( monsterAllyClass == ALLY_CLASS_MELEE )
10412 {
10413 switch ( itemCategory(&item) )
10414 {
10415 case WEAPON:
10416 return !isRangedWeapon(item);
10417 case ARMOR:
10418 if ( checkEquipType(&item) == TYPE_HAT )
10419 {
10420 return hats;
10421 }
10422 else if ( checkEquipType(&item) == TYPE_HELM )
10423 {
10424 return helm;
10425 }
10426 return true;
10427 case MAGICSTAFF:
10428 return false;
10429 case THROWN:
10430 return false;
10431 case TOOL:
10432 if ( itemTypeIsQuiver(item.type) )
10433 {
10434 return false;
10435 }
10436 break;
10437 default:
10438 return false;
10439 }
10440 }
10441 }
10442 return false;
10443 }
10444
monsterIsTinkeringCreation()10445 bool Entity::monsterIsTinkeringCreation()
10446 {
10447 int race = this->getMonsterTypeFromSprite();
10448 if ( behavior != &actMonster )
10449 {
10450 return false;
10451 }
10452 if ( race == GYROBOT || race == DUMMYBOT || race == SENTRYBOT || race == SPELLBOT )
10453 {
10454 return true;
10455 }
10456 return false;
10457 }
10458
monsterHandleKnockbackVelocity(real_t monsterFacingTangent,real_t weightratio)10459 void Entity::monsterHandleKnockbackVelocity(real_t monsterFacingTangent, real_t weightratio)
10460 {
10461 // this function makes the monster accelerate to running forwards or 0 movement speed after being knocked back.
10462 // vel_x, vel_y are set on knockback impact and this slowly accumulates speed from the knocked back movement by a factor of monsterKnockbackVelocity.
10463 real_t maxVelX = cos(monsterFacingTangent) * .045 * (monsterGetDexterityForMovement() + 10) * weightratio;
10464 real_t maxVelY = sin(monsterFacingTangent) * .045 * (monsterGetDexterityForMovement() + 10) * weightratio;
10465 bool mobile = ((monsterState == MONSTER_STATE_WAIT) || isMobile()); // if immobile, the intended max speed is 0 (stopped).
10466
10467 if ( maxVelX > 0 )
10468 {
10469 this->vel_x = std::min(this->vel_x + (this->monsterKnockbackVelocity * maxVelX), mobile ? maxVelX : 0.0);
10470 }
10471 else
10472 {
10473 this->vel_x = std::max(this->vel_x + (this->monsterKnockbackVelocity * maxVelX), mobile ? maxVelX : 0.0);
10474 }
10475 if ( maxVelY > 0 )
10476 {
10477 this->vel_y = std::min(this->vel_y + (this->monsterKnockbackVelocity * maxVelY), mobile ? maxVelY : 0.0);
10478 }
10479 else
10480 {
10481 this->vel_y = std::max(this->vel_y + (this->monsterKnockbackVelocity * maxVelY), mobile ? maxVelY : 0.0);
10482 }
10483 this->monsterKnockbackVelocity *= 1.1;
10484 }
10485
monsterGetDexterityForMovement()10486 int Entity::monsterGetDexterityForMovement()
10487 {
10488 int myDex = this->getDEX();
10489 if ( this->monsterAllyGetPlayerLeader() )
10490 {
10491 myDex = std::min(myDex, MONSTER_ALLY_DEXTERITY_SPEED_CAP);
10492 }
10493 if ( this->getStats()->EFFECTS[EFF_DASH] )
10494 {
10495 myDex += 30;
10496 }
10497 return myDex;
10498 }
10499
monsterGenerateQuiverItem(Stat * myStats,bool lesserMonster)10500 void Entity::monsterGenerateQuiverItem(Stat* myStats, bool lesserMonster)
10501 {
10502 if ( !myStats )
10503 {
10504 return;
10505 }
10506 int ammo = 5;
10507 if ( currentlevel >= 15 )
10508 {
10509 ammo += 5 + rand() % 16;
10510 }
10511 else
10512 {
10513 ammo += rand() % 11;
10514 }
10515 switch ( myStats->type )
10516 {
10517 case HUMAN:
10518 switch ( rand() % 5 )
10519 {
10520 case 0:
10521 case 1:
10522 myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10523 break;
10524 case 2:
10525 case 3:
10526 myStats->shield = newItem(QUIVER_SILVER, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10527 break;
10528 case 4:
10529 if ( currentlevel >= 18 )
10530 {
10531 if ( rand() % 2 )
10532 {
10533 myStats->shield = newItem(QUIVER_CRYSTAL, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10534 }
10535 else
10536 {
10537 myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10538 }
10539 }
10540 else
10541 {
10542 myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10543 }
10544 break;
10545 default:
10546 break;
10547 }
10548 break;
10549 case GOBLIN:
10550 switch ( rand() % 5 )
10551 {
10552 case 0:
10553 case 1:
10554 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10555 break;
10556 case 2:
10557 case 3:
10558 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10559 break;
10560 case 4:
10561 if ( currentlevel >= 18 )
10562 {
10563 if ( rand() % 2 )
10564 {
10565 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10566 }
10567 else
10568 {
10569 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10570 }
10571 }
10572 else
10573 {
10574 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10575 }
10576 break;
10577 default:
10578 break;
10579 }
10580 break;
10581 case INSECTOID:
10582 switch ( rand() % 5 )
10583 {
10584 case 0:
10585 case 1:
10586 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10587 break;
10588 case 2:
10589 case 3:
10590 myStats->shield = newItem(QUIVER_HUNTING, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10591 break;
10592 case 4:
10593 if ( currentlevel >= 18 )
10594 {
10595 if ( rand() % 2 )
10596 {
10597 myStats->shield = newItem(QUIVER_CRYSTAL, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10598 }
10599 else
10600 {
10601 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10602 }
10603 }
10604 else
10605 {
10606 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10607 }
10608 break;
10609 default:
10610 break;
10611 }
10612 break;
10613 case KOBOLD:
10614 switch ( rand() % 5 )
10615 {
10616 case 0:
10617 case 1:
10618 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10619 break;
10620 case 2:
10621 case 3:
10622 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10623 break;
10624 case 4:
10625 if ( currentlevel >= 28 )
10626 {
10627 if ( rand() % 2 )
10628 {
10629 myStats->shield = newItem(QUIVER_CRYSTAL, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10630 }
10631 else
10632 {
10633 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10634 }
10635 }
10636 else
10637 {
10638 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10639 }
10640 break;
10641 default:
10642 break;
10643 }
10644 break;
10645 case INCUBUS:
10646 switch ( rand() % 5 )
10647 {
10648 case 0:
10649 case 1:
10650 myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10651 break;
10652 case 2:
10653 case 3:
10654 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10655 break;
10656 case 4:
10657 if ( currentlevel >= 18 )
10658 {
10659 if ( rand() % 2 )
10660 {
10661 myStats->shield = newItem(QUIVER_CRYSTAL, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10662 }
10663 else
10664 {
10665 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10666 }
10667 }
10668 else
10669 {
10670 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10671 }
10672 break;
10673 default:
10674 break;
10675 }
10676 break;
10677 case SKELETON:
10678 switch ( rand() % 5 )
10679 {
10680 case 0:
10681 case 1:
10682 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10683 break;
10684 case 2:
10685 case 3:
10686 myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10687 break;
10688 case 4:
10689 if ( currentlevel >= 18 )
10690 {
10691 if ( rand() % 2 )
10692 {
10693 myStats->shield = newItem(QUIVER_FIRE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10694 }
10695 else
10696 {
10697 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10698 }
10699 }
10700 else
10701 {
10702 myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr);
10703 }
10704 break;
10705 default:
10706 break;
10707 }
10708 break;
10709 case AUTOMATON:
10710 myStats->shield = newItem(QUIVER_PIERCE, SERVICABLE, 0, ammo, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr);
10711 break;
10712 default:
10713 break;
10714 }
10715 }
10716
getMonsterEffectiveDistanceOfRangedWeapon(Item * weapon)10717 int Entity::getMonsterEffectiveDistanceOfRangedWeapon(Item* weapon)
10718 {
10719 if ( !weapon )
10720 {
10721 return 160;
10722 }
10723
10724 if ( getMonsterTypeFromSprite() == SENTRYBOT )
10725 {
10726 return sightranges[SENTRYBOT];
10727 }
10728 else if ( getMonsterTypeFromSprite() == SPELLBOT )
10729 {
10730 return sightranges[SPELLBOT];
10731 }
10732
10733 int distance = 160;
10734 switch ( weapon->type )
10735 {
10736 case SLING:
10737 case CROSSBOW:
10738 case HEAVY_CROSSBOW:
10739 distance = 100;
10740 break;
10741 case LONGBOW:
10742 distance = 200;
10743 break;
10744 default:
10745 break;
10746 }
10747 return distance;
10748 }
10749
isFollowerFreeToPathToPlayer(Stat * myStats)10750 bool Entity::isFollowerFreeToPathToPlayer(Stat* myStats)
10751 {
10752 Entity* currentTarget = uidToEntity(monsterTarget);
10753 if ( currentTarget )
10754 {
10755 if ( monsterState == MONSTER_STATE_ATTACK )
10756 {
10757 // fighting something.
10758 return false;
10759 }
10760 else if ( entityDist(this, currentTarget) < TOUCHRANGE * 2 )
10761 {
10762 // in the vicinity of our target
10763 return false;
10764 }
10765 }
10766 return true;
10767 }