1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: monster_automaton.cpp
5 	Desc: implements all of the automaton 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 #include "magic/magic.hpp"
23 
initAutomaton(Entity * my,Stat * myStats)24 void initAutomaton(Entity* my, Stat* myStats)
25 {
26 	node_t* node;
27 
28 	 //Sprite 467 = Automaton head model
29 	my->initMonster(467);
30 
31 	if ( multiplayer != CLIENT )
32 	{
33 		MONSTER_SPOTSND = 263;
34 		MONSTER_SPOTVAR = 3;
35 		MONSTER_IDLESND = 257;
36 		MONSTER_IDLEVAR = 2;
37 	}
38 	if ( multiplayer != CLIENT && !MONSTER_INIT )
39 	{
40 		if ( myStats != NULL )
41 		{
42 			if ( !myStats->leader_uid )
43 			{
44 				myStats->leader_uid = 0;
45 			}
46 			bool lesserMonster = false;
47 			bool greaterMonster = false;
48 			if ( !strncmp(myStats->name, "damaged automaton", strlen("damaged automaton")) )
49 			{
50 				lesserMonster = true;
51 				myStats->HP = 80;
52 				myStats->MAXHP = 115;
53 				myStats->RANDOM_MAXHP = 0;
54 				myStats->RANDOM_HP = 30;
55 				myStats->OLDHP = myStats->HP;
56 				myStats->STR = 13;
57 				myStats->DEX = 4;
58 				myStats->CON = 8;
59 				myStats->INT = -1;
60 				myStats->PER = 10;
61 				myStats->CHR = -3;
62 				myStats->EXP = 0;
63 				myStats->LVL = 16;
64 			}
65 			else if ( !strncmp(myStats->name, "corrupted automaton", strlen("corrupted automaton")) )
66 			{
67 				greaterMonster = true;
68 				myStats->HP = 150;
69 				myStats->MAXHP = 150;
70 				myStats->RANDOM_MAXHP = 0;
71 				myStats->RANDOM_HP = 0;
72 				myStats->OLDHP = myStats->HP;
73 				myStats->STR = 35;
74 				myStats->DEX = 13;
75 				myStats->CON = 8;
76 				myStats->INT = 10;
77 				myStats->PER = 25;
78 				myStats->CHR = -3;
79 				myStats->LVL = 30;
80 				myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 1;
81 				myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 1;
82 				myStats->EDITOR_ITEMS[ITEM_SLOT_ARMOR] = 1;
83 				myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] = 1;
84 				myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] = 1;
85 			}
86 			// apply random stat increases if set in stat_shared.cpp or editor
87 			setRandomMonsterStats(myStats);
88 
89 			// generate 6 items max, less if there are any forced items from boss variants
90 			int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
91 
92 			// boss variants
93 			//if ( rand() % 50 || my->flags[USERFLAG2] )
94 			//{
95 			//	if ( strncmp(map.name, "Underworld", 10) )
96 			//	{
97 			//		switch ( rand() % 10 )
98 			//		{
99 			//			case 0:
100 			//			case 1:
101 			//				//myStats->weapon = newItem(BRONZE_AXE, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
102 			//				break;
103 			//			case 2:
104 			//			case 3:
105 			//				//myStats->weapon = newItem(BRONZE_SWORD, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
106 			//				break;
107 			//			case 4:
108 			//			case 5:
109 			//				//myStats->weapon = newItem(IRON_SPEAR, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
110 			//				break;
111 			//			case 6:
112 			//			case 7:
113 			//				//myStats->weapon = newItem(IRON_AXE, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
114 			//				break;
115 			//			case 8:
116 			//			case 9:
117 			//				//myStats->weapon = newItem(IRON_SWORD, WORN, -1 + rand() % 2, 1, rand(), false, NULL);
118 			//				break;
119 			//		}
120 			//	}
121 			//}
122 			//else
123 			//{
124 			//	myStats->HP = 100;
125 			//	myStats->MAXHP = 100;
126 			//	strcpy(myStats->name, "Funny Bones");
127 			//	myStats->weapon = newItem(ARTIFACT_AXE, EXCELLENT, 1, 1, rand(), true, NULL);
128 			//	myStats->cloak = newItem(CLOAK_PROTECTION, WORN, 0, 1, 2, true, NULL);
129 			//}
130 
131 			// random effects
132 
133 			// generates equipment and weapons if available from editor
134 			createMonsterEquipment(myStats);
135 
136 			// create any custom inventory items from editor if available
137 			createCustomInventory(myStats, customItemsToGenerate);
138 
139 			// count if any custom inventory items from editor
140 			int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
141 
142 														 // count any inventory items set to default in edtior
143 			int defaultItems = countDefaultItems(myStats);
144 
145 			my->setHardcoreStats(*myStats);
146 
147 			// generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
148 			switch ( defaultItems )
149 			{
150 				case 6:
151 				case 5:
152 				case 4:
153 				case 3:
154 				case 2:
155 				case 1:
156 					break;
157 				default:
158 					break;
159 			}
160 
161 			//give weapon
162 			if ( myStats->weapon == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
163 			{
164 				if ( greaterMonster )
165 				{
166 					switch ( rand() % 4 )
167 					{
168 						case 0:
169 							myStats->weapon = newItem(MAGICSTAFF_LIGHTNING, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
170 							break;
171 						case 1:
172 							myStats->weapon = newItem(CRYSTAL_SPEAR, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
173 							break;
174 						case 2:
175 							myStats->weapon = newItem(SHORTBOW, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
176 							break;
177 						case 3:
178 							myStats->weapon = newItem(CROSSBOW, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
179 							break;
180 						default:
181 							break;
182 					}
183 				}
184 			}
185 
186 			//give helmet
187 			if ( myStats->helmet == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_HELM] == 1 )
188 			{
189 				switch ( rand() % 10 )
190 				{
191 					case 0:
192 					case 1:
193 					case 2:
194 					case 3:
195 					case 4:
196 						break;
197 					case 5:
198 						//myStats->helmet = newItem(LEATHER_HELM, DECREPIT, -1 + rand() % 2, 1, 0, false, NULL);
199 						break;
200 					case 6:
201 					case 7:
202 					case 8:
203 					case 9:
204 						//myStats->helmet = newItem(IRON_HELM, DECREPIT, -1 + rand() % 2, 1, 0, false, NULL);
205 						break;
206 				}
207 			}
208 
209 			//give shield
210 			if ( myStats->shield == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 )
211 			{
212 				if ( myStats->weapon && isRangedWeapon(*myStats->weapon) )
213 				{
214 					my->monsterGenerateQuiverItem(myStats);
215 				}
216 				else
217 				{
218 					if ( greaterMonster )
219 					{
220 						switch ( rand() % 4 )
221 						{
222 							case 0:
223 								myStats->shield = newItem(CRYSTAL_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
224 								break;
225 							case 1:
226 								myStats->shield = newItem(STEEL_SHIELD_RESISTANCE, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
227 								break;
228 							case 2:
229 								myStats->shield = newItem(STEEL_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
230 								break;
231 							case 3:
232 								myStats->shield = newItem(MIRROR_SHIELD, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
233 								break;
234 							default:
235 								break;
236 						}
237 					}
238 				}
239 			}
240 
241 			//give boots
242 			if ( myStats->shoes == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] == 1 )
243 			{
244 				if ( greaterMonster )
245 				{
246 					switch ( rand() % 4 )
247 					{
248 						case 0:
249 						case 1:
250 						case 2:
251 						case 3:
252 							myStats->shoes = newItem(STEEL_BOOTS_LEVITATION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
253 							break;
254 						default:
255 							break;
256 					}
257 				}
258 			}
259 
260 			//give cloak
261 			if ( myStats->cloak == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 )
262 			{
263 				if ( greaterMonster )
264 				{
265 					switch ( rand() % 4 )
266 					{
267 						case 0:
268 							myStats->cloak = newItem(CLOAK_MAGICREFLECTION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
269 							break;
270 						case 1:
271 							myStats->cloak = newItem(CLOAK_PROTECTION, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
272 							break;
273 						case 2:
274 							myStats->cloak = newItem(CLOAK, EXCELLENT, -1 + rand() % 2, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, NULL);
275 							break;
276 						case 3:
277 							break;
278 						default:
279 							break;
280 					}
281 				}
282 			}
283 		}
284 	}
285 
286 	// torso
287 	Entity* entity = newEntity(468, 0, map.entities, nullptr); //Limb entity.
288 	entity->sizex = 4;
289 	entity->sizey = 4;
290 	entity->skill[2] = my->getUID();
291 	entity->flags[PASSABLE] = true;
292 	entity->flags[NOUPDATE] = true;
293 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
294 	entity->focalx = limbs[AUTOMATON][1][0]; // 0
295 	entity->focaly = limbs[AUTOMATON][1][1]; // 0
296 	entity->focalz = limbs[AUTOMATON][1][2]; // 0
297 	entity->behavior = &actAutomatonLimb;
298 	entity->parent = my->getUID();
299 	node = list_AddNodeLast(&my->children);
300 	node->element = entity;
301 	node->deconstructor = &emptyDeconstructor;
302 	node->size = sizeof(Entity*);
303 	my->bodyparts.push_back(entity);
304 
305 	// right leg
306 	entity = newEntity(474, 0, map.entities, nullptr); //Limb entity.
307 	entity->sizex = 4;
308 	entity->sizey = 4;
309 	entity->skill[2] = my->getUID();
310 	entity->flags[PASSABLE] = true;
311 	entity->flags[NOUPDATE] = true;
312 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
313 	entity->focalx = limbs[AUTOMATON][2][0]; // 0
314 	entity->focaly = limbs[AUTOMATON][2][1]; // 0
315 	entity->focalz = limbs[AUTOMATON][2][2]; // 2
316 	entity->behavior = &actAutomatonLimb;
317 	entity->parent = my->getUID();
318 	node = list_AddNodeLast(&my->children);
319 	node->element = entity;
320 	node->deconstructor = &emptyDeconstructor;
321 	node->size = sizeof(Entity*);
322 	my->bodyparts.push_back(entity);
323 
324 	// left leg
325 	entity = newEntity(473, 0, map.entities, nullptr); //Limb entity.
326 	entity->sizex = 4;
327 	entity->sizey = 4;
328 	entity->skill[2] = my->getUID();
329 	entity->flags[PASSABLE] = true;
330 	entity->flags[NOUPDATE] = true;
331 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
332 	entity->focalx = limbs[AUTOMATON][3][0]; // 0
333 	entity->focaly = limbs[AUTOMATON][3][1]; // 0
334 	entity->focalz = limbs[AUTOMATON][3][2]; // 2
335 	entity->behavior = &actAutomatonLimb;
336 	entity->parent = my->getUID();
337 	node = list_AddNodeLast(&my->children);
338 	node->element = entity;
339 	node->deconstructor = &emptyDeconstructor;
340 	node->size = sizeof(Entity*);
341 	my->bodyparts.push_back(entity);
342 
343 	// right arm
344 	entity = newEntity(471, 0, map.entities, nullptr); //Limb entity.
345 	entity->sizex = 4;
346 	entity->sizey = 4;
347 	entity->skill[2] = my->getUID();
348 	entity->flags[PASSABLE] = true;
349 	entity->flags[NOUPDATE] = true;
350 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
351 	entity->focalx = limbs[AUTOMATON][4][0]; // 0
352 	entity->focaly = limbs[AUTOMATON][4][1]; // 0
353 	entity->focalz = limbs[AUTOMATON][4][2]; // 2
354 	entity->behavior = &actAutomatonLimb;
355 	entity->parent = my->getUID();
356 	node = list_AddNodeLast(&my->children);
357 	node->element = entity;
358 	node->deconstructor = &emptyDeconstructor;
359 	node->size = sizeof(Entity*);
360 	my->bodyparts.push_back(entity);
361 
362 	// left arm
363 	entity = newEntity(469, 0, map.entities, nullptr); //Limb entity.
364 	entity->sizex = 4;
365 	entity->sizey = 4;
366 	entity->skill[2] = my->getUID();
367 	entity->flags[PASSABLE] = true;
368 	entity->flags[NOUPDATE] = true;
369 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
370 	entity->focalx = limbs[AUTOMATON][5][0]; // 0
371 	entity->focaly = limbs[AUTOMATON][5][1]; // 0
372 	entity->focalz = limbs[AUTOMATON][5][2]; // 2
373 	entity->behavior = &actAutomatonLimb;
374 	entity->parent = my->getUID();
375 	node = list_AddNodeLast(&my->children);
376 	node->element = entity;
377 	node->deconstructor = &emptyDeconstructor;
378 	node->size = sizeof(Entity*);
379 	my->bodyparts.push_back(entity);
380 
381 	// world weapon
382 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
383 	entity->sizex = 4;
384 	entity->sizey = 4;
385 	entity->skill[2] = my->getUID();
386 	entity->flags[PASSABLE] = true;
387 	entity->flags[NOUPDATE] = true;
388 	entity->flags[INVISIBLE] = true;
389 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
390 	entity->focalx = limbs[AUTOMATON][6][0]; // 2.5
391 	entity->focaly = limbs[AUTOMATON][6][1]; // 0
392 	entity->focalz = limbs[AUTOMATON][6][2]; // 0
393 	entity->behavior = &actAutomatonLimb;
394 	entity->parent = my->getUID();
395 	entity->pitch = .25;
396 	node = list_AddNodeLast(&my->children);
397 	node->element = entity;
398 	node->deconstructor = &emptyDeconstructor;
399 	node->size = sizeof(Entity*);
400 	my->bodyparts.push_back(entity);
401 
402 	// shield
403 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
404 	entity->sizex = 4;
405 	entity->sizey = 4;
406 	entity->skill[2] = my->getUID();
407 	entity->flags[PASSABLE] = true;
408 	entity->flags[NOUPDATE] = true;
409 	entity->flags[INVISIBLE] = true;
410 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
411 	entity->focalx = limbs[AUTOMATON][7][0]; // 2
412 	entity->focaly = limbs[AUTOMATON][7][1]; // 0
413 	entity->focalz = limbs[AUTOMATON][7][2]; // 0
414 	entity->behavior = &actAutomatonLimb;
415 	entity->parent = my->getUID();
416 	node = list_AddNodeLast(&my->children);
417 	node->element = entity;
418 	node->deconstructor = &emptyDeconstructor;
419 	node->size = sizeof(Entity*);
420 	my->bodyparts.push_back(entity);
421 
422 	// cloak
423 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
424 	entity->sizex = 4;
425 	entity->sizey = 4;
426 	entity->skill[2] = my->getUID();
427 	entity->scalex = 1.01;
428 	entity->scaley = 1.01;
429 	entity->scalez = 1.01;
430 	entity->flags[PASSABLE] = true;
431 	entity->flags[NOUPDATE] = true;
432 	entity->flags[INVISIBLE] = true;
433 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
434 	entity->focalx = limbs[AUTOMATON][8][0]; // 0
435 	entity->focaly = limbs[AUTOMATON][8][1]; // 0
436 	entity->focalz = limbs[AUTOMATON][8][2]; // 4
437 	entity->behavior = &actAutomatonLimb;
438 	entity->parent = my->getUID();
439 	node = list_AddNodeLast(&my->children);
440 	node->element = entity;
441 	node->deconstructor = &emptyDeconstructor;
442 	node->size = sizeof(Entity*);
443 	my->bodyparts.push_back(entity);
444 
445 	// helmet
446 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
447 	entity->sizex = 4;
448 	entity->sizey = 4;
449 	entity->skill[2] = my->getUID();
450 	entity->scalex = 1.01;
451 	entity->scaley = 1.01;
452 	entity->scalez = 1.01;
453 	entity->flags[PASSABLE] = true;
454 	entity->flags[NOUPDATE] = true;
455 	entity->flags[INVISIBLE] = true;
456 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
457 	entity->focalx = limbs[AUTOMATON][9][0]; // 0
458 	entity->focaly = limbs[AUTOMATON][9][1]; // 0
459 	entity->focalz = limbs[AUTOMATON][9][2]; // -2
460 	entity->behavior = &actAutomatonLimb;
461 	entity->parent = my->getUID();
462 	node = list_AddNodeLast(&my->children);
463 	node->element = entity;
464 	node->deconstructor = &emptyDeconstructor;
465 	node->size = sizeof(Entity*);
466 	my->bodyparts.push_back(entity);
467 
468 	// mask
469 	entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
470 	entity->sizex = 4;
471 	entity->sizey = 4;
472 	entity->skill[2] = my->getUID();
473 	entity->flags[PASSABLE] = true;
474 	entity->flags[NOUPDATE] = true;
475 	entity->flags[INVISIBLE] = true;
476 	//entity->flags[USERFLAG2] = my->flags[USERFLAG2];
477 	entity->focalx = limbs[AUTOMATON][10][0]; // 0
478 	entity->focaly = limbs[AUTOMATON][10][1]; // 0
479 	entity->focalz = limbs[AUTOMATON][10][2]; // .5
480 	entity->behavior = &actAutomatonLimb;
481 	entity->parent = my->getUID();
482 	node = list_AddNodeLast(&my->children);
483 	node->element = entity;
484 	node->deconstructor = &emptyDeconstructor;
485 	node->size = sizeof(Entity*);
486 	my->bodyparts.push_back(entity);
487 
488 	if ( multiplayer == CLIENT || MONSTER_INIT )
489 	{
490 		return;
491 	}
492 }
493 
actAutomatonLimb(Entity * my)494 void actAutomatonLimb(Entity* my)
495 {
496 	my->actMonsterLimb(true);
497 }
498 
automatonDie(Entity * my)499 void automatonDie(Entity* my)
500 {
501 	my->removeMonsterDeathNodes();
502 
503 	int c;
504 	for ( c = 0; c < 6; c++ )
505 	{
506 		Entity* entity = spawnGib(my);
507 		if ( entity )
508 		{
509 			switch ( c )
510 			{
511 				case 0:
512 					entity->sprite = 467;
513 					break;
514 				case 1:
515 					entity->sprite = 468;
516 					break;
517 				case 2:
518 					entity->sprite = 469;
519 					break;
520 				case 3:
521 					entity->sprite = 470;
522 					break;
523 				case 4:
524 					entity->sprite = 471;
525 					break;
526 				case 5:
527 					entity->sprite = 472;
528 					break;
529 			}
530 			serverSpawnGibForClient(entity);
531 		}
532 	}
533 	playSoundEntity(my, 260 + rand() % 2, 128);
534 	list_RemoveNode(my->mynode);
535 	return;
536 }
537 
538 #define AUTOMATONWALKSPEED .13
539 
automatonMoveBodyparts(Entity * my,Stat * myStats,double dist)540 void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist)
541 {
542 	node_t* node;
543 	Entity* entity = NULL, *entity2 = NULL;
544 	Entity* rightbody = NULL;
545 	Entity* weaponarm = NULL;
546 	int bodypart;
547 	bool wearingring = false;
548 
549 	// set invisibility //TODO: use isInvisible()?
550 	if ( multiplayer != CLIENT )
551 	{
552 		if ( myStats->ring != NULL )
553 			if ( myStats->ring->type == RING_INVISIBILITY )
554 			{
555 				wearingring = true;
556 			}
557 		if ( myStats->cloak != NULL )
558 			if ( myStats->cloak->type == CLOAK_INVISIBILITY )
559 			{
560 				wearingring = true;
561 			}
562 		if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true )
563 		{
564 			my->flags[INVISIBLE] = true;
565 			my->flags[BLOCKSIGHT] = false;
566 			bodypart = 0;
567 			for (node = my->children.first; node != NULL; node = node->next)
568 			{
569 				if ( bodypart < LIMB_HUMANOID_TORSO )
570 				{
571 					bodypart++;
572 					continue;
573 				}
574 				if ( bodypart >= LIMB_HUMANOID_WEAPON )
575 				{
576 					break;
577 				}
578 				entity = (Entity*)node->element;
579 				if ( !entity->flags[INVISIBLE] )
580 				{
581 					entity->flags[INVISIBLE] = true;
582 					serverUpdateEntityBodypart(my, bodypart);
583 				}
584 				bodypart++;
585 			}
586 		}
587 		else
588 		{
589 			my->flags[INVISIBLE] = false;
590 			my->flags[BLOCKSIGHT] = true;
591 			bodypart = 0;
592 			for (node = my->children.first; node != NULL; node = node->next)
593 			{
594 				if ( bodypart < LIMB_HUMANOID_TORSO )
595 				{
596 					bodypart++;
597 					continue;
598 				}
599 				if ( bodypart >= LIMB_HUMANOID_WEAPON )
600 				{
601 					break;
602 				}
603 				entity = (Entity*)node->element;
604 				if ( entity->flags[INVISIBLE] )
605 				{
606 					entity->flags[INVISIBLE] = false;
607 					serverUpdateEntityBodypart(my, bodypart);
608 					serverUpdateEntityFlag(my, INVISIBLE);
609 				}
610 				bodypart++;
611 			}
612 		}
613 
614 		// sleeping
615 		if ( myStats->EFFECTS[EFF_ASLEEP]
616 			&& (my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN) )
617 		{
618 			my->z = 2;
619 			my->pitch = PI / 4;
620 		}
621 		else
622 		{
623 			if ( my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN )
624 			{
625 				my->z = -.5;
626 				my->pitch = 0;
627 				if ( (myStats->HP < 25 && !myStats->EFFECTS[EFF_CONFUSED])
628 					|| (myStats->HP < 50 && !strncmp(myStats->name, "corrupted automaton", strlen("corrupted automaton")))
629 					)
630 				{
631 					// threshold for boom boom
632 					if ( rand() % 4 > 0 ) // 3/4
633 					{
634 						my->monsterSpecialState = AUTOMATON_MALFUNCTION_START;
635 						my->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_AUTOMATON_MALFUNCTION;
636 						serverUpdateEntitySkill(my, 33);
637 
638 						myStats->EFFECTS[EFF_PARALYZED] = true;
639 						myStats->EFFECTS_TIMERS[EFF_PARALYZED] = -1;
640 					}
641 					else
642 					{
643 						myStats->EFFECTS[EFF_CONFUSED] = true;
644 						myStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1;
645 						myStats->EFFECTS[EFF_PARALYZED] = true;
646 						myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 25;
647 						playSoundEntity(my, 263, 128);
648 						spawnMagicEffectParticles(my->x, my->y, my->z, 170);
649 					}
650 				}
651 			}
652 		}
653 	}
654 
655 	if ( my->monsterSpecialState == AUTOMATON_MALFUNCTION_START || my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
656 	{
657 		if ( my->monsterSpecialState == AUTOMATON_MALFUNCTION_START )
658 		{
659 			my->monsterSpecialState = AUTOMATON_MALFUNCTION_RUN;
660 			createParticleExplosionCharge(my, 174, 100, 0.1);
661 		}
662 		if ( multiplayer != CLIENT )
663 		{
664 			if ( my->monsterSpecialTimer <= 0 )
665 			{
666 				my->attack(MONSTER_POSE_AUTOMATON_MALFUNCTION, 0, my);
667 				spawnExplosion(my->x, my->y, my->z);
668 				my->modHP(-1000);
669 			}
670 		}
671 	}
672 
673 	if ( ticks % (2 * TICKS_PER_SECOND) == 0 && rand() % 5 > 0 )
674 	{
675 		playSoundEntityLocal(my, 259, 16);
676 	}
677 
678 	Entity* shieldarm = nullptr;
679 	Entity* helmet = nullptr;
680 	//Move bodyparts
681 	for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++)
682 	{
683 		if ( bodypart < LIMB_HUMANOID_TORSO )
684 		{
685 			if ( multiplayer != CLIENT )
686 			{
687 				if ( bodypart == 0 && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN  )
688 				{
689 					--my->monsterSpecialTimer;
690 					if ( my->monsterSpecialTimer == 100 )
691 					{
692 						playSoundEntity(my, 321, 128);
693 					}
694 					if ( my->monsterSpecialTimer < 80 )
695 					{
696 						my->z += 0.02;
697 						limbAnimateToLimit(my, ANIMATE_PITCH, 0.1, 0.7, true, 0.1);
698 					}
699 					else
700 					{
701 						limbAnimateToLimit(my, ANIMATE_PITCH, 0.01, PI / 5, false, 0.0);
702 					}
703 				}
704 			}
705 			continue;
706 		}
707 		entity = (Entity*)node->element;
708 		entity->x = my->x;
709 		entity->y = my->y;
710 		if ( my->monsterSpecialState != AUTOMATON_MALFUNCTION_START && my->monsterSpecialState != AUTOMATON_MALFUNCTION_RUN )
711 		{
712 			entity->z = my->z;
713 		}
714 		else
715 		{
716 			if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTLEG )
717 			{
718 				entity->z = -.5;
719 			}
720 			else
721 			{
722 				entity->z = my->z;
723 			}
724 		}
725 
726 		if ( (MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP1 || MONSTER_ATTACK == MONSTER_POSE_SPECIAL_WINDUP1) && bodypart == LIMB_HUMANOID_RIGHTARM )
727 		{
728 			// don't let the creatures's yaw move the casting arm
729 		}
730 		else
731 		{
732 			entity->yaw = my->yaw;
733 		}
734 
735 		if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
736 		{
737 			if ( bodypart == LIMB_HUMANOID_LEFTARM && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
738 			{
739 				limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 13 * PI / 8, true, 0.1);
740 			}
741 			else
742 			{
743 				my->humanoidAnimateWalk(entity, node, bodypart, AUTOMATONWALKSPEED, dist, 0.1);
744 			}
745 		}
746 		else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM || bodypart == LIMB_HUMANOID_CLOAK )
747 		{
748 			// left leg, right arm, cloak.
749 			if ( bodypart == LIMB_HUMANOID_RIGHTARM )
750 			{
751 				weaponarm = entity;
752 				if ( MONSTER_ATTACK > 0 )
753 				{
754 					if ( my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1 )
755 					{
756 						Entity* rightbody = nullptr;
757 						// set rightbody to left leg.
758 						node_t* rightbodyNode = list_Node(&my->children, LIMB_HUMANOID_LEFTLEG);
759 						if ( rightbodyNode )
760 						{
761 							rightbody = (Entity*)rightbodyNode->element;
762 						}
763 						else
764 						{
765 							return;
766 						}
767 
768 						// magic wiggle hands
769 						if ( my->monsterAttackTime == 0 )
770 						{
771 							// init rotations
772 							my->monsterArmbended = 0;
773 							my->monsterWeaponYaw = 0;
774 							weaponarm->roll = 0;
775 							weaponarm->pitch = 0;
776 							weaponarm->yaw = my->yaw;
777 							weaponarm->skill[1] = 0;
778 							// casting particles
779 							createParticleDot(my);
780 							// play casting sound
781 							playSoundEntityLocal(my, 170, 32);
782 							if ( multiplayer != CLIENT )
783 							{
784 								myStats->EFFECTS[EFF_PARALYZED] = true;
785 								myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 30;
786 							}
787 						}
788 
789 						double animationYawSetpoint = 0.f;
790 						double animationYawEndpoint = 0.f;
791 						double armSwingRate = 0.f;
792 						double animationPitchSetpoint = 0.f;
793 						double animationPitchEndpoint = 0.f;
794 
795 						switch ( my->monsterSpellAnimation )
796 						{
797 							case MONSTER_SPELLCAST_HUMANOID:
798 								animationYawSetpoint = normaliseAngle2PI(my->yaw + 1 * PI / 8);
799 								animationYawEndpoint = normaliseAngle2PI(my->yaw - 1 * PI / 8);
800 								animationPitchSetpoint = 1 * PI / 8;
801 								animationPitchEndpoint = 15 * PI / 8;
802 								armSwingRate = 0.15;
803 								break;
804 							default:
805 								break;
806 						}
807 
808 						if ( weaponarm->skill[1] == 0 )
809 						{
810 							if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, armSwingRate, animationPitchSetpoint, false, 0.0) )
811 							{
812 								if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, armSwingRate, animationYawSetpoint, false, 0.0) )
813 								{
814 									weaponarm->skill[1] = 1;
815 								}
816 							}
817 						}
818 						else
819 						{
820 							if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -armSwingRate, animationPitchEndpoint, false, 0.0) )
821 							{
822 								if ( limbAnimateToLimit(weaponarm, ANIMATE_YAW, -armSwingRate, animationYawEndpoint, false, 0.0) )
823 								{
824 									weaponarm->skill[1] = 0;
825 								}
826 							}
827 						}
828 
829 						if ( my->monsterAttackTime >= 3 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
830 						{
831 							weaponarm->skill[0] = rightbody->skill[0];
832 							weaponarm->pitch = rightbody->pitch;
833 							my->monsterArmbended = 0;
834 							my->monsterAttack = 0;
835 							if ( multiplayer != CLIENT )
836 							{
837 								spawnMagicEffectParticles(my->x, my->y, my->z / 2, 174);
838 								my->monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_COMPLETE;
839 							}
840 						}
841 					}
842 					else
843 					{
844 						my->handleWeaponArmAttack(entity);
845 					}
846 				}
847 			}
848 			else if ( bodypart == LIMB_HUMANOID_CLOAK )
849 			{
850 				entity->pitch = entity->fskill[0];
851 			}
852 
853 			if ( bodypart == LIMB_HUMANOID_RIGHTARM && my->monsterSpecialState == AUTOMATON_MALFUNCTION_RUN )
854 			{
855 				limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 13 * PI / 8, true, 0.1);
856 			}
857 			else
858 			{
859 				my->humanoidAnimateWalk(entity, node, bodypart, AUTOMATONWALKSPEED, dist, 0.1);
860 			}
861 
862 			if ( bodypart == LIMB_HUMANOID_CLOAK )
863 			{
864 				entity->fskill[0] = entity->pitch;
865 				entity->roll = my->roll - fabs(entity->pitch) / 2;
866 				entity->pitch = 0;
867 			}
868 		}
869 		switch ( bodypart )
870 		{
871 			// torso
872 			case LIMB_HUMANOID_TORSO:
873 				if ( multiplayer != CLIENT )
874 				{
875 					if ( myStats->breastplate == nullptr )
876 					{
877 						entity->sprite = 468;
878 					}
879 					else
880 					{
881 						entity->sprite = itemModel(myStats->breastplate);
882 						entity->scalex = 1;
883 						// shrink the width of the breastplate
884 						entity->scaley = 0.8;
885 					}
886 					if ( multiplayer == SERVER )
887 					{
888 						// update sprites for clients
889 						if ( entity->skill[10] != entity->sprite )
890 						{
891 							entity->skill[10] = entity->sprite;
892 							serverUpdateEntityBodypart(my, bodypart);
893 						}
894 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
895 						{
896 							serverUpdateEntityBodypart(my, bodypart);
897 						}
898 					}
899 				}
900 				else if ( multiplayer == CLIENT )
901 				{
902 					if ( entity->sprite != 468 )
903 					{
904 						entity->scalex = 1;
905 						// shrink the width of the breastplate
906 						entity->scaley = 0.8;
907 					}
908 					else
909 					{
910 						entity->scalex = 1;
911 						entity->scaley = 1;
912 					}
913 				}
914 				my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_TORSO);
915 				break;
916 			// right leg
917 			case LIMB_HUMANOID_RIGHTLEG:
918 				if ( multiplayer != CLIENT )
919 				{
920 					if ( myStats->shoes == nullptr )
921 					{
922 						entity->sprite = 474;
923 					}
924 					else
925 					{
926 						my->setBootSprite(entity, SPRITE_BOOT_RIGHT_OFFSET);
927 					}
928 					if ( multiplayer == SERVER )
929 					{
930 						// update sprites for clients
931 						if ( entity->skill[10] != entity->sprite )
932 						{
933 							entity->skill[10] = entity->sprite;
934 							serverUpdateEntityBodypart(my, bodypart);
935 						}
936 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
937 						{
938 							serverUpdateEntityBodypart(my, bodypart);
939 						}
940 					}
941 				}
942 				my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_RIGHTLEG);
943 				break;
944 			// left leg
945 			case LIMB_HUMANOID_LEFTLEG:
946 				if ( multiplayer != CLIENT )
947 				{
948 					if ( myStats->shoes == nullptr )
949 					{
950 						entity->sprite = 473;
951 					}
952 					else
953 					{
954 						my->setBootSprite(entity, SPRITE_BOOT_LEFT_OFFSET);
955 					}
956 					if ( multiplayer == SERVER )
957 					{
958 						// update sprites for clients
959 						if ( entity->skill[10] != entity->sprite )
960 						{
961 							entity->skill[10] = entity->sprite;
962 							serverUpdateEntityBodypart(my, bodypart);
963 						}
964 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
965 						{
966 							serverUpdateEntityBodypart(my, bodypart);
967 						}
968 					}
969 				}
970 				my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_LEFTLEG);
971 				break;
972 			// right arm
973 			case LIMB_HUMANOID_RIGHTARM:
974 			{
975 				node_t* weaponNode = list_Node(&my->children, 7);
976 				if ( weaponNode )
977 				{
978 					Entity* weapon = (Entity*)weaponNode->element;
979 					if ( MONSTER_ARMBENDED || (weapon->flags[INVISIBLE] && my->monsterAttack == 0) )
980 					{
981 						// if weapon invisible and I'm not attacking, relax arm.
982 						entity->focalx = limbs[AUTOMATON][4][0]; // 0
983 						entity->focaly = limbs[AUTOMATON][4][1]; // 0
984 						entity->focalz = limbs[AUTOMATON][4][2]; // 2
985 						entity->sprite = 471;
986 					}
987 					else
988 					{
989 						// else flex arm.
990 						entity->focalx = limbs[AUTOMATON][4][0] + 1.5; // 1
991 						entity->focaly = limbs[AUTOMATON][4][1] + 0.25; // 0
992 						entity->focalz = limbs[AUTOMATON][4][2] - 1; // 1
993 						entity->sprite = 472;
994 					}
995 				}
996 				my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_RIGHTARM);
997 				entity->yaw += MONSTER_WEAPONYAW;
998 				break;
999 			// left arm
1000 			}
1001 			case LIMB_HUMANOID_LEFTARM:
1002 			{
1003 				shieldarm = entity;
1004 				node_t* shieldNode = list_Node(&my->children, 8);
1005 				if ( shieldNode )
1006 				{
1007 					Entity* shield = (Entity*)shieldNode->element;
1008 					if ( shield->flags[INVISIBLE] )
1009 					{
1010 						// if shield invisible, relax arm.
1011 						entity->sprite = 469;
1012 						entity->focalx = limbs[AUTOMATON][5][0]; // 0
1013 						entity->focaly = limbs[AUTOMATON][5][1]; // 0
1014 						entity->focalz = limbs[AUTOMATON][5][2]; // 2
1015 					}
1016 					else
1017 					{
1018 						// else flex arm.
1019 						entity->sprite = 470;
1020 						entity->focalx = limbs[AUTOMATON][5][0] + 1.5; // 1
1021 						entity->focaly = limbs[AUTOMATON][5][1] - 0.25; // 0
1022 						entity->focalz = limbs[AUTOMATON][5][2] - 1; // 1
1023 					}
1024 				}
1025 				my->setHumanoidLimbOffset(entity, AUTOMATON, LIMB_HUMANOID_LEFTARM);
1026 				if ( my->monsterDefend && my->monsterAttack == 0 )
1027 				{
1028 					MONSTER_SHIELDYAW = PI / 5;
1029 				}
1030 				else
1031 				{
1032 					MONSTER_SHIELDYAW = 0;
1033 				}
1034 				entity->yaw += MONSTER_SHIELDYAW;
1035 				break;
1036 			}
1037 			// weapon
1038 			case LIMB_HUMANOID_WEAPON:
1039 				if ( multiplayer != CLIENT )
1040 				{
1041 					if ( myStats->weapon == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1042 					{
1043 						entity->flags[INVISIBLE] = true;
1044 					}
1045 					else
1046 					{
1047 						entity->sprite = itemModel(myStats->weapon);
1048 						if ( itemCategory(myStats->weapon) == SPELLBOOK )
1049 						{
1050 							entity->flags[INVISIBLE] = true;
1051 						}
1052 						else
1053 						{
1054 							entity->flags[INVISIBLE] = false;
1055 						}
1056 					}
1057 					if ( multiplayer == SERVER )
1058 					{
1059 						// update sprites for clients
1060 						if ( entity->skill[10] != entity->sprite )
1061 						{
1062 							entity->skill[10] = entity->sprite;
1063 							serverUpdateEntityBodypart(my, bodypart);
1064 						}
1065 						if ( entity->skill[11] != entity->flags[INVISIBLE] )
1066 						{
1067 							entity->skill[11] = entity->flags[INVISIBLE];
1068 							serverUpdateEntityBodypart(my, bodypart);
1069 						}
1070 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1071 						{
1072 							serverUpdateEntityBodypart(my, bodypart);
1073 						}
1074 					}
1075 				}
1076 				else
1077 				{
1078 					if ( entity->sprite <= 0 )
1079 					{
1080 						entity->flags[INVISIBLE] = true;
1081 					}
1082 				}
1083 				if ( weaponarm != nullptr )
1084 				{
1085 					my->handleHumanoidWeaponLimb(entity, weaponarm);
1086 				}
1087 				break;
1088 			// shield
1089 			case LIMB_HUMANOID_SHIELD:
1090 				if ( multiplayer != CLIENT )
1091 				{
1092 					if ( myStats->shield == NULL )
1093 					{
1094 						entity->flags[INVISIBLE] = true;
1095 						entity->sprite = 0;
1096 					}
1097 					else
1098 					{
1099 						entity->flags[INVISIBLE] = false;
1100 						entity->sprite = itemModel(myStats->shield);
1101 						if ( itemTypeIsQuiver(myStats->shield->type) )
1102 						{
1103 							entity->handleQuiverThirdPersonModel(*myStats);
1104 						}
1105 					}
1106 					if ( myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1107 					{
1108 						entity->flags[INVISIBLE] = true;
1109 					}
1110 					if ( multiplayer == SERVER )
1111 					{
1112 						// update sprites for clients
1113 						if ( entity->skill[10] != entity->sprite )
1114 						{
1115 							entity->skill[10] = entity->sprite;
1116 							serverUpdateEntityBodypart(my, bodypart);
1117 						}
1118 						if ( entity->skill[11] != entity->flags[INVISIBLE] )
1119 						{
1120 							entity->skill[11] = entity->flags[INVISIBLE];
1121 							serverUpdateEntityBodypart(my, bodypart);
1122 						}
1123 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1124 						{
1125 							serverUpdateEntityBodypart(my, bodypart);
1126 						}
1127 					}
1128 				}
1129 				else
1130 				{
1131 					if ( entity->sprite <= 0 )
1132 					{
1133 						entity->flags[INVISIBLE] = true;
1134 					}
1135 				}
1136 				my->handleHumanoidShieldLimb(entity, shieldarm);
1137 				break;
1138 			// cloak
1139 			case LIMB_HUMANOID_CLOAK:
1140 				if ( multiplayer != CLIENT )
1141 				{
1142 					if ( myStats->cloak == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1143 					{
1144 						entity->flags[INVISIBLE] = true;
1145 					}
1146 					else
1147 					{
1148 						entity->flags[INVISIBLE] = false;
1149 						entity->sprite = itemModel(myStats->cloak);
1150 					}
1151 					if ( multiplayer == SERVER )
1152 					{
1153 						// update sprites for clients
1154 						if ( entity->skill[10] != entity->sprite )
1155 						{
1156 							entity->skill[10] = entity->sprite;
1157 							serverUpdateEntityBodypart(my, bodypart);
1158 						}
1159 						if ( entity->skill[11] != entity->flags[INVISIBLE] )
1160 						{
1161 							entity->skill[11] = entity->flags[INVISIBLE];
1162 							serverUpdateEntityBodypart(my, bodypart);
1163 						}
1164 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1165 						{
1166 							serverUpdateEntityBodypart(my, bodypart);
1167 						}
1168 					}
1169 				}
1170 				else
1171 				{
1172 					if ( entity->sprite <= 0 )
1173 					{
1174 						entity->flags[INVISIBLE] = true;
1175 					}
1176 				}
1177 				entity->x -= cos(my->yaw);
1178 				entity->y -= sin(my->yaw);
1179 				entity->yaw += PI / 2;
1180 				break;
1181 			// helm
1182 			case LIMB_HUMANOID_HELMET:
1183 				helmet = entity;
1184 				entity->focalx = limbs[AUTOMATON][9][0]; // 0
1185 				entity->focaly = limbs[AUTOMATON][9][1]; // 0
1186 				entity->focalz = limbs[AUTOMATON][9][2]; // -2
1187 				entity->pitch = my->pitch;
1188 				entity->roll = 0;
1189 				if ( multiplayer != CLIENT )
1190 				{
1191 					entity->sprite = itemModel(myStats->helmet);
1192 					if ( myStats->helmet == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1193 					{
1194 						entity->flags[INVISIBLE] = true;
1195 					}
1196 					else
1197 					{
1198 						entity->flags[INVISIBLE] = false;
1199 					}
1200 					if ( multiplayer == SERVER )
1201 					{
1202 						// update sprites for clients
1203 						if ( entity->skill[10] != entity->sprite )
1204 						{
1205 							entity->skill[10] = entity->sprite;
1206 							serverUpdateEntityBodypart(my, bodypart);
1207 						}
1208 						if ( entity->skill[11] != entity->flags[INVISIBLE] )
1209 						{
1210 							entity->skill[11] = entity->flags[INVISIBLE];
1211 							serverUpdateEntityBodypart(my, bodypart);
1212 						}
1213 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1214 						{
1215 							serverUpdateEntityBodypart(my, bodypart);
1216 						}
1217 					}
1218 				}
1219 				else
1220 				{
1221 					if ( entity->sprite <= 0 )
1222 					{
1223 						entity->flags[INVISIBLE] = true;
1224 					}
1225 				}
1226 				my->setHelmetLimbOffset(entity);
1227 				break;
1228 			// mask
1229 			case LIMB_HUMANOID_MASK:
1230 				entity->focalx = limbs[AUTOMATON][10][0]; // 0
1231 				entity->focaly = limbs[AUTOMATON][10][1]; // 0
1232 				entity->focalz = limbs[AUTOMATON][10][2]; // .5
1233 				entity->pitch = my->pitch;
1234 				entity->roll = PI / 2;
1235 				if ( multiplayer != CLIENT )
1236 				{
1237 					bool hasSteelHelm = false;
1238 					if ( myStats->helmet )
1239 					{
1240 						if ( myStats->helmet->type == STEEL_HELM
1241 							|| myStats->helmet->type == CRYSTAL_HELM
1242 							|| myStats->helmet->type == ARTIFACT_HELM )
1243 						{
1244 							hasSteelHelm = true;
1245 						}
1246 					}
1247 					if ( myStats->mask == NULL || myStats->EFFECTS[EFF_INVISIBLE] || wearingring || hasSteelHelm ) //TODO: isInvisible()?
1248 					{
1249 						entity->flags[INVISIBLE] = true;
1250 					}
1251 					else
1252 					{
1253 						entity->flags[INVISIBLE] = false;
1254 					}
1255 					if ( myStats->mask != NULL )
1256 					{
1257 						if ( myStats->mask->type == TOOL_GLASSES )
1258 						{
1259 							entity->sprite = 165; // GlassesWorn.vox
1260 						}
1261 						else
1262 						{
1263 							entity->sprite = itemModel(myStats->mask);
1264 						}
1265 					}
1266 					if ( multiplayer == SERVER )
1267 					{
1268 						// update sprites for clients
1269 						if ( entity->skill[10] != entity->sprite )
1270 						{
1271 							entity->skill[10] = entity->sprite;
1272 							serverUpdateEntityBodypart(my, bodypart);
1273 						}
1274 						if ( entity->skill[11] != entity->flags[INVISIBLE] )
1275 						{
1276 							entity->skill[11] = entity->flags[INVISIBLE];
1277 							serverUpdateEntityBodypart(my, bodypart);
1278 						}
1279 						if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1280 						{
1281 							serverUpdateEntityBodypart(my, bodypart);
1282 						}
1283 					}
1284 				}
1285 				else
1286 				{
1287 					if ( entity->sprite <= 0 )
1288 					{
1289 						entity->flags[INVISIBLE] = true;
1290 					}
1291 				}
1292 				if ( entity->sprite != 165 )
1293 				{
1294 					if ( entity->sprite == items[MASK_SHAMAN].index )
1295 					{
1296 						entity->roll = 0;
1297 						my->setHelmetLimbOffset(entity);
1298 						my->setHelmetLimbOffsetWithMask(helmet, entity);
1299 					}
1300 					else
1301 					{
1302 						entity->focalx = limbs[AUTOMATON][10][0] + .35; // .35
1303 						entity->focaly = limbs[AUTOMATON][10][1] - 2; // -2
1304 						entity->focalz = limbs[AUTOMATON][10][2]; // .5
1305 					}
1306 				}
1307 				else
1308 				{
1309 					entity->focalx = limbs[AUTOMATON][10][0] + .25; // .25
1310 					entity->focaly = limbs[AUTOMATON][10][1] - 2.25; // -2.25
1311 					entity->focalz = limbs[AUTOMATON][10][2]; // .5
1312 				}
1313 				break;
1314 		}
1315 	}
1316 	// rotate shield a bit
1317 	node_t* shieldNode = list_Node(&my->children, LIMB_HUMANOID_SHIELD);
1318 	if ( shieldNode )
1319 	{
1320 		Entity* shieldEntity = (Entity*)shieldNode->element;
1321 		if ( shieldEntity->sprite != items[TOOL_TORCH].index && shieldEntity->sprite != items[TOOL_LANTERN].index && shieldEntity->sprite != items[TOOL_CRYSTALSHARD].index )
1322 		{
1323 			shieldEntity->yaw -= PI / 6;
1324 		}
1325 	}
1326 	if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
1327 	{
1328 		MONSTER_ATTACKTIME++;
1329 	}
1330 	else if ( MONSTER_ATTACK == 0 )
1331 	{
1332 		MONSTER_ATTACKTIME = 0;
1333 	}
1334 	else
1335 	{
1336 		// do nothing, don't reset attacktime or increment it.
1337 	}
1338 }
1339 
automatonCanWieldItem(const Item & item) const1340 bool Entity::automatonCanWieldItem(const Item& item) const
1341 {
1342 	Stat* myStats = getStats();
1343 	if ( !myStats )
1344 	{
1345 		return false;
1346 	}
1347 
1348 	if ( monsterAllyIndex >= 0 && (monsterAllyClass != ALLY_CLASS_MIXED || item.interactNPCUid == getUID()) )
1349 	{
1350 		return monsterAllyEquipmentInClass(item);
1351 	}
1352 
1353 	switch ( itemCategory(&item) )
1354 	{
1355 		case WEAPON:
1356 			return true;
1357 		case ARMOR:
1358 			{
1359 				int equipType = checkEquipType(&item);
1360 				if ( equipType == TYPE_HAT )
1361 				{
1362 					return false; //No can wear hats, beep boop
1363 				}
1364 				return true;
1365 			}
1366 		case THROWN:
1367 			return true;
1368 		case TOOL:
1369 			if ( itemTypeIsQuiver(item.type) )
1370 			{
1371 				return true;
1372 			}
1373 			break;
1374 		case MAGICSTAFF:
1375 			return true;
1376 		default:
1377 			return false;
1378 	}
1379 
1380 	return false;
1381 }
1382 
1383 
automatonRecycleItem()1384 void Entity::automatonRecycleItem()
1385 {
1386 	Stat* myStats = getStats();
1387 	if ( !myStats )
1388 	{
1389 		return;
1390 	}
1391 
1392 	if ( this->monsterSpecialTimer > 0 && !(this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_COMPLETE) )
1393 	{
1394 		// if we're on cooldown, skip checking
1395 		// also we need the callback from my->attack() to set monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_COMPLETE once the animation completes.
1396 		return;
1397 	}
1398 
1399 	node_t* node = nullptr;
1400 	node_t* nextnode = nullptr;
1401 	int numItemsHeld = list_Size(&myStats->inventory);
1402 	//messagePlayer(0, "Numitems: %d", numItemsHeld);
1403 	if ( numItemsHeld < 2 )
1404 	{
1405 		return;
1406 	}
1407 
1408 	if ( this->monsterSpecialTimer == 0 && this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_WAITING )
1409 	{
1410 		// put the skill on cooldown.
1411 		this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_AUTOMATON_RECYCLE;
1412 	}
1413 
1414 	int i = 0;
1415 	int itemIndex = 0;
1416 	int chances[10] = { -1 }; // max 10 items
1417 	int matches = 0;
1418 
1419 	// search for valid recyclable items, set chance for valid index.
1420 	for ( node = myStats->inventory.first; node != nullptr; node = nextnode )
1421 	{
1422 		if ( matches >= 10 )
1423 		{
1424 			break;
1425 		}
1426 		nextnode = node->next;
1427 		Item* item = (Item*)node->element;
1428 		if ( item != nullptr )
1429 		{
1430 			if ( (itemCategory(item) == WEAPON || itemCategory(item) == THROWN || itemCategory(item) == ARMOR)
1431 				&& item->status > BROKEN )
1432 			{
1433 				chances[matches] = itemIndex;
1434 				++matches;
1435 			}
1436 		}
1437 		++itemIndex;
1438 	}
1439 
1440 	//messagePlayer(0, "Found %d items", matches);
1441 
1442 	if ( matches < 2 ) // not enough valid items found.
1443 	{
1444 		this->monsterSpecialTimer = 250; // reset cooldown to 5 seconds to check again quicker.
1445 		return;
1446 	}
1447 
1448 	if ( this->monsterSpecialState == AUTOMATON_RECYCLE_ANIMATION_WAITING )
1449 	{
1450 		// this is the first run of the check, we'll execute the casting animation and wait for this to be set to 1 when it ends.
1451 		this->attack(MONSTER_POSE_SPECIAL_WINDUP1, 0, nullptr);
1452 		return;
1453 	}
1454 
1455 	this->monsterSpecialState = AUTOMATON_RECYCLE_ANIMATION_WAITING; // reset my special state after the previous lines.
1456 	int pickItem1 = rand() % matches; // pick random valid item index in inventory
1457 	int pickItem2 = rand() % matches;
1458 	while ( pickItem2 == pickItem1 )
1459 	{
1460 		pickItem2 = rand() % matches; // make sure index 2 is unique
1461 	}
1462 
1463 	itemIndex = 0;
1464 	Item* item1 = nullptr;
1465 	Item* item2 = nullptr;
1466 
1467 	// search again for the 2 indexes, store the items and nodes.
1468 	for ( node = myStats->inventory.first; node != nullptr; node = nextnode )
1469 	{
1470 		nextnode = node->next;
1471 		if ( chances[pickItem1] == itemIndex )
1472 		{
1473 			item1 = (Item*)node->element;
1474 		}
1475 		else if ( chances[pickItem2] == itemIndex )
1476 		{
1477 			item2 = (Item*)node->element;
1478 		}
1479 		++itemIndex;
1480 	}
1481 
1482 	if ( item1 == nullptr || item2 == nullptr )
1483 	{
1484 		return;
1485 	}
1486 
1487 	//messagePlayer(0, "made it past");
1488 
1489 	int maxGoldValue = ((items[item1->type].value + items[item2->type].value) * 2) / 3;
1490 	if ( rand() % 2 == 0 )
1491 	{
1492 		maxGoldValue = ((items[item1->type].value + items[item2->type].value) * 1) / 2;
1493 	}
1494 	int minGoldValue = ((items[item1->type].value + items[item2->type].value) * 1) / 3;
1495 	ItemType type;
1496 	// generate a weapon/armor piece and add it into the inventory.
1497 	switch ( rand() % 10 )
1498 	{
1499 		case 0:
1500 		case 1:
1501 		case 2:
1502 			type = itemTypeWithinGoldValue(WEAPON, minGoldValue, maxGoldValue);
1503 			break;
1504 		case 3:
1505 		case 4:
1506 		case 5:
1507 		case 6:
1508 		case 7:
1509 		case 8:
1510 			type = itemTypeWithinGoldValue(ARMOR, minGoldValue, maxGoldValue);
1511 			break;
1512 		case 9:
1513 			type = itemTypeWithinGoldValue(THROWN, minGoldValue, maxGoldValue);
1514 			break;
1515 		default:
1516 			break;
1517 	}
1518 
1519 	if ( type != GEM_ROCK ) // found an item in category
1520 	{
1521 		Item* item = nullptr;
1522 		// recycle item1 or item2, reduce durability.
1523 		if ( rand() % 2 == 0 )
1524 		{
1525 			item = newItem(type, item1->status, item1->beatitude, 1, rand(), item1->identified, &myStats->inventory);
1526 			item1->status = static_cast<Status>(std::max(0, item1->status - 2));
1527 		}
1528 		else
1529 		{
1530 			item = newItem(type, item2->status, item2->beatitude, 1, rand(), item2->identified, &myStats->inventory);
1531 			item2->status = static_cast<Status>(std::max(0, item2->status - 2));
1532 		}
1533 		// drop newly created item. To pickup if possible or leave behind if overburdened.
1534 		dropItemMonster(item, this, myStats);
1535 		//messagePlayer(0, "Generated %d!", type);
1536 		if ( myStats->leader_uid != 0 )
1537 		{
1538 			for ( int c = 0; c < MAXPLAYERS; ++c )
1539 			{
1540 				if ( players[c] && players[c]->entity )
1541 				{
1542 					Uint32 playerUid = players[c]->entity->getUID();
1543 					if ( playerUid == myStats->leader_uid
1544 						&& (item1->ownerUid == playerUid || item2->ownerUid == playerUid) )
1545 					{
1546 						steamAchievementClient(c, "BARONY_ACH_RECYCLER");
1547 						break;
1548 					}
1549 				}
1550 			}
1551 		}
1552 		//messagePlayer(0, "%d, %d", item1->ownerUid, item2->ownerUid);
1553 	}
1554 
1555 	return;
1556 }
1557