The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# ZHeader: version-specific information and settings
# for each game

package Games::Rezrov::ZHeader;

use Games::Rezrov::ZConst;
use Games::Rezrov::Inliner;

use strict;

use constant FLAGS_1 => 0x01;  # one byte
use constant FLAGS_2 => 0x10;  # TWO BYTES
# location of various flags in the header

# see spec section 11:
use constant RELEASE_NUMBER => 0x02;
use constant PAGED_MEMORY_ADDRESS => 0x04;
use constant FIRST_INSTRUCTION_ADDRESS => 0x06;
use constant DICTIONARY_ADDRESS => 0x08;
use constant OBJECT_TABLE_ADDRESS => 0x0a;
use constant GLOBAL_VARIABLE_ADDRESS => 0x0c;
use constant STATIC_MEMORY_ADDRESS => 0x0e;
use constant SERIAL_CODE => 0x12;
use constant ABBREV_TABLE_ADDRESS => 0x18;
use constant FILE_LENGTH => 0x1a;
use constant CHECKSUM => 0x1c;

use constant STATUS_NOT_AVAILABLE => 0x10;  # bit 4 (#5)
use constant SCREEN_SPLITTING_AVAILABLE => 0x20; # bit 5 (#6)

# Flags 1
use constant TANDY => 0x08;

# Flags 2
use constant TRANSCRIPT_ON => 0x01;            # bit 0
use constant FORCE_FIXED => 0x02;              # bit 1
use constant REQUEST_STATUS_REDRAW => 0x04;    # bit 2

# Flags 2, v5+:
use constant WANTS_PICTURES => 0x08;
use constant WANTS_UNDO => 0x10;
use constant WANTS_MOUSE => 0x20;
use constant WANTS_COLOR => 0x40;
use constant WANTS_SOUND => 0x80;
# Flags 2, v6+:
use constant WANTS_MENUS => 0x0100;  # ??

use constant BACKGROUND_COLOR => 0x2c;
use constant FOREGROUND_COLOR => 0x2d;
# 8.3.2, 8.3.3

use constant SCREEN_HEIGHT_LINES => 0x20;
use constant SCREEN_WIDTH_CHARS => 0x21;
use constant SCREEN_WIDTH_UNITS => 0x22;
use constant SCREEN_HEIGHT_UNITS => 0x24;

use constant FONT_WIDTH_UNITS_V5 => 0x26;
use constant FONT_WIDTH_UNITS_V6 => 0x27;

use constant FONT_HEIGHT_UNITS_V5 => 0x27;
use constant FONT_HEIGHT_UNITS_V6 => 0x26;

use constant ROUTINES_OFFSET => 0x28;
use constant STRINGS_OFFSET => 0x2a;

use Games::Rezrov::MethodMaker ([],
			 qw(
			    abbrev_table_address
			    file_checksum
			    release_number
			    paged_memory_address
			    object_table_address
			    global_variable_address
			    static_memory_address
			    first_instruction_address
			    dictionary_address
			    serial_code
			    file_length
			    story
			    version
			    object_bytes
			    attribute_bytes
			    pointer_size
			    max_properties
			    max_objects
			    attribute_starter
			    object_count
			    encoded_word_length
			    is_time_game
			    strings_offset
			    routines_offset
			   ));

#use SelfLoader;

1;

