1 /**
2 * @file spells.cpp
3 *
4 * Implementation of functionality for casting player spells.
5 */
6 #include "all.h"
7
8 DEVILUTION_BEGIN_NAMESPACE
9
GetManaAmount(int id,int sn)10 int GetManaAmount(int id, int sn)
11 {
12 int ma; // mana amount
13
14 // mana adjust
15 int adj = 0;
16
17 // spell level
18 int sl = plr[id]._pSplLvl[sn] + plr[id]._pISplLvlAdd - 1;
19
20 if (sl < 0) {
21 sl = 0;
22 }
23
24 if (sl > 0) {
25 adj = sl * spelldata[sn].sManaAdj;
26 }
27 if (sn == SPL_FIREBOLT) {
28 adj >>= 1;
29 }
30 if (sn == SPL_RESURRECT && sl > 0) {
31 adj = sl * (spelldata[SPL_RESURRECT].sManaCost / 8);
32 }
33
34 if (sn == SPL_HEAL || sn == SPL_HEALOTHER) {
35 ma = (spelldata[SPL_HEAL].sManaCost + 2 * plr[id]._pLevel - adj);
36 } else if (spelldata[sn].sManaCost == 255) {
37 ma = ((BYTE)plr[id]._pMaxManaBase - adj);
38 } else {
39 ma = (spelldata[sn].sManaCost - adj);
40 }
41
42 if (ma < 0)
43 ma = 0;
44 ma <<= 6;
45
46 if (plr[id]._pClass == PC_SORCERER) {
47 ma >>= 1;
48 } else if (plr[id]._pClass == PC_ROGUE || plr[id]._pClass == PC_MONK || plr[id]._pClass == PC_BARD) {
49 ma -= ma >> 2;
50 }
51
52 if (spelldata[sn].sMinMana > ma >> 6) {
53 ma = spelldata[sn].sMinMana << 6;
54 }
55
56 return ma;
57 }
58
UseMana(int id,int sn)59 void UseMana(int id, int sn)
60 {
61 int ma; // mana cost
62
63 if (id == myplr) {
64 switch (plr[id]._pSplType) {
65 case RSPLTYPE_SKILL:
66 case RSPLTYPE_INVALID:
67 break;
68 case RSPLTYPE_SCROLL:
69 RemoveScroll(id);
70 break;
71 case RSPLTYPE_CHARGES:
72 UseStaffCharge(id);
73 break;
74 case RSPLTYPE_SPELL:
75 #ifdef _DEBUG
76 if (!debug_mode_key_inverted_v) {
77 #endif
78 ma = GetManaAmount(id, sn);
79 plr[id]._pMana -= ma;
80 plr[id]._pManaBase -= ma;
81 drawmanaflag = TRUE;
82 #ifdef _DEBUG
83 }
84 #endif
85 break;
86 }
87 }
88 }
89
90 /**
91 * @brief Gets a value that represents the specified spellID in 64bit bitmask format.
92 * For example:
93 * - spell ID 1: 0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0001
94 * - spell ID 43: 0000.0000.0000.0000.0000.0100.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000
95 * @param spellId The id of the spell to get a bitmask for.
96 * @return A 64bit bitmask representation for the specified spell.
97 */
GetSpellBitmask(int spellId)98 Uint64 GetSpellBitmask(int spellId)
99 {
100 return 1ULL << (spellId - 1);
101 }
102
103 /**
104 * @brief Gets a value indicating whether the player's current readied spell is a valid spell. Readied spells can be
105 * invalidaded in a few scenarios where the spell comes from items, for example (like dropping the only scroll that
106 * provided the spell).
107 * @param player The player whose readied spell is to be checked.
108 * @return 'true' when the readied spell is currently valid, and 'false' otherwise.
109 */
IsReadiedSpellValid(const PlayerStruct & player)110 bool IsReadiedSpellValid(const PlayerStruct &player)
111 {
112 switch (player._pRSplType) {
113 case RSPLTYPE_SKILL:
114 case RSPLTYPE_SPELL:
115 case RSPLTYPE_INVALID:
116 return true;
117
118 case RSPLTYPE_CHARGES:
119 return player._pISpells & GetSpellBitmask(player._pRSpell);
120
121 case RSPLTYPE_SCROLL:
122 return player._pScrlSpells & GetSpellBitmask(player._pRSpell);
123
124 default:
125 return false;
126 }
127 }
128
129 /**
130 * @brief Clears the current player's readied spell selection.
131 * @note Will force a UI redraw in case the values actually change, so that the new spell reflects on the bottom panel.
132 * @param player The player whose readied spell is to be cleared.
133 */
ClearReadiedSpell(PlayerStruct & player)134 void ClearReadiedSpell(PlayerStruct &player)
135 {
136 if (player._pRSpell != SPL_INVALID) {
137 player._pRSpell = SPL_INVALID;
138 force_redraw = 255;
139 }
140
141 if (player._pRSplType != RSPLTYPE_INVALID) {
142 player._pRSplType = RSPLTYPE_INVALID;
143 force_redraw = 255;
144 }
145 }
146
147 /**
148 * @brief Ensures the player's current readied spell is a valid selection for the character. If the current selection is
149 * incompatible with the player's items and spell (for example, if the player does not currently have access to the spell),
150 * the selection is cleared.
151 * @note Will force a UI redraw in case the values actually change, so that the new spell reflects on the bottom panel.
152 * @param player The player whose readied spell is to be checked.
153 */
EnsureValidReadiedSpell(PlayerStruct & player)154 void EnsureValidReadiedSpell(PlayerStruct &player)
155 {
156 if (!IsReadiedSpellValid(player)) {
157 ClearReadiedSpell(player);
158 }
159 }
160
CheckSpell(int id,int sn,char st,BOOL manaonly)161 BOOL CheckSpell(int id, int sn, char st, BOOL manaonly)
162 {
163 BOOL result;
164
165 #ifdef _DEBUG
166 if (debug_mode_key_inverted_v)
167 return TRUE;
168 #endif
169
170 result = TRUE;
171 if (!manaonly && pcurs != CURSOR_HAND) {
172 result = FALSE;
173 } else {
174 if (st != RSPLTYPE_SKILL) {
175 if (GetSpellLevel(id, sn) <= 0) {
176 result = FALSE;
177 } else {
178 result = plr[id]._pMana >= GetManaAmount(id, sn);
179 }
180 }
181 }
182
183 return result;
184 }
185
CastSpell(int id,int spl,int sx,int sy,int dx,int dy,int spllvl)186 void CastSpell(int id, int spl, int sx, int sy, int dx, int dy, int spllvl)
187 {
188 int dir = plr[id]._pdir;
189 if (spl == SPL_FIREWALL || spl == SPL_LIGHTWALL) {
190 dir = plr[id]._pVar3;
191 }
192
193 for (int i = 0; spelldata[spl].sMissiles[i] != 0 && i < 3; i++) {
194 AddMissile(sx, sy, dx, dy, dir, spelldata[spl].sMissiles[i], TARGET_MONSTERS, id, 0, spllvl);
195 }
196
197 if (spl == SPL_TOWN) {
198 UseMana(id, SPL_TOWN);
199 } else if (spl == SPL_CBOLT) {
200 UseMana(id, SPL_CBOLT);
201
202 for (int i = (spllvl >> 1) + 3; i > 0; i--) {
203 AddMissile(sx, sy, dx, dy, dir, MIS_CBOLT, TARGET_MONSTERS, id, 0, spllvl);
204 }
205 }
206 }
207
PlacePlayer(int pnum)208 static void PlacePlayer(int pnum)
209 {
210 int nx, ny, max, min, x, y;
211 DWORD i;
212 BOOL done;
213
214 if (plr[pnum].plrlevel == currlevel) {
215 for (i = 0; i < 8; i++) {
216 nx = plr[pnum]._px + plrxoff2[i];
217 ny = plr[pnum]._py + plryoff2[i];
218
219 if (PosOkPlayer(pnum, nx, ny)) {
220 break;
221 }
222 }
223
224 if (!PosOkPlayer(pnum, nx, ny)) {
225 done = FALSE;
226
227 for (max = 1, min = -1; min > -50 && !done; max++, min--) {
228 for (y = min; y <= max && !done; y++) {
229 ny = plr[pnum]._py + y;
230
231 for (x = min; x <= max && !done; x++) {
232 nx = plr[pnum]._px + x;
233
234 if (PosOkPlayer(pnum, nx, ny)) {
235 done = TRUE;
236 }
237 }
238 }
239 }
240 }
241
242 plr[pnum]._px = nx;
243 plr[pnum]._py = ny;
244
245 dPlayer[nx][ny] = pnum + 1;
246
247 if (pnum == myplr) {
248 ViewX = nx;
249 ViewY = ny;
250 }
251 }
252 }
253
254 /**
255 * @param pnum player index
256 * @param rid target player index
257 */
DoResurrect(int pnum,int rid)258 void DoResurrect(int pnum, int rid)
259 {
260 int hp;
261
262 if ((char)rid != -1) {
263 AddMissile(plr[rid]._px, plr[rid]._py, plr[rid]._px, plr[rid]._py, 0, MIS_RESURRECTBEAM, TARGET_MONSTERS, pnum, 0, 0);
264 }
265
266 if (pnum == myplr) {
267 NewCursor(CURSOR_HAND);
268 }
269
270 if ((char)rid != -1 && plr[rid]._pHitPoints == 0) {
271 if (rid == myplr) {
272 deathflag = FALSE;
273 gamemenu_off();
274 drawhpflag = TRUE;
275 drawmanaflag = TRUE;
276 }
277
278 ClrPlrPath(rid);
279 plr[rid].destAction = ACTION_NONE;
280 plr[rid]._pInvincible = FALSE;
281 PlacePlayer(rid);
282
283 hp = 10 << 6;
284 if (plr[rid]._pMaxHPBase < (10 << 6)) {
285 hp = plr[rid]._pMaxHPBase;
286 }
287 SetPlayerHitPoints(rid, hp);
288
289 plr[rid]._pHPBase = plr[rid]._pHitPoints + (plr[rid]._pMaxHPBase - plr[rid]._pMaxHP);
290 plr[rid]._pMana = 0;
291 plr[rid]._pManaBase = plr[rid]._pMana + (plr[rid]._pMaxManaBase - plr[rid]._pMaxMana);
292
293 CalcPlrInv(rid, TRUE);
294
295 if (plr[rid].plrlevel == currlevel) {
296 StartStand(rid, plr[rid]._pdir);
297 } else {
298 plr[rid]._pmode = PM_STAND;
299 }
300 }
301 }
302
DoHealOther(int pnum,int rid)303 void DoHealOther(int pnum, int rid)
304 {
305 int i, j, hp;
306
307 if (pnum == myplr) {
308 NewCursor(CURSOR_HAND);
309 }
310
311 if ((char)rid != -1 && (plr[rid]._pHitPoints >> 6) > 0) {
312 hp = (random_(57, 10) + 1) << 6;
313
314 for (i = 0; i < plr[pnum]._pLevel; i++) {
315 hp += (random_(57, 4) + 1) << 6;
316 }
317
318 for (j = 0; j < GetSpellLevel(pnum, SPL_HEALOTHER); ++j) {
319 hp += (random_(57, 6) + 1) << 6;
320 }
321
322 if (plr[pnum]._pClass == PC_WARRIOR || plr[pnum]._pClass == PC_BARBARIAN) {
323 hp <<= 1;
324 } else if (plr[pnum]._pClass == PC_ROGUE || plr[pnum]._pClass == PC_BARD) {
325 hp += hp >> 1;
326 } else if (plr[pnum]._pClass == PC_MONK) {
327 hp *= 3;
328 }
329
330 plr[rid]._pHitPoints += hp;
331
332 if (plr[rid]._pHitPoints > plr[rid]._pMaxHP) {
333 plr[rid]._pHitPoints = plr[rid]._pMaxHP;
334 }
335
336 plr[rid]._pHPBase += hp;
337
338 if (plr[rid]._pHPBase > plr[rid]._pMaxHPBase) {
339 plr[rid]._pHPBase = plr[rid]._pMaxHPBase;
340 }
341
342 drawhpflag = TRUE;
343 }
344 }
345
GetSpellBookLevel(spell_id s)346 int GetSpellBookLevel(spell_id s)
347 {
348 if (gbIsSpawn) {
349 switch (s) {
350 case SPL_STONE:
351 case SPL_GUARDIAN:
352 case SPL_GOLEM:
353 case SPL_FLARE:
354 case SPL_BONESPIRIT:
355 return -1;
356 default:
357 break;
358 }
359 }
360
361 if (!gbIsHellfire) {
362 switch (s) {
363 case SPL_NOVA:
364 case SPL_APOCA:
365 return -1;
366 default:
367 if (s > SPL_LASTDIABLO)
368 return -1;
369 break;
370 }
371 }
372
373 if (gbIsHellfire) {
374 switch (s) {
375 case SPL_ELEMENT:
376 return -1;
377 default:
378 break;
379 }
380 }
381
382 return spelldata[s].sBookLvl;
383 }
384
GetSpellStaffLevel(spell_id s)385 int GetSpellStaffLevel(spell_id s)
386 {
387 if (gbIsSpawn) {
388 switch (s) {
389 case SPL_STONE:
390 case SPL_GUARDIAN:
391 case SPL_GOLEM:
392 case SPL_APOCA:
393 case SPL_FLARE:
394 case SPL_BONESPIRIT:
395 return -1;
396 default:
397 break;
398 }
399 }
400
401 if (!gbIsHellfire && s > SPL_LASTDIABLO)
402 return -1;
403
404 if (gbIsHellfire) {
405 switch (s) {
406 case SPL_ELEMENT:
407 return -1;
408 default:
409 break;
410 }
411 }
412
413 return spelldata[s].sStaffLvl;
414 }
415
416 DEVILUTION_END_NAMESPACE
417