@@ -1193,17 +1193,58 @@ sub remove_prohibited_zone_from_cells {
11931193 }
11941194}
11951195
1196+ # # Purpose: Finds portal obstacle positions that are actually used by a route solution.
1197+ # # Args: `($solution)`.
1198+ # # Returns: A hashref keyed by `"x,y"` for portal cells present in the solution.
1199+ # # Notes: This lets portal avoidance stay active for ordinary movement while
1200+ # # exempting portals that are part of the planned route itself.
1201+ sub get_route_portal_positions {
1202+ my ($solution ) = @_ ;
1203+ return {} unless $solution && @{$solution };
1204+
1205+ my %solution_cells ;
1206+ foreach my $node (@{$solution }) {
1207+ next unless $node && defined $node -> {x } && defined $node -> {y };
1208+ $solution_cells {" $node ->{x},$node ->{y}" } = 1;
1209+ }
1210+
1211+ my %portal_positions ;
1212+ foreach my $obstacle_id (keys %obstaclesList ) {
1213+ my $obstacle = $obstaclesList {$obstacle_id };
1214+ next unless $obstacle && $obstacle -> {type } && $obstacle -> {type } eq ' portal' ;
1215+ my $pos = get_actor_position($obstacle );
1216+ next unless $pos ;
1217+ next unless $solution_cells {" $pos ->{x},$pos ->{y}" };
1218+ $portal_positions {" $pos ->{x},$pos ->{y}" } = 1;
1219+ }
1220+
1221+ return \%portal_positions ;
1222+ }
1223+
1224+ # # Purpose: Detects whether a route destination is itself a known portal cell.
1225+ # # Args: `($task)`.
1226+ # # Returns: `1` when the destination matches a portal source on the current map.
1227+ # # Notes: This is the safest early escape hatch for portal routes because it does
1228+ # # not depend on the route solution already being built.
1229+ sub is_route_destination_portal {
1230+ my ($task ) = @_ ;
1231+ return 0 unless $task && $field && $task -> {dest } && $task -> {dest }{pos };
1232+ return 1 if defined portalExists($field -> baseName, $task -> {dest }{pos });
1233+ return 0;
1234+ }
1235+
11961236# # Purpose: Loosens prohibited cells around a route destination when needed.
1197- # # Args: `($task, $prohibited_cells, $target_field)`.
1237+ # # Args: `($task, $prohibited_cells, $target_field, $solution )`.
11981238# # Returns: The original or filtered prohibited-cell hashref.
11991239# # Notes: This exists so route tasks can still finish at destinations such as portals
12001240# # while keeping danger scoring and all other obstacle penalties intact.
12011241sub filter_prohibited_cells_for_route_task {
1202- my ($task , $prohibited_cells , $target_field ) = @_ ;
1242+ my ($task , $prohibited_cells , $target_field , $solution ) = @_ ;
12031243 return $prohibited_cells unless $task && $prohibited_cells && $target_field ;
12041244 return $prohibited_cells unless $task -> {dest } && $task -> {dest }{pos };
12051245
12061246 my $dest = $task -> {dest }{pos };
1247+ my $route_portal_positions = get_route_portal_positions($solution );
12071248 my @matching_portals = grep {
12081249 my $obstacle = $obstaclesList {$_ };
12091250 my $match_dist = 7;
@@ -1239,11 +1280,33 @@ sub filter_prohibited_cells_for_route_task {
12391280 $needs_filter = 1;
12401281 }
12411282
1242- return $prohibited_cells unless $needs_filter || ($destination_is_portal_route && @matching_portals );
1243-
1283+ my %portal_positions_to_clear = %{$route_portal_positions || {}};
12441284 foreach my $portal_id (@matching_portals ) {
12451285 my $portal = $obstaclesList {$portal_id };
1246- next unless $destination_is_portal_route ;
1286+ next unless $portal && $portal -> {pos_to };
1287+ my $portal_pos_key = " $portal ->{pos_to}{x},$portal ->{pos_to}{y}" ;
1288+ next unless $destination_is_portal_route || $portal_positions_to_clear {$portal_pos_key };
1289+ $portal_positions_to_clear {$portal_pos_key } = 1;
1290+ }
1291+
1292+ return $prohibited_cells unless $needs_filter || scalar keys %portal_positions_to_clear ;
1293+
1294+ foreach my $portal_pos_key (keys %portal_positions_to_clear ) {
1295+ my ($portal_x , $portal_y ) = split /,/, $portal_pos_key , 2;
1296+ next unless defined $portal_x && defined $portal_y ;
1297+
1298+ my $portal ;
1299+ foreach my $obstacle_id (keys %obstaclesList ) {
1300+ my $candidate = $obstaclesList {$obstacle_id };
1301+ next unless $candidate && $candidate -> {type } && $candidate -> {type } eq ' portal' ;
1302+ my $candidate_pos = get_actor_position($candidate );
1303+ next unless $candidate_pos ;
1304+ next unless $candidate_pos -> {x } == $portal_x && $candidate_pos -> {y } == $portal_y ;
1305+ $portal = $candidate ;
1306+ last ;
1307+ }
1308+
1309+ next unless $portal && $portal -> {pos_to };
12471310 remove_prohibited_zone_from_cells(\%filtered , $target_field , $portal -> {pos_to }, $portal -> {prohibited_dist });
12481311 }
12491312
@@ -1269,11 +1332,16 @@ sub on_route_step {
12691332 $max_route_step = $max_index if $max_route_step > $max_index ;
12701333 return if $max_route_step < 1;
12711334
1335+ my $route_portal_positions = get_route_portal_positions($args -> {solution });
12721336 my $prohibited_cells = get_cached_prohibited_cells();
1273- $prohibited_cells = filter_prohibited_cells_for_route_task($args -> {task }, $prohibited_cells , $field );
1337+ $prohibited_cells = filter_prohibited_cells_for_route_task($args -> {task }, $prohibited_cells , $field , $args -> { solution } );
12741338 my $danger_cells = get_cached_danger_cells();
12751339 my ($best_step , $best_score ) = choose_best_route_step($args -> {current_calc_pos }, $args -> {solution }, $max_route_step , $prohibited_cells , $danger_cells );
12761340 if (!defined $best_step ) {
1341+ if (scalar keys %{$route_portal_positions }) {
1342+ debug " [" . PLUGIN_NAME . " ] No safe local route_step found, but the planned route uses a portal; keeping the current step selection.\n " , ' route' , 1;
1343+ return ;
1344+ }
12771345 warning " [" . PLUGIN_NAME . " ] No safe local route_step found; local client path would cross a prohibited cell. Requesting repath.\n " ;
12781346 $args -> {task }{resetRoute } = 1;
12791347 return ;
@@ -1457,6 +1525,14 @@ sub on_AI_pre_manual_drop_route_dest_near_Obstacle {
14571525 next unless $task -> {isRandomWalk } || ($task -> {isToLockMap } && $field -> baseName eq $config {lockMap });
14581526 my $obstacle = is_there_an_obstacle_near_pos($task -> {dest }{pos }, 2);
14591527 next unless $obstacle ;
1528+ if ($obstacle -> {type } && $obstacle -> {type } eq ' portal' && is_route_destination_portal($task )) {
1529+ debug " [" . PLUGIN_NAME . " ] Keeping route because destination $task ->{dest}{pos}{x} $task ->{dest}{pos}{y} is a known portal cell.\n " , ' route' , 2;
1530+ next ;
1531+ }
1532+ if ($obstacle -> {type } && $obstacle -> {type } eq ' portal' && $obstacle -> {pos_to }) {
1533+ my $route_portal_positions = get_route_portal_positions($task -> {solution });
1534+ next if $route_portal_positions -> {" $obstacle ->{pos_to}{x},$obstacle ->{pos_to}{y}" };
1535+ }
14601536 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 " ;
14611537 AI::clear(' move' , ' route' );
14621538 last ;
0 commit comments