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