package Blacknote::Mapgen; use strict; use warnings FATAL => qw(all); use Blacknote::Logging; use Blacknote::System::State qw( bnsm_get_all_item_pools bnsm_get_tile_at bnsm_set_tile_at ); use Blacknote::Interface qw(defclass defclass_strict); use Blacknote::Renderer::Curses; use Carp qw(croak confess cluck); use Data::Dumper; defclass_strict Room => attributes => { x => '$', y => '$', w => '$', h => '$', name => '$', item_pool => '\%', }, methods => { area => sub { my $self = shift; return ($self->x, $self->y, $self->x + $self->w, $self->y + $self->h); }, center => sub { my $self = shift; my $center_x = ($self->x + $self->w)/2; my $center_y = ($self->y + $self->h)/2; return ($center_x, $center_y); } }; defclass_strict Map => attributes => { name => '$', layout => '\@', # Layout is raw map data rooms => '\@', }, methods => { add_room => sub { my $self = shift; my $room = shift; ref($room) eq "Blacknote::Mapgen::Room" or croak "Map expects a room class"; $self->rooms( @{$self->rooms()}, $room ); DEBUG "Room ".$room->name." has been added to map: " . $self->name; }, get_tile_at => sub { my $self = shift; my ($x,$y) = @_; my $row = $self->layout->[$y]; confess "No row at line $y" unless defined $row; my $id = $row->[$x]; confess "No tile at $x, $y" unless defined $id; return $id; }, set_tile_at => sub { my $self = shift; my ($x, $y, $id) = @_; $self->get_tile_at($x,$y); # Dies if not defined or out of bounds $self->layout->[$y]->[$x] = $id; }, get_width => sub { my $self = shift; -1 + scalar @{$self->layout->[0]}}, get_height => sub { my $self = shift; -1 + scalar @{$self->layout}}, }; sub randroomrange() { return int (rand 30) + 1 } sub makeroom { my ($x,$y,$w,$h) = @_; my $room = Blacknote::Mapgen::Room->new(x=>$x, y=>$y, w=>$w, h=>$h, name => 'DefaultRoom'); return $room; } sub makemap { my ($name, @rooms) = @_; } sub new_empty_dungeon_map { my $mapname = shift; # We genearate a new empty map with full walls my $map = Blacknote::Mapgen::Map->new(name => $mapname, layout => [ map { [ map { 1 } 0 .. 90 ] } 0 .. 30 ], rooms => []); return $map; } sub hollow_room { my $map = shift; my $room = Blacknote::Mapgen::Room->new(x => randroomrange, y => randroomrange, w => 5, h => 5); my ($sx,$sy, $ex,$ey) = $room->area; for my $x($sx .. $ex){ for my $y($sy .. $ey){ TRACE "Setting tile $x,$y to 0"; $map->set_tile_at($x,$y,0); } } return $room; } sub hollow_path { # Makes a corridor connecting 2 rooms my ($map, $room1, $room2) = @_; my @room1_c = $room1->center; my @room2_c = $room2->center; my $start = $room1_c[0] < $room2_c[0] ? \@room1_c : \@room2_c; my $end = $room1_c[0] < $room2_c[0] ? \@room2_c : \@room1_c; for my $x ($start->[0] .. $end->[0]-1){ # Makes a path starting from the room on the left on the center of its Y axis # to the room on the right towards its center on X $map->set_tile_at($x, $start->[1]); } return 1; # Indicate success } sub gen_dungeon { my $dungeon_name = shift; my $newmap = new_empty_dungeon_map $dungeon_name; my ($r1,$r2) = (hollow_room($newmap), hollow_room($newmap)); #hollow_path($newmap,$r1,$r2); DEBUG "Generating map"; DEBUG $newmap->get_tile_at(0,0); my @map_data = (); for my $y( 0 .. $newmap->get_height()){ my @row = (); for my $x( 0 .. $newmap->get_width()){ my $tile = $Blacknote::System::State::TILE_MAPPING{$newmap->get_tile_at($x,$y)}->new(x => $x, y => $y); push @row, $tile; } push @map_data,\@row; } $newmap->layout(\@map_data); return $newmap; } sub store_map { my $map = shift; # Stores generated map use Data::Dumper; use File::Slurp qw(write_file); use Cwd; croak "Couldn't find storage directory. CWD is " . getcwd unless -d "storage/"; write_file("storage/current.map", Dumper \$map); } sub load_map { my $mapname = shift; use File::Spec qw(catfile); my $map = do(catfile("storage",$mapname)); DEBUG "Loaded map $mapname"; return $map; } sub gen_items { for my $pool(@{bnsm_get_all_item_pools()}){ for my $class(@$pool){ if(int(rand(2))){ my $x = int(rand(50)); my $y = int(rand(10)); #NOTE: Stack-like class to hold multiple items, or just a list? my $container = $Blacknote::System::State::MAPITEMS{"$x,$y"}; if(defined $container){ push @$container, $class->new(x => $x, y => $y); }else{ $Blacknote::System::State::MAPITEMS{"$x,$y"} = [ $class->new(x => $x, y => $y) ]; } } } } } sub mapiter (&$$) { my ($coderef, $max_w, $max_h) = @_; for my $y(0 ..$max_h){ for my $x(0 .. $max_w){ $coderef->($x, $y); } } } # Generates random walls, usefull for outdoor buildings sub make_rand_room_walls { my ($max_x, $max_y) = @_; my $room = Blacknote::Mapgen::Room->new(x => int(rand($max_x))+1, y => int(rand($max_y))+1, w => int(rand(20))+1, h => int(rand(10)+1)); for my $y($room->y .. $room->y + $room->h){ for my $x($room->x .. $room->x + $room->w){ #FIXME: this is a bit inefficient, but works bnsm_set_tile_at($room->x,$y,1); bnsm_set_tile_at($room->x + $room->w,$y,1); bnsm_set_tile_at($x,$room->y,1); bnsm_set_tile_at($x,$room->y + $room->h,1); } } # Make an entrance from the top bnsm_set_tile_at($room->x + ($room->w)/ 2, $room->y, 0); } sub gen_tiles { my ($max_w, $max_h) = @_; } 1; =pod =head1 Classes =head2 Room The room class holds information about a room =head3 x y w h The x y and width and height of the room =head3 name Name of the room for description and information purposes =head3 item_pool A string representing the type of items to go into this room =head3 area() Returns a list of the room boundaries =head2 gen_items() Generates items randomly from the pool defined in %Blacknote::System::State::ITEM_POOLS. It places the newly generated items into (%) $Blacknote::System::State::MAPITEMS{"$x,$y"} =head2 mapiter(&code, $max_w, $max_h) General purpose iteration function. Iterates from 0 to $max_w and $max_h calling &code at every coordinate change. Uses CODE BLOCK syntax. =head2 make_rand_room_walls($max_x, $max_y) Generates random walls, not entire rooms. Usefull for generating outdoor buildings and such. .