#!/usr/bin/perl ############################################################################### ## ## Copyright (C) 2004-2005 Philip Walls (www.rabidgeek.com) ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 2, or (at your option) any ## later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ############################################################################### package Furcadia; # Disable when stable, slows down application #use strict; #use warnings; my $VERSION = "0.6.45"; use IO::Socket::INET; use IO::Select; $| = 1; my $SELECT = IO::Select->new; my $STDIN = IO::Handle->new; # Socket handles my $FURC_SOCKET = 0; my $PROXY_SOCKET = 0; my %ProxyClients = (); # Debugging options (TODO: move out of here) my %DebugMessages = ( "events" => 1, # Debug events (handlers and such) "social" => 1, # Debug whispers, chat, etc "goto" => 0, # Debug path finding code "seen" => 0, # Debug how furres/dreams/etc are found "objects" => 0, # Debug object movements "proxy" => 1, # Debug proxy related messages "print_proxy" => 0, # Debug writing to proxy socket "proxy_read" => 0, # Debug incoming proxy socket data "connection" => 1, # Debug Furcadia server connecting/disconnecting "settings" => 1, # Debug INI loading/writing functions "read_socket" => 0, # Debug Furcadia server incoming socket data "read_stdin" => 0, # Debug standard input data "timers" => 0, # Debug timer creation/execution/removal ); my $DebugPretend = 0; # Enable colorful debugging by default except in Win32 my $ConsoleColors = 1; my $WindowsHost = 0; my $Win32Tray; if ($^O eq "MSWin32") { $ConsoleColors = 0; $WindowsHost = 1; require Win32Tray; $Win32Tray = new Win32Tray(); $Win32Tray->RunEvents(); } my (%FurcInfo, %FurcHandlers, @FurcTimers); my %Temp = ( "specitag" => undef, # Last displayed specitag "lookreason" => undef, # Reason for $Furcadia->look() ); my %FurcData = ( "xpos" => undef, # Current X position "ypos" => undef, # Current Y position "imxpos" => undef, # "In middle" X position "dir" => undef, # Direction "frame" => undef, # Current Furc notation frame "map" => undef, # Current map "lastx" => undef, # Previous X position "lasty" => undef, # Previous Y position ); my ($FurcReaching, %FurcTried, %FurcAvoid, %FurcDreams, %FurreList, %FurrePosList, %MapFurres); # Explanation of data structures: # # %FurreList = # ( # "Furre|Name" => "colorcode" # ) # # %FurrePosList = # ( # "xpos|ypos" => # { # "color" => ... # "frame" => ... # "dir" => ... # } # ) # my %FurcTarget = ( "xpos" => undef, # Target's X position "ypos" => undef, # Target's Y position "imxpos" => undef, # Target's "In middle" X position "color" => undef, # Target's color "name" => undef, # Target's name ); # Direction to number mappings my %Directions = ( 'sw' => 1, 'se' => 3, 'nw' => 7, 'ne' => 9, ); # Color codes for error levels my @DebugLevels = ( "\e[1;31m", # Red "\e[1;33m", # Yellow "\e[1;37m", # White "\e[1;32m", # Green "\e[1;34m", # Blue ); sub new { my $self = {}; my $proto = shift; my $furcdata = shift; my $handlers = shift; %FurcInfo = %{$furcdata}; %FurcHandlers = %{$handlers}; bless $self, $proto; } # Main event loop sub event_loop { my $self = shift; $self->debug("events", 4, "Entered event loop\n"); # Handle STDIN data if handler is set up if (defined $FurcHandlers{"read_stdin"}) { $self->debug("read_stdin", 4, "Enabling standard input handling\n"); $self->select_stdin(); } # Handle proxy connections if (defined $FurcInfo{"proxy"}) { $self->debug("proxy", 4, "Initializing proxy server\n"); $self->select_proxy($FurcInfo{"proxy_addr"}, $FurcInfo{"proxy_port"}); } # Add timer to do a who # FIXME: Is there a better way to pass this function ref? # Don't add the timer unless they want to see when people join/part if (defined $FurcHandlers{"list_join"} || defined $FurcHandlers{"list_part"}) { $self->timer_add_interval(3, \&who, $self); } # While the socket is still readable while ($self->can_read || $FurcInfo{"proxy"}) { # Check for timers $self->timer_check; # Check all read handles (proxy, stdin, socket) $self->read_handles; # Update the systray window if ($WindowsHost) { if ($Win32Tray->RunEvents() < 0) { exit(0); } } } $self->debug("events", 4, "Exiting event loop\n"); } ########################### # Handler related functions sub handle { my $self = shift; my $handlename = shift; my $retdata = shift; # If we've defined a handle, call it with %retdata as an argument if (defined $handlename && defined $FurcHandlers{$handlename}) { my $handlerfunction = $FurcHandlers{$handlename}; # Attempt to run the function eval $handlerfunction->($retdata); # If unsuccessful if ($@) { $self->debug("events", 2, "Failed to execute handler for $handlename\n"); } } } sub add_handler { # Add a new function handler # First argument should be the handler name # Second argument should be a function reference my $self = shift; my $handler = shift; my $function = shift; $self->debug("events", 4, "Added event handler for $handler\n"); if (defined $handler && defined $function) { $FurcHandlers{$handler} = $function; return 1; } return 0; } sub rem_handler { # Remove a handler by name my $self = shift; my $handler = shift; $self->debug("events", 4, "Removed event handler for $handler\n"); if (defined $handler) { $FurcHandlers{$handler} = undef; return 1; } return 0; } #################################### # Timer control (ripped from Idiots) sub timer_check { my $self = shift; # Check for waiting timers for ( my $timer = 1; $timer <= $#FurcTimers; $timer++ ) { next unless $FurcTimers[$timer][0] && $FurcTimers[$timer][1]; # Check to see if it's past the time when the timer should have run if ( $FurcTimers[$timer][0] <= time ) { $self->debug ( "timers", 4, "Timer $timer activated at " . time . "\n" ); # Try to run the function eval $FurcTimers[$timer][1]->($FurcTimers[$timer][2]); # If the eval was unsuccessful if ($@) { $self->debug ( "timers", 4, "Couldn't execute timer: $@\n" ); } else { $self->debug ( "timers", 4, "Timer executed successfully\n" ); } # If there's an interval set up if ($FurcTimers[$timer][3]) { # Recreate the timer with the same interval $self->timer_add_interval ( $FurcTimers[$timer][3], $FurcTimers[$timer][1], $FurcTimers[$timer][2] ); } # Remove the old timer $self->timer_remove ( $timer ); } } } sub timer_add { my $self = shift; my $time = shift; my $function = shift; my $arguments = shift; my $repeat = shift; my $timer = @FurcTimers + 1; $FurcTimers[$timer][0] = $time; $FurcTimers[$timer][1] = $function; $FurcTimers[$timer][2] = $arguments; $FurcTimers[$timer][3] = $repeat; $self->debug ( "timers", 4,"Created timer goes off at $time\n" ); return $timer; } sub timer_add_offset { my $self = shift; my $offset = shift; my $function = shift; my $arguments = shift; my $timer = @FurcTimers + 1; $self->debug("timers", 4, "Created timer with offset of $offset seconds\n"); $self->timer_add ( time + $offset, $function, $arguments ); } sub timer_add_interval { my $self = shift; my $interval = shift; my $function = shift; my $arguments = shift; my $timer = @FurcTimers + 1; $self->debug ("timers", 4, "Created timer to go off every $interval seconds\n"); $self->timer_add( time + $interval, $function, $arguments, $interval); } sub timer_remove { my $self = shift; my $timer = shift; $self->debug ( "timers", 4, "Removed timer $timer\n" ); splice ( @FurcTimers, $timer, 1 ); return 1; } #################### # Connection related sub connect { my $self = shift; my $addr = shift; my $port = shift; # Use default values if none given $addr ||= "lightbringer.furcadia.com"; # Pulled from the Furcadia settings editor (Connection tab) my @portlist = ( 6500, 5000, 2300, 80, 22, 21 ); # If they specified a port, only try that port if ($port) { @portlist = ( $port ); } $self->debug("connection", 2, "Connecting to Furcadia\n"); # Try connecting to each port in turn foreach my $tryport (@portlist) { if ($WindowsHost) { $Win32Tray->RunEvents(); } #$self->debug ("connection", 2, "Trying server [$addr:$tryport]\n"); # while (!$FURC_SOCKET) if (!$FURC_SOCKET) { $FURC_SOCKET = IO::Socket::INET->new ( PeerAddr => $addr, PeerPort => $tryport, Proto => 'tcp' ); } if ($FURC_SOCKET) { $self->debug ( "connection", 2, "Connected successfully.\n" ); last; } else { #$self->debug("connection", 0, "Connection to Furcadia server failed.\n"); sleep(1); } } # If we have no connection after all of that... if (! $FURC_SOCKET) { $self->debug("connection", 0, "Unable to connect to Furcadia server!\n"); return 0; } # Add the socket into our list of selectable handles $FURC_SOCKET->autoflush(1); $SELECT->add($FURC_SOCKET); return 1; } sub disconnect { my $self = shift; # Remove the socket from selectable handles $SELECT->remove($FURC_SOCKET); $FURC_SOCKET->close; $FURC_SOCKET = undef; $self->debug ( "connection", 0, "Connection to Furcadia server closed.\n" ); $self->handle("disconnect", { } ); } sub can_read { my $self = shift; # $self->debug("can_read", 4, "$FURC_SOCKET\n"); #if (!$FURC_SOCKET) #{ # $self->debug # ( # "connection", 0, # "Connection to Furcadia server was lost.\n" # ); #} return $FURC_SOCKET; } sub select_stdin { my $self = shift; if ($^O eq "MSWin32") { $self->debug("read_stdin", 0, "Win32 doesn't handle non-blocking IO, disabling keyboard input support\n"); delete $FurcHandlers{"read_stdin"}; return 0; } # Open standard input and make selectable if (! $STDIN->fdopen(fileno(STDIN),"r")) { $self->debug("read_stdin", 0, "Couldn't open standard input, disabling support\n"); delete $FurcHandlers{"read_stdin"}; return 0; } $STDIN->autoflush(1); $SELECT->add($STDIN); return 1; } sub select_proxy { my $self = shift; my $bindaddr = shift || "127.0.0.1"; my $port = shift || 6850; $self->debug("proxy", 2, "Starting proxy listen socket on port $bindaddr:$port\n"); # Open an "accept" socket if (!$PROXY_SOCKET) { $PROXY_SOCKET = IO::Socket::INET->new ( LocalPort => $port, LocalAddr => $bindaddr, Listen => 1, Reuse => 1, ); } if ($PROXY_SOCKET) { $self->debug ( "proxy", 2, "Socket started successfully.\n" ); } else { $self->debug ( "proxy", 0, "Couldn't create proxy socket!\n" ); return 0; } # Add the socket into our list of selectable handles $PROXY_SOCKET->autoflush(1); $SELECT->add($PROXY_SOCKET); return 1; } sub print_socket { my $self = shift; $self->debug ("print_socket", 4, "@_"); return 0 unless $FURC_SOCKET; $FURC_SOCKET->print("@_") if (!$DebugPretend); } sub print_proxy { my $self = shift; $self->debug ("print_proxy", 4, "@_"); return 0 unless $PROXY_SOCKET; # Print to each of the connected proxy clients foreach my $wh ($SELECT->can_write(0.1)) { if ($ProxyClients{$wh}) { $wh->print("@_") if (!$DebugPretend); } } return 1; } # FIXME: Merge this into one function with read_socket and read_proxy sub read_stdin { my $self = shift; # Cycle through selectable handles foreach my $rh ($SELECT->can_read(0.1)) { # If the read handle is standard input if ($rh == $STDIN) { if (my $stdin_data = <$STDIN>) { $self->debug ("read_stdin", 4, $stdin_data); # Pass the data on to a handler if one exists $self->handle ("read_stdin", { "data" => $stdin_data }); # Return the data in case it called directly return $stdin_data; } else { # stdin got closed somehow $SELECT->remove($STDIN); close($STDIN); return 0; } } } } sub read_socket { my $self = shift; # The argument to can_read tells it how long to block waiting for data foreach my $rh ($SELECT->can_read(0.1)) { # If it our Furcadia socket handle if ($rh == $FURC_SOCKET) { my $socket_data; # Read one character at a time until EOL (non-blocking) while (sysread ($rh,my $buf,1)) { $socket_data .= $buf; last if ($buf eq "\n"); } if ($socket_data) { $self->debug("read_socket", 4, $socket_data); # Do something with $socket_data $self->scour_socket($socket_data); $self->handle("read_socket", { "data" => $socket_data }); return $socket_data; } else { # The socket was closed $self->disconnect; return 0; } } } } sub read_proxy { my $self = shift; # The argument to can_read tells it how long to block waiting for data foreach my $rh ($SELECT->can_read(0.1)) { # If we have a new proxy connection if ($rh == $PROXY_SOCKET) { # Add this to the select list my $new = $PROXY_SOCKET->accept; $SELECT->add($new); my $host = $new->peerhost; $ProxyClients{$new} = $host; $self->debug("proxy", 4, "Received new proxy connection from $host\n"); $self->handle("proxy_connect", { "host" => $host }); } # If it's an existing proxy connection elsif ($ProxyClients{$rh}) { my $socket_data; # Read one character at a time until EOL (non-blocking) while (sysread ($rh,my $buf,1)) { $socket_data .= $buf; last if ($buf eq "\n"); } if ($socket_data) { # Do something with $socket_data $self->debug ("proxy_read", 4, $socket_data); $self->handle ("proxy_read", { "data" => $socket_data }); return $socket_data; } else { # The socket was closed $SELECT->remove($rh); $rh->close; my $host = $ProxyClients{$rh}; delete($ProxyClients{$rh}); $self->handle("proxy_disconnect", { "host" => $host }); $self->debug("proxy", 0, "Proxy client $host disconnected\n"); return 0; } } } } sub read_handles { my $self = shift; # Name of the handle that we read from my $handle; # The argument to can_read tells it how long to block waiting for data foreach my $rh ($SELECT->can_read(0.1)) { # If it our Furcadia socket handle if ($rh == $FURC_SOCKET) { my $socket_data; # Read one character at a time until EOL (non-blocking) while (sysread ($rh,my $buf,1)) { $socket_data .= $buf; last if ($buf eq "\n"); } if ($socket_data) { # Do something with $socket_data $self->debug("read_socket", 4, $socket_data); $self->scour_socket($socket_data); $self->handle("read_socket", { "data" => $socket_data }); return $socket_data; } else { # The socket was closed $self->disconnect; return 0; } } # If the read handle is standard input elsif ($rh == $STDIN) { if (my $stdin_data = <$STDIN>) { $self->debug ("read_stdin", 4, $stdin_data); # Pass the data on to a handler if one exists $self->handle ("read_stdin", { "data" => $stdin_data }); # Return the data in case it called directly return $stdin_data; } else { # stdin got closed somehow $SELECT->remove($STDIN); close($STDIN); return 0; } } # If we have a new proxy connection elsif ($rh == $PROXY_SOCKET) { # Add this to the select list my $new = $PROXY_SOCKET->accept; $SELECT->add($new); my $host = $new->peerhost; $ProxyClients{$new} = $host; $self->debug("proxy", 4, "Received new proxy connection from $host\n"); $self->handle("proxy_connect", { "host" => $host }); } # If it's an existing proxy connection elsif ($ProxyClients{$rh}) { my $socket_data; # Read one character at a time until EOL (non-blocking) while (sysread ($rh,my $buf,1)) { $socket_data .= $buf; last if ($buf eq "\n"); } if ($socket_data) { # Do something with $socket_data $self->debug ("proxy_read", 4, $socket_data); $self->handle ("proxy_read", { "data" => $socket_data }); return $socket_data; } else { # The socket was closed $SELECT->remove($rh); $rh->close; my $host = $ProxyClients{$rh}; delete($ProxyClients{$rh}); $self->handle("proxy_disconnect", { "host" => $host }); $self->debug("proxy", 0, "Proxy client $host disconnected\n"); return 0; } } } } sub proxy_clients { my $self = shift; # Return the number of connected proxy clients my $clients; foreach my $key (keys(%ProxyClients)) { $clients++; } return $clients; } sub swallow_socket { # This blocks and is in general not good to use unless you know # EXACTLY what is going to be returned from the socket # Please see how "seen()" accomplishes safe use of this function # It's not recommended that you grab more than a single line at # a time. As it could result in some really nasty problems. my $self = shift; my $lines = shift || 1; my @data; foreach (1..$lines) { my $data = <$FURC_SOCKET>; push (@data, $data); # FIXME: This can cause frustration with proxies # Consider using a different handle name for this? #$self->handle("read_socket", { "data" => $data }); $self->debug ("swallow_socket", 4, $data); } return @data; } sub furc_command { # Prints all arguments to socket with newline my $self = shift; $self->print_socket("@_\n"); } sub position { my $self = shift; return $FurcData{xpos},$FurcData{ypos},$FurcData{imxpos}; } sub seen { # Find out where someone is # ((You see Malverian.) # # ]f 7 != ===!"!Malverian my $self = shift; my $who = shift; my $color; # Who is a required argument if (!$who) { return 0; } # Turn spaces into | symbol $who =~ s/\s/\|/g; # If we've got them in our list already # With the proper casing if (defined $FurreList{$who}) { $color = $FurreList{$who}; } # If the casing is wrong or we have a partial name else { foreach my $furre (keys %FurreList) { if ($furre =~ /^\Q$who\E/i) { $color = $FurreList{$furre}; $who = $furre; } } } #################################################### # Let's really hope we never get here # Looks at the furre to grab their color code unless (defined $color) { $self->look($who); my $retrieved = 0; until ($retrieved) { my @data = $self->swallow_socket(1); my $data = "@data"; if ($data =~ /^\]f(.{10})(.{3})(.*)/) { $color = $1; $who = $3; $FurreList{$who} = $color; $retrieved = 1; } elsif ($data =~ /^\(\* Sorry, that furre is/) { return undef; } elsif (/^\(\* More than one furre\'s name begins that way\./) { return undef; } else { # Try to pass unrelated data to the scourer $self->scour_socket($data); } } } ##################################################### return unless $color; # Find the position based on their color code foreach my $furre (keys (%FurrePosList)) { next unless defined $furre; next unless defined $FurrePosList{$furre}; my $fpcolor = $FurrePosList{$furre}{"color"}; if ($fpcolor eq $color && $furre ne "0|0") { my @pos = split(/\|/,$furre); my $xpos = shift @pos; my $ypos = shift @pos; my $imxpos = $self->get_imx_pos($xpos,$ypos); my $frame = $FurrePosList{$furre}{"frame"}; my $dir = $FurrePosList{$furre}{"dir"}; $self->debug("seen", 4, "Found $who in list: $furre ($fpcolor) -- Facing $dir\n"); return ($who, $xpos, $ypos, $dir, $color); } } return undef; } sub seen_dream { my $self = shift; my $dream = shift; foreach my $tdream (keys %FurcDreams) { if ($FurcDreams{$tdream} =~ /^\Q$dream\E/i) { my $name = $FurcDreams{$tdream}; my ($xpos, $ypos) = split(/\|/, $tdream); $self->debug("seen", 4, "Found dream $name at $xpos, $ypos\n"); return ($name, $xpos, $ypos); } } } sub target { my $self = shift; return ( $FurcTarget{xpos}, $FurcTarget{ypos}, $FurcTarget{imxpos}, $FurcTarget{name}, $FurcTarget{dream} ); } sub map { my $self = shift; return $FurcData{map}; } # FIXME: This is a stupid function sub status { my $self = shift; my $xpos = shift; my $ypos = shift; return $FurcDreams{"$xpos|$ypos"}; } # TODO: Add handlers for all scour_socket areas.. # Q: What order should the handlers be called in? # A: Probably after any %FurcData variables have been set sub scour_socket { # Scour the socket data for position codes # and other persistant data # Call any function handlers # Function handler specified in $handlename # is called with %retdata as an argument my $self = shift; my $data = shift; $_ = $data; chop; if (/^\@(.{4})(.{4})?/) { # The bot moved (screen recenter) # @ ( . ) / # @ my $position = $1; my $last = $2; my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($position); $FurcData{xpos} = $xpos; $FurcData{ypos} = $ypos; $FurcData{imxpos} = $imxpos; my ($lastxpos, $lastypos, $lastimxpos) = $self->furc_to_pos($last); $FurcData{lastx} = $lastxpos; $FurcData{lasty} = $lastypos; # TODO: Set up other things similar to this $self->handle("game_screen_move", $lastxpos, $lastypos, $xpos, $ypos); } elsif (/^\/(.{10})(.{4})(.{2})(.{4})?/ || /^\<(.{10})(.{4})(.{2})/) { # Avatar moves to new location # /)-B& %!%8- ) / K ) . # Avatar frame update (sitting, spinning, etc) # <)-B& %!%8- ) / I my $color = $1; my $position = $2; my $frame = $3; my $from = $4; # Convert to human readable positions/directions my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($position); my ($oldxpos,$oldypos,$oldimxpos) = $self->furc_to_pos($from); my $dir = $self->frame_to_dir($frame); # Determine if it's a movement code my $movement = /^\//; # Determine if we were the one that moved my $wemoved = ($xpos == $FurcData{xpos} && $ypos == $FurcData{ypos}); # Determine if we're seeking some place my $pathfinding = (defined $FurcTarget{xpos} && defined $FurcTarget{ypos}); # Determine if there is a vector (successful movement) my $vector = $self->get_vector($oldxpos, $oldypos, $xpos, $ypos); if ($vector) { #$self->debug("goto", 4, "Furre ($color) moved to $xpos [$imxpos], $ypos (direction: $vector)\n"); } # FIXME: This is so the bot can't follow himself, use a better method unless ($wemoved) { $FurrePosList{"$xpos|$ypos"}{"color"} = $color; $FurrePosList{"$xpos|$ypos"}{"frame"} = $frame; $FurrePosList{"$xpos|$ypos"}{"dir"} = $dir; #$self->handle("game_other_moved", $color, $frame, $dir); } $self->handle ( "move", { "color" => $color, "xpos" => $xpos, "ypos" => $ypos, "imxpos" => $imxpos, "fpos" => $position, "frame" => $frame, "from" => $from } ); # If the position spoken of is where we stand if ($wemoved) { # If we reached our lock position if ($FurcReaching eq "$xpos|$ypos") { # Remove the lock $FurcReaching = undef; } # Update our current direction and frame $FurcData{dir} = $dir; $FurcData{frame} = $frame; # If it's a movement code (not spinning/sitting/etc) if ($movement) { # If we ran into something if ($from eq $position) { # Figure out where we tried moving to my ($nxpos, $nypos, $nimxpos) = $self->relative_position($xpos, $ypos, $dir); $self->debug("goto", 4, "Failed moving $dir from $xpos, $ypos to $nxpos [$nimxpos], $nypos\n"); # If we're currently seeking if ($pathfinding && ! $self->next_to($FurcTarget{name})) { # Don't go there again $self->avoid($nxpos, $nypos, $xpos, $ypos); # If we're seeking a person if ($FurcTarget{name}) { # Recalculate the "nearest" position to the person we're going to # FIXME: Should probably only do this if we found out our target # position is occupied. Also.. this $tdir doesn't work as intended :-P my ($twho,$txpos,$typos) = $self->seen($FurcTarget{name}); my $tdir = $self->get_vector($txpos,$typos,$xpos,$ypos); my ($tnx, $tny) = $self->get_nearest($txpos, $typos, $tdir); $self->goto($tnx, $tny); } else { # Otherwise just keep trying our current target positions #$self->goto($FurcTarget{xpos}, $FurcTarget{ypos}); } } } # If it was a successful move else { # "Close" that position we came from so we don't go back there $self->debug("goto", 4, "Successfully moved from $oldxpos, $oldypos to $xpos, $ypos\n"); if ($pathfinding) { $self->tried($oldxpos, $oldypos); $self->avoid($oldxpos, $oldypos); } } # Once we've moved and recalculated, go again if ($pathfinding) { $self->reach_dest(); } } } # If we are following the furre that moved elsif (defined $FurcTarget{color} && defined $color && $FurcTarget{color} eq $color) { # When we update our target, clear out the avoid list %FurcAvoid = (); $FurcTarget{dir} = $dir; # If we're not already next to our target if (! $self->next_to($FurcTarget{name})) { # Go to the position behind them (or nearest empty place) my $flip = $self->flip_direction($dir); my ($goxpos, $goypos, $goimxpos) = $self->get_nearest($xpos, $ypos, $flip); #$self->debug("goto", 4, "Target is now facing $dir\n"); $self->goto($goxpos,$goypos); # If we're not already processing movement if (! $FurcReaching) { $self->reach_dest(); } } } } elsif (/^>(.*)$/) { # Tell about an object position # Usually sent as >XXYYNN # But on map join, sent as >XXYYNN[XXYYNN[...]] my $olist = $1; while ($olist =~ /(.{4})(.{2})/g) { my $position = $1; my $object = $2; my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($position); my $onum = $self->furc_to_int($object); if ($onum) { $self->debug("objects", 4, "Placing object $onum at position $xpos, $ypos\n"); } else { $self->debug("objects", 4, "Clearing object at position $xpos, $ypos\n"); } } } elsif (/^\)(.{4})$/) { # Clear furre from position my $position = $1; my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($position); #$self->debug("goto", 4, "Clearing furre at position $xpos, $ypos\n"); delete $FurrePosList{"$xpos|$ypos"}; } elsif (/\]-\#(.)(.{10})(.{3})$/) { # Someone spoke or whispered # Displays specitag before line my $modifier = $1; my $color = $2; my $spec = $3; $Temp{specitag} = $color; } elsif (/^\]s(.{4})(..)(.*?) (.*)?/) { # Set up dream list eg: ]s 7 gLost|Lakes my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($1); my $sponsored = $2; # FIXME: Just a guess my $dreamname = $3; my $title = $4; $FurcDreams{"$xpos|$ypos"} = $dreamname; } elsif (/^\]t(.{4})(.*)/) { # When a dream disappears, remove it from the list my ($xpos,$ypos,$imxpos) = $self->furc_to_pos($1); $FurcDreams{"$xpos|$ypos"} = undef; } elsif (/^]f(.{10})(.)(.)(.)(.*)/) { # Looked at someone my $color = $1; my $gender = $2; my $species = $3; my $form = $4; my $name = $5; my $gname = $self->furc_to_gender($gender); my $sname = $self->furc_to_species($species); # If we're supposed to be following this person if (defined $FurcTarget{name} && $FurcTarget{name} eq $name) { $self->debug("goto", 4, "Located target $name ($color)\n"); $FurcTarget{color} = $color; } $self->handle ( "look", { "color" => $color, "species" => $species, "name" => $name, } ); $self->debug("goto", 4, "$name is a $gname $sname\n"); } elsif (/\]cc.+/) { # New map / dream %FurrePosList = (); %FurreList = (); %MapFurres = (); %FurcDreams = (); # Clear path finding data $self->stop; if (! $FurcInfo{"proxy"}) { $self->furc_command("vascodagama"); } } elsif (/^\]q/) { # Entered a dream } elsif (/^\]\#xxxx 0 Whoops\! The username and password did not match -- please check your spelling\./) { # Incorrect login $self->debug("connection", 2, "Login incorrect, username and password do not match\n"); } elsif (/^END$/) { # If we're not acting as a proxy and aren't reconnecting if ($FurcInfo{name} && $FurcInfo{password}) { # Log in the bot $self->login ( $FurcInfo{name}, $FurcInfo{password}, $FurcInfo{colors}, $FurcInfo{species} ); # Set the bot's description $self->desc ($FurcInfo{desc}); } } elsif (/^handle("social", { "data" => $1 }); $self->debug("social", 4, "$1\n"); if (/^\(\(You enter the dream of (.*)\.\)$/) { # Entered a dream $FurcData{'map'} = "dream_$1"; $self->handle("map", { "map" => "dream_$1" }); } elsif (/^\(\[\*\] (.*?) asks you to join his \/ her company\./) { # Someone summons you $self->handle ("summon", { "name" => $1 }); } elsif (/\(\[\%\] (.*?) just gave you a cookie\!/) { $self->handle("gift", { "name" => $1, "type" => "cookie" }); } elsif (/\(\[\*\] (.*?) requests permission to join your company\./) { $self->handle("join", { "name" => $1 }); } elsif (/\(\* Current players: (.*)/) { my @players = split(/,\s|\./, $1); my %newfurres; foreach my $player (@players) { $newfurres{$player} = 1; if (!$MapFurres{$player} && %MapFurres != ()) { # New Furre arrived $self->handle("list_join", { "name" => $player }); } } foreach my $player (keys(%MapFurres)) { if (!$newfurres{$player}) { # Player left $self->handle("list_part", { "name" => $player }); } } %MapFurres = %newfurres; } elsif (/^\[ (.*)<\/name> whispers, "(.*?)" to you\. \]<\/font>$/) { # Someone whispered you $FurreList{$1} = $Temp{specitag}; $self->handle ( "whisper", { "from" => $1, "message" => $3, } ); $self->debug("social", 4, "$1 whispered \"$2\"\n"); } elsif (/^\((\S*?): (.*)/) { # Someone spoke $FurreList{$1} = $Temp{specitag}; $self->handle ( "say", { "from" => $1, "message" => $2, } ); #$self->debug("social", 4, "$1 said \"$2\"\n"); } } elsif (/^;(.+)\.map$/) { # Changed maps my $mapname = $1; $FurcData{'map'} = $mapname; $self->handle("map", { "map" => $mapname }); $self->stop; # Hardcoded dreams to avoid in certain maps -- lame! # FIXME: Once map parsing is done, I can do this by object type if ($mapname eq "allegria") { $FurcDreams{"50|59"} = "Sponsor dream"; } } return 1; } sub get_heuristic { my $self = shift; my $startx = shift; my $starty = shift; my $startimx = $self->get_imx_pos($startx, $starty); my $endx = shift; my $endy = shift; my $endimx = $self->get_imx_pos($endx, $endy); # Use the smallest possible heuristic value my $heuristic = abs($startx - $endx) + abs($starty - $endy); my $imheuristic = abs($startimx - $endimx) + abs($starty - $endy); if ($imheuristic < $heuristic) { $heuristic = $imheuristic } $self->debug("goto", 4, "Heuristic for ($startx [$startimx], $starty -- $endx [$endimx], $endy) is $heuristic\n"); return $heuristic; } sub calculate_next_move { my $self = shift; my $startx = shift; my $starty = shift; my $endx = shift; my $endy = shift; my $lowest = undef; my $nextx = undef; my $nexty = undef; foreach my $tdir ($self->each_direction()) { my ($xpos, $ypos, $imxpos) = $self->relative_position($startx,$starty,$tdir); if ($self->occupied($xpos, $ypos)) { $self->debug("goto", 4, "Next position is occupied: $xpos, $ypos [D=$tdir]\n"); next; } my $heuristic = $self->get_heuristic($xpos, $ypos, $endx, $endy); my $tried1 = $FurcTried{"$xpos|$ypos"}; my $tried2 = $FurcTried{"$nextx|$nexty"}; if (! defined $lowest || $heuristic < $lowest) { $self->debug("goto", 4, "Found better move to $xpos, $ypos [H=$heuristic, T=$tried1]\n"); #$self->debug("goto", 4, "New move replaces $nextx, $nexty [H=$lowest, T=$tried2]\n"); $nextx = $xpos; $nexty = $ypos; $lowest = $heuristic; #$self->tried($xpos,$ypos); } } $self->debug("goto", 4, "Found best move ($nextx, $nexty)\n"); return ($nextx, $nexty); } sub reach_dest { my $self = shift; my $botx = $FurcData{xpos}; my $boty = $FurcData{ypos}; my $targetx = $FurcTarget{xpos}; my $targety = $FurcTarget{ypos}; my $distance = $self->get_heuristic($botx,$boty,$targetx,$targety); # If we've reached our destination pause/stop if (($FurcTarget{name} && $self->next_to($FurcTarget{name})) || $distance < 1) { # If we're following someone if ($FurcTarget{color}) { $self->debug("goto", 4, "Reached target, pausing next to them\n"); $self->pause; } # If we were seeking to someone elsif ($FurcTarget{name}) { $self->debug("goto", 4, "Reached furre, stopping next to them\n"); # Sit and face them $self->sit; my ($name,$xpos,$ypos) = $self->seen($FurcTarget{name}); $self->face($xpos, $ypos); $self->stop; } # If we're seeking to a position else { $self->debug("goto", 4, "Reached position, stopping here\n"); $self->stop; $self->twitch; } return; } my $maxtries = 3; my ($nextx, $nexty); for (my $i = 0; $i <= $maxtries; $i++) { ($nextx,$nexty) = $self->calculate_next_move($botx,$boty,$targetx,$targety); if ($nextx && $nexty) { last; } else { $self->debug("goto", 4, "Couldn't determine next move\n"); $self->pause; } } my $direction = $self->get_vector($botx,$boty,$nextx,$nexty); # If we've got a direction if (defined $direction) { $self->debug("goto", 4, "Moving toward target ($targetx, $targety): $direction\n"); $FurcReaching = "$nextx|$nexty"; $self->godir($direction); } # We couldn't find a direction else { $self->debug("goto", 4, "Giving up, couldn't find our target after $maxtries tries\n"); $self->stop; $self->twitch; } } sub tried { my $self = shift; my $xpos = shift; my $ypos = shift; $FurcTried{"$xpos|$ypos"}++; #$self->debug("goto", 4, "Adding $xpos, $ypos to tried list (Tried: " . $FurcTried{"$xpos|$ypos"} . ")\n"); } sub avoid { # Tell the bot to avoid a specific position my $self = shift; my $xpos = shift; my $ypos = shift; my $lastx = shift; my $lasty = shift; # Determine which way we failed going # TODO: Read in map data, propogate this list with object locations # Also, set "value" to be the direction from which you can't move to that # location (needed eg. for walls..) $FurcAvoid{"$xpos|$ypos"} = 1; if ($lastx && $lasty) { $FurcAvoid{"$xpos|$ypos"} = "$lastx|$lasty"; } #$self->debug("goto", 4, "Adding $xpos, $ypos to avoid list (from $lastx, $lasty)\n"); } sub occupied { # Determines if a position is occupied my $self = shift; my $xpos = shift; my $ypos = shift; my $occupied = 0; if ($FurcDreams{"$xpos|$ypos"}) { # If it's not the dream we're seeking if ($FurcTarget{dream} ne $FurcDreams{"$xpos|$ypos"}) { $occupied = 1; } } if ($FurrePosList{"$xpos|$ypos"}) { $occupied = 1; } if ($FurcAvoid{"$xpos|$ypos"}) { my $botx = $FurcData{xpos}; my $boty = $FurcData{ypos}; if ($FurcAvoid{"$xpos|$ypos"} eq "$botx|$boty") { $self->debug("goto", 4, "Can't move to $xpos, $ypos from $botx, $boty\n"); $occupied = 1; } elsif ($FurcAvoid{"$xpos|$ypos"} == 1) { $occupied = 1; } } #$self->debug("goto", 4, "Checking if $xpos, $ypos is occupied (O=$occupied)\n"); return $occupied; } sub get_vector { # Determine direction of movement my $self = shift; my $xpos1 = shift; my $ypos1 = shift; my $xpos2 = shift; my $ypos2 = shift; # Use the "in middle" X position for vectorizing my $imxpos1 = $self->get_imx_pos($xpos1, $ypos1); my $imxpos2 = $self->get_imx_pos($xpos2, $ypos2); # Get the real distance on each axis #my $xdist = sqrt(($xpos1 - $xpos2) ** 2); #my $ydist = sqrt(($ypos1 - $ypos2) ** 2); my $direction; # If we haven't defined both, there is no vector unless ($imxpos1 && $ypos1 && $imxpos2 && $ypos2) { return undef; } # If they're the same, we have no vector if ($imxpos1 == $imxpos2 && $ypos1 == $ypos2) { return undef; } # X Axis different (Furre directly east or west) if ($imxpos2 > $imxpos1 && $ypos2 == $ypos1) { # Furre directly east #$direction = "e"; $direction = "ne"; } elsif ($imxpos2 < $imxpos1 && $ypos2 == $ypos1) { # Furre directly west #$direction = "w"; $direction = "sw"; } # Y Axis different (Furre directly north or south) elsif ($imxpos2 == $imxpos1 && $ypos2 > $ypos1) { # Furre directly south #$direction = "s"; $direction = "se"; } elsif ($imxpos2 == $imxpos1 && $ypos2 < $ypos1) { # Furre directly north #$direction = "n"; $direction = "nw"; } # Both axis different elsif ($imxpos2 > $imxpos1 && $ypos2 < $ypos1) { $direction = "ne"; } elsif ($imxpos2 < $imxpos1 && $ypos2 > $ypos1) { $direction = "sw"; } elsif ($imxpos2 > $imxpos1 && $ypos2 > $ypos1) { $direction = "se"; } elsif ($imxpos2 < $imxpos1 && $ypos2 < $ypos1) { $direction = "nw"; } #$self->debug("goto", 4, "Vector for ($xpos1 [$imxpos1], $ypos1) to ($xpos2 [$imxpos2], $ypos2) is $direction\n"); return $direction; } sub get_nearest { # Return nearest clear position my $self = shift; my $xpos = shift; my $ypos = shift; my $dir = shift; foreach my $tdir ($dir, $self->each_direction) { my ($goxpos,$goypos,$goimxpos) = $self->relative_position($xpos,$ypos,$tdir); if (! $self->occupied($goxpos, $goypos)) { return ($goxpos, $goypos); } # Eeeeeek.. recursive function with no maximum depth! # FIXME: Enable this after depth checks added #return $self->get_nearest($goxpos, $goypos, $tdir); } $self->debug("goto", 2, "Couldn't find an open spot near $xpos, $ypos\n"); return undef; } sub each_direction { # Return an array of all directions my $self = shift; return ("se", "sw", "nw", "ne"); } sub direction_to { my $self = shift; my $targetx = shift; my $targety = shift; my $botx = $FurcData{xpos}; my $boty = $FurcData{ypos}; my $vector = $self->get_vector($botx,$boty,$targetx,$targety); return $vector; } sub direction { # Return the direction I'm facing my $self = shift; return $FurcData{dir}; } sub relative_position { # Get the relative position based on a position and direction my $self = shift; my $xpos = shift; my $ypos = shift; my $direction = shift; my $imxpos = $self->get_imx_pos($xpos, $ypos); if ($direction eq "nw") { $imxpos--; $ypos--; } elsif ($direction eq "ne") { $imxpos++; $ypos--; } elsif ($direction eq "se") { $imxpos++; $ypos++; } elsif ($direction eq "sw") { $imxpos--; $ypos++; } else { $self->debug("goto", 1, "Unhandled direction: $direction\n"); } # FIXME: WTF is going on in this function? Use get_imx_pos? $xpos = $imxpos; # If the Y position is even, add 1 to the y position if ($ypos & 1) { $xpos++ } return ($xpos, $ypos, $imxpos); } sub flip_direction { my $self = shift; my $direction = shift; my %flips = ( "nw" => "se", "se" => "nw", "ne" => "sw", "sw" => "ne" ); return $flips{$direction}; } sub flip_axis { my $self = shift; my $axis = shift; my %faxis = ( "x" => "y", "y" => "x", ); return $faxis{$axis}; } sub get_axis { my $self = shift; my $direction = shift; my %axis = ( "nw" => "x", "se" => "x", "ne" => "y", "sw" => "y" ); return $axis{$direction}; } sub distance_to { my $self = shift; my $name = shift; # Get the position of the person my ($tname,$xpos,$ypos) = $self->seen($name); my $distance = $self->distance_to_pos($xpos,$ypos); return $distance; } sub distance_to_pos { my $self = shift; my $targetx = shift; my $targety = shift; my $botx = $FurcData{xpos}; my $boty = $FurcData{ypos}; my $distance = $self->get_heuristic($botx, $boty, $targetx, $targety); return $distance; } sub next_to { my $self = shift; my $name = shift; # FIXME: If name is blank, hangs forever. Could have further implications. if (!$name) { return 0; } # Get the position of the person my ($tname,$xpos,$ypos) = $self->seen($name); my $nextto = $self->next_to_pos($xpos,$ypos); $self->debug("goto", 4, "Checking if we're next to $name: $nextto\n"); return $nextto; } sub next_to_pos { my $self = shift; my $targetx = shift; my $targety = shift; my $nextto = 0; my $distance = $self->distance_to_pos($targetx, $targety); if ($distance <= 1) { $nextto = 1; } $self->debug("goto", 4, "Checking if we're next to $targetx, $targety [D=$distance R=$nextto]\n"); return $nextto; } ##################### # Furcadia specific # sub login { my $self = shift; my $name = shift; my $pass = shift; my $colors = shift; my $species = shift; my $speccode; if ($species) { my @spec = split(//,$species); foreach my $num (@spec) { $speccode .= chr($num + 32); } } $self->debug("connection", 2, "Logging in with username $name\n"); $self->furc_command("connect $name $pass"); $self->furc_command("color $colors$speccode"); } sub desc { my $self = shift; my $desc = shift; $self->furc_command("desc $desc [RG]"); } # Append "tags" to end of %FurcInfo description sub desc_tag { my $self = shift; my $tag = shift; my $desc = $FurcInfo{"desc"}; $self->desc("$desc $tag"); } sub who { my $self = shift; $self->furc_command("who"); } sub whisper { my $self = shift; my $furre = shift; my $message = shift; $furre = $self->pipe_space($furre); $self->furc_command("wh $furre $message"); } sub say { my $self = shift; my $message = shift; $self->furc_command("\"$message"); } sub emote { my $self = shift; my $emote = shift; $self->furc_command(":$emote"); } sub look { my $self = shift; my $look = shift; return unless $look; $self->furc_command("\"look $look"); } sub online { my $self = shift; my $name = shift; return unless $name; $self->furc_command("onln $name"); while (1) { my @data = $self->swallow_socket(1); my $data = "@data"; if ($data =~ /^\]\%(\d)/) { my $online = $1; return $online; } else { # Try to pass unrelated data to the scourer $self->scour_socket($data); } } } sub gomap { my $self = shift; my $map = shift; $self->furc_command("gomap $map"); } sub goto { my $self = shift; my $xpos = shift; my $ypos = shift; # If the target hasn't changed, don't bother trying to reach it again if ($FurcTarget{xpos} eq $xpos && $FurcTarget{ypos} == $ypos) { return } $FurcTarget{xpos} = $xpos; $FurcTarget{imxpos} = $self->get_imx_pos($xpos,$ypos); $FurcTarget{ypos} = $ypos; $self->debug("goto", 4, "Updated target location: $xpos [$FurcTarget{imxpos}], $ypos\n"); } sub follow { my $self = shift; my $who = shift; my ($name,$xpos,$ypos) = $self->seen($who); return undef unless ($name && $xpos && $ypos); $self->stop; $FurcTarget{name} = $name; $Temp{lookreason} = "follow"; $self->look($name); return $name; } sub seek { my $self = shift; my $who = shift; my ($name,$xpos,$ypos,$dir) = $self->seen($who); return undef unless ($name && $xpos && $ypos); if($self->seek_pos($xpos,$ypos,$dir)) { $FurcTarget{name} = $name; return $name; } } sub seek_dream { my $self = shift; my $dream = shift; my ($name,$xpos,$ypos) = $self->seen_dream($dream); return undef unless ($name && $xpos && $ypos); $self->goto($xpos,$ypos); $self->reach_dest(); $FurcTarget{dream} = $name; return $name; } sub seek_pos { my $self = shift; my $xpos = shift; my $ypos = shift; my $dir = shift; $self->stop; my ($goxpos,$goypos) = $self->get_nearest($xpos,$ypos,$dir); if ($goxpos && $goypos) { $self->goto($goxpos,$goypos); $self->reach_dest(); return 1; } # Give up if there are no open spots near them :-/ return 0; } sub face { my $self = shift; my $xpos = shift; my $ypos = shift; my $botd = $FurcData{dir}; my $targetd = $self->direction_to($xpos,$ypos); my %dirs = ('nw' => 1,'ne' => 2, 'se' => 3,'sw' => 4); my $difference = $dirs{$targetd} - $dirs{$botd}; my $times; my $direction; if ($difference > 0) { $times = $difference; $direction = 0; } elsif ($difference < 0) { $times = $difference * -1; $direction = 1; } if ($times == 3) { $times = 1; $direction = $direction ^ 1; } return unless $times; foreach my $i (1 .. $times) { if ($direction == 0) { $self->right; } if ($direction == 1) { $self->left; } } } sub stop { my $self = shift; $self->debug("goto", 4, "Stopping path finding\n"); $self->pause; %FurcTarget = (); #$self->twitch; } sub pause { my $self = shift; %FurcAvoid = (); %FurcTried = (); $FurcReaching = undef; #$self->twitch; } sub join { my $self = shift; my $name = shift; $self->furc_command("join $name"); } sub summon { my $self = shift; my $name = shift; $self->furc_command("summon $name"); } sub left { my $self = shift; $self->furc_command("<"); } sub right { my $self = shift; $self->furc_command(">"); } sub nw { my $self = shift; $self->furc_command("m 7"); } sub ne { my $self = shift; $self->furc_command("m 9"); } sub sw { my $self = shift; $self->furc_command("m 1"); } sub se { my $self = shift; $self->furc_command("m 3"); } sub godir { my $self = shift; my $direction = shift; return unless defined $Directions{$direction}; $direction = $Directions{$direction}; $self->furc_command("m $direction"); } sub twitch { my $self = shift; $self->left; $self->right; } sub stand { my $self = shift; $self->furc_command("stand"); } sub sit { my $self = shift; $self->furc_command("sit"); } sub lie { my $self = shift; $self->furc_command("lie"); } sub liedown { my $self = shift; $self->furc_command("liedown"); } sub quit { my $self = shift; $self->furc_command("quit"); } # FIXME: Pointless? sub exit { my $self = shift; $self->quit; } ############################### # Furcadia Conversion related # sub pipe_space { # Convert whitespace to pipes my $self = shift; my $conv = shift; $conv =~ s/\s/|/g; return $conv; } sub int_to_furc { # Convert single integer to Furc my $self = shift; my $int = shift; my $tmp1 = (int($int / 95)) + 32; my $tmp2 = (int($int % 95)) + 32; return (chr($tmp1) . chr($tmp2)); } sub furc_to_int { # Convert Furc to integer my $self = shift; my $furc = shift; # FIXME: This is probably a hack ("0" pads Furc numbers) if (length($furc) == 1) { $furc = " $furc"; } my $tmp1 = substr($furc, 0,1); my $tmp2 = substr($furc, 1,1); return ((ord($tmp1) - 32) * 95) + (ord($tmp2) - 32); } sub furc_to_dir { # Convert m 1/3/7/9 to dir my $self = shift; my $m = shift; foreach my $dir (keys %Directions) { if ($Directions{$dir} == $m) { return $dir; } } return undef; } sub furc_to_gender { my $self = shift; my $gender = shift; my $gint = $self->furc_to_int($gender); # FIXME: Make these global! my @glist = ( "female", "male", "unspecified" ); my $gname = $glist[$gint]; if (! $gname) { $self->debug("goto", 4, "Couldn't find gender name from '$gint' ($gender)\n"); } return $gname; } sub furc_to_species { my $self = shift; my $species = shift; my $sint = $self->furc_to_int($species); # FIXME: Make these global! my @slist = ( "rodent", "equine", "feline", "canine", "musteline", "lapine", ); my $sname = $slist[$sint]; if (! $sname) { $self->debug("goto", 4, "Couldn't find species name from '$sint' ($species)\n"); } return $sname; } sub furc_to_pos { # Convert full Furc coordinate to x,y my $self = shift; my $furc = shift; if (!$furc) { return undef } my $furcx = substr($furc,0,2); my $furcy = substr($furc,2,2); my $intx = $self->furc_to_int($furcx) * 2; my $inty = $self->furc_to_int($furcy); my $imagx = $self->get_imx_pos($intx,$inty); #$self->debug("goto", 4, "Converting '$furc' to position: $intx, $inty\n"); return ($intx, $inty, $imagx); } sub pos_to_furc { # Convert x,y to Furc coordinate my $self = shift; my $xpos = shift; my $ypos = shift; my $furc = $self->int_to_furc($xpos/2) . $self->int_to_furc($ypos); return $furc; } sub get_imx_pos { my $self = shift; my $xpos = shift; my $ypos = shift; my $imagx = $xpos; # If Y is odd number, subtract one from "in middle" number if ($ypos & 1) { $imagx-- } return $imagx; } sub near_furres { my @result; foreach my $furre (keys %FurreList) { push (@result, $furre); } return @result; } sub frame_to_dir { my $self = shift; my $frame = shift; my $fint = $self->furc_to_int($frame); my $direction; # Ignore "frameless" furres if ($fint == 0) { return undef; } # Propagate this using lowest SW frame #my @species = #( # "1", # Standard rodent # "41", # Standard feline # "161", # Winged feline # "261", # Dragon # "781", # Gryphin # "941", # Squirrel #); my @frames = ( "sw", "se", "nw", "ne", ); # Example matrix (feline standard) # 41-44,57 -- SW # 45-48,58 -- SE # 49-52,59 -- NW # 53-56,60 -- NE # Determine the correct bracket my $bracket = int ($fint / 10) * 10; if ($bracket % 20 == 0 && $bracket > 0) { if ($bracket == $fint) { $bracket -= 20; } } elsif ($bracket > 0) { $bracket -= 10; } my $min = $bracket + 1; my $max = $min + 3; my $lie = $min + 16; # Loop through and find the appropriate frame foreach my $i (0..3) { if ($fint >= $min && $fint <= $max || $fint == $lie) { $direction = $frames[$i]; # Doesn't make any sense... They swapped NE and NW if ($fint == $lie) { if ($direction eq "ne") { $direction = "nw"; } elsif ($direction eq "nw") { $direction = "ne"; } } #$self->debug("goto", 4, "Calculated direction ($min - $max, $lie) from frame '$frame' [F=$fint,B=$bracket]: $direction\n"); last; } $lie++; $min += 4; $max = $min + 3; } if (! $direction) { $self->debug("goto", 1, "Couldn't find direction from frame '$frame' [$fint] -- ($_)\n"); } return $direction; } # Read in Furcadia INI file sub load_ini { my $self = shift; my $file = shift; my %conf; if (!open (CONF, $file)) { $self->debug("settings", 0, "Error parsing INI file $file: $!\n"); return 0; } while () { if (/(.*?)=(.*)/) { my $key = lc($1); my $val = $2; # Enable "hash" style configuration options # Eg: owners@=Malverian # owners@=Cinder|Bear if ($key =~ /(.*?)\@/) { $key = $1; $conf{$key}{$val} = 1; } else { $conf{$key} = $val; } } } return %conf; } sub write_ini { my $self = shift; my $file = shift; my %conf = @_; if (!open (CONF, ">$file")) { $self->debug("settings", 0, "Couldn't open INI file $file for writing: $!\n"); return 0; } foreach my $key (keys (%conf)) { my $val = $conf{$key}; $key = "\u$key"; next unless defined($val); # If it's an array, print it out with foo@ if (ref($val) eq 'ARRAY') { foreach my $sub (@{$val}) { print CONF "$key\@=$sub\n"; } } # Same thing for hashes.. so that write_ini(load_ini('some.ini')) will work :-P elsif (ref($val) eq 'HASH') { foreach my $sub (keys %{$val}) { print CONF "$key\@=$sub\n"; } } # Anything else should be printed as flat value else { print CONF "$key=$val\n"; } } close(CONF); return 1; } #################### # Accessor Methods # sub ret_pos_list { my $self = shift; return (%FurrePosList); } sub ret_furc_data { my $self = shift; return (%FurcData); } ################## # DEBUGGING ONLY # sub debugmessages { my $self = shift; my @messages = @_; foreach my $debugmsg (@messages) { if ($debugmsg =~ /^\-(.*)/) { $DebugMessages{$1} = -1; } elsif ($debugmsg =~ /^\+?(.*)/) { $DebugMessages{$1} = 1; } } return (%DebugMessages); } sub debug { my $self = shift; my $package = shift; my $level = shift; my $data = shift; my $nlevel = $DebugLevels[$level]; return 0 if (defined $DebugMessages{'all'} && $DebugMessages{'all'} < 0); if ($DebugMessages{$package} || $DebugMessages{'all'}) { if (!defined $DebugMessages{$package} || $DebugMessages{$package} >= 0) { if ($WindowsHost) { $Win32Tray->WriteDebug("[Furcadia->${package}] $data"); } else { print STDERR "[${nlevel}Furcadia->${package}\e[m] $data"; #print STDERR "[($$) ${nlevel}Furcadia->${package}\e[m] $data"; } } #cluck; } } sub pretend { my $self = shift; $DebugPretend = shift; } sub dump_hash { my $hashref = shift; foreach my $key ( keys %{$hashref} ) { if ( ref ($hashref->{$key} ) ) { print STDOUT "!! '$key' !!\n"; dump_hash($hashref->{$key}); } else { print STDOUT "'$key' = '".$hashref->{$key}."'\n"; } } } sub version { # Return the module version my $self = shift; return $VERSION; } 1;