my $INLINE_CODE = '
sub new {
  my ($type, $zio) = @_;
  my $self = [];
  bless $self, $type;
  
  my $version = GET_BYTE_AT(0);
  if ($version < 1 or $version > 8) {
    die "\nThis does not appear to be a valid game file.\n";
  } elsif (($version < 3 or $version > 5) and $version != 8) {
#  } elsif ($version < 3 or $version > 8) {
#    die "Sorry, only z-code versions 3-8 are supported at present...\nAnd even those need work!  :)\n"
    die "Sorry, only z-code versions 3,4,5 and 8 are supported at present...\nAnd even those need work!  :)\n"
  } else {
    $self->version($version);
  }

  my $f1 = GET_BYTE_AT(FLAGS_1);
  $self->is_time_game($f1 & 0x02 ? 1 : 0);
  # a "time" game: 8.2.3.2

  my $start_rows = Games::Rezrov::StoryFile::rows();
  my $start_columns = Games::Rezrov::StoryFile::columns();

  $f1 |= TANDY if Games::Rezrov::ZOptions::TANDY_BIT();
  # turn on the "tandy bit"
  
  if ($version <= 3) {
    $self->encoded_word_length(6);
    # 13.3, 13.4

    # set bits 4 (status line) and 5 (screen splitting) appropriately
    # depending on the ability of the ZIO implementation
    if ($zio->can_split()) {
      # yes
      $f1 |= SCREEN_SPLITTING_AVAILABLE;
      $f1 &= ~ STATUS_NOT_AVAILABLE;
    } else {
      # no
      $f1 &= ~ SCREEN_SPLITTING_AVAILABLE;
      $f1 |= STATUS_NOT_AVAILABLE;
    }

    # "bit 6" (#7): variable-pitch font is default?
    if ($zio->fixed_font_default()) {
      $f1 |= 0x40;
    } else {
      $f1 &= ~0x40;
    }
  } else {
    #
    # versions 4+
    #
    $self->encoded_word_length(9);
    # 13.3, 13.4

    if ($version >= 4) {
      $f1 |= 0x04;
      # "bit 2" (#3): boldface available
      $f1 |= 0x08;
      # "bit 3" (#4): italic available
      $f1 |= 0x10;
      # "bit 4" (#5): fixed-font available

#      $f1 |= 0x80;
      $f1 &= ~0x80;
      # "bit 7" (#8): timed input NOT available

      Games::Rezrov::StoryFile::set_byte_at(30, Games::Rezrov::ZOptions::INTERPRETER_ID());
      # interpreter number
      Games::Rezrov::StoryFile::set_byte_at(31, ord "R");
      # interpreter version; "R" for rezrov
      
      $self->set_columns($start_columns);
      $self->set_rows($start_rows);
    }
    if ($version >= 5) {
      if ($zio->can_use_color()) {
	# "bit 0" (#1): colors available
	$f1 |= 0x01;
      }

      Games::Rezrov::StoryFile::set_byte_at(BACKGROUND_COLOR, Games::Rezrov::ZConst::COLOR_BLACK);
      Games::Rezrov::StoryFile::set_byte_at(FOREGROUND_COLOR, Games::Rezrov::ZConst::COLOR_WHITE);
      # 8.3.3: default foreground and background
      # FIX ME!

      my $f2 = Games::Rezrov::StoryFile::get_word_at(FLAGS_2);
      if ($zio->groks_font_3() and
	  !Games::Rezrov::StoryFile::font_3_disabled()) {
	# ZIO can decode font 3 characters
	$f2 |= WANTS_PICTURES;
      } else {
	# nope
	$f2 &= ~ WANTS_PICTURES;
      }
      
#      $f2 |= WANTS_UNDO;
      $f2 &= ~ WANTS_UNDO;
      # FIX ME: should we never use this???

      if ($f2 & WANTS_COLOR) {
	# 8.3.4: the game wants to use colors
#	print "wants color!\n";
      }
      Games::Rezrov::StoryFile::set_word_at(FLAGS_2, $f2);
    }
    if ($version >= 6) {
      # more unimplemented: see 8.3.2, etc
      $self->routines_offset(Games::Rezrov::StoryFile::get_word_at(ROUTINES_OFFSET));
      $self->strings_offset(Games::Rezrov::StoryFile::get_word_at(STRINGS_OFFSET));
    }
  }

  Games::Rezrov::StoryFile::set_byte_at(FLAGS_1, $f1);
  # write back the header flags

  $self->release_number(Games::Rezrov::StoryFile::get_word_at(RELEASE_NUMBER));
  $self->paged_memory_address(Games::Rezrov::StoryFile::get_word_at(PAGED_MEMORY_ADDRESS));
  $self->first_instruction_address(Games::Rezrov::StoryFile::get_word_at(FIRST_INSTRUCTION_ADDRESS));
  $self->dictionary_address(Games::Rezrov::StoryFile::get_word_at(DICTIONARY_ADDRESS));
  $self->object_table_address(Games::Rezrov::StoryFile::get_word_at(OBJECT_TABLE_ADDRESS));
  $self->global_variable_address(Games::Rezrov::StoryFile::get_word_at(GLOBAL_VARIABLE_ADDRESS));
  $self->static_memory_address(Games::Rezrov::StoryFile::get_word_at(STATIC_MEMORY_ADDRESS));
  $self->serial_code(Games::Rezrov::StoryFile::get_string_at(SERIAL_CODE, 6));
  # see zmach06e.txt
  $self->abbrev_table_address(Games::Rezrov::StoryFile::get_word_at(ABBREV_TABLE_ADDRESS));
  $self->file_checksum(Games::Rezrov::StoryFile::get_word_at(CHECKSUM));

  my $flen = Games::Rezrov::StoryFile::get_word_at(FILE_LENGTH);
  if ($version <= 3) {
    # see 11.1.6
    $flen *= 2;
  } elsif ($version == 4 || $version == 5) {
    $flen *= 4;
  } else {
    $flen *= 8;
  }
  $self->file_length($flen);
  
  #
  #  set object "constants" for this version...
  #
  if ($version <= 3) {
    # 12.3.1
    $self->object_bytes(9);
    $self->attribute_bytes(4);
    $self->pointer_size(1);
    $self->max_properties(31);	# 12.2
    $self->max_objects(255);		# 12.3.1
  } else {
    # 12.3.2
    $self->object_bytes(14);
    $self->attribute_bytes(6);
    $self->pointer_size(2);
    $self->max_properties(63);	# 12.2
    $self->max_objects(65535);	# 12.3.2
  }
  die("check your math!")
    if (($self->attribute_bytes() + ($self->pointer_size() * 3) + 2)
	!= $self->object_bytes());
  
  $self->attribute_starter($self->object_table_address() +
			   ($self->max_properties() * 2));
  
  my $obj_space = $self->global_variable_address() - $self->attribute_starter();
  # how many bytes exist between the start of the object area and
  # the beginning of the global variable block?
  my $object_count;
  if ($obj_space > 0) {
    # hack:
    # guess approximate object count; most useful for games later than v3
    # FIX ME: is this _way_ off?  Better to check validity of each object
    # sequentially, stopping w/invalid pointers, etc?
    $object_count = $obj_space / $self->object_bytes();
    $object_count = $self->max_objects()
      if $object_count > $self->max_objects();
  } else {
    # header data not arranged the way we expect; oh well.
    $object_count = $self->max_objects();
  }
  $self->object_count($object_count);
#  die sprintf "objects: %s\n", $object_count;
  
  return $self;
}

