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