initial commit

main
commit de918c767a
  1. 5
      .gitignore
  2. 94
      HexGrid.pm
  3. 50
      HexGrid/Pin.pm
  4. 74
      HexGrid/PopUp.pm
  5. 66
      HexGrid/Region.pm
  6. 150
      HexGrid/Tile.pm
  7. 22
      MWTemplate.pm
  8. 82
      parse-regions.pl
  9. 37
      popup.pl
  10. 20
      svg/hex-lines.svg
  11. 17
      svg/hexes.svg
  12. 7
      svg/house.svg
  13. 3
      svg/jagged.svg
  14. 3
      svg/mountain.svg
  15. 32
      svg/popup.svg
  16. 19
      svg/popup2.svg
  17. 127
      test.pl
  18. 109
      wiki-tile.pl
  19. 132
      wiki-tile2.pl

5
.gitignore vendored

@ -0,0 +1,5 @@
*.png
*.jpg
*.jpeg
*.gif
output/*

@ -0,0 +1,94 @@
package HexGrid;
use v5.30;
use Moo;
use MooX::Aliases;
use SVG;
use Hash::Merge qw(merge);
use HexGrid::Tile;
use HexGrid::Region;
use HexGrid::PopUp;
use Carp;
use Data::Dumper;
use feature "signatures";
no warnings "experimental::signatures";
has regions => (is => 'rw', default => sub { {} });
has sideLength => (is => 'rw', default => 100);
has width => (is => 'rw', default => 1000);
has height => (is => 'rw', default => 1000);
has defaults => (is => 'rw', default => sub { {} });
has make_popups => (is => 'rw', default => 1, alias => 'popups');
has popup_class => (is => 'rw', default => 'pin-popup');
has hidden_popups => (is => 'rw', default => 1, alias => 'popups_are_hidden');
sub tile_width($this) { 2 * $this->{sideLength} }
sub tile_height($this) { sqrt(3) * $this->{sideLength} }
#Hash::Merge::merge defaults to Left Precedence, i.e. merge first arg onto second arg
sub add_region($this, $region) { $this->{regions}{$region->{name}} = $region; }
sub make_region($this, $name, %defaults)
{
my $tile_defaults = merge(\%defaults, $this->{defaults});
$this->add_region(HexGrid::Region->new(name => $name, defaults => $tile_defaults));
}
sub get_tile_at($this, $nw, $sw)
{
# return $this->{tiles}{$nw}{$sw} if exists $this->{tiles}{$nw}{$sw};
foreach my $region (keys $this->{regions}->%*)
{
return $this->{regions}{$region}{tiles}{$nw}{$sw} if exists $this->{regions}{$region}{tiles}{$nw}{$sw};
}
croak "No pin at $nw,$sw";
}
sub render($this)
{
my ($min_x,$min_y,$max_x,$max_y) = qw(Inf Inf -Inf -Inf);
my $svg = SVG->new();
my $root_style = $svg->style();
my $style_text = "";
$style_text .= ".$this->{popup_class} { visibility: hidden; }" if $this->{hidden_popups};
$root_style->cdata($style_text);
my $laters = [];
foreach my $region (keys %{$this->{regions}})
{
my $m = $this->{regions}{$region}->render($svg, $laters, $this);
$min_x = $m->{min_x} if $m->{min_x} < $min_x;
$min_y = $m->{min_y} if $m->{min_y} < $min_y;
$max_x = $m->{max_x} if $m->{max_x} > $max_x;
$max_y = $m->{max_y} if $m->{max_y} > $max_y;
}
foreach my $later (@$laters)
{
$later->($svg);
}
my $width = $max_x - $min_x + $this->tile_width;
my $height = $max_y - $min_y + $this->tile_height;
$svg->{-docref}{-document}{viewBox} = "$min_x $min_y $width $height";
$svg->{-docref}{-document}{width} = $this->{width};
$svg->{-docref}{-document}{height} = $this->{height};
$svg->{-docref}{-document}{preserveAspectRatio} = "xMidYMid";
$svg->{-docref}{-document}{id}='grid-root';
$svg->{-docref}{-document}{version}='1.2';
# $svg->tag('textArea', width => "300", height => "300")->cdata("this is sum text like a bunch.");
# $svg->foreignObject(width => "300", height => "300")->tag('div', xmlns => "http://www.w3.org/1999/xhtml")->cdata('foo');
return $svg->xmlify;
}
sub translate_coords($this, $nw, $sw)
{
return (-3/4 * $this->tile_width * ($nw + $sw),
1/2 * $this->tile_height * ($sw - $nw));
}
sub to_id($string) { $string =~ s/\W/-/g && return $string; }
1;

@ -0,0 +1,50 @@
package HexGrid::Pin;
use Moo;
use MooX::Aliases;
use feature "signatures";
no warnings "experimental::signatures";
has name => (is => 'rw');
has id => (is => 'ro', required => 1);
has icon => (is => 'rw', alias => [qw(img source src)], required => 1);
has link => (is => 'rw', alias => 'href');
has description => (is => 'rw', alias => 'desc');
has popup_class => (is => 'rw', default => 'pin-popup');
sub render($this, $pin_container, $x, $y, $w, $h, $laters = undef)
{
my $element = $pin_container->image(href => $this->{icon},
x => $x, y => $y, width => $w, height => $h);
$element->{id} = "$this->{id}-img";
$element->{onclick} = "clickPin('$this->{id}', '$pin_container->{id}');";
my $center_x = $x + $w/2;
my $center_y = $y + $h/2;
if(defined $laters)
{
push @$laters, sub ($popup_container) { $this->render_popup($popup_container,
$pin_container->{transform}, $center_x, $center_y); };
}
return $element;
}
sub render_popup($this, $popup_container, $transform, $x_shift, $y_shift)
{
my $popup_scaler = $popup_container->
g(id => "$this->{id}-popup",transform => $transform, class => $this->{popup_class})->
svg(id => "$this->{id}-scaler");
my $popup = new HexGrid::PopUp
(
name => $this->{name},
description => $this->{description},
link => $this->{link}
);
my $popup_element = $popup->render($popup_scaler);
$popup_element->{x} = $x_shift - $popup->{origin_x};
$popup_element->{y} = $y_shift - $popup_element->{height};
}
1;

@ -0,0 +1,74 @@
package HexGrid::PopUp;
use v5.30;
use Moo;
use Text::Wrap::OO;
use Data::Dumper;
use feature "signatures";
no warnings "experimental::signatures";
has width => (is => 'rw', default => 600);
has height => (is => 'rw', default => 750);
has origin_x => (is => 'rw', default => 0);
has origin_y => (is => 'rw', default => 100);
has corner_radius => (is => 'rw', default => 20);
has gap_start => (is => 'rw', default => 40);
has gap_width => (is => 'rw', default => 40);
has name => (is => 'rw');
has link => (is => 'rw', alias => 'href');
has description => (is => 'rw', alias => 'desc');
has margin => (is => 'rw', default => 20);
has 'stroke-width' => (is => 'rw', default => 2);
sub render($this, $scaler)
{
my %t = %$this;
my $gap_end = $t{gap_start} + $t{gap_width};
$scaler->{width} = $t{width};
$scaler->{height} = $t{height};
$scaler->{viewBox} = "0 0 $t{width} $t{height}";
my $box_height = $t{height} - $t{origin_y};
my $path_string = _M($t{gap_start},$box_height);
$path_string .= _L($t{origin_x},$t{height});
$path_string .= _L($gap_end,$box_height);
$path_string .= _l($t{width} - $gap_end - $t{corner_radius}, 0);
$path_string .= _a($t{corner_radius}, $t{corner_radius}, 0, 0, 0, $t{corner_radius}, -$t{corner_radius});
$path_string .= _l(0, -($box_height - 2*$t{corner_radius}));
$path_string .= _a($t{corner_radius}, $t{corner_radius}, 0, 0, 0, -$t{corner_radius}, -$t{corner_radius});
$path_string .= _l(-($t{width} - 2*$t{corner_radius}),0);
$path_string .= _a($t{corner_radius}, $t{corner_radius}, 0, 0, 0, -$t{corner_radius}, $t{corner_radius});
$path_string .= _l(0, ($box_height - 2*$t{corner_radius}));
$path_string .= _a($t{corner_radius}, $t{corner_radius}, 0, 0, 0, $t{corner_radius}, $t{corner_radius});
$path_string .= " Z";
my $path = $scaler->path(d => $path_string, stroke => 'black', fill => 'white', 'stroke-width' => $t{'stroke-width'});
my $content_container = $scaler->g(transform => "translate($t{margin},$t{margin})");
my $obj = $content_container->foreignObject(width => ($t{width} - 2*$t{margin}), height => $box_height - 2*$t{margin});
basic_popup($obj, $t{name}, $t{description}, $t{link});
return $scaler;
}
sub _M { " M " . join(",", @_); }
sub _L { " L " . join(",", @_); }
sub _l { " l " . join(",", @_); }
sub _a { " a $_[0],$_[1] $_[2] $_[3] $_[4] $_[5],$_[6]"; }
# Split this out
sub basic_popup($obj, $name, $description, $link)
{
my $div = $obj->tag('div', xmlns => "http://www.w3.org/1999/xhtml");
$div->tag('h3', style => 'margin-top: 0; margin-left: 0;')->tag('a', href => $link)->cdata($name);
foreach (split /\n/, $description)
{
/^\s*$/
? $div->tag('br')->cdata
: $div->tag('span')->cdata($_);
}
}
1;

@ -0,0 +1,66 @@
package HexGrid::Region;
use v5.30;
use Moo;
use MooX::Aliases;
use Hash::Merge qw(merge);
use HexGrid::Tile;
use Data::Dumper;
use feature "signatures";
no warnings "experimental::signatures";
has tiles => (is => 'rw', default => sub { {} });
has subregions => (is => 'rw', default => sub { {} });
has name => (is => 'rw', required => 1);
has defaults => (is => 'rw', default => sub { {} });
has id_suffix => (is => 'rw', default => '_region');
sub add_tile($this, $tile) { $this->{tiles}{$tile->{nw}}{$tile->{sw}} = $tile; }
sub make_tile_at($this, $nw, $sw, %tile_settings)
{
my %settings = %{merge(\%tile_settings, $this->{defaults})};
# say STDERR Dumper(\%settings);
$settings{css_class} = HexGrid::to_id($this->{name});
$this->add_tile(HexGrid::Tile::at($nw, $sw, %settings));
}
sub add_subregion($this, $region) { $this->{subregions}{$region->{name}} = $region; }
sub make_subregion($this, $name, %defaults)
{
my $tile_defaults = merge(\%defaults, $this->{defaults});
$this->add_subregion(HexGrid::Region->new(name => $name, defaults => $tile_defaults));
}
sub render($this, $svg, $laters, $grid)
{
my ($min_x,$min_y,$max_x,$max_y) = qw(Inf Inf -Inf -Inf);
my $g = $svg->g(id => HexGrid::to_id("$this->{name}$this->{id_suffix}"));
foreach my $nw (keys %{$this->{tiles}})
{
foreach my $sw (keys %{$this->{tiles}{$nw}})
{
my ($x_translate, $y_translate) = $grid->translate_coords($nw, $sw);
my $tile_group = $this->{tiles}{$nw}{$sw}->render($g, $grid->tile_width, $grid->tile_height, $laters);
$tile_group->{transform} = "translate($x_translate, $y_translate)";
$min_x = $x_translate if $x_translate < $min_x;
$min_y = $y_translate if $y_translate < $min_y;
$max_x = $x_translate if $x_translate > $max_x;
$max_y = $y_translate if $y_translate > $max_y;
}
}
foreach my $region (keys %{$this->{subregions}})
{
my $m = $this->{subregions}{$region}->render($svg, $laters, $grid);
$min_x = $m->{min_x} if $m->{min_x} < $min_x;
$min_y = $m->{min_y} if $m->{min_y} < $min_y;
$max_x = $m->{max_x} if $m->{max_x} > $max_x;
$max_y = $m->{max_y} if $m->{max_y} > $max_y;
}
return { min_x => $min_x,min_y => $min_y,max_x => $max_x,max_y => $max_y, group => $g };
}
1;

@ -0,0 +1,150 @@
package HexGrid::Tile;
use v5.30;
use Moo;
use MooX::Aliases;
use Carp;
use HexGrid::Pin;
use Data::Dumper;
use feature "signatures";
no warnings "experimental::signatures";
has nw => (is => 'ro', required => 1);
has sw => (is => 'ro', required => 1);
has style => (is => 'rw');
has colour => (is => 'rw', alias => 'color');
has opacity => (is => 'rw');
has images => (is => 'rw', alias => [qw(image background backgrounds)]);
has show_coords => (is => 'rw');
has css_class => (is => 'rw');
has id_suffix => (is => 'rw', default => '_tile');
my %docks = ( #values are fractions of the height/width
sw => { order => 1, x => 0, y => 0.5, w => 0.5, h => 0.5 },
se => { order => 2, x => 0.5, y => 0.5, w => 0.5, h => 0.5 },
n => { order => 3, x => 0.25, y => 0, w => 0.5, h => 0.5 },
);
has pins => (is => 'rw', default => sub { +{ map {$_ => undef} keys %docks } });
# Class
sub at($nw,$sw,@rest)
{
return HexGrid::Tile->new(nw => $nw, sw => $sw, @rest);
}
sub hex_path($w, $h)
{
# More readable for me at least as a sequence of points than as two sequences
my @points = ([0, $h/2], [$w*1/4, 0], [$w*3/4, 0], [$w, $h/2], [$w*3/4, $h], [$w*1/4, $h]);
return join " ", (map {join ",", @$_} @points);
}
# Instance
sub pin($this, $pin, $dock = undef)
{
if(ref $pin eq 'HexGrid::Pin')
{
$this->_do_pin($pin, $dock);
}
elsif(ref $pin eq 'ARRAY')
{
map $this->_do_pin($_), @$pin;
}
elsif(ref $pin eq 'HASH')
{
foreach my $dock (keys %$pin)
{
croak "dock $dock doesn't exist" unless grep { $_ eq $dock } keys %docks;
$this->{pins}{$dock} = $pin->{$dock};
}
return;
}
else
{
croak "bad pin type";
}
}
sub _do_pin($this, $pin, $dock = undef)
{
if($dock)
{
croak "bad dock" unless $dock =~ /^sw|se|n$/i;
$this->{pins}{$dock} = $pin;
}
else
{
my $success = 0;
DOCK: foreach my $dock (sort { $docks{$a}->{order} <=> $docks{$b}->{order} } (keys %docks))
{
# say STDERR Dumper($dock);
unless(defined $this->{pins}{$dock})
{
$this->{pins}{$dock} = $pin;
$success = 1;
last DOCK;
}
}
croak "no free dock" unless $success;
}
}
sub render($this, $container, $width, $height, $laters = undef)
{
my $g = $container->g(
id => HexGrid::to_id("$this->{nw}_$this->{sw}$this->{id_suffix}"),
);
# $g->{onclick} = "say('($this->{nw},$this->{sw})')";
my $hex = $g->polygon(points => hex_path($width, $height),
id => "$this->{nw}_$this->{sw}_inner_hex", style => $this->{style} || {});
my $clipPath = $g->clipPath(id => "$this->{nw}_$this->{sw}_clip");
$clipPath->use(href => "#$hex->{id}");
if($this->{colour})
{
$hex->{fill} = $this->{colour};
}
if(defined($this->{images}))
{
$this->{images} = [$this->{images}] if ref($this->{images}) ne 'ARRAY';
foreach my $image ($this->{images}->@*)
{
my $image_element = $g->image(id => "$this->{nw}_$this->{sw}_${image}_img",
href => $image, width => $width, height => $height,
"clip-path" => "url(#$clipPath->{id})");
}
}
if(defined($this->{opacity})) { $hex->{'fill-opacity'} = $this->{opacity}; }
if($this->{show_coords})
{
my $text = $g->text(x => "@{[0.1 * $width]}", y => "@{[0.6 * $height]}", class => 'coords');
$text->{'font-size'} = '3em';
$text->cdata("$this->{nw},$this->{sw}");
}
if(defined $this->{css_class}) { $g->{class} = $this->{css_class} }
if(defined $this->{pins})
{
foreach my $key (keys $this->{pins}->%*)
{
next unless defined $this->{pins}{$key};
my $x = $width * $docks{$key}->{x};
my $w = $width * $docks{$key}->{w};
my $y = $height * $docks{$key}->{y};
my $h = $height * $docks{$key}->{h};
my $image_element = $this->{pins}{$key}->render($g, $x, $y, $w, $h, $laters);
$image_element->{"clip-path"} = "url(#$clipPath->{id})";
}
}
return $g;
}
1;

@ -0,0 +1,22 @@
package MWTemplate;
use v5.30;
use feature "signatures";
no warnings "experimental::signatures";
sub Parse($input, $template_name)
{
my ($contents) = $input =~ /\{\{ \s* $template_name \s* \| (.*) \}\}/sx;
my @params = split /\|/, $contents;
my @positional_params;
my %named_params;
foreach (@params)
{
if(/(.*?)=(.*)/) { $named_params{$1} = $2 }
else { push @positional_params, $_ }
}
return {positional_params => \@positional_params, named_params => \%named_params};
}
1;

@ -0,0 +1,82 @@
use v5.30;
use HexGrid;
use Carp;
use Data::Dumper;
my $grid = HexGrid->new(defaults => { style => { 'stroke-width' => 1, 'stroke' => 'black' }, show_coords => 1 });
$grid->make_region("Viper's Gate", colour => 'red');
my $adderwall = $grid->make_region('Adderwall', colour => 'orange');
# $adderwall->make_subregion('Adderwall foothills', colour => 'moccasin');
$grid->make_region('Adderwall foothills', colour => 'moccasin');
$grid->make_region('Midhills', colour => 'moccasin');
# $grid->make_region('Minev', colour => 'green', images => ["forest-tile.png"]);
$grid->make_region('Minev', colour => 'green');
$grid->make_region('Barrowcross', colour => 'darkgreen');
$grid->make_region('Shinei Mavet', colour => 'orange');
$grid->make_region('Jagged Shinei Mavet', colour => 'orange');
$grid->make_region('Shinei Mavet waters', colour => 'lightcyan');
# $grid->make_region('Shinei Mavet', colour => 'orange', image => 'mountain.svg');
# $grid->make_region('Jagged Shinei Mavet', colour => 'orange', images => ['mountain.svg', 'jagged.svg']);
# $grid->make_region('Shinei Mavet waters', colour => 'lightcyan', image => 'foo.png');
$grid->make_region('Southern Ocean', colour => 'skyblue');
$grid->make_region('A volcano', colour => 'orange');
$grid->make_region('Buslish Scrubs', colour => 'beige');
$grid->make_region('The Blasted Metropolis', colour => 'gray');
$grid->make_region('The sprawling grasses', colour => 'beige');
$grid->make_region('Something sandy?', colour => 'navajowhite');
$grid->make_region('Necromancer', colour => 'orange');
$grid->make_region('The Narrow Sea', colour => 'skyblue');
$grid->make_region('Naurardhon', colour => 'palegoldenrod');
$grid->make_region('The Glades of Leamingbury', colour => 'lightgreen');
$grid->make_region('Cape of Lost Causes', colour => 'gray');
$grid->make_region('Deadlands', colour => 'black');
$grid->make_region('Deadlands', colour => 'black');
$grid->make_region('Glade borders', colour => 'mediumaquamarine');
$grid->make_region('Thicket', colour => 'darkgreen');
$grid->make_region('Random house', colour => 'darkgreen');
$grid->make_region('Abbey of Plendor', colour => 'beige');
$grid->make_region('Khulanu', opacity => '0', style => { 'stroke-width' => 5, 'stroke' => 'black' });
my $tiles;
open $tiles, "$ENV{HOME}/RPG-stuff/WestOfVipers/coordinate-mapping";
my $region;
my $region_file;
start_region();
while(my $coords = <$tiles>)
{
if ($coords =~ /^\s*$/)
{
start_region();
next;
}
print $region_file $coords if $coords;
}
close $region_file if $region_file;
sub start_region
{
close $region_file if $region_file;
$region = <$tiles>;
chomp($region);
my $encoded_region = $region;
$encoded_region =~ s/\W/_/g;
open $region_file, ">region_files/$encoded_region" || croak "Can't open file for $region: $!";
say $region_file "== Style ==";
foreach my $key (keys %{$grid->{regions}{$region}{defaults}})
{
next if $key eq 'style';
say $region_file "$key: $grid->{regions}{$region}{defaults}{$key}";
}
if(my $css = $grid->{regions}{$region}{defaults}{style})
{
say $region_file "\n=== CSS ===";
foreach my $key (keys %{$grid->{regions}{$region}{defaults}{style}})
{
say $region_file "$key: $grid->{regions}{$region}{defaults}{style}{$key}";
}
}
say $region_file "\n== Tiles ==";
}

@ -0,0 +1,37 @@
use v5.30;
# use Getopt::Long;
use SVG;
my $width = 400;
my $height = 300;
my $origin_x = 0;
my $origin_height = 100;
my $corner_radius = 25;
my $gap_start = 50;
my $gap_width = 50;
my $full_height = $height + $origin_height;
my $svg = SVG->new(width => $width, height => $full_height, viewBox => "0 0 $width $full_height");
my $path_string = "M $gap_start,$height";
$path_string .= L($origin_x,$full_height);
$path_string .= L($gap_start + $gap_width,$height);
$path_string .= L($width - $corner_radius, $height);
$path_string .= a($corner_radius, $corner_radius, 0, 0, 0, $corner_radius, -$corner_radius);
$path_string .= l(0, -($height - 2*$corner_radius));
$path_string .= a($corner_radius, $corner_radius, 0, 0, 0, -$corner_radius, -$corner_radius);
$path_string .= l(-($width - 2*$corner_radius),0);
$path_string .= a($corner_radius, $corner_radius, 0, 0, 0, -$corner_radius, $corner_radius);
$path_string .= l(0, ($height - 2*$corner_radius));
$path_string .= a($corner_radius, $corner_radius, 0, 0, 0, $corner_radius, $corner_radius);
$path_string .= " Z";
my $path = $svg->path(d => $path_string, stroke => 'black', fill => 'white');
say $svg->render();
sub L { " L " . join(",", @_); }
sub l { " l " . join(",", @_); }
sub a { " a $_[0],$_[1] $_[2] $_[3] $_[4] $_[5],$_[6]"; }

@ -0,0 +1,20 @@
<svg version="1.1" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<style>
.link { stroke-width: 2px; fill: transparent; }
</style>
<polygon points="0,50 25,0 75,0 100,50, 75,100 25,100" />
<circle cx="50" cy="50" r="2" fill="white" stroke="white" />
<circle cx="12.5" cy="25" r="2" fill="white " />
<circle cx="50" cy="0" r="2" fill="lime " />
<circle cx="87.5" cy="25" r="2" fill="yellow " />
<circle cx="87.5" cy="75" r="2" fill="red " />
<circle cx="50" cy="100" r="2" fill="magenta" />
<circle cx="12.5" cy="75" r="2" fill="cyan " />
<path d="M 12.5,25 Q 50,50 50 , 0" stroke="lime " class="link" />
<path d="M 12.5,25 Q 50,50 87.5, 25" stroke="yellow " class="link" />
<path d="M 12.5,25 Q 50,50 87.5, 75" stroke="red " class="link" />
<path d="M 12.5,25 Q 50,50 50 ,100" stroke="magenta" class="link" />
<path d="M 12.5,25 Q 50,50 12.5, 75" stroke="cyan " class="link" />
</svg>

After

Width:  |  Height:  |  Size: 948 B

@ -0,0 +1,17 @@
<svg version="1.1" viewBox="-400 -250 500 500" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="myhex" width="100" height="100" viewbox="0 0 100 100">
<polygon points="0,50 25,0 75,0 100,50, 75,100 25,100" />
</symbol>
</defs>
<!-- <path d="M 0,0 H 500" stroke="black" stroke-width="1" /> -->
<!-- <path d="M 0,0 H -500" stroke="black" stroke-width="1" /> -->
<!-- <path d="M 0,0 V 500" stroke="black" stroke-width="1" /> -->
<!-- <path d="M 0,0 V -500" stroke="black" stroke-width="1" /> -->
<use href="#myhex" fill="green" x=" -50" y=" -50" />
<use href="#myhex" fill="red" x="-125" y="-100" />
<use href="#myhex" fill="blue" x="-125" y=" 0" />
<use href="#myhex" fill="purple" x="-200" y="-150" />
<use href="#myhex" fill="pink" x="-200" y=" -50" />
<use href="#myhex" fill="magenta" x="-200" y=" 50" />
</svg>

After

Width:  |  Height:  |  Size: 861 B

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewbox="0 0 50 50">
<rect x="10" y="20" width="30" height="30" fill="orange" stroke="black" />
<rect x="30" y="0" width="8" height="20" fill="red" stroke="black" />
<polygon points="0,20 50,20 25,0" fill="red" stroke="black" />
<rect x="25" y="30" width="8" height="20" fill="navajowhite" stroke="black" />
<circle cx="30.5" cy="40" r="1" fill="black" />
</svg>

After

Width:  |  Height:  |  Size: 431 B

@ -0,0 +1,3 @@
<svg version="1.1" width="200" height="175" xmlns="http://www.w3.org/2000/svg">
<polyline points="65,130 77,100 90,130 102,100 115,130" fill-opacity="0" stroke-width="5" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 195 B

@ -0,0 +1,3 @@
<svg version="1.1" width="200" height="175" xmlns="http://www.w3.org/2000/svg">
<polyline points="50,120 100,55, 150,120" fill-opacity="0" stroke-width="10" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 182 B

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="500" width="500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" >
<path fill="white" stroke="black" d="
M 0,500
l 50,-100
l -25,0
a 25,25 0 0 1 -25,-25
l 0,-350
a 25,25 0 0 1 25,-25
l 450,0
a 25,25 0 0 1 25,25
l 0,350
a 25,25 0 0 1 -25,25
l -375,0
Z" />
<text x="50" y="50">
Doloribus neque corrupti repudiandae harum voluptatem fugit rerum aut.
</text>
<text x="50" y="74">
Omnis ipsa quo eius expedita voluptas itaque numquam.
Reiciendis magni nostrum odio maxime eos recusandae in.
</text>
<text x="50" y="100">
Ut rerum illum qui dolore.
Iste in qui fugit.
Rem quas explicabo quidem delectus est.
Nesciunt et exercitationem ullam quia quaerat consectetur blanditiis.
Voluptates omnis fugit illum nostrum sit.Dolores quia molestiae laborum nulla doloribus voluptatum ducimus. Architecto ipsa voluptates fugiat est rerum nesciunt. Ipsum occaecati esse quia aut accusamus reprehenderit eos.Non voluptate dolor ut quia quidem. Distinctio non tempore rerum saepe. Eius minus molestias nemo ratione illo dignissimos in quis. Veritatis officia excepturi atque. Est aperiam sit nobis velit debitis distinctio deserunt hic.
</text>
<rect width="5em" height="3em" x="10%" y="50%" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="500" width="500" viewBox="0 0 50em 50em" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" >
<svg height="50em" width="50em" viewBox="0 0 50 50">
<path fill="white" stroke-width="0.1" stroke="black" d="
M 0,20
l 2,-4
l -1,0
a 1,1 0 0 1 -1,-1
l 0,-14
a 1,1 0 0 1 1,-1
l 18,0
a 1,1 0 0 1 1,1
l 0,14
a 1,1 0 0 1 -1,1
l -15,0
Z" />
</svg>
</svg>

After

Width:  |  Height:  |  Size: 582 B

@ -0,0 +1,127 @@
use v5.30;
use HexGrid;
use HexGrid::Pin;
use Data::Dumper;
use Text::Lorem;
my $lorem = Text::Lorem->new;
# my $adder_pin = new HexGrid::Pin(id => 'mountain-pin', icon => 'mountain.svg', name => 'Adder', description => $lorem->sentences(1));
my $grid = HexGrid->new(defaults => { style => { 'stroke-width' => 1, 'stroke' => 'black' }, show_coords => 1 });
$grid->make_region("Viper's Gate", colour => 'red');
# $grid->make_region('Adderwall', colour => 'orange', pins => {n => $adder_pin});
my $adderwall = $grid->make_region('Adderwall', colour => 'orange');
# $adderwall->make_subregion('Adderwall foothills', colour => 'moccasin');
$grid->make_region('Adderwall foothills', colour => 'moccasin');
$grid->make_region('Midhills', colour => 'moccasin');
# $grid->make_region('Minev', colour => 'green', images => ["forest-tile.png"]);
$grid->make_region('Minev', colour => 'green');
$grid->make_region('Barrowcross', colour => 'darkgreen');
$grid->make_region('Shinei Mavet', colour => 'orange');
$grid->make_region('Jagged Shinei Mavet', colour => 'orange');
$grid->make_region('Shinei Mavet waters', colour => 'lightcyan');
# $grid->make_region('Shinei Mavet', colour => 'orange', image => 'mountain.svg');
# $grid->make_region('Jagged Shinei Mavet', colour => 'orange', images => ['mountain.svg', 'jagged.svg']);
# $grid->make_region('Shinei Mavet waters', colour => 'lightcyan', image => 'foo.png');
$grid->make_region('Southern Ocean', colour => 'skyblue');
$grid->make_region('A volcano', colour => 'orange');
$grid->make_region('Buslish Scrubs', colour => 'beige');
$grid->make_region('The Blasted Metropolis', colour => 'gray');
$grid->make_region('The sprawling grasses', colour => 'beige');
$grid->make_region('Something sandy?', colour => 'navajowhite');
$grid->make_region('Necromancer', colour => 'orange');
$grid->make_region('The Narrow Sea', colour => 'skyblue');
$grid->make_region('Naurardhon', colour => 'palegoldenrod');
$grid->make_region('The Glades of Leamingbury', colour => 'lightgreen');
$grid->make_region('Cape of Lost Causes', colour => 'gray');
$grid->make_region('Deadlands', colour => 'black');
$grid->make_region('Deadlands', colour => 'black');
$grid->make_region('Glade borders', colour => 'mediumaquamarine');
$grid->make_region('Thicket', colour => 'darkgreen');
$grid->make_region('Random house', colour => 'darkgreen');
$grid->make_region('Abbey of Plendor', colour => 'beige');
$grid->make_region('Khulanu', opacity => '0', style => { 'stroke-width' => 5, 'stroke' => 'black' });
my $tiles;
open $tiles, "$ENV{HOME}/RPG-stuff/WestOfVipers/coordinate-mapping";
my $region = <$tiles>;
chomp($region);
while(my $coords = <$tiles>)
{
if ($coords =~ /^\s*$/)
{
$region = <$tiles>;
chomp($region);
next;
}
chomp $coords;
my %settings;
if($coords =~ /:$/)
{
for(my $line = <$tiles>; $line !~ /^;/; $line = <$tiles>)
{
my($key, $val) = split /:/, $line;
$settings{$key} = $val;
}
chop($coords);
}
my ($nw,$sw) = split /,/, $coords;
my $tile = $grid->{regions}{$region}->make_tile_at($nw, $sw, %settings);
if($region eq "Necromancer")
{
$tile->pin(new HexGrid::Pin(id => "circle-pin", icon => "foo2.svg", name => "zap", description => scalar $lorem->paragraphs(4), href => 'http://www.tensor.green'));
}
}
close $tiles;
# $grid->get_tile_at(0,0)->pin(
# $grid->{regions}{"Viper's Gate"}{tiles}{0}{0}->pin(
# {
# 'n' => new HexGrid::Pin(icon => "1"),
# 'se' => new HexGrid::Pin(icon => "2"),
# 'sw' => new HexGrid::Pin(icon => "3")
# }
# );
# $grid->get_tile_at(0,0)->pin(
# new HexGrid::Pin(icon => "foo2.svg"),
# 'se'
# );
# $grid->get_tile_at(0,0)->pin(
# new HexGrid::Pin(icon => "mountain.svg")
# );
# $grid->get_tile_at(0,0)->pin(
# new HexGrid::Pin(icon => "foo2.svg")
# );
# $grid->{regions}{"Viper's Gate"}{tiles}{0}{0}->pin(
# new HexGrid::Pin(icon => "2")
# );
# $grid->{regions}{"Viper's Gate"}{tiles}{0}{0}->pin(
# new HexGrid::Pin(icon => "3"),
# 'sw'
# );
say "<!DOCTYPE html>";
say "<html>\n<body>";
say <<EOS;
<script>
function clickPin(pinId, containerId) {
let popup = document.getElementById(pinId + '-popup');
popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible';
}
</script>
EOS
say $grid->render;
# $grid->render;
say "</body>\n</html>";
sub random_colour
{
my $str = '#';
$str .= sprintf('%02X', int(rand(256))) for 1..3;
return $str;
}

@ -0,0 +1,109 @@
use v5.30;
use HexGrid;
use HexGrid::Pin;
use MWTemplate;
use Getopt::Long;
use File::Find;
use File::Basename;
use Carp;
use Data::Dumper;
$Data::Dumper::Indent = 1;
use feature "signatures";
no warnings "experimental::signatures";
my $template_name = "MapRegion";
my $border_width = 1;
my $border_colour = 'black';
my $show_coords = 0;
my @region_files;
my @pin_files;
my @include_dirs;
GetOptions(
'border-width=f' => \$border_width,
'border-colour|border-color=s' => \$border_colour,
'show-coords|coords!' => \$show_coords,
'region-file=s' => \@region_files,
'pin-file=s' => \@pin_files,
'directory|include-directory=s' => \@include_dirs
);
my $grid = HexGrid->new(defaults => {
style => { 'stroke-width' => $border_width, stroke => $border_colour },
show_coords => $show_coords });
@region_files = split(/,/, join(',', @region_files));
@pin_files = split(/,/, join(',', @pin_files));
@include_dirs = split(/,/, join(',', @include_dirs));
find(sub
{
push @region_files, $File::Find::name if /\.region$/;
push @pin_files, $File::Find::name if /\.pin$/;
}, @include_dirs);
foreach my $file (@region_files)
{
my $name = fileparse($file, qr/\.region/);
open my $fh, $file || croak "Couldn't open region file $file: $!";
my @region_lines = <$fh>;
close $fh;
my $tiles_file = $file;
$tiles_file =~ s/region$/tiles/;
open my $tfh, $tiles_file || croak "Couldn't open tiles file $tiles_file: $!";
my @tiles = <$tfh>;
close $tfh;
process_region($grid, $name, (join "", @region_lines), (join "", @tiles));
}
foreach my $file (@pin_files)
{
my $name = fileparse($file, qr/\.pin/);
open my $fh, $file || croak "Couldn't open pin file $file: $!";
my @lines = <$fh>;
close $fh;
process_pin($grid, $name, @lines);
}
say wrap_in_html($grid);
sub process_region($grid, $name, $region_spec, $tiles)
{
my $region = $grid->make_region($name);
my $parsed_template = MWTemplate::Parse($region_spec, $template_name);
$region->{defaults}{colour} = $parsed_template->{named_params}{colour};
$region->{defaults}{image} = $parsed_template->{named_params}{background};
say STDERR Dumper($tiles);
foreach my $coords (split /;/, $tiles)
{
# The below regex is a whitespace forgiving version of /^(-?\d+),(-?\d+)/, an int pair
do { carp "Skipping bad spec: $coords"; next; }
unless $coords =~ /^\s*(-?\s*\d+)\s*,\s*(-?\s*\d+)\s*$/;
$region->make_tile_at($1,$2);
}
}
sub process_pin($file)
{
}
sub wrap_in_html($grid)
{
my $html_builder = "<!DOCTYPE html>";
$html_builder .= "\n<html>\n<body>";
$html_builder .= "\n" . <<EOS;
<script>
function clickPin(pinId, containerId) {
let popup = document.getElementById(pinId + '-popup');
popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible';
}
</script>
EOS
$html_builder .= "\n" . $grid->render;
$html_builder .= "\n</body>\n</html>";
return $html_builder;
}

@ -0,0 +1,132 @@
use v5.30;
use MediaWiki::API;
use HexGrid;
use HexGrid::Pin;
use MWTemplate;
use Carp;
use Data::Dumper;
$Data::Dumper::Indent = 1;
use feature "signatures";
no warnings "experimental::signatures";
my $api_url = "https://wiki.tensor.green/w/api.php";
my $region_template_name = "MapRegion";
my $site_template_name = "MapSite";
my $border_width = 1;
my $border_colour = 'black';
my $show_coords = 1;
my $grid = HexGrid->new(defaults => {
style => { 'stroke-width' => $border_width, stroke => $border_colour },
show_coords => $show_coords });
my $mw = MediaWiki::API->new();
$mw->{config}->{api_url} = $api_url;
my $region_pages = $mw->list ( {
action => 'query',
list => 'categorymembers',
cmtitle => 'Category:Regions',
cmlimit => 'max' } )
|| croak $mw->{error}->{code} . ': ' . $mw->{error}->{details};
say STDERR "Regions found: " . join(", ", map { $_->{title} } @$region_pages);
foreach my $region_page_ref (@$region_pages)
{
my $region_name = $region_page_ref->{title};
say STDERR "Processing Region: $region_name";
my $region_page = $mw->get_page({ title => $region_name });
my $tile_page = $mw->get_page({ title => "$region_name/Tiles" });
process_region($grid, $region_name, $region_page->{'*'}, $tile_page->{'*'});
}
my $site_query_results = $mw->api
( {
action => 'query',
generator => 'categorymembers',
prop => 'info|revisions',
gcmtitle => 'Category:Sites',
gcmlimit => 'max',
rvprop => 'content',
inprop => 'url',
} ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details};
# say STDERR "Sites found: " . join(" ,", map { $_->{title} } @$site_pages);
# say STDERR Dumper($site_query_results);
foreach my $site_page_ref (values %{$site_query_results->{query}{pages}})
{
say STDERR Dumper($site_page_ref);
my $site_name = $site_page_ref->{title};
say STDERR "Processing Site $site_name";
my $site_url = $site_page_ref->{canonicalurl};
my $site_content = $site_page_ref->{revisions}[0]{'*'};
my $parsed_template = MWTemplate::Parse($site_content, $site_template_name);
my ($nw,$sw) = split /,/, $parsed_template->{named_params}{coords};
my $imageinfo_query_results = $mw->api({ action => 'query',
prop => 'imageinfo',
titles => "File:$parsed_template->{named_params}{icon}",
iiprop => 'url'
}) || carp $mw->{error}->{code} . ': ' . $mw->{error}->{details};
my %image_pages = %{$imageinfo_query_results->{query}{pages}};
my $image_url = (values %image_pages)[0]{imageinfo}[0]{url};
my $pin = HexGrid::Pin->new
(
name => $site_name,
id => "${site_name}_pin",
icon => $image_url,
link => $site_url,
description => $parsed_template->{named_params}{abstract}
);
# say STDERR Dumper($pin);
$grid->get_tile_at($nw, $sw)->pin($pin);
}
say wrap_in_html($grid);
sub process_region($grid, $region_name, $region_spec, $tiles)
{
my $region = $grid->make_region($region_name);
my $parsed_template = MWTemplate::Parse($region_spec, $region_template_name);
$region->{defaults}{colour} = $parsed_template->{named_params}{colour};
my $image_info = $mw->api({ action => 'query',
prop => 'imageinfo',
titles => "File:$parsed_template->{named_params}{background}",
iiprop => 'url'
}) || carp $mw->{error}->{code} . ': ' . $mw->{error}->{details};
$region->{defaults}{image} = [];
foreach my $page (values %{$image_info->{query}{pages}})
{
push @{$region->{defaults}{image}}, $page->{imageinfo}[0]{url};
}
foreach my $coords (split /;/, $tiles)
{
# The below regex is a whitespace forgiving version of /^(-?\d+),(-?\d+)/, an int pair
do { carp "Skipping bad spec: $coords"; next; }
unless $coords =~ /^\s*(-?\s*\d+)\s*,\s*(-?\s*\d+)\s*$/;
$region->make_tile_at($1,$2);
}
}
sub wrap_in_html($grid)
{
my $html_builder = "<!DOCTYPE html>";
$html_builder .= "\n<html>\n<body>";
$html_builder .= "\n" . <<EOS;
<script>
function clickPin(pinId, containerId) {
let popup = document.getElementById(pinId + '-popup');
popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible';
}
</script>
EOS
$html_builder .= "\n" . $grid->render;
$html_builder .= "\n</body>\n</html>";
return $html_builder;
}
Loading…
Cancel
Save