View Single Post
  #1  
Old 11-30-2010, 04:44 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default Multiple Plugins: Spawn Formation Tools, etc

In celebration of the new Quest::Plugins section of the forums, I figured I would post a few of the new plugins I have made over the past year.

These plugins are really cool and have tons of uses once you are familiar with them. Some of them are for spawning NPCs into a formation, and moving those NPCs while staying in formation, as well as having formations attack each other. Some are for calculating a destination location based on heading and distance. Each of the plugins have an example for usage and an explanation of all possible fields they can use.

You may notice commented out lines that use plugin:ebug(), which is a debug I made (and submitted in this post) to help debug the plugins if/when needed. To my knowledge, all of these work 100% without any bugs.

Note that some of these plugins use plugins within them, so you MUST have the plugins they use for them to function properly. I believe the current PEQ quests download includes all of the required plugins with the exception of plugin::RandomRange(). All of the extra required plugins can also be found as submissions in this section of the forums and were submitted by either myself or Shendare. So, if you don't have them already, you can get them easily.


Here are the steps to add these plugins to your server:

1. Open notepad, or whatever text editor you prefer.

2. Copy and paste the following code into Notepad:

Code:
#Usage: plugin::MoveInFormation(NPCID, [$npc = 0, OnGrid = 0, MoveToX = 0, MoveToY = 0, NoSpeedBuffs = false]);
# This script will move all of 1 NPC Type ID to the relative Guard position that they were the first time this plugin was run
# NPCID is the NPC ID of the NPCs you want to move in the formation (moves all of this NPC ID in the whole zone)
# $npc is an optional field where a specific leader NPC can be specified (default is the NPC running the script)
# OnGrid is am optional field where 1 will use NPC waypoints if they are on a grid, and 0 (default) will use guard or set XYZ
# MoveToX/MoveToY/MoveToZ are optional fields for setting a destination X Y and Z instead of using guard points or way points to determine the destination.
# At this time, the MoveToZ field is basically ignored, as the ground Z is calculated based on the leader NPC's Z.
# NoSpeedBuffs is an optional field for all speedbuffs to be removed during pathing if enabled

sub MoveInFormation {
	my $npc = plugin::val('$npc');
	my $entity_list = plugin::val('$entity_list');
	my $SquadNPCID = $_[0];
	my $Mob = $_[1];
	my $OnGrid = $_[2];
	my $MoveToX = $_[3];
	my $MoveToY = $_[4];
	my $NoSpeedBuffs = $_[5];
	if (!$Mob) { $Mob = $npc; }
	
	if (!$MoveToX && !$MoveToY && !$MoveToZ) {
		if (!$OnGrid) {
			$MoveToX = $Mob->GetGuardPointX();
			$MoveToY = $Mob->GetGuardPointY();
			#plugin::Debug("Using Guard Locs");
		}
		else {
			$MoveToX = $Mob->GetWaypointX();
			$MoveToY = $Mob->GetWaypointY();
		}
	}
	my $NewHeading = $Mob->CalculateHeadingToTarget($MoveToX, $MoveToY);
	if ($NoSpeedBuffs) {
		$Mob->BuffFadeByEffect(3);	# Prevent Speed buffs from messing up the group of NPCs
	}
	
	my @npclist = $entity_list->GetNPCList();
	foreach $ent (@npclist)
	{
		if($ent->GetNPCTypeID() == $SquadNPCID) {
			#plugin::Debug("Got NPC");
			if ($NoSpeedBuffs) {
				$ent->BuffFadeByEffect(3);	# Prevent Speed buffs from messing up the group of NPCs
			}
			
			# Set the Initial Relative Position and Heading variables
			if(!$ent->EntityVariableExists(50)) {	# If this doesn't exist, then neither of them do
				my $SquadX = $ent->GetX();
				my $SquadY = $ent->GetY();
				my $SquadHeading = $Mob->CalculateHeadingToTarget($SquadX, $SquadY);
				my $CurHeading = $Mob->GetHeading();
				my $CurDist = plugin::DistNoZ($Mob, $ent);
				my $HeadingDiff = 0;
				if ($CurHeading > $SquadHeading) {
					$HeadingDiff = $CurHeading - $SquadHeading;
				}
				else {
					$HeadingDiff = $CurHeading + 256 - $SquadHeading;
				}
				$ent->SetEntityVariable(50, $CurDist);	# Original Distance from Leader
				$ent->SetEntityVariable(51, $HeadingDiff);	# Original Heading Difference from Leader
			}
			my $SquadDist = $ent->GetEntityVariable(50);
			my $HDiff = $ent->GetEntityVariable(51);
			#plugin::Debug("Got Variables");
			
			my $NewSquadHeading = 0;
			if ($NewHeading - $HDiff > 0) {
				$NewSquadHeading = $NewHeading - $HDiff;
			}
			else {
				$NewSquadHeading = 256 + $NewHeading - $HDiff;
			}
			#plugin::Debug("Before Calc");
			my @DestArray = plugin::CalcDestFromHeadingXY($NewSquadHeading, $SquadDist, $MoveToX, $MoveToY, 10, $Mob);
			my $NewX = $DestArray[0];
			my $NewY = $DestArray[1];
			my $NewZ = $DestArray[2];
			#plugin::Debug("After Calc");
			$ent->MoveTo($NewX, $NewY, $NewZ, $NewHeading, 1);
			#plugin::Debug("Moving NPC");
		}
	}
}


