mirror of
https://github.com/ProjectEQ/projecteqquests
synced 2026-08-12 00:26:10 -04:00
452 lines
No EOL
12 KiB
Perl
452 lines
No EOL
12 KiB
Perl
#Usage: my $LoSDistance = plugin::GetMaxLoSDistFromHeading(Heading, [DistIncrements, MaxDist, Mob]);
|
|
# This plugin attempts to get the max visible distance from the specified heading for the specified mob
|
|
# It returns the Max LoS that it can find for the settings provided
|
|
# Heading is the direction to get the max LoS for.
|
|
# DistIncrements is how much the distance check will increase each loop it does (default 20).
|
|
# Higher distance is more efficient but less precise.
|
|
# MaxDist is the maximum distance to check up to for LoS (default 200).
|
|
# Mob is an optional field to set the source NPC/Client (default is current NPC)
|
|
sub GetMaxLoSDistFromHeading {
|
|
|
|
my $npc = plugin::val('$npc');
|
|
my $client = plugin::val('$client');
|
|
my $Heading = $_[0];
|
|
my $DistIncrements = $_[1];
|
|
my $MaxDist = $_[2];
|
|
my $Mob = $_[3];
|
|
if (!$DistIncrements)
|
|
{
|
|
$DistIncrements = 20;
|
|
}
|
|
if (!$MaxDist)
|
|
{
|
|
$MaxDist = 200;
|
|
}
|
|
if (!$Mob)
|
|
{
|
|
$Mob = $npc;
|
|
}
|
|
my $LoSMobSize = 5;
|
|
my $MaxZDiff = 10;
|
|
my $MaxLoS = 0;
|
|
my $OrigX = $Mob->GetX();
|
|
my $OrigY = $Mob->GetY();
|
|
for ($LoSDistCheck = $DistIncrements; $LoSDistCheck <= $MaxDist; $LoSDistCheck += $DistIncrements)
|
|
{
|
|
my $Degrees = plugin::ConvertHeadingToDegrees($Heading);
|
|
#plugin::Debug("Current Distance Check: $LoSDistCheck || Degrees $Degrees");
|
|
|
|
my $PI = 3.141592653589793238;
|
|
my $Radian = $Degrees * ($PI / 180);
|
|
|
|
my $CircleX = $LoSDistCheck * cos($Radian);
|
|
my $CircleY = $LoSDistCheck * sin($Radian);
|
|
my $DestX = $CircleX + $OrigX;
|
|
my $DestY = $CircleY + $OrigY;
|
|
my $DestZ = $Mob->FindGroundZ($DestX, $DestY, $MaxZDiff);
|
|
my $LoS_Check = $Mob->CheckLoSToLoc($DestX, $DestY, $DestZ, $LoSMobSize);
|
|
if ($LoS_Check)
|
|
{
|
|
#plugin::Debug("LoS Success");
|
|
# If LoS is successful at this distance, continue the loop to check the next distance
|
|
$MaxLoS = $LoSDistCheck;
|
|
}
|
|
else
|
|
{
|
|
# Once LoS fails, end the loop
|
|
$LoSDistCheck = $MaxDist + 1;
|
|
}
|
|
}
|
|
|
|
return $MaxLoS;
|
|
|
|
}
|
|
|
|
|
|
#Usage: plugin::FaceBestHeading([MinLoSDist]);
|
|
# This plugin attempts to find a good heading that isn't facing a wall/structure that blocks LoS
|
|
# If the NPC is currently facing a wall, this will look for the clearest/longest view available and point that way
|
|
# It returns the best heading that it finds as well
|
|
# MinLoSDist is the minimum distance to start searching for better headings (default 20)
|
|
# If the NPC is already facing a heading that has further LoS than MinLoSDist, it will not change heading
|
|
sub FaceBestHeading {
|
|
|
|
my $npc = plugin::val('$npc');
|
|
my $MinLoSDist = $_[0];
|
|
|
|
if (!$MinLoSDist)
|
|
{
|
|
$MinLoSDist = 20;
|
|
}
|
|
|
|
# Build an array for the best matching headings
|
|
my @BestHeadings = ();
|
|
|
|
my $CurHeading = $npc->GetHeading();
|
|
my $LoSDistance = plugin::GetMaxLoSDistFromHeading($CurHeading);
|
|
my $MaxLoSDistance = $LoSDistance;
|
|
my $EqualHeadings = 0;
|
|
|
|
if ($LoSDistance < $MinLoSDist)
|
|
{
|
|
# Check headings in 16 directions for the best one(s)
|
|
for ($HeadingCheck = 0; $HeadingCheck < 256; $HeadingCheck += 16)
|
|
{
|
|
$LoSDistance = plugin::GetMaxLoSDistFromHeading($HeadingCheck, 5, 35);
|
|
if ($LoSDistance >= $MaxLoSDistance)
|
|
{
|
|
if ($LoSDistance == $MaxLoSDistance && $EqualHeadings > 0)
|
|
{
|
|
# If this headings max LoS is equal to the previous one, add it to the array of best headings
|
|
push(@BestHeadings, $HeadingCheck);
|
|
# For equal headings to be added to the array, they must all be consecutive
|
|
$EqualHeadings++;
|
|
}
|
|
else
|
|
{
|
|
@BestHeadings = ($HeadingCheck);
|
|
#plugin::Debug("Array Set to $HeadingCheck");
|
|
$MaxLoSDistance = $LoSDistance;
|
|
# Reset the equal headings count to 1 when a new max is found
|
|
$EqualHeadings = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Set equal headings to 0 if the current heading is less than the current Max LoS
|
|
# This ensures that equal headings will always be consecutive
|
|
$EqualHeadings = 0;
|
|
}
|
|
}
|
|
my $NewHeading = $CurHeading;
|
|
my $BestCount = @BestHeadings;
|
|
|
|
# If the Heading Array has entries, use the one in the middle of the array
|
|
if ($BestCount)
|
|
{
|
|
# Choose the best heading in the middle of the array to face the most open/clear angle
|
|
my $ArrayPick = int($BestCount / 2);
|
|
$NewHeading = $BestHeadings[$ArrayPick];
|
|
}
|
|
|
|
$npc->SetHeading($NewHeading);
|
|
$npc->SendPosition();
|
|
}
|
|
return $NewHeading;
|
|
|
|
}
|
|
|
|
|
|
#Usage: my $ShortestHeading = plugin::HeadingToShortestLoS([MaxDistToCheck=8]);
|
|
# MaxDistToCheck is the maximum distance to check for nearby LoS blocking
|
|
sub HeadingToShortestLoS {
|
|
|
|
my $npc = plugin::val('$npc');
|
|
my $MaxDistToCheck = $_[0];
|
|
|
|
if (!$MaxDistToCheck)
|
|
{
|
|
$MaxDistToCheck = 8;
|
|
}
|
|
|
|
my $OrigX = $npc->GetX();
|
|
my $OrigY = $npc->GetY();
|
|
my $OrigZ = $npc->GetZ();
|
|
my $ZeroLoSCheck = $npc->CheckLoSToLoc(($OrigX + 0.1), $OrigY, $OrigZ, 5);
|
|
if (!$ZeroLoSCheck)
|
|
{
|
|
# If this check fails, they are probably stuck in a wall
|
|
return -2;
|
|
}
|
|
|
|
my @BestHeadings = ();
|
|
|
|
my $CurHeading = $npc->GetHeading();
|
|
my $MinLoSDistance = 5000;
|
|
|
|
for ($HeadingCheck = 0; $HeadingCheck < 256; $HeadingCheck += 16)
|
|
{
|
|
$LoSDistance = plugin::GetMaxLoSDistFromHeading($HeadingCheck, 1, $MaxDistToCheck);
|
|
if ($LoSDistance <= $MinLoSDistance)
|
|
{
|
|
if ($LoSDistance == $MinLoSDistance)
|
|
{
|
|
push(@BestHeadings, $HeadingCheck);
|
|
}
|
|
else
|
|
{
|
|
@BestHeadings = ($HeadingCheck);
|
|
$MinLoSDistance = $LoSDistance;
|
|
}
|
|
}
|
|
}
|
|
my $NewHeading = $CurHeading;
|
|
my $BestCount = @BestHeadings;
|
|
|
|
# If the Heading Array has entries, use the one in the middle of the array
|
|
if ($BestCount && $BestCount < 15)
|
|
{
|
|
my $ArrayPick = int($BestCount / 2);
|
|
$NewHeading = $BestHeadings[$ArrayPick];
|
|
}
|
|
else
|
|
{
|
|
# Return -1 if no nearby objects found blocking LoS
|
|
return -1;
|
|
}
|
|
|
|
return $NewHeading;
|
|
|
|
}
|
|
|
|
|
|
#Usage: my $NPCMoved = plugin::MoveAwayFromWall([MinLoSDist=5]);
|
|
# MinLoSDist is the minimum distance to start searching for better headings (default 40)
|
|
# If the NPC is already facing a heading that has further LoS than MinLoSDist, it will not change heading
|
|
sub MoveAwayFromWall {
|
|
|
|
my $npc = plugin::val('$npc');
|
|
|
|
my $ShortestHeading = plugin::HeadingToShortestLoS();
|
|
if ($ShortestHeading == -2)
|
|
{
|
|
# Stuck in a wall
|
|
#$npc->Depop();
|
|
return -1;
|
|
}
|
|
elsif ($ShortestHeading == -1)
|
|
{
|
|
# Not too close to a wall
|
|
#plugin::FaceBestHeading(25);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
# Close to a wall
|
|
my @DestArray = plugin::CalcDestFromHeading($ShortestHeading, 5);
|
|
my $DestX = $DestArray[0];
|
|
my $DestY = $DestArray[1];
|
|
my $DestZ = $DestArray[2];
|
|
my $LoS_Check = $npc->CheckLoSToLoc($DestX, $DestY, $DestZ, 5);
|
|
|
|
# If this check fails, they are too close to the wall
|
|
if (!$LoS_Check)
|
|
{
|
|
# Face them toward the wall
|
|
$npc->SetHeading($ShortestHeading);
|
|
# Get the reverse heading from the wall
|
|
my $ReverseHeading = plugin::GetReverseHeading();
|
|
#my $LoSDistance = plugin::GetMaxLoSDistFromHeading($ReverseHeading, 1, 5);
|
|
@DestArray = plugin::CalcDestFromHeading($ReverseHeading, 10);
|
|
$DestX = $DestArray[0];
|
|
$DestY = $DestArray[1];
|
|
$DestZ = $DestArray[2];
|
|
$LoS_Check = $npc->CheckLoSToLoc($DestX, $DestY, $DestZ, 5);
|
|
# If there is enough room, move them the opposite direction from the nearest wall
|
|
if ($LoS_Check)
|
|
{
|
|
@DestArray = plugin::CalcDestFromHeading($ReverseHeading, 10);
|
|
$DestX = $DestArray[0];
|
|
$DestY = $DestArray[1];
|
|
$DestZ = $DestArray[2];
|
|
$npc->GMMove($DestX, $DestY, ($DestZ + 1), $ReverseHeading);
|
|
# Return true if moved
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
# Not enough room to move toward or away from the wall
|
|
# Move toward the best heading
|
|
my $BestHeading = plugin::FaceBestHeading();
|
|
@DestArray = plugin::CalcDestFromHeading($BestHeading, 6);
|
|
$DestX = $DestArray[0];
|
|
$DestY = $DestArray[1];
|
|
$DestZ = $DestArray[2];
|
|
$npc->GMMove($DestX, $DestY, ($DestZ + 1), $BestHeading);
|
|
# Return true if moved
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# If no move, return false
|
|
return 0;
|
|
}
|
|
|
|
|
|
#Usage: plugin::MoveToFirstBestZ();
|
|
sub MoveToFirstBestZ {
|
|
|
|
my $npc = plugin::val('$npc');
|
|
my $x = plugin::val('$x');
|
|
my $y = plugin::val('$y');
|
|
my $h = plugin::val('$h');
|
|
$npc->GMMove($x, $y, 1000, $h);
|
|
my $GroundZ = $npc->FindGroundZ($x, $y, 3);
|
|
if($GroundZ > -5000)
|
|
{
|
|
$npc->GMMove($x, $y, ($GroundZ -10), $h);
|
|
$NextGroundZ = $npc->FindGroundZ($x, $y, 3);
|
|
if($NextGroundZ > -5000)
|
|
{
|
|
$npc->GMMove($x, $y, $NextGroundZ, $h);
|
|
}
|
|
else
|
|
{
|
|
$npc->GMMove($x, $y, $GroundZ, $h);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#Usage: plugin::SpawnZone(X, Y, Z, Distance, Variance, Columns, Rows);
|
|
# This is used to spawn a grid of NPCs and can be used to spawn an entire zone
|
|
# 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
|
|
# Variance is the max distance of scatter effect you want on the grid positioning to make it look less like a grid
|
|
# Columns is the number of columns you want in the formation
|
|
# Rows is the number of rows you want in the formation
|
|
|
|
sub SpawnZone {
|
|
|
|
my $entity_list = plugin::val('$entity_list');
|
|
my $SpawnX = $_[0];
|
|
my $SpawnY = $_[1];
|
|
my $SpawnZ = $_[2];
|
|
my $Distance = $_[3];
|
|
my $Variance = $_[4];
|
|
my $Columns = $_[5];
|
|
my $Rows = $_[6];
|
|
|
|
$Heading = plugin::RandomRange(0, 254);
|
|
$MaxZDiff = 3;
|
|
|
|
# Create the array of NPCIDs to spawn in the grid
|
|
my @NPCIDList = ();
|
|
my $NPCNum = 7;
|
|
while ($_[$NPCNum])
|
|
{
|
|
push(@NPCIDList, $_[$NPCNum]);
|
|
$NPCNum++;
|
|
}
|
|
|
|
my $ListLength = (@NPCIDList) - 1;
|
|
my $FirstNPCID = $NPCIDList[0];
|
|
|
|
# Set the first NPC to spawn directly in the center of the grid
|
|
$SpawnX = $SpawnX - (($Rows - 1) * $Distance / 2);
|
|
$SpawnY = $SpawnY - (($Columns - 1) * $Distance / 2);
|
|
# Spawn the first NPC
|
|
quest::spawn2($FirstNPCID, 0, 0, $SpawnX, $SpawnY, $SpawnZ, $Heading);
|
|
|
|
# Get the first NPC
|
|
my $MainNPC = $entity_list->GetNPCByNPCTypeID($FirstNPCID);
|
|
|
|
my $NewX = $SpawnX;
|
|
my $NewY = $SpawnY;
|
|
$NewX = $NewX + $Distance;
|
|
|
|
if ($MainNPC)
|
|
{
|
|
for ($ColNum = 0; $ColNum < $Columns; $ColNum++)
|
|
{
|
|
for ($RowNum = 0; $RowNum < $Rows; $RowNum++)
|
|
{
|
|
# Prevent Respawn over the first NPC
|
|
if ($RowNum > 0 || $ColNum > 0)
|
|
{
|
|
my $RandXDiff = plugin::RandomRange(0, $Variance);
|
|
my $RandYDiff = plugin::RandomRange(0, $Variance);
|
|
# Set even numbers to negative
|
|
if ($RandXDiff && ($RandXDiff / 2) == int($RandXDiff / 2))
|
|
{
|
|
$RandXDiff *= -1;
|
|
}
|
|
if ($RandYDiff && ($RandYDiff / 2) == int($RandYDiff / 2))
|
|
{
|
|
$RandYDiff *= -1;
|
|
}
|
|
my $ModX = $NewX + $RandXDiff;
|
|
my $ModY = $NewY + $RandYDiff;
|
|
my $NewZ = $MainNPC->FindGroundZ($ModX, $ModY, $MaxZDiff);
|
|
|
|
if ($NewZ > -5000)
|
|
{
|
|
my $RandomNPCNum = plugin::RandomRange(0, $ListLength);
|
|
my $NPCID = $NPCIDList[$RandomNPCNum];
|
|
my $RandHeading = plugin::RandomRange(0, 254);
|
|
quest::spawn2($NPCID, 0, 0, $ModX, $ModY, $NewZ, $RandHeading);
|
|
}
|
|
$NewX = $NewX + $Distance;
|
|
}
|
|
}
|
|
$NewY = $NewY + $Distance;
|
|
$NewX = $SpawnX;
|
|
}
|
|
}
|
|
}
|
|
|
|
#moelib_spawn_block(npctypeid, fromx, tox, fromy, toy, space, zposition=20, heading=0, grid=0)
|
|
sub moelib_spawn_block {
|
|
my $npctypeid = $_[0];
|
|
my $fromx = $_[1];
|
|
my $tox = $_[2];
|
|
my $fromy = $_[3];
|
|
my $toy = $_[4];
|
|
my $space = $_[5];
|
|
my $z = $_[6] || 20;
|
|
my $heading = $_[7] || 0;
|
|
my $grid = $_[8] || 0;
|
|
my $count = 0;
|
|
for ($x = $fromx; $x <= $tox; $x += $space) {
|
|
for ($y = $fromy; $y <= $toy; $y += $space) {
|
|
$count++;
|
|
quest::spawn2($npctypeid,$grid,0,($x), ($y), $z,$heading);
|
|
}
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
#moelib_spawn_block_center(npctypeid, centerx, centery, range, amount, zposition=20, heading=0, grid=0)
|
|
sub moelib_spawn_block_center {
|
|
my $npctypeid = $_[0];
|
|
my $centerx = $_[1];
|
|
my $centery = $_[2];
|
|
my $range = $_[3];
|
|
my $amount = $_[4];
|
|
my $z = $_[5] || 20;
|
|
my $heading = $_[6] || 0;
|
|
my $grid = $_[7] || 0;
|
|
my $fromx = $centerx-$range;
|
|
my $fromy = $centery-$range;
|
|
my $tox = $centerx+$range;
|
|
my $toy = $centery+$range;
|
|
my $size = $range*2;
|
|
my $space = $size / sqrt($amount);
|
|
$fromx += ($space / 2);
|
|
$fromy += ($space / 2);
|
|
return spawn_block($npctypeid, $fromx, $tox, $fromy, $toy, $space, $z, $heading, $grid);
|
|
}
|
|
|
|
|
|
#moelib_spawn_circle(npctypeid, centerx, centery, radius, amount, zposition=20, heading=0, grid=0)
|
|
sub moelib_spawn_circle {
|
|
my $npctypeid = $_[0];
|
|
my $centerx = $_[1];
|
|
my $centery = $_[2];
|
|
my $radius = $_[3];
|
|
my $amount = $_[4];
|
|
my $z = $_[5] || 20;
|
|
my $heading = $_[6] || 0;
|
|
my $grid = $_[7] || 0;
|
|
|
|
my $a = (2*3.14159)/$amount;
|
|
for ($i = 0; $i < $amount; $i++) {
|
|
$x = int(cos($a*$i)*$radius) + ($centerx - ($radius / 2));
|
|
$y = int(sin($a*$i)*$radius) + ($centery - ($radius / 2));
|
|
quest::spawn2($npctypeid, $grid, 0, ($x), ($y), $z, $heading);
|
|
}
|
|
}
|
|
|
|
return 1; #This line is required at the end of every plugin file in order to use it |