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