#Usage: plugin::MoveToFormation(NPCID, LeaderMob, Distance, Columns, Rows, [LeadDist, MaxZDiff]);
# NPCID is the NPC Type ID of the NPCs you want to form the squad formation with
# LeaderMob is the Client or NPC you want to spawn the formation behind (use invisible and/or temp NPC to show no leader)
# Distance is the distance each member of the formation will be from each other on both axis
# Columns is the number of columns you want in the formation
# Rows is the number of rows you want in the formation
# LeadDist is the distance you want the formation to be created behind the leader
# MaxZDiff is the maximum height above the leader that the best Z will be searched for

sub MoveToFormation {
	my $NPCID = $_[0];
	my $Mob = $_[1];
	my $Distance = $_[2];
	my $Columns = $_[3];
	my $Rows = $_[4];
	my $LeadDist = $_[5];
	my $MaxZDiff = $_[6];
	
	if (!$MaxZDiff)
	{
		$MaxZDiff = 15;
	}
	
	if (!$LeadDist)
	{
		$LeadDist = $Distance;
	}
	
	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $entity_list = plugin::val('$entity_list');
	
	if (!$Mob) { 
		if ($npc->IsNPC())
		{
			$Mob = $npc;
		}
		elsif ($client->IsClient())
		{
			$Mob = $client;
		}
		else
		{
			return; # No Client or NPC found
		}
	}
	
	my $SpawnX = $Mob->GetX();
	my $SpawnY = $Mob->GetY();
	my $SpawnZ = $Mob->GetZ();

	my $LeaderHeading = $Mob->GetHeading();
	$SpawnX = $SpawnX + $LeadDist;
	$SpawnY = $SpawnY - (($Columns - 1) * $Distance / 2);

	my $NewX = $SpawnX;
	my $NewY = $SpawnY;
	
	my @npclist = $entity_list->GetNPCList();
	my @SquadNPCs = (); # Creates a cleared array
	foreach $ent (@npclist)
	{
		if($ent->GetNPCTypeID() == $NPCID) {
			#plugin::Debug("Found a matching NPC");
			push(@SquadNPCs, $ent->GetID());	# Adds an element to the end of the array
			#plugin::Debug("Added the NPC to the list");
		}
	}

	my $ArrayEntry = 0;
	for ($ColNum = 0; $ColNum < $Columns; $ColNum++)
	{
		#plugin::Debug("Column $ColNum");
		for ($RowNum = 0; $RowNum < $Rows; $RowNum++)
		{
			#plugin::Debug("Row $RowNum");
			my $SquadHeading = $Mob->CalculateHeadingToTarget($NewX, $NewY);
			my $CurDist = plugin::DistNoZToCoords($Mob, $NewX, $NewY);
			my $DestH = 0;
			
			if ($LeaderHeading > ($SquadHeading - 64))
			{
				$DestH = $LeaderHeading - $SquadHeading - 64;
			}
			else
			{
				$DestH = 256 + $LeaderHeading - $SquadHeading - 64;
			}
			
			my @DestArray = plugin::CalcDestFromHeading($DestH, $CurDist, $Mob, $MaxZDiff);
			my $DestX = $DestArray[0];
			my $DestY = $DestArray[1];
			my $DestZ = $DestArray[2];

			my $ArrayNPC = $SquadNPCs[$ArrayEntry];
			my $CurNPC = $entity_list->GetNPCByID($ArrayNPC);
			$CurNPC->MoveTo($DestX, $DestY, $DestZ, $LeaderHeading, 1);
			my $NewSquadHeading = $Mob->CalculateHeadingToTarget($DestX, $DestY);
			my $HeadingDiff = 0;
			if ($LeaderHeading > $NewSquadHeading) {
				$HeadingDiff = $LeaderHeading - $NewSquadHeading;
			}
			else {
				$HeadingDiff = $LeaderHeading + 256 - $NewSquadHeading;
			}
			$CurNPC->SetEntityVariable(50, $CurDist);	# Original Distance from Leader
			$CurNPC->SetEntityVariable(51, $HeadingDiff);	# Original Heading Difference from Leader
			$NewX = $NewX + $Distance;
			$ArrayEntry++;
		}
		$NewY = $NewY + $Distance;
		$NewX = $SpawnX;
	}

}


