1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: actbeartrap.cpp
5 Desc: behavior function for set beartraps
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 "messages.hpp"
16 #include "sound.hpp"
17 #include "entity.hpp"
18 #include "items.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23 #include "scores.hpp"
24 #include "monster.hpp"
25
26 /*-------------------------------------------------------------------------------
27
28 act*
29
30 The following function describes an entity behavior. The function
31 takes a pointer to the entity that uses it as an argument.
32
33 -------------------------------------------------------------------------------*/
34
35 #define BEARTRAP_CAUGHT my->skill[0]
36 #define BEARTRAP_STATUS my->skill[11]
37 #define BEARTRAP_BEATITUDE my->skill[12]
38 #define BEARTRAP_APPEARANCE my->skill[14]
39 #define BEARTRAP_IDENTIFIED my->skill[15]
40 #define BEARTRAP_OWNER my->skill[17]
41
actBeartrap(Entity * my)42 void actBeartrap(Entity* my)
43 {
44 int i;
45 if ( my->sprite == 667 )
46 {
47 my->roll = 0;
48 my->z = 6.75;
49 }
50
51 if ( multiplayer == CLIENT )
52 {
53 return;
54 }
55
56 // undo beartrap
57 for (i = 0; i < MAXPLAYERS; i++)
58 {
59 if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
60 {
61 if (inrange[i])
62 {
63 Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
64 entity->flags[INVISIBLE] = true;
65 entity->flags[UPDATENEEDED] = true;
66 entity->flags[PASSABLE] = true;
67 entity->x = my->x;
68 entity->y = my->y;
69 entity->z = my->z;
70 entity->sizex = my->sizex;
71 entity->sizey = my->sizey;
72 entity->yaw = my->yaw;
73 entity->pitch = my->pitch;
74 entity->roll = PI / 2;
75 entity->behavior = &actItem;
76 entity->skill[10] = TOOL_BEARTRAP;
77 entity->skill[11] = BEARTRAP_STATUS;
78 entity->skill[12] = BEARTRAP_BEATITUDE;
79 entity->skill[13] = 1;
80 entity->skill[14] = BEARTRAP_APPEARANCE;
81 entity->skill[15] = BEARTRAP_IDENTIFIED;
82 entity->itemNotMoving = 1;
83 entity->itemNotMovingClient = 1;
84 messagePlayer(i, language[1300]);
85 list_RemoveNode(my->mynode);
86 return;
87 }
88 }
89 }
90
91 if ( BEARTRAP_CAUGHT == 1 )
92 {
93 return;
94 }
95
96 // launch beartrap
97 node_t* node;
98 for ( node = map.creatures->first; node != nullptr; node = node->next )
99 {
100 Entity* entity = (Entity*)node->element;
101 if ( my->parent == entity->getUID() )
102 {
103 continue;
104 }
105 if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
106 {
107 Stat* stat = entity->getStats();
108 if ( stat )
109 {
110 Entity* parent = uidToEntity(my->parent);
111 if ( (parent && parent->checkFriend(entity)) )
112 {
113 continue;
114 }
115 if ( !parent && BEARTRAP_OWNER >= 0 )
116 {
117 if ( entity->behavior == &actPlayer )
118 {
119 continue; // players won't trigger if owner dead.
120 }
121 else if ( entity->monsterAllyGetPlayerLeader() )
122 {
123 continue; // player followers won't trigger if owner dead.
124 }
125 }
126 if ( entityDist(my, entity) < 6.5 )
127 {
128 entity->setEffect(EFF_PARALYZED, true, 200, false);
129 entity->setEffect(EFF_BLEEDING, true, 300, false);
130 int damage = 10 + 3 * (BEARTRAP_STATUS + BEARTRAP_BEATITUDE);
131 if ( parent )
132 {
133 stat->bleedInflictedBy = static_cast<Sint32>(parent->getUID());
134 //damage += trapperStat->PROFICIENCIES[PRO_LOCKPICKING] / 20;
135 }
136 int oldHP = stat->HP;
137 //messagePlayer(0, "dmg: %d", damage);
138 entity->modHP(-damage);
139 //// alert the monster! DOES NOT WORK DURING PARALYZE.
140 //if ( entity->behavior == &actMonster && entity->monsterState != MONSTER_STATE_ATTACK && (stat->type < LICH || stat->type >= SHOPKEEPER) )
141 //{
142 // Entity* attackTarget = uidToEntity(my->parent);
143 // if ( attackTarget )
144 // {
145 // entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
146 // }
147 //}
148 // set obituary
149 entity->setObituary(language[1504]);
150
151 if ( stat->HP <= 0 && oldHP > 0 )
152 {
153 if ( parent )
154 {
155 parent->awardXP( entity, true, true );
156 }
157 }
158 if ( entity->behavior == &actPlayer )
159 {
160 int player = entity->skill[2];
161 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
162 messagePlayerColor(player, color, language[454]);
163 if ( player > 0 )
164 {
165 serverUpdateEffects(player);
166 }
167 if ( player == clientnum || splitscreen )
168 {
169 cameravars[entity->skill[2]].shakex += .1;
170 cameravars[entity->skill[2]].shakey += 10;
171 }
172 else if ( player > 0 && multiplayer == SERVER )
173 {
174 strcpy((char*)net_packet->data, "SHAK");
175 net_packet->data[4] = 10; // turns into .1
176 net_packet->data[5] = 10;
177 net_packet->address.host = net_clients[player - 1].host;
178 net_packet->address.port = net_clients[player - 1].port;
179 net_packet->len = 6;
180 sendPacketSafe(net_sock, -1, net_packet, player - 1);
181 }
182 }
183 else if ( parent && parent->behavior == &actPlayer )
184 {
185 int player = parent->skill[2];
186 if ( player >= 0 )
187 {
188 if ( oldHP > 0 )
189 {
190 if ( entityDist(my, parent) >= 64 && entityDist(my, parent) < 128 )
191 {
192 messagePlayer(player, language[2521]);
193 }
194 else
195 {
196 messagePlayer(player, language[2522]);
197 }
198 if ( rand() % 10 == 0 )
199 {
200 parent->increaseSkill(PRO_LOCKPICKING);
201 }
202 if ( rand() % 5 == 0 )
203 {
204 parent->increaseSkill(PRO_RANGED);
205 }
206 }
207 // update enemy bar for attacker
208 if ( !strcmp(stat->name, "") )
209 {
210 if ( stat->type < KOBOLD ) //Original monster count
211 {
212 updateEnemyBar(parent, entity, language[90 + stat->type], stat->HP, stat->MAXHP);
213 }
214 else if ( stat->type >= KOBOLD ) //New monsters
215 {
216 updateEnemyBar(parent, entity, language[2000 + (stat->type - KOBOLD)], stat->HP, stat->MAXHP);
217 }
218 }
219 else
220 {
221 updateEnemyBar(parent, entity, stat->name, stat->HP, stat->MAXHP);
222 }
223 }
224 }
225 playSoundEntity(my, 76, 64);
226 playSoundEntity(entity, 28, 64);
227 Entity* gib = spawnGib(entity);
228 serverSpawnGibForClient(gib);
229
230 --BEARTRAP_STATUS;
231 if ( BEARTRAP_STATUS < DECREPIT )
232 {
233 // make first arm
234 entity = newEntity(668, 1, map.entities, nullptr); //Special effect entity.
235 entity->behavior = &actBeartrapLaunched;
236 entity->flags[PASSABLE] = true;
237 entity->flags[UPDATENEEDED] = true;
238 entity->x = my->x;
239 entity->y = my->y;
240 entity->z = my->z + 1;
241 entity->yaw = my->yaw;
242 entity->pitch = my->pitch;
243 entity->roll = 0;
244
245 // and then the second
246 entity = newEntity(668, 1, map.entities, nullptr); //Special effect entity.
247 entity->behavior = &actBeartrapLaunched;
248 entity->flags[PASSABLE] = true;
249 entity->flags[UPDATENEEDED] = true;
250 entity->x = my->x;
251 entity->y = my->y;
252 entity->z = my->z + 1;
253 entity->yaw = my->yaw;
254 entity->pitch = my->pitch;
255 entity->roll = PI;
256 list_RemoveNode(my->mynode);
257 }
258 else
259 {
260 BEARTRAP_CAUGHT = 1;
261 my->sprite = 667;
262 serverUpdateEntitySkill(my, 0);
263 }
264 return;
265 }
266 }
267 }
268 }
269 }
270
actBeartrapLaunched(Entity * my)271 void actBeartrapLaunched(Entity* my)
272 {
273 if ( my->ticks >= 200 )
274 {
275 list_RemoveNode(my->mynode);
276 return;
277 }
278 }
279
280 #define BOMB_PLACEMENT my->skill[16]
281 #define BOMB_PLAYER_OWNER my->skill[17]
282 #define BOMB_ENTITY_ATTACHED_TO my->skill[18]
283 #define BOMB_ENTITY_ATTACHED_START_HP my->skill[19]
284 #define BOMB_DIRECTION my->skill[20]
285 #define BOMB_ITEMTYPE my->skill[21]
286 #define BOMB_TRIGGER_TYPE my->skill[22]
287 #define BOMB_CHEST_STATUS my->skill[23]
288 #define BOMB_HIT_BY_PROJECTILE my->skill[24]
289
bombDoEffect(Entity * my,Entity * triggered,real_t entityDistance,bool spawnMagicOnTriggeredMonster,bool hitByAOE)290 void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spawnMagicOnTriggeredMonster, bool hitByAOE )
291 {
292 if ( !triggered || !my )
293 {
294 return;
295 }
296 Entity* parent = uidToEntity(my->parent);
297 Stat* stat = triggered->getStats();
298 Stat* parentStats = nullptr;
299 if ( parent )
300 {
301 parentStats = parent->getStats();
302 }
303 int damage = 0;
304 //messagePlayer(0, "dmg: %d", damage);
305 int doSpell = SPELL_NONE;
306 bool doVertical = false;
307 switch ( BOMB_ITEMTYPE )
308 {
309 case TOOL_BOMB:
310 doSpell = SPELL_FIREBALL;
311 damage = 5;
312 if ( parentStats )
313 {
314 damage += std::max(0, parent->getPER() / 2);
315 }
316 break;
317 case TOOL_SLEEP_BOMB:
318 doSpell = SPELL_SLEEP;
319 damage = 0;
320 break;
321 case TOOL_FREEZE_BOMB:
322 doSpell = SPELL_COLD;
323 damage = 5;
324 if ( parentStats )
325 {
326 damage += std::max(0, parent->getPER() / 4);
327 }
328 break;
329 case TOOL_TELEPORT_BOMB:
330 doSpell = SPELL_TELEPORTATION;
331 damage = 0;
332 break;
333 default:
334 break;
335 }
336 if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
337 {
338 doVertical = true;
339 }
340
341 // stumbled into the trap!
342 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
343 if ( parent && parent->behavior == &actPlayer && triggered != parent )
344 {
345 if ( !hitByAOE )
346 {
347 messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3498], language[3499], MSG_TOOL_BOMB, my);
348 }
349 else
350 {
351 messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3613], language[3614], MSG_TOOL_BOMB, my);
352 }
353 }
354 if ( triggered->behavior == &actPlayer )
355 {
356 int player = triggered->skill[2];
357 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
358 // you stumbled into the trap!
359 if ( !hitByAOE )
360 {
361 messagePlayerColor(player, color, language[3497], items[BOMB_ITEMTYPE].name_identified);
362 }
363 else
364 {
365 messagePlayerColor(player, color, language[3612], items[BOMB_ITEMTYPE].name_identified);
366 }
367 }
368
369 if ( doSpell == SPELL_TELEPORTATION )
370 {
371 if ( triggered->isBossMonster() )
372 {
373 // no effect.
374 if ( parent && parent->behavior == &actPlayer )
375 {
376 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
377 messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3603], language[3604], MSG_COMBAT);
378 }
379 return;
380 }
381 std::vector<Entity*> goodspots;
382 bool teleported = false;
383 for ( node_t* node = map.entities->first; node != NULL; node = node->next )
384 {
385 Entity* entity = (Entity*)node->element;
386 if ( entity && entity != my && entity->behavior == &actBomb )
387 {
388 if ( entity->skill[21] == TOOL_TELEPORT_BOMB && entity->skill[22] == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
389 {
390 // receiver location.
391 goodspots.push_back(entity);
392 }
393 }
394 }
395
396 std::vector<Entity*> parentgoodspots; // prioritize traps from the player owner, instead of other player's traps.
397 for ( auto it = goodspots.begin(); it != goodspots.end(); ++it )
398 {
399 Entity* entry = *it;
400 if ( entry && (entry->parent == my->parent) )
401 {
402 parentgoodspots.push_back(entry);
403 }
404 }
405
406 if ( !parentgoodspots.empty() )
407 {
408 Entity* targetLocation = parentgoodspots[rand() % parentgoodspots.size()];
409 teleported = triggered->teleportAroundEntity(targetLocation, 2);
410 }
411 else if ( !goodspots.empty() )
412 {
413 Entity* targetLocation = goodspots[rand() % goodspots.size()];
414 teleported = triggered->teleportAroundEntity(targetLocation, 2);
415 }
416 else
417 {
418 teleported = triggered->teleportRandom(); // woosh!
419 }
420
421 if ( teleported )
422 {
423 createParticleErupt(my, 593);
424 serverSpawnMiscParticles(my, PARTICLE_EFFECT_ERUPT, 593);
425 if ( parent && parent->behavior == &actPlayer )
426 {
427 // whisked away!
428 if ( triggered != parent )
429 {
430 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
431 messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3601], language[3602], MSG_COMBAT);
432 }
433 }
434 if ( triggered->behavior == &actPlayer )
435 {
436 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
437 messagePlayerColor(triggered->skill[2], color, language[3611]);
438 achievementObserver.playerAchievements[triggered->skill[2]].checkPathBetweenObjects(triggered, my, AchievementObserver::BARONY_ACH_WONDERFUL_TOYS);
439 }
440
441 if ( triggered->behavior == &actMonster )
442 {
443 triggered->monsterReleaseAttackTarget();
444 }
445
446 createParticleErupt(triggered, 593);
447 serverSpawnMiscParticlesAtLocation(triggered->x / 16, triggered->y / 16, 0, PARTICLE_EFFECT_ERUPT, 593);
448 }
449 else
450 {
451 if ( parent && parent->behavior == &actPlayer && triggered != parent )
452 {
453 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
454 messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3615], language[3616], MSG_COMBAT);
455 }
456 }
457 return;
458 }
459 else if ( doSpell != SPELL_NONE )
460 {
461 Entity* spell = castSpell(my->getUID(), getSpellFromID(doSpell), false, true);
462 spell->parent = my->parent;
463 spell->x = triggered->x;
464 spell->y = triggered->y;
465 if ( !doVertical )
466 {
467 real_t speed = 1.f;
468 real_t ticksToHit = (entityDistance / speed);
469 /*real_t predictx = triggered->x + (triggered->vel_x * ticksToHit);
470 real_t predicty = triggered->y + (triggered->vel_y * ticksToHit);
471 double tangent = atan2(predicty - my->y, predictx - my->x);*/
472 double tangent = atan2(triggered->y - my->y, triggered->x - my->x);
473 spell->yaw = tangent;
474 spell->vel_x = speed * cos(spell->yaw);
475 spell->vel_y = speed * sin(spell->yaw);
476 }
477 else
478 {
479 spell->x = my->x;
480 spell->y = my->y;
481 real_t speed = 3.f;
482 real_t ticksToHit = (entityDistance / speed);
483 real_t predictx = triggered->x + (triggered->vel_x * ticksToHit);
484 real_t predicty = triggered->y + (triggered->vel_y * ticksToHit);
485 double tangent = atan2(predicty - my->y, predictx - my->x);
486 spell->yaw = tangent;
487 spell->vel_z = -2.f;
488 spell->vel_x = speed * cos(spell->yaw);
489 spell->vel_y = speed * sin(spell->yaw);
490 spell->pitch = atan2(spell->vel_z, speed);
491 spell->actmagicIsVertical = MAGIC_ISVERTICAL_XYZ;
492 }
493 spell->actmagicCastByTinkerTrap = 1;
494 if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL )
495 {
496 spell->actmagicTinkerTrapFriendlyFire = 1;
497 if ( triggered == parent )
498 {
499 spell->parent = 0;
500 }
501 }
502 spell->skill[5] = 10; // travel time
503 }
504 // set obituary
505 int oldHP = stat->HP;
506 if ( stat )
507 {
508 damage *= triggered->getDamageTableMultiplier(*stat, DAMAGE_TABLE_MAGIC); // reduce/increase by magic table.
509 }
510 bool wasAsleep = stat->EFFECTS[EFF_ASLEEP];
511 triggered->modHP(-damage);
512 triggered->setObituary(language[3496]);
513
514 if ( stat->HP <= 0 && oldHP > 0 )
515 {
516 if ( parent )
517 {
518 parent->awardXP(triggered, true, true);
519 if ( stat->type == MINOTAUR )
520 {
521 if ( parent->behavior == &actPlayer )
522 {
523 steamAchievementClient(parent->skill[2], "BARONY_ACH_TIME_TO_PLAN");
524 }
525 }
526 }
527 else
528 {
529 if ( achievementObserver.checkUidIsFromPlayer(my->parent) >= 0 )
530 {
531 steamAchievementClient(achievementObserver.checkUidIsFromPlayer(my->parent), "BARONY_ACH_TAKING_WITH");
532 }
533 }
534 }
535
536 if ( triggered->behavior == &actPlayer )
537 {
538 int player = triggered->skill[2];
539
540 if ( player == clientnum || splitscreen )
541 {
542 cameravars[triggered->skill[2]].shakex += .1;
543 cameravars[triggered->skill[2]].shakey += 10;
544 }
545 else if ( player > 0 && multiplayer == SERVER )
546 {
547 strcpy((char*)net_packet->data, "SHAK");
548 net_packet->data[4] = 10; // turns into .1
549 net_packet->data[5] = 10;
550 net_packet->address.host = net_clients[player - 1].host;
551 net_packet->address.port = net_clients[player - 1].port;
552 net_packet->len = 6;
553 sendPacketSafe(net_sock, -1, net_packet, player - 1);
554 }
555 }
556 if ( parent && parent != triggered && parent->behavior == &actPlayer )
557 {
558 int player = parent->skill[2];
559 if ( player >= 0 )
560 {
561 double tangent = atan2(parent->y - my->y, parent->x - my->x);
562 lineTraceTarget(my, my->x, my->y, tangent, 128, 0, false, parent);
563 if ( hit.entity != parent )
564 {
565 if ( entityDist(my, parent) >= 64 && entityDist(my, parent) < 128 )
566 {
567 messagePlayer(player, language[3494]);
568 }
569 else
570 {
571 messagePlayer(player, language[3495]);
572 }
573 }
574 if ( triggered->behavior == &actMonster )
575 {
576 if ( oldHP > 0 && stat->HP == 0 ) // got a kill
577 {
578 if ( rand() % 5 == 0 )
579 {
580 parent->increaseSkill(PRO_LOCKPICKING);
581 }
582 }
583 else if ( oldHP > stat->HP )
584 {
585 if ( rand() % 20 == 0 ) // wounded
586 {
587 parent->increaseSkill(PRO_LOCKPICKING);
588 }
589 }
590 else if( rand() % 20 == 0) // any other effect
591 {
592 parent->increaseSkill(PRO_LOCKPICKING);
593 }
594
595 if ( !achievementObserver.playerAchievements[player].bombTrack )
596 {
597 achievementObserver.addEntityAchievementTimer(triggered, AchievementObserver::BARONY_ACH_BOMBTRACK, 250, false, 1);
598 achievementObserver.awardAchievementIfActive(player, triggered, AchievementObserver::BARONY_ACH_BOMBTRACK);
599 }
600 if ( !achievementObserver.playerAchievements[player].calmLikeABomb )
601 {
602 if ( BOMB_ITEMTYPE == TOOL_SLEEP_BOMB )
603 {
604 achievementObserver.addEntityAchievementTimer(triggered, AchievementObserver::BARONY_ACH_CALM_LIKE_A_BOMB, 50 * 15, true, 0);
605 }
606 else if ( wasAsleep && damage > 0 )
607 {
608 achievementObserver.awardAchievementIfActive(player, triggered, AchievementObserver::BARONY_ACH_CALM_LIKE_A_BOMB);
609 }
610 }
611 }
612 // update enemy bar for attacker
613 if ( damage > 0 )
614 {
615 if ( !strcmp(stat->name, "") )
616 {
617 if ( stat->type < KOBOLD ) //Original monster count
618 {
619 updateEnemyBar(parent, triggered, language[90 + stat->type], stat->HP, stat->MAXHP);
620 }
621 else if ( stat->type >= KOBOLD ) //New monsters
622 {
623 updateEnemyBar(parent, triggered, language[2000 + (stat->type - KOBOLD)], stat->HP, stat->MAXHP);
624 }
625 }
626 else
627 {
628 updateEnemyBar(parent, triggered, stat->name, stat->HP, stat->MAXHP);
629 }
630 Entity* gib = spawnGib(triggered);
631 serverSpawnGibForClient(gib);
632 }
633 }
634 }
635 }
636
actBomb(Entity * my)637 void actBomb(Entity* my)
638 {
639 my->removeLightField();
640 if ( multiplayer == CLIENT )
641 {
642 if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
643 {
644 my->spawnAmbientParticles(25, 576, 10 + rand() % 40, 1.0, false);
645 my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 92);
646 }
647 return;
648 }
649 else
650 {
651 my->skill[2] = -15;
652 }
653
654 // undo bomb
655 for ( int i = 0; i < MAXPLAYERS; i++ )
656 {
657 if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
658 {
659 if ( inrange[i] )
660 {
661 Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
662 entity->flags[INVISIBLE] = true;
663 entity->flags[UPDATENEEDED] = true;
664 entity->flags[PASSABLE] = true;
665 entity->x = my->x;
666 entity->y = my->y;
667 entity->z = my->z;
668 entity->sizex = my->sizex;
669 entity->sizey = my->sizey;
670 entity->yaw = my->yaw;
671 entity->pitch = my->pitch;
672 entity->roll = 3 * PI / 2;
673 entity->behavior = &actItem;
674 entity->skill[10] = BOMB_ITEMTYPE;
675 entity->skill[11] = BEARTRAP_STATUS;
676 entity->skill[12] = BEARTRAP_BEATITUDE;
677 entity->skill[13] = 1;
678 entity->skill[14] = BEARTRAP_APPEARANCE;
679 entity->skill[15] = BEARTRAP_IDENTIFIED;
680 if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
681 {
682 // don't fall down
683 entity->itemNotMoving = 1;
684 entity->itemNotMovingClient = 1;
685 serverUpdateEntitySkill(entity, 18); //update both the above flags.
686 serverUpdateEntitySkill(entity, 19);
687 }
688 else
689 {
690 entity->itemNotMoving = 0;
691 entity->itemNotMovingClient = 0;
692 }
693 messagePlayer(i, language[3600], items[BOMB_ITEMTYPE].name_identified);
694 list_RemoveNode(my->mynode);
695 return;
696 }
697 }
698 }
699
700 if ( my->isInteractWithMonster() )
701 {
702 Entity* monsterInteracting = uidToEntity(my->interactedByMonster);
703 if ( monsterInteracting && monsterInteracting->getMonsterTypeFromSprite() == GYROBOT )
704 {
705 if ( monsterInteracting->monsterAllyGetPlayerLeader() )
706 {
707 Item* tmp = newItemFromEntity(my);
708 if ( tmp )
709 {
710 tmp->applyLockpick(monsterInteracting->monsterAllyIndex, *my);
711 free(tmp);
712 }
713 }
714 }
715 my->clearMonsterInteract();
716 }
717
718 if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB && BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
719 {
720 my->spawnAmbientParticles(25, 576, 10 + rand() % 40, 1.0, false);
721 my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 92);
722 my->sprite = 899;
723 return;
724 }
725 else if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB )
726 {
727 my->sprite = 884;
728 }
729
730 // launch bomb
731 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
732 Entity* triggered = nullptr;
733 real_t entityDistance = 0.f;
734 bool bombExplodeAOETargets = false;
735
736
737 int explosionSprite = 49;
738 if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB )
739 {
740 explosionSprite = 145;
741 }
742 else if ( BOMB_ITEMTYPE == TOOL_FREEZE_BOMB )
743 {
744 explosionSprite = 135;
745 }
746 else if ( BOMB_ITEMTYPE == TOOL_SLEEP_BOMB )
747 {
748 explosionSprite = 0;
749 }
750
751 if ( BOMB_ENTITY_ATTACHED_TO != 0 || BOMB_HIT_BY_PROJECTILE == 1 || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL )
752 {
753 Entity* onEntity = uidToEntity(static_cast<Uint32>(BOMB_ENTITY_ATTACHED_TO));
754 bool shouldExplode = false;
755 if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL )
756 {
757 int checkx = my->x;
758 int checky = my->y;
759 switch ( BOMB_DIRECTION )
760 {
761 case Item::ItemBombFacingDirection::BOMB_EAST:
762 checkx -= 8;
763 break;
764 case Item::ItemBombFacingDirection::BOMB_WEST:
765 checkx += 8;
766 break;
767 case Item::ItemBombFacingDirection::BOMB_SOUTH:
768 checky -= 8;
769 break;
770 case Item::ItemBombFacingDirection::BOMB_NORTH:
771 checky += 8;
772 break;
773 default:
774 break;
775 }
776 checkx = checkx >> 4;
777 checky = checky >> 4;
778 if ( !map.tiles[OBSTACLELAYER + checky * MAPLAYERS + checkx * MAPLAYERS * map.height] ) // wall
779 {
780 shouldExplode = true;
781 }
782 else if ( BOMB_HIT_BY_PROJECTILE == 1 )
783 {
784 shouldExplode = true;
785 }
786 }
787 else if ( onEntity )
788 {
789 if ( onEntity->behavior == &actDoor )
790 {
791 if ( onEntity->doorHealth < BOMB_ENTITY_ATTACHED_START_HP || onEntity->flags[PASSABLE]
792 || BOMB_HIT_BY_PROJECTILE == 1 )
793 {
794 if ( onEntity->doorHealth > 0 )
795 {
796 onEntity->doorHandleDamageMagic(50, *my, uidToEntity(my->parent));
797 }
798 shouldExplode = true;
799 }
800 }
801 else if ( onEntity->behavior == &actChest )
802 {
803 if ( onEntity->skill[3] < BOMB_ENTITY_ATTACHED_START_HP || BOMB_CHEST_STATUS != onEntity->skill[1]
804 || BOMB_HIT_BY_PROJECTILE == 1 )
805 {
806 if ( onEntity->skill[3] > 0 )
807 {
808 if ( BOMB_ITEMTYPE == TOOL_BOMB ) // fire bomb do more.
809 {
810 onEntity->chestHandleDamageMagic(50, *my, uidToEntity(my->parent));
811 }
812 else
813 {
814 onEntity->chestHandleDamageMagic(20, *my, uidToEntity(my->parent));
815 }
816 }
817 shouldExplode = true;
818 }
819 }
820 }
821 else
822 {
823 shouldExplode = true; // my attached entity died.
824 }
825
826 if ( shouldExplode )
827 {
828 if ( onEntity )
829 {
830 spawnExplosionFromSprite(explosionSprite, onEntity->x, onEntity->y, onEntity->z);
831 }
832 else
833 {
834 spawnExplosionFromSprite(explosionSprite, my->x, my->y, my->z);
835 }
836 bombExplodeAOETargets = true;
837 BOMB_TRIGGER_TYPE = Item::ItemBombTriggerType::BOMB_TRIGGER_ALL;
838 }
839 }
840
841 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end() && !triggered; ++it )
842 {
843 list_t* currentList = *it;
844 node_t* node;
845 for ( node = currentList->first; node != nullptr && !triggered; node = node->next )
846 {
847 Entity* entity = (Entity*)node->element;
848 if ( !entity )
849 {
850 continue;
851 }
852 if ( my->parent == entity->getUID() && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
853 {
854 continue;
855 }
856 if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
857 {
858 Stat* stat = entity->getStats();
859 if ( stat )
860 {
861 Entity* parent = uidToEntity(my->parent);
862 if ( parent && parent->checkFriend(entity) && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
863 {
864 continue;
865 }
866 if ( stat->type == GYROBOT )
867 {
868 continue;
869 }
870 if ( !parent && BOMB_PLAYER_OWNER >= 0 && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
871 {
872 if ( entity->behavior == &actPlayer )
873 {
874 continue; // players won't trigger if owner dead.
875 }
876 else if ( entity->monsterAllyGetPlayerLeader() )
877 {
878 continue; // player followers won't trigger if owner dead.
879 }
880 }
881 if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
882 {
883 entityDistance = entityDist(my, entity);
884 if ( entityDistance < 6.5 )
885 {
886 spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y + rand() % 9, my->z - 2);
887 triggered = entity;
888 }
889 }
890 else if ( bombExplodeAOETargets )
891 {
892 Entity* onEntity = uidToEntity(static_cast<Uint32>(BOMB_ENTITY_ATTACHED_TO));
893 if ( onEntity )
894 {
895 entityDistance = entityDist(onEntity, entity);
896 if ( entityDistance < STRIKERANGE )
897 {
898 spawnExplosionFromSprite(explosionSprite, entity->x, entity->y, entity->z);
899 bombDoEffect(my, entity, entityDistance, true, true);
900 }
901 }
902 else
903 {
904 entityDistance = entityDist(my, entity);
905 if ( entityDistance < STRIKERANGE )
906 {
907 spawnExplosionFromSprite(explosionSprite, entity->x, entity->y, entity->z);
908 bombDoEffect(my, entity, entityDistance, true, true);
909 }
910 }
911 }
912 else
913 {
914 real_t oldx = my->x;
915 real_t oldy = my->y;
916 // pretend the bomb is in the center of the tile it's facing.
917 switch ( BOMB_DIRECTION )
918 {
919 case Item::ItemBombFacingDirection::BOMB_EAST:
920 my->x += 8;
921 entityDistance = entityDist(my, entity);
922 if ( entityDistance < 12 )
923 {
924 triggered = entity;
925 spawnExplosionFromSprite(explosionSprite, my->x - rand() % 9, my->y - 4 + rand() % 9, my->z);
926 }
927 break;
928 case Item::ItemBombFacingDirection::BOMB_WEST:
929 my->x -= 8;
930 entityDistance = entityDist(my, entity);
931 if ( entityDistance < 12 )
932 {
933 triggered = entity;
934 spawnExplosionFromSprite(explosionSprite, my->x + rand() % 9, my->y - 4 + rand() % 9, my->z);
935 }
936 break;
937 case Item::ItemBombFacingDirection::BOMB_SOUTH:
938 my->y += 8;
939 entityDistance = entityDist(my, entity);
940 if ( entityDistance < 12 )
941 {
942 triggered = entity;
943 spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y - rand() % 9, my->z);
944 }
945 break;
946 case Item::ItemBombFacingDirection::BOMB_NORTH:
947 my->y -= 8;
948 entityDistance = entityDist(my, entity);
949 if ( entityDistance < 12 )
950 {
951 triggered = entity;
952 spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y + rand() % 9, my->z);
953 }
954 break;
955 default:
956 break;
957 }
958 my->x = oldx;
959 my->y = oldy;
960 }
961 }
962 }
963 }
964 }
965
966 if ( !bombExplodeAOETargets && triggered
967 && (BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_DOOR || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_CHEST) )
968 {
969 // found enemy, do AoE effect.
970 BOMB_HIT_BY_PROJECTILE = 1;
971 }
972 else if ( bombExplodeAOETargets || triggered )
973 {
974 //Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
975 //entity->flags[INVISIBLE] = true;
976 //entity->flags[UPDATENEEDED] = true;
977 //entity->flags[PASSABLE] = true;
978 //entity->x = my->x;
979 //entity->y = my->y;
980 //entity->z = my->z;
981 //entity->sizex = my->sizex;
982 //entity->sizey = my->sizey;
983 //entity->yaw = my->yaw;
984 //entity->pitch = my->pitch;
985 //entity->roll = 3 * PI / 2;
986 //entity->behavior = &actItem;
987 //entity->skill[10] = TOOL_DETONATOR_CHARGE;
988 //entity->skill[11] = BROKEN;
989 //entity->skill[12] = 0;
990 //entity->skill[13] = 1;
991 //entity->skill[14] = ITEM_TINKERING_APPEARANCE;
992 //entity->skill[15] = 1;
993 //if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
994 //{
995 // // don't fall down
996 // entity->itemNotMoving = 1;
997 // entity->itemNotMovingClient = 1;
998 // serverUpdateEntitySkill(entity, 18); //update both the above flags.
999 // serverUpdateEntitySkill(entity, 19);
1000 //}
1001 //else
1002 //{
1003 // entity->itemNotMoving = 0;
1004 // entity->itemNotMovingClient = 0;
1005 //}
1006 Item* charge = newItem(TOOL_DETONATOR_CHARGE, BROKEN, 0, 1, ITEM_TINKERING_APPEARANCE, true, nullptr);
1007 Entity* dropped = dropItemMonster(charge, my, nullptr);
1008 if ( dropped )
1009 {
1010 dropped->flags[USERFLAG1] = true;
1011 }
1012 my->removeLightField();
1013 if ( triggered )
1014 {
1015 bombDoEffect(my, triggered, entityDistance, false, false);
1016 }
1017 playSoundEntity(my, 76, 64);
1018 list_RemoveNode(my->mynode);
1019 return;
1020 }
1021 }
1022
entityCheckIfTriggeredBomb(bool triggerBomb)1023 bool Entity::entityCheckIfTriggeredBomb(bool triggerBomb)
1024 {
1025 if ( multiplayer == CLIENT )
1026 {
1027 return false;
1028 }
1029 if ( this->behavior != &actThrown && this->behavior != &actArrow )
1030 {
1031 return false;
1032 }
1033 bool foundBomb = false;
1034 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 2);
1035 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1036 {
1037 list_t* currentList = *it;
1038 node_t* node;
1039 for ( node = currentList->first; node != nullptr; node = node->next )
1040 {
1041 Entity* entity = (Entity*)node->element;
1042 if ( entity && entity->behavior == &actBomb && entity->skill[24] == 0 )
1043 {
1044 if ( entityInsideEntity(this, entity) )
1045 {
1046 if ( triggerBomb )
1047 {
1048 entity->skill[24] = 1;
1049 }
1050 foundBomb = true;
1051 }
1052 }
1053 }
1054 }
1055 return foundBomb;
1056 }
1057
actDecoyBox(Entity * my)1058 void actDecoyBox(Entity* my)
1059 {
1060 my->flags[PASSABLE] = true;
1061 if ( my->skill[0] == 0 )
1062 {
1063 my->skill[0] = 1;
1064 //spawn a crank.
1065 Entity* entity = newEntity(895, 1, map.entities, nullptr); //Decoy crank.
1066 entity->behavior = &actDecoyBoxCrank;
1067 entity->parent = my->getUID();
1068 entity->x = my->x;
1069 entity->y = my->y;
1070 entity->z = my->z;
1071 entity->yaw = my->yaw;
1072 entity->flags[PASSABLE] = true;
1073 entity->flags[NOUPDATE] = true;
1074 entity->flags[UNCLICKABLE] = true;
1075 if ( multiplayer != CLIENT )
1076 {
1077 entity_uids--;
1078 }
1079 entity->setUID(-3);
1080 playSoundEntityLocal(my, 455, 64);
1081 }
1082 else
1083 {
1084 // let's make some noise.
1085 if ( my->ticks % 5 == 0 && rand() % 3 == 0 )
1086 {
1087 playSoundEntityLocal(my, 472 + rand() % 13, 192);
1088 }
1089 if ( my->ticks % 20 == 0 && rand() % 3 > 0 )
1090 {
1091 playSoundEntityLocal(my, 475 + rand() % 10, 192);
1092 }
1093 }
1094 if ( multiplayer == CLIENT )
1095 {
1096 return;
1097 }
1098
1099 if ( my->ticks % TICKS_PER_SECOND == 0 )
1100 {
1101 Entity* parent = uidToEntity(my->parent);
1102 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange * 2 + 1);
1103 std::vector<Entity*> listOfOtherDecoys;
1104 // find other decoys (so monsters don't wiggle back and forth.)
1105 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1106 {
1107 list_t* currentList = *it;
1108 node_t* node;
1109 for ( node = currentList->first; node != nullptr; node = node->next )
1110 {
1111 Entity* entity = (Entity*)node->element;
1112 if ( entity && entity->behavior == &actDecoyBox && entity != my )
1113 {
1114 listOfOtherDecoys.push_back(entity);
1115 }
1116 }
1117 }
1118 entLists.clear();
1119
1120 entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange);
1121 bool message = false;
1122 bool detected = false;
1123 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1124 {
1125 list_t* currentList = *it;
1126 node_t* node;
1127 for ( node = currentList->first; node != nullptr; node = node->next )
1128 {
1129 Entity* entity = (Entity*)node->element;
1130 if ( parent && entity && entity->behavior == &actMonster
1131 && parent->checkEnemy(entity) && entity->isMobile() )
1132 {
1133 if ( (entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0)
1134 || (entityDist(entity,my) < 2 * TOUCHRANGE && (Uint32)(entity->monsterLastDistractedByNoisemaker) != my->getUID()) )
1135 {
1136 Stat* myStats = entity->getStats();
1137 if ( !entity->isBossMonster() && !entity->monsterIsTinkeringCreation()
1138 && myStats && !uidToEntity(myStats->leader_uid) )
1139 {
1140 // found an eligible monster (far enough away, non-boss)
1141 // now look through other decoys, if others are in range of our target,
1142 // then the most recent decoy will pull them (this decoy won't do anything)
1143 bool foundMoreRecentDecoy = false;
1144 if ( !listOfOtherDecoys.empty() )
1145 {
1146 for ( std::vector<Entity*>::iterator decoyIt = listOfOtherDecoys.begin(); decoyIt != listOfOtherDecoys.end(); ++decoyIt )
1147 {
1148 Entity* decoy = *decoyIt;
1149 if ( entityDist(decoy, entity) < (decoyBoxRange * 16) ) // less than x tiles from our monster
1150 {
1151 if ( decoy->ticks < my->ticks )
1152 {
1153 // this decoy is newer (less game ticks alive)
1154 // defer to this decoy.
1155 foundMoreRecentDecoy = true;
1156 break;
1157 }
1158 }
1159 }
1160 }
1161 if ( (Uint32)(entity->monsterLastDistractedByNoisemaker) == my->getUID() )
1162 {
1163 // ignore pathing to this noisemaker as we're already distracted by it.
1164 if ( entityDist(entity, my) < TOUCHRANGE
1165 && !myStats->EFFECTS[EFF_DISORIENTED]
1166 && !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
1167 {
1168 // if we pathed within range
1169 detected = false; // skip the message.
1170
1171 // can I see the noisemaker next to me?
1172 real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
1173 lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
1174 if ( hit.entity == entity )
1175 {
1176 // set disoriented and start a cooldown on being distracted.
1177 if ( entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0 )
1178 {
1179 // not attacking, duration longer.
1180 entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 3, false);
1181 entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1182 }
1183 else
1184 {
1185 entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 1, false);
1186 entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1187 }
1188 spawnFloatingSpriteMisc(134, entity->x + (-4 + rand() % 9) + cos(entity->yaw) * 2,
1189 entity->y + (-4 + rand() % 9) + sin(entity->yaw) * 2, entity->z + rand() % 4);
1190 }
1191 }
1192 break;
1193 }
1194 if ( foundMoreRecentDecoy )
1195 {
1196 break;
1197 }
1198 if ( !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN]
1199 && entity->monsterSetPathToLocation(my->x / 16, my->y / 16, 2) && entity->children.first )
1200 {
1201 // path only if we're not on cooldown
1202 entity->monsterLastDistractedByNoisemaker = my->getUID();
1203 entity->monsterTarget = my->getUID();
1204 entity->monsterState = MONSTER_STATE_HUNT; // hunt state
1205 serverUpdateEntitySkill(entity, 0);
1206 detected = true;
1207
1208 if ( entityDist(entity, my) < TOUCHRANGE
1209 && !myStats->EFFECTS[EFF_DISORIENTED]
1210 && !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
1211 {
1212 detected = false; // skip the message.
1213
1214 // can I see the noisemaker next to me?
1215 real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
1216 lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
1217 if ( hit.entity == entity )
1218 {
1219 // set disoriented and start a cooldown on being distracted.
1220 if ( entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0 )
1221 {
1222 // not attacking, duration longer.
1223 entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 3, false);
1224 entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1225 }
1226 else
1227 {
1228 entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 1, false);
1229 entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1230 }
1231 spawnFloatingSpriteMisc(134, entity->x + (-4 + rand() % 9) + cos(entity->yaw) * 2,
1232 entity->y + (-4 + rand() % 9) + sin(entity->yaw) * 2, entity->z + rand() % 4);
1233 }
1234 }
1235
1236 if ( parent->behavior == &actPlayer && stats[parent->skill[2]] )
1237 {
1238 // see if we have a gyrobot follower to tell us what's goin on
1239 for ( node_t* tmpNode = stats[parent->skill[2]]->FOLLOWERS.first; tmpNode != nullptr; tmpNode = tmpNode->next )
1240 {
1241 Uint32* c = (Uint32*)tmpNode->element;
1242 Entity* gyrobot = uidToEntity(*c);
1243 if ( gyrobot && gyrobot->getRace() == GYROBOT )
1244 {
1245 if ( entity->entityShowOnMap < 250 )
1246 {
1247 entity->entityShowOnMap = TICKS_PER_SECOND * 5;
1248 if ( parent->skill[2] != 0 )
1249 {
1250 serverUpdateEntitySkill(entity, 59);
1251 }
1252 }
1253 if ( !message )
1254 {
1255 messagePlayer(parent->skill[2], language[3671]);
1256 message = true;
1257 }
1258 break;
1259 }
1260 }
1261 }
1262 }
1263 }
1264 }
1265 }
1266 }
1267 }
1268 if ( !message && detected )
1269 {
1270 if ( parent && parent->behavior == &actPlayer )
1271 {
1272 messagePlayer(parent->skill[2], language[3882]);
1273 }
1274 }
1275 }
1276
1277 if ( my->ticks > TICKS_PER_SECOND * 7 )
1278 {
1279 // stop working.
1280 bool decoyBreak = (rand() % 5 == 0);
1281 Entity* parent = uidToEntity(my->parent);
1282 playSoundEntity(my, 485 + rand() % 3, 192);
1283 if ( !decoyBreak )
1284 {
1285 playSoundEntity(my, 176, 128);
1286 Item* item = newItem(TOOL_DECOY, static_cast<Status>(BEARTRAP_STATUS), BEARTRAP_BEATITUDE, 1, BEARTRAP_APPEARANCE, true, nullptr);
1287 Entity* entity = dropItemMonster(item, my, nullptr);
1288 if ( entity )
1289 {
1290 entity->flags[USERFLAG1] = true; // makes items passable, improves performance
1291 }
1292 /*if ( parent && parent->behavior == &actPlayer )
1293 {
1294 messagePlayer(parent->skill[2], language[3769]);
1295 }*/
1296 list_RemoveNode(my->mynode);
1297 return;
1298 }
1299 else
1300 {
1301 playSoundEntity(my, 132, 16);
1302 for ( int c = 0; c < 3; c++ )
1303 {
1304 Entity* entity = spawnGib(my);
1305 if ( entity )
1306 {
1307 switch ( c )
1308 {
1309 case 0:
1310 entity->sprite = 895;
1311 break;
1312 case 1:
1313 entity->sprite = 894;
1314 break;
1315 case 2:
1316 entity->sprite = 874;
1317 break;
1318 default:
1319 break;
1320 }
1321 serverSpawnGibForClient(entity);
1322 }
1323 }
1324 if ( parent && parent->behavior == &actPlayer )
1325 {
1326 messagePlayer(parent->skill[2], language[3770]);
1327 }
1328 list_RemoveNode(my->mynode);
1329 return;
1330 }
1331 }
1332 }
1333
actDecoyBoxCrank(Entity * my)1334 void actDecoyBoxCrank(Entity* my)
1335 {
1336 Entity* parent = uidToEntity(my->parent);
1337 if ( !parent )
1338 {
1339 list_RemoveNode(my->mynode);
1340 return;
1341 }
1342 my->x = parent->x;
1343 my->y = parent->y;
1344 my->z = parent->z;
1345 my->yaw = parent->yaw;
1346
1347 my->x += limbs[DUMMYBOT][12][0] * cos(parent->yaw) + limbs[DUMMYBOT][12][1] * cos(parent->yaw + PI / 2);
1348 my->y += limbs[DUMMYBOT][12][0] * sin(parent->yaw) + limbs[DUMMYBOT][12][1] * sin(parent->yaw + PI / 2);
1349 my->z = limbs[DUMMYBOT][12][2];
1350 my->focalx = limbs[DUMMYBOT][11][0];
1351 my->focaly = limbs[DUMMYBOT][11][1];
1352 my->focalz = limbs[DUMMYBOT][11][2];
1353
1354 my->pitch += 0.1;
1355 if ( my->pitch > 2 * PI )
1356 {
1357 my->pitch -= 2 * PI;
1358 }
1359 }