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 }