#Usage: plugin::SpawnInFormation(NPCID, LeaderMob, Distance, Columns, Rows, [LeadDist, MaxZDiff]);
# NPCID is the NPC Type ID of the NPCs you want to form the squad formation with
# LeaderMob is the Client or NPC you want to spawn the formation behind (use invisible and/or temp NPC to show no leader)
# Distance is the distance each member of the formation will be from each other on both axis
# Columns is the number of columns you want in the formation
# Rows is the number of rows you want in the formation
# LeadDist is the distance you want the formation to be created behind the leader
# MaxZDiff is the maximum height above the leader that the best Z will be searched for

sub SpawnInFormation {
	my $NPCID = $_[0];
	my $Mob = $_[1];
	my $Distance = $_[2];
	my $Columns = $_[3];
	my $Rows = $_[4];
	my $LeadDist = $_[5];
	my $MaxZDiff = $_[6];
	
	if (!$MaxZDiff)
	{
		$MaxZDiff = 15;
	}
	
	if (!$LeadDist)
	{
		$LeadDist = $Distance;
	}
	
	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $entity_list = plugin::val('$entity_list');
	
	if (!$Mob) { 
		if ($npc->IsNPC())
		{
			$Mob = $npc;
		}
		elsif ($client->IsClient())
		{
			$Mob = $client;
		}
		else
		{
			return; # No Client or NPC found
		}
	}
	
	my $SpawnX = $Mob->GetX();
	my $SpawnY = $Mob->GetY();
	my $SpawnZ = $Mob->GetZ();

	my $LeaderHeading = $Mob->GetHeading();
	$SpawnX = $SpawnX + $LeadDist;
	$SpawnY = $SpawnY - (($Columns - 1) * $Distance / 2);

	my $NewX = $SpawnX;
	my $NewY = $SpawnY;

	for ($ColNum = 0; $ColNum < $Columns; $ColNum++)
	{
		#plugin::Debug("Column $ColNum");
		for ($RowNum = 0; $RowNum < $Rows; $RowNum++)
		{
			#plugin::Debug("Row $RowNum");
			my $SquadHeading = $Mob->CalculateHeadingToTarget($NewX, $NewY);
			my $CurDist = plugin::DistNoZToCoords($Mob, $NewX, $NewY);
			my $DestH = 0;
			
			if ($LeaderHeading > ($SquadHeading - 64))
			{
				$DestH = $LeaderHeading - $SquadHeading - 64;
			}
			else
			{
				$DestH = 256 + $LeaderHeading - $SquadHeading - 64;
			}
			
			my @DestArray = plugin::CalcDestFromHeading($DestH, $CurDist, $Mob, $MaxZDiff);
			my $DestX = $DestArray[0];
			my $DestY = $DestArray[1];
			my $DestZ = $DestArray[2];

			my $EntityID = quest::spawn2($NPCID, 0, 0, $DestX, $DestY, $DestZ, $LeaderHeading);
			my $CurNPC = $entity_list->GetNPCByID($EntityID);
			my $NewSquadHeading = $Mob->CalculateHeadingToTarget($DestX, $DestY);
			my $HeadingDiff = 0;
			if ($LeaderHeading > $NewSquadHeading) {
				$HeadingDiff = $LeaderHeading - $NewSquadHeading;
			}
			else {
				$HeadingDiff = $LeaderHeading + 256 - $NewSquadHeading;
			}
			$CurNPC->SetEntityVariable(50, $CurDist);	# Original Distance from Leader
			$CurNPC->SetEntityVariable(51, $HeadingDiff);	# Original Heading Difference from Leader
			$NewX = $NewX + $Distance;
		}
		$NewY = $NewY + $Distance;
		$NewX = $SpawnX;
	}

}


