1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: actmagic.cpp
5 	Desc: behavior function for magic balls
6 
7 	Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 	See LICENSE for details.
9 
10 -------------------------------------------------------------------------------*/
11 
12 #include "../main.hpp"
13 #include "../game.hpp"
14 #include "../stat.hpp"
15 #include "../entity.hpp"
16 #include "../interface/interface.hpp"
17 #include "../sound.hpp"
18 #include "../items.hpp"
19 #include "../monster.hpp"
20 #include "../net.hpp"
21 #include "../collision.hpp"
22 #include "../paths.hpp"
23 #include "../player.hpp"
24 #include "magic.hpp"
25 #include "../scores.hpp"
26 
actMagiclightBall(Entity * my)27 void actMagiclightBall(Entity* my)
28 {
29 	Entity* caster = NULL;
30 	if (!my)
31 	{
32 		return;
33 	}
34 
35 	my->skill[2] = -10; // so the client sets the behavior of this entity
36 
37 	if (clientnum != 0 && multiplayer == CLIENT)
38 	{
39 		my->removeLightField();
40 
41 		//Light up the area.
42 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
43 
44 
45 		if ( flickerLights )
46 		{
47 			//Magic light ball will never flicker if this setting is disabled.
48 			lightball_flicker++;
49 		}
50 
51 		if (lightball_flicker > 5)
52 		{
53 			lightball_lighting = (lightball_lighting == 1) + 1;
54 
55 			if (lightball_lighting == 1)
56 			{
57 				my->removeLightField();
58 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
59 			}
60 			else
61 			{
62 				my->removeLightField();
63 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 174);
64 			}
65 			lightball_flicker = 0;
66 		}
67 
68 		lightball_timer--;
69 		return;
70 	}
71 
72 	my->yaw += .01;
73 	if ( my->yaw >= PI * 2 )
74 	{
75 		my->yaw -= PI * 2;
76 	}
77 
78 	/*if (!my->parent) { //This means that it doesn't have a caster. In other words, magic light staff.
79 		return;
80 	})*/
81 
82 	//list_t *path = NULL;
83 	pathnode_t* pathnode = NULL;
84 
85 	//TODO: Follow player around (at a distance -- and delay before starting to follow).
86 	//TODO: Circle around player's head if they stand still for a little bit. Continue circling even if the player walks away -- until the player is far enough to trigger move (or if the player moved for a bit and then stopped, then update position).
87 	//TODO: Don't forget to cast flickering light all around it.
88 	//TODO: Move out of creatures' way if they collide.
89 
90 	/*if (!my->children) {
91 		list_RemoveNode(my->mynode); //Delete the light spell.
92 		return;
93 	}*/
94 	if (!my->children.first)
95 	{
96 		list_RemoveNode(my->mynode); //Delete the light spell.C
97 		return;
98 	}
99 	node_t* node = NULL;
100 
101 	spell_t* spell = NULL;
102 	node = my->children.first;
103 	spell = (spell_t*)node->element;
104 	if (!spell)
105 	{
106 		list_RemoveNode(my->mynode);
107 		return; //We need the source spell!
108 	}
109 
110 	caster = uidToEntity(spell->caster);
111 	if (caster)
112 	{
113 		Stat* stats = caster->getStats();
114 		if (stats)
115 		{
116 			if (stats->HP <= 0)
117 			{
118 				my->removeLightField();
119 				list_RemoveNode(my->mynode); //Delete the light spell.
120 				return;
121 			}
122 		}
123 	}
124 	else if (spell->caster >= 1)     //So no caster...but uidToEntity returns NULL if entity is already dead, right? And if the uid is supposed to point to an entity, but it doesn't...it means the caster has died.
125 	{
126 		my->removeLightField();
127 		list_RemoveNode(my->mynode);
128 		return;
129 	}
130 
131 	// if the spell has been unsustained, remove it
132 	if ( !spell->magicstaff && !spell->sustain )
133 	{
134 		int i = 0;
135 		int player = -1;
136 		for (i = 0; i < MAXPLAYERS; ++i)
137 		{
138 			if (players[i]->entity == caster)
139 			{
140 				player = i;
141 			}
142 		}
143 		if (player > 0 && multiplayer == SERVER)
144 		{
145 			strcpy( (char*)net_packet->data, "UNCH");
146 			net_packet->data[4] = player;
147 			SDLNet_Write32(spell->ID, &net_packet->data[5]);
148 			net_packet->address.host = net_clients[player - 1].host;
149 			net_packet->address.port = net_clients[player - 1].port;
150 			net_packet->len = 9;
151 			sendPacketSafe(net_sock, -1, net_packet, player - 1);
152 		}
153 		my->removeLightField();
154 		list_RemoveNode(my->mynode);
155 		return;
156 	}
157 
158 	if (magic_init)
159 	{
160 		my->removeLightField();
161 
162 		if (lightball_timer <= 0)
163 		{
164 			if ( spell->sustain )
165 			{
166 				//Attempt to sustain the magic light.
167 				if (caster)
168 				{
169 					//Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false).
170 					bool deducted = caster->safeConsumeMP(1); //Consume 1 mana every duration / mana seconds
171 					if (deducted)
172 					{
173 						lightball_timer = spell->channel_duration / getCostOfSpell(spell);
174 					}
175 					else
176 					{
177 						int i = 0;
178 						int player = -1;
179 						for (i = 0; i < MAXPLAYERS; ++i)
180 						{
181 							if (players[i]->entity == caster)
182 							{
183 								player = i;
184 							}
185 						}
186 						if (player > 0 && multiplayer == SERVER)
187 						{
188 							strcpy( (char*)net_packet->data, "UNCH");
189 							net_packet->data[4] = player;
190 							SDLNet_Write32(spell->ID, &net_packet->data[5]);
191 							net_packet->address.host = net_clients[player - 1].host;
192 							net_packet->address.port = net_clients[player - 1].port;
193 							net_packet->len = 9;
194 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
195 						}
196 						my->removeLightField();
197 						list_RemoveNode(my->mynode);
198 						return;
199 					}
200 				}
201 			}
202 		}
203 
204 		//TODO: Make hovering always smooth. For example, when transitioning from ceiling to no ceiling, don't just have it jump to a new position. Figure out  away to transition between the two.
205 		if (lightball_hoverangle > 360)
206 		{
207 			lightball_hoverangle = 0;
208 		}
209 		if (map.tiles[(int)((my->y / 16) * MAPLAYERS + (my->x / 16) * MAPLAYERS * map.height)])
210 		{
211 			//Ceiling.
212 			my->z = lightball_hover_basez + ((lightball_hover_basez + LIGHTBALL_HOVER_HIGHPEAK + lightball_hover_basez + LIGHTBALL_HOVER_LOWPEAK) / 2) * sin(lightball_hoverangle * (12.568f / 360.0f)) * 0.1f;
213 		}
214 		else
215 		{
216 			//No ceiling. //TODO: Float higher?
217 			//my->z = lightball_hover_basez - 4 + ((lightball_hover_basez + LIGHTBALL_HOVER_HIGHPEAK - 4 + lightball_hover_basez + LIGHTBALL_HOVER_LOWPEAK - 4) / 2) * sin(lightball_hoverangle * (12.568f / 360.0f)) * 0.1f;
218 			my->z = lightball_hover_basez + ((lightball_hover_basez + LIGHTBALL_HOVER_HIGHPEAK + lightball_hover_basez + LIGHTBALL_HOVER_LOWPEAK) / 2) * sin(lightball_hoverangle * (12.568f / 360.0f)) * 0.1f;
219 		}
220 		lightball_hoverangle += 1;
221 
222 		//Lightball moving.
223 		//messagePlayer(0, "*");
224 		Entity* parent = uidToEntity(my->parent);
225 		if ( !parent )
226 		{
227 			return;
228 		}
229 		double distance = sqrt(pow(my->x - parent->x, 2) + pow(my->y - parent->y, 2));
230 		if ( distance > MAGICLIGHT_BALL_FOLLOW_DISTANCE || my->path)
231 		{
232 			lightball_player_lastmove_timer = 0;
233 			if (lightball_movement_timer > 0)
234 			{
235 				lightball_movement_timer--;
236 			}
237 			else
238 			{
239 				//messagePlayer(0, "****Moving.");
240 				double tangent = atan2(parent->y - my->y, parent->x - my->x);
241 				lineTraceTarget(my, my->x, my->y, tangent, 1024, IGNORE_ENTITIES, false, parent);
242 				if ( !hit.entity || hit.entity == parent )   //Line of sight to caster?
243 				{
244 					if (my->path != NULL)
245 					{
246 						list_FreeAll(my->path);
247 						my->path = NULL;
248 					}
249 					//double tangent = atan2(parent->y - my->y, parent->x - my->x);
250 					my->vel_x = cos(tangent) * ((distance - MAGICLIGHT_BALL_FOLLOW_DISTANCE) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
251 					my->vel_y = sin(tangent) * ((distance - MAGICLIGHT_BALL_FOLLOW_DISTANCE) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
252 					my->x += (my->vel_x < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_x : MAGIC_LIGHTBALL_SPEEDLIMIT;
253 					my->y += (my->vel_y < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_y : MAGIC_LIGHTBALL_SPEEDLIMIT;
254 					//} else if (!map.tiles[(int)(OBSTACLELAYER + (my->y / 16) * MAPLAYERS + (my->x / 16) * MAPLAYERS * map.height)]) { //If not in wall..
255 				}
256 				else
257 				{
258 					//messagePlayer(0, "******Pathfinding.");
259 					//Caster is not in line of sight. Calculate a move path.
260 					/*if (my->children->first != NULL) {
261 						list_RemoveNode(my->children->first);
262 						my->children->first = NULL;
263 					}*/
264 					if (!my->path)
265 					{
266 						//messagePlayer(0, "[Light ball] Generating path.");
267 						list_t* path = generatePath((int)floor(my->x / 16), (int)floor(my->y / 16), (int)floor(parent->x / 16), (int)floor(parent->y / 16), my, parent);
268 						if ( path != NULL )
269 						{
270 							my->path = path;
271 						}
272 						else
273 						{
274 							//messagePlayer(0, "[Light ball] Failed to generate path (%s line %d).", __FILE__, __LINE__);
275 						}
276 					}
277 
278 					if (my->path)
279 					{
280 						double total_distance = 0; //Calculate the total distance to the player to get the right move speed.
281 						double prevx = my->x;
282 						double prevy = my->y;
283 						if (my->path != NULL)
284 						{
285 							for (node = my->path->first; node != NULL; node = node->next)
286 							{
287 								if (node->element)
288 								{
289 									pathnode = (pathnode_t*)node->element;
290 									//total_distance += sqrt(pow(pathnode->y - prevy, 2) + pow(pathnode->x - prevx, 2) );
291 									total_distance += sqrt(pow(prevx - pathnode->x, 2) + pow(prevy - pathnode->y, 2) );
292 									prevx = pathnode->x;
293 									prevy = pathnode->y;
294 								}
295 							}
296 						}
297 						else if (my->path)     //If the path has been traversed, reset it.
298 						{
299 							list_FreeAll(my->path);
300 							my->path = NULL;
301 						}
302 						total_distance -= MAGICLIGHT_BALL_FOLLOW_DISTANCE;
303 
304 						if (my->path != NULL)
305 						{
306 							if (my->path->first != NULL)
307 							{
308 								pathnode = (pathnode_t*)my->path->first->element;
309 								//double distance = sqrt(pow(pathnode->y * 16 + 8 - my->y, 2) + pow(pathnode->x * 16 + 8 - my->x, 2) );
310 								//double distance = sqrt(pow((my->y) - ((pathnode->y + 8) * 16), 2) + pow((my->x) - ((pathnode->x + 8) * 16), 2));
311 								double distance = sqrt(pow(((pathnode->y * 16) + 8) - (my->y), 2) + pow(((pathnode->x * 16) + 8) - (my->x), 2));
312 								if (distance <= 4)
313 								{
314 									list_RemoveNode(pathnode->node); //TODO: Make sure it doesn't get stuck here. Maybe remove the node only if it's the last one?
315 									if (!my->path->first)
316 									{
317 										list_FreeAll(my->path);
318 										my->path = NULL;
319 									}
320 								}
321 								else
322 								{
323 									double target_tangent = atan2((pathnode->y * 16) + 8 - my->y, (pathnode->x * 16) + 8 - my->x);
324 									if (target_tangent > my->yaw)   //TODO: Do this yaw thing for all movement.
325 									{
326 										my->yaw = (target_tangent >= my->yaw + MAGIC_LIGHTBALL_TURNSPEED) ? my->yaw + MAGIC_LIGHTBALL_TURNSPEED : target_tangent;
327 									}
328 									else if (target_tangent < my->yaw)
329 									{
330 										my->yaw = (target_tangent <= my->yaw - MAGIC_LIGHTBALL_TURNSPEED) ? my->yaw - MAGIC_LIGHTBALL_TURNSPEED : target_tangent;
331 									}
332 									my->vel_x = cos(my->yaw) * (total_distance / MAGICLIGHTBALL_DIVIDE_CONSTANT);
333 									my->vel_y = sin(my->yaw) * (total_distance / MAGICLIGHTBALL_DIVIDE_CONSTANT);
334 									my->x += (my->vel_x < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_x : MAGIC_LIGHTBALL_SPEEDLIMIT;
335 									my->y += (my->vel_y < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_y : MAGIC_LIGHTBALL_SPEEDLIMIT;
336 								}
337 							}
338 						} //else assertion error, hehehe
339 					}
340 					else     //Path failed to generate. Fallback on moving straight to the player.
341 					{
342 						//messagePlayer(0, "**************NO PATH WHEN EXPECTED PATH.");
343 						my->vel_x = cos(tangent) * ((distance) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
344 						my->vel_y = sin(tangent) * ((distance) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
345 						my->x += (my->vel_x < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_x : MAGIC_LIGHTBALL_SPEEDLIMIT;
346 						my->y += (my->vel_y < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_y : MAGIC_LIGHTBALL_SPEEDLIMIT;
347 					}
348 				} /*else {
349 					//In a wall. Get out of it.
350 					double tangent = atan2(parent->y - my->y, parent->x - my->x);
351 					my->vel_x = cos(tangent) * ((distance) / 100);
352 					my->vel_y = sin(tangent) * ((distance) / 100);
353 					my->x += my->vel_x;
354 					my->y += my->vel_y;
355 				}*/
356 			}
357 		}
358 		else
359 		{
360 			lightball_movement_timer = LIGHTBALL_MOVE_DELAY;
361 			/*if (lightball_player_lastmove_timer < LIGHTBALL_CIRCLE_TIME) {
362 				lightball_player_lastmove_timer++;
363 			} else {
364 				//TODO: Orbit the player. Maybe.
365 				my->x = parent->x + (lightball_orbit_length * cos(lightball_orbit_angle));
366 				my->y = parent->y + (lightball_orbit_length * sin(lightball_orbit_angle));
367 
368 				lightball_orbit_angle++;
369 				if (lightball_orbit_angle > 360) {
370 					lightball_orbit_angle = 0;
371 				}
372 			}*/
373 			if (my->path != NULL)
374 			{
375 				list_FreeAll(my->path);
376 				my->path = NULL;
377 			}
378 			if (map.tiles[(int)(OBSTACLELAYER + (my->y / 16) * MAPLAYERS + (my->x / 16) * MAPLAYERS * map.height)])   //If the ball has come to rest in a wall, move its butt.
379 			{
380 				double tangent = atan2(parent->y - my->y, parent->x - my->x);
381 				my->vel_x = cos(tangent) * ((distance) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
382 				my->vel_y = sin(tangent) * ((distance) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
383 				my->x += my->vel_x;
384 				my->y += my->vel_y;
385 			}
386 		}
387 
388 		//Light up the area.
389 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
390 
391 		if ( flickerLights )
392 		{
393 			//Magic light ball  will never flicker if this setting is disabled.
394 			lightball_flicker++;
395 		}
396 
397 		if (lightball_flicker > 5)
398 		{
399 			lightball_lighting = (lightball_lighting == 1) + 1;
400 
401 			if (lightball_lighting == 1)
402 			{
403 				my->removeLightField();
404 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
405 			}
406 			else
407 			{
408 				my->removeLightField();
409 				my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 174);
410 			}
411 			lightball_flicker = 0;
412 		}
413 
414 		lightball_timer--;
415 	}
416 	else
417 	{
418 		//Init the lightball. That is, shoot out from the player.
419 
420 		//Move out from the player.
421 		my->vel_x = cos(my->yaw) * 4;
422 		my->vel_y = sin(my->yaw) * 4;
423 		double dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
424 
425 		unsigned int distance = sqrt(pow(my->x - lightball_player_startx, 2) + pow(my->y - lightball_player_starty, 2));
426 		if (distance > MAGICLIGHT_BALL_FOLLOW_DISTANCE * 2 || dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y))
427 		{
428 			magic_init = 1;
429 			my->sprite = 174; //Go from black ball to white ball.
430 			lightball_lighting = 1;
431 			lightball_movement_timer = 0; //Start off at 0 so that it moves towards the player as soon as it's created (since it's created farther away from the player).
432 		}
433 	}
434 }
435 
actMagicMissile(Entity * my)436 void actMagicMissile(Entity* my)   //TODO: Verify this function.
437 {
438 	if (!my || !my->children.first || !my->children.first->element)
439 	{
440 		return;
441 	}
442 	spell_t* spell = (spell_t*)my->children.first->element;
443 	if (!spell)
444 	{
445 		return;
446 	}
447 	//node_t *node = NULL;
448 	spellElement_t* element = NULL;
449 	node_t* node = NULL;
450 	int i = 0;
451 	int c = 0;
452 	Entity* entity = NULL;
453 	double tangent;
454 
455 	Entity* parent = uidToEntity(my->parent);
456 
457 	if (magic_init)
458 	{
459 		my->removeLightField();
460 
461 		if (clientnum == 0 || multiplayer == SERVER)
462 		{
463 			//Handle the missile's life.
464 			MAGIC_LIFE++;
465 
466 			if (MAGIC_LIFE >= MAGIC_MAXLIFE)
467 			{
468 				my->removeLightField();
469 				list_RemoveNode(my->mynode);
470 				return;
471 			}
472 
473 			if ( spell->ID == SPELL_CHARM_MONSTER || spell->ID == SPELL_ACID_SPRAY )
474 			{
475 				Entity* caster = uidToEntity(spell->caster);
476 				if ( !caster )
477 				{
478 					my->removeLightField();
479 					list_RemoveNode(my->mynode);
480 					return;
481 				}
482 			}
483 
484 			node = spell->elements.first;
485 			//element = (spellElement_t *) spell->elements->first->element;
486 			element = (spellElement_t*)node->element;
487 			Sint32 entityHealth = 0;
488 			double dist = 0.f;
489 			bool hitFromAbove = false;
490 			if ( (parent && parent->behavior == &actMagicTrapCeiling) || my->actmagicIsVertical == MAGIC_ISVERTICAL_Z )
491 			{
492 				// moving vertically.
493 				my->z += my->vel_z;
494 				hitFromAbove = my->magicFallingCollision();
495 				if ( !hitFromAbove )
496 				{
497 					// nothing hit yet, let's keep trying...
498 				}
499 			}
500 			else if ( my->actmagicIsOrbiting != 0 )
501 			{
502 				int turnRate = 4;
503 				if ( parent && my->actmagicIsOrbiting == 1 )
504 				{
505 					my->yaw += 0.1;
506 					my->x = parent->x + my->actmagicOrbitDist * cos(my->yaw);
507 					my->y = parent->y + my->actmagicOrbitDist * sin(my->yaw);
508 				}
509 				else if ( my->actmagicIsOrbiting == 2 )
510 				{
511 					my->yaw += 0.2;
512 					turnRate = 4;
513 					my->x = my->actmagicOrbitStationaryX + my->actmagicOrbitStationaryCurrentDist * cos(my->yaw);
514 					my->y = my->actmagicOrbitStationaryY + my->actmagicOrbitStationaryCurrentDist * sin(my->yaw);
515 					my->actmagicOrbitStationaryCurrentDist =
516 						std::min(my->actmagicOrbitStationaryCurrentDist + 0.5, static_cast<real_t>(my->actmagicOrbitDist));
517 				}
518 				hitFromAbove = my->magicOrbitingCollision();
519 				my->z += my->vel_z * my->actmagicOrbitVerticalDirection;
520 
521 				if ( my->actmagicIsOrbiting == 2 )
522 				{
523 					// we don't change direction, upwards we go!
524 					// target speed is actmagicOrbitVerticalSpeed.
525 					my->vel_z = std::min(my->actmagicOrbitVerticalSpeed, my->vel_z / 0.95);
526 					my->roll += (PI / 8) / (turnRate / my->vel_z) * my->actmagicOrbitVerticalDirection;
527 					my->roll = std::max(my->roll, -PI / 4);
528 				}
529 				else if ( my->z > my->actmagicOrbitStartZ )
530 				{
531 					if ( my->actmagicOrbitVerticalDirection == 1 )
532 					{
533 						my->vel_z = std::max(0.01, my->vel_z * 0.95);
534 						my->roll -= (PI / 8) / (turnRate / my->vel_z) * my->actmagicOrbitVerticalDirection;
535 					}
536 					else
537 					{
538 						my->vel_z = std::min(my->actmagicOrbitVerticalSpeed, my->vel_z / 0.95);
539 						my->roll += (PI / 8) / (turnRate / my->vel_z) * my->actmagicOrbitVerticalDirection;
540 					}
541 				}
542 				else
543 				{
544 					if ( my->actmagicOrbitVerticalDirection == 1 )
545 					{
546 						my->vel_z = std::min(my->actmagicOrbitVerticalSpeed, my->vel_z / 0.95);
547 						my->roll += (PI / 8) / (turnRate / my->vel_z) * my->actmagicOrbitVerticalDirection;
548 					}
549 					else
550 					{
551 						my->vel_z = std::max(0.01, my->vel_z * 0.95);
552 						my->roll -= (PI / 8) / (turnRate / my->vel_z) * my->actmagicOrbitVerticalDirection;
553 					}
554 				}
555 
556 				if ( my->actmagicIsOrbiting == 1 )
557 				{
558 					if ( (my->z > my->actmagicOrbitStartZ + 4) && my->actmagicOrbitVerticalDirection == 1 )
559 					{
560 						my->actmagicOrbitVerticalDirection = -1;
561 					}
562 					else if ( (my->z < my->actmagicOrbitStartZ - 4) && my->actmagicOrbitVerticalDirection != 1 )
563 					{
564 						my->actmagicOrbitVerticalDirection = 1;
565 					}
566 				}
567 			}
568 			else
569 			{
570 				if ( my->actmagicIsVertical == MAGIC_ISVERTICAL_XYZ )
571 				{
572 					// moving vertically and horizontally, check if we hit the floor
573 					my->z += my->vel_z;
574 					hitFromAbove = my->magicFallingCollision();
575 					dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
576 					if ( !hitFromAbove && my->z > -5 )
577 					{
578 						// if we didn't hit the floor, process normal horizontal movement collision if we aren't too high
579 						if ( dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y) )
580 						{
581 							hitFromAbove = true;
582 						}
583 					}
584 					if ( my->actmagicProjectileArc > 0 )
585 					{
586 						real_t z = -1 - my->z;
587 						if ( z > 0 )
588 						{
589 							my->pitch = -atan(z * 0.1 / sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y));
590 						}
591 						else
592 						{
593 							my->pitch = -atan(z * 0.15 / sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y));
594 						}
595 						if ( my->actmagicProjectileArc == 1 )
596 						{
597 							//messagePlayer(0, "z: %f vel: %f", my->z, my->vel_z);
598 							my->vel_z = my->vel_z * 0.9;
599 							if ( my->vel_z > -0.1 )
600 							{
601 								//messagePlayer(0, "arc down");
602 								my->actmagicProjectileArc = 2;
603 								my->vel_z = 0.01;
604 							}
605 						}
606 						else if ( my->actmagicProjectileArc == 2 )
607 						{
608 							//messagePlayer(0, "z: %f vel: %f", my->z, my->vel_z);
609 							my->vel_z = std::min(0.8, my->vel_z * 1.2);
610 						}
611 					}
612 				}
613 				else
614 				{
615 					dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my); //normal flat projectiles
616 				}
617 			}
618 
619 			if ( hitFromAbove || (my->actmagicIsVertical != MAGIC_ISVERTICAL_XYZ && dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y)) )
620 			{
621 				node = element->elements.first;
622 				//element = (spellElement_t *) element->elements->first->element;
623 				element = (spellElement_t*)node->element;
624 				//if (hit.entity != NULL) {
625 				Stat* hitstats = nullptr;
626 				int player = -1;
627 				if ( hit.entity )
628 				{
629 					hitstats = hit.entity->getStats();
630 					if ( hit.entity->behavior == &actPlayer )
631 					{
632 						bool skipMessage = false;
633 						if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) && my->actmagicTinkerTrapFriendlyFire == 0 )
634 						{
635 							if ( parent && (parent->behavior == &actMonster || parent->behavior == &actPlayer) && parent->checkFriend(hit.entity) )
636 							{
637 								skipMessage = true;
638 							}
639 						}
640 
641 						player = hit.entity->skill[2];
642 						if ( my->actmagicCastByTinkerTrap == 1 )
643 						{
644 							skipMessage = true;
645 						}
646 						if ( !skipMessage )
647 						{
648 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
649 							messagePlayerColor(player, color, language[376]);
650 						}
651 						if ( hitstats )
652 						{
653 							entityHealth = hitstats->HP;
654 						}
655 					}
656 					if ( parent && hitstats )
657 					{
658 						if ( parent->behavior == &actPlayer )
659 						{
660 							Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
661 							if ( strcmp(element->name, spellElement_charmMonster.name) )
662 							{
663 								if ( my->actmagicCastByTinkerTrap == 1 )
664 								{
665 									//messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3498], language[3499], MSG_COMBAT);
666 								}
667 								else
668 								{
669 									messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[378], language[377], MSG_COMBAT);
670 								}
671 							}
672 						}
673 					}
674 				}
675 
676 				// Handling reflecting the missile
677 				int reflection = 0;
678 				if ( hitstats )
679 				{
680 					if ( !strcmp(map.name, "Hell Boss") && hit.entity->behavior == &actPlayer )
681 					{
682 						/* no longer in use */
683 						/*bool founddevil = false;
684 						node_t* tempNode;
685 						for ( tempNode = map.creatures->first; tempNode != nullptr; tempNode = tempNode->next )
686 						{
687 							Entity* tempEntity = (Entity*)tempNode->element;
688 							if ( tempEntity->behavior == &actMonster )
689 							{
690 								Stat* stats = tempEntity->getStats();
691 								if ( stats )
692 								{
693 									if ( stats->type == DEVIL )
694 									{
695 										founddevil = true;
696 										break;
697 									}
698 								}
699 							}
700 						}
701 						if ( !founddevil )
702 						{
703 							reflection = 3;
704 						}*/
705 					}
706 					else if ( parent &&
707 							(	(hit.entity->getRace() == LICH_ICE && parent->getRace() == LICH_FIRE)
708 								|| ( (hit.entity->getRace() == LICH_FIRE || hitstats->leader_uid == parent->getUID()) && parent->getRace() == LICH_ICE)
709 								|| (parent->getRace() == LICH_ICE) && !strncmp(hitstats->name, "corrupted automaton", 19)
710 							)
711 						)
712 					{
713 						reflection = 3;
714 					}
715 					if ( !reflection )
716 					{
717 						reflection = hit.entity->getReflection();
718 					}
719 					if ( my->actmagicCastByTinkerTrap == 1 )
720 					{
721 						reflection = 0;
722 					}
723 					if ( reflection == 3 && hitstats->shield && hitstats->shield->type == MIRROR_SHIELD && hitstats->defending )
724 					{
725 						if ( my->actmagicIsVertical == MAGIC_ISVERTICAL_Z )
726 						{
727 							reflection = 0;
728 						}
729 						// calculate facing angle to projectile, need to be facing projectile to reflect.
730 						else if ( player >= 0 && players[player] && players[player]->entity )
731 						{
732 							real_t yawDiff = my->yawDifferenceFromPlayer(player);
733 							if ( yawDiff < (6 * PI / 5) )
734 							{
735 								reflection = 0;
736 							}
737 							else
738 							{
739 								reflection = 3;
740 								if ( parent && (parent->behavior == &actMonster || parent->behavior == &actPlayer) )
741 								{
742 									my->actmagicMirrorReflected = 1;
743 									my->actmagicMirrorReflectedCaster = parent->getUID();
744 								}
745 							}
746 						}
747 					}
748 				}
749 				if ( reflection )
750 				{
751 					spell_t* spellIsReflectingMagic = hit.entity->getActiveMagicEffect(SPELL_REFLECT_MAGIC);
752 					playSoundEntity(hit.entity, 166, 128);
753 					if ( hit.entity )
754 					{
755 						if ( hit.entity->behavior == &actPlayer )
756 						{
757 							if ( !strcmp(element->name, spellElement_charmMonster.name) )
758 							{
759 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
760 								messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[378], language[377], MSG_COMBAT);
761 							}
762 							if ( !spellIsReflectingMagic )
763 							{
764 								messagePlayer(player, language[379]);
765 							}
766 							else
767 							{
768 								messagePlayer(player, language[2475]);
769 							}
770 						}
771 					}
772 					if ( parent )
773 					{
774 						if ( parent->behavior == &actPlayer )
775 						{
776 							messagePlayer(parent->skill[2], language[379]);
777 						}
778 					}
779 					if ( hit.side == HORIZONTAL )
780 					{
781 						my->vel_x *= -1;
782 						my->yaw = atan2(my->vel_y, my->vel_x);
783 					}
784 					else if ( hit.side == VERTICAL )
785 					{
786 						my->vel_y *= -1;
787 						my->yaw = atan2(my->vel_y, my->vel_x);
788 					}
789 					else if ( hit.side == 0 )
790 					{
791 						my->vel_x *= -1;
792 						my->vel_y *= -1;
793 						my->yaw = atan2(my->vel_y, my->vel_x);
794 					}
795 					if ( hit.entity )
796 					{
797 						if ( (parent && parent->behavior == &actMagicTrapCeiling) || my->actmagicIsVertical == MAGIC_ISVERTICAL_Z )
798 						{
799 							// this missile came from the ceiling, let's redirect it..
800 							my->x = hit.entity->x + cos(hit.entity->yaw);
801 							my->y = hit.entity->y + sin(hit.entity->yaw);
802 							my->yaw = hit.entity->yaw;
803 							my->z = -1;
804 							my->vel_x = 4 * cos(hit.entity->yaw);
805 							my->vel_y = 4 * sin(hit.entity->yaw);
806 							my->vel_z = 0;
807 							my->pitch = 0;
808 						}
809 						my->parent = hit.entity->getUID();
810 					}
811 
812 					// Only degrade the equipment if Friendly Fire is ON or if it is (OFF && target is an enemy)
813 					bool bShouldEquipmentDegrade = false;
814 					if ( (svFlags & SV_FLAG_FRIENDLYFIRE) )
815 					{
816 						// Friendly Fire is ON, equipment should always degrade, as hit will register
817 						bShouldEquipmentDegrade = true;
818 					}
819 					else
820 					{
821 						// Friendly Fire is OFF, is the target an enemy?
822 						if ( parent != nullptr && (parent->checkFriend(hit.entity)) == false )
823 						{
824 							// Target is an enemy, equipment should degrade
825 							bShouldEquipmentDegrade = true;
826 						}
827 					}
828 
829 					if ( bShouldEquipmentDegrade )
830 					{
831 						// Reflection of 3 does not degrade equipment
832 						if ( rand() % 2 == 0 && hitstats && reflection < 3 )
833 						{
834 							// set armornum to the relevant equipment slot to send to clients
835 							int armornum = 5 + reflection;
836 							if ( player == clientnum || player < 0 )
837 							{
838 								if ( reflection == 1 )
839 								{
840 									if ( hitstats->cloak->count > 1 )
841 									{
842 										newItem(hitstats->cloak->type, hitstats->cloak->status, hitstats->cloak->beatitude, hitstats->cloak->count - 1, hitstats->cloak->appearance, hitstats->cloak->identified, &hitstats->inventory);
843 									}
844 								}
845 								else if ( reflection == 2 )
846 								{
847 									if ( hitstats->amulet->count > 1 )
848 									{
849 										newItem(hitstats->amulet->type, hitstats->amulet->status, hitstats->amulet->beatitude, hitstats->amulet->count - 1, hitstats->amulet->appearance, hitstats->amulet->identified, &hitstats->inventory);
850 									}
851 								}
852 								else if ( reflection == -1 )
853 								{
854 									if ( hitstats->shield->count > 1 )
855 									{
856 										newItem(hitstats->shield->type, hitstats->shield->status, hitstats->shield->beatitude, hitstats->shield->count - 1, hitstats->shield->appearance, hitstats->shield->identified, &hitstats->inventory);
857 									}
858 								}
859 							}
860 							if ( reflection == 1 )
861 							{
862 								hitstats->cloak->count = 1;
863 								hitstats->cloak->status = static_cast<Status>(std::max(static_cast<int>(BROKEN), hitstats->cloak->status - 1));
864 								if ( hitstats->cloak->status != BROKEN )
865 								{
866 									messagePlayer(player, language[380]);
867 								}
868 								else
869 								{
870 									messagePlayer(player, language[381]);
871 									playSoundEntity(hit.entity, 76, 64);
872 								}
873 							}
874 							else if ( reflection == 2 )
875 							{
876 								hitstats->amulet->count = 1;
877 								hitstats->amulet->status = static_cast<Status>(std::max(static_cast<int>(BROKEN), hitstats->amulet->status - 1));
878 								if ( hitstats->amulet->status != BROKEN )
879 								{
880 									messagePlayer(player, language[382]);
881 								}
882 								else
883 								{
884 									messagePlayer(player, language[383]);
885 									playSoundEntity(hit.entity, 76, 64);
886 								}
887 							}
888 							else if ( reflection == -1 )
889 							{
890 								hitstats->shield->count = 1;
891 								hitstats->shield->status = static_cast<Status>(std::max(static_cast<int>(BROKEN), hitstats->shield->status - 1));
892 								if ( hitstats->shield->status != BROKEN )
893 								{
894 									messagePlayer(player, language[384]);
895 								}
896 								else
897 								{
898 									messagePlayer(player, language[385]);
899 									playSoundEntity(hit.entity, 76, 64);
900 								}
901 							}
902 							if ( player > 0 && multiplayer == SERVER )
903 							{
904 								strcpy((char*)net_packet->data, "ARMR");
905 								net_packet->data[4] = armornum;
906 								if ( reflection == 1 )
907 								{
908 									net_packet->data[5] = hitstats->cloak->status;
909 								}
910 								else if ( reflection == 2 )
911 								{
912 									net_packet->data[5] = hitstats->amulet->status;
913 								}
914 								else
915 								{
916 									net_packet->data[5] = hitstats->shield->status;
917 								}
918 								net_packet->address.host = net_clients[player - 1].host;
919 								net_packet->address.port = net_clients[player - 1].port;
920 								net_packet->len = 6;
921 								sendPacketSafe(net_sock, -1, net_packet, player - 1);
922 							}
923 						}
924 					}
925 
926 					if ( spellIsReflectingMagic )
927 					{
928 						int spellCost = getCostOfSpell(spell);
929 						bool unsustain = false;
930 						if ( spellCost >= hit.entity->getMP() ) //Unsustain the spell if expended all mana.
931 						{
932 							unsustain = true;
933 						}
934 
935 						hit.entity->drainMP(spellCost);
936 						spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z / 2, 174);
937 						playSoundEntity(hit.entity, 166, 128); //TODO: Custom sound effect?
938 
939 						if ( unsustain )
940 						{
941 							spellIsReflectingMagic->sustain = false;
942 							if ( hitstats )
943 							{
944 								hit.entity->setEffect(EFF_MAGICREFLECT, false, 0, true);
945 								messagePlayer(player, language[2476]);
946 							}
947 						}
948 					}
949 					return;
950 				}
951 
952 				// Test for Friendly Fire, if Friendly Fire is OFF, delete the missile
953 				if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
954 				{
955 					if ( !strcmp(element->name, spellElement_telePull.name)
956 						|| !strcmp(element->name, spellElement_shadowTag.name)
957 						|| my->actmagicTinkerTrapFriendlyFire == 1 )
958 					{
959 						// these spells can hit allies no penalty.
960 					}
961 					else if ( parent && parent->checkFriend(hit.entity) )
962 					{
963 						my->removeLightField();
964 						list_RemoveNode(my->mynode);
965 						return;
966 					}
967 				}
968 
969 				// Alerting the hit Entity
970 				if ( hit.entity )
971 				{
972 					// alert the hit entity if it was a monster
973 					if ( hit.entity->behavior == &actMonster && parent != nullptr )
974 					{
975 						if ( parent->behavior == &actMagicTrap || parent->behavior == &actMagicTrapCeiling )
976 						{
977 							if ( parent->behavior == &actMagicTrap )
978 							{
979 								if ( static_cast<int>(parent->y / 16) == static_cast<int>(hit.entity->y / 16) )
980 								{
981 									// avoid y axis.
982 									int direction = 1;
983 									if ( rand() % 2 == 0 )
984 									{
985 										direction = -1;
986 									}
987 									if ( hit.entity->monsterSetPathToLocation(hit.entity->x / 16, (hit.entity->y / 16) + 1 * direction, 0) )
988 									{
989 										hit.entity->monsterState = MONSTER_STATE_HUNT;
990 										serverUpdateEntitySkill(hit.entity, 0);
991 									}
992 									else if ( hit.entity->monsterSetPathToLocation(hit.entity->x / 16, (hit.entity->y / 16) - 1 * direction, 0) )
993 									{
994 										hit.entity->monsterState = MONSTER_STATE_HUNT;
995 										serverUpdateEntitySkill(hit.entity, 0);
996 									}
997 									else
998 									{
999 										monsterMoveAside(hit.entity, hit.entity);
1000 									}
1001 								}
1002 								else if ( static_cast<int>(parent->x / 16) == static_cast<int>(hit.entity->x / 16) )
1003 								{
1004 									int direction = 1;
1005 									if ( rand() % 2 == 0 )
1006 									{
1007 										direction = -1;
1008 									}
1009 									// avoid x axis.
1010 									if ( hit.entity->monsterSetPathToLocation((hit.entity->x / 16) + 1 * direction, hit.entity->y / 16, 0) )
1011 									{
1012 										hit.entity->monsterState = MONSTER_STATE_HUNT;
1013 										serverUpdateEntitySkill(hit.entity, 0);
1014 									}
1015 									else if ( hit.entity->monsterSetPathToLocation((hit.entity->x / 16) - 1 * direction, hit.entity->y / 16, 0) )
1016 									{
1017 										hit.entity->monsterState = MONSTER_STATE_HUNT;
1018 										serverUpdateEntitySkill(hit.entity, 0);
1019 									}
1020 									else
1021 									{
1022 										monsterMoveAside(hit.entity, hit.entity);
1023 									}
1024 								}
1025 								else
1026 								{
1027 									monsterMoveAside(hit.entity, hit.entity);
1028 								}
1029 							}
1030 							else
1031 							{
1032 								monsterMoveAside(hit.entity, hit.entity);
1033 							}
1034 						}
1035 						else
1036 						{
1037 							bool alertTarget = true;
1038 							bool alertAllies = true;
1039 							if ( parent->behavior == &actMonster && parent->monsterAllyIndex != -1 )
1040 							{
1041 								if ( hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1 )
1042 								{
1043 									// if a player ally + hit another ally, don't aggro back
1044 									alertTarget = false;
1045 								}
1046 							}
1047 							if ( !strcmp(element->name, spellElement_telePull.name) || !strcmp(element->name, spellElement_shadowTag.name) )
1048 							{
1049 								alertTarget = false;
1050 								alertAllies = false;
1051 							}
1052 							if ( my->actmagicCastByTinkerTrap == 1 )
1053 							{
1054 								if ( entityDist(hit.entity, parent) > TOUCHRANGE )
1055 								{
1056 									// don't alert if bomb thrower far away.
1057 									alertTarget = false;
1058 									alertAllies = false;
1059 								}
1060 							}
1061 
1062 							if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) )
1063 							{
1064 								hit.entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true);
1065 							}
1066 
1067 							if ( parent->behavior == &actPlayer || parent->monsterAllyIndex != -1 )
1068 							{
1069 								if ( hit.entity->behavior == &actPlayer || (hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1) )
1070 								{
1071 									// if a player ally + hit another ally or player, don't alert other allies.
1072 									alertAllies = false;
1073 								}
1074 							}
1075 
1076 							// alert other monsters too
1077 							Entity* ohitentity = hit.entity;
1078 							for ( node = map.creatures->first; node != nullptr && alertAllies; node = node->next )
1079 							{
1080 								entity = (Entity*)node->element;
1081 								if ( entity->behavior == &actMonster && entity != ohitentity )
1082 								{
1083 									Stat* buddystats = entity->getStats();
1084 									if ( buddystats != nullptr )
1085 									{
1086 										if ( hit.entity && hit.entity->checkFriend(entity) ) //TODO: hit.entity->checkFriend() without first checking if it's NULL crashes because hit.entity turns to NULL somewhere along the line. It looks like ohitentity preserves that value though, so....uh...ya, I don't know.
1087 										{
1088 											if ( entity->monsterState == MONSTER_STATE_WAIT )
1089 											{
1090 												tangent = atan2( entity->y - ohitentity->y, entity->x - ohitentity->x );
1091 												lineTrace(ohitentity, ohitentity->x, ohitentity->y, tangent, 1024, 0, false);
1092 												if ( hit.entity == entity )
1093 												{
1094 													entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH);
1095 												}
1096 											}
1097 										}
1098 									}
1099 								}
1100 							}
1101 							hit.entity = ohitentity;
1102 						}
1103 					}
1104 				}
1105 
1106 				// check for magic resistance...
1107 				// resistance stacks diminishingly
1108 				//TODO: EFFECTS[EFF_MAGICRESIST]
1109 				int resistance = 0;
1110 				if ( hit.entity )
1111 				{
1112 					resistance = hit.entity->getMagicResistance();
1113 				}
1114 
1115 				if ( resistance > 0 )
1116 				{
1117 					if ( parent )
1118 					{
1119 						if ( parent->behavior == &actPlayer )
1120 						{
1121 							messagePlayer(parent->skill[2], language[386]);
1122 						}
1123 					}
1124 				}
1125 
1126 				real_t spellbookDamageBonus = (my->actmagicSpellbookBonus / 100.f);
1127 				if ( my->actmagicCastByMagicstaff == 0 && my->actmagicCastByTinkerTrap == 0 )
1128 				{
1129 					spellbookDamageBonus += getBonusFromCasterOfSpellElement(parent, element);
1130 				}
1131 
1132 				if (!strcmp(element->name, spellElement_force.name))
1133 				{
1134 					if (hit.entity)
1135 					{
1136 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1137 						{
1138 							Entity* parent = uidToEntity(my->parent);
1139 							playSoundEntity(hit.entity, 28, 128);
1140 							int damage = element->damage;
1141 							damage += (spellbookDamageBonus * damage);
1142 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1143 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1144 							damage /= (1 + (int)resistance);
1145 							hit.entity->modHP(-damage);
1146 							for (i = 0; i < damage; i += 2)   //Spawn a gib for every two points of damage.
1147 							{
1148 								Entity* gib = spawnGib(hit.entity);
1149 								serverSpawnGibForClient(gib);
1150 							}
1151 
1152 							if (parent)
1153 							{
1154 								parent->killedByMonsterObituary(hit.entity);
1155 							}
1156 
1157 							// update enemy bar for attacker
1158 							if ( !strcmp(hitstats->name, "") )
1159 							{
1160 								if ( hitstats->type < KOBOLD ) //Original monster count
1161 								{
1162 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1163 								}
1164 								else if ( hitstats->type >= KOBOLD ) //New monsters
1165 								{
1166 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1167 								}
1168 							}
1169 							else
1170 							{
1171 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1172 							}
1173 
1174 							if ( hitstats->HP <= 0 && parent)
1175 							{
1176 								parent->awardXP( hit.entity, true, true );
1177 							}
1178 						}
1179 						else if (hit.entity->behavior == &actDoor)
1180 						{
1181 							int damage = element->damage;
1182 							damage += (spellbookDamageBonus * damage);
1183 							damage /= (1 + (int)resistance);
1184 							hit.entity->doorHandleDamageMagic(damage, *my, parent);
1185 							my->removeLightField();
1186 							list_RemoveNode(my->mynode);
1187 							return;
1188 						}
1189 						else if ( hit.entity->behavior == &actChest )
1190 						{
1191 							int damage = element->damage;
1192 							damage += (spellbookDamageBonus * damage);
1193 							damage /= (1 + (int)resistance);
1194 							hit.entity->chestHandleDamageMagic(damage, *my, parent);
1195 							my->removeLightField();
1196 							list_RemoveNode(my->mynode);
1197 							return;
1198 						}
1199 						else if (hit.entity->behavior == &actFurniture )
1200 						{
1201 							int damage = element->damage;
1202 							damage += (spellbookDamageBonus * damage);
1203 							damage /= (1 + (int)resistance);
1204 							hit.entity->furnitureHealth -= damage;
1205 							if ( hit.entity->furnitureHealth < 0 )
1206 							{
1207 								if ( parent )
1208 								{
1209 									if ( parent->behavior == &actPlayer )
1210 									{
1211 										switch ( hit.entity->furnitureType )
1212 										{
1213 											case FURNITURE_CHAIR:
1214 												messagePlayer(parent->skill[2], language[388]);
1215 												updateEnemyBar(parent, hit.entity, language[677], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1216 												break;
1217 											case FURNITURE_TABLE:
1218 												messagePlayer(parent->skill[2], language[389]);
1219 												updateEnemyBar(parent, hit.entity, language[676], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1220 												break;
1221 											case FURNITURE_BED:
1222 												messagePlayer(parent->skill[2], language[2508], language[2505]);
1223 												updateEnemyBar(parent, hit.entity, language[2505], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1224 												break;
1225 											case FURNITURE_BUNKBED:
1226 												messagePlayer(parent->skill[2], language[2508], language[2506]);
1227 												updateEnemyBar(parent, hit.entity, language[2506], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1228 												break;
1229 											case FURNITURE_PODIUM:
1230 												messagePlayer(parent->skill[2], language[2508], language[2507]);
1231 												updateEnemyBar(parent, hit.entity, language[2507], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1232 												break;
1233 											default:
1234 												break;
1235 										}
1236 									}
1237 								}
1238 							}
1239 							playSoundEntity(hit.entity, 28, 128);
1240 						}
1241 					}
1242 				}
1243 				else if (!strcmp(element->name, spellElement_magicmissile.name))
1244 				{
1245 					spawnExplosion(my->x, my->y, my->z);
1246 					if (hit.entity)
1247 					{
1248 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1249 						{
1250 							Entity* parent = uidToEntity(my->parent);
1251 							playSoundEntity(hit.entity, 28, 128);
1252 							int damage = element->damage;
1253 							damage += (spellbookDamageBonus * damage);
1254 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1255 							if ( my->actmagicIsOrbiting == 2 )
1256 							{
1257 								spawnExplosion(my->x, my->y, my->z);
1258 								if ( parent && my->actmagicOrbitCastFromSpell == 1 )
1259 								{
1260 									// cast through amplify magic effect
1261 									damage /= 2;
1262 								}
1263 								damage = damage - rand() % ((damage / 8) + 1);
1264 							}
1265 
1266 
1267 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1268 							damage /= (1 + (int)resistance);
1269 							hit.entity->modHP(-damage);
1270 							for (i = 0; i < damage; i += 2)   //Spawn a gib for every two points of damage.
1271 							{
1272 								Entity* gib = spawnGib(hit.entity);
1273 								serverSpawnGibForClient(gib);
1274 							}
1275 
1276 							// write the obituary
1277 							if ( parent )
1278 							{
1279 								parent->killedByMonsterObituary(hit.entity);
1280 							}
1281 
1282 							// update enemy bar for attacker
1283 							if ( !strcmp(hitstats->name, "") )
1284 							{
1285 								if ( hitstats->type < KOBOLD ) //Original monster count
1286 								{
1287 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1288 								}
1289 								else if ( hitstats->type >= KOBOLD ) //New monsters
1290 								{
1291 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1292 								}
1293 							}
1294 							else
1295 							{
1296 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1297 							}
1298 
1299 							if ( hitstats->HP <= 0 && parent)
1300 							{
1301 								parent->awardXP( hit.entity, true, true );
1302 							}
1303 						}
1304 						else if ( hit.entity->behavior == &actDoor )
1305 						{
1306 							int damage = element->damage;
1307 							damage += (spellbookDamageBonus * damage);
1308 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1309 							damage /= (1 + (int)resistance);
1310 							hit.entity->doorHandleDamageMagic(damage, *my, parent);
1311 							if ( my->actmagicProjectileArc > 0 )
1312 							{
1313 								Entity* caster = uidToEntity(spell->caster);
1314 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1315 							}
1316 							if ( !(my->actmagicIsOrbiting == 2) )
1317 							{
1318 								my->removeLightField();
1319 								list_RemoveNode(my->mynode);
1320 							}
1321 							else
1322 							{
1323 								spawnExplosion(my->x, my->y, my->z);
1324 							}
1325 							return;
1326 						}
1327 						else if ( hit.entity->behavior == &actChest )
1328 						{
1329 							int damage = element->damage;
1330 							damage += (spellbookDamageBonus * damage);
1331 							damage /= (1 + (int)resistance);
1332 							hit.entity->chestHandleDamageMagic(damage, *my, parent);
1333 							if ( my->actmagicProjectileArc > 0 )
1334 							{
1335 								Entity* caster = uidToEntity(spell->caster);
1336 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1337 							}
1338 							if ( !(my->actmagicIsOrbiting == 2) )
1339 							{
1340 								my->removeLightField();
1341 								list_RemoveNode(my->mynode);
1342 							}
1343 							else
1344 							{
1345 								spawnExplosion(my->x, my->y, my->z);
1346 							}
1347 							return;
1348 						}
1349 						else if (hit.entity->behavior == &actFurniture )
1350 						{
1351 							int damage = element->damage;
1352 							damage += (spellbookDamageBonus * damage);
1353 							damage /= (1 + (int)resistance);
1354 							hit.entity->furnitureHealth -= damage;
1355 							if ( hit.entity->furnitureHealth < 0 )
1356 							{
1357 								if ( parent )
1358 								{
1359 									if ( parent->behavior == &actPlayer )
1360 									{
1361 										switch ( hit.entity->furnitureType )
1362 										{
1363 											case FURNITURE_CHAIR:
1364 												messagePlayer(parent->skill[2], language[388]);
1365 												updateEnemyBar(parent, hit.entity, language[677], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1366 												break;
1367 											case FURNITURE_TABLE:
1368 												messagePlayer(parent->skill[2], language[389]);
1369 												updateEnemyBar(parent, hit.entity, language[676], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1370 												break;
1371 											case FURNITURE_BED:
1372 												messagePlayer(parent->skill[2], language[2508], language[2505]);
1373 												updateEnemyBar(parent, hit.entity, language[2505], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1374 												break;
1375 											case FURNITURE_BUNKBED:
1376 												messagePlayer(parent->skill[2], language[2508], language[2506]);
1377 												updateEnemyBar(parent, hit.entity, language[2506], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1378 												break;
1379 											case FURNITURE_PODIUM:
1380 												messagePlayer(parent->skill[2], language[2508], language[2507]);
1381 												updateEnemyBar(parent, hit.entity, language[2507], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1382 												break;
1383 											default:
1384 												break;
1385 										}
1386 									}
1387 								}
1388 							}
1389 							playSoundEntity(hit.entity, 28, 128);
1390 							if ( my->actmagicProjectileArc > 0 )
1391 							{
1392 								Entity* caster = uidToEntity(spell->caster);
1393 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1394 							}
1395 							if ( !(my->actmagicIsOrbiting == 2) )
1396 							{
1397 								my->removeLightField();
1398 								list_RemoveNode(my->mynode);
1399 							}
1400 							else
1401 							{
1402 								spawnExplosion(my->x, my->y, my->z);
1403 							}
1404 							return;
1405 						}
1406 					}
1407 				}
1408 				else if (!strcmp(element->name, spellElement_fire.name))
1409 				{
1410 					if ( !(my->actmagicIsOrbiting == 2) )
1411 					{
1412 						spawnExplosion(my->x, my->y, my->z);
1413 					}
1414 					if (hit.entity)
1415 					{
1416 						// Attempt to set the Entity on fire
1417 						hit.entity->SetEntityOnFire();
1418 
1419 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1420 						{
1421 							//playSoundEntity(my, 153, 64);
1422 							playSoundEntity(hit.entity, 28, 128);
1423 							//TODO: Apply fire resistances/weaknesses.
1424 							int damage = element->damage;
1425 							damage += (spellbookDamageBonus * damage);
1426 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1427 							if ( my->actmagicIsOrbiting == 2 )
1428 							{
1429 								spawnExplosion(my->x, my->y, my->z);
1430 								if ( parent && my->actmagicOrbitCastFromSpell == 0 )
1431 								{
1432 									if ( parent->behavior == &actParticleDot )
1433 									{
1434 										damage = parent->skill[1];
1435 									}
1436 									else if ( parent->behavior == &actPlayer )
1437 									{
1438 										Stat* playerStats = parent->getStats();
1439 										if ( playerStats )
1440 										{
1441 											int skillLVL = playerStats->PROFICIENCIES[PRO_ALCHEMY] / 20;
1442 											damage = (14 + skillLVL * 1.5);
1443 										}
1444 									}
1445 									else
1446 									{
1447 										damage = 14;
1448 									}
1449 								}
1450 								else if ( parent && my->actmagicOrbitCastFromSpell == 1 )
1451 								{
1452 									// cast through amplify magic effect
1453 									damage /= 2;
1454 								}
1455 								else
1456 								{
1457 									damage = 14;
1458 								}
1459 								damage = damage - rand() % ((damage / 8) + 1);
1460 							}
1461 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1462 							if ( parent )
1463 							{
1464 								Stat* casterStats = parent->getStats();
1465 								if ( casterStats && casterStats->type == LICH_FIRE && parent->monsterLichAllyStatus == LICH_ALLY_DEAD )
1466 								{
1467 									damage *= 2;
1468 								}
1469 							}
1470 							int oldHP = hitstats->HP;
1471 							damage /= (1 + (int)resistance);
1472 							hit.entity->modHP(-damage);
1473 							//for (i = 0; i < damage; i += 2) { //Spawn a gib for every two points of damage.
1474 							Entity* gib = spawnGib(hit.entity);
1475 							serverSpawnGibForClient(gib);
1476 							//}
1477 
1478 							// write the obituary
1479 							if ( parent )
1480 							{
1481 								if ( my->actmagicIsOrbiting == 2
1482 									&& parent->behavior == &actParticleDot
1483 									&& parent->skill[1] > 0 )
1484 								{
1485 									if ( hitstats && hitstats->obituary && !strcmp(hitstats->obituary, language[3898]) )
1486 									{
1487 										// was caused by a flaming boulder.
1488 										hit.entity->setObituary(language[3898]);
1489 									}
1490 									else
1491 									{
1492 										// blew the brew (alchemy)
1493 										hit.entity->setObituary(language[3350]);
1494 									}
1495 								}
1496 								else
1497 								{
1498 									parent->killedByMonsterObituary(hit.entity);
1499 								}
1500 							}
1501 							if ( hitstats )
1502 							{
1503 								hitstats->burningInflictedBy = static_cast<Sint32>(my->parent);
1504 							}
1505 
1506 							// update enemy bar for attacker
1507 							if ( !strcmp(hitstats->name, "") )
1508 							{
1509 								if ( hitstats->type < KOBOLD ) //Original monster count
1510 								{
1511 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1512 								}
1513 								else if ( hitstats->type >= KOBOLD ) //New monsters
1514 								{
1515 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1516 								}
1517 							}
1518 							else
1519 							{
1520 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1521 							}
1522 							if ( oldHP > 0 && hitstats->HP <= 0 )
1523 							{
1524 								if ( parent )
1525 								{
1526 									if ( my->actmagicIsOrbiting == 2 && my->actmagicOrbitCastFromSpell == 0 && parent->behavior == &actPlayer )
1527 									{
1528 										if ( hitstats->type == LICH || hitstats->type == LICH_ICE || hitstats->type == LICH_FIRE )
1529 										{
1530 											if ( client_classes[parent->skill[2]] == CLASS_BREWER )
1531 											{
1532 												steamAchievementClient(parent->skill[2], "BARONY_ACH_SECRET_WEAPON");
1533 											}
1534 										}
1535 										steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_BOMBARDIER, STEAM_STAT_INT, 1);
1536 									}
1537 									if ( my->actmagicCastByTinkerTrap == 1 && parent->behavior == &actPlayer && hitstats->type == MINOTAUR )
1538 									{
1539 										steamAchievementClient(parent->skill[2], "BARONY_ACH_TIME_TO_PLAN");
1540 									}
1541 									parent->awardXP( hit.entity, true, true );
1542 								}
1543 								else
1544 								{
1545 									if ( achievementObserver.checkUidIsFromPlayer(my->parent) >= 0 )
1546 									{
1547 										steamAchievementClient(achievementObserver.checkUidIsFromPlayer(my->parent), "BARONY_ACH_TAKING_WITH");
1548 									}
1549 								}
1550 							}
1551 						}
1552 						else if (hit.entity->behavior == &actDoor)
1553 						{
1554 							int damage = element->damage;
1555 							damage += (spellbookDamageBonus * damage);
1556 							damage /= (1 + (int)resistance);
1557 
1558 							hit.entity->doorHandleDamageMagic(damage, *my, parent);
1559 							if ( my->actmagicProjectileArc > 0 )
1560 							{
1561 								Entity* caster = uidToEntity(spell->caster);
1562 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1563 							}
1564 							if ( !(my->actmagicIsOrbiting == 2) )
1565 							{
1566 								my->removeLightField();
1567 								list_RemoveNode(my->mynode);
1568 							}
1569 							else
1570 							{
1571 								spawnExplosion(my->x, my->y, my->z);
1572 							}
1573 							return;
1574 						}
1575 						else if (hit.entity->behavior == &actChest)
1576 						{
1577 							int damage = element->damage;
1578 							damage += (spellbookDamageBonus * damage);
1579 							damage /= (1+(int)resistance);
1580 							hit.entity->chestHandleDamageMagic(damage, *my, parent);
1581 							if ( my->actmagicProjectileArc > 0 )
1582 							{
1583 								Entity* caster = uidToEntity(spell->caster);
1584 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1585 							}
1586 							if ( !(my->actmagicIsOrbiting == 2) )
1587 							{
1588 								my->removeLightField();
1589 								list_RemoveNode(my->mynode);
1590 							}
1591 							else
1592 							{
1593 								spawnExplosion(my->x, my->y, my->z);
1594 							}
1595 							return;
1596 						}
1597 						else if (hit.entity->behavior == &actFurniture )
1598 						{
1599 							int damage = element->damage;
1600 							damage += (spellbookDamageBonus * damage);
1601 							damage /= (1 + (int)resistance);
1602 							hit.entity->furnitureHealth -= damage;
1603 							if ( hit.entity->furnitureHealth < 0 )
1604 							{
1605 								if ( parent )
1606 								{
1607 									if ( parent->behavior == &actPlayer )
1608 									{
1609 										switch ( hit.entity->furnitureType )
1610 										{
1611 											case FURNITURE_CHAIR:
1612 												messagePlayer(parent->skill[2], language[388]);
1613 												updateEnemyBar(parent, hit.entity, language[677], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1614 												break;
1615 											case FURNITURE_TABLE:
1616 												messagePlayer(parent->skill[2], language[389]);
1617 												updateEnemyBar(parent, hit.entity, language[676], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1618 												break;
1619 											case FURNITURE_BED:
1620 												messagePlayer(parent->skill[2], language[2508], language[2505]);
1621 												updateEnemyBar(parent, hit.entity, language[2505], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1622 												break;
1623 											case FURNITURE_BUNKBED:
1624 												messagePlayer(parent->skill[2], language[2508], language[2506]);
1625 												updateEnemyBar(parent, hit.entity, language[2506], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1626 												break;
1627 											case FURNITURE_PODIUM:
1628 												messagePlayer(parent->skill[2], language[2508], language[2507]);
1629 												updateEnemyBar(parent, hit.entity, language[2507], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
1630 												break;
1631 											default:
1632 												break;
1633 										}
1634 									}
1635 								}
1636 							}
1637 							playSoundEntity(hit.entity, 28, 128);
1638 							if ( my->actmagicProjectileArc > 0 )
1639 							{
1640 								Entity* caster = uidToEntity(spell->caster);
1641 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
1642 							}
1643 							if ( !(my->actmagicIsOrbiting == 2) )
1644 							{
1645 								my->removeLightField();
1646 								list_RemoveNode(my->mynode);
1647 							}
1648 							else
1649 							{
1650 								spawnExplosion(my->x, my->y, my->z);
1651 							}
1652 							return;
1653 						}
1654 					}
1655 				}
1656 				else if (!strcmp(element->name, spellElement_confuse.name))
1657 				{
1658 					if (hit.entity)
1659 					{
1660 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1661 						{
1662 							playSoundEntity(hit.entity, 174, 64);
1663 							hitstats->EFFECTS[EFF_CONFUSED] = true;
1664 							hitstats->EFFECTS_TIMERS[EFF_CONFUSED] = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
1665 							hitstats->EFFECTS_TIMERS[EFF_CONFUSED] /= (1 + (int)resistance);
1666 
1667 							// If the Entity hit is a Player, update their status to be Slowed
1668 							if ( hit.entity->behavior == &actPlayer )
1669 							{
1670 								serverUpdateEffects(hit.entity->skill[2]);
1671 							}
1672 
1673 							hit.entity->skill[1] = 0; //Remove the monster's target.
1674 							if ( parent )
1675 							{
1676 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1677 								if ( parent->behavior == &actPlayer )
1678 								{
1679 									messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[391], language[390], MSG_COMBAT);
1680 								}
1681 							}
1682 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1683 							if ( player >= 0 )
1684 							{
1685 								messagePlayerColor(player, color, language[392]);
1686 							}
1687 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
1688 						}
1689 					}
1690 				}
1691 				else if (!strcmp(element->name, spellElement_cold.name))
1692 				{
1693 					playSoundEntity(my, 197, 128);
1694 					if (hit.entity)
1695 					{
1696 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1697 						{
1698 							playSoundEntity(hit.entity, 28, 128);
1699 							hitstats->EFFECTS[EFF_SLOW] = true;
1700 							hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
1701 							hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance);
1702 
1703 							// If the Entity hit is a Player, update their status to be Slowed
1704 							if ( hit.entity->behavior == &actPlayer )
1705 							{
1706 								serverUpdateEffects(hit.entity->skill[2]);
1707 							}
1708 
1709 							int damage = element->damage;
1710 							damage += (spellbookDamageBonus * damage);
1711 							//messagePlayer(0, "damage: %d", damage);
1712 							if ( my->actmagicIsOrbiting == 2 )
1713 							{
1714 								if ( parent && my->actmagicOrbitCastFromSpell == 0 )
1715 								{
1716 									if ( parent->behavior == &actParticleDot )
1717 									{
1718 										damage = parent->skill[1];
1719 									}
1720 									else if ( parent->behavior == &actPlayer )
1721 									{
1722 										Stat* playerStats = parent->getStats();
1723 										if ( playerStats )
1724 										{
1725 											int skillLVL = playerStats->PROFICIENCIES[PRO_ALCHEMY] / 20;
1726 											damage = (18 + skillLVL * 1.5);
1727 										}
1728 									}
1729 									else
1730 									{
1731 										damage = 18;
1732 									}
1733 								}
1734 								else if ( parent && my->actmagicOrbitCastFromSpell == 1 )
1735 								{
1736 									// cast through amplify magic effect
1737 									damage /= 2;
1738 								}
1739 								else
1740 								{
1741 									damage = 18;
1742 								}
1743 								damage = damage - rand() % ((damage / 8) + 1);
1744 							}
1745 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1746 							int oldHP = hitstats->HP;
1747 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1748 							damage /= (1 + (int)resistance);
1749 							hit.entity->modHP(-damage);
1750 							Entity* gib = spawnGib(hit.entity);
1751 							serverSpawnGibForClient(gib);
1752 
1753 							// write the obituary
1754 							if ( parent )
1755 							{
1756 								parent->killedByMonsterObituary(hit.entity);
1757 							}
1758 
1759 							// update enemy bar for attacker
1760 							if ( !strcmp(hitstats->name, "") )
1761 							{
1762 								if ( hitstats->type < KOBOLD ) //Original monster count
1763 								{
1764 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1765 								}
1766 								else if ( hitstats->type >= KOBOLD ) //New monsters
1767 								{
1768 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1769 								}
1770 							}
1771 							else
1772 							{
1773 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1774 							}
1775 							if ( parent )
1776 							{
1777 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1778 								if ( parent->behavior == &actPlayer )
1779 								{
1780 									messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[394], language[393], MSG_COMBAT);
1781 								}
1782 							}
1783 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1784 							if ( player >= 0 )
1785 							{
1786 								messagePlayerColor(player, color, language[395]);
1787 							}
1788 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
1789 							if ( oldHP > 0 && hitstats->HP <= 0 )
1790 							{
1791 								if ( parent )
1792 								{
1793 									parent->awardXP(hit.entity, true, true);
1794 									if ( my->actmagicIsOrbiting == 2 && my->actmagicOrbitCastFromSpell == 0 && parent->behavior == &actPlayer )
1795 									{
1796 										if ( hitstats->type == LICH || hitstats->type == LICH_ICE || hitstats->type == LICH_FIRE )
1797 										{
1798 											if ( client_classes[parent->skill[2]] == CLASS_BREWER )
1799 											{
1800 												steamAchievementClient(parent->skill[2], "BARONY_ACH_SECRET_WEAPON");
1801 											}
1802 										}
1803 										steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_BOMBARDIER, STEAM_STAT_INT, 1);
1804 									}
1805 									if ( my->actmagicCastByTinkerTrap == 1 && parent->behavior == &actPlayer && hitstats->type == MINOTAUR )
1806 									{
1807 										steamAchievementClient(parent->skill[2], "BARONY_ACH_TIME_TO_PLAN");
1808 									}
1809 								}
1810 								else
1811 								{
1812 									if ( achievementObserver.checkUidIsFromPlayer(my->parent) >= 0 )
1813 									{
1814 										steamAchievementClient(achievementObserver.checkUidIsFromPlayer(my->parent), "BARONY_ACH_TAKING_WITH");
1815 									}
1816 								}
1817 							}
1818 						}
1819 					}
1820 				}
1821 				else if (!strcmp(element->name, spellElement_slow.name))
1822 				{
1823 					if (hit.entity)
1824 					{
1825 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1826 						{
1827 							playSoundEntity(hit.entity, 396 + rand() % 3, 64);
1828 							hitstats->EFFECTS[EFF_SLOW] = true;
1829 							hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
1830 							hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance);
1831 
1832 							// If the Entity hit is a Player, update their status to be Slowed
1833 							if ( hit.entity->behavior == &actPlayer )
1834 							{
1835 								serverUpdateEffects(hit.entity->skill[2]);
1836 							}
1837 
1838 							// update enemy bar for attacker
1839 							if ( parent )
1840 							{
1841 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1842 								if ( parent->behavior == &actPlayer )
1843 								{
1844 									messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[394], language[393], MSG_COMBAT);
1845 								}
1846 							}
1847 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1848 							if ( player >= 0 )
1849 							{
1850 								messagePlayerColor(player, color, language[395]);
1851 							}
1852 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
1853 						}
1854 					}
1855 				}
1856 				else if (!strcmp(element->name, spellElement_sleep.name))
1857 				{
1858 					if (hit.entity)
1859 					{
1860 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1861 						{
1862 							playSoundEntity(hit.entity, 174, 64);
1863 							int effectDuration = 0;
1864 							if ( parent && parent->behavior == &actMagicTrapCeiling )
1865 							{
1866 								effectDuration = 200 + rand() % 150; // 4 seconds + 0 to 3 seconds.
1867 							}
1868 							else
1869 							{
1870 								effectDuration = 600 + rand() % 300; // 12 seconds + 0 to 6 seconds.
1871 								if ( hitstats )
1872 								{
1873 									effectDuration = std::max(0, effectDuration - ((hitstats->CON % 10) * 50)); // reduce 1 sec every 10 CON.
1874 								}
1875 							}
1876 							effectDuration /= (1 + (int)resistance);
1877 
1878 							bool magicTrapReapplySleep = true;
1879 
1880 							if ( parent && (parent->behavior == &actMagicTrap || parent->behavior == &actMagicTrapCeiling) )
1881 							{
1882 								if ( hitstats && hitstats->EFFECTS[EFF_ASLEEP] )
1883 								{
1884 									// check to see if we're reapplying the sleep effect.
1885 									int preventSleepRoll = rand() % 4 - resistance;
1886 									if ( hit.entity->behavior == &actPlayer || (preventSleepRoll <= 0) )
1887 									{
1888 										magicTrapReapplySleep = false;
1889 										//messagePlayer(0, "Target already asleep!");
1890 									}
1891 								}
1892 							}
1893 
1894 							if ( magicTrapReapplySleep )
1895 							{
1896 								if ( hit.entity->setEffect(EFF_ASLEEP, true, effectDuration, false) )
1897 								{
1898 									hitstats->OLDHP = hitstats->HP;
1899 									if ( hit.entity->behavior == &actPlayer )
1900 									{
1901 										serverUpdateEffects(hit.entity->skill[2]);
1902 										Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1903 										messagePlayerColor(hit.entity->skill[2], color, language[396]);
1904 									}
1905 									if ( parent )
1906 									{
1907 										Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1908 										if ( parent->behavior == &actPlayer )
1909 										{
1910 											messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[398], language[397], MSG_COMBAT);
1911 										}
1912 									}
1913 								}
1914 								else
1915 								{
1916 									if ( parent )
1917 									{
1918 										Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1919 										if ( parent->behavior == &actPlayer )
1920 										{
1921 											messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
1922 										}
1923 									}
1924 								}
1925 							}
1926 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
1927 						}
1928 					}
1929 				}
1930 				else if (!strcmp(element->name, spellElement_lightning.name))
1931 				{
1932 					playSoundEntity(my, 173, 128);
1933 					if (hit.entity)
1934 					{
1935 						if (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer)
1936 						{
1937 							Entity* parent = uidToEntity(my->parent);
1938 							playSoundEntity(my, 173, 64);
1939 							playSoundEntity(hit.entity, 28, 128);
1940 							int damage = element->damage;
1941 							damage += (spellbookDamageBonus * damage);
1942 							if ( my->actmagicIsOrbiting == 2 )
1943 							{
1944 								if ( parent && my->actmagicOrbitCastFromSpell == 0 )
1945 								{
1946 									if ( parent->behavior == &actParticleDot )
1947 									{
1948 										damage = parent->skill[1];
1949 									}
1950 									else if ( parent->behavior == &actPlayer )
1951 									{
1952 										Stat* playerStats = parent->getStats();
1953 										if ( playerStats )
1954 										{
1955 											int skillLVL = playerStats->PROFICIENCIES[PRO_ALCHEMY] / 20;
1956 											damage = (22 + skillLVL * 1.5);
1957 										}
1958 									}
1959 									else
1960 									{
1961 										damage = 22;
1962 									}
1963 								}
1964 								else if ( parent && my->actmagicOrbitCastFromSpell == 1 )
1965 								{
1966 									// cast through amplify magic effect
1967 									damage /= 2;
1968 								}
1969 								else
1970 								{
1971 									damage = 22;
1972 								}
1973 								damage = damage - rand() % ((damage / 8) + 1);
1974 							}
1975 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1976 							int oldHP = hitstats->HP;
1977 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1978 							damage /= (1 + (int)resistance);
1979 							hit.entity->modHP(-damage);
1980 
1981 							// write the obituary
1982 							if (parent)
1983 							{
1984 								parent->killedByMonsterObituary(hit.entity);
1985 							}
1986 
1987 							// update enemy bar for attacker
1988 							if ( !strcmp(hitstats->name, "") )
1989 							{
1990 								if ( hitstats->type < KOBOLD ) //Original monster count
1991 								{
1992 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1993 								}
1994 								else if ( hitstats->type >= KOBOLD ) //New monsters
1995 								{
1996 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1997 								}
1998 							}
1999 							else
2000 							{
2001 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
2002 							}
2003 							if ( oldHP > 0 && hitstats->HP <= 0 && parent)
2004 							{
2005 								parent->awardXP( hit.entity, true, true );
2006 								if ( my->actmagicIsOrbiting == 2 && my->actmagicOrbitCastFromSpell == 0 && parent->behavior == &actPlayer )
2007 								{
2008 									if ( hitstats->type == LICH || hitstats->type == LICH_ICE || hitstats->type == LICH_FIRE )
2009 									{
2010 										if ( client_classes[parent->skill[2]] == CLASS_BREWER )
2011 										{
2012 											steamAchievementClient(parent->skill[2], "BARONY_ACH_SECRET_WEAPON");
2013 										}
2014 									}
2015 									steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_BOMBARDIER, STEAM_STAT_INT, 1);
2016 								}
2017 							}
2018 						}
2019 						else if ( hit.entity->behavior == &actDoor )
2020 						{
2021 							int damage = element->damage;
2022 							damage += (spellbookDamageBonus * damage);
2023 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
2024 							damage /= (1 + (int)resistance);
2025 
2026 							hit.entity->doorHandleDamageMagic(damage, *my, parent);
2027 							if ( my->actmagicProjectileArc > 0 )
2028 							{
2029 								Entity* caster = uidToEntity(spell->caster);
2030 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
2031 							}
2032 							if ( !(my->actmagicIsOrbiting == 2) )
2033 							{
2034 								my->removeLightField();
2035 								list_RemoveNode(my->mynode);
2036 							}
2037 							return;
2038 						}
2039 						else if ( hit.entity->behavior == &actChest )
2040 						{
2041 							int damage = element->damage;
2042 							damage += (spellbookDamageBonus * damage);
2043 							damage /= (1 + (int)resistance);
2044 							hit.entity->chestHandleDamageMagic(damage, *my, parent);
2045 							if ( my->actmagicProjectileArc > 0 )
2046 							{
2047 								Entity* caster = uidToEntity(spell->caster);
2048 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
2049 							}
2050 							if ( !(my->actmagicIsOrbiting == 2) )
2051 							{
2052 								my->removeLightField();
2053 								list_RemoveNode(my->mynode);
2054 							}
2055 							return;
2056 						}
2057 						else if (hit.entity->behavior == &actFurniture )
2058 						{
2059 							int damage = element->damage;
2060 							damage += (spellbookDamageBonus * damage);
2061 							damage /= (1 + (int)resistance);
2062 							hit.entity->furnitureHealth -= damage;
2063 							if ( hit.entity->furnitureHealth < 0 )
2064 							{
2065 								if ( parent )
2066 								{
2067 									if ( parent->behavior == &actPlayer )
2068 									{
2069 										switch ( hit.entity->furnitureType )
2070 										{
2071 											case FURNITURE_CHAIR:
2072 												messagePlayer(parent->skill[2], language[388]);
2073 												updateEnemyBar(parent, hit.entity, language[677], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
2074 												break;
2075 											case FURNITURE_TABLE:
2076 												messagePlayer(parent->skill[2], language[389]);
2077 												updateEnemyBar(parent, hit.entity, language[676], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
2078 												break;
2079 											case FURNITURE_BED:
2080 												messagePlayer(parent->skill[2], language[2508], language[2505]);
2081 												updateEnemyBar(parent, hit.entity, language[2505], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
2082 												break;
2083 											case FURNITURE_BUNKBED:
2084 												messagePlayer(parent->skill[2], language[2508], language[2506]);
2085 												updateEnemyBar(parent, hit.entity, language[2506], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
2086 												break;
2087 											case FURNITURE_PODIUM:
2088 												messagePlayer(parent->skill[2], language[2508], language[2507]);
2089 												updateEnemyBar(parent, hit.entity, language[2507], hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth);
2090 												break;
2091 											default:
2092 												break;
2093 										}
2094 									}
2095 								}
2096 							}
2097 							playSoundEntity(hit.entity, 28, 128);
2098 							if ( my->actmagicProjectileArc > 0 )
2099 							{
2100 								Entity* caster = uidToEntity(spell->caster);
2101 								spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
2102 							}
2103 						}
2104 					}
2105 				}
2106 				else if (!strcmp(element->name, spellElement_locking.name))
2107 				{
2108 					if ( hit.entity )
2109 					{
2110 						if (hit.entity->behavior == &actDoor)
2111 						{
2112 							if ( parent && parent->behavior == &actPlayer && MFLAG_DISABLEOPENING )
2113 							{
2114 								Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2115 								messagePlayerColor(parent->skill[2], 0xFFFFFFFF, language[3096], language[3097]);
2116 								messagePlayerColor(parent->skill[2], color, language[3101]); // disabled locking spell.
2117 							}
2118 							else
2119 							{
2120 								playSoundEntity(hit.entity, 92, 64);
2121 								hit.entity->skill[5] = 1; //Lock the door.
2122 								if ( parent )
2123 								{
2124 									if ( parent->behavior == &actPlayer )
2125 									{
2126 										messagePlayer(parent->skill[2], language[399]);
2127 									}
2128 								}
2129 							}
2130 						}
2131 						else if (hit.entity->behavior == &actChest)
2132 						{
2133 							//Lock chest
2134 							playSoundEntity(hit.entity, 92, 64);
2135 							if ( !hit.entity->chestLocked )
2136 							{
2137 								if ( parent && parent->behavior == &actPlayer && MFLAG_DISABLEOPENING )
2138 								{
2139 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2140 									messagePlayerColor(parent->skill[2], 0xFFFFFFFF, language[3096], language[3099]);
2141 									messagePlayerColor(parent->skill[2], color, language[3100]); // disabled locking spell.
2142 								}
2143 								else
2144 								{
2145 									hit.entity->lockChest();
2146 									if ( parent )
2147 									{
2148 										if ( parent->behavior == &actPlayer )
2149 										{
2150 											messagePlayer(parent->skill[2], language[400]);
2151 										}
2152 									}
2153 								}
2154 							}
2155 						}
2156 						else
2157 						{
2158 							if ( parent )
2159 								if ( parent->behavior == &actPlayer )
2160 								{
2161 									messagePlayer(parent->skill[2], language[401]);
2162 								}
2163 							if ( player >= 0 )
2164 							{
2165 								messagePlayer(player, language[401]);
2166 							}
2167 						}
2168 						spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
2169 					}
2170 				}
2171 				else if (!strcmp(element->name, spellElement_opening.name))
2172 				{
2173 					if (hit.entity)
2174 					{
2175 						if (hit.entity->behavior == &actDoor)
2176 						{
2177 							if ( MFLAG_DISABLEOPENING || hit.entity->doorDisableOpening == 1 )
2178 							{
2179 								if ( parent && parent->behavior == &actPlayer )
2180 								{
2181 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2182 									messagePlayerColor(parent->skill[2], 0xFFFFFFFF, language[3096], language[3097]);
2183 									messagePlayerColor(parent->skill[2], color, language[3101]); // disabled opening spell.
2184 								}
2185 							}
2186 							else
2187 							{
2188 								// Open the Door
2189 								playSoundEntity(hit.entity, 91, 64); // "UnlockDoor.ogg"
2190 								hit.entity->doorLocked = 0; // Unlocks the Door
2191 								hit.entity->doorPreventLockpickExploit = 1;
2192 
2193 								if ( !hit.entity->skill[0] && !hit.entity->skill[3] )
2194 								{
2195 									hit.entity->skill[3] = 1 + (my->x > hit.entity->x); // Opens the Door
2196 									playSoundEntity(hit.entity, 21, 96); // "UnlockDoor.ogg"
2197 								}
2198 								else if ( hit.entity->skill[0] && !hit.entity->skill[3] )
2199 								{
2200 									hit.entity->skill[3] = 1 + (my->x < hit.entity->x); // Opens the Door
2201 									playSoundEntity(hit.entity, 21, 96); // "UnlockDoor.ogg"
2202 								}
2203 								if ( parent )
2204 								{
2205 									if ( parent->behavior == &actPlayer)
2206 									{
2207 										messagePlayer(parent->skill[2], language[402]);
2208 									}
2209 								}
2210 							}
2211 						}
2212 						else if ( hit.entity->behavior == &actGate )
2213 						{
2214 							if ( MFLAG_DISABLEOPENING || hit.entity->gateDisableOpening == 1 )
2215 							{
2216 								if ( parent && parent->behavior == &actPlayer )
2217 								{
2218 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2219 									messagePlayerColor(parent->skill[2], 0xFFFFFFFF, language[3096], language[3098]);
2220 									messagePlayerColor(parent->skill[2], color, language[3102]); // disabled opening spell.
2221 								}
2222 							}
2223 							else
2224 							{
2225 								// Open the Gate
2226 								if ( (hit.entity->skill[28] != 2 && hit.entity->gateInverted == 0)
2227 									|| (hit.entity->skill[28] != 1 && hit.entity->gateInverted == 1) )
2228 								{
2229 									if ( hit.entity->gateInverted == 1 )
2230 									{
2231 										hit.entity->skill[28] = 1; // Depowers the Gate
2232 									}
2233 									else
2234 									{
2235 										hit.entity->skill[28] = 2; // Powers the Gate
2236 									}
2237 									if ( parent )
2238 									{
2239 										if ( parent->behavior == &actPlayer )
2240 										{
2241 											messagePlayer(parent->skill[2], language[403]); // "The spell opens the gate!"
2242 										}
2243 									}
2244 								}
2245 							}
2246 						}
2247 						else if ( hit.entity->behavior == &actChest )
2248 						{
2249 							// Unlock the Chest
2250 							if ( hit.entity->chestLocked )
2251 							{
2252 								if ( MFLAG_DISABLEOPENING )
2253 								{
2254 									if ( parent && parent->behavior == &actPlayer )
2255 									{
2256 										Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2257 										messagePlayerColor(parent->skill[2], 0xFFFFFFFF, language[3096], language[3099]);
2258 										messagePlayerColor(parent->skill[2], color, language[3100]); // disabled opening spell.
2259 									}
2260 								}
2261 								else
2262 								{
2263 									playSoundEntity(hit.entity, 91, 64); // "UnlockDoor.ogg"
2264 									hit.entity->unlockChest();
2265 									if ( parent )
2266 									{
2267 										if ( parent->behavior == &actPlayer)
2268 										{
2269 											messagePlayer(parent->skill[2], language[404]); // "The spell unlocks the chest!"
2270 										}
2271 									}
2272 								}
2273 							}
2274 						}
2275 						else if ( hit.entity->behavior == &actPowerCrystalBase )
2276 						{
2277 							Entity* childentity = nullptr;
2278 							if ( hit.entity->children.first )
2279 							{
2280 								childentity = static_cast<Entity*>((&hit.entity->children)->first->element);
2281 								if ( childentity != nullptr )
2282 								{
2283 									//Unlock crystal
2284 									if ( childentity->crystalSpellToActivate )
2285 									{
2286 										playSoundEntity(hit.entity, 151, 128);
2287 										childentity->crystalSpellToActivate = 0;
2288 										// send the clients the updated skill.
2289 										serverUpdateEntitySkill(childentity, 10);
2290 										if ( parent )
2291 										{
2292 											if ( parent->behavior == &actPlayer )
2293 											{
2294 												messagePlayer(parent->skill[2], language[2358]);
2295 											}
2296 										}
2297 									}
2298 								}
2299 							}
2300 						}
2301 						else
2302 						{
2303 							if ( parent )
2304 							{
2305 								if ( parent->behavior == &actPlayer )
2306 								{
2307 									messagePlayer(parent->skill[2], language[401]); // "No telling what it did..."
2308 								}
2309 							}
2310 
2311 							if ( player >= 0 )
2312 							{
2313 								messagePlayer(player, language[401]); // "No telling what it did..."
2314 							}
2315 						}
2316 
2317 						spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
2318 					}
2319 				}
2320 				else if (!strcmp(element->name, spellElement_dig.name))
2321 				{
2322 					if ( !hit.entity )
2323 					{
2324 						if ( hit.mapx >= 1 && hit.mapx < map.width - 1 && hit.mapy >= 1 && hit.mapy < map.height - 1 )
2325 						{
2326 							magicDig(parent, my, 8, 4);
2327 						}
2328 					}
2329 					else
2330 					{
2331 						if ( hit.entity->behavior == &actBoulder )
2332 						{
2333 							if ( hit.entity->sprite == 989 || hit.entity->sprite == 990 )
2334 							{
2335 								magicDig(parent, my, 0, 1);
2336 							}
2337 							else
2338 							{
2339 								magicDig(parent, my, 8, 4);
2340 							}
2341 						}
2342 						else
2343 						{
2344 							if ( parent )
2345 								if ( parent->behavior == &actPlayer )
2346 								{
2347 									messagePlayer(parent->skill[2], language[401]);
2348 								}
2349 							if ( player >= 0 )
2350 							{
2351 								messagePlayer(player, language[401]);
2352 							}
2353 						}
2354 					}
2355 				}
2356 				else if ( !strcmp(element->name, spellElement_stoneblood.name) )
2357 				{
2358 					if ( hit.entity )
2359 					{
2360 						if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
2361 						{
2362 							playSoundEntity(hit.entity, 172, 64); //TODO: Paralyze spell sound.
2363 							int effectDuration = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
2364 							effectDuration /= (1 + (int)resistance);
2365 							if ( hit.entity->setEffect(EFF_PARALYZED, true, effectDuration, false) )
2366 							{
2367 								if ( hit.entity->behavior == &actPlayer )
2368 								{
2369 									serverUpdateEffects(hit.entity->skill[2]);
2370 								}
2371 								// update enemy bar for attacker
2372 								if ( parent )
2373 								{
2374 									Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2375 									if ( parent->behavior == &actPlayer )
2376 									{
2377 										messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2421], language[2420], MSG_COMBAT);
2378 									}
2379 								}
2380 
2381 								Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2382 								if ( player >= 0 )
2383 								{
2384 									messagePlayerColor(player, color, language[2422]);
2385 								}
2386 							}
2387 							else
2388 							{
2389 								if ( parent )
2390 								{
2391 									Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2392 									if ( parent->behavior == &actPlayer )
2393 									{
2394 										messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
2395 									}
2396 								}
2397 							}
2398 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
2399 						}
2400 					}
2401 				}
2402 				else if ( !strcmp(element->name, spellElement_bleed.name) )
2403 				{
2404 					playSoundEntity(my, 173, 128);
2405 					if ( hit.entity )
2406 					{
2407 						if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
2408 						{
2409 							Entity* parent = uidToEntity(my->parent);
2410 							playSoundEntity(my, 173, 64);
2411 							playSoundEntity(hit.entity, 28, 128);
2412 							int damage = element->damage;
2413 							damage += (spellbookDamageBonus * damage);
2414 							//damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
2415 							damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
2416 							if ( parent )
2417 							{
2418 								Stat* casterStats = parent->getStats();
2419 								if ( casterStats && casterStats->type == LICH_FIRE && parent->monsterLichAllyStatus == LICH_ALLY_DEAD )
2420 								{
2421 									damage *= 2;
2422 								}
2423 							}
2424 							damage /= (1 + (int)resistance);
2425 
2426 							hit.entity->modHP(-damage);
2427 
2428 							// write the obituary
2429 							if ( parent )
2430 							{
2431 								parent->killedByMonsterObituary(hit.entity);
2432 							}
2433 
2434 							int bleedDuration = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
2435 							bleedDuration /= (1 + (int)resistance);
2436 							if ( hit.entity->setEffect(EFF_BLEEDING, true, bleedDuration, true) )
2437 							{
2438 								if ( parent )
2439 								{
2440 									hitstats->bleedInflictedBy = static_cast<Sint32>(my->parent);
2441 								}
2442 							}
2443 							hitstats->EFFECTS[EFF_SLOW] = true;
2444 							hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
2445 							hitstats->EFFECTS_TIMERS[EFF_SLOW] /= 4;
2446 							hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance);
2447 							if ( hit.entity->behavior == &actPlayer )
2448 							{
2449 								serverUpdateEffects(hit.entity->skill[2]);
2450 							}
2451 							// update enemy bar for attacker
2452 							if ( parent )
2453 							{
2454 								Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2455 								if ( parent->behavior == &actPlayer )
2456 								{
2457 									messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2424], language[2423], MSG_COMBAT);
2458 								}
2459 							}
2460 
2461 							// write the obituary
2462 							if ( parent )
2463 							{
2464 								parent->killedByMonsterObituary(hit.entity);
2465 							}
2466 
2467 							// update enemy bar for attacker
2468 							if ( !strcmp(hitstats->name, "") )
2469 							{
2470 								if ( hitstats->type < KOBOLD ) //Original monster count
2471 								{
2472 									updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
2473 								}
2474 								else if ( hitstats->type >= KOBOLD ) //New monsters
2475 								{
2476 									updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
2477 								}
2478 							}
2479 							else
2480 							{
2481 								updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
2482 							}
2483 
2484 							if ( hitstats->HP <= 0 && parent )
2485 							{
2486 								parent->awardXP(hit.entity, true, true);
2487 
2488 								if ( hit.entity->behavior == &actMonster )
2489 								{
2490 									bool tryBloodVial = false;
2491 									if ( gibtype[hitstats->type] == 1 || gibtype[hitstats->type] == 2 )
2492 									{
2493 										for ( c = 0; c < MAXPLAYERS; ++c )
2494 										{
2495 											if ( players[c]->entity && players[c]->entity->playerRequiresBloodToSustain() )
2496 											{
2497 												tryBloodVial = true;
2498 												break;
2499 											}
2500 										}
2501 										if ( tryBloodVial )
2502 										{
2503 											Item* blood = newItem(FOOD_BLOOD, EXCELLENT, 0, 1, gibtype[hitstats->type] - 1, true, &hitstats->inventory);
2504 										}
2505 									}
2506 								}
2507 							}
2508 
2509 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2510 							if ( player >= 0 )
2511 							{
2512 								messagePlayerColor(player, color, language[2425]);
2513 							}
2514 							spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my->sprite);
2515 							for ( int gibs = 0; gibs < 10; ++gibs )
2516 							{
2517 								Entity* gib = spawnGib(hit.entity);
2518 								serverSpawnGibForClient(gib);
2519 							}
2520 						}
2521 					}
2522 				}
2523 				else if ( !strcmp(element->name, spellElement_dominate.name) )
2524 				{
2525 					Entity *caster = uidToEntity(spell->caster);
2526 					if ( caster )
2527 					{
2528 						if ( spellEffectDominate(*my, *element, *caster, parent) )
2529 						{
2530 							//Success
2531 						}
2532 					}
2533 				}
2534 				else if ( !strcmp(element->name, spellElement_acidSpray.name) )
2535 				{
2536 					Entity* caster = uidToEntity(spell->caster);
2537 					if ( caster )
2538 					{
2539 						spellEffectAcid(*my, *element, parent, resistance);
2540 					}
2541 				}
2542 				else if ( !strcmp(element->name, spellElement_poison.name) )
2543 				{
2544 					Entity* caster = uidToEntity(spell->caster);
2545 					if ( caster )
2546 					{
2547 						spellEffectPoison(*my, *element, parent, resistance);
2548 					}
2549 				}
2550 				else if ( !strcmp(element->name, spellElement_sprayWeb.name) )
2551 				{
2552 					Entity* caster = uidToEntity(spell->caster);
2553 					if ( caster )
2554 					{
2555 						spellEffectSprayWeb(*my, *element, parent, resistance);
2556 					}
2557 				}
2558 				else if ( !strcmp(element->name, spellElement_stealWeapon.name) )
2559 				{
2560 					Entity* caster = uidToEntity(spell->caster);
2561 					if ( caster )
2562 					{
2563 						spellEffectStealWeapon(*my, *element, parent, resistance);
2564 					}
2565 				}
2566 				else if ( !strcmp(element->name, spellElement_drainSoul.name) )
2567 				{
2568 					Entity* caster = uidToEntity(spell->caster);
2569 					if ( caster )
2570 					{
2571 						spellEffectDrainSoul(*my, *element, parent, resistance);
2572 					}
2573 				}
2574 				else if ( !strcmp(element->name, spellElement_charmMonster.name) )
2575 				{
2576 					Entity* caster = uidToEntity(spell->caster);
2577 					if ( caster )
2578 					{
2579 						spellEffectCharmMonster(*my, *element, parent, resistance, static_cast<bool>(my->actmagicCastByMagicstaff));
2580 					}
2581 				}
2582 				else if ( !strcmp(element->name, spellElement_telePull.name) )
2583 				{
2584 					Entity* caster = uidToEntity(spell->caster);
2585 					if ( caster )
2586 					{
2587 						spellEffectTeleportPull(my, *element, parent, hit.entity, resistance);
2588 					}
2589 				}
2590 				else if ( !strcmp(element->name, spellElement_shadowTag.name) )
2591 				{
2592 					Entity* caster = uidToEntity(spell->caster);
2593 					if ( caster )
2594 					{
2595 						spellEffectShadowTag(*my, *element, parent, resistance);
2596 					}
2597 				}
2598 				else if ( !strcmp(element->name, spellElement_demonIllusion.name) )
2599 				{
2600 					Entity* caster = uidToEntity(spell->caster);
2601 					if ( caster )
2602 					{
2603 						spellEffectDemonIllusion(*my, *element, parent, hit.entity, resistance);
2604 					}
2605 				}
2606 
2607 				if ( hitstats )
2608 				{
2609 					if ( player >= 0 )
2610 					{
2611 						entityHealth -= hitstats->HP;
2612 						if ( entityHealth > 0 )
2613 						{
2614 							// entity took damage, shake screen.
2615 							if ( multiplayer == SERVER && player > 0 )
2616 							{
2617 								strcpy((char*)net_packet->data, "SHAK");
2618 								net_packet->data[4] = 10; // turns into .1
2619 								net_packet->data[5] = 10;
2620 								net_packet->address.host = net_clients[player - 1].host;
2621 								net_packet->address.port = net_clients[player - 1].port;
2622 								net_packet->len = 6;
2623 								sendPacketSafe(net_sock, -1, net_packet, player - 1);
2624 							}
2625 							else if (player == 0 || splitscreen)
2626 							{
2627 								cameravars[player].shakex += .1;
2628 								cameravars[player].shakey += 10;
2629 							}
2630 						}
2631 					}
2632 					else
2633 					{
2634 						if ( parent && parent->behavior == &actPlayer )
2635 						{
2636 							if ( hitstats->HP <= 0 )
2637 							{
2638 								if ( hitstats->type == SCARAB )
2639 								{
2640 									// killed a scarab with magic.
2641 									steamAchievementEntity(parent, "BARONY_ACH_THICK_SKULL");
2642 								}
2643 								if ( my->actmagicMirrorReflected == 1 && static_cast<Uint32>(my->actmagicMirrorReflectedCaster) == hit.entity->getUID() )
2644 								{
2645 									// killed a monster with it's own spell with mirror reflection.
2646 									steamAchievementEntity(parent, "BARONY_ACH_NARCISSIST");
2647 								}
2648 								if ( stats[parent->skill[2]] && stats[parent->skill[2]]->playerRace == RACE_INSECTOID && stats[parent->skill[2]]->appearance == 0 )
2649 								{
2650 									if ( !achievementObserver.playerAchievements[parent->skill[2]].gastricBypass )
2651 									{
2652 										if ( achievementObserver.playerAchievements[parent->skill[2]].gastricBypassSpell.first == spell->ID )
2653 										{
2654 											Uint32 oldTicks = achievementObserver.playerAchievements[parent->skill[2]].gastricBypassSpell.second;
2655 											if ( parent->ticks - oldTicks < TICKS_PER_SECOND * 5 )
2656 											{
2657 												steamAchievementEntity(parent, "BARONY_ACH_GASTRIC_BYPASS");
2658 												achievementObserver.playerAchievements[parent->skill[2]].gastricBypass = true;
2659 											}
2660 										}
2661 									}
2662 								}
2663 							}
2664 						}
2665 					}
2666 				}
2667 
2668 				if ( my->actmagicProjectileArc > 0 )
2669 				{
2670 					Entity* caster = uidToEntity(spell->caster);
2671 					spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr);
2672 				}
2673 
2674 				if ( !(my->actmagicIsOrbiting == 2) )
2675 				{
2676 					my->removeLightField();
2677 					if ( my->mynode )
2678 					{
2679 						list_RemoveNode(my->mynode);
2680 					}
2681 				}
2682 				return;
2683 			}
2684 		}
2685 
2686 		//Go down two levels to the next element. This will need to get re-written shortly.
2687 		node = spell->elements.first;
2688 		element = (spellElement_t*)node->element;
2689 		//element = (spellElement_t *)spell->elements->first->element;
2690 		//element = (spellElement_t *)element->elements->first->element; //Go down two levels to the second element.
2691 		node = element->elements.first;
2692 		element = (spellElement_t*)node->element;
2693 		if (!strcmp(element->name, spellElement_fire.name) || !strcmp(element->name, spellElement_lightning.name))
2694 		{
2695 			//Make the ball light up stuff as it travels.
2696 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
2697 
2698 			if ( flickerLights )
2699 			{
2700 				//Magic light ball will never flicker if this setting is disabled.
2701 				lightball_flicker++;
2702 			}
2703 			my->skill[2] = -11; // so clients know to create a light field
2704 
2705 			if (lightball_flicker > 5)
2706 			{
2707 				lightball_lighting = (lightball_lighting == 1) + 1;
2708 
2709 				if (lightball_lighting == 1)
2710 				{
2711 					my->removeLightField();
2712 					my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
2713 				}
2714 				else
2715 				{
2716 					my->removeLightField();
2717 					my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 174);
2718 				}
2719 				lightball_flicker = 0;
2720 			}
2721 		}
2722 		else
2723 		{
2724 			my->skill[2] = -12; // so clients know to simply spawn particles
2725 		}
2726 
2727 		// spawn particles
2728 		spawnMagicParticle(my);
2729 	}
2730 	else
2731 	{
2732 		//Any init stuff that needs to happen goes here.
2733 		magic_init = 1;
2734 		my->skill[2] = -7; // ordinarily the client won't do anything with this entity
2735 		if ( my->actmagicIsOrbiting == 1 || my->actmagicIsOrbiting == 2 )
2736 		{
2737 			MAGIC_MAXLIFE = my->actmagicOrbitLifetime;
2738 		}
2739 		else if ( my->actmagicIsVertical != MAGIC_ISVERTICAL_NONE )
2740 		{
2741 			MAGIC_MAXLIFE = 512;
2742 		}
2743 	}
2744 }
2745 
actMagicClient(Entity * my)2746 void actMagicClient(Entity* my)
2747 {
2748 	my->removeLightField();
2749 	my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
2750 
2751 	if ( flickerLights )
2752 	{
2753 		//Magic light ball will never flicker if this setting is disabled.
2754 		lightball_flicker++;
2755 	}
2756 	my->skill[2] = -11; // so clients know to create a light field
2757 
2758 	if (lightball_flicker > 5)
2759 	{
2760 		lightball_lighting = (lightball_lighting == 1) + 1;
2761 
2762 		if (lightball_lighting == 1)
2763 		{
2764 			my->removeLightField();
2765 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 192);
2766 		}
2767 		else
2768 		{
2769 			my->removeLightField();
2770 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 8, 174);
2771 		}
2772 		lightball_flicker = 0;
2773 	}
2774 
2775 	// spawn particles
2776 	spawnMagicParticle(my);
2777 }
2778 
actMagicClientNoLight(Entity * my)2779 void actMagicClientNoLight(Entity* my)
2780 {
2781 	spawnMagicParticle(my); // simply spawn particles
2782 }
2783 
actMagicParticle(Entity * my)2784 void actMagicParticle(Entity* my)
2785 {
2786 	my->x += my->vel_x;
2787 	my->y += my->vel_y;
2788 	my->z += my->vel_z;
2789 	if ( my->sprite == 943 || my->sprite == 979 )
2790 	{
2791 		my->scalex -= 0.05;
2792 		my->scaley -= 0.05;
2793 		my->scalez -= 0.05;
2794 	}
2795 	my->scalex -= 0.05;
2796 	my->scaley -= 0.05;
2797 	my->scalez -= 0.05;
2798 	if ( my->scalex <= 0 )
2799 	{
2800 		my->scalex = 0;
2801 		my->scaley = 0;
2802 		my->scalez = 0;
2803 		list_RemoveNode(my->mynode);
2804 		return;
2805 	}
2806 }
2807 
spawnMagicParticle(Entity * parentent)2808 Entity* spawnMagicParticle(Entity* parentent)
2809 {
2810 	if ( !parentent )
2811 	{
2812 		return nullptr;
2813 	}
2814 	Entity* entity;
2815 
2816 	entity = newEntity(parentent->sprite, 1, map.entities, nullptr); //Particle entity.
2817 
2818 	entity->x = parentent->x + (rand() % 50 - 25) / 20.f;
2819 	entity->y = parentent->y + (rand() % 50 - 25) / 20.f;
2820 	entity->z = parentent->z + (rand() % 50 - 25) / 20.f;
2821 	entity->scalex = 0.7;
2822 	entity->scaley = 0.7;
2823 	entity->scalez = 0.7;
2824 	entity->sizex = 1;
2825 	entity->sizey = 1;
2826 	entity->yaw = parentent->yaw;
2827 	entity->pitch = parentent->pitch;
2828 	entity->roll = parentent->roll;
2829 	entity->flags[NOUPDATE] = true;
2830 	entity->flags[PASSABLE] = true;
2831 	entity->flags[BRIGHT] = true;
2832 	entity->flags[UNCLICKABLE] = true;
2833 	entity->flags[NOUPDATE] = true;
2834 	entity->flags[UPDATENEEDED] = false;
2835 	entity->behavior = &actMagicParticle;
2836 	if ( multiplayer != CLIENT )
2837 	{
2838 		entity_uids--;
2839 	}
2840 	entity->setUID(-3);
2841 
2842 	return entity;
2843 }
2844 
spawnMagicParticleCustom(Entity * parentent,int sprite,real_t scale,real_t spreadReduce)2845 Entity* spawnMagicParticleCustom(Entity* parentent, int sprite, real_t scale, real_t spreadReduce)
2846 {
2847 	if ( !parentent )
2848 	{
2849 		return nullptr;
2850 	}
2851 	Entity* entity;
2852 
2853 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
2854 
2855 	int size = 50 / spreadReduce;
2856 	entity->x = parentent->x + (rand() % size - size / 2) / 20.f;
2857 	entity->y = parentent->y + (rand() % size - size / 2) / 20.f;
2858 	entity->z = parentent->z + (rand() % size - size / 2) / 20.f;
2859 	entity->scalex = scale;
2860 	entity->scaley = scale;
2861 	entity->scalez = scale;
2862 	entity->sizex = 1;
2863 	entity->sizey = 1;
2864 	entity->yaw = parentent->yaw;
2865 	entity->pitch = parentent->pitch;
2866 	entity->roll = parentent->roll;
2867 	entity->flags[NOUPDATE] = true;
2868 	entity->flags[PASSABLE] = true;
2869 	entity->flags[BRIGHT] = true;
2870 	entity->flags[UNCLICKABLE] = true;
2871 	entity->flags[NOUPDATE] = true;
2872 	entity->flags[UPDATENEEDED] = false;
2873 	entity->behavior = &actMagicParticle;
2874 	if ( multiplayer != CLIENT )
2875 	{
2876 		entity_uids--;
2877 	}
2878 	entity->setUID(-3);
2879 
2880 	return entity;
2881 }
2882 
spawnMagicEffectParticles(Sint16 x,Sint16 y,Sint16 z,Uint32 sprite)2883 void spawnMagicEffectParticles(Sint16 x, Sint16 y, Sint16 z, Uint32 sprite)
2884 {
2885 	int c;
2886 	if ( multiplayer == SERVER )
2887 	{
2888 		for ( c = 1; c < MAXPLAYERS; c++ )
2889 		{
2890 			if ( client_disconnected[c] )
2891 			{
2892 				continue;
2893 			}
2894 			strcpy((char*)net_packet->data, "MAGE");
2895 			SDLNet_Write16(x, &net_packet->data[4]);
2896 			SDLNet_Write16(y, &net_packet->data[6]);
2897 			SDLNet_Write16(z, &net_packet->data[8]);
2898 			SDLNet_Write32(sprite, &net_packet->data[10]);
2899 			net_packet->address.host = net_clients[c - 1].host;
2900 			net_packet->address.port = net_clients[c - 1].port;
2901 			net_packet->len = 14;
2902 			sendPacketSafe(net_sock, -1, net_packet, c - 1);
2903 		}
2904 	}
2905 
2906 	// boosty boost
2907 	for ( c = 0; c < 10; c++ )
2908 	{
2909 		Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
2910 		entity->x = x - 5 + rand() % 11;
2911 		entity->y = y - 5 + rand() % 11;
2912 		entity->z = z - 10 + rand() % 21;
2913 		entity->scalex = 0.7;
2914 		entity->scaley = 0.7;
2915 		entity->scalez = 0.7;
2916 		entity->sizex = 1;
2917 		entity->sizey = 1;
2918 		entity->yaw = (rand() % 360) * PI / 180.f;
2919 		entity->flags[PASSABLE] = true;
2920 		entity->flags[BRIGHT] = true;
2921 		entity->flags[NOUPDATE] = true;
2922 		entity->flags[UNCLICKABLE] = true;
2923 		entity->behavior = &actMagicParticle;
2924 		entity->vel_z = -1;
2925 		if ( multiplayer != CLIENT )
2926 		{
2927 			entity_uids--;
2928 		}
2929 		entity->setUID(-3);
2930 	}
2931 }
2932 
createParticle1(Entity * caster,int player)2933 void createParticle1(Entity* caster, int player)
2934 {
2935 	Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Particle entity.
2936 	entity->sizex = 0;
2937 	entity->sizey = 0;
2938 	entity->x = caster->x;
2939 	entity->y = caster->y;
2940 	entity->z = -7;
2941 	entity->vel_z = 0.3;
2942 	entity->yaw = (rand() % 360) * PI / 180.0;
2943 	entity->skill[0] = 50;
2944 	entity->skill[1] = player;
2945 	entity->fskill[0] = 0.03;
2946 	entity->light = lightSphereShadow(entity->x / 16, entity->y / 16, 3, 192);
2947 	entity->behavior = &actParticleCircle;
2948 	entity->flags[PASSABLE] = true;
2949 	entity->flags[INVISIBLE] = true;
2950 	entity->setUID(-3);
2951 
2952 }
2953 
createParticleCircling(Entity * parent,int duration,int sprite)2954 void createParticleCircling(Entity* parent, int duration, int sprite)
2955 {
2956 	if ( !parent )
2957 	{
2958 		return;
2959 	}
2960 
2961 	Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
2962 	entity->sizex = 1;
2963 	entity->sizey = 1;
2964 	entity->x = parent->x;
2965 	entity->y = parent->y;
2966 	entity->focalx = 8;
2967 	entity->z = -7;
2968 	entity->vel_z = 0.15;
2969 	entity->yaw = (rand() % 360) * PI / 180.0;
2970 	entity->skill[0] = duration;
2971 	entity->skill[1] = -1;
2972 	//entity->scalex = 0.01;
2973 	//entity->scaley = 0.01;
2974 	entity->fskill[0] = -0.1;
2975 	entity->behavior = &actParticleCircle;
2976 	entity->flags[PASSABLE] = true;
2977 	entity->setUID(-3);
2978 
2979 	real_t tmp = entity->yaw;
2980 
2981 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
2982 	entity->sizex = 1;
2983 	entity->sizey = 1;
2984 	entity->x = parent->x;
2985 	entity->y = parent->y;
2986 	entity->focalx = 8;
2987 	entity->z = -7;
2988 	entity->vel_z = 0.15;
2989 	entity->yaw = tmp + (2 * PI / 3);
2990 	entity->particleDuration = duration;
2991 	entity->skill[1] = -1;
2992 	//entity->scalex = 0.01;
2993 	//entity->scaley = 0.01;
2994 	entity->fskill[0] = -0.1;
2995 	entity->behavior = &actParticleCircle;
2996 	entity->flags[PASSABLE] = true;
2997 	entity->setUID(-3);
2998 
2999 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3000 	entity->sizex = 1;
3001 	entity->sizey = 1;
3002 	entity->x = parent->x;
3003 	entity->y = parent->y;
3004 	entity->focalx = 8;
3005 	entity->z = -7;
3006 	entity->vel_z = 0.15;
3007 	entity->yaw = tmp - (2 * PI / 3);
3008 	entity->particleDuration = duration;
3009 	entity->skill[1] = -1;
3010 	//entity->scalex = 0.01;
3011 	//entity->scaley = 0.01;
3012 	entity->fskill[0] = -0.1;
3013 	entity->behavior = &actParticleCircle;
3014 	entity->flags[PASSABLE] = true;
3015 	entity->setUID(-3);
3016 
3017 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3018 	entity->sizex = 1;
3019 	entity->sizey = 1;
3020 	entity->x = parent->x;
3021 	entity->y = parent->y;
3022 	entity->focalx = 16;
3023 	entity->z = -12;
3024 	entity->vel_z = 0.2;
3025 	entity->yaw = tmp;
3026 	entity->particleDuration = duration;
3027 	entity->skill[1] = -1;
3028 	//entity->scalex = 0.01;
3029 	//entity->scaley = 0.01;
3030 	entity->fskill[0] = 0.1;
3031 	entity->behavior = &actParticleCircle;
3032 	entity->flags[PASSABLE] = true;
3033 	entity->setUID(-3);
3034 
3035 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3036 	entity->sizex = 1;
3037 	entity->sizey = 1;
3038 	entity->x = parent->x;
3039 	entity->y = parent->y;
3040 	entity->focalx = 16;
3041 	entity->z = -12;
3042 	entity->vel_z = 0.2;
3043 	entity->yaw = tmp + (2 * PI / 3);
3044 	entity->particleDuration = duration;
3045 	entity->skill[1] = -1;
3046 	//entity->scalex = 0.01;
3047 	//entity->scaley = 0.01;
3048 	entity->fskill[0] = 0.1;
3049 	entity->behavior = &actParticleCircle;
3050 	entity->flags[PASSABLE] = true;
3051 	entity->setUID(-3);
3052 
3053 	entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3054 	entity->sizex = 1;
3055 	entity->sizey = 1;
3056 	entity->x = parent->x;
3057 	entity->y = parent->y;
3058 	entity->focalx = 16;
3059 	entity->z = -12;
3060 	entity->vel_z = 0.2;
3061 	entity->yaw = tmp - (2 * PI / 3);
3062 	entity->particleDuration = duration;
3063 	entity->skill[1] = -1;
3064 	//entity->scalex = 0.01;
3065 	//entity->scaley = 0.01;
3066 	entity->fskill[0] = 0.1;
3067 	entity->behavior = &actParticleCircle;
3068 	entity->flags[PASSABLE] = true;
3069 	entity->setUID(-3);
3070 }
3071 
3072 #define PARTICLE_LIFE my->skill[0]
3073 #define PARTICLE_CASTER my->skill[1]
3074 
actParticleCircle(Entity * my)3075 void actParticleCircle(Entity* my)
3076 {
3077 	if ( PARTICLE_LIFE < 0 )
3078 	{
3079 		list_RemoveNode(my->mynode);
3080 		return;
3081 	}
3082 	else
3083 	{
3084 		--PARTICLE_LIFE;
3085 		my->yaw += my->fskill[0];
3086 		if ( my->fskill[0] < 0.4 && my->fskill[0] > (-0.4) )
3087 		{
3088 			my->fskill[0] = my->fskill[0] * 1.05;
3089 		}
3090 		my->z += my->vel_z;
3091 		if ( my->focalx > 0.05 )
3092 		{
3093 			if ( my->vel_z == 0.15 )
3094 			{
3095 				my->focalx = my->focalx * 0.97;
3096 			}
3097 			else
3098 			{
3099 				my->focalx = my->focalx * 0.97;
3100 			}
3101 		}
3102 		my->scalex *= 0.995;
3103 		my->scaley *= 0.995;
3104 		my->scalez *= 0.995;
3105 	}
3106 }
3107 
createParticleDot(Entity * parent)3108 void createParticleDot(Entity* parent)
3109 {
3110 	if ( !parent )
3111 	{
3112 		return;
3113 	}
3114 	for ( int c = 0; c < 50; c++ )
3115 	{
3116 		Entity* entity = newEntity(576, 1, map.entities, nullptr); //Particle entity.
3117 		entity->sizex = 1;
3118 		entity->sizey = 1;
3119 		entity->x = parent->x + (-4 + rand() % 9);
3120 		entity->y = parent->y + (-4 + rand() % 9);
3121 		entity->z = 7.5 + rand()%50;
3122 		entity->vel_z = -1;
3123 		//entity->yaw = (rand() % 360) * PI / 180.0;
3124 		entity->skill[0] = 10 + rand()% 50;
3125 		entity->behavior = &actParticleDot;
3126 		entity->flags[PASSABLE] = true;
3127 		entity->flags[NOUPDATE] = true;
3128 		entity->flags[UNCLICKABLE] = true;
3129 		if ( multiplayer != CLIENT )
3130 		{
3131 			entity_uids--;
3132 		}
3133 		entity->setUID(-3);
3134 	}
3135 }
3136 
createParticleAestheticOrbit(Entity * parent,int sprite,int duration,int effectType)3137 Entity* createParticleAestheticOrbit(Entity* parent, int sprite, int duration, int effectType)
3138 {
3139 	if ( !parent )
3140 	{
3141 		return nullptr;
3142 	}
3143 	Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3144 	entity->sizex = 1;
3145 	entity->sizey = 1;
3146 	entity->actmagicOrbitDist = 6;
3147 	entity->yaw = parent->yaw;
3148 	entity->x = parent->x + entity->actmagicOrbitDist * cos(entity->yaw);
3149 	entity->y = parent->y + entity->actmagicOrbitDist * sin(entity->yaw);
3150 	entity->z = parent->z;
3151 	entity->skill[1] = effectType;
3152 	entity->parent = parent->getUID();
3153 	//entity->vel_z = -1;
3154 	//entity->yaw = (rand() % 360) * PI / 180.0;
3155 	entity->skill[0] = duration;
3156 	entity->fskill[0] = entity->x;
3157 	entity->fskill[1] = entity->y;
3158 	entity->behavior = &actParticleAestheticOrbit;
3159 	entity->flags[PASSABLE] = true;
3160 	entity->flags[NOUPDATE] = true;
3161 	entity->flags[BRIGHT] = true;
3162 	entity->flags[UNCLICKABLE] = true;
3163 	if ( multiplayer != CLIENT )
3164 	{
3165 		entity_uids--;
3166 	}
3167 	entity->setUID(-3);
3168 	return entity;
3169 }
3170 
createParticleRock(Entity * parent)3171 void createParticleRock(Entity* parent)
3172 {
3173 	if ( !parent )
3174 	{
3175 		return;
3176 	}
3177 	for ( int c = 0; c < 5; c++ )
3178 	{
3179 		Entity* entity = newEntity(78, 1, map.entities, nullptr); //Particle entity.
3180 		entity->sizex = 1;
3181 		entity->sizey = 1;
3182 		entity->x = parent->x + (-4 + rand() % 9);
3183 		entity->y = parent->y + (-4 + rand() % 9);
3184 		entity->z = 7.5;
3185 		entity->yaw = c * 2 * PI / 5;//(rand() % 360) * PI / 180.0;
3186 		entity->roll = (rand() % 360) * PI / 180.0;
3187 
3188 		entity->vel_x = 0.2 * cos(entity->yaw);
3189 		entity->vel_y = 0.2 * sin(entity->yaw);
3190 		entity->vel_z = 3;// 0.25 - (rand() % 5) / 10.0;
3191 
3192 		entity->skill[0] = 50; // particle life
3193 		entity->skill[1] = 0; // particle direction, 0 = upwards, 1 = downwards.
3194 
3195 		entity->behavior = &actParticleRock;
3196 		entity->flags[PASSABLE] = true;
3197 		entity->flags[NOUPDATE] = true;
3198 		entity->flags[UNCLICKABLE] = true;
3199 		if ( multiplayer != CLIENT )
3200 		{
3201 			entity_uids--;
3202 		}
3203 		entity->setUID(-3);
3204 	}
3205 }
3206 
actParticleRock(Entity * my)3207 void actParticleRock(Entity* my)
3208 {
3209 	if ( PARTICLE_LIFE < 0 || my->z > 10 )
3210 	{
3211 		list_RemoveNode(my->mynode);
3212 	}
3213 	else
3214 	{
3215 		--PARTICLE_LIFE;
3216 		my->x += my->vel_x;
3217 		my->y += my->vel_y;
3218 
3219 		my->roll += 0.1;
3220 
3221 		if ( my->vel_z < 0.01 )
3222 		{
3223 			my->skill[1] = 1; // start moving downwards
3224 			my->vel_z = 0.1;
3225 		}
3226 
3227 		if ( my->skill[1] == 0 ) // upwards motion
3228 		{
3229 			my->z -= my->vel_z;
3230 			my->vel_z *= 0.7;
3231 		}
3232 		else // downwards motion
3233 		{
3234 			my->z += my->vel_z;
3235 			my->vel_z *= 1.1;
3236 		}
3237 	}
3238 	return;
3239 }
3240 
actParticleDot(Entity * my)3241 void actParticleDot(Entity* my)
3242 {
3243 	if ( PARTICLE_LIFE < 0 )
3244 	{
3245 		list_RemoveNode(my->mynode);
3246 	}
3247 	else
3248 	{
3249 		--PARTICLE_LIFE;
3250 		my->z += my->vel_z;
3251 		//my->z -= 0.01;
3252 	}
3253 	return;
3254 }
3255 
actParticleAestheticOrbit(Entity * my)3256 void actParticleAestheticOrbit(Entity* my)
3257 {
3258 	if ( PARTICLE_LIFE < 0 )
3259 	{
3260 		list_RemoveNode(my->mynode);
3261 	}
3262 	else
3263 	{
3264 		Entity* parent = uidToEntity(my->parent);
3265 		if ( !parent )
3266 		{
3267 			list_RemoveNode(my->mynode);
3268 			return;
3269 		}
3270 		Stat* stats = parent->getStats();
3271 		if ( my->skill[1] == PARTICLE_EFFECT_SPELLBOT_ORBIT )
3272 		{
3273 			my->yaw = parent->yaw;
3274 			my->x = parent->x + 2 * cos(parent->yaw);
3275 			my->y = parent->y + 2 * sin(parent->yaw);
3276 			my->z = parent->z - 1.5;
3277 			Entity* particle = spawnMagicParticle(my);
3278 			if ( particle )
3279 			{
3280 				particle->x = my->x + (-10 + rand() % 21) / (50.f);
3281 				particle->y = my->y + (-10 + rand() % 21) / (50.f);
3282 				particle->z = my->z + (-10 + rand() % 21) / (50.f);
3283 				particle->scalex = my->scalex;
3284 				particle->scaley = my->scaley;
3285 				particle->scalez = my->scalez;
3286 			}
3287 			//spawnMagicParticle(my);
3288 		}
3289 		else if ( my->skill[1] == PARTICLE_EFFECT_SPELL_WEB_ORBIT )
3290 		{
3291 			if ( my->sprite == 863 && !stats->EFFECTS[EFF_WEBBED] )
3292 			{
3293 				list_RemoveNode(my->mynode);
3294 				return;
3295 			}
3296 			my->yaw += 0.2;
3297 			spawnMagicParticle(my);
3298 			my->x = parent->x + my->actmagicOrbitDist * cos(my->yaw);
3299 			my->y = parent->y + my->actmagicOrbitDist * sin(my->yaw);
3300 		}
3301 		--PARTICLE_LIFE;
3302 	}
3303 	return;
3304 }
3305 
actParticleTest(Entity * my)3306 void actParticleTest(Entity* my)
3307 {
3308 	if ( PARTICLE_LIFE < 0 )
3309 	{
3310 		list_RemoveNode(my->mynode);
3311 		return;
3312 	}
3313 	else
3314 	{
3315 		--PARTICLE_LIFE;
3316 		my->x += my->vel_x;
3317 		my->y += my->vel_y;
3318 		my->z += my->vel_z;
3319 		//my->z -= 0.01;
3320 	}
3321 }
3322 
createParticleErupt(Entity * parent,int sprite)3323 void createParticleErupt(Entity* parent, int sprite)
3324 {
3325 	if ( !parent )
3326 	{
3327 		return;
3328 	}
3329 
3330 	real_t yaw = 0;
3331 	int numParticles = 8;
3332 	for ( int c = 0; c < 8; c++ )
3333 	{
3334 		Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3335 		entity->sizex = 1;
3336 		entity->sizey = 1;
3337 		entity->x = parent->x;
3338 		entity->y = parent->y;
3339 		entity->z = 7.5; // start from the ground.
3340 		entity->yaw = yaw;
3341 		entity->vel_x = 0.2;
3342 		entity->vel_y = 0.2;
3343 		entity->vel_z = -2;
3344 		entity->skill[0] = 100;
3345 		entity->skill[1] = 0; // direction.
3346 		entity->fskill[0] = 0.1;
3347 		entity->behavior = &actParticleErupt;
3348 		entity->flags[PASSABLE] = true;
3349 		entity->flags[NOUPDATE] = true;
3350 		entity->flags[UNCLICKABLE] = true;
3351 		if ( multiplayer != CLIENT )
3352 		{
3353 			entity_uids--;
3354 		}
3355 		entity->setUID(-3);
3356 		yaw += 2 * PI / numParticles;
3357 	}
3358 }
3359 
createParticleSapCenter(Entity * parent,Entity * target,int spell,int sprite,int endSprite)3360 Entity* createParticleSapCenter(Entity* parent, Entity* target, int spell, int sprite, int endSprite)
3361 {
3362 	if ( !parent || !target )
3363 	{
3364 		return nullptr;
3365 	}
3366 	// spawns the invisible 'center' of the magic particle
3367 	Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3368 	entity->sizex = 1;
3369 	entity->sizey = 1;
3370 	entity->x = target->x;
3371 	entity->y = target->y;
3372 	entity->parent = (parent->getUID());
3373 	entity->yaw = parent->yaw + PI; // face towards the caster.
3374 	entity->skill[0] = 45;
3375 	entity->skill[2] = -13; // so clients know my behavior.
3376 	entity->skill[3] = 0; // init
3377 	entity->skill[4] = sprite; // visible sprites.
3378 	entity->skill[5] = endSprite; // sprite to spawn on return to caster.
3379 	entity->skill[6] = spell;
3380 	entity->behavior = &actParticleSapCenter;
3381 	if ( target->sprite == 977 )
3382 	{
3383 		// boomerang.
3384 		entity->yaw = target->yaw;
3385 		entity->roll = target->roll;
3386 		entity->pitch = target->pitch;
3387 		entity->z = target->z;
3388 	}
3389 	entity->flags[INVISIBLE] = true;
3390 	entity->flags[PASSABLE] = true;
3391 	entity->flags[UPDATENEEDED] = true;
3392 	entity->flags[UNCLICKABLE] = true;
3393 	return entity;
3394 }
3395 
createParticleSap(Entity * parent)3396 void createParticleSap(Entity* parent)
3397 {
3398 	real_t speed = 0.4;
3399 	if ( !parent )
3400 	{
3401 		return;
3402 	}
3403 	for ( int c = 0; c < 4; c++ )
3404 	{
3405 		// 4 particles, in an 'x' pattern around parent sprite.
3406 		int sprite = parent->sprite;
3407 		if ( parent->sprite == 977 )
3408 		{
3409 			if ( c > 0 )
3410 			{
3411 				continue;
3412 			}
3413 			// boomerang return.
3414 			sprite = parent->sprite;
3415 		}
3416 		if ( parent->skill[6] == SPELL_STEAL_WEAPON || parent->skill[6] == SHADOW_SPELLCAST )
3417 		{
3418 			sprite = parent->sprite;
3419 		}
3420 		else if ( parent->skill[6] == SPELL_DRAIN_SOUL )
3421 		{
3422 			if ( c == 0 || c == 3 )
3423 			{
3424 				sprite = parent->sprite;
3425 			}
3426 			else
3427 			{
3428 				sprite = 599;
3429 			}
3430 		}
3431 		else if ( parent->skill[6] == SPELL_SUMMON )
3432 		{
3433 			sprite = parent->sprite;
3434 		}
3435 		else if ( parent->skill[6] == SPELL_FEAR )
3436 		{
3437 			sprite = parent->sprite;
3438 		}
3439 		else if ( multiplayer == CLIENT )
3440 		{
3441 			// client won't receive the sprite skill data in time, fix for this until a solution is found!
3442 			if ( sprite == 598 )
3443 			{
3444 				if ( c == 0 || c == 3 )
3445 				{
3446 				// drain HP particle
3447 					sprite = parent->sprite;
3448 				}
3449 				else
3450 				{
3451 					// drain MP particle
3452 					sprite = 599;
3453 				}
3454 			}
3455 		}
3456 		Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3457 		entity->sizex = 1;
3458 		entity->sizey = 1;
3459 		entity->x = parent->x;
3460 		entity->y = parent->y;
3461 		entity->z = 0;
3462 		entity->scalex = 0.9;
3463 		entity->scaley = 0.9;
3464 		entity->scalez = 0.9;
3465 		if ( sprite == 598 || sprite == 599 )
3466 		{
3467 			entity->scalex = 0.5;
3468 			entity->scaley = 0.5;
3469 			entity->scalez = 0.5;
3470 		}
3471 		entity->parent = (parent->getUID());
3472 		entity->yaw = parent->yaw;
3473 		if ( c == 0 )
3474 		{
3475 			entity->vel_z = -speed;
3476 			entity->vel_x = speed * cos(entity->yaw + PI / 2);
3477 			entity->vel_y = speed * sin(entity->yaw + PI / 2);
3478 			entity->yaw += PI / 3;
3479 			entity->pitch -= PI / 6;
3480 			entity->fskill[2] = -(PI / 3) / 25; // yaw rate of change.
3481 			entity->fskill[3] = (PI / 6) / 25; // pitch rate of change.
3482 		}
3483 		else if ( c == 1 )
3484 		{
3485 			entity->vel_z = -speed;
3486 			entity->vel_x = speed * cos(entity->yaw - PI / 2);
3487 			entity->vel_y = speed * sin(entity->yaw - PI / 2);
3488 			entity->yaw -= PI / 3;
3489 			entity->pitch -= PI / 6;
3490 			entity->fskill[2] = (PI / 3) / 25; // yaw rate of change.
3491 			entity->fskill[3] = (PI / 6) / 25; // pitch rate of change.
3492 		}
3493 		else if ( c == 2 )
3494 		{
3495 			entity->vel_x = speed * cos(entity->yaw + PI / 2);
3496 			entity->vel_y = speed * sin(entity->yaw + PI / 2);
3497 			entity->vel_z = speed;
3498 			entity->yaw += PI / 3;
3499 			entity->pitch += PI / 6;
3500 			entity->fskill[2] = -(PI / 3) / 25; // yaw rate of change.
3501 			entity->fskill[3] = -(PI / 6) / 25; // pitch rate of change.
3502 		}
3503 		else if ( c == 3 )
3504 		{
3505 			entity->vel_x = speed * cos(entity->yaw - PI / 2);
3506 			entity->vel_y = speed * sin(entity->yaw - PI / 2);
3507 			entity->vel_z = speed;
3508 			entity->yaw -= PI / 3;
3509 			entity->pitch += PI / 6;
3510 			entity->fskill[2] = (PI / 3) / 25; // yaw rate of change.
3511 			entity->fskill[3] = -(PI / 6) / 25; // pitch rate of change.
3512 		}
3513 
3514 		entity->skill[3] = c; // particle index
3515 		entity->fskill[0] = entity->vel_x; // stores the accumulated x offset from center
3516 		entity->fskill[1] = entity->vel_y; // stores the accumulated y offset from center
3517 		entity->skill[0] = 200; // lifetime
3518 		entity->skill[1] = 0; // direction outwards
3519 		entity->behavior = &actParticleSap;
3520 		entity->flags[PASSABLE] = true;
3521 		entity->flags[NOUPDATE] = true;
3522 		if ( multiplayer != CLIENT )
3523 		{
3524 			entity_uids--;
3525 		}
3526 		entity->setUID(-3);
3527 
3528 		if ( sprite = 977 ) // boomerang
3529 		{
3530 			entity->z = parent->z;
3531 			entity->scalex = 1.f;
3532 			entity->scaley = 1.f;
3533 			entity->scalez = 1.f;
3534 			entity->skill[0] = 175;
3535 			entity->fskill[2] = -((PI / 3) + (PI / 6)) / (150); // yaw rate of change over 3 seconds
3536 			entity->fskill[3] = 0.f;
3537 			entity->focalx = 2;
3538 			entity->focalz = 0.5;
3539 			entity->pitch = parent->pitch;
3540 			entity->yaw = parent->yaw;
3541 			entity->roll = parent->roll;
3542 
3543 			entity->vel_x = 1 * cos(entity->yaw);
3544 			entity->vel_y = 1 * sin(entity->yaw);
3545 			int x = entity->x / 16;
3546 			int y = entity->y / 16;
3547 			if ( !map.tiles[(MAPLAYERS - 1) + y * MAPLAYERS + x * MAPLAYERS * map.height] )
3548 			{
3549 				// no ceiling, bounce higher.
3550 				entity->vel_z = -0.4;
3551 				entity->skill[3] = 1; // high bounce.
3552 			}
3553 			else
3554 			{
3555 				entity->vel_z = -0.08;
3556 			}
3557 			entity->yaw += PI / 3;
3558 		}
3559 	}
3560 }
3561 
createParticleDropRising(Entity * parent,int sprite,double scale)3562 void createParticleDropRising(Entity* parent, int sprite, double scale)
3563 {
3564 	if ( !parent )
3565 	{
3566 		return;
3567 	}
3568 
3569 	for ( int c = 0; c < 50; c++ )
3570 	{
3571 		// shoot drops to the sky
3572 		Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
3573 		entity->sizex = 1;
3574 		entity->sizey = 1;
3575 		entity->x = parent->x - 4 + rand() % 9;
3576 		entity->y = parent->y - 4 + rand() % 9;
3577 		entity->z = 7.5 + rand() % 50;
3578 		entity->vel_z = -1;
3579 		//entity->yaw = (rand() % 360) * PI / 180.0;
3580 		entity->particleDuration = 10 + rand() % 50;
3581 		entity->scalex *= scale;
3582 		entity->scaley *= scale;
3583 		entity->scalez *= scale;
3584 		entity->behavior = &actParticleDot;
3585 		entity->flags[PASSABLE] = true;
3586 		entity->flags[NOUPDATE] = true;
3587 		entity->flags[UNCLICKABLE] = true;
3588 		if ( multiplayer != CLIENT )
3589 		{
3590 			entity_uids--;
3591 		}
3592 		entity->setUID(-3);
3593 	}
3594 }
3595 
createParticleTimer(Entity * parent,int duration,int sprite)3596 Entity* createParticleTimer(Entity* parent, int duration, int sprite)
3597 {
3598 	Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Timer entity.
3599 	entity->sizex = 1;
3600 	entity->sizey = 1;
3601 	if ( parent )
3602 	{
3603 		entity->x = parent->x;
3604 		entity->y = parent->y;
3605 		entity->parent = (parent->getUID());
3606 	}
3607 	entity->behavior = &actParticleTimer;
3608 	entity->particleTimerDuration = duration;
3609 	entity->flags[INVISIBLE] = true;
3610 	entity->flags[PASSABLE] = true;
3611 	entity->flags[NOUPDATE] = true;
3612 	if ( multiplayer != CLIENT )
3613 	{
3614 		entity_uids--;
3615 	}
3616 	entity->setUID(-3);
3617 
3618 	return entity;
3619 }
3620 
actParticleErupt(Entity * my)3621 void actParticleErupt(Entity* my)
3622 {
3623 	if ( PARTICLE_LIFE < 0 )
3624 	{
3625 		list_RemoveNode(my->mynode);
3626 		return;
3627 	}
3628 	else
3629 	{
3630 		// particles jump up from the ground then back down again.
3631 		--PARTICLE_LIFE;
3632 		my->x += my->vel_x * cos(my->yaw);
3633 		my->y += my->vel_y * sin(my->yaw);
3634 		my->scalex *= 0.99;
3635 		my->scaley *= 0.99;
3636 		my->scalez *= 0.99;
3637 		spawnMagicParticle(my);
3638 		if ( my->skill[1] == 0 ) // rising
3639 		{
3640 			my->z += my->vel_z;
3641 			my->vel_z *= 0.8;
3642 			my->pitch = std::min<real_t>(my->pitch + my->fskill[0], PI / 2);
3643 			my->fskill[0] = std::max<real_t>(my->fskill[0] * 0.85, 0.05);
3644 			if ( my->vel_z > -0.02 )
3645 			{
3646 				my->skill[1] = 1;
3647 			}
3648 		}
3649 		else // falling
3650 		{
3651 			my->pitch = std::min<real_t>(my->pitch + my->fskill[0], 15 * PI / 16);
3652 			my->fskill[0] = std::min<real_t>(my->fskill[0] * (1 / 0.99), 0.1);
3653 			my->z -= my->vel_z;
3654 			my->vel_z *= (1 / 0.8);
3655 			my->vel_z = std::max<real_t>(my->vel_z, -0.8);
3656 		}
3657 	}
3658 }
3659 
actParticleTimer(Entity * my)3660 void actParticleTimer(Entity* my)
3661 {
3662 	if ( PARTICLE_LIFE < 0 )
3663 	{
3664 		if ( multiplayer != CLIENT )
3665 		{
3666 			if ( my->particleTimerEndAction == PARTICLE_EFFECT_INCUBUS_TELEPORT_STEAL )
3667 			{
3668 				// teleport to random location spell.
3669 				Entity* parent = uidToEntity(my->parent);
3670 				if ( parent )
3671 				{
3672 					createParticleErupt(parent, my->particleTimerEndSprite);
3673 					if ( parent->teleportRandom() )
3674 					{
3675 						// teleport success.
3676 						if ( multiplayer == SERVER )
3677 						{
3678 							serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3679 						}
3680 					}
3681 				}
3682 			}
3683 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_INCUBUS_TELEPORT_TARGET )
3684 			{
3685 				// teleport to target spell.
3686 				Entity* parent = uidToEntity(my->parent);
3687 				Entity* target = uidToEntity(static_cast<Uint32>(my->particleTimerTarget));
3688 				if ( parent && target )
3689 				{
3690 					createParticleErupt(parent, my->particleTimerEndSprite);
3691 					if ( parent->teleportAroundEntity(target, my->particleTimerVariable1) )
3692 					{
3693 						// teleport success.
3694 						if ( multiplayer == SERVER )
3695 						{
3696 							serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3697 						}
3698 					}
3699 				}
3700 			}
3701 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_TELEPORT_PULL )
3702 			{
3703 				// teleport to target spell.
3704 				Entity* parent = uidToEntity(my->parent);
3705 				Entity* target = uidToEntity(static_cast<Uint32>(my->particleTimerTarget));
3706 				if ( parent && target )
3707 				{
3708 					real_t oldx = target->x;
3709 					real_t oldy = target->y;
3710 					my->flags[PASSABLE] = true;
3711 					int tx = static_cast<int>(std::floor(my->x)) >> 4;
3712 					int ty = static_cast<int>(std::floor(my->y)) >> 4;
3713 					if ( !target->isBossMonster() &&
3714 						target->teleport(tx, ty) )
3715 					{
3716 						// teleport success.
3717 						if ( parent->behavior == &actPlayer )
3718 						{
3719 							Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
3720 							if ( target->getStats() )
3721 							{
3722 								messagePlayerMonsterEvent(parent->skill[2], color, *(target->getStats()), language[3450], language[3451], MSG_COMBAT);
3723 							}
3724 						}
3725 						if ( target->behavior == &actPlayer )
3726 						{
3727 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
3728 							messagePlayerColor(target->skill[2], color, language[3461]);
3729 						}
3730 						real_t distance =  sqrt((target->x - oldx) * (target->x - oldx) + (target->y - oldy) * (target->y - oldy)) / 16.f;
3731 						//real_t distance = (entityDist(parent, target)) / 16;
3732 						createParticleErupt(target, my->particleTimerEndSprite);
3733 						int durationToStun = 0;
3734 						if ( distance >= 2 )
3735 						{
3736 							durationToStun = 25 + std::min((distance - 4) * 10, 50.0);
3737 						}
3738 						if ( target->behavior == &actMonster )
3739 						{
3740 							if ( durationToStun > 0 && target->setEffect(EFF_DISORIENTED, true, durationToStun, false) )
3741 							{
3742 								int numSprites = std::min(3, durationToStun / 25);
3743 								for ( int i = 0; i < numSprites; ++i )
3744 								{
3745 									spawnFloatingSpriteMisc(134, target->x + (-4 + rand() % 9) + cos(target->yaw) * 2,
3746 										target->y + (-4 + rand() % 9) + sin(target->yaw) * 2, target->z + rand() % 4);
3747 								}
3748 							}
3749 							target->monsterReleaseAttackTarget();
3750 							target->lookAtEntity(*parent);
3751 							target->monsterLookDir += (PI - PI / 4 + (rand() % 10) * PI / 40);
3752 						}
3753 						else if ( target->behavior == &actPlayer )
3754 						{
3755 							durationToStun = std::max(50, durationToStun);
3756 							target->setEffect(EFF_DISORIENTED, true, durationToStun, false);
3757 							int numSprites = std::min(3, durationToStun / 50);
3758 							for ( int i = 0; i < numSprites; ++i )
3759 							{
3760 								spawnFloatingSpriteMisc(134, target->x + (-4 + rand() % 9) + cos(target->yaw) * 2,
3761 									target->y + (-4 + rand() % 9) + sin(target->yaw) * 2, target->z + rand() % 4);
3762 							}
3763 							Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
3764 							messagePlayerColor(target->skill[2], color, language[3462]);
3765 						}
3766 						if ( multiplayer == SERVER )
3767 						{
3768 							serverSpawnMiscParticles(target, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3769 						}
3770 					}
3771 				}
3772 			}
3773 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_PORTAL_SPAWN )
3774 			{
3775 				Entity* parent = uidToEntity(my->parent);
3776 				if ( parent )
3777 				{
3778 					parent->flags[INVISIBLE] = false;
3779 					serverUpdateEntityFlag(parent, INVISIBLE);
3780 					playSoundEntity(parent, 164, 128);
3781 				}
3782 				spawnExplosion(my->x, my->y, 0);
3783 			}
3784 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_SUMMON_MONSTER
3785 				|| my->particleTimerEndAction == PARTICLE_EFFECT_DEVIL_SUMMON_MONSTER )
3786 			{
3787 				playSoundEntity(my, 164, 128);
3788 				spawnExplosion(my->x, my->y, -4.0);
3789 				bool forceLocation = false;
3790 				if ( my->particleTimerEndAction == PARTICLE_EFFECT_DEVIL_SUMMON_MONSTER &&
3791 					!map.tiles[static_cast<int>(my->y / 16) * MAPLAYERS + static_cast<int>(my->x / 16) * MAPLAYERS * map.height] )
3792 				{
3793 					if ( my->particleTimerVariable1 == SHADOW || my->particleTimerVariable1 == CREATURE_IMP )
3794 					{
3795 						forceLocation = true;
3796 					}
3797 				}
3798 				Entity* monster = summonMonster(static_cast<Monster>(my->particleTimerVariable1), my->x, my->y, forceLocation);
3799 				if ( monster )
3800 				{
3801 					Stat* monsterStats = monster->getStats();
3802 					if ( my->parent != 0 && uidToEntity(my->parent) )
3803 					{
3804 						if ( uidToEntity(my->parent)->getRace() == LICH_ICE )
3805 						{
3806 							//monsterStats->leader_uid = my->parent;
3807 							switch ( monsterStats->type )
3808 							{
3809 								case AUTOMATON:
3810 									strcpy(monsterStats->name, "corrupted automaton");
3811 									monsterStats->EFFECTS[EFF_CONFUSED] = true;
3812 									monsterStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1;
3813 									break;
3814 								default:
3815 									break;
3816 							}
3817 						}
3818 						else if ( uidToEntity(my->parent)->getRace() == DEVIL )
3819 						{
3820 							monsterStats->LVL = 5;
3821 							if ( my->particleTimerVariable2 >= 0
3822 								&& players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity )
3823 							{
3824 								monster->monsterAcquireAttackTarget(*(players[my->particleTimerVariable2]->entity), MONSTER_STATE_ATTACK);
3825 							}
3826 						}
3827 					}
3828 				}
3829 			}
3830 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_SPELL_SUMMON )
3831 			{
3832 				//my->removeLightField();
3833 			}
3834 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_SHADOW_TELEPORT )
3835 			{
3836 				// teleport to target spell.
3837 				Entity* parent = uidToEntity(my->parent);
3838 				if ( parent )
3839 				{
3840 					if ( parent->monsterSpecialState == SHADOW_TELEPORT_ONLY )
3841 					{
3842 						//messagePlayer(0, "Resetting shadow's monsterSpecialState!");
3843 						parent->monsterSpecialState = 0;
3844 						serverUpdateEntitySkill(parent, 33); // for clients to keep track of animation
3845 					}
3846 				}
3847 				Entity* target = uidToEntity(static_cast<Uint32>(my->particleTimerTarget));
3848 				if ( parent )
3849 				{
3850 					bool teleported = false;
3851 					createParticleErupt(parent, my->particleTimerEndSprite);
3852 					if ( target )
3853 					{
3854 						teleported = parent->teleportAroundEntity(target, my->particleTimerVariable1);
3855 					}
3856 					else
3857 					{
3858 						teleported = parent->teleportRandom();
3859 					}
3860 
3861 					if ( teleported )
3862 					{
3863 						// teleport success.
3864 						if ( multiplayer == SERVER )
3865 						{
3866 							serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3867 						}
3868 					}
3869 				}
3870 			}
3871 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_LICHFIRE_TELEPORT_STATIONARY )
3872 			{
3873 				// teleport to fixed location spell.
3874 				node_t* node;
3875 				int c = 0 + rand() % 3;
3876 				Entity* target = nullptr;
3877 				for ( node = map.entities->first; node != nullptr; node = node->next )
3878 				{
3879 					target = (Entity*)node->element;
3880 					if ( target->behavior == &actDevilTeleport )
3881 					{
3882 						if ( (c == 0 && target->sprite == 72)
3883 							|| (c == 1 && target->sprite == 73)
3884 							|| (c == 2 && target->sprite == 74) )
3885 						{
3886 							break;
3887 						}
3888 					}
3889 				}
3890 				Entity* parent = uidToEntity(my->parent);
3891 				if ( parent && target )
3892 				{
3893 					createParticleErupt(parent, my->particleTimerEndSprite);
3894 					if ( parent->teleport(target->x / 16, target->y / 16) )
3895 					{
3896 						// teleport success.
3897 						if ( multiplayer == SERVER )
3898 						{
3899 							serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3900 						}
3901 					}
3902 				}
3903 			}
3904 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_LICH_TELEPORT_ROAMING )
3905 			{
3906 				bool teleported = false;
3907 				// teleport to target spell.
3908 				node_t* node;
3909 				Entity* parent = uidToEntity(my->parent);
3910 				Entity* target = nullptr;
3911 				if ( parent )
3912 				{
3913 					for ( node = map.entities->first; node != nullptr; node = node->next )
3914 					{
3915 						target = (Entity*)node->element;
3916 						if ( target->behavior == &actDevilTeleport
3917 							&& target->sprite == 128 )
3918 						{
3919 								break; // found specified center of map
3920 						}
3921 					}
3922 
3923 					if ( target )
3924 					{
3925 						createParticleErupt(parent, my->particleTimerEndSprite);
3926 						teleported = parent->teleport((target->x / 16) - 11 + rand() % 23, (target->y / 16) - 11 + rand() % 23);
3927 
3928 						if ( teleported )
3929 						{
3930 							// teleport success.
3931 							if ( multiplayer == SERVER )
3932 							{
3933 								serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3934 							}
3935 						}
3936 					}
3937 				}
3938 			}
3939 			else if ( my->particleTimerEndAction == PARTICLE_EFFECT_LICHICE_TELEPORT_STATIONARY )
3940 			{
3941 				// teleport to fixed location spell.
3942 				node_t* node;
3943 				Entity* target = nullptr;
3944 				for ( node = map.entities->first; node != nullptr; node = node->next )
3945 				{
3946 					target = (Entity*)node->element;
3947 					if ( target->behavior == &actDevilTeleport
3948 						&& target->sprite == 128 )
3949 					{
3950 							break;
3951 					}
3952 				}
3953 				Entity* parent = uidToEntity(my->parent);
3954 				if ( parent && target )
3955 				{
3956 					createParticleErupt(parent, my->particleTimerEndSprite);
3957 					if ( parent->teleport(target->x / 16, target->y / 16) )
3958 					{
3959 						// teleport success.
3960 						if ( multiplayer == SERVER )
3961 						{
3962 							serverSpawnMiscParticles(parent, PARTICLE_EFFECT_ERUPT, my->particleTimerEndSprite);
3963 						}
3964 						parent->lichIceCreateCannon();
3965 					}
3966 				}
3967 			}
3968 		}
3969 		my->removeLightField();
3970 		list_RemoveNode(my->mynode);
3971 		return;
3972 	}
3973 	else
3974 	{
3975 		--PARTICLE_LIFE;
3976 		if ( my->particleTimerPreDelay <= 0 )
3977 		{
3978 			// shoot particles for the duration of the timer, centered at caster.
3979 			if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_SHOOT_PARTICLES )
3980 			{
3981 				Entity* parent = uidToEntity(my->parent);
3982 				// shoot drops to the sky
3983 				if ( parent && my->particleTimerCountdownSprite != 0 )
3984 				{
3985 					Entity* entity = newEntity(my->particleTimerCountdownSprite, 1, map.entities, nullptr); //Particle entity.
3986 					entity->sizex = 1;
3987 					entity->sizey = 1;
3988 					entity->x = parent->x - 4 + rand() % 9;
3989 					entity->y = parent->y - 4 + rand() % 9;
3990 					entity->z = 7.5;
3991 					entity->vel_z = -1;
3992 					entity->yaw = (rand() % 360) * PI / 180.0;
3993 					entity->particleDuration = 10 + rand() % 30;
3994 					entity->behavior = &actParticleDot;
3995 					entity->flags[PASSABLE] = true;
3996 					entity->flags[NOUPDATE] = true;
3997 					entity->flags[UNCLICKABLE] = true;
3998 					if ( multiplayer != CLIENT )
3999 					{
4000 						entity_uids--;
4001 					}
4002 					entity->setUID(-3);
4003 				}
4004 			}
4005 			// fire once off.
4006 			else if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_SPAWN_PORTAL )
4007 			{
4008 				Entity* parent = uidToEntity(my->parent);
4009 				if ( parent && my->particleTimerCountdownAction < 100 )
4010 				{
4011 					playSoundEntityLocal(parent, 167, 128);
4012 					createParticleDot(parent);
4013 					createParticleCircling(parent, 100, my->particleTimerCountdownSprite);
4014 					my->particleTimerCountdownAction = 0;
4015 				}
4016 			}
4017 			// fire once off.
4018 			else if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_SUMMON_MONSTER )
4019 			{
4020 				if ( my->particleTimerCountdownAction < 100 )
4021 				{
4022 					my->light = lightSphereShadow(my->x / 16, my->y / 16, 5, 92);
4023 					playSoundEntityLocal(my, 167, 128);
4024 					createParticleDropRising(my, 680, 1.0);
4025 					createParticleCircling(my, 70, my->particleTimerCountdownSprite);
4026 					my->particleTimerCountdownAction = 0;
4027 				}
4028 			}
4029 			// fire once off.
4030 			else if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_DEVIL_SUMMON_MONSTER )
4031 			{
4032 				if ( my->particleTimerCountdownAction < 100 )
4033 				{
4034 					my->light = lightSphereShadow(my->x / 16, my->y / 16, 5, 92);
4035 					playSoundEntityLocal(my, 167, 128);
4036 					createParticleDropRising(my, 593, 1.0);
4037 					createParticleCircling(my, 70, my->particleTimerCountdownSprite);
4038 					my->particleTimerCountdownAction = 0;
4039 				}
4040 			}
4041 			// continually fire
4042 			else if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_SPELL_SUMMON )
4043 			{
4044 				if ( multiplayer != CLIENT && my->particleTimerPreDelay != -100 )
4045 				{
4046 					// once-off hack :)
4047 					spawnExplosion(my->x, my->y, -1);
4048 					playSoundEntity(my, 171, 128);
4049 					my->particleTimerPreDelay = -100;
4050 
4051 					createParticleErupt(my, my->particleTimerCountdownSprite);
4052 					serverSpawnMiscParticles(my, PARTICLE_EFFECT_ERUPT, my->particleTimerCountdownSprite);
4053 				}
4054 			}
4055 			// fire once off.
4056 			else if ( my->particleTimerCountdownAction == PARTICLE_EFFECT_TELEPORT_PULL_TARGET_LOCATION )
4057 			{
4058 				createParticleDropRising(my, my->particleTimerCountdownSprite, 1.0);
4059 				my->particleTimerCountdownAction = 0;
4060 			}
4061 		}
4062 		else
4063 		{
4064 			--my->particleTimerPreDelay;
4065 		}
4066 	}
4067 }
4068 
actParticleSap(Entity * my)4069 void actParticleSap(Entity* my)
4070 {
4071 	real_t decel = 0.9;
4072 	real_t accel = 0.9;
4073 	real_t z_accel = accel;
4074 	real_t z_decel = decel;
4075 	real_t minSpeed = 0.05;
4076 
4077 	if ( PARTICLE_LIFE < 0 )
4078 	{
4079 		list_RemoveNode(my->mynode);
4080 		return;
4081 	}
4082 	else
4083 	{
4084 		if ( my->sprite == 977 ) // boomerang
4085 		{
4086 			if ( my->skill[3] == 1 )
4087 			{
4088 				// specific for the animation I want...
4089 				// magic numbers that take approximately 75 frames (50% of travel time) to go outward or inward.
4090 				// acceleration is a little faster to overshoot into the right hand side.
4091 				decel = 0.9718;
4092 				accel = 0.9710;
4093 				z_decel = decel;
4094 				z_accel = z_decel;
4095 			}
4096 			else
4097 			{
4098 				decel = 0.95;
4099 				accel = 0.949;
4100 				z_decel = 0.9935;
4101 				z_accel = z_decel;
4102 			}
4103 			Entity* particle = spawnMagicParticleCustom(my, (rand() % 2) ? 943 : 979, 1, 10);
4104 			if ( particle )
4105 			{
4106 				particle->focalx = 2;
4107 				particle->focaly = -2;
4108 				particle->focalz = 2.5;
4109 			}
4110 			if ( PARTICLE_LIFE < 100 && my->ticks % 6 == 0 )
4111 			{
4112 				if ( PARTICLE_LIFE < 70 )
4113 				{
4114 					playSoundEntityLocal(my, 434 + rand() % 10, 64);
4115 				}
4116 				else
4117 				{
4118 					playSoundEntityLocal(my, 434 + rand() % 10, 32);
4119 				}
4120 			}
4121 			//particle->flags[SPRITE] = true;
4122 		}
4123 		else
4124 		{
4125 			spawnMagicParticle(my);
4126 		}
4127 		Entity* parent = uidToEntity(my->parent);
4128 		if ( parent )
4129 		{
4130 			my->x = parent->x + my->fskill[0];
4131 			my->y = parent->y + my->fskill[1];
4132 		}
4133 		else
4134 		{
4135 			list_RemoveNode(my->mynode);
4136 			return;
4137 		}
4138 
4139 		if ( my->skill[1] == 0 )
4140 		{
4141 			// move outwards diagonally.
4142 			if ( abs(my->vel_z) > minSpeed )
4143 			{
4144 				my->fskill[0] += my->vel_x;
4145 				my->fskill[1] += my->vel_y;
4146 				my->vel_x *= decel;
4147 				my->vel_y *= decel;
4148 
4149 				my->z += my->vel_z;
4150 				my->vel_z *= z_decel;
4151 
4152 				my->yaw += my->fskill[2];
4153 				my->pitch += my->fskill[3];
4154 			}
4155 			else
4156 			{
4157 				my->skill[1] = 1;
4158 				my->vel_x *= -1;
4159 				my->vel_y *= -1;
4160 				my->vel_z *= -1;
4161 			}
4162 		}
4163 		else if ( my->skill[1] == 1 )
4164 		{
4165 			// move inwards diagonally.
4166 			if ( (abs(my->vel_z) < 0.08 && my->skill[3] == 0) || (abs(my->vel_z) < 0.4 && my->skill[3] == 1) )
4167 			{
4168 				my->fskill[0] += my->vel_x;
4169 				my->fskill[1] += my->vel_y;
4170 				my->vel_x /= accel;
4171 				my->vel_y /= accel;
4172 
4173 				my->z += my->vel_z;
4174 				my->vel_z /= z_accel;
4175 
4176 				my->yaw += my->fskill[2];
4177 				my->pitch += my->fskill[3];
4178 			}
4179 			else
4180 			{
4181 				// movement completed.
4182 				my->skill[1] = 2;
4183 			}
4184 		}
4185 
4186 		my->scalex *= 0.99;
4187 		my->scaley *= 0.99;
4188 		my->scalez *= 0.99;
4189 		if ( my->sprite == 977 )
4190 		{
4191 			my->scalex = 1.f;
4192 			my->scaley = 1.f;
4193 			my->scalez = 1.f;
4194 			my->roll -= 0.5;
4195 			my->pitch = std::max(my->pitch - 0.015, 0.0);
4196 		}
4197 		--PARTICLE_LIFE;
4198 	}
4199 }
4200 
actParticleSapCenter(Entity * my)4201 void actParticleSapCenter(Entity* my)
4202 {
4203 	// init
4204 	if ( my->skill[3] == 0 )
4205 	{
4206 		// for clients and server spawn the visible arcing particles.
4207 		my->skill[3] = 1;
4208 		createParticleSap(my);
4209 	}
4210 
4211 	if ( multiplayer == CLIENT )
4212 	{
4213 		return;
4214 	}
4215 
4216 	Entity* parent = uidToEntity(my->parent);
4217 	if ( parent )
4218 	{
4219 		// if reached the caster, delete self and spawn some particles.
4220 		if ( my->sprite == 977 && PARTICLE_LIFE > 1 )
4221 		{
4222 			// store these in case parent dies.
4223 			// boomerang doesn't check for collision until end of life.
4224 			my->fskill[4] = parent->x;
4225 			my->fskill[5] = parent->y;
4226 		}
4227 		else if ( entityInsideEntity(my, parent) || (my->sprite == 977 && PARTICLE_LIFE == 0) )
4228 		{
4229 			if ( my->skill[6] == SPELL_STEAL_WEAPON )
4230 			{
4231 				if ( my->skill[7] == 1 )
4232 				{
4233 					// found stolen item.
4234 					Item* item = newItemFromEntity(my);
4235 					if ( parent->behavior == &actPlayer )
4236 					{
4237 						itemPickup(parent->skill[2], item);
4238 					}
4239 					else if ( parent->behavior == &actMonster )
4240 					{
4241 						parent->addItemToMonsterInventory(item);
4242 						Stat *myStats = parent->getStats();
4243 						if ( myStats )
4244 						{
4245 							node_t* weaponNode = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON);
4246 							if ( weaponNode )
4247 							{
4248 								swapMonsterWeaponWithInventoryItem(parent, myStats, weaponNode, false, true);
4249 								if ( myStats->type == INCUBUS )
4250 								{
4251 									parent->monsterSpecialState = INCUBUS_TELEPORT_STEAL;
4252 									parent->monsterSpecialTimer = 100 + rand() % MONSTER_SPECIAL_COOLDOWN_INCUBUS_TELEPORT_RANDOM;
4253 								}
4254 							}
4255 						}
4256 					}
4257 					item = nullptr;
4258 				}
4259 				playSoundEntity(parent, 168, 128);
4260 				spawnMagicEffectParticles(parent->x, parent->y, parent->z, my->skill[5]);
4261 			}
4262 			else if ( my->skill[6] == SPELL_DRAIN_SOUL )
4263 			{
4264 				parent->modHP(my->skill[7]);
4265 				parent->modMP(my->skill[8]);
4266 				if ( parent->behavior == &actPlayer )
4267 				{
4268 					Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
4269 					messagePlayerColor(parent->skill[2], color, language[2445]);
4270 				}
4271 				playSoundEntity(parent, 168, 128);
4272 				spawnMagicEffectParticles(parent->x, parent->y, parent->z, 169);
4273 			}
4274 			else if ( my->skill[6] == SHADOW_SPELLCAST )
4275 			{
4276 				parent->shadowSpecialAbility(parent->monsterShadowInitialMimic);
4277 				playSoundEntity(parent, 166, 128);
4278 				spawnMagicEffectParticles(parent->x, parent->y, parent->z, my->skill[5]);
4279 			}
4280 			else if ( my->skill[6] == SPELL_SUMMON )
4281 			{
4282 				parent->modMP(my->skill[7]);
4283 				/*if ( parent->behavior == &actPlayer )
4284 				{
4285 					Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
4286 					messagePlayerColor(parent->skill[2], color, language[774]);
4287 				}*/
4288 				playSoundEntity(parent, 168, 128);
4289 				spawnMagicEffectParticles(parent->x, parent->y, parent->z, 169);
4290 			}
4291 			else if ( my->skill[6] == SPELL_FEAR )
4292 			{
4293 				playSoundEntity(parent, 168, 128);
4294 				spawnMagicEffectParticles(parent->x, parent->y, parent->z, 174);
4295 				Entity* caster = uidToEntity(my->skill[7]);
4296 				if ( caster )
4297 				{
4298 					spellEffectFear(nullptr, spellElement_fear, caster, parent, 0);
4299 				}
4300 			}
4301 			else if ( my->sprite == 977 ) // boomerang
4302 			{
4303 				Item* item = newItemFromEntity(my);
4304 				if ( parent->behavior == &actPlayer )
4305 				{
4306 					item->ownerUid = parent->getUID();
4307 					Item* pickedUp = itemPickup(parent->skill[2], item);
4308 					Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
4309 					messagePlayerColor(parent->skill[2], color, language[3746], items[item->type].name_unidentified);
4310 					achievementObserver.awardAchievementIfActive(parent->skill[2], parent, AchievementObserver::BARONY_ACH_IF_YOU_LOVE_SOMETHING);
4311 					if ( pickedUp )
4312 					{
4313 						if ( parent->skill[2] == 0 )
4314 						{
4315 							// pickedUp is the new inventory stack for server, free the original items
4316 							free(item);
4317 							item = nullptr;
4318 							if ( multiplayer != CLIENT && !stats[parent->skill[2]]->weapon )
4319 							{
4320 								useItem(pickedUp, parent->skill[2]);
4321 							}
4322 							if ( magicBoomerangHotbarSlot >= 0 )
4323 							{
4324 								hotbar[magicBoomerangHotbarSlot].item = pickedUp->uid;
4325 								for ( int i = 0; i < NUM_HOTBAR_SLOTS; ++i )
4326 								{
4327 									if ( i != magicBoomerangHotbarSlot && hotbar[i].item == pickedUp->uid )
4328 									{
4329 										hotbar[i].item = 0;
4330 									}
4331 								}
4332 							}
4333 						}
4334 						else
4335 						{
4336 							free(pickedUp); // item is the picked up items (item == pickedUp)
4337 						}
4338 					}
4339 				}
4340 				else if ( parent->behavior == &actMonster )
4341 				{
4342 					parent->addItemToMonsterInventory(item);
4343 					Stat *myStats = parent->getStats();
4344 					if ( myStats )
4345 					{
4346 						node_t* weaponNode = itemNodeInInventory(myStats, static_cast<ItemType>(-1), WEAPON);
4347 						if ( weaponNode )
4348 						{
4349 							swapMonsterWeaponWithInventoryItem(parent, myStats, weaponNode, false, true);
4350 						}
4351 					}
4352 				}
4353 				playSoundEntity(parent, 431 + rand() % 3, 92);
4354 				item = nullptr;
4355 			}
4356 			list_RemoveNode(my->mynode);
4357 			return;
4358 		}
4359 
4360 		// calculate direction to caster and move.
4361 		real_t tangent = atan2(parent->y - my->y, parent->x - my->x);
4362 		real_t dist = sqrt(pow(my->x - parent->x, 2) + pow(my->y - parent->y, 2));
4363 		real_t speed = dist / std::max(PARTICLE_LIFE, 1);
4364 		my->vel_x = speed * cos(tangent);
4365 		my->vel_y = speed * sin(tangent);
4366 		my->x += my->vel_x;
4367 		my->y += my->vel_y;
4368 	}
4369 	else
4370 	{
4371 		if ( my->skill[6] == SPELL_SUMMON )
4372 		{
4373 			real_t dist = sqrt(pow(my->x - my->skill[8], 2) + pow(my->y - my->skill[9], 2));
4374 			if ( dist < 4 )
4375 			{
4376 				spawnMagicEffectParticles(my->skill[8], my->skill[9], 0, my->skill[5]);
4377 				Entity* caster = uidToEntity(my->skill[7]);
4378 				if ( caster && caster->behavior == &actPlayer )
4379 				{
4380 					// kill old summons.
4381 					for ( node_t* node = stats[caster->skill[2]]->FOLLOWERS.first; node != nullptr; node = node->next )
4382 					{
4383 						Entity* follower = nullptr;
4384 						if ( (Uint32*)(node)->element )
4385 						{
4386 							follower = uidToEntity(*((Uint32*)(node)->element));
4387 						}
4388 						if ( follower && follower->monsterAllySummonRank != 0 )
4389 						{
4390 							Stat* followerStats = follower->getStats();
4391 							if ( followerStats && followerStats->HP > 0 )
4392 							{
4393 								follower->setMP(followerStats->MAXMP * (followerStats->HP / static_cast<float>(followerStats->MAXHP)));
4394 								follower->setHP(0);
4395 							}
4396 						}
4397 					}
4398 
4399 					Monster creature = SKELETON;
4400 					Entity* monster = summonMonster(creature, my->skill[8], my->skill[9]);
4401 					if ( monster )
4402 					{
4403 						Stat* monsterStats = monster->getStats();
4404 						monster->yaw = my->yaw - PI;
4405 						if ( monsterStats )
4406 						{
4407 							int magicLevel = 1;
4408 							if ( stats[caster->skill[2]] )
4409 							{
4410 								magicLevel = std::min(7, 1 + (stats[caster->skill[2]]->playerSummonLVLHP >> 16) / 5);
4411 							}
4412 							monster->monsterAllySummonRank = magicLevel;
4413 							strcpy(monsterStats->name, "skeleton knight");
4414 							forceFollower(*caster, *monster);
4415 
4416 							monster->setEffect(EFF_STUNNED, true, 20, false);
4417 							bool spawnSecondAlly = false;
4418 
4419 							if ( (caster->getINT() + stats[caster->skill[2]]->PROFICIENCIES[PRO_MAGIC]) >= SKILL_LEVEL_EXPERT )
4420 							{
4421 								spawnSecondAlly = true;
4422 							}
4423 							//parent->increaseSkill(PRO_LEADERSHIP);
4424 							monster->monsterAllyIndex = caster->skill[2];
4425 							if ( multiplayer == SERVER )
4426 							{
4427 								serverUpdateEntitySkill(monster, 42); // update monsterAllyIndex for clients.
4428 							}
4429 
4430 							// change the color of the hit entity.
4431 							monster->flags[USERFLAG2] = true;
4432 							serverUpdateEntityFlag(monster, USERFLAG2);
4433 							if ( monsterChangesColorWhenAlly(monsterStats) )
4434 							{
4435 								int bodypart = 0;
4436 								for ( node_t* node = (monster)->children.first; node != nullptr; node = node->next )
4437 								{
4438 									if ( bodypart >= LIMB_HUMANOID_TORSO )
4439 									{
4440 										Entity* tmp = (Entity*)node->element;
4441 										if ( tmp )
4442 										{
4443 											tmp->flags[USERFLAG2] = true;
4444 											serverUpdateEntityFlag(tmp, USERFLAG2);
4445 										}
4446 									}
4447 									++bodypart;
4448 								}
4449 							}
4450 
4451 							if ( spawnSecondAlly )
4452 							{
4453 								Entity* monster = summonMonster(creature, my->skill[8], my->skill[9]);
4454 								if ( monster )
4455 								{
4456 									if ( multiplayer != CLIENT )
4457 									{
4458 										spawnExplosion(monster->x, monster->y, -1);
4459 										playSoundEntity(monster, 171, 128);
4460 
4461 										createParticleErupt(monster, 791);
4462 										serverSpawnMiscParticles(monster, PARTICLE_EFFECT_ERUPT, 791);
4463 									}
4464 
4465 									Stat* monsterStats = monster->getStats();
4466 									monster->yaw = my->yaw - PI;
4467 									if ( monsterStats )
4468 									{
4469 										strcpy(monsterStats->name, "skeleton sentinel");
4470 										magicLevel = 1;
4471 										if ( stats[caster->skill[2]] )
4472 										{
4473 											magicLevel = std::min(7, 1 + (stats[caster->skill[2]]->playerSummon2LVLHP >> 16) / 5);
4474 										}
4475 										monster->monsterAllySummonRank = magicLevel;
4476 
4477 										forceFollower(*caster, *monster);
4478 										monster->setEffect(EFF_STUNNED, true, 20, false);
4479 
4480 										monster->monsterAllyIndex = caster->skill[2];
4481 										if ( multiplayer == SERVER )
4482 										{
4483 											serverUpdateEntitySkill(monster, 42); // update monsterAllyIndex for clients.
4484 										}
4485 
4486 										if ( caster && caster->behavior == &actPlayer )
4487 										{
4488 											steamAchievementClient(caster->skill[2], "BARONY_ACH_SKELETON_CREW");
4489 										}
4490 
4491 										// change the color of the hit entity.
4492 										monster->flags[USERFLAG2] = true;
4493 										serverUpdateEntityFlag(monster, USERFLAG2);
4494 										if ( monsterChangesColorWhenAlly(monsterStats) )
4495 										{
4496 											int bodypart = 0;
4497 											for ( node_t* node = (monster)->children.first; node != nullptr; node = node->next )
4498 											{
4499 												if ( bodypart >= LIMB_HUMANOID_TORSO )
4500 												{
4501 													Entity* tmp = (Entity*)node->element;
4502 													if ( tmp )
4503 													{
4504 														tmp->flags[USERFLAG2] = true;
4505 														serverUpdateEntityFlag(tmp, USERFLAG2);
4506 													}
4507 												}
4508 												++bodypart;
4509 											}
4510 										}
4511 									}
4512 								}
4513 							}
4514 						}
4515 					}
4516 				}
4517 				list_RemoveNode(my->mynode);
4518 				return;
4519 			}
4520 
4521 			// calculate direction to caster and move.
4522 			real_t tangent = atan2(my->skill[9] - my->y, my->skill[8] - my->x);
4523 			real_t speed = dist / PARTICLE_LIFE;
4524 			my->vel_x = speed * cos(tangent);
4525 			my->vel_y = speed * sin(tangent);
4526 			my->x += my->vel_x;
4527 			my->y += my->vel_y;
4528 		}
4529 		else if ( my->skill[6] == SPELL_STEAL_WEAPON )
4530 		{
4531 			Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
4532 			entity->flags[INVISIBLE] = true;
4533 			entity->flags[UPDATENEEDED] = true;
4534 			entity->x = my->x;
4535 			entity->y = my->y;
4536 			entity->sizex = 4;
4537 			entity->sizey = 4;
4538 			entity->yaw = my->yaw;
4539 			entity->vel_x = (rand() % 20 - 10) / 10.0;
4540 			entity->vel_y = (rand() % 20 - 10) / 10.0;
4541 			entity->vel_z = -.5;
4542 			entity->flags[PASSABLE] = true;
4543 			entity->flags[USERFLAG1] = true; // speeds up game when many items are dropped
4544 			entity->behavior = &actItem;
4545 			entity->skill[10] = my->skill[10];
4546 			entity->skill[11] = my->skill[11];
4547 			entity->skill[12] = my->skill[12];
4548 			entity->skill[13] = my->skill[13];
4549 			entity->skill[14] = my->skill[14];
4550 			entity->skill[15] = my->skill[15];
4551 			entity->itemOriginalOwner = my->itemOriginalOwner;
4552 			entity->parent = 0;
4553 
4554 			// no parent, no target to travel to.
4555 			list_RemoveNode(my->mynode);
4556 			return;
4557 		}
4558 		else if ( my->sprite == 977 )
4559 		{
4560 			// calculate direction to caster and move.
4561 			real_t tangent = atan2(my->fskill[5] - my->y, my->fskill[4] - my->x);
4562 			real_t dist = sqrt(pow(my->x - my->fskill[4], 2) + pow(my->y - my->fskill[5], 2));
4563 			real_t speed = dist / std::max(PARTICLE_LIFE, 1);
4564 
4565 			if ( dist < 4 || (abs(my->fskill[5]) < 0.001 && abs(my->fskill[4]) < 0.001) )
4566 			{
4567 				// reached goal, or goal not set then spawn the item.
4568 				Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
4569 				entity->flags[INVISIBLE] = true;
4570 				entity->flags[UPDATENEEDED] = true;
4571 				entity->x = my->x;
4572 				entity->y = my->y;
4573 				entity->sizex = 4;
4574 				entity->sizey = 4;
4575 				entity->yaw = my->yaw;
4576 				entity->vel_x = (rand() % 20 - 10) / 10.0;
4577 				entity->vel_y = (rand() % 20 - 10) / 10.0;
4578 				entity->vel_z = -.5;
4579 				entity->flags[PASSABLE] = true;
4580 				entity->flags[USERFLAG1] = true; // speeds up game when many items are dropped
4581 				entity->behavior = &actItem;
4582 				entity->skill[10] = my->skill[10];
4583 				entity->skill[11] = my->skill[11];
4584 				entity->skill[12] = my->skill[12];
4585 				entity->skill[13] = my->skill[13];
4586 				entity->skill[14] = my->skill[14];
4587 				entity->skill[15] = my->skill[15];
4588 				entity->itemOriginalOwner = 0;
4589 				entity->parent = 0;
4590 
4591 				list_RemoveNode(my->mynode);
4592 				return;
4593 			}
4594 			my->vel_x = speed * cos(tangent);
4595 			my->vel_y = speed * sin(tangent);
4596 			my->x += my->vel_x;
4597 			my->y += my->vel_y;
4598 		}
4599 		else
4600 		{
4601 			// no parent, no target to travel to.
4602 			list_RemoveNode(my->mynode);
4603 			return;
4604 		}
4605 	}
4606 
4607 	if ( PARTICLE_LIFE < 0 )
4608 	{
4609 		list_RemoveNode(my->mynode);
4610 		return;
4611 	}
4612 	else
4613 	{
4614 		--PARTICLE_LIFE;
4615 	}
4616 }
4617 
createParticleExplosionCharge(Entity * parent,int sprite,int particleCount,double scale)4618 void createParticleExplosionCharge(Entity* parent, int sprite, int particleCount, double scale)
4619 {
4620 	if ( !parent )
4621 	{
4622 		return;
4623 	}
4624 
4625 	for ( int c = 0; c < particleCount; c++ )
4626 	{
4627 		// shoot drops to the sky
4628 		Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
4629 		entity->sizex = 1;
4630 		entity->sizey = 1;
4631 		entity->x = parent->x - 3 + rand() % 7;
4632 		entity->y = parent->y - 3 + rand() % 7;
4633 		entity->z = 0 + rand() % 190;
4634 		if ( parent && parent->behavior == &actPlayer )
4635 		{
4636 			entity->z /= 2;
4637 		}
4638 		entity->vel_z = -1;
4639 		entity->yaw = (rand() % 360) * PI / 180.0;
4640 		entity->particleDuration = entity->z + 10;
4641 		/*if ( rand() % 5 > 0 )
4642 		{
4643 			entity->vel_x = 0.5*cos(entity->yaw);
4644 			entity->vel_y = 0.5*sin(entity->yaw);
4645 			entity->particleDuration = 6;
4646 			entity->z = 0;
4647 			entity->vel_z = 0.5 *(-1 + rand() % 3);
4648 		}*/
4649 		entity->scalex *= scale;
4650 		entity->scaley *= scale;
4651 		entity->scalez *= scale;
4652 		entity->behavior = &actParticleExplosionCharge;
4653 		entity->flags[PASSABLE] = true;
4654 		entity->flags[NOUPDATE] = true;
4655 		entity->flags[UNCLICKABLE] = true;
4656 		entity->parent = parent->getUID();
4657 		if ( multiplayer != CLIENT )
4658 		{
4659 			entity_uids--;
4660 		}
4661 		entity->setUID(-3);
4662 	}
4663 
4664 	int radius = STRIKERANGE * 2 / 3;
4665 	real_t arc = PI / 16;
4666 	int randScale = 1;
4667 	for ( int c = 0; c < 128; c++ )
4668 	{
4669 		// shoot drops to the sky
4670 		Entity* entity = newEntity(670, 1, map.entities, nullptr); //Particle entity.
4671 		entity->sizex = 1;
4672 		entity->sizey = 1;
4673 		entity->yaw = 0 + c * arc;
4674 
4675 		entity->x = parent->x + (radius * cos(entity->yaw));// - 2 + rand() % 5;
4676 		entity->y = parent->y + (radius * sin(entity->yaw));// - 2 + rand() % 5;
4677 		entity->z = radius + 150;
4678 		entity->particleDuration = entity->z + rand() % 3;
4679 		entity->vel_z = -1;
4680 		if ( parent && parent->behavior == &actPlayer )
4681 		{
4682 			entity->z /= 2;
4683 		}
4684 		randScale = 1 + rand() % 3;
4685 
4686 		entity->scalex *= (scale / randScale);
4687 		entity->scaley *= (scale / randScale);
4688 		entity->scalez *= (scale / randScale);
4689 		entity->behavior = &actParticleExplosionCharge;
4690 		entity->flags[PASSABLE] = true;
4691 		entity->flags[NOUPDATE] = true;
4692 		entity->flags[UNCLICKABLE] = true;
4693 		entity->parent = parent->getUID();
4694 		if ( multiplayer != CLIENT )
4695 		{
4696 			entity_uids--;
4697 		}
4698 		entity->setUID(-3);
4699 		if ( c > 0 && c % 16 == 0 )
4700 		{
4701 			radius -= 2;
4702 		}
4703 	}
4704 }
4705 
actParticleExplosionCharge(Entity * my)4706 void actParticleExplosionCharge(Entity* my)
4707 {
4708 	if ( PARTICLE_LIFE < 0 || (my->z < -4 && rand() % 4 == 0) || (ticks % 14 == 0 && uidToEntity(my->parent) == nullptr) )
4709 	{
4710 		list_RemoveNode(my->mynode);
4711 	}
4712 	else
4713 	{
4714 		--PARTICLE_LIFE;
4715 		my->yaw += 0.1;
4716 		my->x += my->vel_x;
4717 		my->y += my->vel_y;
4718 		my->z += my->vel_z;
4719 		my->scalex /= 0.99;
4720 		my->scaley /= 0.99;
4721 		my->scalez /= 0.99;
4722 		//my->z -= 0.01;
4723 	}
4724 	return;
4725 }
4726 
magicFallingCollision()4727 bool Entity::magicFallingCollision()
4728 {
4729 	hit.entity = nullptr;
4730 	if ( z <= -5 || fabs(vel_z) < 0.01 )
4731 	{
4732 		// check if particle stopped or too high.
4733 		return false;
4734 	}
4735 
4736 	if ( z >= 7.5 )
4737 	{
4738 		return true;
4739 	}
4740 
4741 	if ( actmagicIsVertical == MAGIC_ISVERTICAL_Z )
4742 	{
4743 		std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 1);
4744 		for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
4745 		{
4746 			list_t* currentList = *it;
4747 			node_t* node;
4748 			for ( node = currentList->first; node != nullptr; node = node->next )
4749 			{
4750 				Entity* entity = (Entity*)node->element;
4751 				if ( entity )
4752 				{
4753 					if ( entity == this )
4754 					{
4755 						continue;
4756 					}
4757 					if ( entityInsideEntity(this, entity) && !entity->flags[PASSABLE] && (entity->getUID() != this->parent) )
4758 					{
4759 						hit.entity = entity;
4760 						//hit.side = HORIZONTAL;
4761 						return true;
4762 					}
4763 				}
4764 			}
4765 		}
4766 	}
4767 
4768 	return false;
4769 }
4770 
magicOrbitingCollision()4771 bool Entity::magicOrbitingCollision()
4772 {
4773 	hit.entity = nullptr;
4774 
4775 	if ( this->actmagicIsOrbiting == 2 )
4776 	{
4777 		if ( this->ticks == 5 && this->actmagicOrbitHitTargetUID4 != 0 )
4778 		{
4779 			// hit this target automatically
4780 			Entity* tmp = uidToEntity(actmagicOrbitHitTargetUID4);
4781 			if ( tmp )
4782 			{
4783 				hit.entity = tmp;
4784 				return true;
4785 			}
4786 		}
4787 		if ( this->z < -8 || this->z > 3 )
4788 		{
4789 			return false;
4790 		}
4791 		else if ( this->ticks >= 12 && this->ticks % 4 != 0 ) // check once every 4 ticks, after the missile is alive for a bit
4792 		{
4793 			return false;
4794 		}
4795 	}
4796 	else if ( this->z < -10 )
4797 	{
4798 		return false;
4799 	}
4800 
4801 	if ( this->actmagicIsOrbiting == 2 )
4802 	{
4803 		if ( this->actmagicOrbitStationaryHitTarget >= 3 )
4804 		{
4805 			return false;
4806 		}
4807 	}
4808 
4809 	Entity* caster = uidToEntity(parent);
4810 
4811 	std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 1);
4812 
4813 	for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
4814 	{
4815 		list_t* currentList = *it;
4816 		node_t* node;
4817 		for ( node = currentList->first; node != NULL; node = node->next )
4818 		{
4819 			Entity* entity = (Entity*)node->element;
4820 			if ( entity == this )
4821 			{
4822 				continue;
4823 			}
4824 			if ( entity->behavior != &actMonster
4825 				&& entity->behavior != &actPlayer
4826 				&& entity->behavior != &actDoor
4827 				&& entity->behavior != &::actChest
4828 				&& entity->behavior != &::actFurniture )
4829 			{
4830 				continue;
4831 			}
4832 			if ( caster && !(svFlags & SV_FLAG_FRIENDLYFIRE) && caster->checkFriend(entity) )
4833 			{
4834 				continue;
4835 			}
4836 			if ( actmagicIsOrbiting == 2 )
4837 			{
4838 				if ( static_cast<Uint32>(actmagicOrbitHitTargetUID1) == entity->getUID()
4839 					|| static_cast<Uint32>(actmagicOrbitHitTargetUID2) == entity->getUID()
4840 					|| static_cast<Uint32>(actmagicOrbitHitTargetUID3) == entity->getUID()
4841 					|| static_cast<Uint32>(actmagicOrbitHitTargetUID4) == entity->getUID() )
4842 				{
4843 					// we already hit these guys.
4844 					continue;
4845 				}
4846 			}
4847 			if ( entityInsideEntity(this, entity) && !entity->flags[PASSABLE] && (entity->getUID() != this->parent) )
4848 			{
4849 				hit.entity = entity;
4850 				if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
4851 				{
4852 					if ( actmagicIsOrbiting == 2 )
4853 					{
4854 						if ( actmagicOrbitHitTargetUID4 != 0 && caster && caster->behavior == &actPlayer )
4855 						{
4856 							if ( actmagicOrbitHitTargetUID1 == 0
4857 								&& actmagicOrbitHitTargetUID2 == 0
4858 								&& actmagicOrbitHitTargetUID3 == 0
4859 								&& hit.entity->behavior == &actMonster )
4860 							{
4861 								steamStatisticUpdateClient(caster->skill[2], STEAM_STAT_VOLATILE, STEAM_STAT_INT, 1);
4862 							}
4863 						}
4864 						++actmagicOrbitStationaryHitTarget;
4865 						if ( actmagicOrbitHitTargetUID1 == 0 )
4866 						{
4867 							actmagicOrbitHitTargetUID1 = entity->getUID();
4868 						}
4869 						else if ( actmagicOrbitHitTargetUID2 == 0 )
4870 						{
4871 							actmagicOrbitHitTargetUID2 = entity->getUID();
4872 						}
4873 						else if ( actmagicOrbitHitTargetUID3 == 0 )
4874 						{
4875 							actmagicOrbitHitTargetUID3 = entity->getUID();
4876 						}
4877 					}
4878 				}
4879 				return true;
4880 			}
4881 		}
4882 	}
4883 
4884 	return false;
4885 }
4886 
castFallingMagicMissile(int spellID,real_t distFromCaster,real_t angleFromCasterDirection,int heightDelay)4887 void Entity::castFallingMagicMissile(int spellID, real_t distFromCaster, real_t angleFromCasterDirection, int heightDelay)
4888 {
4889 	spell_t* spell = getSpellFromID(spellID);
4890 	Entity* entity = castSpell(getUID(), spell, false, true);
4891 	if ( entity )
4892 	{
4893 		entity->x = x + distFromCaster * cos(yaw + angleFromCasterDirection);
4894 		entity->y = y + distFromCaster * sin(yaw + angleFromCasterDirection);
4895 		entity->z = -25 - heightDelay;
4896 		double missile_speed = 4 * ((double)(((spellElement_t*)(spell->elements.first->element))->mana)
4897 			/ ((spellElement_t*)(spell->elements.first->element))->overload_multiplier);
4898 		entity->vel_x = 0.0;
4899 		entity->vel_y = 0.0;
4900 		entity->vel_z = 0.5 * (missile_speed);
4901 		entity->pitch = PI / 2;
4902 		entity->actmagicIsVertical = MAGIC_ISVERTICAL_Z;
4903 		spawnMagicEffectParticles(entity->x, entity->y, 0, 174);
4904 		playSoundEntity(entity, spellGetCastSound(spell), 128);
4905 	}
4906 }
4907 
castOrbitingMagicMissile(int spellID,real_t distFromCaster,real_t angleFromCasterDirection,int duration)4908 Entity* Entity::castOrbitingMagicMissile(int spellID, real_t distFromCaster, real_t angleFromCasterDirection, int duration)
4909 {
4910 	spell_t* spell = getSpellFromID(spellID);
4911 	Entity* entity = castSpell(getUID(), spell, false, true);
4912 	if ( entity )
4913 	{
4914 		if ( spellID == SPELL_FIREBALL )
4915 		{
4916 			entity->sprite = 671;
4917 		}
4918 		else if ( spellID == SPELL_MAGICMISSILE )
4919 		{
4920 			entity->sprite = 679;
4921 		}
4922 		entity->yaw = angleFromCasterDirection;
4923 		entity->x = x + distFromCaster * cos(yaw + entity->yaw);
4924 		entity->y = y + distFromCaster * sin(yaw + entity->yaw);
4925 		entity->z = -2.5;
4926 		double missile_speed = 4 * ((double)(((spellElement_t*)(spell->elements.first->element))->mana)
4927 			/ ((spellElement_t*)(spell->elements.first->element))->overload_multiplier);
4928 		entity->vel_x = 0.0;
4929 		entity->vel_y = 0.0;
4930 		entity->actmagicIsOrbiting = 1;
4931 		entity->actmagicOrbitDist = distFromCaster;
4932 		entity->actmagicOrbitStartZ = entity->z;
4933 		entity->z += 4 * sin(angleFromCasterDirection);
4934 		entity->roll += (PI / 8) * (1 - abs(sin(angleFromCasterDirection)));
4935 		entity->actmagicOrbitVerticalSpeed = 0.1;
4936 		entity->actmagicOrbitVerticalDirection = 1;
4937 		entity->actmagicOrbitLifetime = duration;
4938 		entity->vel_z = entity->actmagicOrbitVerticalSpeed;
4939 		playSoundEntity(entity, spellGetCastSound(spell), 128);
4940 		//spawnMagicEffectParticles(entity->x, entity->y, 0, 174);
4941 	}
4942 	return entity;
4943 }
4944 
castStationaryOrbitingMagicMissile(Entity * parent,int spellID,real_t centerx,real_t centery,real_t distFromCenter,real_t angleFromCenterDirection,int duration)4945 Entity* castStationaryOrbitingMagicMissile(Entity* parent, int spellID, real_t centerx, real_t centery,
4946 	real_t distFromCenter, real_t angleFromCenterDirection, int duration)
4947 {
4948 	spell_t* spell = getSpellFromID(spellID);
4949 	if ( !parent )
4950 	{
4951 		Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Particle entity.
4952 		entity->sizex = 1;
4953 		entity->sizey = 1;
4954 		entity->x = centerx;
4955 		entity->y = centery;
4956 		entity->z = 15;
4957 		entity->vel_z = 0;
4958 		//entity->yaw = (rand() % 360) * PI / 180.0;
4959 		entity->skill[0] = 100;
4960 		entity->skill[1] = 10;
4961 		entity->behavior = &actParticleDot;
4962 		entity->flags[PASSABLE] = true;
4963 		entity->flags[NOUPDATE] = true;
4964 		entity->flags[UNCLICKABLE] = true;
4965 		entity->flags[INVISIBLE] = true;
4966 		parent = entity;
4967 	}
4968 	Stat* stats = parent->getStats();
4969 	bool amplify = false;
4970 	if ( stats )
4971 	{
4972 		amplify = stats->EFFECTS[EFF_MAGICAMPLIFY];
4973 		stats->EFFECTS[EFF_MAGICAMPLIFY] = false; // temporary skip amplify effects otherwise recursion.
4974 	}
4975 	Entity* entity = castSpell(parent->getUID(), spell, false, true);
4976 	if ( stats )
4977 	{
4978 		stats->EFFECTS[EFF_MAGICAMPLIFY] = amplify;
4979 	}
4980 	if ( entity )
4981 	{
4982 		if ( spellID == SPELL_FIREBALL )
4983 		{
4984 			entity->sprite = 671;
4985 		}
4986 		else if ( spellID == SPELL_COLD )
4987 		{
4988 			entity->sprite = 797;
4989 		}
4990 		else if ( spellID == SPELL_LIGHTNING )
4991 		{
4992 			entity->sprite = 798;
4993 		}
4994 		else if ( spellID == SPELL_MAGICMISSILE )
4995 		{
4996 			entity->sprite = 679;
4997 		}
4998 		entity->yaw = angleFromCenterDirection;
4999 		entity->x = centerx;
5000 		entity->y = centery;
5001 		entity->z = 4;
5002 		double missile_speed = 4 * ((double)(((spellElement_t*)(spell->elements.first->element))->mana)
5003 			/ ((spellElement_t*)(spell->elements.first->element))->overload_multiplier);
5004 		entity->vel_x = 0.0;
5005 		entity->vel_y = 0.0;
5006 		entity->actmagicIsOrbiting = 2;
5007 		entity->actmagicOrbitDist = distFromCenter;
5008 		entity->actmagicOrbitStationaryCurrentDist = 0.0;
5009 		entity->actmagicOrbitStartZ = entity->z;
5010 		//entity->roll -= (PI / 8);
5011 		entity->actmagicOrbitVerticalSpeed = -0.3;
5012 		entity->actmagicOrbitVerticalDirection = 1;
5013 		entity->actmagicOrbitLifetime = duration;
5014 		entity->actmagicOrbitStationaryX = centerx;
5015 		entity->actmagicOrbitStationaryY = centery;
5016 		entity->vel_z = -0.1;
5017 		playSoundEntity(entity, spellGetCastSound(spell), 128);
5018 
5019 		//spawnMagicEffectParticles(entity->x, entity->y, 0, 174);
5020 	}
5021 	return entity;
5022 }
5023 
createParticleFollowerCommand(real_t x,real_t y,real_t z,int sprite)5024 void createParticleFollowerCommand(real_t x, real_t y, real_t z, int sprite)
5025 {
5026 	Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
5027 	//entity->sizex = 1;
5028 	//entity->sizey = 1;
5029 	entity->x = x;
5030 	entity->y = y;
5031 	entity->z = 7.5;
5032 	entity->vel_z = -0.8;
5033 	//entity->yaw = (rand() % 360) * PI / 180.0;
5034 	entity->skill[0] = 50;
5035 	entity->behavior = &actParticleFollowerCommand;
5036 	entity->flags[PASSABLE] = true;
5037 	entity->flags[NOUPDATE] = true;
5038 	entity->flags[UNCLICKABLE] = true;
5039 	if ( multiplayer != CLIENT )
5040 	{
5041 		entity_uids--;
5042 	}
5043 	entity->setUID(-3);
5044 
5045 	// boosty boost
5046 	for ( int c = 0; c < 10; c++ )
5047 	{
5048 		entity = newEntity(sprite, 1, map.entities, nullptr); //Particle entity.
5049 		entity->x = x - 4 + rand() % 9;
5050 		entity->y = y - 4 + rand() % 9;
5051 		entity->z = z - 0 + rand() % 11;
5052 		entity->scalex = 0.7;
5053 		entity->scaley = 0.7;
5054 		entity->scalez = 0.7;
5055 		entity->sizex = 1;
5056 		entity->sizey = 1;
5057 		entity->yaw = (rand() % 360) * PI / 180.f;
5058 		entity->flags[PASSABLE] = true;
5059 		entity->flags[BRIGHT] = true;
5060 		entity->flags[NOUPDATE] = true;
5061 		entity->flags[UNCLICKABLE] = true;
5062 		entity->behavior = &actMagicParticle;
5063 		entity->vel_z = -1;
5064 		if ( multiplayer != CLIENT )
5065 		{
5066 			entity_uids--;
5067 		}
5068 		entity->setUID(-3);
5069 	}
5070 
5071 }
5072 
actParticleFollowerCommand(Entity * my)5073 void actParticleFollowerCommand(Entity* my)
5074 {
5075 	if ( PARTICLE_LIFE < 0 )
5076 	{
5077 		list_RemoveNode(my->mynode);
5078 		return;
5079 	}
5080 	else
5081 	{
5082 		--PARTICLE_LIFE;
5083 		my->z += my->vel_z;
5084 		my->yaw += my->vel_z * 2;
5085 		if ( my->z < -3 )
5086 		{
5087 			my->vel_z *= 0.9;
5088 		}
5089 	}
5090 }
5091 
actParticleShadowTag(Entity * my)5092 void actParticleShadowTag(Entity* my)
5093 {
5094 	if ( PARTICLE_LIFE < 0 )
5095 	{
5096 		// once off, fire some erupt dot particles at end of life.
5097 		real_t yaw = 0;
5098 		int numParticles = 8;
5099 		for ( int c = 0; c < 8; c++ )
5100 		{
5101 			Entity* entity = newEntity(871, 1, map.entities, nullptr); //Particle entity.
5102 			entity->sizex = 1;
5103 			entity->sizey = 1;
5104 			entity->x = my->x;
5105 			entity->y = my->y;
5106 			entity->z = -10 + my->fskill[0];
5107 			entity->yaw = yaw;
5108 			entity->vel_x = 0.2;
5109 			entity->vel_y = 0.2;
5110 			entity->vel_z = -0.02;
5111 			entity->skill[0] = 100;
5112 			entity->skill[1] = 0; // direction.
5113 			entity->fskill[0] = 0.1;
5114 			entity->behavior = &actParticleErupt;
5115 			entity->flags[PASSABLE] = true;
5116 			entity->flags[NOUPDATE] = true;
5117 			entity->flags[UNCLICKABLE] = true;
5118 			if ( multiplayer != CLIENT )
5119 			{
5120 				entity_uids--;
5121 			}
5122 			entity->setUID(-3);
5123 			yaw += 2 * PI / numParticles;
5124 		}
5125 
5126 		if ( multiplayer != CLIENT )
5127 		{
5128 			Uint32 casterUid = static_cast<Uint32>(my->skill[2]);
5129 			Entity* caster = uidToEntity(casterUid);
5130 			Entity* parent = uidToEntity(my->parent);
5131 			if ( caster && caster->behavior == &actPlayer
5132 				&& parent )
5133 			{
5134 				// caster is alive, notify they lost their mark
5135 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
5136 				if ( parent->getStats() )
5137 				{
5138 					messagePlayerMonsterEvent(caster->skill[2], color, *(parent->getStats()), language[3466], language[3467], MSG_COMBAT);
5139 					parent->setEffect(EFF_SHADOW_TAGGED, false, 0, true);
5140 				}
5141 			}
5142 		}
5143 		my->removeLightField();
5144 		list_RemoveNode(my->mynode);
5145 		return;
5146 	}
5147 	else
5148 	{
5149 		--PARTICLE_LIFE;
5150 		my->removeLightField();
5151 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 92);
5152 
5153 		Entity* parent = uidToEntity(my->parent);
5154 		if ( parent )
5155 		{
5156 			my->x = parent->x;
5157 			my->y = parent->y;
5158 		}
5159 
5160 		if ( my->skill[1] >= 50 ) // stop changing size
5161 		{
5162 			real_t maxspeed = .03;
5163 			real_t acceleration = 0.95;
5164 			if ( my->skill[3] == 0 )
5165 			{
5166 				// once off, store the normal height of the particle.
5167 				my->skill[3] = 1;
5168 				my->vel_z = -maxspeed;
5169 			}
5170 			if ( my->skill[1] % 5 == 0 )
5171 			{
5172 				Uint32 casterUid = static_cast<Uint32>(my->skill[2]);
5173 				Entity* caster = uidToEntity(casterUid);
5174 				if ( caster && caster->creatureShadowTaggedThisUid == my->parent && parent )
5175 				{
5176 					// caster is alive, and they have still marked the parent this particle is following.
5177 				}
5178 				else
5179 				{
5180 					PARTICLE_LIFE = 0;
5181 				}
5182 			}
5183 
5184 			if ( PARTICLE_LIFE > 0 && PARTICLE_LIFE < TICKS_PER_SECOND )
5185 			{
5186 				if ( parent && parent->getStats() && parent->getStats()->EFFECTS[EFF_SHADOW_TAGGED] )
5187 				{
5188 					++PARTICLE_LIFE;
5189 				}
5190 			}
5191 			// bob up and down movement.
5192 			if ( my->skill[3] == 1 )
5193 			{
5194 				my->vel_z *= acceleration;
5195 				if ( my->vel_z > -0.005 )
5196 				{
5197 					my->skill[3] = 2;
5198 					my->vel_z = -0.005;
5199 				}
5200 				my->z += my->vel_z;
5201 			}
5202 			else if ( my->skill[3] == 2 )
5203 			{
5204 				my->vel_z /= acceleration;
5205 				if ( my->vel_z < -maxspeed )
5206 				{
5207 					my->skill[3] = 3;
5208 					my->vel_z = -maxspeed;
5209 				}
5210 				my->z -= my->vel_z;
5211 			}
5212 			else if ( my->skill[3] == 3 )
5213 			{
5214 				my->vel_z *= acceleration;
5215 				if ( my->vel_z > -0.005 )
5216 				{
5217 					my->skill[3] = 4;
5218 					my->vel_z = -0.005;
5219 				}
5220 				my->z -= my->vel_z;
5221 			}
5222 			else if ( my->skill[3] == 4 )
5223 			{
5224 				my->vel_z /= acceleration;
5225 				if ( my->vel_z < -maxspeed )
5226 				{
5227 					my->skill[3] = 1;
5228 					my->vel_z = -maxspeed;
5229 				}
5230 				my->z += my->vel_z;
5231 			}
5232 			my->yaw += 0.01;
5233 		}
5234 		else
5235 		{
5236 			my->z += my->vel_z;
5237 			my->yaw += my->vel_z * 2;
5238 			if ( my->scalex < 0.5 )
5239 			{
5240 				my->scalex += 0.02;
5241 			}
5242 			else
5243 			{
5244 				my->scalex = 0.5;
5245 			}
5246 			my->scaley = my->scalex;
5247 			my->scalez = my->scalex;
5248 			if ( my->z < -3 + my->fskill[0] )
5249 			{
5250 				my->vel_z *= 0.9;
5251 			}
5252 		}
5253 
5254 		// once off, fire some erupt dot particles at start.
5255 		if ( my->skill[1] == 0 )
5256 		{
5257 			real_t yaw = 0;
5258 			int numParticles = 8;
5259 			for ( int c = 0; c < 8; c++ )
5260 			{
5261 				Entity* entity = newEntity(871, 1, map.entities, nullptr); //Particle entity.
5262 				entity->sizex = 1;
5263 				entity->sizey = 1;
5264 				entity->x = my->x;
5265 				entity->y = my->y;
5266 				entity->z = -10 + my->fskill[0];
5267 				entity->yaw = yaw;
5268 				entity->vel_x = 0.2;
5269 				entity->vel_y = 0.2;
5270 				entity->vel_z = -0.02;
5271 				entity->skill[0] = 100;
5272 				entity->skill[1] = 0; // direction.
5273 				entity->fskill[0] = 0.1;
5274 				entity->behavior = &actParticleErupt;
5275 				entity->flags[PASSABLE] = true;
5276 				entity->flags[NOUPDATE] = true;
5277 				entity->flags[UNCLICKABLE] = true;
5278 				if ( multiplayer != CLIENT )
5279 				{
5280 					entity_uids--;
5281 				}
5282 				entity->setUID(-3);
5283 				yaw += 2 * PI / numParticles;
5284 			}
5285 		}
5286 		++my->skill[1];
5287 	}
5288 }
5289 
createParticleShadowTag(Entity * parent,Uint32 casterUid,int duration)5290 void createParticleShadowTag(Entity* parent, Uint32 casterUid, int duration)
5291 {
5292 	if ( !parent )
5293 	{
5294 		return;
5295 	}
5296 	Entity* entity = newEntity(870, 1, map.entities, nullptr); //Particle entity.
5297 	entity->parent = parent->getUID();
5298 	entity->x = parent->x;
5299 	entity->y = parent->y;
5300 	entity->z = 7.5;
5301 	entity->fskill[0] = parent->z;
5302 	entity->vel_z = -0.8;
5303 	entity->scalex = 0.1;
5304 	entity->scaley = 0.1;
5305 	entity->scalez = 0.1;
5306 	entity->yaw = (rand() % 360) * PI / 180.0;
5307 	entity->skill[0] = duration;
5308 	entity->skill[1] = 0;
5309 	entity->skill[2] = static_cast<Sint32>(casterUid);
5310 	entity->skill[3] = 0;
5311 	entity->behavior = &actParticleShadowTag;
5312 	entity->flags[PASSABLE] = true;
5313 	entity->flags[NOUPDATE] = true;
5314 	entity->flags[UNCLICKABLE] = true;
5315 	if ( multiplayer != CLIENT )
5316 	{
5317 		entity_uids--;
5318 	}
5319 	entity->setUID(-3);
5320 }
5321 
createParticleCharmMonster(Entity * parent)5322 void createParticleCharmMonster(Entity* parent)
5323 {
5324 	if ( !parent )
5325 	{
5326 		return;
5327 	}
5328 	Entity* entity = newEntity(685, 1, map.entities, nullptr); //Particle entity.
5329 	//entity->sizex = 1;
5330 	//entity->sizey = 1;
5331 	entity->parent = parent->getUID();
5332 	entity->x = parent->x;
5333 	entity->y = parent->y;
5334 	entity->z = 7.5;
5335 	entity->vel_z = -0.8;
5336 	entity->scalex = 0.1;
5337 	entity->scaley = 0.1;
5338 	entity->scalez = 0.1;
5339 	entity->yaw = (rand() % 360) * PI / 180.0;
5340 	entity->skill[0] = 45;
5341 	entity->behavior = &actParticleCharmMonster;
5342 	entity->flags[PASSABLE] = true;
5343 	entity->flags[NOUPDATE] = true;
5344 	entity->flags[UNCLICKABLE] = true;
5345 	if ( multiplayer != CLIENT )
5346 	{
5347 		entity_uids--;
5348 	}
5349 	entity->setUID(-3);
5350 }
5351 
actParticleCharmMonster(Entity * my)5352 void actParticleCharmMonster(Entity* my)
5353 {
5354 	if ( PARTICLE_LIFE < 0 )
5355 	{
5356 		real_t yaw = 0;
5357 		int numParticles = 8;
5358 		for ( int c = 0; c < 8; c++ )
5359 		{
5360 			Entity* entity = newEntity(576, 1, map.entities, nullptr); //Particle entity.
5361 			entity->sizex = 1;
5362 			entity->sizey = 1;
5363 			entity->x = my->x;
5364 			entity->y = my->y;
5365 			entity->z = -10;
5366 			entity->yaw = yaw;
5367 			entity->vel_x = 0.2;
5368 			entity->vel_y = 0.2;
5369 			entity->vel_z = -0.02;
5370 			entity->skill[0] = 100;
5371 			entity->skill[1] = 0; // direction.
5372 			entity->fskill[0] = 0.1;
5373 			entity->behavior = &actParticleErupt;
5374 			entity->flags[PASSABLE] = true;
5375 			entity->flags[NOUPDATE] = true;
5376 			entity->flags[UNCLICKABLE] = true;
5377 			if ( multiplayer != CLIENT )
5378 			{
5379 				entity_uids--;
5380 			}
5381 			entity->setUID(-3);
5382 			yaw += 2 * PI / numParticles;
5383 		}
5384 		list_RemoveNode(my->mynode);
5385 		return;
5386 	}
5387 	else
5388 	{
5389 		--PARTICLE_LIFE;
5390 		Entity* parent = uidToEntity(my->parent);
5391 		if ( parent )
5392 		{
5393 			my->x = parent->x;
5394 			my->y = parent->y;
5395 		}
5396 		my->z += my->vel_z;
5397 		my->yaw += my->vel_z * 2;
5398 		if ( my->scalex < 0.8 )
5399 		{
5400 			my->scalex += 0.02;
5401 		}
5402 		else
5403 		{
5404 			my->scalex = 0.8;
5405 		}
5406 		my->scaley = my->scalex;
5407 		my->scalez = my->scalex;
5408 		if ( my->z < -3 )
5409 		{
5410 			my->vel_z *= 0.9;
5411 		}
5412 	}
5413 }
5414 
spawnMagicTower(Entity * parent,real_t x,real_t y,int spellID,Entity * autoHitTarget,bool castedSpell)5415 void spawnMagicTower(Entity* parent, real_t x, real_t y, int spellID, Entity* autoHitTarget, bool castedSpell)
5416 {
5417 	bool autoHit = false;
5418 	if ( autoHitTarget && (autoHitTarget->behavior == &actPlayer || autoHitTarget->behavior == &actMonster) )
5419 	{
5420 		autoHit = true;
5421 		if ( parent )
5422 		{
5423 			if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) && parent->checkFriend(autoHitTarget) )
5424 			{
5425 				autoHit = false; // don't hit friendlies
5426 			}
5427 		}
5428 	}
5429 	Entity* orbit = castStationaryOrbitingMagicMissile(parent, spellID, x, y, 16.0, 0.0, 40);
5430 	if ( orbit )
5431 	{
5432 		if ( castedSpell )
5433 		{
5434 			orbit->actmagicOrbitCastFromSpell = 1;
5435 		}
5436 		if ( autoHit )
5437 		{
5438 			orbit->actmagicOrbitHitTargetUID4 = autoHitTarget->getUID();
5439 		}
5440 	}
5441 	orbit = castStationaryOrbitingMagicMissile(parent, spellID, x, y, 16.0, 2 * PI / 3, 40);
5442 	if ( orbit )
5443 	{
5444 		if ( castedSpell )
5445 		{
5446 			orbit->actmagicOrbitCastFromSpell = 1;
5447 		}
5448 		if ( autoHit )
5449 		{
5450 			orbit->actmagicOrbitHitTargetUID4 = autoHitTarget->getUID();
5451 		}
5452 	}
5453 	orbit = castStationaryOrbitingMagicMissile(parent, spellID, x, y, 16.0, 4 * PI / 3, 40);
5454 	if ( orbit )
5455 	{
5456 		if ( castedSpell )
5457 		{
5458 			orbit->actmagicOrbitCastFromSpell = 1;
5459 		}
5460 		if ( autoHit )
5461 		{
5462 			orbit->actmagicOrbitHitTargetUID4 = autoHitTarget->getUID();
5463 		}
5464 	}
5465 	spawnMagicEffectParticles(x, y, 0, 174);
5466 	spawnExplosion(x, y, -4 + rand() % 8);
5467 }
5468 
magicDig(Entity * parent,Entity * projectile,int numRocks,int randRocks)5469 void magicDig(Entity* parent, Entity* projectile, int numRocks, int randRocks)
5470 {
5471 	if ( !hit.entity )
5472 	{
5473 		if ( map.tiles[(int)(OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height)] != 0 )
5474 		{
5475 			if ( parent && parent->behavior == &actPlayer && MFLAG_DISABLEDIGGING )
5476 			{
5477 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
5478 				messagePlayerColor(parent->skill[2], color, language[2380]); // disabled digging.
5479 				playSoundPos(hit.x, hit.y, 66, 128); // strike wall
5480 			}
5481 			else
5482 			{
5483 				if ( projectile )
5484 				{
5485 					playSoundEntity(projectile, 66, 128);
5486 					playSoundEntity(projectile, 67, 128);
5487 				}
5488 
5489 				// spawn several rock items
5490 				if ( randRocks <= 0 )
5491 				{
5492 					randRocks = 1;
5493 				}
5494 				int i = numRocks + rand() % randRocks;
5495 				for ( int c = 0; c < i; c++ )
5496 				{
5497 					Entity* rock = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
5498 					rock->flags[INVISIBLE] = true;
5499 					rock->flags[UPDATENEEDED] = true;
5500 					rock->x = hit.mapx * 16 + 4 + rand() % 8;
5501 					rock->y = hit.mapy * 16 + 4 + rand() % 8;
5502 					rock->z = -6 + rand() % 12;
5503 					rock->sizex = 4;
5504 					rock->sizey = 4;
5505 					rock->yaw = rand() % 360 * PI / 180;
5506 					rock->vel_x = (rand() % 20 - 10) / 10.0;
5507 					rock->vel_y = (rand() % 20 - 10) / 10.0;
5508 					rock->vel_z = -.25 - (rand() % 5) / 10.0;
5509 					rock->flags[PASSABLE] = true;
5510 					rock->behavior = &actItem;
5511 					rock->flags[USERFLAG1] = true; // no collision: helps performance
5512 					rock->skill[10] = GEM_ROCK;    // type
5513 					rock->skill[11] = WORN;        // status
5514 					rock->skill[12] = 0;           // beatitude
5515 					rock->skill[13] = 1;           // count
5516 					rock->skill[14] = 0;           // appearance
5517 					rock->skill[15] = 1;		   // identified
5518 				}
5519 
5520 				if ( map.tiles[(int)(OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height)] >= 41
5521 					&& map.tiles[(int)(OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height)] <= 49 )
5522 				{
5523 					steamAchievementEntity(parent, "BARONY_ACH_BAD_REVIEW");
5524 				}
5525 
5526 				map.tiles[(int)(OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height)] = 0;
5527 
5528 				// send wall destroy info to clients
5529 				for ( int c = 1; c < MAXPLAYERS; c++ )
5530 				{
5531 					if ( client_disconnected[c] == true )
5532 					{
5533 						continue;
5534 					}
5535 					strcpy((char*)net_packet->data, "WALD");
5536 					SDLNet_Write16((Uint16)hit.mapx, &net_packet->data[4]);
5537 					SDLNet_Write16((Uint16)hit.mapy, &net_packet->data[6]);
5538 					net_packet->address.host = net_clients[c - 1].host;
5539 					net_packet->address.port = net_clients[c - 1].port;
5540 					net_packet->len = 8;
5541 					sendPacketSafe(net_sock, -1, net_packet, c - 1);
5542 				}
5543 
5544 				generatePathMaps();
5545 			}
5546 		}
5547 	}
5548 	else if ( hit.entity->behavior == &actBoulder )
5549 	{
5550 		int i = numRocks + rand() % 4;
5551 
5552 		// spawn several rock items //TODO: This should really be its own function.
5553 		int c;
5554 		for ( c = 0; c < i; c++ )
5555 		{
5556 			Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
5557 			entity->flags[INVISIBLE] = true;
5558 			entity->flags[UPDATENEEDED] = true;
5559 			entity->x = hit.entity->x - 4 + rand() % 8;
5560 			entity->y = hit.entity->y - 4 + rand() % 8;
5561 			entity->z = -6 + rand() % 12;
5562 			entity->sizex = 4;
5563 			entity->sizey = 4;
5564 			entity->yaw = rand() % 360 * PI / 180;
5565 			entity->vel_x = (rand() % 20 - 10) / 10.0;
5566 			entity->vel_y = (rand() % 20 - 10) / 10.0;
5567 			entity->vel_z = -.25 - (rand() % 5) / 10.0;
5568 			entity->flags[PASSABLE] = true;
5569 			entity->behavior = &actItem;
5570 			entity->flags[USERFLAG1] = true; // no collision: helps performance
5571 			entity->skill[10] = GEM_ROCK;    // type
5572 			entity->skill[11] = WORN;        // status
5573 			entity->skill[12] = 0;           // beatitude
5574 			entity->skill[13] = 1;           // count
5575 			entity->skill[14] = 0;           // appearance
5576 			entity->skill[15] = false;       // identified
5577 		}
5578 
5579 		double ox = hit.entity->x;
5580 		double oy = hit.entity->y;
5581 
5582 		boulderLavaOrArcaneOnDestroy(hit.entity, hit.entity->sprite, nullptr);
5583 
5584 		// destroy the boulder
5585 		playSoundEntity(hit.entity, 67, 128);
5586 		list_RemoveNode(hit.entity->mynode);
5587 		if ( parent )
5588 		{
5589 			if ( parent->behavior == &actPlayer )
5590 			{
5591 				messagePlayer(parent->skill[2], language[405]);
5592 			}
5593 		}
5594 
5595 		// on sokoban, destroying boulders spawns scorpions
5596 		if ( !strcmp(map.name, "Sokoban") )
5597 		{
5598 			Entity* monster = nullptr;
5599 			if ( rand() % 2 == 0 )
5600 			{
5601 				monster = summonMonster(INSECTOID, ox, oy);
5602 			}
5603 			else
5604 			{
5605 				monster = summonMonster(SCORPION, ox, oy);
5606 			}
5607 			if ( monster )
5608 			{
5609 				int c;
5610 				for ( c = 0; c < MAXPLAYERS; c++ )
5611 				{
5612 					Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
5613 					messagePlayerColor(c, color, language[406]);
5614 				}
5615 			}
5616 			boulderSokobanOnDestroy(false);
5617 		}
5618 	}
5619 }