1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: actboulder.cpp
5 Desc: implements boulder and boulder trap code
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 "sound.hpp"
17 #include "items.hpp"
18 #include "net.hpp"
19 #include "monster.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23 #include "paths.hpp"
24 #include "scores.hpp"
25 #include "mod_tools.hpp"
26
27 #define BOULDER_STOPPED my->skill[0]
28 #define BOULDER_AMBIENCE my->skill[1]
29 #define BOULDER_NOGROUND my->skill[3]
30 #define BOULDER_ROLLING my->skill[4]
31 #define BOULDER_ROLLDIR my->skill[5]
32 #define BOULDER_DESTX my->skill[6]
33 #define BOULDER_DESTY my->skill[7]
34 #define BOULDER_PLAYERPUSHED my->skill[8]
35 #define BOULDER_SPAWNBLOOD my->skill[9]
36 #define BOULDER_BLOODTIME my->skill[10]
37 #define BOULDER_INIT my->skill[11]
38 #define BOULDER_LAVA_EXPLODE my->skill[12]
39 #define BOULDER_SOUND_ON_PUSH my->skill[13]
40
41 const int BOULDER_LAVA_SPRITE = 989;
42 const int BOULDER_ARCANE_SPRITE = 990;
43
boulderCheckIfBlockedExit(Entity * my)44 bool boulderCheckIfBlockedExit(Entity* my)
45 {
46 if ( conductGameChallenges[CONDUCT_MODDED] )
47 {
48 return true; // ignore for custom maps.
49 }
50 if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_DEFAULT )
51 {
52 return true; // ignore for custom modes.
53 }
54
55 bool playerAlive = false;
56 for ( int c = 0; c < MAXPLAYERS; ++c )
57 {
58 if ( players[c] && players[c]->entity )
59 {
60 playerAlive = true;
61 }
62 }
63 if ( !playerAlive )
64 {
65 return true;
66 }
67 // check if this blocked the exit.
68 for ( node_t* node = map.entities->first; node != nullptr; node = node->next )
69 {
70 Entity* ladder = (Entity*)node->element;
71 if ( ladder && (ladder->behavior == &actLadder || ladder->behavior == &actPortal) )
72 {
73 //if ( ladder->behavior == &actPortal && (ladder->portalNotSecret == 0) )
74 //{
75 // continue; // secret exit, don't care.
76 //}
77 for ( int c = 0; c < MAXPLAYERS; ++c )
78 {
79 if ( players[c] && players[c]->entity )
80 {
81 list_t* path = generatePath(players[c]->entity->x / 16, players[c]->entity->y / 16, ladder->x / 16, ladder->y / 16,
82 players[c]->entity, ladder, true);
83 if ( path != NULL )
84 {
85 list_FreeAll(path);
86 free(path);
87 //messagePlayer(0, "found path to exit");
88 return true;
89 }
90 }
91 }
92 }
93 }
94 return false;
95 }
96
97 /*-------------------------------------------------------------------------------
98
99 doesEntityStopBoulder
100
101 checks which objects the boulder breaks when it hits.
102
103 -------------------------------------------------------------------------------*/
104
doesEntityStopBoulder(Entity * entity)105 bool doesEntityStopBoulder(Entity* entity)
106 {
107 if ( !entity )
108 {
109 return false;
110 }
111 if ( entity->behavior == &actGate )
112 {
113 return true;
114 }
115 else if ( entity->behavior == &actBoulder )
116 {
117 return true;
118 }
119 else if ( entity->behavior == &actChest )
120 {
121 return true;
122 }
123 else if ( entity->behavior == &actHeadstone )
124 {
125 return true;
126 }
127 else if ( entity->behavior == &actFountain )
128 {
129 return true;
130 }
131 else if ( entity->behavior == &actSink )
132 {
133 return true;
134 }
135 else if ( entity->behavior == &actStalagCeiling )
136 {
137 if ( entity->z > -8 )
138 {
139 // not on ceiling layer
140 return true;
141 }
142 }
143 else if ( entity->behavior == &actStalagFloor )
144 {
145 return true;
146 }
147 else if ( entity->behavior == &actStalagColumn )
148 {
149 return true;
150 }
151 else if ( entity->behavior == &actPedestalBase )
152 {
153 return true;
154 }
155 else if ( entity->behavior == &actColumn )
156 {
157 return true;
158 }
159 return false;
160 }
161
162 /*-------------------------------------------------------------------------------
163
164 boulderCheckAgainstEntity
165
166 causes the boulder given in my to crush the object given in entity
167 or vice versa
168
169 -------------------------------------------------------------------------------*/
170
boulderCheckAgainstEntity(Entity * my,Entity * entity,bool ignoreInsideEntity)171 int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntity)
172 {
173 if (!my || !entity)
174 {
175 return 0;
176 }
177
178 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
179 {
180 if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
181 {
182 Stat* stats = entity->getStats();
183 if ( stats )
184 {
185 if ( entity->behavior == &actPlayer )
186 {
187 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
188 messagePlayerColor(entity->skill[2], color, language[455]);
189 if ( entity->skill[2] == clientnum || splitscreen )
190 {
191 cameravars[entity->skill[2]].shakex += .1;
192 cameravars[entity->skill[2]].shakey += 10;
193 }
194 else
195 {
196 if ( entity->skill[2] > 0 )
197 {
198 strcpy((char*)net_packet->data, "SHAK");
199 net_packet->data[4] = 10; // turns into .1
200 net_packet->data[5] = 10;
201 net_packet->address.host = net_clients[entity->skill[2] - 1].host;
202 net_packet->address.port = net_clients[entity->skill[2] - 1].port;
203 net_packet->len = 6;
204 sendPacketSafe(net_sock, -1, net_packet, entity->skill[2] - 1);
205 }
206 }
207 }
208 playSoundEntity(my, 181, 128);
209 playSoundEntity(entity, 28, 64);
210 Entity* gib = spawnGib(entity);
211 if ( my->sprite == BOULDER_LAVA_SPRITE )
212 {
213 entity->modHP(-50);
214 entity->setObituary(language[3898]);
215 }
216 else if ( my->sprite == BOULDER_ARCANE_SPRITE )
217 {
218 entity->modHP(-50);
219 entity->setObituary(language[3899]);
220 }
221 else
222 {
223 entity->modHP(-80);
224 entity->setObituary(language[1505]);
225 }
226 if ( entity->behavior == &actPlayer )
227 {
228 if ( stats->HP <= 0 )
229 {
230 if ( stats->type == AUTOMATON )
231 {
232 entity->playerAutomatonDeathCounter = TICKS_PER_SECOND * 5; // set the death timer to immediately pop for players.
233 }
234 steamAchievementClient(entity->skill[2], "BARONY_ACH_THROW_ME_THE_WHIP");
235 if ( BOULDER_PLAYERPUSHED >= 0 && entity->skill[2] != BOULDER_PLAYERPUSHED )
236 {
237 steamAchievementClient(BOULDER_PLAYERPUSHED, "BARONY_ACH_MOVED_ITSELF");
238 }
239 achievementObserver.updateGlobalStat(STEAM_GSTAT_BOULDER_DEATHS);
240 }
241 }
242
243 bool lifeSaving = (stats->HP <= 0 && stats->amulet && stats->amulet->type == AMULET_LIFESAVING);
244 if ( !lifeSaving )
245 {
246 if ( stats->HP <= 0 && entity->behavior == &actPlayer
247 && ((stats->playerRace == RACE_SKELETON && stats->appearance == 0) || stats->type == SKELETON) )
248 {
249 if ( stats->MP >= 75 )
250 {
251 lifeSaving = true;
252 }
253 else
254 {
255 int spellCost = getCostOfSpell(&spell_summon, entity);
256 int numSummonedAllies = 0;
257 int firstManaToRefund = 0;
258 int secondManaToRefund = 0;
259 for ( node_t* node = stats->FOLLOWERS.first; node != nullptr; node = node->next )
260 {
261 Uint32* c = (Uint32*)node->element;
262 Entity* mySummon = nullptr;
263 if ( c )
264 {
265 mySummon = uidToEntity(*c);
266 }
267 if ( mySummon && mySummon->monsterAllySummonRank != 0 )
268 {
269 Stat* mySummonStats = mySummon->getStats();
270 if ( mySummonStats )
271 {
272 int mp = (mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
273 if ( numSummonedAllies == 0 )
274 {
275 firstManaToRefund += std::min(spellCost, static_cast<int>((mp / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
276 ++numSummonedAllies;
277 }
278 else if ( numSummonedAllies == 1 )
279 {
280 mySummon->setMP(mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
281 secondManaToRefund += std::min(spellCost, static_cast<int>((mp / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
282 ++numSummonedAllies;
283 break;
284 }
285 }
286 }
287 }
288
289 if ( numSummonedAllies == 2 )
290 {
291 firstManaToRefund /= 2;
292 secondManaToRefund /= 2;
293 }
294
295 int manaTotal = stats->MP + firstManaToRefund + secondManaToRefund;
296 if ( manaTotal >= 75 )
297 {
298 lifeSaving = true;
299 }
300 }
301 }
302 }
303
304 if ( stats->HP > 0 || lifeSaving )
305 {
306 // spawn several rock items
307 int i = 8 + rand() % 4;
308 if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
309 {
310 i = 0;
311 }
312 int c;
313 for ( c = 0; c < i; c++ )
314 {
315 Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock/item entity.
316 entity->flags[INVISIBLE] = true;
317 entity->flags[UPDATENEEDED] = true;
318 entity->x = my->x - 4 + rand() % 8;
319 entity->y = my->y - 4 + rand() % 8;
320 entity->z = -6 + rand() % 12;
321 entity->sizex = 4;
322 entity->sizey = 4;
323 entity->yaw = rand() % 360 * PI / 180;
324 entity->vel_x = (rand() % 20 - 10) / 10.0;
325 entity->vel_y = (rand() % 20 - 10) / 10.0;
326 entity->vel_z = -.25 - (rand() % 5) / 10.0;
327 entity->flags[PASSABLE] = true;
328 entity->behavior = &actItem;
329 entity->flags[USERFLAG1] = true; // no collision: helps performance
330 entity->skill[10] = GEM_ROCK; // type
331 entity->skill[11] = WORN; // status
332 entity->skill[12] = 0; // beatitude
333 entity->skill[13] = 1; // count
334 entity->skill[14] = 0; // appearance
335 entity->skill[15] = false; // identified
336 }
337
338 double ox = my->x;
339 double oy = my->y;
340
341 boulderLavaOrArcaneOnDestroy(my, my->sprite, entity);
342
343 // destroy the boulder
344 playSoundEntity(my, 67, 128);
345 list_RemoveNode(my->mynode);
346
347 // on sokoban, destroying boulders spawns scorpions / insectoids
348 if ( !strcmp(map.name, "Sokoban") )
349 {
350 Entity* monster = nullptr;
351 if ( rand() % 2 == 0 )
352 {
353 monster = summonMonster(INSECTOID, ox, oy);
354 }
355 else
356 {
357 monster = summonMonster(SCORPION, ox, oy);
358 }
359 if ( monster )
360 {
361 int c;
362 for ( c = 0; c < MAXPLAYERS; c++ )
363 {
364 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
365 messagePlayerColor(c, color, language[406]);
366 }
367 }
368 boulderSokobanOnDestroy(false);
369 }
370
371 return 1;
372 }
373 else
374 {
375 if ( stats->type == GYROBOT )
376 {
377 Entity* leader = entity->monsterAllyGetPlayerLeader();
378 if ( leader )
379 {
380 real_t tangent = atan2(leader->y - entity->y, leader->x - entity->x);
381 Entity* ohitentity = hit.entity;
382 lineTraceTarget(entity, entity->x, entity->y, tangent, 1024, 0, false, leader);
383 if ( hit.entity == leader )
384 {
385 steamAchievementClient(entity->monsterAllyIndex, "BARONY_ACH_GOODNIGHT_SWEET_PRINCE");
386 }
387 hit.entity = ohitentity;
388 }
389 }
390 if ( gibtype[stats->type] > 0 )
391 {
392 if ( gibtype[stats->type] == 1 )
393 {
394 BOULDER_SPAWNBLOOD = 203; //Blood entity.
395 }
396 else if ( gibtype[stats->type] == 2 )
397 {
398 BOULDER_SPAWNBLOOD = 213; //Blood entity.
399 }
400 else if ( gibtype[stats->type] == 4 )
401 {
402 BOULDER_SPAWNBLOOD = 682; //Blood entity.
403 }
404 if ( BOULDER_SPAWNBLOOD > 0 )
405 {
406 BOULDER_BLOODTIME = TICKS_PER_SECOND * 3;
407 }
408 }
409 }
410 }
411 }
412 }
413 else if ( doesEntityStopBoulder(entity) )
414 {
415 if ( !entity->flags[PASSABLE] )
416 {
417 if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
418 {
419 // stop the boulder
420 BOULDER_STOPPED = 1;
421 my->vel_x = 0.0; // TODOR: Anywhere this is could possible be changed to be a static 'if( BOULDER_ROLLING == 0 ) { vel = 0 }' instead of duplicating code everywhere
422 my->vel_y = 0.0;
423 BOULDER_ROLLING = 0;
424 playSoundEntity(my, 181, 128);
425 if ( my->flags[PASSABLE] )
426 {
427 my->flags[PASSABLE] = false;
428 if ( multiplayer == SERVER )
429 {
430 serverUpdateEntityFlag(my, PASSABLE);
431 }
432 }
433 }
434 }
435 }
436 else if ( entity->behavior == &actDoor )
437 {
438 if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
439 {
440 playSoundEntity(entity, 28, 64);
441 entity->skill[4] = 0;
442 if ( !entity->skill[0] )
443 {
444 entity->skill[6] = (my->x > entity->x);
445 }
446 else
447 {
448 entity->skill[6] = (my->y < entity->y);
449 }
450 playSoundEntity(my, 181, 128);
451 }
452 }
453 else if ( entity->behavior == &actFurniture )
454 {
455 if ( ignoreInsideEntity || entityInsideEntity(my, entity) )
456 {
457 playSoundEntity(entity, 28, 64);
458 entity->furnitureHealth = 0;
459 playSoundEntity(my, 181, 128);
460 }
461 }
462 return 0;
463 }
464
465 /*-------------------------------------------------------------------------------
466
467 act*
468
469 The following function describes an entity behavior. The function
470 takes a pointer to the entity that uses it as an argument.
471
472 -------------------------------------------------------------------------------*/
473
actBoulder(Entity * my)474 void actBoulder(Entity* my)
475 {
476 int i;
477
478 if ( multiplayer == CLIENT )
479 {
480 if ( my->sprite == 989 ) // boulder_lava.vox
481 {
482 my->flags[BURNABLE] = true;
483 }
484 return;
485 }
486 my->skill[2] = -16; // invokes actBoulder() on clients
487 my->flags[UPDATENEEDED] = true;
488
489 bool noground = false;
490 int x = std::min<int>(std::max(0, (int)(my->x / 16)), map.width);
491 int y = std::min<int>(std::max(0, (int)(my->y / 16)), map.height);
492 Uint32 index = y * MAPLAYERS + x * MAPLAYERS * map.height;
493 if ( !map.tiles[index] || swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]] )
494 {
495 if ( (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]])
496 && (my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE) )
497 {
498 // lava/arcane balls, roll over lava.
499 noground = false;
500 }
501 else
502 {
503 noground = true;
504 }
505 }
506
507 if ( !BOULDER_INIT )
508 {
509 BOULDER_LAVA_EXPLODE = -1;
510 if ( my->sprite == BOULDER_LAVA_SPRITE )
511 {
512 if ( rand() % 4 == 0 )
513 {
514 BOULDER_LAVA_EXPLODE = 100 + rand() % 150;
515 }
516 }
517 BOULDER_INIT = 1;
518 BOULDER_PLAYERPUSHED = -1;
519 }
520
521 if ( BOULDER_LAVA_EXPLODE > 0 )
522 {
523 --BOULDER_LAVA_EXPLODE;
524 if ( BOULDER_LAVA_EXPLODE == 0 )
525 {
526 spawnExplosion(my->x, my->y, my->z);
527 boulderLavaOrArcaneOnDestroy(my, my->sprite, nullptr);
528 for ( int c = 0; c < 8; ++c )
529 {
530 my->yaw = ((double)c + ((rand() % 100) / 100.f)) * (PI * 2) / 8.f;
531 castSpell(my->getUID(), &spell_fireball, true, true);
532 }
533 list_RemoveNode(my->mynode);
534 return;
535 }
536 }
537
538 // gravity
539 bool nobounce = true;
540 if ( !BOULDER_NOGROUND )
541 {
542 if ( noground )
543 {
544 BOULDER_NOGROUND = true;
545 }
546 }
547 if ( my->z < 0 || BOULDER_NOGROUND )
548 {
549 my->vel_z = std::min<real_t>(my->vel_z + .1, 3.0);
550 my->vel_x *= 0.85f;
551 my->vel_y *= 0.85f;
552 nobounce = true;
553 if ( my->z >= 128 )
554 {
555 list_RemoveNode(my->mynode);
556 if ( multiplayer != CLIENT && !strncmp(map.name, "Sokoban", 7) )
557 {
558 boulderSokobanOnDestroy(true);
559 }
560 return;
561 }
562 if ( !BOULDER_NOGROUND )
563 {
564 if ( my->z >= -8 && fabs(my->vel_z) > 2 )
565 {
566 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
567 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
568 {
569 list_t* currentList = *it;
570 node_t* node;
571 for ( node = currentList->first; node != nullptr; node = node->next )
572 {
573 Entity* entity = (Entity*)node->element;
574 if ( entity == my )
575 {
576 continue;
577 }
578 if ( boulderCheckAgainstEntity(my, entity, false) )
579 {
580 return;
581 }
582 else
583 {
584 if ( entity->behavior == &actBoulder && entityInsideEntity(my, entity) )
585 {
586 // destroy this boulder if falling on another boulder.
587 Entity* ohitentity = hit.entity;
588 hit.entity = my;
589 if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
590 {
591 magicDig(nullptr, nullptr, 0, 1);
592 }
593 else
594 {
595 magicDig(nullptr, nullptr, 2, 4);
596 }
597 hit.entity = ohitentity;
598 return;
599 }
600 }
601 }
602 }
603 }
604 }
605 }
606 else
607 {
608 if ( fabs(my->vel_z) > 1 )
609 {
610 playSoundEntity(my, 182, 128);
611 my->vel_z = -(my->vel_z / 2);
612 nobounce = true;
613 }
614 else
615 {
616 if ( my->vel_z )
617 {
618 playSoundEntity(my, 182, 128);
619 }
620 my->vel_z = 0;
621 nobounce = false;
622 }
623 my->z = 0;
624 }
625 my->z += my->vel_z;
626 if ( nobounce )
627 {
628 if ( !my->flags[PASSABLE] )
629 {
630 my->flags[PASSABLE] = true;
631 if ( multiplayer == SERVER )
632 {
633 serverUpdateEntityFlag(my, PASSABLE);
634 }
635 }
636 if ( !BOULDER_STOPPED )
637 {
638 my->x += my->vel_x;
639 my->y += my->vel_y;
640 double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
641 my->pitch += dist * .06;
642 my->roll = PI / 2;
643 }
644 }
645 else if ( !BOULDER_STOPPED )
646 {
647 if ( my->flags[PASSABLE] )
648 {
649 my->flags[PASSABLE] = false;
650 if ( multiplayer == SERVER )
651 {
652 serverUpdateEntityFlag(my, PASSABLE);
653 }
654 }
655
656 // horizontal velocity
657 my->vel_x += cos(my->yaw) * .1;
658 my->vel_y += sin(my->yaw) * .1;
659 real_t maxSpeed = 1.5;
660 if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
661 {
662 maxSpeed = 2.5;
663 }
664 if ( my->vel_x > maxSpeed )
665 {
666 my->vel_x = maxSpeed;
667 }
668 if ( my->vel_x < -maxSpeed )
669 {
670 my->vel_x = -maxSpeed;
671 }
672 if ( my->vel_y > maxSpeed )
673 {
674 my->vel_y = maxSpeed;
675 }
676 if ( my->vel_y < -maxSpeed )
677 {
678 my->vel_y = -maxSpeed;
679 }
680
681 /*int x = std::min<int>(std::max<int>(0, (my->x + my->vel_x + 8) / 16), map.width - 1);
682 int y = std::min<int>(std::max<int>(0, (my->y + my->vel_y + 8) / 16), map.height - 1);*/
683 //int x = std::min<int>(std::max<int>(0, (my->x + my->vel_x * 8) / 16), map.width - 1);
684 //int y = std::min<int>(std::max<int>(0, (my->y + my->vel_y * 8) / 16), map.height - 1);
685
686 real_t clipDist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
687 double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
688 if ( clipDist != dist && !hit.entity/*map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height]*/ )
689 {
690 playSoundEntity(my, 181, 128);
691 BOULDER_STOPPED = 1;
692 TileEntityList.updateEntity(*my);
693 bool foundPathToExit = boulderCheckIfBlockedExit(my);
694
695 if ( !foundPathToExit )
696 {
697 hit.entity = my; // for magicDig
698
699 // spawn luckstone
700 Entity* rock = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
701 rock->flags[INVISIBLE] = true;
702 rock->flags[UPDATENEEDED] = true;
703 rock->x = my->x;
704 rock->y = my->y;
705 rock->z = -6 + rand() % 12;
706 rock->sizex = 4;
707 rock->sizey = 4;
708 rock->yaw = rand() % 360 * PI / 180;
709 rock->vel_z = -.25 - (rand() % 5) / 10.0;
710 rock->flags[PASSABLE] = true;
711 rock->behavior = &actItem;
712 rock->flags[USERFLAG1] = true; // no collision: helps performance
713 rock->skill[10] = GEM_LUCK; // type
714 rock->skill[11] = WORN; // status
715 rock->skill[12] = 0; // beatitude
716 rock->skill[13] = 1; // count
717 rock->skill[14] = 0; // appearance
718 rock->skill[15] = false; // identified
719
720 for ( int c = 0; c < MAXPLAYERS; ++c )
721 {
722 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
723 if ( !client_disconnected[c] )
724 {
725 messagePlayerColor(c, color, language[3401]);
726 }
727 }
728
729 if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
730 {
731 magicDig(nullptr, nullptr, 0, 1);
732 }
733 else
734 {
735 magicDig(nullptr, nullptr, 2, 4);
736 }
737 printlog("notice: boulder stopped path to exit, removed.");
738 return;
739 }
740 }
741 else
742 {
743 my->pitch += dist * .06;
744 my->roll = PI / 2;
745
746 // crush objects
747 if ( dist && !BOULDER_NOGROUND )
748 {
749 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
750 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
751 {
752 list_t* currentList = *it;
753 node_t* node;
754 for ( node = currentList->first; node != nullptr; node = node->next )
755 {
756 Entity* entity = (Entity*)node->element;
757 if ( entity == my )
758 {
759 continue;
760 }
761 bool wasStopped = (BOULDER_STOPPED == 1);
762 if ( clipDist != dist )
763 {
764 if ( hit.entity )
765 {
766 if ( boulderCheckAgainstEntity(my, hit.entity, true) )
767 {
768 return;
769 }
770 hit.entity = nullptr;
771 }
772 }
773 else
774 {
775 if ( boulderCheckAgainstEntity(my, entity, false) )
776 {
777 return;
778 }
779 }
780 if ( BOULDER_STOPPED == 1 && !wasStopped )
781 {
782 TileEntityList.updateEntity(*my);
783 bool foundPathToExit = boulderCheckIfBlockedExit(my);
784
785 if ( !foundPathToExit )
786 {
787 hit.entity = my;
788
789 // spawn luckstone
790 Entity* rock = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
791 rock->flags[INVISIBLE] = true;
792 rock->flags[UPDATENEEDED] = true;
793 rock->x = my->x;
794 rock->y = my->y;
795 rock->z = -6 + rand() % 12;
796 rock->sizex = 4;
797 rock->sizey = 4;
798 rock->yaw = rand() % 360 * PI / 180;
799 rock->vel_z = -.25 - (rand() % 5) / 10.0;
800 rock->flags[PASSABLE] = true;
801 rock->behavior = &actItem;
802 rock->flags[USERFLAG1] = true; // no collision: helps performance
803 rock->skill[10] = GEM_LUCK; // type
804 rock->skill[11] = WORN; // status
805 rock->skill[12] = 0; // beatitude
806 rock->skill[13] = 1; // count
807 rock->skill[14] = 0; // appearance
808 rock->skill[15] = false; // identified
809
810 for ( int c = 0; c < MAXPLAYERS; ++c )
811 {
812 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
813 if ( !client_disconnected[c] )
814 {
815 messagePlayerColor(c, color, language[3401]);
816 }
817 }
818
819 if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
820 {
821 magicDig(nullptr, nullptr, 0, 1);
822 }
823 else
824 {
825 magicDig(nullptr, nullptr, 2, 4);
826 }
827 printlog("notice: boulder stopped path to exit, removed.");
828 return;
829 }
830 }
831 }
832 }
833 }
834 }
835 }
836
837 // pushing boulders
838 if ( BOULDER_STOPPED )
839 {
840 if ( !BOULDER_ROLLING )
841 {
842 BOULDER_PLAYERPUSHED = -1;
843 for (i = 0; i < MAXPLAYERS; i++)
844 {
845 if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
846 {
847 if (inrange[i])
848 {
849 int playerSTR = 0;
850 if ( players[i] )
851 {
852 playerSTR = statGetSTR(stats[i], players[i]->entity);
853 }
854 if ( playerSTR < 5 )
855 {
856 messagePlayer(i, language[456]);
857 }
858 else
859 {
860 if (players[i] && players[i]->entity)
861 {
862 //playSoundEntity(my, 151, 128);
863 BOULDER_SOUND_ON_PUSH = i + 1;
864 BOULDER_ROLLING = 1;
865 /*my->x = floor(my->x / 16) * 16 + 8;
866 my->y = floor(my->y / 16) * 16 + 8;*/
867
868 BOULDER_DESTX = (int)(my->x / 16) * 16 + 8;
869 BOULDER_DESTY = (int)(my->y / 16) * 16 + 8;
870 if ( (int)(players[i]->entity->x / 16) < (int)(my->x / 16) )
871 {
872 BOULDER_ROLLDIR = 0; // east
873 }
874 else if ( (int)(players[i]->entity->y / 16) < (int)(my->y / 16) )
875 {
876 BOULDER_ROLLDIR = 1; // south
877 }
878 else if ( (int)(players[i]->entity->x / 16) > (int)(my->x / 16) )
879 {
880 BOULDER_ROLLDIR = 2; // west
881 }
882 else if ( (int)(players[i]->entity->y / 16) > (int)(my->y / 16) )
883 {
884 BOULDER_ROLLDIR = 3; // north
885 }
886 switch ( BOULDER_ROLLDIR )
887 {
888 case 0:
889 BOULDER_DESTX += 16;
890 break;
891 case 1:
892 BOULDER_DESTY += 16;
893 break;
894 case 2:
895 BOULDER_DESTX -= 16;
896 break;
897 case 3:
898 BOULDER_DESTY -= 16;
899 break;
900 }
901 BOULDER_PLAYERPUSHED = i;
902 }
903 }
904 }
905 }
906 }
907 }
908 else
909 {
910 switch ( BOULDER_ROLLDIR )
911 {
912 case 0:
913 my->vel_x = 1;
914 my->vel_y = 0;
915 break;
916 case 1:
917 my->vel_x = 0;
918 my->vel_y = 1;
919 break;
920 case 2:
921 my->vel_x = -1;
922 my->vel_y = 0;
923 break;
924 case 3:
925 my->vel_x = 0;
926 my->vel_y = -1;
927 break;
928 }
929 int x = (my->x + my->vel_x * 8) / 16;
930 int y = (my->y + my->vel_y * 8) / 16;
931 x = std::min<unsigned int>(std::max<int>(0, x), map.width - 1);
932 y = std::min<unsigned int>(std::max<int>(0, y), map.height - 1);
933 if ( map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
934 {
935 my->vel_x = 0.0;
936 my->vel_y = 0.0;
937 BOULDER_ROLLING = 0;
938 if ( BOULDER_SOUND_ON_PUSH > 0 )
939 {
940 messagePlayer(BOULDER_SOUND_ON_PUSH - 1, language[3974]);
941 BOULDER_SOUND_ON_PUSH = 0;
942 }
943 }
944 else
945 {
946 real_t clipDist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
947 /*my->x += my->vel_x;
948 my->y += my->vel_y;*/
949 double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
950 if ( clipDist > 0.001 )
951 {
952 my->pitch += dist * .06;
953 }
954
955 if ( BOULDER_ROLLDIR == 0 )
956 {
957 if ( my->x >= BOULDER_DESTX )
958 {
959 my->x = BOULDER_DESTX;
960 my->vel_x = 0.0;
961 my->vel_y = 0.0;
962 BOULDER_ROLLING = 0;
963 }
964 }
965 else if ( BOULDER_ROLLDIR == 1 )
966 {
967 if ( my->y >= BOULDER_DESTY )
968 {
969 my->y = BOULDER_DESTY;
970 my->vel_x = 0.0;
971 my->vel_y = 0.0;
972 BOULDER_ROLLING = 0;
973 }
974 }
975 else if ( BOULDER_ROLLDIR == 2 )
976 {
977 if ( my->x <= BOULDER_DESTX )
978 {
979 my->x = BOULDER_DESTX;
980 my->vel_x = 0.0;
981 my->vel_y = 0.0;
982 BOULDER_ROLLING = 0;
983 }
984 }
985 else if ( BOULDER_ROLLDIR == 3 )
986 {
987 if ( my->y <= BOULDER_DESTY )
988 {
989 my->y = BOULDER_DESTY;
990 my->vel_x = 0.0;
991 my->vel_y = 0.0;
992 BOULDER_ROLLING = 0;
993 }
994 }
995 double dir = my->yaw - BOULDER_ROLLDIR * PI / 2;
996 while ( dir >= PI )
997 {
998 dir -= PI * 2;
999 }
1000 while ( dir < -PI )
1001 {
1002 dir += PI * 2;
1003 }
1004
1005 if ( clipDist > 0.001 )
1006 {
1007 my->yaw -= dir / 16;
1008 }
1009
1010 while ( my->yaw < 0 )
1011 {
1012 my->yaw += 2 * PI;
1013 }
1014 while ( my->yaw >= 2 * PI )
1015 {
1016 my->yaw -= 2 * PI;
1017 }
1018
1019 // crush objects
1020 if ( !BOULDER_NOGROUND )
1021 {
1022 if ( clipDist != dist )
1023 {
1024 if ( BOULDER_SOUND_ON_PUSH > 0 )
1025 {
1026 if ( clipDist > 0.001 )
1027 {
1028 playSoundEntity(my, 151, 128);
1029 }
1030 else
1031 {
1032 messagePlayer(BOULDER_SOUND_ON_PUSH - 1, language[3974]);
1033 }
1034 BOULDER_SOUND_ON_PUSH = 0;
1035 }
1036 if ( hit.entity && boulderCheckAgainstEntity(my, hit.entity, true) )
1037 {
1038 return;
1039 }
1040 }
1041 /*std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
1042 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1043 {
1044 list_t* currentList = *it;
1045 node_t* node;
1046 for ( node = currentList->first; node != nullptr; node = node->next )
1047 {
1048 Entity* entity = (Entity*)node->element;
1049 if ( entity == my )
1050 {
1051 continue;
1052 }
1053 if ( boulderCheckAgainstEntity(my, entity) )
1054 {
1055 return;
1056 }
1057 }
1058 }*/
1059 }
1060
1061 if ( BOULDER_SOUND_ON_PUSH > 0 )
1062 {
1063 playSoundEntity(my, 151, 128);
1064 BOULDER_SOUND_ON_PUSH = 0;
1065 }
1066 }
1067 }
1068 }
1069
1070 // wrap around angles
1071 while ( my->pitch >= PI * 2 )
1072 {
1073 my->pitch -= PI * 2;
1074 }
1075 while ( my->pitch < 0 )
1076 {
1077 my->pitch += PI * 2;
1078 }
1079 while ( my->roll >= PI * 2 )
1080 {
1081 my->roll -= PI * 2;
1082 }
1083 while ( my->roll < 0 )
1084 {
1085 my->roll += PI * 2;
1086 }
1087
1088 // rolling sound
1089 if ( !BOULDER_STOPPED && (fabs(my->vel_x) > 0 || fabs(my->vel_y) > 0) )
1090 {
1091 BOULDER_AMBIENCE++;
1092 if ( !strncmp(map.name, "Hell Boss", 9) )
1093 {
1094 if ( BOULDER_AMBIENCE >= TICKS_PER_SECOND / 2)
1095 {
1096 BOULDER_AMBIENCE = 0;
1097 playSoundEntity(my, 151, 64);
1098 }
1099 }
1100 else if ( BOULDER_AMBIENCE >= TICKS_PER_SECOND / 3 )
1101 {
1102 BOULDER_AMBIENCE = 0;
1103 playSoundEntity(my, 151, 128);
1104 }
1105
1106 if ( my->sprite == BOULDER_LAVA_SPRITE )
1107 {
1108 if ( !my->flags[BURNING] && BOULDER_LAVA_EXPLODE >= 0 )
1109 {
1110 my->flags[BURNABLE] = true;
1111 my->flags[BURNING] = true;
1112 serverUpdateEntityFlag(my, BURNING);
1113 }
1114 }
1115 }
1116 if ( (!BOULDER_STOPPED || BOULDER_ROLLING) && (fabs(my->vel_x) > 0 || fabs(my->vel_y) > 0) )
1117 {
1118 if ( multiplayer != CLIENT && map.tiles[static_cast<int>(my->y / 16) * MAPLAYERS + static_cast<int>(my->x / 16) * MAPLAYERS * map.height] )
1119 {
1120 // spawn blood only if there's a floor!
1121 if ( BOULDER_SPAWNBLOOD != 0 && BOULDER_BLOODTIME > 0 )
1122 {
1123 int rate = 20;
1124 if ( BOULDER_BLOODTIME > 2 * TICKS_PER_SECOND )
1125 {
1126 rate = 7;
1127 }
1128 else if ( BOULDER_BLOODTIME > 1 * TICKS_PER_SECOND )
1129 {
1130 rate = 15;
1131 }
1132 if ( spawn_blood && my->ticks % (rate + rand() % 3) == 0 )
1133 {
1134 Entity* blood = newEntity(BOULDER_SPAWNBLOOD, 1, map.entities, nullptr); //Gib entity.;
1135 if ( blood != NULL )
1136 {
1137 blood->x = my->x - 4 + rand() % 9;
1138 blood->y = my->y - 4 + rand() % 9;
1139 blood->z = 8.0 + (rand() % 20) / 100.0;
1140 blood->parent = my->getUID();
1141 blood->sizex = 2;
1142 blood->sizey = 2;
1143 int randomScale = rand() % 10;
1144 blood->scalex = (100 - randomScale) / 100.f;
1145 blood->scaley = blood->scalex;
1146 blood->yaw = (rand() % 360) * PI / 180.0;
1147 blood->flags[UPDATENEEDED] = true;
1148 blood->flags[PASSABLE] = true;
1149 }
1150 }
1151 }
1152 }
1153 }
1154 if ( BOULDER_BLOODTIME > 0 )
1155 {
1156 --BOULDER_BLOODTIME;
1157 }
1158 }
1159
1160 #define BOULDERTRAP_FIRED my->skill[0]
1161 #define BOULDERTRAP_AMBIENCE my->skill[6]
1162
actBoulderTrap(Entity * my)1163 void actBoulderTrap(Entity* my)
1164 {
1165 int x, y;
1166 int c;
1167
1168 if ( !BOULDERTRAP_FIRED )
1169 {
1170 BOULDERTRAP_AMBIENCE--;
1171 if ( BOULDERTRAP_AMBIENCE <= 0 )
1172 {
1173 BOULDERTRAP_AMBIENCE = TICKS_PER_SECOND * 30;
1174 playSoundEntity(my, 149, 64);
1175 }
1176 }
1177
1178 if ( !my->skill[28] )
1179 {
1180 return;
1181 }
1182
1183 // received on signal
1184 if ( my->skill[28] == 2 )
1185 {
1186 if ( !BOULDERTRAP_FIRED )
1187 {
1188 int foundTrapdoor = -1;
1189 BOULDERTRAP_FIRED = 1;
1190 for ( c = 0; c < 4; c++ )
1191 {
1192 if ( my->boulderTrapRocksToSpawn & (1 << c) )
1193 {
1194 switch ( c )
1195 {
1196 case 0:
1197 x = 16;
1198 y = 0;
1199 break;
1200 case 1:
1201 x = 0;
1202 y = 16;
1203 break;
1204 case 2:
1205 x = -16;
1206 y = 0;
1207 break;
1208 case 3:
1209 x = 0;
1210 y = -16;
1211 break;
1212 }
1213 x = ((int)(x + my->x)) >> 4;
1214 y = ((int)(y + my->y)) >> 4;
1215 if ( x >= 0 && y >= 0 && x < map.width && y < map.height )
1216 {
1217 if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1218 {
1219 list_t* trapdoors = TileEntityList.getTileList(x, y);
1220 for ( node_t* trapNode = trapdoors->first; trapNode != nullptr; trapNode = trapNode->next )
1221 {
1222 Entity* trapEntity = (Entity*)trapNode->element;
1223 if ( trapEntity && trapEntity->sprite == 252 && trapEntity->z <= -10 )
1224 {
1225 foundTrapdoor = c;
1226 break;
1227 }
1228 }
1229 if ( foundTrapdoor == c )
1230 {
1231 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1232 entity->parent = my->getUID();
1233 entity->x = (x << 4) + 8;
1234 entity->y = (y << 4) + 8;
1235 entity->z = -64;
1236 entity->yaw = c * (PI / 2.f);
1237 entity->sizex = 7;
1238 entity->sizey = 7;
1239 if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1240 {
1241 entity->yaw += PI * (rand() % 2) - PI / 2;
1242 if ( entity->yaw >= PI * 2 )
1243 {
1244 entity->yaw -= PI * 2;
1245 }
1246 else if ( entity->yaw < 0 )
1247 {
1248 entity->yaw += PI * 2;
1249 }
1250 }
1251 entity->behavior = &actBoulder;
1252 entity->flags[UPDATENEEDED] = true;
1253 entity->flags[PASSABLE] = true;
1254 }
1255 }
1256 }
1257 }
1258 }
1259 if ( foundTrapdoor >= 0 )
1260 {
1261 playSoundEntity(my, 150, 128);
1262 for ( c = 0; c < MAXPLAYERS; c++ )
1263 {
1264 playSoundPlayer(c, 150, 64);
1265 }
1266 }
1267 }
1268 }
1269 }
1270
actBoulderTrapEast(Entity * my)1271 void actBoulderTrapEast(Entity* my)
1272 {
1273 int x, y;
1274 int c;
1275
1276 if ( !my->boulderTrapFired )
1277 {
1278 my->boulderTrapAmbience--;
1279 if ( my->boulderTrapAmbience <= 0 )
1280 {
1281 my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1282 playSoundEntity(my, 149, 64);
1283 }
1284 }
1285
1286 if ( my->boulderTrapRefireCounter > 0 )
1287 {
1288 --my->boulderTrapRefireCounter;
1289 if ( my->boulderTrapRefireCounter <= 0 )
1290 {
1291 my->boulderTrapFired = 0;
1292 my->boulderTrapRefireCounter = 0;
1293 }
1294 }
1295
1296 if ( !my->skill[28] )
1297 {
1298 return;
1299 }
1300
1301 // received on signal
1302 if ( my->skill[28] == 2 )
1303 {
1304 if ( !my->boulderTrapFired )
1305 {
1306 if ( my->boulderTrapPreDelay > 0 )
1307 {
1308 --my->boulderTrapPreDelay;
1309 return;
1310 }
1311 playSoundEntity(my, 150, 128);
1312 for ( c = 0; c < MAXPLAYERS; c++ )
1313 {
1314 playSoundPlayer(c, 150, 64);
1315 }
1316 my->boulderTrapFired = 1;
1317
1318 c = 0; // direction
1319 x = ((int)(my->x)) >> 4;
1320 y = ((int)(my->y)) >> 4;
1321 if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1322 {
1323 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1324 entity->parent = my->getUID();
1325 entity->x = (x << 4) + 8;
1326 entity->y = (y << 4) + 8;
1327 entity->z = -64;
1328 entity->yaw = c * (PI / 2.f);
1329 entity->sizex = 7;
1330 entity->sizey = 7;
1331 /*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1332 {
1333 entity->yaw += PI * (rand() % 2) - PI / 2;
1334 if ( entity->yaw >= PI * 2 )
1335 {
1336 entity->yaw -= PI * 2;
1337 }
1338 else if ( entity->yaw < 0 )
1339 {
1340 entity->yaw += PI * 2;
1341 }
1342 }*/
1343 entity->behavior = &actBoulder;
1344 entity->flags[UPDATENEEDED] = true;
1345 entity->flags[PASSABLE] = true;
1346 }
1347
1348 if ( my->boulderTrapRefireAmount > 0 )
1349 {
1350 --my->boulderTrapRefireAmount;
1351 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1352 }
1353 else if ( my->boulderTrapRefireAmount == -1 )
1354 {
1355 // infinite boulders.
1356 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1357 }
1358 }
1359 }
1360 }
1361
actBoulderTrapSouth(Entity * my)1362 void actBoulderTrapSouth(Entity* my)
1363 {
1364 int x, y;
1365 int c;
1366
1367 if ( !my->boulderTrapFired )
1368 {
1369 my->boulderTrapAmbience--;
1370 if ( my->boulderTrapAmbience <= 0 )
1371 {
1372 my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1373 playSoundEntity(my, 149, 64);
1374 }
1375 }
1376
1377 if ( my->boulderTrapRefireCounter > 0 )
1378 {
1379 --my->boulderTrapRefireCounter;
1380 if ( my->boulderTrapRefireCounter <= 0 )
1381 {
1382 my->boulderTrapFired = 0;
1383 my->boulderTrapRefireCounter = 0;
1384 }
1385 }
1386
1387 if ( !my->skill[28] )
1388 {
1389 return;
1390 }
1391
1392 // received on signal
1393 if ( my->skill[28] == 2 )
1394 {
1395 if ( !my->boulderTrapFired )
1396 {
1397 if ( my->boulderTrapPreDelay > 0 )
1398 {
1399 --my->boulderTrapPreDelay;
1400 return;
1401 }
1402 playSoundEntity(my, 150, 128);
1403 for ( c = 0; c < MAXPLAYERS; c++ )
1404 {
1405 playSoundPlayer(c, 150, 64);
1406 }
1407 my->boulderTrapFired = 1;
1408
1409 c = 1; // direction
1410 x = ((int)(my->x)) >> 4;
1411 y = ((int)(my->y)) >> 4;
1412 if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1413 {
1414 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1415 entity->parent = my->getUID();
1416 entity->x = (x << 4) + 8;
1417 entity->y = (y << 4) + 8;
1418 entity->z = -64;
1419 entity->yaw = c * (PI / 2.f);
1420 entity->sizex = 7;
1421 entity->sizey = 7;
1422 /*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1423 {
1424 entity->yaw += PI * (rand() % 2) - PI / 2;
1425 if ( entity->yaw >= PI * 2 )
1426 {
1427 entity->yaw -= PI * 2;
1428 }
1429 else if ( entity->yaw < 0 )
1430 {
1431 entity->yaw += PI * 2;
1432 }
1433 }*/
1434 entity->behavior = &actBoulder;
1435 entity->flags[UPDATENEEDED] = true;
1436 entity->flags[PASSABLE] = true;
1437 }
1438
1439 if ( my->boulderTrapRefireAmount > 0 )
1440 {
1441 --my->boulderTrapRefireAmount;
1442 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1443 }
1444 else if ( my->boulderTrapRefireAmount == -1 )
1445 {
1446 // infinite boulders.
1447 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1448 }
1449 }
1450 }
1451 }
1452
actBoulderTrapWest(Entity * my)1453 void actBoulderTrapWest(Entity* my)
1454 {
1455 int x, y;
1456 int c;
1457
1458 if ( !my->boulderTrapFired )
1459 {
1460 my->boulderTrapAmbience--;
1461 if ( my->boulderTrapAmbience <= 0 )
1462 {
1463 my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1464 playSoundEntity(my, 149, 64);
1465 }
1466 }
1467
1468 if ( my->boulderTrapRefireCounter > 0 )
1469 {
1470 --my->boulderTrapRefireCounter;
1471 if ( my->boulderTrapRefireCounter <= 0 )
1472 {
1473 my->boulderTrapFired = 0;
1474 my->boulderTrapRefireCounter = 0;
1475 }
1476 }
1477
1478 if ( !my->skill[28] )
1479 {
1480 return;
1481 }
1482
1483 // received on signal
1484 if ( my->skill[28] == 2 )
1485 {
1486 if ( !my->boulderTrapFired )
1487 {
1488 if ( my->boulderTrapPreDelay > 0 )
1489 {
1490 --my->boulderTrapPreDelay;
1491 return;
1492 }
1493 playSoundEntity(my, 150, 128);
1494 for ( c = 0; c < MAXPLAYERS; c++ )
1495 {
1496 playSoundPlayer(c, 150, 64);
1497 }
1498
1499 my->boulderTrapFired = 1;
1500
1501 c = 2; // direction
1502 x = ((int)(my->x)) >> 4;
1503 y = ((int)(my->y)) >> 4;
1504 if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1505 {
1506 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1507 entity->parent = my->getUID();
1508 entity->x = (x << 4) + 8;
1509 entity->y = (y << 4) + 8;
1510 entity->z = -64;
1511 entity->yaw = c * (PI / 2.f);
1512 entity->sizex = 7;
1513 entity->sizey = 7;
1514 /*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1515 {
1516 entity->yaw += PI * (rand() % 2) - PI / 2;
1517 if ( entity->yaw >= PI * 2 )
1518 {
1519 entity->yaw -= PI * 2;
1520 }
1521 else if ( entity->yaw < 0 )
1522 {
1523 entity->yaw += PI * 2;
1524 }
1525 }*/
1526 entity->behavior = &actBoulder;
1527 entity->flags[UPDATENEEDED] = true;
1528 entity->flags[PASSABLE] = true;
1529 }
1530
1531 if ( my->boulderTrapRefireAmount > 0 )
1532 {
1533 --my->boulderTrapRefireAmount;
1534 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1535 }
1536 else if ( my->boulderTrapRefireAmount == -1 )
1537 {
1538 // infinite boulders.
1539 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1540 }
1541 }
1542 }
1543 }
1544
actBoulderTrapNorth(Entity * my)1545 void actBoulderTrapNorth(Entity* my)
1546 {
1547 int x, y;
1548 int c;
1549
1550 if ( !my->boulderTrapFired )
1551 {
1552 my->boulderTrapAmbience--;
1553 if ( my->boulderTrapAmbience <= 0 )
1554 {
1555 my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1556 playSoundEntity(my, 149, 64);
1557 }
1558 }
1559
1560 if ( my->boulderTrapRefireCounter > 0 )
1561 {
1562 --my->boulderTrapRefireCounter;
1563 if ( my->boulderTrapRefireCounter <= 0 )
1564 {
1565 my->boulderTrapFired = 0;
1566 my->boulderTrapRefireCounter = 0;
1567 }
1568 }
1569
1570 if ( !my->skill[28] )
1571 {
1572 return;
1573 }
1574
1575 // received on signal
1576 if ( my->skill[28] == 2 )
1577 {
1578 if ( !my->boulderTrapFired )
1579 {
1580 if ( my->boulderTrapPreDelay > 0 )
1581 {
1582 --my->boulderTrapPreDelay;
1583 return;
1584 }
1585 playSoundEntity(my, 150, 128);
1586 for ( c = 0; c < MAXPLAYERS; c++ )
1587 {
1588 playSoundPlayer(c, 150, 64);
1589 }
1590 my->boulderTrapFired = 1;
1591
1592 c = 3; // direction
1593 x = ((int)(my->x)) >> 4;
1594 y = ((int)(my->y)) >> 4;
1595 if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1596 {
1597 Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1598 entity->parent = my->getUID();
1599 entity->x = (x << 4) + 8;
1600 entity->y = (y << 4) + 8;
1601 entity->z = -64;
1602 entity->yaw = c * (PI / 2.f);
1603 entity->sizex = 7;
1604 entity->sizey = 7;
1605 /* if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1606 {
1607 entity->yaw += PI * (rand() % 2) - PI / 2;
1608 if ( entity->yaw >= PI * 2 )
1609 {
1610 entity->yaw -= PI * 2;
1611 }
1612 else if ( entity->yaw < 0 )
1613 {
1614 entity->yaw += PI * 2;
1615 }
1616 }*/
1617 entity->behavior = &actBoulder;
1618 entity->flags[UPDATENEEDED] = true;
1619 entity->flags[PASSABLE] = true;
1620 }
1621
1622 if ( my->boulderTrapRefireAmount > 0 )
1623 {
1624 --my->boulderTrapRefireAmount;
1625 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1626 }
1627 else if ( my->boulderTrapRefireAmount == -1 )
1628 {
1629 // infinite boulders.
1630 my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1631 }
1632 }
1633 }
1634 }
1635
boulderSokobanOnDestroy(bool pushedOffLedge)1636 void boulderSokobanOnDestroy(bool pushedOffLedge)
1637 {
1638 if ( multiplayer == CLIENT || strcmp(map.name, "Sokoban") )
1639 {
1640 return; // return for client and if map not sokoban.
1641 }
1642
1643 int goldToDestroy = 5 + rand() % 4; // 5-8 bags destroy
1644 bool bouldersAround = false;
1645 node_t* node = nullptr;
1646
1647 if ( !pushedOffLedge ) // destroy some gold
1648 {
1649 for ( node_t* node = map.entities->first; node != nullptr; )
1650 {
1651 Entity* entity = (Entity*)node->element;
1652 node = node->next;
1653 if ( entity )
1654 {
1655 if ( entity->behavior == &actGoldBag && entity->goldSokoban == 1 && goldToDestroy > 0 )
1656 {
1657 if ( entity->mynode )
1658 {
1659 list_RemoveNode(entity->mynode);
1660 }
1661 --goldToDestroy;
1662 }
1663 }
1664 }
1665 }
1666
1667 for ( node_t* node = map.entities->first; node != nullptr; node = node->next )
1668 {
1669 Entity* entity = (Entity*)node->element;
1670 if ( entity )
1671 {
1672 if ( !bouldersAround && entity->behavior == &actBoulder )
1673 {
1674 bouldersAround = true;
1675 break;
1676 }
1677 }
1678 }
1679
1680 if ( !bouldersAround )
1681 {
1682 int goldCount = 0;
1683 Entity* sokobanItemReward = nullptr;
1684 for ( node = map.entities->first; node != nullptr; node = node->next )
1685 {
1686 Entity* entity = (Entity*)node->element;
1687 if ( entity )
1688 {
1689 if ( entity->behavior == &actGoldBag && entity->goldSokoban == 1 )
1690 {
1691 ++goldCount;
1692 }
1693 if ( entity->behavior == &actItem && entity->itemSokobanReward == 1 ) // artifact gloves.
1694 {
1695 sokobanItemReward = entity;
1696 }
1697 }
1698 }
1699 //messagePlayer(0, "Solved it!");
1700 for ( int c = 0; c < MAXPLAYERS; c++ )
1701 {
1702 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
1703 if ( goldCount >= 39 )
1704 {
1705 playSoundPlayer(c, 393, 128);
1706 messagePlayerColor(c, color, language[2969]);
1707 }
1708 else
1709 {
1710 playSoundPlayer(c, 395, 128);
1711 if ( goldCount < 25 )
1712 {
1713 messagePlayerColor(c, color, language[2971]); // less than impressed.
1714 }
1715 else
1716 {
1717 messagePlayerColor(c, color, language[2970]); // mildly entertained.
1718 }
1719 }
1720 }
1721 if ( goldCount >= 25 && sokobanItemReward )
1722 {
1723 sokobanItemReward->flags[INVISIBLE] = false;
1724 serverUpdateEntityFlag(sokobanItemReward, INVISIBLE);
1725 }
1726 }
1727 }
1728
isBoulderSprite()1729 bool Entity::isBoulderSprite()
1730 {
1731 if ( sprite == 245 || sprite == 989 || sprite == 990 )
1732 {
1733 return true;
1734 }
1735 return false;
1736 }
1737
boulderLavaOrArcaneOnDestroy(Entity * my,int sprite,Entity * boulderHitEntity)1738 void boulderLavaOrArcaneOnDestroy(Entity* my, int sprite, Entity* boulderHitEntity)
1739 {
1740 if ( !boulderHitEntity && !my )
1741 {
1742 return;
1743 }
1744 if ( sprite != BOULDER_LAVA_SPRITE && sprite != BOULDER_ARCANE_SPRITE )
1745 {
1746 return;
1747 }
1748
1749 if ( !boulderHitEntity )
1750 {
1751 if ( my )
1752 {
1753 if ( sprite == BOULDER_LAVA_SPRITE )
1754 {
1755 spawnMagicTower(my, my->x, my->y, SPELL_FIREBALL, nullptr);
1756 }
1757 else if ( sprite == BOULDER_ARCANE_SPRITE )
1758 {
1759 switch ( rand() % 4 )
1760 {
1761 case 0:
1762 spawnMagicTower(my, my->x, my->y, SPELL_LIGHTNING, nullptr);
1763 break;
1764 case 1:
1765 spawnMagicTower(my, my->x, my->y, SPELL_COLD, nullptr);
1766 break;
1767 case 2:
1768 spawnMagicTower(my, my->x, my->y, SPELL_FIREBALL, nullptr);
1769 break;
1770 case 3:
1771 spawnMagicTower(my, my->x, my->y, SPELL_MAGICMISSILE, nullptr);
1772 break;
1773 default:
1774 spawnMagicTower(my, my->x, my->y, SPELL_MAGICMISSILE, nullptr);
1775 break;
1776 }
1777 }
1778 }
1779 }
1780 else
1781 {
1782 boulderHitEntity->SetEntityOnFire();
1783 boulderHitEntity->setObituary(language[3898]);
1784 if ( sprite == BOULDER_LAVA_SPRITE )
1785 {
1786 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_FIREBALL, boulderHitEntity);
1787 }
1788 else if ( sprite == BOULDER_ARCANE_SPRITE )
1789 {
1790 switch ( rand() % 4 )
1791 {
1792 case 0:
1793 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_LIGHTNING, boulderHitEntity);
1794 break;
1795 case 1:
1796 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_COLD, boulderHitEntity);
1797 break;
1798 case 2:
1799 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_FIREBALL, boulderHitEntity);
1800 break;
1801 case 3:
1802 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_MAGICMISSILE, boulderHitEntity);
1803 break;
1804 default:
1805 spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_MAGICMISSILE, boulderHitEntity);
1806 break;
1807 }
1808 }
1809 }
1810 }