#Usage: plugin::SpawnInFormationXY(NPCID, X, Y, Z, Distance, Columns, Rows, Heading, MaxZDiff);
# NPCID is the NPC Type ID of the NPCs you want to form the squad formation with
# X/Y/Z are the coords of the first NPC to spawn that the others are spawned based on it's location
# Distance is the distance each member of the formation will be from each other on both axis
# Columns is the number of columns you want in the formation
# Rows is the number of rows you want in the formation
# Heading is heading for the spawns to face
# MaxZDiff is the maximum height above the leader that the best Z will be searched for

sub SpawnInFormationXY {
	my $NPCID = $_[0];
	my $SpawnX = $_[1];
	my $SpawnY = $_[2];
	my $SpawnZ = $_[3];
	my $Distance = $_[4];
	my $Columns = $_[5];
	my $Rows = $_[6];
	my $Heading = $_[7];
	my $MaxZDiff = $_[8];
	
	if (!$Heading)
	{
		$Heading = 0;
	}
	if (!$MaxZDiff)
	{
		$MaxZDiff = 15;
	}

	my $entity_list = plugin::val('$entity_list');
	
	$SpawnX = $SpawnX + $Distance;
	$SpawnY = $SpawnY - (($Columns - 1) * $Distance / 2);
	
	# Spawn the first NPC
	quest::spawn2($NPCID, 0, 0, $SpawnX, $SpawnY, $SpawnZ, $Heading);
	#plugin::Debug("Spawned First NPC");
	
	# Get the first NPC
	my $MainNPC = $entity_list->GetNPCByNPCTypeID($NPCID);
	#plugin::Debug("Got First NPC");
	my $NewX = $SpawnX;
	my $NewY = $SpawnY;
	my $RowNum = 1;	# Prevent Respawn over the first NPC
	$NewX = $NewX + $Distance;
	
	if ($MainNPC)
	{
		for ($ColNum = 0; $ColNum < $Columns; $ColNum++)
		{
			#plugin::Debug("Column $ColNum");
			for ($RowNum; $RowNum < $Rows; $RowNum++)
			{
				#plugin::Debug("Row $RowNum");
				my $NewZ = $MainNPC->FindGroundZ($NewX, $NewY, $MaxZDiff);
				quest::spawn2($NPCID, 0, 0, $NewX, $NewY, $NewZ, $Heading);
				$NewX = $NewX + $Distance;
			}
			$NewY = $NewY + $Distance;
			$NewX = $SpawnX;
			$RowNum = 0;
		}
	}
}


