Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
514 changes: 210 additions & 304 deletions control/config.txt

Large diffs are not rendered by default.

88 changes: 82 additions & 6 deletions plugins/avoidObstacles/avoidObstacles.pl
Original file line number Diff line number Diff line change
Expand Up @@ -1193,17 +1193,58 @@ sub remove_prohibited_zone_from_cells {
}
}

## Purpose: Finds portal obstacle positions that are actually used by a route solution.
## Args: `($solution)`.
## Returns: A hashref keyed by `"x,y"` for portal cells present in the solution.
## Notes: This lets portal avoidance stay active for ordinary movement while
## exempting portals that are part of the planned route itself.
sub get_route_portal_positions {
my ($solution) = @_;
return {} unless $solution && @{$solution};

my %solution_cells;
foreach my $node (@{$solution}) {
next unless $node && defined $node->{x} && defined $node->{y};
$solution_cells{"$node->{x},$node->{y}"} = 1;
}

my %portal_positions;
foreach my $obstacle_id (keys %obstaclesList) {
my $obstacle = $obstaclesList{$obstacle_id};
next unless $obstacle && $obstacle->{type} && $obstacle->{type} eq 'portal';
my $pos = get_actor_position($obstacle);
next unless $pos;
next unless $solution_cells{"$pos->{x},$pos->{y}"};
$portal_positions{"$pos->{x},$pos->{y}"} = 1;
}

return \%portal_positions;
}

## Purpose: Detects whether a route destination is itself a known portal cell.
## Args: `($task)`.
## Returns: `1` when the destination matches a portal source on the current map.
## Notes: This is the safest early escape hatch for portal routes because it does
## not depend on the route solution already being built.
sub is_route_destination_portal {
my ($task) = @_;
return 0 unless $task && $field && $task->{dest} && $task->{dest}{pos};
return 1 if defined portalExists($field->baseName, $task->{dest}{pos});
return 0;
}