sub get_colors {
  return (GET_BYTE_AT(FOREGROUND_COLOR),
	  GET_BYTE_AT(BACKGROUND_COLOR));
}

';

Games::Rezrov::Inliner::inline(\$INLINE_CODE);
eval $INLINE_CODE;
#print $INLINE_CODE;
#die "oof";
undef $INLINE_CODE;

1;

#__DATA__

sub get_abbreviation_addr {
  my ($self, $entry) = @_;
  # Spec 3.3: fetch and convert the "word address" of the given entry
  # in the abbreviations table.
#  print STDERR "gaa\n";
  my $abbrev_addr = $self->abbrev_table_address() + ($entry * 2);
  return Games::Rezrov::StoryFile::get_word_at($abbrev_addr) * 2;
  # "word address"; only used for abbreviations (packed address
  # rules do not apply here)
}

sub set_columns {
  # 8.4: set the dimensions of the screen.
  # only needed in v4+
  # arg: number of columns
  Games::Rezrov::StoryFile::set_byte_at(SCREEN_WIDTH_CHARS, $_[1]);
  if ($_[0]->version >= 5) {
    Games::Rezrov::StoryFile::set_byte_at($_[0]->version >= 6 ?
					  FONT_WIDTH_UNITS_V6 : FONT_WIDTH_UNITS_V5, 1);
    Games::Rezrov::StoryFile::set_word_at(SCREEN_WIDTH_UNITS, $_[1]);
    # ?
  }
}

sub set_rows {
  # arg: number of rows
  Games::Rezrov::StoryFile::set_byte_at(SCREEN_HEIGHT_LINES, $_[1]);
  if ($_[0]->version >= 5) {
    Games::Rezrov::StoryFile::set_byte_at($_[0]->version >= 6 ?
		FONT_HEIGHT_UNITS_V6 : FONT_HEIGHT_UNITS_V5, 1);
    Games::Rezrov::StoryFile::set_word_at(SCREEN_HEIGHT_UNITS, $_[1]);
  }
}

sub wants_color {
  # 8.3.4: does the game want to use colors?
  return Games::Rezrov::StoryFile::get_word_at(FLAGS_2) & WANTS_COLOR ? 1 : 0;
}

sub fixed_font_forced {
  # 8.1: fixed-font printing may be forced by the game
  if ($_[0]->version >= 3) {
    # see section 10
    return Games::Rezrov::StoryFile::get_word_at(FLAGS_2) & FORCE_FIXED;
  } else {
    return 0;
  }
}


1;