#Usage: plugin::FollowInFormation(NPCID, Mob, [MaxZDiff]);
# This script will move all of 1 NPC Type ID to the relative Guard position that they were the first time this plugin was run
# NPCID is the NPC ID of the NPCs you want to move in the formation (moves all of this NPC ID in the whole zone)
# Mob is the leader NPC that you want the NPCs to follow in formation
# MaxZDiff is an optional field (default of 30) to set the Max Z the NPCs will check to find the ground Z location

sub FollowInFormation {

	my $entity_list = plugin::val('$entity_list');
	my $SquadNPCID = $_[0];
	my $Mob = $_[1];
	my $MaxZDiff = $_[2];
	my $MoveToX = $Mob->GetX();
	my $MoveToY = $Mob->GetY();
	
	if(!$MaxZDiff) { $MaxZDiff = 15; }

	my $NewHeading = $Mob->GetHeading();
	my $RunSpeed = 0;
	
	my @npclist = $entity_list->GetNPCList();
	foreach $ent (@npclist)
	{
		if($ent->GetNPCTypeID() == $SquadNPCID) {
			
			# Set the Initial Relative Position and Heading variables
			if(!$ent->EntityVariableExists(50)) {	# If this doesn't exist, then neither of them do
				my $SquadX = $ent->GetX();
				my $SquadY = $ent->GetY();
				my $SquadHeading = $Mob->CalculateHeadingToTarget($SquadX, $SquadY);
				my $CurHeading = $Mob->GetHeading();
				my $StartDist = plugin::DistNoZ($Mob, $ent);
				my $HeadingDiff = 0;
				if ($CurHeading > $SquadHeading) {
					$HeadingDiff = $CurHeading - $SquadHeading;
				}
				else {
					$HeadingDiff = $CurHeading + 256 - $SquadHeading;
				}
				$ent->SetEntityVariable(50, $StartDist);	# Original Distance from Leader
				$ent->SetEntityVariable(51, $HeadingDiff);	# Original Heading Difference from Leader
			}
			my $SquadDist = $ent->GetEntityVariable(50);
			my $HDiff = $ent->GetEntityVariable(51);
			#plugin::Debug("Got Variables");
			
			#Set Run Speed depending on distance from the leader
			if(!$RunSpeed)
			{
				my $CurDist = plugin::DistNoZ($Mob, $ent);
				if ($CurDist > ($SquadDist * 10))
				{
					$RunSpeed = 5;
				}
				elsif($CurDist > ($SquadDist * 5))
				{
					$RunSpeed = 3;
				}
				else
				{
					$RunSpeed = 1.25;
				}
			}
			$ent->ModifyNPCStat("runspeed", $RunSpeed);
			
			my $NewSquadHeading = 0;
			if ($NewHeading - $HDiff > 0) {
				$NewSquadHeading = $NewHeading - $HDiff;
			}
			else {
				$NewSquadHeading = 256 + $NewHeading - $HDiff;
			}
			#plugin::Debug("Before Calc");
			#Usage: my @DestArray = plugin::CalcDestFromHeadingXY(Heading, Distance, X, Y, [MaxZDiff, Mob]);
			my @DestArray = plugin::CalcDestFromHeadingXY($NewSquadHeading, $SquadDist, $MoveToX, $MoveToY, $MaxZDiff, $Mob);
			my $NewX = $DestArray[0];
			my $NewY = $DestArray[1];
			my $NewZ = $DestArray[2];
			#plugin::Debug("After Calc");
			$ent->MoveTo($NewX, $NewY, $NewZ, $NewHeading, 1);
			#plugin::Debug("Moving NPC");
		}
	}
}


