1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_scarab.cpp
5 Desc: implements all of the scarab 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 "monster.hpp"
17 #include "sound.hpp"
18 #include "items.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22
initScarab(Entity * my,Stat * myStats)23 void initScarab(Entity* my, Stat* myStats)
24 {
25 int c;
26 node_t* node;
27
28 my->sprite = 429; // scarab model
29
30 my->flags[UPDATENEEDED] = true;
31 my->flags[INVISIBLE] = false;
32
33 if ( multiplayer != CLIENT )
34 {
35 MONSTER_SPOTSND = 310;
36 MONSTER_SPOTVAR = 3;
37 MONSTER_IDLESND = 306;
38 MONSTER_IDLEVAR = 2;
39 }
40 if ( multiplayer != CLIENT && !MONSTER_INIT )
41 {
42 if ( myStats != NULL )
43 {
44 if ( !myStats->leader_uid )
45 {
46 myStats->leader_uid = 0;
47 }
48
49 // apply random stat increases if set in stat_shared.cpp or editor
50 setRandomMonsterStats(myStats);
51
52 // generate 6 items max, less if there are any forced items from boss variants
53 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
54
55 // boss variants
56 if ( rand() % 50 == 0 && !my->flags[USERFLAG2] && !myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] )
57 {
58 strcpy(myStats->name, "Xyggi");
59 myStats->HP = 70;
60 myStats->MAXHP = 70;
61 myStats->OLDHP = myStats->HP;
62 myStats->MP = 40;
63 myStats->MAXMP = 40;
64 myStats->STR = -1;
65 myStats->DEX = 20;
66 myStats->CON = 2;
67 myStats->INT = 20;
68 myStats->PER = -2;
69 myStats->CHR = 5;
70 myStats->LVL = 10;
71 my->setEffect(EFF_MAGICREFLECT, true, -1, true); //-1 duration, never expires.
72 newItem(ENCHANTED_FEATHER, EXCELLENT, 0, 1, (ENCHANTED_FEATHER_MAX_DURABILITY - 1), false, &myStats->inventory);
73 myStats->weapon = newItem(SPELLBOOK_COLD, EXCELLENT, 0, 1, 0, false, NULL);
74 customItemsToGenerate = customItemsToGenerate - 1;
75 int c;
76 for ( c = 0; c < 4; ++c )
77 {
78 Entity* entity = summonMonster(SCARAB, my->x, my->y);
79 if ( entity )
80 {
81 entity->parent = my->getUID();
82 }
83 }
84 }
85 // random effects
86
87 // generates equipment and weapons if available from editor
88 createMonsterEquipment(myStats);
89
90 // create any custom inventory items from editor if available
91 createCustomInventory(myStats, customItemsToGenerate);
92
93 // count if any custom inventory items from editor
94 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
95
96 // count any inventory items set to default in edtior
97 int defaultItems = countDefaultItems(myStats);
98
99 my->setHardcoreStats(*myStats);
100
101 int playerCount = 0;
102 for ( c = 0; c < MAXPLAYERS; ++c )
103 {
104 if ( !client_disconnected[c] )
105 {
106 ++playerCount;
107 }
108 }
109
110 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
111 switch ( defaultItems )
112 {
113 case 6:
114 case 5:
115 case 4:
116 case 3:
117 case 2:
118 case 1:
119 if ( rand() % 2 || playerCount > 1 )
120 {
121 if ( rand() % 3 > 0 )
122 {
123 newItem(FOOD_TOMALLEY, static_cast<Status>(DECREPIT + rand() % 4), 0, 1, rand(), false, &myStats->inventory);
124 }
125 else
126 {
127 ItemType gem = GEM_GLASS;
128 switch( rand() % 7 )
129 {
130 case 0:
131 gem = GEM_OPAL;
132 break;
133 case 1:
134 gem = GEM_JADE;
135 break;
136 case 2:
137 gem = GEM_AMETHYST;
138 break;
139 case 3:
140 gem = GEM_FLUORITE;
141 break;
142 case 4:
143 gem = GEM_JETSTONE;
144 break;
145 case 5:
146 gem = GEM_OBSIDIAN;
147 break;
148 default:
149 gem = GEM_GLASS;
150 break;
151 }
152 newItem(gem, static_cast<Status>(DECREPIT + rand()%2), (rand()%4 == 0), 1, rand(), false, &myStats->inventory);
153 }
154 if ( playerCount > 2 )
155 {
156 newItem(FOOD_TOMALLEY, static_cast<Status>(DECREPIT + rand() % 4), 0, 1 + rand() % 2, rand(), false, &myStats->inventory);
157 }
158 }
159 break;
160 default:
161 break;
162 }
163 }
164 }
165
166 // right wing
167 Entity* entity = newEntity(483, 0, map.entities, nullptr); //Limb entity.
168 entity->sizex = 5;
169 entity->sizey = 11;
170 entity->skill[2] = my->getUID();
171 entity->flags[PASSABLE] = true;
172 entity->flags[NOUPDATE] = true;
173 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
174 entity->focalx = limbs[SCARAB][1][0]; // 0
175 entity->focaly = limbs[SCARAB][1][1] + 2; // 0
176 entity->focalz = limbs[SCARAB][1][2]; // 0
177 entity->behavior = &actScarabLimb;
178 entity->parent = my->getUID();
179 node = list_AddNodeLast(&my->children);
180 node->element = entity;
181 node->deconstructor = &emptyDeconstructor;
182 node->size = sizeof(Entity*);
183 my->bodyparts.push_back(entity);
184
185 // left wing
186 entity = newEntity(484, 0, map.entities, nullptr); //Limb entity.
187 entity->sizex = 5;
188 entity->sizey = 11;
189 entity->skill[2] = my->getUID();
190 entity->flags[PASSABLE] = true;
191 entity->flags[NOUPDATE] = true;
192 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
193 entity->focalx = limbs[SCARAB][2][0]; // 0
194 entity->focaly = limbs[SCARAB][2][1] - 2; // 0
195 entity->focalz = limbs[SCARAB][2][2]; // 0
196 entity->behavior = &actScarabLimb;
197 entity->parent = my->getUID();
198 node = list_AddNodeLast(&my->children);
199 node->element = entity;
200 node->deconstructor = &emptyDeconstructor;
201 node->size = sizeof(Entity*);
202 my->bodyparts.push_back(entity);
203 }
204
scarabAnimate(Entity * my,Stat * myStats,double dist)205 void scarabAnimate(Entity* my, Stat* myStats, double dist)
206 {
207 node_t* node;
208 int bodypart;
209 Entity* entity = nullptr;
210
211 // set invisibility //TODO: isInvisible()?
212 if ( multiplayer != CLIENT )
213 {
214 if ( myStats->EFFECTS[EFF_INVISIBLE] == true )
215 {
216 my->flags[INVISIBLE] = true;
217 my->flags[BLOCKSIGHT] = false;
218 bodypart = 0;
219 for ( node = my->children.first; node != nullptr; node = node->next )
220 {
221 if ( bodypart >= 3 )
222 {
223 break;
224 }
225 entity = (Entity*)node->element;
226 if ( !entity->flags[INVISIBLE] )
227 {
228 entity->flags[INVISIBLE] = true;
229 serverUpdateEntityBodypart(my, bodypart);
230 }
231 ++bodypart;
232 }
233 }
234 else
235 {
236 my->flags[INVISIBLE] = false;
237 //my->flags[BLOCKSIGHT] = true; //No. It never blocks sight.
238 bodypart = 0;
239 for ( node = my->children.first; node != NULL; node = node->next )
240 {
241 if ( bodypart < 2 )
242 {
243 continue;
244 }
245 entity = (Entity*)node->element;
246 if ( entity->flags[INVISIBLE] )
247 {
248 entity->flags[INVISIBLE] = false;
249 serverUpdateEntityBodypart(my, bodypart);
250 serverUpdateEntityFlag(my, INVISIBLE);
251 }
252 bodypart++;
253 }
254 }
255 }
256
257 // move legs
258 if ( (ticks % 10 == 0 && dist > 0.1) || (MONSTER_ATTACKTIME == 0 && MONSTER_ATTACK == 1) )
259 {
260 //MONSTER_ATTACKTIME = MONSTER_ATTACK;
261 if ( my->sprite == 429 )
262 {
263 my->sprite = 430;
264 }
265 else
266 {
267 my->sprite = 429;
268 }
269 }
270
271 // move wings
272 for ( bodypart = 0, node = my->children.first; node != nullptr; node = node->next, ++bodypart )
273 {
274 //messagePlayer(0, "bodypart - %d", bodypart);
275 if ( bodypart < 2 )
276 {
277
278 }
279 else
280 {
281 //if ( bodypart == 2 || bodypart == 3 )
282 //{
283 //messagePlayer(0, "bodypart - %d", bodypart);
284 entity = (Entity*)node->element;
285 entity->x = my->x - 1.1 * cos(my->yaw);
286 entity->y = my->y - 1.1 * sin(my->yaw);
287 entity->z = my->z - 3.4;
288 entity->yaw = my->yaw;
289
290 if ( bodypart == 2 )
291 {
292 if ( MONSTER_ATTACK == 1 )
293 {
294 if ( MONSTER_ATTACKTIME == 0 )
295 {
296 entity->pitch = 0;
297 entity->roll = 0;
298 entity->skill[0] = 0;
299 }
300 else
301 {
302 if ( entity->skill[0] == 0 )
303 {
304 if ( MONSTER_ATTACKTIME <= 5 )
305 {
306 entity->pitch = 1;
307 entity->roll = -1;
308 }
309 else
310 {
311 entity->skill[0] = 1;
312 }
313 }
314 else if ( entity->skill[0] == 1 )
315 {
316 entity->pitch -= 0.1;
317 if ( entity->roll < 0)
318 {
319 entity->roll += 0.1;
320 }
321 if ( entity->pitch <= 0 && entity->roll >= 0)
322 {
323 entity->skill[0] = 0;
324 entity->pitch = 0;
325 entity->roll = 0;
326 MONSTER_ATTACK = 0;
327 }
328 }
329 }
330 }
331 else if ( my->monsterState == 1 )
332 {
333 if ( entity->pitch < 0.5 )
334 {
335 entity->pitch += 0.1;
336 }
337 else
338 {
339 entity->pitch = 0.5;
340 }
341
342 if ( entity->roll > -0.2 )
343 {
344 entity->roll -= 0.1;
345 }
346 else
347 {
348 entity->roll = -0.2;
349 }
350 }
351 else if ( my->monsterState == 0 )
352 {
353 if ( entity->pitch > 0 )
354 {
355 entity->pitch -= 0.1;
356 }
357 else
358 {
359 entity->pitch = 0;
360 }
361
362 if ( entity->roll < 0 )
363 {
364 entity->roll += 0.1;
365 }
366 else
367 {
368 entity->roll = 0;
369 }
370 }
371
372 if ( multiplayer == SERVER )
373 {
374 // update sprites for clients
375 if ( entity->skill[10] != entity->sprite )
376 {
377 entity->skill[10] = entity->sprite;
378 serverUpdateEntityBodypart(my, bodypart);
379 }
380 if ( entity->skill[11] != entity->flags[INVISIBLE] )
381 {
382 entity->skill[11] = entity->flags[INVISIBLE];
383 serverUpdateEntityBodypart(my, bodypart);
384 }
385 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
386 {
387 serverUpdateEntityBodypart(my, bodypart);
388 }
389 }
390 }
391 else if ( bodypart == 3 )
392 {
393 if ( MONSTER_ATTACK == 1 )
394 {
395 if ( MONSTER_ATTACKTIME == 0 )
396 {
397 entity->pitch = 0;
398 entity->roll = 0;
399 entity->skill[0] = 0;
400 }
401 else
402 {
403 if ( entity->skill[0] == 0 )
404 {
405 if ( MONSTER_ATTACKTIME <= 5 )
406 {
407 entity->pitch = 1;
408 entity->roll = 1;
409 }
410 else
411 {
412 entity->skill[0] = 1;
413 }
414 }
415 else if ( entity->skill[0] == 1 )
416 {
417 entity->pitch -= 0.1;
418 if ( entity->roll > 0 )
419 {
420 entity->roll -= 0.1;
421 }
422 if ( entity->pitch <= 0 && entity->roll <= 0 )
423 {
424 entity->skill[0] = 0;
425 entity->pitch = 0;
426 entity->roll = 0;
427 MONSTER_ATTACK = 0;
428 }
429 }
430 }
431 }
432 else if ( my->monsterState == 1 )
433 {
434 if ( entity->pitch < 0.5 )
435 {
436 entity->pitch += 0.1;
437 }
438 else
439 {
440 entity->pitch = 0.5;
441 }
442
443 if ( entity->roll < 0.2 )
444 {
445 entity->roll += 0.1;
446 }
447 else
448 {
449 entity->roll = 0.2;
450 }
451 }
452 else if ( my->monsterState == 0 )
453 {
454 if ( entity->pitch > 0 )
455 {
456 entity->pitch -= 0.1;
457 }
458 else
459 {
460 entity->pitch = 0;
461 }
462
463 if ( entity->roll > 0 )
464 {
465 entity->roll -= 0.1;
466 }
467 else
468 {
469 entity->roll = 0;
470 }
471 }
472
473 if ( multiplayer == SERVER )
474 {
475 // update sprites for clients
476 if ( entity->skill[10] != entity->sprite )
477 {
478 entity->skill[10] = entity->sprite;
479 serverUpdateEntityBodypart(my, bodypart);
480 }
481 if ( entity->skill[11] != entity->flags[INVISIBLE] )
482 {
483 entity->skill[11] = entity->flags[INVISIBLE];
484 serverUpdateEntityBodypart(my, bodypart);
485 }
486 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
487 {
488 serverUpdateEntityBodypart(my, bodypart);
489 }
490 }
491 }
492 }
493 }
494
495 if ( MONSTER_ATTACK != 0 )
496 {
497 MONSTER_ATTACKTIME++;
498 }
499 else
500 {
501 MONSTER_ATTACKTIME = 0;
502 }
503 }
504
actScarabLimb(Entity * my)505 void actScarabLimb(Entity* my)
506 {
507 my->actMonsterLimb(true); //Can create light, but can't hold a lightsource.
508 }
509
scarabDie(Entity * my)510 void scarabDie(Entity* my)
511 {
512 int c = 0;
513 for ( c = 0; c < 2; c++ )
514 {
515 Entity* gib = spawnGib(my);
516 serverSpawnGibForClient(gib);
517 }
518
519 my->spawnBlood(212);
520
521 playSoundEntity(my, 308 + rand() % 2, 64); //TODO: Scarab death sound effect.
522 list_RemoveNode(my->mynode);
523 return;
524 }
525