commit de918c767acee66585a58614ca3e89bbb4ea8d1f Author: Daniel Asher Resnick Date: Tue Jun 27 11:40:00 2023 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53969fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.png +*.jpg +*.jpeg +*.gif +output/* diff --git a/HexGrid.pm b/HexGrid.pm new file mode 100644 index 0000000..efd347c --- /dev/null +++ b/HexGrid.pm @@ -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; diff --git a/HexGrid/Pin.pm b/HexGrid/Pin.pm new file mode 100644 index 0000000..bdd3a9a --- /dev/null +++ b/HexGrid/Pin.pm @@ -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; diff --git a/HexGrid/PopUp.pm b/HexGrid/PopUp.pm new file mode 100644 index 0000000..df00e77 --- /dev/null +++ b/HexGrid/PopUp.pm @@ -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; diff --git a/HexGrid/Region.pm b/HexGrid/Region.pm new file mode 100644 index 0000000..48f321a --- /dev/null +++ b/HexGrid/Region.pm @@ -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; diff --git a/HexGrid/Tile.pm b/HexGrid/Tile.pm new file mode 100644 index 0000000..11563b3 --- /dev/null +++ b/HexGrid/Tile.pm @@ -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; diff --git a/MWTemplate.pm b/MWTemplate.pm new file mode 100644 index 0000000..ef325e1 --- /dev/null +++ b/MWTemplate.pm @@ -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; diff --git a/parse-regions.pl b/parse-regions.pl new file mode 100644 index 0000000..6c894cd --- /dev/null +++ b/parse-regions.pl @@ -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 =="; +} diff --git a/popup.pl b/popup.pl new file mode 100644 index 0000000..561c42c --- /dev/null +++ b/popup.pl @@ -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]"; } diff --git a/svg/hex-lines.svg b/svg/hex-lines.svg new file mode 100644 index 0000000..cfaa27c --- /dev/null +++ b/svg/hex-lines.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/svg/hexes.svg b/svg/hexes.svg new file mode 100644 index 0000000..7b913f7 --- /dev/null +++ b/svg/hexes.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/svg/house.svg b/svg/house.svg new file mode 100644 index 0000000..741a892 --- /dev/null +++ b/svg/house.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/svg/jagged.svg b/svg/jagged.svg new file mode 100644 index 0000000..a1b8527 --- /dev/null +++ b/svg/jagged.svg @@ -0,0 +1,3 @@ + + + diff --git a/svg/mountain.svg b/svg/mountain.svg new file mode 100644 index 0000000..f359f64 --- /dev/null +++ b/svg/mountain.svg @@ -0,0 +1,3 @@ + + + diff --git a/svg/popup.svg b/svg/popup.svg new file mode 100644 index 0000000..76babf3 --- /dev/null +++ b/svg/popup.svg @@ -0,0 +1,32 @@ + + + + + + Doloribus neque corrupti repudiandae harum voluptatem fugit rerum aut. + + + Omnis ipsa quo eius expedita voluptas itaque numquam. + Reiciendis magni nostrum odio maxime eos recusandae in. + + + 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. + + + diff --git a/svg/popup2.svg b/svg/popup2.svg new file mode 100644 index 0000000..fcdc01f --- /dev/null +++ b/svg/popup2.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/test.pl b/test.pl new file mode 100644 index 0000000..c410137 --- /dev/null +++ b/test.pl @@ -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 ""; +say "\n"; +say < + function clickPin(pinId, containerId) { + let popup = document.getElementById(pinId + '-popup'); + popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible'; + } + +EOS +say $grid->render; +# $grid->render; +say "\n"; + +sub random_colour +{ + my $str = '#'; + $str .= sprintf('%02X', int(rand(256))) for 1..3; + return $str; +} diff --git a/wiki-tile.pl b/wiki-tile.pl new file mode 100644 index 0000000..7cc3450 --- /dev/null +++ b/wiki-tile.pl @@ -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 = ""; + $html_builder .= "\n\n"; + $html_builder .= "\n" . < + function clickPin(pinId, containerId) { + let popup = document.getElementById(pinId + '-popup'); + popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible'; + } + +EOS + $html_builder .= "\n" . $grid->render; + $html_builder .= "\n\n"; + return $html_builder; +} diff --git a/wiki-tile2.pl b/wiki-tile2.pl new file mode 100644 index 0000000..2151447 --- /dev/null +++ b/wiki-tile2.pl @@ -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 = ""; + $html_builder .= "\n\n"; + $html_builder .= "\n" . < + function clickPin(pinId, containerId) { + let popup = document.getElementById(pinId + '-popup'); + popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible'; + } + +EOS + $html_builder .= "\n" . $grid->render; + $html_builder .= "\n\n"; + return $html_builder; +}