#Usage: plugin::SquadAttackTarget(SquadNPCID, TargetID);
# Squad1NPCID is the NPC ID of the Squad attacking the other
# TargetID is the Entity ID of the Target being attacked

sub SquadAttackTarget {

	my $entity_list = plugin::val('$entity_list');
	my $SquadNPCID = $_[0];
	my $TargetID = $_[1];
	
	my $TargetNPC = $entity_list->GetNPCByID($TargetID);	# Gets the NPC from the array
	if ($TargetNPC)
	{
		if($TargetNPC->EntityVariableExists(50))	# If this exists, the target is part of a squad
		{
			# Have the squad attack the other squad
			my $TargetNPCID = $TargetNPC->GetNPCTypeID();
			plugin::SquadAttackSquad($SquadNPCID, $TargetNPCID);
		}
		else
		{
			my @npclist = $entity_list->GetNPCList();
			foreach $ent (@npclist)
			{
				# Have the squad attack the single target
				if($ent->GetNPCTypeID() == $SquadNPCID) {
					$ent->AddToHateList($TargetNPC, 1);
				}
			}
		}
	}

}


#Usage: plugin::SquadAttackSquad(Squad1NPCID, Squad2NPCID);
# Squad1NPCID is the NPC ID of the Squad attacking the other squad
# Squad2NPCID is the NPC ID of the Squad being attacked

sub SquadAttackSquad {

	my $entity_list = plugin::val('$entity_list');
	my $SquadNPCID1 = $_[0];
	my $SquadNPCID2 = $_[1];

	my @npclist = $entity_list->GetNPCList();
	my @Squad1 = (); # Creates a cleared array
	my @Squad2 = (); # Creates a cleared array
	foreach $ent (@npclist)
	{
		if($ent->GetNPCTypeID() == $SquadNPCID1) {
			push(@Squad1, $ent->GetID());	# Adds an element to the end of the array
		}
		if($ent->GetNPCTypeID() == $SquadNPCID2) {
			push(@Squad2, $ent->GetID());	# Adds an element to the end of the array
		}
	}
	my $ArraySize1 = @Squad1;
	my $ArraySize2 = @Squad2;
	#plugin::Debug("Squad1: $ArraySize1, Squad2: $ArraySize2");
	
	foreach $EntID (@Squad1)
	{
		if ($EntID)
		{
			my $GotSquadNPC = $entity_list->GetNPCByID($EntID);	# Gets the NPC from the array
			my $RandNPC = plugin::RandomRange(0, $ArraySize2);	# Pick one of the chosen NPCs
			my $GotNPC2 = $entity_list->GetNPCByID($Squad2[$RandNPC]);	# Gets the NPC from the array
			#AddToHateList(other, hate= 0, damage= 0, iYellForHelp= true, bFrenzy= false, iBuffTic= false)
			if ($GotNPC2 && $GotNPC2->IsNPC())
			{
				$GotSquadNPC->AddToHateList($GotNPC2, 1, 0, 0);
			}
			else
			{
				my $GotNPCDefault = $entity_list->GetNPCByID($Squad2[0]);	# Gets the NPC from the array
				$GotSquadNPC->AddToHateList($GotNPCDefault, 1, 0, 0);
			}
		}
	}

}


#Usage: my @DestArray = plugin::CalcDestFromHeading(Heading, Distance, [Mob, MaxZDiff]);
# This plugin calculates the destination X and Y loc based on heading and distance
# Heading is the heading you want to calculate destination X Y Z from
# Distance is the distance you want for the destination X and Y from the source
# Mob is an optional field to allow any mob to be set, but $npc is default
# MaxZDiff is the max height difference from the source mob's Z you want to calculate the destination from.
#
# The output array can be used as follows:
# my $DestX = $DestArray[0];
# my $DestY = $DestArray[1];
# my $DestZ = $DestArray[2];

