1 /**
2  * @file
3  * @brief Monster level-up code.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "mon-grow.h"
9 
10 #include "message.h"
11 #include "mon-place.h"
12 #include "monster.h"
13 #include "mon-util.h"
14 
15 // Base experience required by a monster to reach HD 1.
16 const int monster_xp_base       = 15;
17 // Experience multiplier to determine the experience needed to gain levels.
18 const int monster_xp_multiplier = 150;
19 const mons_experience_levels mexplevs;
20 
21 // Monster growing-up sequences, for Beogh orcs. You can specify a chance to
22 // indicate that the monster only has a chance of changing type, otherwise the
23 // monster will grow up when it reaches the HD of the target form.
24 //
25 // No special cases are in place: make sure source and target forms are similar.
26 // If the target form requires special handling of some sort, add the handling
27 // to level_up_change().
28 
29 static const monster_level_up mon_grow[] =
30 {
31     monster_level_up(MONS_ORC, MONS_ORC_WARRIOR),
32     monster_level_up(MONS_ORC_WARRIOR, MONS_ORC_KNIGHT),
33     monster_level_up(MONS_ORC_KNIGHT, MONS_ORC_WARLORD),
34     monster_level_up(MONS_ORC_PRIEST, MONS_ORC_HIGH_PRIEST),
35     monster_level_up(MONS_ORC_WIZARD, MONS_ORC_SORCERER),
36 };
37 
mons_experience_levels()38 mons_experience_levels::mons_experience_levels()
39 {
40     int experience = monster_xp_base;
41     for (int i = 1; i <= MAX_MONS_HD; ++i)
42     {
43         mexp[i] = experience;
44 
45         int delta =
46             (monster_xp_base + experience) * 2 * monster_xp_multiplier
47             / 500;
48         delta =
49             min(max(delta, monster_xp_base * monster_xp_multiplier / 100),
50                 40000);
51         experience += delta;
52     }
53 }
54 
_monster_level_up_target(monster_type type,int hit_dice)55 static const monster_level_up *_monster_level_up_target(monster_type type,
56                                                         int hit_dice)
57 {
58     for (const monster_level_up &mlup : mon_grow)
59     {
60         if (mlup.before == type)
61         {
62             const monsterentry *me = get_monster_data(mlup.after);
63             if (me->HD == hit_dice && x_chance_in_y(mlup.chance, 1000))
64                 return &mlup;
65         }
66     }
67     return nullptr;
68 }
69 
upgrade_type(monster_type after,bool adjust_hd,bool adjust_hp)70 void monster::upgrade_type(monster_type after, bool adjust_hd,
71                             bool adjust_hp)
72 {
73     // Ta-da!
74     type   = after;
75 
76     // Initialise a dummy monster to save work.
77     monster dummy;
78     dummy.type         = after;
79     dummy.base_monster = base_monster;
80     dummy.number       = number;
81     define_monster(dummy); // assumes after is not a zombie
82 
83     colour = dummy.colour;
84     speed  = dummy.speed;
85     spells = dummy.spells;
86     calc_speed();
87 
88     if (adjust_hd)
89         set_hit_dice(max(get_experience_level(), dummy.get_hit_dice()));
90 
91     if (adjust_hp)
92     {
93         const int minhp = dummy.max_hit_points;
94         if (max_hit_points < minhp)
95         {
96             hit_points    += minhp - max_hit_points;
97             max_hit_points = minhp;
98             hit_points     = min(hit_points, max_hit_points);
99         }
100     }
101 }
102 
level_up_change()103 bool monster::level_up_change()
104 {
105     const monster_level_up *lup =
106         _monster_level_up_target(type, get_experience_level());
107     if (lup)
108     {
109         upgrade_type(lup->after, false, lup->adjust_hp);
110         return true;
111     }
112     return false;
113 }
114 
level_up()115 bool monster::level_up()
116 {
117     if (get_experience_level() >= MAX_MONS_HD)
118         return false;
119 
120     set_hit_dice(get_experience_level() + 1);
121 
122     // A little maxhp boost.
123     if (max_hit_points < 1000)
124     {
125         int hpboost =
126             (get_experience_level() > 3? max_hit_points / 8 : max_hit_points / 4)
127             + random2(5);
128 
129         // Not less than 3 hp, not more than 25.
130         hpboost = min(max(hpboost, 3), 25);
131 
132         dprf("%s: HD: %d, maxhp: %d, boost: %d",
133              name(DESC_PLAIN).c_str(), get_experience_level(),
134              max_hit_points, hpboost);
135 
136         max_hit_points += hpboost;
137         hit_points     += hpboost;
138         hit_points      = min(hit_points, max_hit_points);
139     }
140 
141     level_up_change();
142 
143     return true;
144 }
145 
init_experience()146 void monster::init_experience()
147 {
148     if (experience || !alive())
149         return;
150     set_hit_dice(max(get_experience_level(), 1));
151     experience = mexplevs[min(get_experience_level(), MAX_MONS_HD)];
152 }
153 
gain_exp(int exp,int max_levels_to_gain)154 bool monster::gain_exp(int exp, int max_levels_to_gain)
155 {
156     if (!alive())
157         return false;
158 
159     init_experience();
160     if (get_experience_level() >= MAX_MONS_HD)
161         return false;
162 
163     // Only natural monsters can level-up.
164     if (!(holiness() & MH_NATURAL))
165         return false;
166 
167     // Only monsters that you can gain XP from can level-up.
168     if (!mons_class_gives_xp(type) || is_summoned())
169         return false;
170 
171     // Avoid wrap-around.
172     if (experience + exp < experience)
173         return false;
174 
175     experience += exp;
176 
177     const monster mcopy(*this);
178     int levels_gained = 0;
179     // Monsters can normally gain a maximum of two levels from one kill.
180     while (get_experience_level() < MAX_MONS_HD
181            && experience >= mexplevs[get_experience_level() + 1]
182            && level_up()
183            && ++levels_gained < max_levels_to_gain);
184 
185     if (levels_gained)
186     {
187         if (mons_intel(*this) >= I_HUMAN)
188             simple_monster_message(mcopy, " looks more experienced.");
189         else
190             simple_monster_message(mcopy, " looks stronger.");
191     }
192 
193     if (get_experience_level() < MAX_MONS_HD
194         && experience >= mexplevs[get_experience_level() + 1])
195     {
196         experience = (mexplevs[get_experience_level()]
197                       + mexplevs[get_experience_level() + 1]) / 2;
198     }
199 
200     return levels_gained > 0;
201 }
202