diff --git a/control/config.txt b/control/config.txt index e5dd6adb19..8520558550 100644 --- a/control/config.txt +++ b/control/config.txt @@ -275,6 +275,7 @@ sitAuto_over_50 0 sitAuto_idle 1 sitAuto_look sitAuto_look_from_wall +sitAuto_look_delay sitTensionRelax 0 statsAddAuto 0 diff --git a/src/AI.pm b/src/AI.pm index 5ef42a19cf..ce222483ff 100644 --- a/src/AI.pm +++ b/src/AI.pm @@ -758,27 +758,27 @@ sub sit { require Task::SitStand; my $task = new Task::SitStand(actor => $char, mode => 'sit', wait => $timeout{ai_sit_wait}{timeout}); AI::queue("sitting", $task); - if (defined $config{sitAuto_look} && !$config{sitAuto_look_from_wall}) { - Misc::look($config{sitAuto_look}); - } elsif (defined $config{sitAuto_look} && $config{sitAuto_look_from_wall}) { - my $sitAutoLook = $config{sitAuto_look}; - my $wallRange = $config{sitAuto_look_from_wall}; - for (my $i=1;$i<=$wallRange;$i++) { - if ((!$field->isWalkable($char->{pos}{x},$char->{pos}{y}+$wallRange) && $sitAutoLook == 0) - || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}+$wallRange) && $sitAutoLook == 1) - || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}) && $sitAutoLook == 2) - || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}-$wallRange) && $sitAutoLook == 3) - ) { - $sitAutoLook += 4; - } elsif ((!$field->isWalkable($char->{pos}{x},$char->{pos}{y}-$wallRange) && $sitAutoLook == 4) - || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}-$wallRange) && $sitAutoLook == 5) - || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}) && $sitAutoLook == 6) - || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}+$wallRange) && $sitAutoLook == 7) - ) { - $sitAutoLook -= 4; + my $lookDelay = $config{sitAuto_look_delay}; + $lookDelay = 0 if (!defined $lookDelay || $lookDelay < 0); + delete $ai_v{sitAuto_pendingLook}; + if (defined $config{sitAuto_look}) { + my $defaultLook = $config{sitAuto_look}; + my $lookPlan = { body => $defaultLook, delay => $lookDelay }; + + if ($config{sitAuto_look_from_wall}) { + my $closestWalls = Misc::getClosestWalls($char->{pos}, $config{sitAuto_look_from_wall}); + if (@{$closestWalls}) { + my $referenceWall = $closestWalls->[int(rand(@{$closestWalls}))]; + my $oppositeDirectionPos = { + x => (2 * $char->{pos}{x}) - $referenceWall->{x}, + y => (2 * $char->{pos}{y}) - $referenceWall->{y}, + }; + my ($bodyDirection, $headDirection) = Misc::getNaturalLookDirections($char->{pos}, $oppositeDirectionPos, $defaultLook); + $lookPlan = { body => $bodyDirection, head => $headDirection, delay => $lookDelay }; } } - Misc::look($sitAutoLook); + + $ai_v{sitAuto_pendingLook} = $lookPlan; } } diff --git a/src/AI/CoreLogic.pm b/src/AI/CoreLogic.pm index edad04a1cc..5adaa5cefb 100644 --- a/src/AI/CoreLogic.pm +++ b/src/AI/CoreLogic.pm @@ -414,6 +414,11 @@ sub processClientSuspend { } sub processLook { + if ($ai_v{sitAuto_scheduledLook} && time >= $ai_v{sitAuto_scheduledLook}{due}) { + my $scheduledLook = delete $ai_v{sitAuto_scheduledLook}; + Misc::look($scheduledLook->{body}, $scheduledLook->{head}); + } + if (AI::action eq "look" && timeOut($timeout{'ai_look'})) { $timeout{'ai_look'}{'time'} = time; $messageSender->sendLook(AI::args->{'look_body'}, AI::args->{'look_head'}); diff --git a/src/Misc.pm b/src/Misc.pm index b92c5a3a48..ddc4737b77 100644 --- a/src/Misc.pm +++ b/src/Misc.pm @@ -140,6 +140,8 @@ our @EXPORT = ( look lookAtPosition lookAtPositionNaturally + getNaturalLookDirections + getClosestWalls manualMove meetingPosition objectAdded @@ -2488,14 +2490,14 @@ sub lookAtPosition { } ## -# lookAtPositionNaturally(from_pos, to_pos, [current_body]) +# getNaturalLookDirections(from_pos, to_pos, [current_body]) # from_pos: source position hashref (character) # to_pos: target position hashref # current_body: optional current body direction, defaults to $char->{look}{body} # -# Calculates and executes look change using partial-turn strategy. +# Calculates look change using partial-turn strategy. # Returns: (body, head) where body is 0-7 and head is 0-2. -sub lookAtPositionNaturally { +sub getNaturalLookDirections { my ($from_pos, $to_pos, $current_body) = @_; return unless ($from_pos && $to_pos); @@ -2520,10 +2522,65 @@ sub lookAtPositionNaturally { } } + return ($body, $head); +} + +## +# lookAtPositionNaturally(from_pos, to_pos, [current_body]) +# from_pos: source position hashref (character) +# to_pos: target position hashref +# current_body: optional current body direction, defaults to $char->{look}{body} +# +# Calculates and executes look change using partial-turn strategy. +# Returns: (body, head) where body is 0-7 and head is 0-2. +sub lookAtPositionNaturally { + my ($from_pos, $to_pos, $current_body) = @_; + my ($body, $head) = getNaturalLookDirections($from_pos, $to_pos, $current_body); + return unless defined $body; + look($body, $head) if ($body != $char->{look}{body} || $head != $char->{look}{head}); return ($body, $head); } +## +# getClosestWalls(from_pos, wall_range, [field_obj]) +# from_pos: source position hashref +# wall_range: search range around from_pos +# field_obj: optional field object, defaults to current global field +# +# Returns: arrayref with all nearest non-walkable cells found in range. +sub getClosestWalls { + my ($from_pos, $wall_range, $field_obj) = @_; + return [] unless ($from_pos && defined $wall_range && $wall_range > 0); + + $field_obj ||= $field; + return [] unless $field_obj; + + my $closest_distance; + my @closest_walls; + for (my $dx = -$wall_range; $dx <= $wall_range; $dx++) { + for (my $dy = -$wall_range; $dy <= $wall_range; $dy++) { + next if $dx == 0 && $dy == 0; + + my $candidate = { + x => $from_pos->{x} + $dx, + y => $from_pos->{y} + $dy, + }; + next if $field_obj->isWalkable($candidate->{x}, $candidate->{y}); + + my $distance = distance($candidate, $from_pos); + if (!defined $closest_distance || $distance < $closest_distance) { + $closest_distance = $distance; + @closest_walls = ($candidate); + } elsif ($distance == $closest_distance) { + push @closest_walls, $candidate; + } + } + } + + return \@closest_walls; +} + ## # manualMove(dx, dy) # diff --git a/src/Network/Receive.pm b/src/Network/Receive.pm index 7733494419..a933913e86 100644 --- a/src/Network/Receive.pm +++ b/src/Network/Receive.pm @@ -2604,6 +2604,22 @@ sub actor_action { if ($args->{sourceID} eq $accountID) { message T("You are sitting.\n") if (!$char->{sitting}); $char->{sitting} = 1; + if ($ai_v{sitAuto_pendingLook}) { + my $pendingLook = delete $ai_v{sitAuto_pendingLook}; + delete $ai_v{sitAuto_scheduledLook}; + my $delay = ($pendingLook->{delay} && $pendingLook->{delay} > 0) ? $pendingLook->{delay} : 0; + if ($delay > 0) { + $ai_v{sitAuto_scheduledLook} = { + due => time + $delay, + body => $pendingLook->{body}, + head => $pendingLook->{head}, + }; + } elsif (defined $pendingLook->{head}) { + Misc::look($pendingLook->{body}, $pendingLook->{head}); + } elsif (defined $pendingLook->{body}) { + Misc::look($pendingLook->{body}); + } + } AI::queue("sitAuto") unless (AI::inQueue("sitAuto")) || $ai_v{sitAuto_forcedBySitCommand}; } else { message TF("%s is sitting.\n", getActorName($args->{sourceID})), 'parseMsg_statuslook', 2;