sub CalcDestFromHeading {

	my $npc = plugin::val('$npc');
	my $Heading = $_[0];
	my $Distance = $_[1];
	my $Mob = $_[2];
	my $MaxZDiff = $_[3];
	
	if (!$Distance) { return; }
	if (!$Mob) { $Mob = $npc; }
	if (!$MaxZDiff) { $MaxZDiff = 50; }
	
	my $ReverseHeading = 256 - $Heading;
	my $ConvertAngle = $ReverseHeading * 1.40625;
	if ($ConvertAngle <= 270) {
		$ConvertAngle = $ConvertAngle + 90;
	}
	else {
		$ConvertAngle = $ConvertAngle - 270;
	}
	my $Radian = $ConvertAngle * (3.1415927 / 180);

	my $CircleX = $Distance * cos($Radian);
	my $CircleY = $Distance * sin($Radian);
	my $DestX = $CircleX + $Mob->GetX();
	my $DestY = $CircleY + $Mob->GetY();
	my $DestZ = $Mob->FindGroundZ($DestX, $DestY, $MaxZDiff);
	my @DestArray = ($DestX, $DestY, $DestZ);
	return @DestArray;
}


#Usage: my @DestArray = plugin::CalcDestFromHeadingXY(Heading, Distance, X, Y, [MaxZDiff, Mob]);
# This plugin calculates the destination X and Y loc based on heading and distance
# Heading is the heading you want to calculate destination X Y Z from
# Distance is the distance you want for the destination X and Y from the source
# X is the X loc that the destination will be calculated from
# Y is the Y loc that the destination will be calculated from
# MaxZDiff is the max height difference from the source mob's Z you want to calculate the destination from.
#
# The output array can be used as follows:
# my $DestX = $DestArray[0];
# my $DestY = $DestArray[1];
# my $DestZ = $DestArray[2];

sub CalcDestFromHeadingXY {

	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $Heading = $_[0];
	my $Distance = $_[1];
	my $MobX = $_[2];
	my $MobY = $_[3];
	my $MaxZDiff = $_[4];
	my $Mob = $_[5];

	if (!$Mob) { 
		if ($npc->IsNPC())
		{
			$Mob = $npc;
		}
		elsif ($client->IsClient())
		{
			$Mob = $client;
		}
		else
		{
			return; # No Client or NPC found
		}
	}
	if (!$Distance) { return; }
	if (!$MaxZDiff) { $MaxZDiff = 50; }
	
	my $ReverseHeading = 256 - $Heading;
	my $ConvertAngle = $ReverseHeading * 1.40625;
	if ($ConvertAngle <= 270) {
		$ConvertAngle = $ConvertAngle + 90;
	}
	else {
		$ConvertAngle = $ConvertAngle - 270;
	}
	my $PI = 3.141592653589793238;

	my $Radian = $ConvertAngle * ($PI / 180);

	my $CircleX = $Distance * cos($Radian);
	my $CircleY = $Distance * sin($Radian);
	my $DestX = $CircleX + $MobX;
	my $DestY = $CircleY + $MobY;
	my $DestZ = $Mob->FindGroundZ($DestX, $DestY, $MaxZDiff);
	my @DestArray = ($DestX, $DestY, $DestZ);
	return @DestArray;
}


#Usage: plugin::GetReverseHeading($mob);
# Returns the heading of the opposite direction the mob is facing

sub GetReverseHeading {
	my $Mob = $_[0];
	
	my $CurHeading = $Mob->GetHeading();
	my $ReverseHeading = 128 + $CurHeading;
	if ($ReverseHeading >= 256) {
		$ReverseHeading = $ReverseHeading - 256;
	}
	return $ReverseHeading;
}


#Usage: my $Degrees = plugin::ConvertHeadingToDegrees(Heading);
# Converts 0-256 headings into 0 to 360 degrees