## Purpose: Loosens prohibited cells around a route destination when needed.
## Args: `($task, $prohibited_cells, $target_field)`.
## Args: `($task, $prohibited_cells, $target_field, $solution)`.
## Returns: The original or filtered prohibited-cell hashref.
## Notes: This exists so route tasks can still finish at destinations such as portals
## while keeping danger scoring and all other obstacle penalties intact.
sub filter_prohibited_cells_for_route_task {
my ($task, $prohibited_cells, $target_field) = @_;
my ($task, $prohibited_cells, $target_field, $solution) = @_;
return $prohibited_cells unless $task && $prohibited_cells && $target_field;
return $prohibited_cells unless $task->{dest} && $task->{dest}{pos};

my $dest = $task->{dest}{pos};
my $route_portal_positions = get_route_portal_positions($solution);
my @matching_portals = grep {
my $obstacle = $obstaclesList{$_};
my $match_dist = 7;
Expand Down Expand Up @@ -1239,11 +1280,33 @@ sub filter_prohibited_cells_for_route_task {
$needs_filter = 1;
}

return $prohibited_cells unless $needs_filter || ($destination_is_portal_route && @matching_portals);

my %portal_positions_to_clear = %{$route_portal_positions || {}};
foreach my $portal_id (@matching_portals) {
my $portal = $obstaclesList{$portal_id};
next unless $destination_is_portal_route;
next unless $portal && $portal->{pos_to};
my $portal_pos_key = "$portal->{pos_to}{x},$portal->{pos_to}{y}";
next unless $destination_is_portal_route || $portal_positions_to_clear{$portal_pos_key};
$portal_positions_to_clear{$portal_pos_key} = 1;
}

return $prohibited_cells unless $needs_filter || scalar keys %portal_positions_to_clear;

foreach my $portal_pos_key (keys %portal_positions_to_clear) {
my ($portal_x, $portal_y) = split /,/, $portal_pos_key, 2;
next unless defined $portal_x && defined $portal_y;

my $portal;
foreach my $obstacle_id (keys %obstaclesList) {
my $candidate = $obstaclesList{$obstacle_id};
next unless $candidate && $candidate->{type} && $candidate->{type} eq 'portal';
my $candidate_pos = get_actor_position($candidate);
next unless $candidate_pos;
next unless $candidate_pos->{x} == $portal_x && $candidate_pos->{y} == $portal_y;
$portal = $candidate;
last;
}

next unless $portal && $portal->{pos_to};
remove_prohibited_zone_from_cells(\%filtered, $target_field, $portal->{pos_to}, $portal->{prohibited_dist});
}

Expand All @@ -1269,11 +1332,16 @@ sub on_route_step {
$max_route_step = $max_index if $max_route_step > $max_index;
return if $max_route_step < 1;

my $route_portal_positions = get_route_portal_positions($args->{solution});
my $prohibited_cells = get_cached_prohibited_cells();
$prohibited_cells = filter_prohibited_cells_for_route_task($args->{task}, $prohibited_cells, $field);
$prohibited_cells = filter_prohibited_cells_for_route_task($args->{task}, $prohibited_cells, $field, $args->{solution});
my $danger_cells = get_cached_danger_cells();
my ($best_step, $best_score) = choose_best_route_step($args->{current_calc_pos}, $args->{solution}, $max_route_step, $prohibited_cells, $danger_cells);
if (!defined $best_step) {
if (scalar keys %{$route_portal_positions}) {
debug "[" . PLUGIN_NAME . "] No safe local route_step found, but the planned route uses a portal; keeping the current step selection.\n", 'route', 1;
return;
}
warning "[" . PLUGIN_NAME . "] No safe local route_step found; local client path would cross a prohibited cell. Requesting repath.\n";
$args->{task}{resetRoute} = 1;
return;
Expand Down Expand Up @@ -1457,6 +1525,14 @@ sub on_AI_pre_manual_drop_route_dest_near_Obstacle {
next unless $task->{isRandomWalk} || ($task->{isToLockMap} && $field->baseName eq $config{lockMap});
my $obstacle = is_there_an_obstacle_near_pos($task->{dest}{pos}, 2);
next unless $obstacle;
if ($obstacle->{type} && $obstacle->{type} eq 'portal' && is_route_destination_portal($task)) {
debug "[" . PLUGIN_NAME . "] Keeping route because destination $task->{dest}{pos}{x} $task->{dest}{pos}{y} is a known portal cell.\n", 'route', 2;
next;
}
if ($obstacle->{type} && $obstacle->{type} eq 'portal' && $obstacle->{pos_to}) {
my $route_portal_positions = get_route_portal_positions($task->{solution});
next if $route_portal_positions->{"$obstacle->{pos_to}{x},$obstacle->{pos_to}{y}"};
}
warning "[" . PLUGIN_NAME . "] Dropping current route because an obstacle appeared near its destination ($task->{dest}{pos}{x} $task->{dest}{pos}{y}) close to (" . ($obstacle->{name}) . ").\n";
AI::clear('move', 'route');
last;
Expand Down
128 changes: 112 additions & 16 deletions plugins/checkAggressive/checkAggressive.pl
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,125 @@ package checkAggressive;
PLUGIN_NAME => 'checkAggressive',
};

my %ai_constant = (
'01' => 0x81, '02' => 0x83, '03' => 0x1089, '04' => 0x3885,
'05' => 0x2085, '06' => 0, '07' => 0x108B, '08' => 0x7085,
'09' => 0x3095, '10' => 0x84, '11' => 0x84, '12' => 0x2085,
'13' => 0x308D, '17' => 0x91, '19' => 0x3095, '20' => 0x3295,
'21' => 0x3695, '24' => 0xA1, '25' => 0x1, '26' => 0xB695,
'27' => 0x8084, 'ABR_PASSIVE' => 0x21, 'ABR_OFFENSIVE' => 0xA5
);

sub Unload {
Plugins::delHooks($hooks);
message "[".PLUGIN_NAME."] Plugin unloading or reloading.\n", 'success';
}

sub is_monster_ai_aggressive {
my ($ai_str) = @_;
$ai_str = uc($ai_str);
sub is_monster_engaged_with_us {
my ($monster) = @_;

my $mode_value = exists $ai_constant{$ai_str}
? $ai_constant{$ai_str}
: $ai_constant{'06'};
return ($monster->{sentAttack} || $monster->{engaged}) ? 1 : 0;
}

return ($mode_value & 0x4) ? 1 : 0;
sub is_looking_at_actor {
my ($monster, $actor) = @_;

return unless ($monster && $actor);
return unless defined $monster->{look}{body};

my $monster_pos = calcPosFromPathfinding($field, $monster);
my $actor_pos = calcPosFromPathfinding($field, $actor);
return unless ($monster_pos && $actor_pos);

return 1 if ($monster_pos->{x} == $actor_pos->{x} && $monster_pos->{y} == $actor_pos->{y});

my %vec;
getVector(\%vec, $actor_pos, $monster_pos);
my $degree = vectorToDegree(\%vec);
return unless defined $degree;

my $target_body = int(sprintf("%.0f", (360 - $degree) / 45)) % 8;
return $monster->{look}{body} == $target_body ? 1 : 0;
}

sub get_attackable_actors {
my @actors;

push @actors, @{ $playersList ? $playersList->getItems : [] };
push @actors, @{ $slavesList ? $slavesList->getItems : [] };
push @actors, @{ $elementalsList ? $elementalsList->getItems : [] };

return @actors;
}

sub get_line_points_between {
my ($from_pos, $to_pos) = @_;
return [] unless ($from_pos && $to_pos);

my ($x1, $y1) = ($from_pos->{x}, $from_pos->{y});
my ($x2, $y2) = ($to_pos->{x}, $to_pos->{y});

my @points;
my $dx = abs($x2 - $x1);
my $dy = abs($y2 - $y1);
my $sx = $x1 < $x2 ? 1 : -1;
my $sy = $y1 < $y2 ? 1 : -1;
my $err = $dx - $dy;

while (!($x1 == $x2 && $y1 == $y2)) {
my $e2 = 2 * $err;
if ($e2 > -$dy) {
$err -= $dy;
$x1 += $sx;
}
if ($e2 < $dx) {
$err += $dx;
$y1 += $sy;
}

last if ($x1 == $x2 && $y1 == $y2);
push @points, {x => $x1, y => $y1};
}

return \@points;
}

sub has_attackable_actor_between {
my ($actor, $monster) = @_;
return unless ($actor && $monster);

my $actor_pos = calcPosFromPathfinding($field, $actor);
my $monster_pos = calcPosFromPathfinding($field, $monster);
return unless ($actor_pos && $monster_pos);

my $line_points = get_line_points_between($actor_pos, $monster_pos);
return 0 unless (@{$line_points});

my %line_lookup = map { $_->{x} . ',' . $_->{y} => 1 } @{$line_points};

foreach my $other_actor (get_attackable_actors()) {
next unless $other_actor;
next if ($actor->{ID} && $other_actor->{ID} && $actor->{ID} eq $other_actor->{ID});
next if $other_actor->{dead};

my $other_pos = calcPosFromPathfinding($field, $other_actor);
next unless $other_pos;

return 1 if $line_lookup{$other_pos->{x} . ',' . $other_pos->{y}};
}

return 0;
}

sub is_aggressive_towards_actor {
my ($monster, $actor, $is_clean) = @_;
return unless ($monster && $actor);

return 1 if is_monster_engaged_with_us($monster);

return unless $is_clean;
return unless exists $monstersTable{$monster->{nameID}};
return unless $monstersTable{$monster->{nameID}}{isAIMode_Aggressive};

my $monster_pos = calcPosFromPathfinding($field, $monster);
my $actor_pos = calcPosFromPathfinding($field, $actor);
return unless ($monster_pos && $actor_pos);
return unless (blockDistance($actor_pos, $monster_pos) < 10);
return unless (Misc::objectIsMovingTowards($monster, $actor) || is_looking_at_actor($monster, $actor));
return if has_attackable_actor_between($actor, $monster);

return 1;
}

sub is_monster_engaged_with_us {
Expand Down
23 changes: 1 addition & 22 deletions plugins/checkLooter/checkLooter.pl
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,6 @@ package checkLooter;
['check_attackLooter', \&oncheck_attackLooter, undef],
);

my %ai_constant = (
'01' => 0x81, '02' => 0x83, '03' => 0x1089, '04' => 0x3885,
'05' => 0x2085, '06' => 0, '07' => 0x108B, '08' => 0x7085,
'09' => 0x3095, '10' => 0x84, '11' => 0x84, '12' => 0x2085,
'13' => 0x308D, '17' => 0x91, '19' => 0x3095, '20' => 0x3295,
'21' => 0x3695, '24' => 0xA1, '25' => 0x1, '26' => 0xB695,
'27' => 0x8084, 'ABR_PASSIVE' => 0x21, 'ABR_OFFENSIVE' => 0xA5
);

=pod
/// Monster mode definitions to clear up code reading. [Skotlex]
enum e_mode {
Expand Down Expand Up @@ -108,8 +99,7 @@ sub oncheck_attackLooter {
}

my $mob = $monstersTable{$args->{monster}->{nameID}};
my $ai = $mob->{Ai};
my $is_looter = is_monster_ai_looter($ai);
my $is_looter = $mob->{isAIMode_Looter} ? 1 : 0;
if (!$is_looter) {
debug "[checkLooter] [False] $args->{monster} ($args->{monster}->{nameID}) is not a Looter\n", 'checkLooter';
$args->{return} = 1;
Expand All @@ -118,15 +108,4 @@ sub oncheck_attackLooter {
}
}

sub is_monster_ai_looter {
my ($ai_str) = @_;
$ai_str = uc($ai_str);

my $mode_value = exists $ai_constant{$ai_str}
? $ai_constant{$ai_str}
: $ai_constant{'06'};

return ($mode_value & 0x2) ? 1 : 0;
}

1;
Loading
Loading