1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: actgeneral.cpp
5 	Desc: very small and general behavior functions
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 "sound.hpp"
16 #include "entity.hpp"
17 #include "net.hpp"
18 #include "collision.hpp"
19 #include "player.hpp"
20 #include "menu.hpp"
21 #include "magic/magic.hpp"
22 #include "interface/interface.hpp"
23 #include "items.hpp"
24 #include "scores.hpp"
25 
26 /*-------------------------------------------------------------------------------
27 
28 	act*
29 
30 	The following functions describe various entity behaviors. All functions
31 	take a pointer to the entities that use them as an argument.
32 
33 -------------------------------------------------------------------------------*/
34 
actAnimator(Entity * my)35 void actAnimator(Entity* my)
36 {
37 	if ( my->skill[4] == 0 )
38 	{
39 		my->skill[4] = 1;
40 		map.tiles[my->skill[0] + (int)my->y * MAPLAYERS + (int)my->x * MAPLAYERS * map.height] -= my->skill[1] - 1;
41 	}
42 
43 	if ( (int)floor(my->x) < 0 || (int)floor(my->x) >= map.width || (int)floor(my->y) < 0 || (int)floor(my->y) >= map.height )
44 	{
45 		list_RemoveNode(my->mynode);
46 		return;
47 	}
48 
49 	my->skill[3]++;
50 	if ( my->skill[3] >= 10 )
51 	{
52 		my->skill[3] = 0;
53 		map.tiles[my->skill[0] + (int)floor(my->y)*MAPLAYERS + (int)floor(my->x)*MAPLAYERS * map.height]++;
54 		my->skill[5]++;
55 		if (my->skill[5] == my->skill[1])
56 		{
57 			my->skill[5] = 0;
58 			map.tiles[my->skill[0] + (int)floor(my->y)*MAPLAYERS + (int)floor(my->x)*MAPLAYERS * map.height] -= my->skill[1];
59 		}
60 	}
61 }
62 
63 #define TESTSPRITES
64 
actRotate(Entity * my)65 void actRotate(Entity* my)
66 {
67 	my->yaw += 0.1;
68 	my->flags[PASSABLE] = true; // this entity should always be passable
69 
70 #ifdef TESTSPRITES
71 	if ( keystatus[SDL_SCANCODE_HOME] )
72 	{
73 		keystatus[SDL_SCANCODE_HOME] = 0;
74 		my->sprite++;
75 		if ( my->sprite >= nummodels )
76 		{
77 			my->sprite = 0;
78 		}
79 		messagePlayer(clientnum, "test sprite: %d", my->sprite);
80 	}
81 	if ( keystatus[SDL_SCANCODE_END] )
82 	{
83 		keystatus[SDL_SCANCODE_END] = 0;
84 		my->sprite += 10;
85 		if ( my->sprite >= nummodels )
86 		{
87 			my->sprite = 0;
88 		}
89 		messagePlayer(clientnum, "test sprite: %d", my->sprite);
90 	}
91 #endif
92 }
93 
94 #define LIQUID_TIMER my->skill[0]
95 #define LIQUID_INIT my->skill[1]
96 #define LIQUID_LAVA my->flags[USERFLAG1]
97 #define LIQUID_LAVANOBUBBLE my->skill[4]
98 
actLiquid(Entity * my)99 void actLiquid(Entity* my)
100 {
101 	// as of 1.0.7 this function is DEPRECATED
102 
103 	list_RemoveNode(my->mynode);
104 	return;
105 
106 	if ( !LIQUID_INIT )
107 	{
108 		LIQUID_INIT = 1;
109 		LIQUID_TIMER = 60 * (rand() % 20);
110 		if ( LIQUID_LAVA )
111 		{
112 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 2, 128);
113 		}
114 	}
115 	LIQUID_TIMER--;
116 	if ( LIQUID_TIMER <= 0 )
117 	{
118 		LIQUID_TIMER = 60 * 20 + 60 * (rand() % 20);
119 		if ( !LIQUID_LAVA )
120 		{
121 			playSoundEntityLocal( my, 135, 32 );
122 		}
123 		else
124 		{
125 			playSoundEntityLocal( my, 155, 100 );
126 		}
127 	}
128 	if ( LIQUID_LAVA && !LIQUID_LAVANOBUBBLE )
129 	{
130 		if ( ticks % 40 == my->getUID() % 40 && rand() % 3 == 0 )
131 		{
132 			int c, j = 1 + rand() % 2;
133 			for ( c = 0; c < j; c++ )
134 			{
135 				Entity* entity = spawnGib( my );
136 				entity->x += rand() % 16 - 8;
137 				entity->y += rand() % 16 - 8;
138 				entity->flags[SPRITE] = true;
139 				entity->sprite = 42;
140 				entity->fskill[3] = 0.01;
141 				double vel = (rand() % 10) / 20.f;
142 				entity->vel_x = vel * cos(entity->yaw);
143 				entity->vel_y = vel * sin(entity->yaw);
144 				entity->vel_z = -.15 - (rand() % 15) / 100.f;
145 				entity->z = 7.5;
146 			}
147 		}
148 	}
149 }
150 
actEmpty(Entity * my)151 void actEmpty(Entity* my)
152 {
153 	// an empty action
154 	// used on clients to permit dead reckoning and other interpolation
155 }
156 
actFurniture(Entity * my)157 void actFurniture(Entity* my)
158 {
159 	if ( !my )
160 	{
161 		return;
162 	}
163 
164 	if ( !my->flags[BURNABLE] )
165 	{
166 		my->flags[BURNABLE] = true;
167 	}
168 
169 	my->actFurniture();
170 }
171 
actFurniture()172 void Entity::actFurniture()
173 {
174 	if ( !furnitureInit )
175 	{
176 		furnitureInit = 1;
177 		if ( furnitureType == FURNITURE_TABLE || FURNITURE_BUNKBED || FURNITURE_BED || FURNITURE_PODIUM )
178 		{
179 			furnitureHealth = 15 + rand() % 5;
180 		}
181 		else
182 		{
183 			furnitureHealth = 4 + rand() % 4;
184 		}
185 		furnitureMaxHealth = furnitureHealth;
186 		flags[BURNABLE] = true;
187 	}
188 	else
189 	{
190 		if ( multiplayer != CLIENT )
191 		{
192 			// burning
193 			if ( flags[BURNING] )
194 			{
195 				if ( ticks % 15 == 0 )
196 				{
197 					furnitureHealth--;
198 				}
199 			}
200 
201 			// furniture mortality :p
202 			if ( furnitureHealth <= 0 )
203 			{
204 				int c;
205 				for ( c = 0; c < 5; c++ )
206 				{
207 					Entity* entity = spawnGib(this);
208 					entity->flags[INVISIBLE] = false;
209 					entity->sprite = 187; // Splinter.vox
210 					entity->x = floor(x / 16) * 16 + 8;
211 					entity->y = floor(y / 16) * 16 + 8;
212 					entity->y += -3 + rand() % 6;
213 					entity->x += -3 + rand() % 6;
214 					entity->z = -5 + rand() % 10;
215 					entity->yaw = (rand() % 360) * PI / 180.0;
216 					entity->pitch = (rand() % 360) * PI / 180.0;
217 					entity->roll = (rand() % 360) * PI / 180.0;
218 					entity->vel_x = (rand() % 10 - 5) / 10.0;
219 					entity->vel_y = (rand() % 10 - 5) / 10.0;
220 					entity->vel_z = -.5;
221 					entity->fskill[3] = 0.04;
222 					serverSpawnGibForClient(entity);
223 				}
224 				playSoundEntity(this, 176, 128);
225 				Entity* entity = uidToEntity(parent);
226 				if ( entity != NULL )
227 				{
228 					entity->itemNotMoving = 0; // drop the item that was on the table
229 					entity->itemNotMovingClient = 0; // clear the client item gravity flag
230 					serverUpdateEntitySkill(entity, 18); //update both the above flags.
231 					serverUpdateEntitySkill(entity, 19);
232 				}
233 				list_RemoveNode(mynode);
234 				return;
235 			}
236 
237 			// using
238 			int i;
239 			for (i = 0; i < MAXPLAYERS; i++)
240 			{
241 				if ( (i == 0 && selectedEntity == this) || (client_selected[i] == this) )
242 				{
243 					if (inrange[i])
244 					{
245 						switch ( furnitureType )
246 						{
247 							case FURNITURE_CHAIR:
248 								messagePlayer(i, language[476]);
249 								break;
250 							case FURNITURE_TABLE:
251 								messagePlayer(i, language[477]);
252 								break;
253 							case FURNITURE_BED:
254 								messagePlayer(i, language[2493]);
255 								break;
256 							case FURNITURE_BUNKBED:
257 								if ( i == 0 || i == 2 )
258 								{
259 									messagePlayer(i, language[2494]);
260 								}
261 								else
262 								{
263 									messagePlayer(i, language[2495]);
264 								}
265 								break;
266 							case FURNITURE_PODIUM:
267 								messagePlayer(i, language[2496]);
268 								break;
269 							default:
270 								messagePlayer(i, language[477]);
271 								break;
272 						}
273 					}
274 				}
275 			}
276 		}
277 	}
278 }
279 
280 // an easter egg
281 #define MCAXE_USED my->skill[0]
282 
actMCaxe(Entity * my)283 void actMCaxe(Entity* my)
284 {
285 	my->yaw += .05;
286 	if ( my->yaw > PI * 2 )
287 	{
288 		my->yaw -= PI * 2;
289 	}
290 	if ( !MCAXE_USED )
291 	{
292 		if ( multiplayer != CLIENT )
293 		{
294 			// use
295 			int i;
296 			for (i = 0; i < MAXPLAYERS; i++)
297 			{
298 				if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
299 				{
300 					if (inrange[i])
301 					{
302 						messagePlayer(i, language[478 + rand() % 5]);
303 						MCAXE_USED = 1;
304 						serverUpdateEntitySkill(my, 0);
305 					}
306 				}
307 			}
308 		}
309 
310 		// bob
311 		my->z -= sin(my->fskill[0] * PI / 180.f);
312 		my->fskill[0] += 6;
313 		if ( my->fskill[0] >= 360 )
314 		{
315 			my->fskill[0] -= 360;
316 		}
317 		my->z += sin(my->fskill[0] * PI / 180.f);
318 	}
319 	else
320 	{
321 		my->z += 1;
322 		if ( my->z > 64 )
323 		{
324 			list_RemoveNode(my->mynode);
325 			return;
326 		}
327 	}
328 }
329 
actStalagFloor(Entity * my)330 void actStalagFloor(Entity* my)
331 {
332 	//TODO: something?
333 	if ( !my )
334 	{
335 		return;
336 	}
337 
338 	my->actStalagFloor();
339 }
340 
actStalagFloor()341 void Entity::actStalagFloor()
342 {
343 
344 }
345 
actStalagCeiling(Entity * my)346 void actStalagCeiling(Entity* my)
347 {
348 	//TODO: something?
349 	if ( !my )
350 	{
351 		return;
352 	}
353 	my->actStalagCeiling();
354 }
355 
actStalagCeiling()356 void Entity::actStalagCeiling()
357 {
358 
359 }
360 
actStalagColumn(Entity * my)361 void actStalagColumn(Entity* my)
362 {
363 	//TODO: something?
364 	if ( !my )
365 	{
366 		return;
367 	}
368 
369 	my->actStalagColumn();
370 }
371 
actStalagColumn()372 void Entity::actStalagColumn()
373 {
374 
375 }
376 
actColumn(Entity * my)377 void actColumn(Entity* my)
378 {
379 	//TODO: something?
380 	if ( !my )
381 	{
382 		return;
383 	}
384 	if ( my->flags[BLOCKSIGHT] ) // stop the compiler optimising into a different entity.
385 	{
386 		my->flags[BLOCKSIGHT] = false;
387 	}
388 	my->actColumn();
389 }
390 
actColumn()391 void Entity::actColumn()
392 {
393 
394 }
395 
actCeilingTile(Entity * my)396 void actCeilingTile(Entity* my)
397 {
398 	if ( !my )
399 	{
400 		return;
401 	}
402 	if ( !my->flags[PASSABLE] )
403 	{
404 		my->flags[PASSABLE] = true;
405 	}
406 	if ( my->flags[BLOCKSIGHT] )
407 	{
408 		my->flags[BLOCKSIGHT] = false;
409 	}
410 }
411 
actPistonBase(Entity * my)412 void actPistonBase(Entity* my)
413 {
414 	if ( !my )
415 	{
416 		return;
417 	}
418 }
419 
actPistonCam(Entity * my)420 void actPistonCam(Entity* my)
421 {
422 	if ( !my )
423 	{
424 		return;
425 	}
426 	my->actPistonCam();
427 }
428 
actPistonCam()429 void Entity::actPistonCam()
430 {
431 	yaw += pistonCamRotateSpeed;
432 	while ( yaw > 2 * PI )
433 	{
434 		yaw -= 2 * PI;
435 	}
436 	while ( yaw < 0 )
437 	{
438 		yaw += 2 * PI;
439 	}
440 	if ( (pistonCamDir == 0 || pistonCamDir == 2) && pistonCamRotateSpeed > 0 )
441 	{
442 		if ( yaw <= PI && yaw >= -pistonCamRotateSpeed + PI )
443 		{
444 			yaw = PI;
445 			pistonCamRotateSpeed = 0;
446 		}
447 	}
448 	--pistonCamTimer;
449 
450 	if ( pistonCamDir == 0 ) // bottom
451 	{
452 		if ( pistonCamTimer <= 0 )
453 		{
454 			pistonCamDir = 1; // up
455 			pistonCamRotateSpeed = 0.2;
456 			pistonCamTimer = rand() % 5 * TICKS_PER_SECOND;
457 		}
458 	}
459 	if ( pistonCamDir == 1 ) // up
460 	{
461 		z -= 0.1;
462 		if ( z < -1.75 )
463 		{
464 			z = -1.75;
465 			pistonCamRotateSpeed *= rand() % 2 == 0 ? -1 : 1;
466 			pistonCamDir = 2; // top
467 		}
468 	}
469 	else if ( pistonCamDir == 2 ) // top
470 	{
471 		if ( pistonCamTimer <= 0 )
472 		{
473 			pistonCamDir = 3; // down
474 			pistonCamRotateSpeed = -0.2;
475 			pistonCamTimer = rand() % 5 * TICKS_PER_SECOND;
476 		}
477 	}
478 	else if ( pistonCamDir == 3 ) // down
479 	{
480 		z += 0.1;
481 		if ( z > 1.75 )
482 		{
483 			z = 1.75;
484 			pistonCamRotateSpeed *= rand() % 2 == 0 ? -1 : 1;
485 			pistonCamDir = 0; // down
486 		}
487 	}
488 }
489 
actFloorDecoration(Entity * my)490 void actFloorDecoration(Entity* my)
491 {
492 	if ( !my )
493 	{
494 		return;
495 	}
496 	if ( !my->flags[PASSABLE] )
497 	{
498 		my->flags[PASSABLE] = true;
499 	}
500 
501 	if ( multiplayer == CLIENT )
502 	{
503 		return;
504 	}
505 
506 	if ( my->floorDecorationInteractText1 == 0 )
507 	{
508 		// no text.
509 		return;
510 	}
511 
512 	// using
513 	int i;
514 	for ( i = 0; i < MAXPLAYERS; i++ )
515 	{
516 		if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
517 		{
518 			if ( inrange[i] )
519 			{
520 				// assemble the string.
521 				char buf[256] = "";
522 				int totalChars = 0;
523 				for ( int i = 8; i < 60; ++i )
524 				{
525 					if ( i == 28 ) // circuit_status
526 					{
527 						continue;
528 					}
529 					if ( my->skill[i] != 0 )
530 					{
531 						for ( int c = 0; c < 4; ++c )
532 						{
533 							buf[totalChars] = static_cast<char>((my->skill[i] >> (c * 8)) & 0xFF);
534 							//messagePlayer(0, "%d %d", i, c);
535 							++totalChars;
536 						}
537 					}
538 				}
539 				if ( buf[totalChars] != '\0' )
540 				{
541 					buf[totalChars] = '\0';
542 				}
543 				std::string output = buf;
544 
545 
546 				size_t foundSignpostCharacter = output.find("#");
547 				if ( foundSignpostCharacter == 0 )
548 				{
549 					output.erase(0, 1);
550 					output.insert(0, "The sign says:\n \"");
551 					output += "\"";
552 				}
553 
554 				size_t foundInputTag = output.find("@in=");
555 				while ( foundInputTag != std::string::npos )
556 				{
557 					output.erase(foundInputTag, strlen("@in="));
558 					std::string impulseStr;
559 					size_t inputTagStrIndex = foundInputTag;
560 					while ( inputTagStrIndex < output.length()
561 						&& output.at(inputTagStrIndex) != ' '
562 						&& output.at(inputTagStrIndex) != '.'
563 						&& output.at(inputTagStrIndex) != ','
564 						&& output.at(inputTagStrIndex) != '\0'
565 						)
566 					{
567 						// usage: @in=IN_USE will get the input key for use
568 						impulseStr = impulseStr + output.at(inputTagStrIndex);
569 						++inputTagStrIndex;
570 					}
571 					for ( int i = 0; i < NUMIMPULSES; ++i )
572 					{
573 						if ( impulseStrings[i].compare(impulseStr) == 0 )
574 						{
575 							output.erase(output.find(impulseStr), impulseStr.length());
576 							std::string inputFormatted = "[";
577 							inputFormatted.append(getInputName(impulses[i])).append("]");
578 							output.insert(foundInputTag, inputFormatted.c_str());
579 							break;
580 						}
581 					}
582 					foundInputTag = output.find("@in=");
583 				}
584 
585 				size_t found = output.find("\\n");
586 				while ( found != std::string::npos )
587 				{
588 					output.erase(found, 2);
589 					output.insert(found, 1, '\n');
590 					found = output.find("\\n");
591 				}
592 				strcpy(buf, output.c_str());
593 				messagePlayer(i, buf);
594 			}
595 		}
596 	}
597 }
598 
actTextSource(Entity * my)599 void actTextSource(Entity* my)
600 {
601 	if ( !my )
602 	{
603 		return;
604 	}
605 	my->actTextSource();
606 }
607 
608 TextSourceScript textSourceScript;
609 
textSourceProcessScriptTag(std::string & input,std::string findTag)610 int TextSourceScript::textSourceProcessScriptTag(std::string& input, std::string findTag)
611 {
612 	size_t foundScriptTag = input.find(findTag);
613 	if ( foundScriptTag != std::string::npos )
614 	{
615 		if ( !containsOperator(findTag.back()) )
616 		{
617 			eraseTag(input, findTag, foundScriptTag);
618 			// end the function here, return non-error as we found the tag.
619 			return 0;
620 		}
621 		input.erase(foundScriptTag, strlen(findTag.c_str()));
622 		if ( input.empty() )
623 		{
624 			return 0;
625 		}
626 
627 		std::string tagValue;
628 
629 		while ( foundScriptTag < input.length()
630 			&& input.at(foundScriptTag) != ' '
631 			&& input.at(foundScriptTag) != '@'
632 			&& input.at(foundScriptTag) != '\0'
633 			)
634 		{
635 			// usage: Hello @p @d 123 will send to distance 123 units away and send message "Hello player "
636 			tagValue = tagValue + input.at(foundScriptTag);
637 			//messagePlayer(clientnum, "%c", output.at(foundDistanceRequirement));
638 			++foundScriptTag;
639 		}
640 		if ( foundScriptTag < input.length() && input.at(foundScriptTag) == ' ' )
641 		{
642 			input.erase(input.find(tagValue), tagValue.length() + 1); // clear trailing space
643 		}
644 		else
645 		{
646 			input.erase(input.find(tagValue), tagValue.length());
647 		}
648 
649 		size_t foundMapReference = tagValue.find(",");
650 		// look for comma or - symbol, e.g @power=15,16 or @power=15-20,16-21
651 		if ( foundMapReference != std::string::npos )
652 		{
653 			if ( findTag.compare("@setvar=") == 0 )
654 			{
655 				std::string variableName = tagValue.substr(0, foundMapReference);
656 				int value = std::stoi(tagValue.substr(foundMapReference + 1, tagValue.length() - foundMapReference));
657 				if ( scriptVariables.find(variableName) != scriptVariables.end() )
658 				{
659 					scriptVariables[variableName] = value;
660 				}
661 				else
662 				{
663 					scriptVariables.insert(std::make_pair(variableName, value));
664 				}
665 				return 0;
666 			}
667 
668 
669 			std::string x_str = tagValue.substr(0, foundMapReference);
670 			std::string y_str = tagValue.substr(foundMapReference + 1, tagValue.length() - foundMapReference);
671 			int x1 = 0;
672 			int x2 = 0;
673 			int y1 = 0;
674 			int y2 = 0;
675 			size_t foundMapRange = x_str.find("-");
676 			if ( foundMapRange != std::string::npos )
677 			{
678 				// found map range reference.
679 				x1 = std::stoi(x_str.substr(0, foundMapRange));
680 				x2 = std::stoi(x_str.substr(foundMapRange + 1, x_str.length() - foundMapRange));
681 			}
682 			else
683 			{
684 				x1 = std::stoi(x_str);
685 				x2 = x1;
686 			}
687 			foundMapRange = y_str.find("-");
688 			if ( foundMapRange != std::string::npos )
689 			{
690 				// found map range reference.
691 				y1 = std::stoi(y_str.substr(0, foundMapRange));
692 				y2 = std::stoi(y_str.substr(foundMapRange + 1, y_str.length() - foundMapRange));
693 			}
694 			else
695 			{
696 				y1 = std::stoi(y_str);
697 				y2 = y1;
698 			}
699 
700 			x1 = std::min(std::max(0, x1), (int)map.width - 1);
701 			y1 = std::min(std::max(0, y1), (int)map.height - 1);
702 			x2 = std::min(std::max(x1, x2), (int)map.width - 1);
703 			y2 = std::min(std::max(y1, y2), (int)map.height - 1);
704 
705 			return (x1 & 0xFF) + ((x2 & 0xFF) << 8) + ((y1 & 0xFF) << 16) + ((y2 & 0xFF) << 24);
706 		}
707 		else
708 		{
709 			size_t foundSeperator = tagValue.find(";");
710 			if ( foundSeperator != std::string::npos )
711 			{
712 				std::pair<int, int> param1 = std::make_pair(0, 0);
713 				std::pair<int, int> param2 = std::make_pair(0, 0);
714 				std::string first_str = tagValue.substr(0, foundSeperator);
715 				std::string second_str = tagValue.substr(foundSeperator + 1, tagValue.length() - foundSeperator);
716 				size_t foundRange = first_str.find("-");
717 				if ( foundRange != std::string::npos )
718 				{
719 					// found range reference.
720 					param1.first = std::stoi(first_str.substr(0, foundRange));
721 					param1.second = std::stoi(first_str.substr(foundRange + 1, first_str.length() - foundRange));
722 				}
723 				else
724 				{
725 					param1.first = std::stoi(first_str);
726 					param1.second = param1.first;
727 				}
728 				foundRange = second_str.find("-");
729 				if ( foundRange != std::string::npos )
730 				{
731 					// found range reference.
732 					param2.first = std::stoi(second_str.substr(0, foundRange));
733 					param2.second = std::stoi(second_str.substr(foundRange + 1, second_str.length() - foundRange));
734 				}
735 				else
736 				{
737 					param2.first = std::stoi(second_str);
738 					param2.second = param2.first;
739 				}
740 
741 				param1.second = std::max(param1.first, param1.second);
742 				param2.second = std::max(param2.first, param2.second);
743 
744 				return (param1.first & 0xFF) + ((param1.second & 0xFF) << 8) + ((param2.first & 0xFF) << 16) + ((param2.second & 0xFF) << 24);
745 			}
746 			else
747 			{
748 				size_t foundRange = tagValue.find("-");
749 				if ( foundRange != std::string::npos )
750 				{
751 					std::pair<int, int> param1 = std::make_pair(0, 0);
752 					// found range reference.
753 					std::string first_str = tagValue.substr(0, foundRange);
754 					std::string second_str = tagValue.substr(foundRange + 1, tagValue.length() - foundRange);
755 
756 					param1.first = std::stoi(first_str);
757 					param1.second = std::stoi(second_str);
758 					param1.second = std::max(param1.first, param1.second);
759 					return (param1.first & 0xFF) + ((param1.second & 0xFF) << 8);
760 				}
761 			}
762 		}
763 
764 		if ( !tagValue.compare("all") )
765 		{
766 			int x1 = 0;
767 			int x2 = map.width - 1;
768 			int y1 = 0;
769 			int y2 = map.height - 1;
770 			return (x1 & 0xFF) + ((x2 & 0xFF) << 8) + ((y1 & 0xFF) << 16) + ((y2 & 0xFF) << 24);
771 		}
772 		else if ( findTag.compare("@attachto=") == 0 || findTag.compare("@reattachto=") == 0 )
773 		{
774 			if ( !tagValue.compare("monsters") )
775 			{
776 				return TO_MONSTERS;
777 			}
778 			else if ( !tagValue.compare("items") )
779 			{
780 				return TO_ITEMS;
781 			}
782 			else if ( !tagValue.compare("players") )
783 			{
784 				return TO_PLAYERS;
785 			}
786 			else
787 			{
788 				for ( int i = 0; i < NUMMONSTERS; ++i )
789 				{
790 					if ( !tagValue.compare(monstertypename[i]) )
791 					{
792 						return TO_NOTHING + i;
793 					}
794 				}
795 				return k_ScriptError;
796 			}
797 		}
798 		else if ( findTag.compare("@triggerif=") == 0 )
799 		{
800 			if ( !tagValue.compare("attached#dies") )
801 			{
802 				return TRIGGER_ATTACHED_ISREMOVED;
803 			}
804 			else if ( !tagValue.compare("attached#exists") )
805 			{
806 				return TRIGGER_ATTACHED_EXISTS;
807 			}
808 			else if ( !tagValue.compare("attached#invisible") )
809 			{
810 				return TRIGGER_ATTACHED_INVIS;
811 			}
812 			else if ( !tagValue.compare("attached#visible") )
813 			{
814 				return TRIGGER_ATTACHED_VISIBLE;
815 			}
816 			else if ( !tagValue.compare("always") )
817 			{
818 				return TRIGGER_ATTACHED_ALWAYS;
819 			}
820 			else
821 			{
822 				return k_ScriptError;
823 			}
824 		}
825 
826 		return std::stoi(tagValue);
827 	}
828 	return k_ScriptError;
829 }
830 
handleTextSourceScript(Entity & src,std::string input)831 void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
832 {
833 	bool statOnlyUpdateNeeded = false;
834 
835 	std::vector<std::string> tokens;
836 	std::string searchString = input;
837 	size_t findToken = searchString.find("@");
838 	while ( findToken != std::string::npos )
839 	{
840 		std::string token = "@";
841 		++findToken;
842 		while ( findToken < searchString.length()
843 			&& searchString.at(findToken) != ' '
844 			&& searchString.at(findToken) != '@'
845 			&& searchString.at(findToken) != '\0'
846 			)
847 		{
848 			token = token + searchString.at(findToken);
849 			++findToken;
850 		}
851 		searchString.erase(searchString.find(token), token.length());
852 		tokens.push_back(token);
853 		findToken = searchString.find("@");
854 	}
855 
856 	printlog("[SCRIPT]: Starting Execution...");
857 	std::string executionLog = "[SCRIPT]: Processed tokens:";
858 	std::vector<Entity*> attachedEntities = textSourceScript.getScriptAttachedEntities(src);
859 
860 	for ( auto it = tokens.begin(); it != tokens.end(); ++it )
861 	{
862 		executionLog.append(" ").append(*it);
863 
864 		bool processOnAttachedEntity = false;
865 		size_t foundAttachedTag = (*it).find("@attached.");
866 		if ( foundAttachedTag != std::string::npos )
867 		{
868 			processOnAttachedEntity = true;
869 			// erase "attached." in token, pass the information on to the tag contents.
870 			(*it).erase(foundAttachedTag + 1, strlen("attached."));
871 			size_t foundAttachedTag = input.find("@attached.");
872 			if ( foundAttachedTag != std::string::npos )
873 			{
874 				input.erase(foundAttachedTag + 1, strlen("attached."));
875 			}
876 		}
877 
878 		if ( (*it).find("@reattachto=") != std::string::npos )
879 		{
880 			int attachTo = textSourceProcessScriptTag(input, "@reattachto=");
881 			if ( attachTo == k_ScriptError )
882 			{
883 				return;
884 			}
885 			attachedEntities.clear();
886 			list_FreeAll(&src.children); // reattach all the entities again.
887 
888 			textSourceScript.setScriptType(src.textSourceIsScript, textSourceScript.SCRIPT_ATTACHED);
889 			int x1 = static_cast<int>(src.x / 16); // default to just whatever this script is sitting on.
890 			int x2 = static_cast<int>(src.x / 16);
891 			int y1 = static_cast<int>(src.y / 16);
892 			int y2 = static_cast<int>(src.y / 16);
893 			if ( input.find("@attachrange=") != std::string::npos )
894 			{
895 				int result = textSourceProcessScriptTag(input, "@attachrange=");
896 				if ( result != k_ScriptError )
897 				{
898 					x1 = result & 0xFF;
899 					x2 = (result >> 8) & 0xFF;
900 					y1 = (result >> 16) & 0xFF;
901 					y2 = (result >> 24) & 0xFF;
902 				}
903 			}
904 			textSourceScript.setAttachedToEntityType(src.textSourceIsScript, attachTo);
905 			for ( node_t* node = map.entities->first; node; node = node->next )
906 			{
907 				Entity* entity = (Entity*)node->element;
908 				if ( entity )
909 				{
910 					if ( (entity->behavior == &actMonster && attachTo == TO_MONSTERS)
911 						|| (entity->behavior == &actPlayer && attachTo == TO_PLAYERS)
912 						|| (entity->behavior == &actItem && attachTo == TO_ITEMS)
913 						|| (entity->behavior == &actMonster
914 							&& attachTo >= TO_NOTHING && attachTo <= TO_DUMMYBOT
915 							&& entity->getRace() == (attachTo - TO_NOTHING)) )
916 					{
917 						// found our entity.
918 					}
919 					else
920 					{
921 						continue;
922 					}
923 					int findx = static_cast<int>(entity->x) >> 4;
924 					int findy = static_cast<int>(entity->y) >> 4;
925 					if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
926 					{
927 						node_t* node = list_AddNodeLast(&src.children);
928 						node->deconstructor = &defaultDeconstructor;
929 						Uint32* entityUid = (Uint32*)(malloc(sizeof(Uint32)));
930 						node->element = entityUid;
931 						node->size = sizeof(Uint32);
932 						*entityUid = entity->getUID();
933 					}
934 				}
935 			}
936 			attachedEntities = textSourceScript.getScriptAttachedEntities(src);
937 		}
938 		else if ( (*it).find("@clrplayer") != std::string::npos )
939 		{
940 			int result = textSourceProcessScriptTag(input, "@clrplayer");
941 			if ( result != k_ScriptError )
942 			{
943 				std::vector<Entity*> applyToEntities;
944 				if ( processOnAttachedEntity )
945 				{
946 					for ( auto entity : attachedEntities )
947 					{
948 						if ( entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]] )
949 						{
950 							applyToEntities.push_back(entity);
951 						}
952 					}
953 				}
954 				else
955 				{
956 					for ( int c = 0; c < MAXPLAYERS; ++c )
957 					{
958 						if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
959 						{
960 							applyToEntities.push_back(players[c]->entity);
961 						}
962 					}
963 				}
964 
965 				for ( auto entity : applyToEntities )
966 				{
967 					Stat* stats = entity->getStats();
968 					int player = entity->skill[2];
969 					if ( player == 0 )
970 					{
971 						playerClearInventory(true);
972 					}
973 					else
974 					{
975 						// other players
976 						stats->freePlayerEquipment();
977 						stats->clearStats();
978 						updateClientInformation(player, true, true, TextSourceScript::CLIENT_UPDATE_ALL);
979 					}
980 				}
981 			}
982 		}
983 		else if ( (*it).find("@class=") != std::string::npos )
984 		{
985 			int result = textSourceProcessScriptTag(input, "@class=");
986 			if ( result != k_ScriptError && result >= CLASS_BARBARIAN && result < NUMCLASSES )
987 			{
988 				if ( !enabledDLCPack1 && result >= CLASS_CONJURER && result <= CLASS_BREWER )
989 				{
990 					result = CLASS_BARBARIAN;
991 				}
992 				if ( !enabledDLCPack2 && result >= CLASS_MACHINIST && result <= CLASS_HUNTER )
993 				{
994 					result = CLASS_BARBARIAN;
995 				}
996 
997 				std::vector<Entity*> applyToEntities;
998 				if ( processOnAttachedEntity )
999 				{
1000 					for ( auto entity : attachedEntities )
1001 					{
1002 						if ( entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]] )
1003 						{
1004 							applyToEntities.push_back(entity);
1005 						}
1006 					}
1007 				}
1008 				else
1009 				{
1010 					for ( int c = 0; c < MAXPLAYERS; ++c )
1011 					{
1012 						if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
1013 						{
1014 							applyToEntities.push_back(players[c]->entity);
1015 						}
1016 					}
1017 				}
1018 
1019 				for ( auto entity : applyToEntities )
1020 				{
1021 					int player = entity->skill[2];
1022 					client_classes[player] = result;
1023 					bool oldIntro = intro;
1024 					intro = true;
1025 					initClass(player);
1026 					intro = oldIntro;
1027 				}
1028 
1029 				if ( !applyToEntities.empty() )
1030 				{
1031 					for ( int c = 1; c < MAXPLAYERS; ++c )
1032 					{
1033 						updateClientInformation(c, false, false, TextSourceScript::CLIENT_UPDATE_CLASS);
1034 					}
1035 				}
1036 			}
1037 		}
1038 		else if ( (*it).find("@clrstats") != std::string::npos )
1039 		{
1040 			int result = textSourceProcessScriptTag(input, "@clrstats");
1041 			if ( result != k_ScriptError )
1042 			{
1043 				std::vector<Entity*> applyToEntities;
1044 				if ( processOnAttachedEntity )
1045 				{
1046 					for ( auto entity : attachedEntities )
1047 					{
1048 						if ( entity->behavior != &actPlayer && entity->behavior != actMonster )
1049 						{
1050 							continue;
1051 						}
1052 						if ( entity->behavior == &actPlayer && !client_disconnected[entity->skill[2]] )
1053 						{
1054 							statOnlyUpdateNeeded = true;
1055 						}
1056 						applyToEntities.push_back(entity);
1057 					}
1058 				}
1059 				else
1060 				{
1061 					for ( int c = 0; c < MAXPLAYERS; ++c )
1062 					{
1063 						if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
1064 						{
1065 							applyToEntities.push_back(players[c]->entity);
1066 						}
1067 					}
1068 				}
1069 
1070 				for ( auto entity : applyToEntities )
1071 				{
1072 					if ( entity->behavior == &actPlayer )
1073 					{
1074 						statOnlyUpdateNeeded = true;
1075 					}
1076 					Stat* stats = entity->getStats();
1077 					if ( stats )
1078 					{
1079 						stats->HP = DEFAULT_HP;
1080 						stats->MAXHP = DEFAULT_HP;
1081 						stats->OLDHP = stats->HP;
1082 						stats->MP = DEFAULT_MP;
1083 						stats->MAXMP = DEFAULT_MP;
1084 						stats->STR = 0;
1085 						stats->DEX = 0;
1086 						stats->CON = 0;
1087 						stats->INT = 0;
1088 						stats->PER = 0;
1089 						stats->CHR = 0;
1090 						stats->LVL = 1;
1091 						stats->GOLD = 0;
1092 						stats->EXP = 0;
1093 					}
1094 				}
1095 			}
1096 		}
1097 		else if ( (*it).find("@hunger=") != std::string::npos )
1098 		{
1099 			int result = textSourceProcessScriptTag(input, "@hunger=");
1100 			if ( result != k_ScriptError )
1101 			{
1102 				std::vector<Entity*> applyToEntities;
1103 				if ( processOnAttachedEntity )
1104 				{
1105 					for ( auto entity : attachedEntities )
1106 					{
1107 						if ( entity->behavior != &actPlayer )
1108 						{
1109 							continue;
1110 						}
1111 						applyToEntities.push_back(entity);
1112 					}
1113 				}
1114 				else
1115 				{
1116 					for ( int c = 0; c < MAXPLAYERS; ++c )
1117 					{
1118 						if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
1119 						{
1120 							applyToEntities.push_back(players[c]->entity);
1121 						}
1122 					}
1123 				}
1124 
1125 				for ( auto entity : applyToEntities )
1126 				{
1127 					int player = -1;
1128 					if ( entity->behavior != &actPlayer )
1129 					{
1130 						continue;
1131 					}
1132 					player = entity->skill[2];
1133 					if ( stats[player] && !client_disconnected[player] )
1134 					{
1135 						stats[player]->HUNGER = std::min(result, 2000);
1136 						if ( player != clientnum )
1137 						{
1138 							updateClientInformation(player, false, false, TextSourceScript::CLIENT_UPDATE_HUNGER);
1139 						}
1140 					}
1141 				}
1142 			}
1143 		}
1144 		else if ( (*it).find("@nextlevel=") != std::string::npos )
1145 		{
1146 			int result = textSourceProcessScriptTag(input, "@nextlevel=");
1147 			if ( result != k_ScriptError )
1148 			{
1149 				loadnextlevel = true;
1150 				skipLevelsOnLoad = result;
1151 			}
1152 		}
1153 		else if ( (*it).find("@power=") != std::string::npos )
1154 		{
1155 			int result = textSourceProcessScriptTag(input, "@power=");
1156 			if ( result != k_ScriptError )
1157 			{
1158 				int x1 = result & 0xFF;
1159 				int x2 = (result >> 8) & 0xFF;
1160 				int y1 = (result >> 16) & 0xFF;
1161 				int y2 = (result >> 24) & 0xFF;
1162 				bool foundExisting = false;
1163 				std::unordered_set<int> plateSpots;
1164 				for ( node_t* node = map.entities->first; node; node = node->next )
1165 				{
1166 					Entity* pressurePlate = (Entity*)node->element;
1167 					if ( pressurePlate && pressurePlate->behavior == &actTrapPermanent )
1168 					{
1169 						int findx = static_cast<int>(pressurePlate->x) >> 4;
1170 						int findy = static_cast<int>(pressurePlate->y) >> 4;
1171 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1172 						{
1173 							if ( pressurePlate->skill[0] == 0 )
1174 							{
1175 								pressurePlate->toggleSwitch();
1176 							}
1177 							plateSpots.insert(static_cast<int>(pressurePlate->x / 16) + map.width * static_cast<int>(pressurePlate->y / 16));
1178 						}
1179 					}
1180 				}
1181 				for ( int x = x1; x <= x2; ++x )
1182 				{
1183 					for ( int y = y1; y <= y2; ++y )
1184 					{
1185 						if ( plateSpots.find(x + map.width * y) == plateSpots.end() )
1186 						{
1187 							Entity* pressurePlate = newEntity(-1, 1, map.entities, nullptr);
1188 							pressurePlate->sizex = 2;
1189 							pressurePlate->sizey = 2;
1190 							pressurePlate->x = x * 16 + 8;
1191 							pressurePlate->y = y * 16 + 8;
1192 							pressurePlate->behavior = &actTrapPermanent;
1193 							pressurePlate->skill[1] = 1;
1194 							pressurePlate->flags[SPRITE] = true;
1195 							pressurePlate->flags[INVISIBLE] = true;
1196 							pressurePlate->flags[PASSABLE] = true;
1197 							pressurePlate->flags[NOUPDATE] = true;
1198 							TileEntityList.addEntity(*pressurePlate); // make sure new nodes are added to the tile list to properly update neighbors.
1199 							pressurePlate->toggleSwitch();
1200 						}
1201 					}
1202 				}
1203 			}
1204 		}
1205 		else if ( (*it).find("@unpower=") != std::string::npos )
1206 		{
1207 			int result = textSourceProcessScriptTag(input, "@unpower=");
1208 			if ( result != k_ScriptError )
1209 			{
1210 				int x1 = result & 0xFF;
1211 				int x2 = (result >> 8) & 0xFF;
1212 				int y1 = (result >> 16) & 0xFF;
1213 				int y2 = (result >> 24) & 0xFF;
1214 				for ( node_t* node = map.entities->first; node; node = node->next )
1215 				{
1216 					Entity* pressurePlate = (Entity*)node->element;
1217 					if ( pressurePlate && pressurePlate->behavior == &actTrapPermanent )
1218 					{
1219 						int findx = static_cast<int>(pressurePlate->x) >> 4;
1220 						int findy = static_cast<int>(pressurePlate->y) >> 4;
1221 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1222 						{
1223 							if ( pressurePlate->skill[0] != 0 )
1224 							{
1225 								pressurePlate->toggleSwitch();
1226 								pressurePlate->skill[1] = 1; // can't be interacted.
1227 							}
1228 						}
1229 					}
1230 				}
1231 			}
1232 		}
1233 		else if ( (*it).find("@freezemonsters=") != std::string::npos )
1234 		{
1235 			int result = textSourceProcessScriptTag(input, "@freezemonsters=");
1236 			if ( result != k_ScriptError )
1237 			{
1238 				int x1 = result & 0xFF;
1239 				int x2 = (result >> 8) & 0xFF;
1240 				int y1 = (result >> 16) & 0xFF;
1241 				int y2 = (result >> 24) & 0xFF;
1242 				if ( processOnAttachedEntity )
1243 				{
1244 					for ( auto entity : attachedEntities )
1245 					{
1246 						if ( entity->behavior != &actMonster )
1247 						{
1248 							continue;
1249 						}
1250 						int findx = static_cast<int>(entity->x) >> 4;
1251 						int findy = static_cast<int>(entity->y) >> 4;
1252 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1253 						{
1254 							entity->setEffect(EFF_STUNNED, true, -1, false);
1255 						}
1256 					}
1257 				}
1258 				else
1259 				{
1260 					for ( node_t* node = map.creatures->first; node; node = node->next )
1261 					{
1262 						Entity* entity = (Entity*)node->element;
1263 						if ( entity && entity->behavior == &actMonster )
1264 						{
1265 							int findx = static_cast<int>(entity->x) >> 4;
1266 							int findy = static_cast<int>(entity->y) >> 4;
1267 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1268 							{
1269 								entity->setEffect(EFF_STUNNED, true, -1, false);
1270 							}
1271 						}
1272 					}
1273 				}
1274 			}
1275 		}
1276 		else if ( (*it).find("@unfreezemonsters=") != std::string::npos )
1277 		{
1278 			int result = textSourceProcessScriptTag(input, "@unfreezemonsters=");
1279 			if ( result != k_ScriptError )
1280 			{
1281 				int x1 = result & 0xFF;
1282 				int x2 = (result >> 8) & 0xFF;
1283 				int y1 = (result >> 16) & 0xFF;
1284 				int y2 = (result >> 24) & 0xFF;
1285 				if ( processOnAttachedEntity )
1286 				{
1287 					for ( auto entity : attachedEntities )
1288 					{
1289 						if ( entity->behavior != &actMonster )
1290 						{
1291 							continue;
1292 						}
1293 						int findx = static_cast<int>(entity->x) >> 4;
1294 						int findy = static_cast<int>(entity->y) >> 4;
1295 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1296 						{
1297 							entity->setEffect(EFF_STUNNED, false, 0, false);
1298 						}
1299 					}
1300 				}
1301 				else
1302 				{
1303 					for ( node_t* node = map.creatures->first; node; node = node->next )
1304 					{
1305 						Entity* entity = (Entity*)node->element;
1306 						if ( entity && entity->behavior == &actMonster )
1307 						{
1308 							int findx = static_cast<int>(entity->x) >> 4;
1309 							int findy = static_cast<int>(entity->y) >> 4;
1310 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1311 							{
1312 								entity->setEffect(EFF_STUNNED, false, 0, false);
1313 							}
1314 						}
1315 					}
1316 				}
1317 			}
1318 		}
1319 		else if ( (*it).find("@seteffect=") != std::string::npos )
1320 		{
1321 			int result = textSourceProcessScriptTag(input, "@seteffect=");
1322 			if ( result != k_ScriptError )
1323 			{
1324 				int effect = result & 0xFF;
1325 				int effectEndRange = (result >> 8) & 0xFF;
1326 				int duration = (result >> 16) & 0xFF;
1327 				int durationEndRange = (result >> 24) & 0xFF;
1328 				duration = duration + rand() % (std::max(1, (durationEndRange - duration)));
1329 				if ( processOnAttachedEntity )
1330 				{
1331 					for ( auto entity : attachedEntities )
1332 					{
1333 						if ( entity->behavior != &actMonster && entity->behavior != &actPlayer )
1334 						{
1335 							continue;
1336 						}
1337 						for ( int i = effect; i <= effectEndRange && i < NUMEFFECTS && i >= 0; ++i )
1338 						{
1339 							entity->setEffect(i, true, duration * TICKS_PER_SECOND, false);
1340 						}
1341 						if ( multiplayer == SERVER )
1342 						{
1343 							entity->serverUpdateEffectsForEntity(true);
1344 						}
1345 					}
1346 				}
1347 			}
1348 		}
1349 		else if ( (*it).find("@clreffect=") != std::string::npos )
1350 		{
1351 			int result = textSourceProcessScriptTag(input, "@clreffect=");
1352 			if ( result != k_ScriptError )
1353 			{
1354 				int effect = result & 0xFF;
1355 				int effectEndRange = (result >> 8) & 0xFF;
1356 				if ( processOnAttachedEntity )
1357 				{
1358 					for ( auto entity : attachedEntities )
1359 					{
1360 						if ( entity->behavior != &actMonster && entity->behavior != &actPlayer )
1361 						{
1362 							continue;
1363 						}
1364 						for ( int i = effect; i <= effectEndRange && i < NUMEFFECTS && i >= 0; ++i )
1365 						{
1366 							entity->setEffect(i, false, 0, false);
1367 						}
1368 						if ( multiplayer == SERVER )
1369 						{
1370 							entity->serverUpdateEffectsForEntity(true);
1371 						}
1372 					}
1373 				}
1374 			}
1375 		}
1376 		else if ( (*it).find("@findtarget=") != std::string::npos )
1377 		{
1378 			int result = textSourceProcessScriptTag(input, "@findtarget=");
1379 			if ( result != k_ScriptError )
1380 			{
1381 				int x1 = result & 0xFF;
1382 				int x2 = (result >> 8) & 0xFF;
1383 				int y1 = (result >> 16) & 0xFF;
1384 				int y2 = (result >> 24) & 0xFF;
1385 				if ( processOnAttachedEntity )
1386 				{
1387 					for ( auto entity : attachedEntities )
1388 					{
1389 						if ( entity->behavior != &actMonster )
1390 						{
1391 							continue;
1392 						}
1393 						Stat* stats = entity->getStats();
1394 						real_t dist = 10000.f;
1395 						real_t sightrange = 256.0;
1396 						Entity* toAttack = nullptr;
1397 						for ( node_t* node = map.creatures->first; node; node = node->next )
1398 						{
1399 							Entity* target = (Entity*)node->element;
1400 							if ( (target->behavior == &actMonster || target->behavior == &actPlayer) && target != entity
1401 								&& entity->checkEnemy(target) )
1402 							{
1403 								int findx = static_cast<int>(target->x) >> 4;
1404 								int findy = static_cast<int>(target->y) >> 4;
1405 								if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1406 								{
1407 									real_t oldDist = dist;
1408 									dist = sqrt(pow(entity->x - target->x, 2) + pow(entity->y - target->y, 2));
1409 									if ( dist < sightrange && dist <= oldDist )
1410 									{
1411 										double tangent = atan2(target->y - entity->y, target->x - entity->x);
1412 										//lineTrace(entity, entity->x, entity->y, tangent, sightrange, 0, false);
1413 										//if ( hit.entity == target )
1414 										//{
1415 											//my->monsterLookTime = 1;
1416 											//my->monsterMoveTime = rand() % 10 + 1;
1417 											entity->monsterLookDir = tangent;
1418 											toAttack = target;
1419 										//}
1420 									}
1421 									else
1422 									{
1423 										dist = oldDist;
1424 									}
1425 								}
1426 							}
1427 						}
1428 						if ( toAttack )
1429 						{
1430 							entity->monsterAcquireAttackTarget(*toAttack, MONSTER_STATE_PATH);
1431 						}
1432 					}
1433 				}
1434 			}
1435 		}
1436 		else if ( (*it).find("@killall=") != std::string::npos )
1437 		{
1438 			int result = textSourceProcessScriptTag(input, "@killall=");
1439 			if ( result != k_ScriptError )
1440 			{
1441 				int x1 = result & 0xFF;
1442 				int x2 = (result >> 8) & 0xFF;
1443 				int y1 = (result >> 16) & 0xFF;
1444 				int y2 = (result >> 24) & 0xFF;
1445 				if ( processOnAttachedEntity )
1446 				{
1447 					for ( auto entity : attachedEntities )
1448 					{
1449 						if ( entity->behavior != &actMonster )
1450 						{
1451 							continue;
1452 						}
1453 						int findx = static_cast<int>(entity->x) >> 4;
1454 						int findy = static_cast<int>(entity->y) >> 4;
1455 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1456 						{
1457 							entity->setHP(0);
1458 						}
1459 					}
1460 				}
1461 				else
1462 				{
1463 					for ( node_t* node = map.creatures->first; node; node = node->next )
1464 					{
1465 						Entity* entity = (Entity*)node->element;
1466 						if ( entity && entity->behavior == &actMonster )
1467 						{
1468 							int findx = static_cast<int>(entity->x) >> 4;
1469 							int findy = static_cast<int>(entity->y) >> 4;
1470 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1471 							{
1472 								entity->setHP(0);
1473 							}
1474 						}
1475 					}
1476 				}
1477 			}
1478 		}
1479 		else if ( (*it).find("@killenemies=") != std::string::npos )
1480 		{
1481 			int result = textSourceProcessScriptTag(input, "@killenemies=");
1482 			if ( result != k_ScriptError )
1483 			{
1484 				int x1 = result & 0xFF;
1485 				int x2 = (result >> 8) & 0xFF;
1486 				int y1 = (result >> 16) & 0xFF;
1487 				int y2 = (result >> 24) & 0xFF;
1488 				if ( processOnAttachedEntity )
1489 				{
1490 					for ( auto entity : attachedEntities )
1491 					{
1492 						if ( entity->behavior != &actMonster || !entity->monsterAllyGetPlayerLeader() )
1493 						{
1494 							continue;
1495 						}
1496 						int findx = static_cast<int>(entity->x) >> 4;
1497 						int findy = static_cast<int>(entity->y) >> 4;
1498 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1499 						{
1500 							entity->setHP(0);
1501 						}
1502 					}
1503 				}
1504 				else
1505 				{
1506 					for ( node_t* node = map.creatures->first; node; node = node->next )
1507 					{
1508 						Entity* entity = (Entity*)node->element;
1509 						if ( entity && entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1510 						{
1511 							int findx = static_cast<int>(entity->x) >> 4;
1512 							int findy = static_cast<int>(entity->y) >> 4;
1513 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1514 							{
1515 								entity->setHP(0);
1516 							}
1517 						}
1518 					}
1519 				}
1520 			}
1521 		}
1522 		else if ( (*it).find("@nodropitems=") != std::string::npos )
1523 		{
1524 			int result = textSourceProcessScriptTag(input, "@nodropitems=");
1525 			if ( result != k_ScriptError )
1526 			{
1527 				int x1 = result & 0xFF;
1528 				int x2 = (result >> 8) & 0xFF;
1529 				int y1 = (result >> 16) & 0xFF;
1530 				int y2 = (result >> 24) & 0xFF;
1531 				if ( processOnAttachedEntity )
1532 				{
1533 					for ( auto entity : attachedEntities )
1534 					{
1535 						if ( entity->behavior != &actMonster )
1536 						{
1537 							continue;
1538 						}
1539 						int findx = static_cast<int>(entity->x) >> 4;
1540 						int findy = static_cast<int>(entity->y) >> 4;
1541 						if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1542 						{
1543 							if ( entity->getStats() )
1544 							{
1545 								entity->getStats()->monsterNoDropItems = 1;
1546 							}
1547 						}
1548 					}
1549 				}
1550 				else
1551 				{
1552 					for ( node_t* node = map.creatures->first; node; node = node->next )
1553 					{
1554 						Entity* entity = (Entity*)node->element;
1555 						if ( entity && entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1556 						{
1557 							int findx = static_cast<int>(entity->x) >> 4;
1558 							int findy = static_cast<int>(entity->y) >> 4;
1559 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1560 							{
1561 								if ( entity->getStats() )
1562 								{
1563 									entity->getStats()->monsterNoDropItems = 1;
1564 								}
1565 							}
1566 						}
1567 					}
1568 				}
1569 			}
1570 		}
1571 		else if ( (*it).find("@copyNPC=") != std::string::npos )
1572 		{
1573 			std::string profTag = "@copyNPC=";
1574 			int result = textSourceProcessScriptTag(input, profTag);
1575 			if ( result != k_ScriptError )
1576 			{
1577 				std::vector<Entity*> applyToEntities;
1578 				if ( processOnAttachedEntity )
1579 				{
1580 					for ( auto entity : attachedEntities )
1581 					{
1582 						if ( entity->behavior != &actMonster && entity->behavior != &actPlayer )
1583 						{
1584 							continue;
1585 						}
1586 						if ( entity->behavior == &actPlayer && client_disconnected[entity->skill[2]] )
1587 						{
1588 							continue;
1589 						}
1590 						applyToEntities.push_back(entity);
1591 					}
1592 				}
1593 
1594 				for ( node_t* node = map.entities->first; node; node = node->next )
1595 				{
1596 					Entity* scriptEntity = (Entity*)node->element;
1597 					if ( scriptEntity && scriptEntity->behavior == &actMonster )
1598 					{
1599 						Stat* scriptStats = scriptEntity->getStats();
1600 						if ( scriptStats && !strcmp(scriptStats->name, "scriptNPC") && (scriptStats->MISC_FLAGS[STAT_FLAG_NPC] & 0xFF) == result )
1601 						{
1602 							// copy stats.
1603 							if ( processOnAttachedEntity )
1604 							{
1605 								for ( auto entity : applyToEntities )
1606 								{
1607 									Stat* stats = entity->getStats();
1608 									if ( stats )
1609 									{
1610 										stats->copyNPCStatsAndInventoryFrom(*scriptStats);
1611 										if ( entity->behavior == &actPlayer )
1612 										{
1613 											statOnlyUpdateNeeded = true;
1614 										}
1615 									}
1616 								}
1617 							}
1618 							else
1619 							{
1620 								for ( int c = 0; c < MAXPLAYERS && !client_disconnected[c] && players[c] && players[c]->entity; ++c )
1621 								{
1622 									stats[c]->copyNPCStatsAndInventoryFrom(*scriptStats);
1623 								}
1624 								statOnlyUpdateNeeded = true;
1625 							}
1626 						}
1627 					}
1628 				}
1629 			}
1630 		}
1631 		else if ( (*it).find("@setenemy=") != std::string::npos )
1632 		{
1633 			std::string profTag = "@setenemy=";
1634 			int result = textSourceProcessScriptTag(input, profTag);
1635 			if ( result != k_ScriptError )
1636 			{
1637 				int x1 = result & 0xFF;
1638 				int x2 = (result >> 8) & 0xFF;
1639 				int y1 = (result >> 16) & 0xFF;
1640 				int y2 = (result >> 24) & 0xFF;
1641 				if ( processOnAttachedEntity )
1642 				{
1643 					for ( auto entity : attachedEntities )
1644 					{
1645 						if ( entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1646 						{
1647 							int findx = static_cast<int>(entity->x) >> 4;
1648 							int findy = static_cast<int>(entity->y) >> 4;
1649 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1650 							{
1651 								if ( entity->getStats() )
1652 								{
1653 									entity->getStats()->monsterForceAllegiance = Stat::MONSTER_FORCE_PLAYER_ENEMY;
1654 									serverUpdateEntityStatFlag(entity, 20);
1655 								}
1656 							}
1657 						}
1658 					}
1659 				}
1660 				else
1661 				{
1662 					for ( node_t* node = map.creatures->first; node; node = node->next )
1663 					{
1664 						Entity* entity = (Entity*)node->element;
1665 						if ( entity && entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1666 						{
1667 							int findx = static_cast<int>(entity->x) >> 4;
1668 							int findy = static_cast<int>(entity->y) >> 4;
1669 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1670 							{
1671 								if ( entity->getStats() )
1672 								{
1673 									entity->getStats()->monsterForceAllegiance = Stat::MONSTER_FORCE_PLAYER_ENEMY;
1674 									serverUpdateEntityStatFlag(entity, 20);
1675 								}
1676 							}
1677 						}
1678 					}
1679 				}
1680 			}
1681 		}
1682 		else if ( (*it).find("@setally=") != std::string::npos )
1683 		{
1684 			std::string profTag = "@setally=";
1685 			int result = textSourceProcessScriptTag(input, profTag);
1686 			if ( result != k_ScriptError )
1687 			{
1688 				int x1 = result & 0xFF;
1689 				int x2 = (result >> 8) & 0xFF;
1690 				int y1 = (result >> 16) & 0xFF;
1691 				int y2 = (result >> 24) & 0xFF;
1692 				if ( processOnAttachedEntity )
1693 				{
1694 					for ( auto entity : attachedEntities )
1695 					{
1696 						if ( entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1697 						{
1698 							int findx = static_cast<int>(entity->x) >> 4;
1699 							int findy = static_cast<int>(entity->y) >> 4;
1700 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1701 							{
1702 								if ( entity->getStats() )
1703 								{
1704 									entity->getStats()->monsterForceAllegiance = Stat::MONSTER_FORCE_PLAYER_ALLY;
1705 								}
1706 							}
1707 						}
1708 					}
1709 				}
1710 				else
1711 				{
1712 					for ( node_t* node = map.creatures->first; node; node = node->next )
1713 					{
1714 						Entity* entity = (Entity*)node->element;
1715 						if ( entity && entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
1716 						{
1717 							int findx = static_cast<int>(entity->x) >> 4;
1718 							int findy = static_cast<int>(entity->y) >> 4;
1719 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1720 							{
1721 								if ( entity->getStats() )
1722 								{
1723 									entity->getStats()->monsterForceAllegiance = Stat::MONSTER_FORCE_PLAYER_ALLY;
1724 								}
1725 							}
1726 						}
1727 					}
1728 				}
1729 			}
1730 		}
1731 		else if ( (*it).find("@setvar=") != std::string::npos )
1732 		{
1733 			std::string profTag = "@setvar=";
1734 			int result = textSourceProcessScriptTag(input, profTag);
1735 			if ( result != k_ScriptError )
1736 			{
1737 				achievementObserver.checkMapScriptsOnVariableSet();
1738 				/*for ( auto it = scriptVariables.begin(); it != scriptVariables.end(); ++it )
1739 				{
1740 					printlog("%s | %d", (*it).first.c_str(), (*it).second);
1741 				}*/
1742 			}
1743 		}
1744 		else if ( (*it).find("@addtochest=") != std::string::npos )
1745 		{
1746 			int result = textSourceProcessScriptTag(input, "@addtochest=");
1747 			if ( result != k_ScriptError )
1748 			{
1749 				int x1 = result & 0xFF;
1750 				int x2 = (result >> 8) & 0xFF;
1751 				int y1 = (result >> 16) & 0xFF;
1752 				int y2 = (result >> 24) & 0xFF;
1753 				std::vector<Entity*> applyToEntities;
1754 				if ( processOnAttachedEntity && textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_ITEMS )
1755 				{
1756 					for ( auto entity : attachedEntities )
1757 					{
1758 						if ( entity->behavior == &actItem )
1759 						{
1760 							applyToEntities.push_back(entity);
1761 						}
1762 					}
1763 					Entity* chest = nullptr;
1764 					for ( node_t* node = map.entities->first; node; node = node->next )
1765 					{
1766 						Entity* entity = (Entity*)node->element;
1767 						if ( entity && entity->behavior == &actChest )
1768 						{
1769 							int findx = static_cast<int>(entity->x) >> 4;
1770 							int findy = static_cast<int>(entity->y) >> 4;
1771 							if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
1772 							{
1773 								chest = entity;
1774 							}
1775 						}
1776 					}
1777 					if ( chest )
1778 					{
1779 						for ( auto entity : applyToEntities )
1780 						{
1781 							Item* item = newItemFromEntity(entity);
1782 							if ( item )
1783 							{
1784 								chest->addItemToChest(item);
1785 							}
1786 							list_RemoveNode(entity->mynode);
1787 							entity = nullptr;
1788 						}
1789 					}
1790 				}
1791 				else
1792 				{
1793 					// not implemented, needs to be attached to items.
1794 				}
1795 			}
1796 		}
1797 		else
1798 		{
1799 			for ( int i = 0; i < NUMSTATS; ++i )
1800 			{
1801 				std::string profTag = "@st";
1802 				switch ( i )
1803 				{
1804 					case STAT_STR:
1805 						profTag.append("STR");
1806 						break;
1807 					case STAT_DEX:
1808 						profTag.append("DEX");
1809 						break;
1810 					case STAT_CON:
1811 						profTag.append("INT");
1812 						break;
1813 					case STAT_INT:
1814 						profTag.append("CON");
1815 						break;
1816 					case STAT_PER:
1817 						profTag.append("PER");
1818 						break;
1819 					case STAT_CHR:
1820 						profTag.append("CHR");
1821 						break;
1822 					default:
1823 						break;
1824 				}
1825 				profTag.append("=");
1826 				if ( (*it).find(profTag) != std::string::npos )
1827 				{
1828 					int result = textSourceProcessScriptTag(input, profTag);
1829 					if ( result != k_ScriptError )
1830 					{
1831 						std::vector<Entity*> applyToEntities;
1832 						if ( processOnAttachedEntity )
1833 						{
1834 							for ( auto entity : attachedEntities )
1835 							{
1836 								if ( entity->behavior == &actMonster
1837 									|| (entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]]) )
1838 								{
1839 									applyToEntities.push_back(entity);
1840 									statOnlyUpdateNeeded = (entity->behavior == &actPlayer);
1841 								}
1842 							}
1843 						}
1844 						else
1845 						{
1846 							for ( int c = 0; c < MAXPLAYERS; ++c )
1847 							{
1848 								if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
1849 								{
1850 									applyToEntities.push_back(players[c]->entity);
1851 									statOnlyUpdateNeeded = true;
1852 								}
1853 							}
1854 						}
1855 						for ( auto entity : applyToEntities )
1856 						{
1857 							Stat* stats = entity->getStats();
1858 							if ( stats )
1859 							{
1860 								switch ( i )
1861 								{
1862 									case STAT_STR:
1863 										stats->STR = result;
1864 										break;
1865 									case STAT_DEX:
1866 										stats->DEX = result;
1867 										break;
1868 									case STAT_CON:
1869 										stats->CON = result;
1870 										break;
1871 									case STAT_INT:
1872 										stats->INT = result;
1873 										break;
1874 									case STAT_PER:
1875 										stats->PER = result;
1876 										break;
1877 									case STAT_CHR:
1878 										stats->CHR = result;
1879 										break;
1880 									default:
1881 										break;
1882 								}
1883 							}
1884 						}
1885 						statOnlyUpdateNeeded = true;
1886 					}
1887 				}
1888 			}
1889 			for ( int i = 0; i < NUMSTATS; ++i )
1890 			{
1891 				std::string profTag = "@st";
1892 				switch ( i )
1893 				{
1894 					case STAT_STR:
1895 						profTag.append("STR");
1896 						break;
1897 					case STAT_DEX:
1898 						profTag.append("DEX");
1899 						break;
1900 					case STAT_CON:
1901 						profTag.append("INT");
1902 						break;
1903 					case STAT_INT:
1904 						profTag.append("CON");
1905 						break;
1906 					case STAT_PER:
1907 						profTag.append("PER");
1908 						break;
1909 					case STAT_CHR:
1910 						profTag.append("CHR");
1911 						break;
1912 					default:
1913 						break;
1914 				}
1915 				profTag.append("+");
1916 				if ( (*it).find(profTag) != std::string::npos )
1917 				{
1918 					int result = textSourceProcessScriptTag(input, profTag);
1919 					if ( result != k_ScriptError )
1920 					{
1921 						std::vector<Entity*> applyToEntities;
1922 						if ( processOnAttachedEntity )
1923 						{
1924 							for ( auto entity : attachedEntities )
1925 							{
1926 								if ( entity->behavior == &actMonster
1927 									|| (entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]]) )
1928 								{
1929 									applyToEntities.push_back(entity);
1930 									statOnlyUpdateNeeded = (entity->behavior == &actPlayer);
1931 								}
1932 							}
1933 						}
1934 						else
1935 						{
1936 							for ( int c = 0; c < MAXPLAYERS; ++c )
1937 							{
1938 								if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
1939 								{
1940 									applyToEntities.push_back(players[c]->entity);
1941 									statOnlyUpdateNeeded = true;
1942 								}
1943 							}
1944 						}
1945 						for ( auto entity : applyToEntities )
1946 						{
1947 							Stat* stats = entity->getStats();
1948 							if ( stats )
1949 							{
1950 								switch ( i )
1951 								{
1952 									case STAT_STR:
1953 										stats->STR += result;
1954 										break;
1955 									case STAT_DEX:
1956 										stats->DEX += result;
1957 										break;
1958 									case STAT_CON:
1959 										stats->CON += result;
1960 										break;
1961 									case STAT_INT:
1962 										stats->INT += result;
1963 										break;
1964 									case STAT_PER:
1965 										stats->PER += result;
1966 										break;
1967 									case STAT_CHR:
1968 										stats->CHR += result;
1969 										break;
1970 									default:
1971 										break;
1972 								}
1973 							}
1974 						}
1975 					}
1976 				}
1977 			}
1978 
1979 			for ( int i = 0; i < NUMPROFICIENCIES; ++i )
1980 			{
1981 				std::string profTag = "@pro";
1982 				profTag.append(std::to_string(i).append("="));
1983 				if ( (*it).find(profTag) != std::string::npos )
1984 				{
1985 					int result = textSourceProcessScriptTag(input, profTag);
1986 					if ( result != k_ScriptError )
1987 					{
1988 						std::vector<Entity*> applyToEntities;
1989 						if ( processOnAttachedEntity )
1990 						{
1991 							for ( auto entity : attachedEntities )
1992 							{
1993 								if ( entity->behavior == &actMonster
1994 									|| (entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]]) )
1995 								{
1996 									applyToEntities.push_back(entity);
1997 									statOnlyUpdateNeeded = (entity->behavior == &actPlayer);
1998 								}
1999 							}
2000 						}
2001 						else
2002 						{
2003 							for ( int c = 0; c < MAXPLAYERS; ++c )
2004 							{
2005 								if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
2006 								{
2007 									applyToEntities.push_back(players[c]->entity);
2008 									statOnlyUpdateNeeded = true;
2009 								}
2010 							}
2011 						}
2012 
2013 						for ( auto entity : applyToEntities )
2014 						{
2015 							Stat* stats = entity->getStats();
2016 							if ( stats )
2017 							{
2018 								stats->PROFICIENCIES[i] = result;
2019 							}
2020 						}
2021 					}
2022 				}
2023 			}
2024 			for ( int i = 0; i < NUMPROFICIENCIES; ++i )
2025 			{
2026 				std::string profTag = "@pro";
2027 				profTag.append(std::to_string(i).append("+"));
2028 				if ( (*it).find(profTag) != std::string::npos )
2029 				{
2030 					int result = textSourceProcessScriptTag(input, profTag);
2031 					if ( result != k_ScriptError )
2032 					{
2033 						std::vector<Entity*> applyToEntities;
2034 						if ( processOnAttachedEntity )
2035 						{
2036 							for ( auto entity : attachedEntities )
2037 							{
2038 								if ( entity->behavior == &actMonster
2039 									|| (entity->behavior == &actPlayer && stats[entity->skill[2]] && !client_disconnected[entity->skill[2]]) )
2040 								{
2041 									applyToEntities.push_back(entity);
2042 									statOnlyUpdateNeeded = (entity->behavior == &actPlayer);
2043 								}
2044 							}
2045 						}
2046 						else
2047 						{
2048 							for ( int c = 0; c < MAXPLAYERS; ++c )
2049 							{
2050 								if ( stats[c] && !client_disconnected[c] && players[c] && players[c]->entity )
2051 								{
2052 									applyToEntities.push_back(players[c]->entity);
2053 									statOnlyUpdateNeeded = true;
2054 								}
2055 							}
2056 						}
2057 
2058 						for ( auto entity : applyToEntities )
2059 						{
2060 							Stat* stats = entity->getStats();
2061 							if ( stats )
2062 							{
2063 								stats->PROFICIENCIES[i] += result;
2064 							}
2065 						}
2066 					}
2067 				}
2068 			}
2069 		}
2070 	}
2071 	printlog("%s", executionLog.c_str());
2072 	if ( statOnlyUpdateNeeded )
2073 	{
2074 		for ( int c = 1; c < MAXPLAYERS; ++c )
2075 		{
2076 			updateClientInformation(c, false, false, TextSourceScript::CLIENT_UPDATE_ALL);
2077 		}
2078 	}
2079 	printlog("[SCRIPT]: Finished running.");
2080 }
2081 
actTextSource()2082 void Entity::actTextSource()
2083 {
2084 	if ( multiplayer == CLIENT )
2085 	{
2086 		return;
2087 	}
2088 
2089 	if ( ((textSourceVariables4W >> 16) & 0xFFFF) == 0 ) // store the delay in the 16 leftmost bits.
2090 	{
2091 		textSourceVariables4W |= (textSourceDelay << 16);
2092 	}
2093 
2094 	bool powered = false;
2095 	if ( textSourceScript.getScriptType(textSourceIsScript) == textSourceScript.NO_SCRIPT
2096 		|| textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_POWER )
2097 	{
2098 		powered = (circuit_status == CIRCUIT_ON);
2099 	}
2100 	else if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_ALWAYS )
2101 	{
2102 		textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
2103 		powered = true;
2104 	}
2105 	else if ( textSourceScript.getScriptType(textSourceIsScript) == textSourceScript.SCRIPT_ATTACHED )
2106 	{
2107 		int entitiesExisting = 0;
2108 		int entitiesVisible = 0;
2109 		int entitiesInvisible = 0;
2110 		// check if our attached entities still exist.
2111 		for ( node_t* node = children.first; node; node = node->next )
2112 		{
2113 			Uint32 entityUid = *((Uint32*)node->element);
2114 			Entity* child = uidToEntity(entityUid);
2115 			if ( child )
2116 			{
2117 				++entitiesExisting;
2118 				if ( child->flags[INVISIBLE] )
2119 				{
2120 					++entitiesInvisible;
2121 				}
2122 				else
2123 				{
2124 					++entitiesVisible;
2125 				}
2126 			}
2127 		}
2128 		if ( entitiesExisting == 0 )
2129 		{
2130 			if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_ISREMOVED )
2131 			{
2132 				textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
2133 				powered = true;
2134 			}
2135 		}
2136 		else
2137 		{
2138 			if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_EXISTS )
2139 			{
2140 				textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
2141 				powered = true;
2142 			}
2143 			else if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_INVIS
2144 				&& entitiesInvisible == entitiesExisting )
2145 			{
2146 				textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
2147 				powered = true;
2148 			}
2149 			else if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_VISIBLE
2150 				&& entitiesVisible == entitiesExisting )
2151 			{
2152 				textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
2153 				powered = true;
2154 			}
2155 		}
2156 	}
2157 	else if ( textSourceScript.getScriptType(textSourceIsScript) == textSourceScript.SCRIPT_ATTACHED_FIRED )
2158 	{
2159 		powered = true;
2160 	}
2161 
2162 	if ( textSourceScript.getScriptType(textSourceIsScript) != textSourceScript.NO_SCRIPT )
2163 	{
2164 		if ( ticks <= 2 )
2165 		{
2166 			return;
2167 		}
2168 	}
2169 
2170 	if ( powered )
2171 	{
2172 		// received power
2173 		if ( textSourceDelay > 0 )
2174 		{
2175 			--textSourceDelay;
2176 			return;
2177 		}
2178 		else
2179 		{
2180 			textSourceDelay = (textSourceVariables4W >> 16) & 0xFFFF;
2181 		}
2182 		if ( (textSourceVariables4W & 0xFF) == 0 )
2183 		{
2184 			textSourceVariables4W |= 1;
2185 
2186 			std::string output = textSourceScript.getScriptFromEntity(*this);
2187 
2188 			Uint32 color = SDL_MapRGB(mainsurface->format, (textSourceColorRGB >> 16) & 0xFF, (textSourceColorRGB >> 8) & 0xFF,
2189 				(textSourceColorRGB >> 0) & 0xFF);
2190 
2191 			if ( textSourceIsScript != textSourceScript.NO_SCRIPT )
2192 			{
2193 				textSourceScript.handleTextSourceScript(*this, output);
2194 				return;
2195 			}
2196 
2197 			size_t foundPlayerRef = output.find("@p");
2198 			if ( foundPlayerRef != std::string::npos )
2199 			{
2200 				output.erase(foundPlayerRef, 2);
2201 				output.insert(foundPlayerRef, "%s");
2202 			}
2203 
2204 			size_t foundDistanceRequirement = output.find("@d");
2205 			int distanceRequirement = -1;
2206 			if ( foundDistanceRequirement != std::string::npos )
2207 			{
2208 				output.erase(foundDistanceRequirement, 2);
2209 				std::string distance;
2210 				while ( foundDistanceRequirement < output.length()
2211 					&& output.at(foundDistanceRequirement) != ' '
2212 					&& output.at(foundDistanceRequirement) != '\0'
2213 					)
2214 				{
2215 					// usage: Hello @p @d 123 will send to distance 123 units away and send message "Hello player "
2216 					distance = distance + output.at(foundDistanceRequirement);
2217 					++foundDistanceRequirement;
2218 				}
2219 				distanceRequirement = std::stoi(distance);
2220 				output.erase(output.find(distance), distance.length());
2221 			}
2222 
2223 			size_t foundInputTag = output.find("@in=");
2224 			while ( foundInputTag != std::string::npos )
2225 			{
2226 				output.erase(foundInputTag, strlen("@in="));
2227 				std::string impulseStr;
2228 				size_t inputTagStrIndex = foundInputTag;
2229 				while ( inputTagStrIndex < output.length()
2230 					&& output.at(inputTagStrIndex) != ' '
2231 					&& output.at(inputTagStrIndex) != '.'
2232 					&& output.at(inputTagStrIndex) != ','
2233 					&& output.at(inputTagStrIndex) != '\0'
2234 					)
2235 				{
2236 					// usage: @in=IN_USE will get the input key for use
2237 					impulseStr = impulseStr + output.at(inputTagStrIndex);
2238 					++inputTagStrIndex;
2239 				}
2240 				for ( int i = 0; i < NUMIMPULSES; ++i )
2241 				{
2242 					if ( impulseStrings[i].compare(impulseStr) == 0 )
2243 					{
2244 						output.erase(output.find(impulseStr), impulseStr.length());
2245 						std::string inputFormatted = "[";
2246 						inputFormatted.append(getInputName(impulses[i])).append("]");
2247 						output.insert(foundInputTag, inputFormatted.c_str());
2248 						break;
2249 					}
2250 				}
2251 				foundInputTag = output.find("@in=");
2252 			}
2253 
2254 			size_t found = output.find("\\n");
2255 			while ( found != std::string::npos )
2256 			{
2257 				output.erase(found, 2);
2258 				output.insert(found, 1, '\n');
2259 				found = output.find("\\n");
2260 			}
2261 
2262 			char buf[256] = "";
2263 			strcpy(buf, output.c_str());
2264 
2265 			for ( int c = 0; c < MAXPLAYERS; ++c )
2266 			{
2267 				if ( !client_disconnected[c] )
2268 				{
2269 					if ( distanceRequirement != -1 && !(players[c] && players[c]->entity && entityDist(this, players[c]->entity) <= distanceRequirement) )
2270 					{
2271 						// not in range.
2272 					}
2273 					else
2274 					{
2275 						if ( foundPlayerRef != std::string::npos && stats[c] )
2276 						{
2277 							messagePlayerColor(c, color, buf, stats[c]->name);
2278 						}
2279 						else
2280 						{
2281 							messagePlayerColor(c, color, buf);
2282 						}
2283 					}
2284 				}
2285 			}
2286 		}
2287 	}
2288 	else if ( !powered )
2289 	{
2290 		textSourceDelay = (textSourceVariables4W >> 16) & 0xFFFF;
2291 		if ( (textSourceVariables4W & 0xFF) == 1 && ((textSourceVariables4W >> 8) & 0xFF) == 0 )
2292 		{
2293 			textSourceVariables4W -= 1;
2294 		}
2295 	}
2296 }
2297 
updateClientInformation(int player,bool clearInventory,bool clearStats,ClientInformationType updateType)2298 void TextSourceScript::updateClientInformation(int player, bool clearInventory, bool clearStats, ClientInformationType updateType)
2299 {
2300 	if ( multiplayer != SERVER )
2301 	{
2302 		return;
2303 	}
2304 	if ( !stats[player] || client_disconnected[player] )
2305 	{
2306 		return;
2307 	}
2308 
2309 	if ( updateType == CLIENT_UPDATE_ALL )
2310 	{
2311 		// update client attributes
2312 		strcpy((char*)net_packet->data, "SCRU");
2313 		net_packet->data[4] = clientnum;
2314 		net_packet->data[5] = (Sint8)stats[player]->STR;
2315 		net_packet->data[6] = (Sint8)stats[player]->DEX;
2316 		net_packet->data[7] = (Sint8)stats[player]->CON;
2317 		net_packet->data[8] = (Sint8)stats[player]->INT;
2318 		net_packet->data[9] = (Sint8)stats[player]->PER;
2319 		net_packet->data[10] = (Sint8)stats[player]->CHR;
2320 		net_packet->data[11] = (Sint8)stats[player]->EXP;
2321 		net_packet->data[12] = (Sint8)stats[player]->LVL;
2322 		SDLNet_Write16((Sint16)stats[player]->HP, &net_packet->data[13]);
2323 		SDLNet_Write16((Sint16)stats[player]->MAXHP, &net_packet->data[15]);
2324 		SDLNet_Write16((Sint16)stats[player]->MP, &net_packet->data[17]);
2325 		SDLNet_Write16((Sint16)stats[player]->MAXMP, &net_packet->data[19]);
2326 		SDLNet_Write32((Sint32)stats[player]->GOLD, &net_packet->data[21]);
2327 		if ( clearInventory )
2328 		{
2329 			net_packet->data[25] = 1;
2330 		}
2331 		else
2332 		{
2333 			net_packet->data[25] = 0;
2334 		}
2335 		if ( clearStats )
2336 		{
2337 			net_packet->data[26] = 1;
2338 		}
2339 		else
2340 		{
2341 			net_packet->data[26] = 0;
2342 		}
2343 
2344 		for ( int i = 0; i < NUMPROFICIENCIES; ++i )
2345 		{
2346 			net_packet->data[27 + i] = (Uint8)stats[player]->PROFICIENCIES[i];
2347 		}
2348 		net_packet->address.host = net_clients[player - 1].host;
2349 		net_packet->address.port = net_clients[player - 1].port;
2350 		net_packet->len = 27 + NUMPROFICIENCIES;
2351 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
2352 
2353 		serverUpdatePlayerLVL();
2354 	}
2355 	else if ( updateType == CLIENT_UPDATE_CLASS )
2356 	{
2357 		strcpy((char*)net_packet->data, "SCRC");
2358 		for ( int i = 0; i < MAXPLAYERS; ++i )
2359 		{
2360 			net_packet->data[4 + i] = client_classes[i];
2361 		}
2362 		net_packet->address.host = net_clients[player - 1].host;
2363 		net_packet->address.port = net_clients[player - 1].port;
2364 		net_packet->len = 5 + MAXPLAYERS;
2365 		sendPacketSafe(net_sock, -1, net_packet, player - 1);
2366 	}
2367 	else if ( updateType == CLIENT_UPDATE_HUNGER )
2368 	{
2369 		serverUpdateHunger(player);
2370 	}
2371 }
2372 
playerClearInventory(bool clearStats)2373 void TextSourceScript::playerClearInventory(bool clearStats)
2374 {
2375 	deinitShapeshiftHotbar();
2376 	for ( int c = 0; c < NUM_HOTBAR_ALTERNATES; ++c )
2377 	{
2378 		selected_spell_alternate[c] = NULL;
2379 		hotbarShapeshiftInit[c] = false;
2380 	}
2381 	selected_spell = NULL; //So you don't start off with a spell when the game restarts.
2382 	selected_spell_last_appearance = -1;
2383 	spellcastingAnimationManager_deactivate(&cast_animation);
2384 	stats[clientnum]->freePlayerEquipment();
2385 	list_FreeAll(&stats[clientnum]->inventory);
2386 	shootmode = true;
2387 	appraisal_timer = 0;
2388 	appraisal_item = 0;
2389 
2390 	if ( clearStats )
2391 	{
2392 		stats[clientnum]->clearStats();
2393 	}
2394 
2395 	this->hasClearedInventory = true;
2396 }
2397 
getScriptFromEntity(Entity & src)2398 std::string TextSourceScript::getScriptFromEntity(Entity& src)
2399 {
2400 	// assemble the string.
2401 	char buf[256] = "";
2402 	int totalChars = 0;
2403 	for ( int i = 4; i < 60; ++i )
2404 	{
2405 		if ( i == 28 ) // circuit_status
2406 		{
2407 			continue;
2408 		}
2409 		if ( src.skill[i] != 0 )
2410 		{
2411 			for ( int c = 0; c < 4; ++c )
2412 			{
2413 				buf[totalChars] = static_cast<char>((src.skill[i] >> (c * 8)) & 0xFF);
2414 				++totalChars;
2415 			}
2416 		}
2417 	}
2418 	if ( buf[totalChars] != '\0' )
2419 	{
2420 		buf[totalChars] = '\0';
2421 	}
2422 	return buf;
2423 }
2424 
parseScriptInMapGeneration(Entity & src)2425 void TextSourceScript::parseScriptInMapGeneration(Entity& src)
2426 {
2427 	std::string script = getScriptFromEntity(src);
2428 	size_t foundScriptTag = script.find("@script");
2429 	if ( foundScriptTag != std::string::npos )
2430 	{
2431 		if ( (foundScriptTag + strlen("@script")) < script.length()
2432 			&& script.at(foundScriptTag + strlen("@script")) == ' ' )
2433 		{
2434 			script.erase(foundScriptTag, strlen("@script") + 1); // trailing space.
2435 		}
2436 		else
2437 		{
2438 			script.erase(foundScriptTag, strlen("@script"));
2439 		}
2440 		textSourceScript.setScriptType(src.textSourceIsScript, textSourceScript.SCRIPT_NORMAL);
2441 		textSourceScript.setTriggerType(src.textSourceIsScript, textSourceScript.TRIGGER_POWER);
2442 	}
2443 	if ( src.textSourceIsScript == NO_SCRIPT )
2444 	{
2445 		return;
2446 	}
2447 
2448 	if ( script.find("@triggerif=") != std::string::npos )
2449 	{
2450 		int result = textSourceProcessScriptTag(script, "@triggerif=");
2451 		if ( result != k_ScriptError )
2452 		{
2453 			textSourceScript.setTriggerType(src.textSourceIsScript, static_cast<ScriptTriggeredBy>(result));
2454 		}
2455 	}
2456 
2457 	if ( script.find("@attachto=") != std::string::npos )
2458 	{
2459 		int attachTo = textSourceProcessScriptTag(script, "@attachto=");
2460 		if ( attachTo == k_ScriptError )
2461 		{
2462 			return;
2463 		}
2464 		textSourceScript.setScriptType(src.textSourceIsScript, textSourceScript.SCRIPT_ATTACHED);
2465 		int x1 = static_cast<int>(src.x / 16); // default to just whatever this script is sitting on.
2466 		int x2 = static_cast<int>(src.x / 16);
2467 		int y1 = static_cast<int>(src.y / 16);
2468 		int y2 = static_cast<int>(src.y / 16);
2469 		if ( script.find("@attachrange=") != std::string::npos )
2470 		{
2471 			int result = textSourceProcessScriptTag(script, "@attachrange=");
2472 			if ( result != k_ScriptError )
2473 			{
2474 				x1 = result & 0xFF;
2475 				x2 = (result >> 8) & 0xFF;
2476 				y1 = (result >> 16) & 0xFF;
2477 				y2 = (result >> 24) & 0xFF;
2478 			}
2479 		}
2480 		textSourceScript.setAttachedToEntityType(src.textSourceIsScript, attachTo);
2481 		for ( node_t* node = map.entities->first; node; node = node->next )
2482 		{
2483 			Entity* entity = (Entity*)node->element;
2484 			if ( entity )
2485 			{
2486 				if ( (entity->behavior == &actMonster && attachTo == TO_MONSTERS)
2487 					|| (entity->behavior == &actPlayer && attachTo == TO_PLAYERS)
2488 					|| (entity->behavior == &actItem && attachTo == TO_ITEMS)
2489 					|| (entity->behavior == &actMonster
2490 						&& attachTo >= TO_NOTHING && attachTo <= TO_DUMMYBOT
2491 						&& entity->getRace() == (attachTo - TO_NOTHING)) )
2492 				{
2493 					// found our entity.
2494 				}
2495 				else
2496 				{
2497 					continue;
2498 				}
2499 				int findx = static_cast<int>(entity->x) >> 4;
2500 				int findy = static_cast<int>(entity->y) >> 4;
2501 				if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
2502 				{
2503 					node_t* node = list_AddNodeLast(&src.children);
2504 					node->deconstructor = &defaultDeconstructor;
2505 					Uint32* entityUid = (Uint32*)(malloc(sizeof(Uint32)));
2506 					node->element = entityUid;
2507 					node->size = sizeof(Uint32);
2508 					*entityUid = entity->getUID();
2509 				}
2510 			}
2511 		}
2512 	}
2513 }