Games::RolePlay::MapGen::MapQueue - An object for storing objects by location, on a map, with visi-calc support
use Games::RolePlay::MapGen; use Games::RolePlay::MapGen::MapQueue; my $map = new Games::RolePlay::MapGen; $map->generate; my $queue = new Games::RolePlay::MapGen::MapQueue( $map ); $queue->add( $object1 => (1, 2) ); $queue->add( $object2 => (5, 3) ); # The objects can be any unique identifier or reference (blessed or unblessed). $queue->replace( $object3 => (5, 3) ); # remove first if it's already on the map somewhere else $queue->remove( $object3 ); # just remove it my $distance = $map->distance( $object1, $object2 ); # the distance from o1 to o2 or undef if the tile is not visible my @all = $queue->all_open_locations; my @los = $queue->locations_in_line_of_sight( @dude_position );
These docs refer to something called the lhs and the rhs from time to time. They are short-hand for left-hand-side and right-hand-side. Where there is a source and a destination, or a beginning and an end, the lhs is the start or beginning and the rhs is the end.
This module assumes that all tiles are in the appropriate base units (5ft by 5ft tiles for d20 game systems) and doesn't even pretend to check their size. If your game uses 5ft by 5ft tiles, it's up to you to make sure the tiles in the map passed to this MapQueue module are actually set up correctly.
MapQueue
Additionally, the distances returned by distance()/ldistance() and given to locations_in_range_and_line_of_sight are in tile units, ignoring the size in feet (or meters or whatever) of the tiles. If you want the distance in feet (or meters), you'll have to multiply and divide it on your own.
distance()
ldistance()
locations_in_range_and_line_of_sight
new() takes a single argument, which is required: a MapGen map object
new()
Most of the MapQueue functions are cached with Memoize, flush() clears the caches.
flush()
Store an object on the map at a specified location. This function will raise an error if the location doesn't make any sense. To make sense, the location must be within the map boundaries and must be an open tile -- either a corridor or a room tile. It will additionally raise an error if the object is already elsewhere on the map.
my $pistol = bless {}, "Sig P229r"; $mq->add($pistol, (2,2) ); $mq->add( "boring string", (2,3) );
See is_open() below for a function to check whether a location makes sense.
is_open()
Remove an object on the map. It raises an error if the object isn't on the map or if the location doesn't make sense (see add()).
add()
$mq->remove( $pistol ); $mq->remove( "boring string" );
Exactly like add(), except that it removes the pistol iff it's already on the map.
$mq->replace( $pistol, (5,4) ); $mq->replace( $pistol, (5,4) ); $mq->replace( blagh => (2,3) );
Locates an object in the queue, returning the (x,y) coordinate. Raises an error if the object isn't on the map.
my @loc = eval { $mq->add($pistol) }; warn "wasn't there" if $@;
In scalar context, distance() returns the distance from one object to another. It raises errors if the lhs or rhs objects aren't found in the queue. If there is no line of sight from the lhs to the rhs, the function returns a scalar undef (even in list context).
my $dist = $mq->distance( blagh => $pistol );
If distance() is given a third argument that evaluates to true, distance will instead return both the distance and a boolean value indicating whether there's a line of sight. (It returns the distance even if there's no line of sight.)
my ($dist, $los) = $mq->distance( blagh => $pistol, 1 );
Works rather like distance() but takes locations as arguments instead of objects. It raises errors if either of the locations don't make sense (see add()). If there's no line of sight, ldistance() returns a scalar undef (even in list context).
my $dist = $mq->ldistance( (1,1), (2,2) );
If given a fifth argument, ldistance returns $dist and $los like distance().
ldistance
$dist
$los
my ($dist, $los) = $mq->ldistance( (1,1), (2,2), 1 );
Returns a scalar indicating whether there's a line of sight from the lhs object to the rhs object. $los will be one of LOS_NO or LOS_YES, which are exported in to the requiring namespace. They are usable as booleans, so you don't have to use the names, and are single scalars in list context.
LOS_NO
LOS_YES
The $los values returned from distance are of the same type as the $los returned here.
my $los = $mq->line_of_sight( blagh => $pistol ); if( $los == LOS_NO ) { # if( $los ) is fine also # blargh }
The function raises errors if the objects aren't found on the map.
Works just like line_of_sight but takes locations as arguments instead of objects. The function raises an error if the locations don't make any sense.
line_of_sight
my $los = $mq->lline_of_sight( (1,1), (2,2) );
Determines if there is a line of sight from an object to a closure (a wall, door, or opening of a tile). Presently there are no functions to return door objects from the map, but you can get them from the map yourself.
my $door = $map->[ $y ][ $x ]{od}{ w }; # west door of ($x,$y); $mq->add( $some_player_object ); $mq->closure_line_of_sight( $some_player_object, $door );
The function raises errors if the $door isn't a door object (or isn't on the map) or if the lhs object isn't on the map.
$door
The decision about whether there's a line of sight includes the idea that the line of sight doesn't do much good if you can't see most of the door at an angle that allows you to examine it...
That minimum angle is a package global that can be changed but defaults to 9 degrees. The global is in radians, but can be set like this:
$Games::RolePlay::MapGen::MapQueue::CLOS_MIN_ANGLE = deg2rad(9);
(deg2rad() comes from Math::Trig.)
deg2rad()
Like closure_line_of_sight(), this tells whether there's a line of sight from a tile location to a closure. It takes five arguments:
closure_line_of_sight()
$mq->closure_lline_of_sight( ($x,$y), ($x,$y,$d) );
Like the other "l-name" functions, the lhs and rhs are coordinate pairs, but unlike the others this function also takes a direction argument (to name the closure). The fifth argument must be one of 'n', 'e', 's', or 'w'.
n
e
s
w
Predictably, it raises errors if the locations don't make sense or the direction is incorrect or missing.
Returns all the object so the map as an array. Each element in the array is just the objects as they were placed on the map.
my @objs = $mq->objs; my ($pistol) = grep {m/P229r/} @objs;
Like objs(), but also returns the locations. Each element is an array ref, whose first element is the location (as an arrayref) and whose second element is an arrayref of objects.
objs()
my @objs = $mq->objs_with_locations; my ($loc, $a) = $objs[0]; my ($x,$y) = @$loc; # the location my @objs_a = @$a; # the objects at the location
Like objs(), but only returns objects at a specific location ($x,$y).
($x,$y)
my @objs = $mq->objs_at_location($x,$y);
The function raises errors if the location doesn't make sense.
Like objs(), but only returns objects with a clear line of sight from ($x,$y).
my @objs = $mq->objs_in_line_of_sight($x,$y);
Returns all the locations on the map.
my @locs = $mq->all_open_locations; my ($x,$y) = @{ $locs[0] };
It will optionally return the array as an arrayref:
my $locs = $mq->all_open_locations; my ($x,$y) = @{ $locs->[0] };
Returns a single random open location from the map.
my ($x,$y) = $mq->random_open_location;
It will optionally return the location as an array:
my @xy = $mq->random_open_location;
Returns all open tile locations to which there is a clear line of sight from the ($x,$y) location argument. It raises an error if the location doesn't make sense.
my @locations = $mq->locations_in_line_of_sight($x,$y); for my $l (@locations) { local $" = ","; print "I can see (@$l).\n"; }
Returns all open tile locations to which there is a clear line of sight from the ($x,$y) location argument that are also within a certain range. It raises an error if the location doesn't make sense or if the range isn't greater than 0.
0
my @locations = $mq->locations_in_range_and_line_of_sight($x,$y);
Takes two locations (four scalars) as arguments. Raises errors if either location doesn't make sense or if there isn't a clear light of sight from the lhs to the rhs.
Returns the locations of tiles a player would step through to get from the lhs to the rhs. The path is not necessarily optimal. locations_in_path() uses a straight line heuristic with slight corrections for passing through doors and the like. It should be reasonably close to optimal.
locations_in_path()
The path is meant to be reasonably compatible with d20 game systems, but may differ slightly in certain cases.
my @locations = $mq->locations_in_path( ($src_x,$src_y), ($dst_x,$dst_y) ); for my $l (@locations) { local $" = ","; print "Player through to (@$l) on his way to ($dst_x,$dst_y).\n"; }
The path is inclusive, meaning, the locations in the path include the starting point and the end point.
Takes two locations (four scalars) as arguments and raises errors if either location doesn't make sense. If one were to draw a line from each corner of the lhs tile to each corner of the rhs tile, this function would return true if and only if none of the lines intersects a wall closure and false otherwise.
This is meant to be reasonably compatible with d20 game systems.
It returns either LOS_COVER, when there is over or LOS_NO_COVER when there isn't. LOS_COVER evaluates to true and LOS_NO_COVER does not, so you can choose not to use these constants.
LOS_COVER
LOS_NO_COVER
my $melee_cover = $mq->melee_cover( @lhs => @rhs ); # if( $melee_cover == LOS_COVER ) would also work if( $melee_cover ) { print "That's a tough shot, really.\n"; }
Additionally, melee_cover() automatically returns LOS_NO_COVER if the tiles are farther apart than one tile in either direction. That's really more of a reach or ranged attack.
melee_cover()
Takes two locations (four scalars) as arguments and raises errors if either location doesn't make sense. If all four corners of the rhs tile are visible from any one corner in the lhs tile, then there is LOS_NO_COVER.
To correct for various weird artifacts because of smallish openings and long distances, when all four corners of the rhs are visible from the lhs, that corner must also be able to see a tighter box toward the middle of the tile. I doesn't do any good to be able to see the corners of the rhs if you can't see the middle!
Lastly, LOS_COVER is upgraded to LOS_DOUBLE_COVER if there is cover (i.e., there is no corner of lhs that can see all four corners of the rhs) and there is also cover when considering a tighter line of sight.
LOS_DOUBLE_COVER
This is meant to be reasonably compatible with d20 game systems, but it differs quite a bit because computers are willing to do more work than humans and the string you're meant to use is only virtual here.
Takes a standard ($x,$y) pair as arguments. Returns a boolean indicating whether a location is an open tile (true) or a wall tile (false). This is actually the function you can use to see if a location "makes sense" as first described in add().
Takes a semi-standard ($x,$y,$d) triplet as arguments (where $d is 'n', 'e', 's', or 'w'). Returns a boolean indicating whether a closure is a door. Raises an error if the location doesn't make sense.
($x,$y,$d)
$d
Takes a semi-standard ($x,$y,$d) triplet as arguments (where $d is 'n', 'e', 's', or 'w'). Returns a boolean indicating whether a door is closed. Raises errors if the location doesn't make sense or there isn't a door there.
Takes a ($x,$y,$d) closure triplet as arguments. Raises errors if the location doesn't make sense, there isn't a door, or the door is already open.
Takes a ($x,$y,$d) closure triplet as arguments. Raises errors if the location doesn't make sense, there isn't a door, or the door is already closed.
Returns either the position of the last column of the map (rather like $#array) or an array of all the columns on the map (like 0 .. $#array).
$#array
0 .. $#array
Returns either the position of the last row of the map (rather like $#array) or an array of all the rows on the map (like 0 .. $#array).
Paul Miller jettero@cpan.org
jettero@cpan.org
I am using this software in my own projects... If you find bugs, please please please let me know.
I normally hang out on #perl on freenode, so you can try to get immediate gratification there if you like. irc://irc.freenode.net/perl
Copyright (c) 2008 Paul Miller -- LGPL [Software::License::LGPL_2_1]
perl -MSoftware::License::LGPL_2_1 \ -e '$l = Software::License::LGPL_2_1->new({ holder=>"Paul Miller"}); print $l->fulltext' | less
perl(1), Games::RolePlay::MapGen
To install Games::RolePlay::MapGen, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Games::RolePlay::MapGen
CPAN shell
perl -MCPAN -e shell install Games::RolePlay::MapGen
For more information on module installation, please visit the detailed CPAN module installation guide.