sub ConvertHeadingToDegrees {

	my $Heading = $_[0];
	
	my $ReverseHeading = 256 - $Heading;
	my $ConvertAngle = $ReverseHeading * 1.40625;
	if ($ConvertAngle <= 270) {
		$ConvertAngle = $ConvertAngle + 90;
	}
	else {
		$ConvertAngle = $ConvertAngle - 270;
	}

	return $ConvertAngle;
}

#Usage: plugin::Debug("Message", Color, Mob);
# "Message" is a required field and is the message you want to show up in the debug
# Color is an optional field and if not set will default to a pink/purple color
# Mob is an optional field if you want to export a particular mob to debug from (used for getting the name)
# Example 1: plugin::Debug("Event Started", 7);
# Example 2: plugin::Debug("Event Started");

sub Debug {

	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $MyMessage = $_[0];
	my $TextColor = $_[1];
	if (!$TextColor)
	{
		$TextColor = 326;	#Set the Text Color for the Message (this one is bright purple)
	}
	my $Mob = $_[2];
	
	if (!$Mob)
	{
		if ($npc)
		{
			# NPC Quest
			$Mob = $npc;
		}
		elsif ($client)
		{
			# Player Quest
			$Mob = $client;
		}
	}
	my $MobName = "NO_NAME";
	if ($Mob) {
		#Get the clean name of the Mob sending the message
		$MobName = $Mob->GetCleanName();	
	}
	
	#Send a message in purple (default) to GMs in the Zone only
	quest::gmsay("$MobName Debugs: $MyMessage", $TextColor);	
}


return 1;	#This line is required at the end of every plugin file in order to use it
3. Save that file to your server /plugins/ folder and name it "formation_tools.pl".

4. Do a #questreload and the new plugin should be ready for use

I am not going to give examples of using each of these plugins, but I will give a simple example of spawning a formation and having them follow a leader NPC.

Note the example below uses 2 NPCs, both are custom made, so you will need to change the NPC IDs to match the leader and followers you want to use. The Leader NPC is set to spawn like a normal NPC and the followers are set to not spawn (they are spawned by the leader). Make sure that the following NPCs you create are unique in the zone and don't spawn elsewhere in the zone already, otherwise, any of those same NPCs will also want to follow the Leader. Once you have the Leader NPC created, just create a path grid for it to walk on and do a #repop and #reloadquest and it should work automatically.

Code:
# 2701876 - #Kobold_Captain_Buark
# 2700096 - Kobold_Patroller

my $LeaderNPCID = 2701876;
my $SquadNPCID = 2700096;
my $SpawnDist = 10;

sub EVENT_SPAWN {

	quest::depopall($SquadNPCID);
	quest::settimer("spawn_guards", 2);
	
}

sub EVENT_TIMER {

	if ($timer eq "spawn_guards") {
		quest::stoptimer("spawn_guards");
		#Usage: plugin::SpawnInFormation(NPCID, LeaderMob, Distance, Columns, Rows, [LeadDist, MaxZDiff]);
		plugin::SpawnInFormation($SquadNPCID, $npc, $SpawnDist, 2, 2, 10, 10);
		#Usage: plugin::MoveInFormation(NPCID, [$npc = 0, OnGrid = 0, MoveToX = 0, MoveToY = 0, NoSpeedBuffs = false]);
		plugin::MoveInFormation($SquadNPCID, $npc , 1);
	}

	if ($timer eq "WPArrive") {
		quest::stoptimer("WPArrive");
		plugin::MoveInFormation($SquadNPCID, $npc, 1);
	}
	
}

my $MoveSquad = 1;
sub EVENT_WAYPOINT_DEPART {

	if ($MoveSquad)
	{
		quest::settimer("WPArrive", 0);
	}
	else {
		quest::stop();
	}

}
I would recommend not overdoing it with these plugins, as they do quite a bit of work. Also, I don't recommend making huge formations of NPCs, as that will add up to lots of lag for players really quickly. Just play around with it until you find the results you want.

Let me know if there are any issues (reply to this thread), and I will try to look into them when I have time.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote