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