package Blacknote::System::State; use strict; use warnings FATAL => qw(all); use Blacknote::Logging; use List::Util qw(any); use Carp qw(croak confess); our %TILE_MAPPING = (); our %FUNCTION_POOL = ( update => [], turn => [], menu => [] ); our $PLAYER = undef; our $MAP_INDEX = -1; our $KEYMAP = undef; our %ITEM_POOLS = (); # NOTE: Item pools are used to generate items = its a database of all available item classes our $MAPDATA = undef; # A reference to an array of numbers # A hashmap with "x,y" keys for fast item lookup our %MAPITEMS = (); our %KEYMAP = (); our $KEYBINDING = undef; our %WINDOWS = (); our $MSGLOG = undef; our %SND_CHUNKS = (); our @OBJECTS = (); our $FOCUSED_WIN = undef; our @TURN_QUEUE = (); our $GAME_TURNS = 0; our %HOOKS = ( init => [], # on start up update => [], # during update turn => [], # on each turn end_turn => [], # at the end of the turn fini => [], # at the end of the program ); use constant { STATE_BOOT => 0, STATE_INTRO => 100, STATE_MAINMENU => 1, STATE_GAME => 2, STATE_PAUSE => 3, STATE_EXIT => 4, STATE_CHARACTER_CREATION => 5, STATE_NEW_GAME => 7, STATE_MENU => 6, STATE_CONTINUE => 200, STATE_TURN => 300 }; our $PLAYING = 1; our $STATE = STATE_BOOT; use Exporter qw(import); our @EXPORT_OK = grep { $_ =~ /^bnsm_.*|^STATE_.*/ and $_ } keys %Blacknote::System::State::; sub bnsm_run_hooks { my ($hooktype) = @_; for(@{$HOOKS{$hooktype}}){ $_->(); } } sub bnsm_get_state { return $STATE; } sub bnsm_set_state { $STATE = shift // $STATE; } sub bnsm_get_tile_mapping { return \%TILE_MAPPING; } sub bnsm_add_tile_mapping { my ($id, $class) = @_; $TILE_MAPPING{$id} = $class; } sub bnsm_set_player { $PLAYER = shift // $PLAYER; } sub bnsm_get_player { return $PLAYER; } sub bnsm_get_objects { return \@OBJECTS; } sub bnsm_add_object { push @OBJECTS, shift() // croak "Supply an object to add"; } sub bnsm_get_item_pool { my $pool = shift; my $ipool = $ITEM_POOLS{$pool}; return $ipool if defined $ipool; croak "No such pool $pool"; } sub bnsm_get_all_item_pools { my @pools = values %ITEM_POOLS; return \@pools; } sub bnsm_add_item_to_pool { my ($pool, $class) = @_; $ITEM_POOLS{$pool} = [] if not exists $ITEM_POOLS{$pool}; push @{$ITEM_POOLS{$pool}}, $class; } sub bnsm_register_function { my ($function_name, $pool, $code) = @_; $FUNCTION_POOL{$pool} = [] if not exists $FUNCTION_POOL{$pool}; $FUNCTION_POOL{$pool} = $code; } sub bnsm_get_mapdata { return $MAPDATA; } sub bnsm_which_tile { my $id = shift; return $TILE_MAPPING{$id} if defined $id and exists $TILE_MAPPING{$id}; return; } sub bnsm_set_active_keybinding { $KEYBINDING = shift; } sub bnsm_get_log { return $MSGLOG; } sub bnsm_get_tile_at { my ($x,$y) = @_; if(defined $MAPDATA->[$y]){ my $tile_id = $MAPDATA->[$y]->[$x]; if(defined $tile_id){ return $tile_id; } croak "Failed to get tile at $x,$y"; } croak "Failed to get tile at y:$y"; } sub bnsm_set_tile_at { my ($x,$y,$id) = @_; bnsm_get_tile_at($x,$y); # dies if out of bounds $id = $id + 0; # dies if not a number due to fatal warnings $MAPDATA->[$y]->[$x] = $id; } sub bnsm_get_snd_chunk { my $chunkname = shift; if(exists $SND_CHUNKS{$chunkname}){ return $SND_CHUNKS{$chunkname}; }else{ croak "No such sound chunk: $chunkname"; } } sub bnsm_add_snd_chunk { my ($chunkname, $chunk) = @_; DEBUG "Added $chunkname to list of sound chunks"; $SND_CHUNKS{$chunkname} =$chunk; return 1; } sub bnsm_request_quit { $PLAYING = 0; } sub bnsm_is_playing { return $PLAYING; } sub bnsm_set_focused_window { my $window = shift; $FOCUSED_WIN = $window; } sub bnsm_get_focused_window { croak "No active window" unless defined $FOCUSED_WIN; return $FOCUSED_WIN; } sub bnsm_push_turn (&) { my $sub = shift; push @TURN_QUEUE, $sub; } sub bnsm_consume_turns ($) { my $turns = shift; $GAME_TURNS += $turns; } sub bnsm_cycle { if(bnsm_get_state == STATE_GAME){ bnsm_change_state(STATE_TURN); while(@TURN_QUEUE){ my $turn = shift @TURN_QUEUE; $turn->(); } bnsm_change_state(STATE_GAME); } } # State change related our %ValidStateTransitions = ( STATE_BOOT() => [ STATE_GAME ], STATE_GAME() => [ STATE_PAUSE, STATE_MENU, STATE_TURN ], STATE_MENU() => [ STATE_GAME ], STATE_CHARACTER_CREATION() => [ STATE_NEW_GAME ], STATE_PAUSE() => [ STATE_GAME ], STATE_INTRO() => [ STATE_MAINMENU ], STATE_MAINMENU() => [ STATE_CHARACTER_CREATION, STATE_CONTINUE, STATE_EXIT ], STATE_CONTINUE() => [ STATE_GAME ], STATE_TURN() => [ STATE_GAME ] ); sub _init_hooks { return { init => [], update => [], fini => [], }; } # Initialize hooks for every state my %StateHooks = map { ${$Blacknote::System::State::{$_}} => _init_hooks() } grep { /^STATE_.*/ } @EXPORT_OK; use Data::Dumper; sub bnsm_change_state { no warnings 'closure'; my $next = shift; WARN "State unchanged! $STATE == $next" and return if $STATE == $next; my $validaref = $ValidStateTransitions{$STATE}; croak "Invalid state transition $STATE => $next" unless any {$_ eq $next} @$validaref; DEBUG "BNSM: old state: $STATE, new state $next"; my $finis = $StateHooks{$STATE}->{fini}; $_->() for @$finis; bnsm_set_state($next); my $inits = $StateHooks{$next}->{init}; $_->() for @$inits; } INIT{ no warnings 'closure'; DEBUG Dumper \%ValidStateTransitions; push @{$StateHooks{STATE_TURN()}->{fini}}, sub { $GAME_TURNS++; DEBUG "Leaving TURN state of SM: $GAME_TURNS" }; } 1; .