1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_ghoul.cpp
5 Desc: implements all of the ghoul monster's 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 "items.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22
initGhoul(Entity * my,Stat * myStats)23 void initGhoul(Entity* my, Stat* myStats)
24 {
25 int c;
26 node_t* node;
27
28 my->initMonster(246);
29
30 if ( multiplayer != CLIENT )
31 {
32 MONSTER_SPOTSND = 142;
33 MONSTER_SPOTVAR = 3;
34 MONSTER_IDLESND = 146;
35 MONSTER_IDLEVAR = 3;
36 }
37 if ( multiplayer != CLIENT && !MONSTER_INIT )
38 {
39 if ( myStats != nullptr )
40 {
41 if ( !myStats->leader_uid )
42 {
43 myStats->leader_uid = 0;
44 }
45
46 bool lesserMonster = false;
47 if ( !strncmp(myStats->name, "enslaved ghoul", strlen("enslaved ghoul")) )
48 {
49 if ( !strncmp(map.name, "Bram's Castle", 13) )
50 {
51 }
52 else
53 {
54 myStats->HP = 110;
55 myStats->MAXHP = myStats->HP;
56 myStats->OLDHP = myStats->HP;
57 myStats->STR = 13;
58 myStats->DEX = 5;
59 if ( !strncmp(map.name, "The Haunted Castle", 18) )
60 {
61 myStats->LVL = 10;
62 }
63 else
64 {
65 myStats->LVL = 15;
66 }
67 myStats->PER = 10;
68 if ( rand() % 2 == 0 )
69 {
70 myStats->EFFECTS[EFF_VAMPIRICAURA] = true;
71 myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] = -1;
72 }
73 }
74 }
75
76
77 // apply random stat increases if set in stat_shared.cpp or editor
78 setRandomMonsterStats(myStats);
79
80 // generate 6 items max, less if there are any forced items from boss variants
81 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
82
83 // boss variants
84 if ( rand() % 50 || my->flags[USERFLAG2] || myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] )
85 {
86 if ( !strncmp(map.name, "Bram's Castle", 13) )
87 {
88 myStats->EFFECTS[EFF_VAMPIRICAURA] = true;
89 myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] = -1;
90 }
91 }
92 else if ( !lesserMonster )
93 {
94 strcpy(myStats->name, "Coral Grimes");
95 for ( c = 0; c < 3; c++ )
96 {
97 Entity* entity = summonMonster(GHOUL, my->x, my->y);
98 if ( entity )
99 {
100 entity->parent = my->getUID();
101 }
102 }
103 myStats->HP *= 3;
104 myStats->MAXHP *= 3;
105 myStats->OLDHP = myStats->HP;
106 myStats->LVL = 15;
107 myStats->DEX = 2;
108 myStats->STR = 13;
109 newItem(GEM_GARNET, EXCELLENT, 0, 1, rand(), false, &myStats->inventory);
110 customItemsToGenerate -= 1;
111 }
112
113 // random effects
114
115 // generates equipment and weapons if available from editor
116 createMonsterEquipment(myStats);
117
118 // create any custom inventory items from editor if available
119 createCustomInventory(myStats, customItemsToGenerate);
120
121 // count if any custom inventory items from editor
122 // max limit of 6 custom items per entity.
123 int customItems = countCustomItems(myStats);
124
125 // count any inventory items set to default in edtior
126 int defaultItems = countDefaultItems(myStats);
127
128 my->setHardcoreStats(*myStats);
129
130 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
131 switch ( defaultItems )
132 {
133 case 6:
134 case 5:
135 case 4:
136 case 3:
137 if ( rand() % 20 == 0 )
138 {
139 newItem(POTION_WATER, SERVICABLE, 2, 1, rand(), false, &myStats->inventory);
140 }
141 case 2:
142 if ( rand() % 10 == 0 )
143 {
144 newItem(itemLevelCurve(TOOL, 0, currentlevel), DECREPIT, 1, 1, rand(), false, &myStats->inventory);
145 }
146 case 1:
147 if ( rand() % 4 == 0 )
148 {
149 newItem(FOOD_MEAT, DECREPIT, -1, 1, rand(), false, &myStats->inventory);
150 }
151 break;
152 default:
153 break;
154 }
155 }
156 }
157
158 // torso
159 Entity* entity = newEntity(247, 0, map.entities, nullptr); //Limb entity.
160 entity->sizex = 4;
161 entity->sizey = 4;
162 entity->skill[2] = my->getUID();
163 entity->flags[PASSABLE] = true;
164 entity->flags[NOUPDATE] = true;
165 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
166 entity->focalx = limbs[GHOUL][1][0]; // 0
167 entity->focaly = limbs[GHOUL][1][1]; // 0
168 entity->focalz = limbs[GHOUL][1][2]; // 0
169 entity->behavior = &actGhoulLimb;
170 entity->parent = my->getUID();
171 node = list_AddNodeLast(&my->children);
172 node->element = entity;
173 node->deconstructor = &emptyDeconstructor;
174 node->size = sizeof(Entity*);
175 my->bodyparts.push_back(entity);
176
177 // right leg
178 entity = newEntity(251, 0, map.entities, nullptr); //Limb entity.
179 entity->sizex = 4;
180 entity->sizey = 4;
181 entity->skill[2] = my->getUID();
182 entity->flags[PASSABLE] = true;
183 entity->flags[NOUPDATE] = true;
184 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
185 entity->focalx = limbs[GHOUL][2][0]; // 1
186 entity->focaly = limbs[GHOUL][2][1]; // 0
187 entity->focalz = limbs[GHOUL][2][2]; // 2
188 entity->behavior = &actGhoulLimb;
189 entity->parent = my->getUID();
190 node = list_AddNodeLast(&my->children);
191 node->element = entity;
192 node->deconstructor = &emptyDeconstructor;
193 node->size = sizeof(Entity*);
194 my->bodyparts.push_back(entity);
195
196 // left leg
197 entity = newEntity(250, 0, map.entities, nullptr); //Limb entity.
198 entity->sizex = 4;
199 entity->sizey = 4;
200 entity->skill[2] = my->getUID();
201 entity->flags[PASSABLE] = true;
202 entity->flags[NOUPDATE] = true;
203 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
204 entity->focalx = limbs[GHOUL][3][0]; // 1
205 entity->focaly = limbs[GHOUL][3][1]; // 0
206 entity->focalz = limbs[GHOUL][3][2]; // 2
207 entity->behavior = &actGhoulLimb;
208 entity->parent = my->getUID();
209 node = list_AddNodeLast(&my->children);
210 node->element = entity;
211 node->deconstructor = &emptyDeconstructor;
212 node->size = sizeof(Entity*);
213 my->bodyparts.push_back(entity);
214
215 // right arm
216 entity = newEntity(249, 0, map.entities, nullptr); //Limb entity.
217 entity->sizex = 4;
218 entity->sizey = 4;
219 entity->skill[2] = my->getUID();
220 entity->flags[PASSABLE] = true;
221 entity->flags[NOUPDATE] = true;
222 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
223 entity->focalx = limbs[GHOUL][4][0]; // -.25
224 entity->focaly = limbs[GHOUL][4][1]; // 0
225 entity->focalz = limbs[GHOUL][4][2]; // 3
226 entity->behavior = &actGhoulLimb;
227 entity->parent = my->getUID();
228 node = list_AddNodeLast(&my->children);
229 node->element = entity;
230 node->deconstructor = &emptyDeconstructor;
231 node->size = sizeof(Entity*);
232 my->bodyparts.push_back(entity);
233
234 // left arm
235 entity = newEntity(248, 0, map.entities, nullptr); //Limb entity.
236 entity->sizex = 4;
237 entity->sizey = 4;
238 entity->skill[2] = my->getUID();
239 entity->flags[PASSABLE] = true;
240 entity->flags[NOUPDATE] = true;
241 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
242 entity->focalx = limbs[GHOUL][5][0]; // -.25
243 entity->focaly = limbs[GHOUL][5][1]; // 0
244 entity->focalz = limbs[GHOUL][5][2]; // 3
245 entity->behavior = &actGhoulLimb;
246 entity->parent = my->getUID();
247 node = list_AddNodeLast(&my->children);
248 node->element = entity;
249 node->deconstructor = &emptyDeconstructor;
250 node->size = sizeof(Entity*);
251 my->bodyparts.push_back(entity);
252 }
253
actGhoulLimb(Entity * my)254 void actGhoulLimb(Entity* my)
255 {
256 my->actMonsterLimb();
257 }
258
ghoulDie(Entity * my)259 void ghoulDie(Entity* my)
260 {
261 int c;
262 for ( c = 0; c < 10; c++ )
263 {
264 Entity* entity = spawnGib(my);
265 if ( entity )
266 {
267 if ( c < 6 )
268 {
269 entity->sprite = 246 + c;
270 }
271 serverSpawnGibForClient(entity);
272 }
273 }
274
275 my->spawnBlood(212);
276
277 my->removeMonsterDeathNodes();
278
279 playSoundEntity(my, 145, 128);
280 list_RemoveNode(my->mynode);
281 return;
282 }
283
284 #define GHOULWALKSPEED .125
285
ghoulMoveBodyparts(Entity * my,Stat * myStats,double dist)286 void ghoulMoveBodyparts(Entity* my, Stat* myStats, double dist)
287 {
288 node_t* node;
289 Entity* entity = nullptr;
290 Entity* rightbody = nullptr;
291 int bodypart;
292
293 // set invisibility //TODO: isInvisible()?
294 if ( multiplayer != CLIENT )
295 {
296 if ( myStats->EFFECTS[EFF_INVISIBLE] == true )
297 {
298 my->flags[INVISIBLE] = true;
299 my->flags[BLOCKSIGHT] = false;
300 bodypart = 0;
301 for (node = my->children.first; node != nullptr; node = node->next)
302 {
303 if ( bodypart < LIMB_HUMANOID_TORSO )
304 {
305 bodypart++;
306 continue;
307 }
308 if ( bodypart >= 7 )
309 {
310 break;
311 }
312 entity = (Entity*)node->element;
313 if ( !entity->flags[INVISIBLE] )
314 {
315 entity->flags[INVISIBLE] = true;
316 serverUpdateEntityBodypart(my, bodypart);
317 }
318 bodypart++;
319 }
320 }
321 else
322 {
323 my->flags[INVISIBLE] = false;
324 my->flags[BLOCKSIGHT] = true;
325 bodypart = 0;
326 for (node = my->children.first; node != nullptr; node = node->next)
327 {
328 if ( bodypart < LIMB_HUMANOID_TORSO )
329 {
330 bodypart++;
331 continue;
332 }
333 if ( bodypart >= 7 )
334 {
335 break;
336 }
337 entity = (Entity*)node->element;
338 if ( entity->flags[INVISIBLE] )
339 {
340 entity->flags[INVISIBLE] = false;
341 serverUpdateEntityBodypart(my, bodypart);
342 serverUpdateEntityFlag(my, INVISIBLE);
343 }
344 bodypart++;
345 }
346 }
347 }
348
349 //Move bodyparts
350 my->x -= cos(my->yaw);
351 my->y -= sin(my->yaw);
352 for (bodypart = 0, node = my->children.first; node != nullptr; node = node->next, bodypart++)
353 {
354 if ( bodypart < LIMB_HUMANOID_TORSO )
355 {
356 continue;
357 }
358 entity = (Entity*)node->element;
359 entity->x = my->x;
360 entity->y = my->y;
361 entity->z = my->z;
362 entity->yaw = my->yaw;
363 if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
364 {
365 if ( bodypart == LIMB_HUMANOID_RIGHTLEG )
366 {
367 rightbody = (Entity*)node->next->element;
368 }
369 if ( bodypart == LIMB_HUMANOID_LEFTARM )
370 {
371 if ( my->monsterAttack > 0 )
372 {
373 // vertical chop windup
374 if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP1 )
375 {
376 if ( my->monsterAttackTime == 0 )
377 {
378 // init rotations
379 entity->pitch = 0;
380 my->monsterArmbended = 0;
381 //my->monsterWeaponYaw = 0; // keep the arms outstretched.
382 entity->roll = 0;
383 entity->skill[1] = 0;
384 }
385
386 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
387
388 if ( my->monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
389 {
390 if ( multiplayer != CLIENT )
391 {
392 my->attack(1, 0, nullptr);
393 }
394 }
395 }
396 // vertical chop attack
397 else if ( my->monsterAttack == 1 )
398 {
399 my->monsterWeaponYaw = 0;
400 if ( entity->pitch >= 3 * PI / 2 )
401 {
402 my->monsterArmbended = 1;
403 }
404
405 if ( entity->skill[1] == 0 )
406 {
407 // chop forwards
408 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.4, PI / 3, false, 0.0) )
409 {
410 entity->skill[1] = 1;
411 }
412 }
413 else if ( entity->skill[1] == 1 )
414 {
415 my->monsterWeaponYaw = -PI / 16.0;
416 // return to neutral
417 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 25 * PI / 16, false, 0.0) )
418 {
419 entity->skill[0] = rightbody->skill[0];
420 entity->pitch = rightbody->pitch;
421 entity->roll = 0;
422 my->monsterArmbended = 0;
423 my->monsterAttack = 0;
424 }
425 }
426 }
427 }
428 else
429 {
430 my->monsterWeaponYaw = -PI / 16.0;
431 entity->pitch = -7 * PI / 16;
432 entity->roll = 0;
433 }
434 }
435 else
436 {
437 if ( dist > 0.1 )
438 {
439 if ( !rightbody->skill[0] )
440 {
441 entity->pitch -= dist * GHOULWALKSPEED;
442 if ( entity->pitch < -PI / 4.0 )
443 {
444 entity->pitch = -PI / 4.0;
445 }
446 }
447 else
448 {
449 entity->pitch += dist * GHOULWALKSPEED;
450 if ( entity->pitch > PI / 4.0 )
451 {
452 entity->pitch = PI / 4.0;
453 }
454 }
455 }
456 else
457 {
458 if ( entity->pitch < 0 )
459 {
460 entity->pitch += 1 / fmax(dist * .1, 10.0);
461 if ( entity->pitch > 0 )
462 {
463 entity->pitch = 0;
464 }
465 }
466 else if ( entity->pitch > 0 )
467 {
468 entity->pitch -= 1 / fmax(dist * .1, 10.0);
469 if ( entity->pitch < 0 )
470 {
471 entity->pitch = 0;
472 }
473 }
474 }
475 }
476 }
477 else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM )
478 {
479 if ( bodypart == LIMB_HUMANOID_RIGHTARM )
480 {
481 if ( my->monsterAttack > 0 )
482 {
483 //vertical chop
484 //get leftarm from bodypart 6 element if ready to attack
485 Entity* leftarm = (Entity*)node->next->element;
486 if ( my->monsterAttack == 1 || my->monsterAttack == MONSTER_POSE_MELEE_WINDUP1 )
487 {
488 if ( leftarm != nullptr )
489 {
490 // follow the right arm animation.
491 entity->pitch = leftarm->pitch;
492 entity->roll = -leftarm->roll;
493 }
494 }
495 }
496 else
497 {
498 entity->pitch = -7 * PI / 16;
499 entity->roll = 0;
500 }
501 }
502 else
503 {
504 if ( dist > 0.1 )
505 {
506 if ( entity->skill[0] )
507 {
508 entity->pitch -= dist * GHOULWALKSPEED * .5;
509 if ( entity->pitch < -PI / 8.0 )
510 {
511 entity->skill[0] = 0;
512 entity->pitch = -PI / 8.0;
513 }
514 }
515 else
516 {
517 entity->pitch += dist * GHOULWALKSPEED * .5;
518 if ( entity->pitch > PI / 8.0 )
519 {
520 entity->skill[0] = 1;
521 entity->pitch = PI / 8.0;
522 }
523 }
524 }
525 else
526 {
527 if ( entity->pitch < 0 )
528 {
529 entity->pitch += (1 / fmax(dist * .1, 10.0)) * .5;
530 if ( entity->pitch > 0 )
531 {
532 entity->pitch = 0;
533 }
534 }
535 else if ( entity->pitch > 0 )
536 {
537 entity->pitch -= (1 / fmax(dist * .1, 10.0)) * .5;
538 if ( entity->pitch < 0 )
539 {
540 entity->pitch = 0;
541 }
542 }
543 }
544 }
545 }
546 switch ( bodypart )
547 {
548 // torso
549 case LIMB_HUMANOID_TORSO:
550 entity->x += .5 * cos(my->yaw);
551 entity->y += .5 * sin(my->yaw);
552 entity->z += 1.5;
553 entity->pitch = PI / 16;
554 break;
555 // right leg
556 case LIMB_HUMANOID_RIGHTLEG:
557 entity->x -= .5 * cos(my->yaw) - 1 * cos(my->yaw + PI / 2);
558 entity->y -= .5 * sin(my->yaw) - 1 * sin(my->yaw + PI / 2);
559 entity->z += 4;
560 entity->yaw += PI / 16;
561 break;
562 // left leg
563 case LIMB_HUMANOID_LEFTLEG:
564 entity->x -= .5 * cos(my->yaw) + 1 * cos(my->yaw + PI / 2);
565 entity->y -= .5 * sin(my->yaw) + 1 * sin(my->yaw + PI / 2);
566 entity->z += 4;
567 entity->yaw -= PI / 4;
568 entity->roll = PI / 8;
569 break;
570 // right arm
571 case LIMB_HUMANOID_RIGHTARM:
572 entity->x += 1 * cos(my->yaw) + 2 * cos(my->yaw + PI / 2);
573 entity->y += 1 * sin(my->yaw) + 2 * sin(my->yaw + PI / 2);
574 entity->z -= 1;
575 entity->yaw -= my->monsterWeaponYaw;
576 break;
577 // left arm
578 case LIMB_HUMANOID_LEFTARM:
579 entity->x += 1 * cos(my->yaw) - 2 * cos(my->yaw + PI / 2);
580 entity->y += 1 * sin(my->yaw) - 2 * sin(my->yaw + PI / 2);
581 entity->z -= 1;
582 entity->yaw += my->monsterWeaponYaw;
583 break;
584 }
585 }
586 if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
587 {
588 MONSTER_ATTACKTIME++;
589 }
590 else if ( MONSTER_ATTACK == 0 )
591 {
592 MONSTER_ATTACKTIME = 0;
593 }
594 else
595 {
596 // do nothing, don't reset attacktime or increment it.
597 }
598 my->x += cos(my->yaw);
599 my->y += sin(my->yaw);
600 }
601