#!/usr/local/bin/perl
#
# File:   rand_mon
# Version: 1.0
# Author: John Lame (lame@clark.net, john.lame@gsc.gte.com)
#
# Description:
#  This is the primary executable for randomizing
#  the r_info.txt file distributed with Angband and 
#  its' variants.
#
# Usage:
#  Place this file, ang_rand.pl, and rand_mon.cfg in
#  the lib/edit directory (or whereever your r_info.txt
#  file is located and run it.  (Type "rand_mon" on
#  Unix systems or "perl rand_mon" in a DOS window).
#  A new file "r_info.new" will be created.  The existing
#  r_info.txt file is not modified in any way.
#
# Revision History:
#  date:          name:            action:
#  032598         J. Lame          Created version 1.0

#Import libraries
require "rand_mon.pl";

#Import global defines
require "rand_mon.cfg";

# Hack: Force 0 <= $SLP_EXP_DEG <= 1
$SLP_EXP_DEG = &min($SLP_EXP_DEG, 1);
$SLP_EXP_DEG = &max($SLP_EXP_DEG, 0);

#######################################################
### Constants
#######################################################
@extract_energy =
  (
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
   2,  2,  2,  2,  2,  2,  2,  3,  3,  3,
   3,  3,  3,  3,  3,  4,  4,  4,  4,  4,
   5,  5,  5,  5,  6,  6,  7,  7,  8,  9,
  10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
  20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
  30, 31, 32, 33, 34, 35, 36, 36, 37, 37,
  38, 38, 39, 39, 40, 40, 40, 41, 41, 41,
  42, 42, 42, 43, 43, 43, 44, 44, 44, 44,
  45, 45, 45, 45, 45, 46, 46, 46, 46, 46,
  47, 47, 47, 47, 47, 48, 48, 48, 48, 48,
  49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
  49, 49, 49, 49, 49, 49, 49, 49, 49, 49
 );

#######################################################
### Global Variables
#######################################################

###    Header    ###
# Need to save initial comments and version
@header_lines = ();  

### Flag Arrays. ### 
# These are constructed from the existing R_INFO.TXT
# file.  "B1" flags are the monster blow methods and
# "B2" flags are the monster blow effects.  The "index"
# of a flag of type "X" is the n such that $Xflags[n]
# equals that flag.  Note that because I have initialized
# the flag arrays as ("NULL"), all flag indices are 
# positive.  This made the get_flag routine slightly
# easier to code. 

@sflags = ("NULL"); # array of spell flags
%sfreq =  (); # ${$sfreq{"SFLAG"}}[n] gives level n frequency
%sdepth = (); # ${$sdepth{"SFLAG"}}[0] gives depth of first occurrence.
              # ${$sdepth{"SFLAG"}}[1] gives depth of last occurrence.

@sfreqs = ("NULL"); # array of "1_IN_%d" flags  

@fflags = ("NULL"); # array of monster intrinsics
%ffreq =  (); # ${$ffreq{"FFLAG"}}[n] gives level n frequency
%fdepth = (); # ${$fdepth{"FFLAG"}}[0] gives depth of first occurrence.
              # ${$fdepth{"FFLAG"}}[1] gives depth of last occurrence.

@b1flags = ("NULL"); # array of blow method flags
%b1freq = (); # ${$b1freq{"B1FLAG"}}[n] gives level n frequency
%b1depth = (); # ${$b1depth{"B1FLAG"}}[0] gives depth of first occurrence.
              # ${$b1depth{"B1FLAG"}}[1] gives depth of last occurrence.

@b2flags = ("NULL"); # array of blow effect flags 
%b2freq = (); # ${$b2freq{"B2FLAG"}}[n] gives level n frequency
%b2depth = (); # ${$b2depth{"B2FLAG"}}[0] gives depth of first occurrence.
              # ${$b2depth{"B2FLAG"}}[1] gives depth of last occurrence.

# @mon is the array containing all monster info.
# $mon[$n] is a reference to the hash table containing
#    all information for monster $n.
# $mon[$n]->{"name"}    (N[2])
# $mon[$n]->{"text"}    (array of D lines)
# $mon[$n]->{"char"}    (G[1])
# $mon[$n]->{"attr"}    (G[2])
# $mon[$n]->{"spd"}     (I[1])
# $mon[$n]->{"hp1"}     (I[2] hp1dhp2)
# $mon[$n]->{"hp2"}     (I[2] hp1dhp2)
# $mon[$n]->{"aaf"}     (I[3])
# $mon[$n]->{"ac"}      (I[4])
# $mon[$n]->{"slp"}     (I[5])
# $mon[$n]->{"level"}   (W[1])
# $mon[$n]->{"rarity"}  (W[2])
# $mon[$n]->{"extra"}   (W[3])
# $mon[$n]->{"exp"}     (W[4])
# $mon[$n]->{"b1"}      (array of B[1]'s)
# $mon[$n]->{"b2"}      (array of B[2]'s)
# $mon[$n]->{"b3"}      (array of B[3]'s)
# $mon[$n]->{"f"}       (array of "F" flags)
# $mon[$n]->{"sfreq"}  ("S" flag matching "1_IN_%d")
# $mon[$n]->{"s"}       (array of real "S" flags)
# $mon[$n]->{"other"}   (array of all other lines)
@mon = ();

# @Xdun is the modified array of per-level frequencies
# for flags of type "X".  $Xdun[n][j] is the modified
# level n frequency of the type "X" flag with index j.
@sdun = ();
@fdun = ();
@b1dun = ();
@b2dun = ();

#######################################################
### Other Variables
#######################################################

# Holds an array of characters.  Used for testing
# purposes only.
@chars = (); 

# Not that the delimiters are ever going to change ...
$linedel = ":";    
$flagdel = "\\|";

@the_line = ();  # holds a single tokenized line
@the_flags = (); # holds one line of flags of any type

$mnum = -1;  # number of current monster
$found_aaf = 0;  # true after first aaf is found
$min_aaf = 0;  # current minimum aaf
$max_aaf = 0;  # current maximum aaf
$found_ac = 0;  # true after first ac is found
$min_ac = 0;  # current minimum ac
$max_ac = 0;  # current maximum ac
$found_slp = 0;  # true after first ac is found
$min_slp = 0;  # current minimum ac
$max_slp = 0;  # current maximum ac

# Before the set of "F" or "S" flags for 
# a monster is randomized, it is first split
# into two temporary arrays.  The @dynamic_flags 
# array is randomized and then the two arrays 
# are put back together.
@static_flags = ();
@dynamic_flags = ();

# Hack: Used to see if FORCE_MAXHP was added or removed
$force_maxhp = 0;

# Temporary frequency and counting arrays 
# are needed to obtain random flags with 
# the correct frequencies.
@f_tmp = ();
@c_tmp = (); 

# some variables to hold temporary data
$old_val = 0;
$new_val = 0;
$min_val = 0;
$max_val = 0;

# Holds a random number
$prn = 0;
# Holds a boolean
$success = 0;
# Holds a single flag index;
$flag = 0;
# Other storage units
$i = 0;
$j = 0;
$x = 0;
$y = 0;
$tot = 0;
$N = 0;
$logN = 0;
$fm = 0;

#######################################################
### main program begins here
#######################################################

# use time and pid to set seed
srand( time() ^ ($$ + ($$ << 15)) );

# open logfile
if ($CREATE_LOG) {
  open R_LOG, ">$r_log";
}
###########################################
### begin processing r_info.txt 
###########################################
if ($CREATE_LOG) {
  print R_LOG "Reading $r_old\n";
}
open R_OLD, "<$r_old";
while (<R_OLD>) {
  $_ = strip($_, "T");
  @the_line = split/$linedel/;
  # "N" lines better always appear first.
  if ($the_line[0] eq "N") {
    $mnum = $the_line[1];
    $mon[$mnum]->{"name"} = $the_line[2];
    if ($SEE_PROGRESS) {
      print "r";
    }
  }
  elsif ($mnum < 0) {  # Haven't found first monster.
    push(@header_lines, $_);
  }
  elsif ($the_line[0] eq "G") {
    $mon[$mnum]->{"char"} = $the_line[1];
    $mon[$mnum]->{"attr"} = $the_line[2];
  }   
  elsif ($the_line[0] eq "I") {
    $mon[$mnum]->{"spd"} = $the_line[1];
    ($mon[$mnum]->{"hp1"},$mon[$mnum]->{"hp2"}) =
      split /d/, $the_line[2];
    $mon[$mnum]->{"aaf"} = $the_line[3];
    if ($found_aaf) {
      $min_aaf = &min($min_aaf,$the_line[3]);
      $max_aaf = &max($max_aaf,$the_line[3]);
    }
    else {
      $min_aaf = $the_line[3];
      $max_aaf = $the_line[3];
      $found_aaf = 1;
    }
    $mon[$mnum]->{"ac"} = $the_line[4];
    if ($found_ac) {
      $min_ac = &min($min_ac,$the_line[4]);
      $max_ac = &max($max_ac,$the_line[4]);
    }
    else {
      $min_ac = $the_line[4];
      $max_ac = $the_line[4];
      $found_ac = 1;
    }
    $mon[$mnum]->{"slp"} = $the_line[5];
    if ($found_slp) {
      $min_slp = &min($min_slp,$the_line[5]);
      $max_slp = &max($max_slp,$the_line[5]);
    }
    else {
      $min_slp = $the_line[5];
      $max_slp = $the_line[5];
      $found_slp = 1;
    }
  }
  # "W" lines must always appear before all "B", "S", 
  # and "F" lines because the "level" of the current
  # monster is used to modify the depth range
  # of each flag.  It is unfortunately assumed
  # that monsters appear in increasing order of level.
  elsif ($the_line[0] eq "W") {
    $mon[$mnum]->{"level"} = $the_line[1];
    $mon[$mnum]->{"rarity"} = $the_line[2];
    $mon[$mnum]->{"extra"} = $the_line[3];
    $mon[$mnum]->{"exp"} = $the_line[4];
  }
  # Now we start building the flag arrays as well
  # as continuing to store the specific monster
  # information.
  elsif ($the_line[0] eq "B") {
    push (@{$mon[$mnum]->{"b1"}}, $the_line[1]);
    push (@{$mon[$mnum]->{"b2"}}, $the_line[2]);
    push (@{$mon[$mnum]->{"b3"}}, $the_line[3]);
    if (!(in_list($the_line[1], "S", @b1flags))) { # new flag
      push (@b1flags, $the_line[1]);
      ${$b1depth{$the_line[1]}}[0] = $mon[$mnum]->{"level"};
    }
    ${$b1freq{$the_line[1]}}[$mon[$mnum]->{"level"}]++;
    ${$b1depth{$the_line[1]}}[1] = $mon[$mnum]->{"level"};
    if ($the_line[2]) {
      if (!(in_list($the_line[2], "S", @b2flags))) { # new flag
        push (@b2flags, $the_line[2]);
        ${$b2depth{$the_line[2]}}[0] = $mon[$mnum]->{"level"};
      }
      ${$b2freq{$the_line[2]}}[$mon[$mnum]->{"level"}]++;
      ${$b2depth{$the_line[2]}}[1] = $mon[$mnum]->{"level"};
    }
  }
  elsif ($the_line[0] eq "F") {
    @the_flags = split /$flagdel/, $the_line[1];
    foreach (@the_flags) {
      $_ = &strip($_);
      if ($_) { # Ignore null flags from final |'s
        push (@{$mon[$mnum]->{"f"}}, $_);
        if ($_ && !(in_list($_, "S", @fflags))) { # new flag
          push (@fflags, $_);
          ${$fdepth{$_}}[0] = $mon[$mnum]->{"level"};
        }
        ${$ffreq{$_}}[$mon[$mnum]->{"level"}]++;
        ${$fdepth{$_}}[1] = $mon[$mnum]->{"level"};
      }
    }
  }
  elsif ($the_line[0] eq "S") {
    @the_flags = split /$flagdel/, $the_line[1];
    foreach (@the_flags) {
      $_ = &strip($_);
      if ($_) { # Ignore null flags from final |'s
        # "1_IN_X" flags are not "real" flags.
        if ($_ !~ /^1_IN_/) {
          push (@{$mon[$mnum]->{"s"}}, $_);
          if ($_ && !(in_list($_, "S", @sflags))) { # new flag
            push (@sflags, $_);
            ${$sdepth{$_}}[0] = $mon[$mnum]->{"level"};
          }
          ${$sfreq{$_}}[$mon[$mnum]->{"level"}]++;
          ${$sdepth{$_}}[1] = $mon[$mnum]->{"level"};
        }
        else {
          if (!(in_list($_, "S", @sfreqs))) {
            push (@sfreqs, $_);
          }
          $mon[$mnum]->{"sfreq"} = $_;
        }
      }
    }
  }
  elsif ($the_line[0] eq "D") {
    # Later I may include an option to modify the
    # description if the monster is altered
    # extensively.  For now, "D" lines are never
    # altered.
    push (@{$mon[$mnum]->{"text"}}, $the_line[1]);
  }
  # If we get here, the line must be a random comment.
  # We will store it in @{$mon[$mnum]->{"other"}} and
  # then print it back out after the "D" lines for
  # monster $mnum.  Note that this won't preserve
  # the positioning of the comment if it appeared
  # somewhere in the middle of the monster entry.
  else {
    push (@{$mon[$mnum]->{"other"}},$_);
  }
}
close R_OLD;
if ($CREATE_LOG) {
  print R_LOG "Done reading $r_old\n";
  print R_LOG "  Found: $min_aaf <= AAF <= $max_aaf\n";
  print R_LOG "  Found: $min_ac <= AC <= $max_ac\n";
  print R_LOG "  Found: $min_slp <= SLP <= $max_slp\n";
}

###########################################
### Build @Xdun arrays
###########################################
if ($CREATE_LOG) {
  print R_LOG "Building flag frequency arrays\n";
}
for ($n=0; $n <=100; $n++) {
  # Modify B1 frequencies
  for ($j=1; $j < @b1flags; $j++) {
    if (!(${$b1freq{$b1flags[$j]}}[$n])) { # Don't like nulls
      ${$b1freq{$b1flags[$j]}}[$n] = 0;
    }
    if (in_list($b1flags[$j], "S", @B1_STATIC)) {
      $b1dun[$n][$j] = 0;
    }
    else {
      $b1dun[$n][$j] = ${$b1freq{$b1flags[$j]}}[$n];
      if (!($PRES_B1_FREQ)) {
        if ((${$b1depth{$b1flags[$j]}}[0] - $B1_D_MOD <= $n) &&
            ($n < ${$b1depth{$b1flags[$j]}}[0])) {
          $b1dun[$n][$j] += 1;
          $b1dun[$n][$j] *= $B1_D_WGT;
        }
        elsif ((${$b1depth{$b1flags[$j]}}[0] <= $n) &&
            ($n <= ${$b1depth{$b1flags[$j]}}[1])) {
          $b1dun[$n][$j] += 1;
          $b1dun[$n][$j] *= $B1_N_WGT;
        }
        elsif ((${$b1depth{$b1flags[$j]}}[1] < $n) &&
            ($n <= ${$b1depth{$b1flags[$j]}}[1] + $B1_S_MOD)) {
          $b1dun[$n][$j] += 1;
          $b1dun[$n][$j] *= $B1_S_WGT;
        } 
      }
    }
  }
  # Modify B2 frequencies
  for ($j=1; $j < @b2flags; $j++) {
    if (!(${$b2freq{$b2flags[$j]}}[$n])) { # Don't like nulls
      ${$b2freq{$b2flags[$j]}}[$n] = 0;
    }
    if (in_list($b2flags[$j], "S", @B2_STATIC)) {
      $b2dun[$n][$j] = 0;
    }
    else {
      $b2dun[$n][$j] = ${$b2freq{$b2flags[$j]}}[$n];
      if (!($PRES_B2_FREQ)) {
        if ((${$b2depth{$b2flags[$j]}}[0] - $B2_D_MOD <= $n) &&
            ($n < ${$b2depth{$b2flags[$j]}}[0])) {
          $b2dun[$n][$j] += 1;
          $b2dun[$n][$j] *= $B2_D_WGT;
        }
        elsif ((${$b2depth{$b2flags[$j]}}[0] <= $n) &&
            ($n <= ${$b2depth{$b2flags[$j]}}[1])) {
          $b2dun[$n][$j] += 1;
          $b2dun[$n][$j] *= $B2_N_WGT;
        }
        elsif ((${$b2depth{$b2flags[$j]}}[1] < $n) &&
            ($n <= ${$b2depth{$b2flags[$j]}}[1] + $B2_S_MOD)) {
          $b2dun[$n][$j] += 1;
          $b2dun[$n][$j] *= $B2_S_WGT;
        } 
      }
    }
  }
  # Modify F frequencies
  for ($j=1; $j < @fflags; $j++) {
    if (!(${$ffreq{$fflags[$j]}}[$n])) { # Don't like nulls
      ${$ffreq{$fflags[$j]}}[$n] = 0;
    }
    if (in_list($fflags[$j], "S", @F_STATIC)) {
      $fdun[$n][$j] = 0;
    }
    else {
      $fdun[$n][$j] = ${$ffreq{$fflags[$j]}}[$n];
      if (!($PRES_F_FREQ)) {
        if ((${$fdepth{$fflags[$j]}}[0] - $F_D_MOD <= $n) &&
            ($n < ${$fdepth{$fflags[$j]}}[0])) {
          $fdun[$n][$j] += 1;
          $fdun[$n][$j] *= $F_D_WGT;
        }
        elsif ((${$fdepth{$fflags[$j]}}[0] <= $n) &&
            ($n <= ${$fdepth{$fflags[$j]}}[1])) {
          $fdun[$n][$j] += 1;
          $fdun[$n][$j] *= $F_N_WGT;
        }
        elsif ((${$fdepth{$fflags[$j]}}[1] < $n) &&
            ($n <= ${$fdepth{$fflags[$j]}}[1] + $F_S_MOD)) {
          $fdun[$n][$j] += 1;
          $fdun[$n][$j] *= $F_S_WGT;
        } 
      }
    }
  }
  # Modify S frequencies
  for ($j=1; $j < @sflags; $j++) {
    if (!(${$sfreq{$sflags[$j]}}[$n])) { # Don't like nulls
      ${$sfreq{$sflags[$j]}}[$n] = 0;
    }
    if (in_list($sflags[$j], "S", @S_STATIC)) {
      $sdun[$n][$j] = 0;
    }
    else {
      $sdun[$n][$j] = ${$sfreq{$sflags[$j]}}[$n];
      if (!($PRES_S_FREQ)) {
        if ((${$sdepth{$sflags[$j]}}[0] - $S_D_MOD <= $n) &&
            ($n < ${$sdepth{$sflags[$j]}}[0])) {
          $sdun[$n][$j] += 1;
          $sdun[$n][$j] *= $S_D_WGT;
        }
        elsif ((${$sdepth{$sflags[$j]}}[0] <= $n) &&
            ($n <= ${$sdepth{$sflags[$j]}}[1])) {
          $sdun[$n][$j] += 1;
          $sdun[$n][$j] *= $S_N_WGT;
        }
        elsif ((${$sdepth{$sflags[$j]}}[1] < $n) &&
            ($n <= ${$sdepth{$sflags[$j]}}[1] + $S_S_MOD)) {
          $sdun[$n][$j] += 1;
          $sdun[$n][$j] *= $S_S_WGT;
        } 
      }
    }
  }
  if ($SEE_PROGRESS) {
    print ".";
  }
}

###########################################
### Modify @mon array
###########################################
if ($CREATE_LOG) {
  print R_LOG "Modifying monster types.\n";
}

for ($mnum = 0; $mnum < @mon; $mnum++) {
  $reason = "";
  # Should we attempt to randomize this monster?
  $success = 
    ((int(rand(100)) < $RAND_FREQ) &&
     (!(&in_list($mnum, "N", @PRESERVE))) &&
     (!(&in_list($mon[$mnum]->{"char"}, "S", @G_PRESERVE))));
  if ($success) {
    foreach (@{$mon[$mnum]->{"b1"}}) {
      $success &&= (!(&in_list($_, "S", @B1_PRESERVE)));
    }
  }
  if ($success) {
    foreach (@{$mon[$mnum]->{"b2"}}) {
      $success &&= (!(&in_list($_, "S", @B2_PRESERVE)));
    }
  }
  if ($success) {
    foreach (@{$mon[$mnum]->{"f"}}) {
      $success &&= (!(&in_list($_, "S", @F_PRESERVE)));
    }
  }
  if ($success) {
    foreach (@{$mon[$mnum]->{"s"}}) {
      $success &&= (!(&in_list($_, "S", @S_PRESERVE)));
    }
  }
  if ($success) { # ok to randomize this monster
    # Modify the name even if no actual changes are made.
    $mon[$mnum]->{"name"} = $NAME_PREFIX . $mon[$mnum]->{"name"};
    if ($CREATE_LOG) {
      print R_LOG "Attempting to randomize $mnum\n";
      print R_LOG "  Modifying name to \"";
      print R_LOG $mon[$mnum]->{"name"};
      print R_LOG "\n";
    }
    # Should we randomize the "B1" flags?
    if (int(rand(100)) < $B1_RAND_FREQ) { # Yes.
      # get the frequency array for b1 flags at this level.
      @f_tmp = @{$b1dun[$mon[$mnum]->{"level"}]};
      # generate the counting array and get the total.
      $tot = &build_c(\@f_tmp, \@c_tmp);
      if ($CREATE_LOG) {
        print R_LOG "  Randomizing B1 flags for $mnum\n";
      }
      # replace each non-static "B1" flag with
      # a random flag using the correct frequency.
      for ($i=0; $i < @{$mon[$mnum]->{"b1"}}; $i++) {
        if (!(&in_list($mon[$mnum]->{"b1"}[$i], "S", @B1_STATIC))) {
          if ($CREATE_LOG) {
            print R_LOG "    Changing ";
            print R_LOG $mon[$mnum]->{"b1"}[$i];
            print R_LOG " to ";
          }
          $prn = int(rand($tot - 1)) + 1;
          $flag = &get_flag(\@c_tmp,$prn,1,@f_tmp - 1);
          $mon[$mnum]->{"b1"}[$i] = $b1flags[$flag];
          if ($CREATE_LOG) {
            print R_LOG $mon[$mnum]->{"b1"}[$i];
            print R_LOG "\n";
          }
        }
        elsif ($CREATE_LOG) {
          print R_LOG "    Preserving ";
          print R_LOG $mon[$mnum]->{"b1"}[$i];
          print R_LOG "\n";
        }
      }
    }
    # Should we randomize the "B2" flags?
    if (int(rand(100)) < $B2_RAND_FREQ) { # Yes.
      # get the frequency array for b2 flags at this level.
      @f_tmp = @{$b2dun[$mon[$mnum]->{"level"}]};
      # generate the counting array and get the total.
      $tot = &build_c(\@f_tmp, \@c_tmp);
      if ($CREATE_LOG) {
        print R_LOG "  Randomizing B2 flags for $mnum\n";
      }
      # replace each non-static non-null "B2" flag with
      # a random flag using the correct frequency.
      for ($i=0; $i < @{$mon[$mnum]->{"b2"}}; $i++) {
        if (!(&in_list($mon[$mnum]->{"b2"}[$i], "S", @B2_STATIC)) &&
             ($mon[$mnum]->{"b2"}[$i])) {
          if ($CREATE_LOG) {
            print R_LOG "    Changing ";
            print R_LOG $mon[$mnum]->{"b2"}[$i];
            print R_LOG " to ";
          }
          # get a random number between 1 and $tot inclusive.
          $prn = int(rand($tot - 1)) + 1;
          # get the flag index
          $flag = &get_flag(\@c_tmp,$prn,1,@f_tmp - 1);
          $mon[$mnum]->{"b2"}[$i] = $b2flags[$flag];
          if ($CREATE_LOG) {
            print R_LOG $mon[$mnum]->{"b2"}[$i];
            print R_LOG "\n";
          }
        }
        else {
          if ($mon[$mnum]->{"b2"}[$i]) {
            if ($CREATE_LOG) {
              print R_LOG "    Preserving ";
              print R_LOG $mon[$mnum]->{"b2"}[$i];
              print R_LOG "\n";
            }
          }
          elsif ($CREATE_LOG) {
            print R_LOG "    No B2 flag found on line $i.\n";
          }
        }
      }
    }
    # Should we randomize the "F" flags?
    if (int(rand(100)) < $F_RAND_FREQ) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Randomizing F flags for $mnum\n";
      }
      # Split the flags for this monster.
      # Start with all the flags.
      @dynamic_flags = @{$mon[$mnum]->{"f"}};
      # Hack:  Changing FORCE_MAXHP should affect experience.
      $force_maxhp = &in_list("FORCE_MAXHP","S",@dynamic_flags);
      # Shift the static ones off.
      @static_flags = ();
      $N = @dynamic_flags + 0;  
      for ($i=0; $i < $N; $i++) {
        if ($CREATE_LOG) {
          print R_LOG "    Processing $dynamic_flags[0] ... ";
        }
        if (&in_list($dynamic_flags[0],"S",@F_STATIC)) {
          push @static_flags, shift @dynamic_flags;
          if ($CREATE_LOG) {
            print R_LOG "STATIC\n";
          }
        }
        else {
          push @dynamic_flags, shift @dynamic_flags;
          if ($CREATE_LOG) {
            print R_LOG "DYNAMIC\n";
          }
        }
      }
      # All we need is the size of the @dynamic_flags
      # array since these are the flags we will be
      # randomizing.
      $N = @dynamic_flags + 0;
      @dynamic_flags = ();
      # get the frequency array for f flags at this level.
      @f_tmp = @{$fdun[$mon[$mnum]->{"level"}]};
      # generate the counting array and get the total.
      $tot = &build_c(\@f_tmp, \@c_tmp);
      # generate $N distinct "F" flags
      for ($i=0; $i < $N; $i++) {
        # get a random number between 1 and $tot inclusive.
        $prn = int(rand($tot - 1)) + 1;
        # get the flag index
        $flag = &get_flag(\@c_tmp,$prn,1,@f_tmp - 1);
        # save the flag
        push @dynamic_flags, $fflags[$flag];
        # zero the temporary frequency of the chosen flag
        $f_tmp[$flag] = 0;
        # regenerate the counting array and get the new total
        $tot = &build_c(\@f_tmp, \@c_tmp);
      }
      if ($CREATE_LOG) {
        if (@dynamic_flags) {
          print R_LOG "    ### New Dynamic Flag List ###\n";
          for ($i=0; $i < @dynamic_flags; $i++) {
            print R_LOG "      $dynamic_flags[$i]\n";
          }
        }
        else {
          print R_LOG "    No dynamic flags found.\n";
        }
      }
      # Hack: Change experience is FORCE_MAXHP was added or removed.
      if ($force_maxhp != &in_list("FORCE_MAXHP","S",@dynamic_flags)) {
        if ($force_maxhp) {
          $old_val = $mon[$mnum]->{"hp1"} * $mon[$mnum]->{"hp2"};
          $new_val = $mon[$mnum]->{"hp1"}*(1+$mon[$mnum]->{"hp2"})/2;
        }
        else {
          $old_val = $mon[$mnum]->{"hp1"}*(1+$mon[$mnum]->{"hp2"})/2;
          $new_val = $mon[$mnum]->{"hp1"} * $mon[$mnum]->{"hp2"};
        }
        if ($CREATE_LOG) {
          print R_LOG "  FORCE_MAXHP flag ";
          if ($force_maxhp) {
            print R_LOG "removed.\n";
          }
          else {
            print R_LOG "added.\n";
          }
          print R_LOG "  Changing experience for $mnum from ";
          print R_LOG $mon[$mnum]->{"exp"};
          print R_LOG " to ";
        }
        $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $HP_EXP_DEG);
        $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
        if ($CREATE_LOG) {
          print R_LOG $mon[$mnum]->{"exp"};
          print R_LOG ".\n";
        }
      }
      # Save the new flag list in the @mon array
      @{$mon[$mnum]->{"f"}} = (@static_flags, @dynamic_flags);
    }
    # Should we randomize the "S" flags?
    if (int(rand(100)) < $S_RAND_FREQ) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Randomizing S flags for $mnum\n";
      }
      # Split the flags for this monster.
      # Start with all the flags.
      @dynamic_flags = @{$mon[$mnum]->{"s"}};
      # Shift the static ones off.
      @static_flags = ();
      $N = @dynamic_flags + 0;  
      for ($i=0; $i < $N; $i++) {
        if ($CREATE_LOG) {
          print R_LOG "    Processing $dynamic_flags[0] ... ";
        }
        if (&in_list($dynamic_flags[0],"S",@S_STATIC)) {
          push @static_flags, shift @dynamic_flags;
          if ($CREATE_LOG) {
            print R_LOG "STATIC\n";
          }
        }
        else {
          push @dynamic_flags, shift @dynamic_flags;
          if ($CREATE_LOG) {
            print R_LOG "DYNAMIC\n";
          }
        }
      }
      # All we need is the size of the @dynamic_flags
      # array since these are the flags we will be
      # randomizing.
      $N = @dynamic_flags + 0;
      @dynamic_flags = ();
      # get the frequency array for s flags at this level.
      @f_tmp = @{$sdun[$mon[$mnum]->{"level"}]};
      # generate the counting array and get the total.
      $tot = &build_c(\@f_tmp, \@c_tmp);
      # generate $N distinct "S" flags
      for ($i=0; $i < $N; $i++) {
        # get a random number between 1 and $tot inclusive.
        $prn = int(rand($tot - 1)) + 1;
        # get the flag index
        $flag = &get_flag(\@c_tmp,$prn,1,@f_tmp - 1);
        # save the flag
        push @dynamic_flags, $sflags[$flag];
        # zero the temporary frequency of the chosen flag
        $f_tmp[$flag] = 0;
        # regenerate the counting array and get the new total
        $tot = &build_c(\@f_tmp, \@c_tmp);
      }
      if ($CREATE_LOG) {
        if (@dynamic_flags) {
          print R_LOG "    ### New Dynamic Flag List ###\n";
          for ($i=0; $i < @dynamic_flags; $i++) {
            print R_LOG "      $dynamic_flags[$i]\n";
          }
        }
        else {
          print R_LOG "    No dynamic flags found.\n";
        }
      }
      # Save the new flag list in the @mon array
      @{$mon[$mnum]->{"s"}} = (@static_flags, @dynamic_flags);
    }
    # Should we randomize the frequency of spell-casting?
    if ((int(rand(100)) < $SFREQ_MOD_FREQ) &&
        ($mon[$mnum]->{"sfreq"})) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Changing spell frequency for $mnum from ";
        print R_LOG $mon[$mnum]->{"sfreq"};
      }
      ($x,$y) = split("_IN_",$mon[$mnum]->{"sfreq"},2);
      $old_val = $x/$y;
      $prn = &randint(1, @sfreqs - 1);
      $mon[$mnum]->{"sfreq"} =  $sfreqs[$prn];
      ($x,$y) = split("_IN_",$mon[$mnum]->{"sfreq"},2);
      $new_val = $x/$y;
      if ($CREATE_LOG) {
        print R_LOG " to ";
        print R_LOG $mon[$mnum]->{"sfreq"};
        print R_LOG ".\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG " to ";
      }
      $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $SFREQ_EXP_DEG);
      $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
      if ($CREATE_LOG) {
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG ".\n";
      }
    }
    # Should we randomize the speed?
    if (int(rand(100)) < $SPEED_MOD_FREQ) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Changing speed for $mnum from ";
        print R_LOG $mon[$mnum]->{"spd"};
      }
      $prn = int(rand($SPEED_MOD_PLUS + $SPEED_MOD_MINUS + 1));
      $prn += $mon[$mnum]->{"spd"} - $SPEED_MOD_MINUS;
      if ($CREATE_LOG) {
        print R_LOG " to $prn.\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
      }
      $mon[$mnum]->{"exp"} =
        &speed_exp_mod
         (
          $mon[$mnum]->{"spd"},
          $prn,
          $mon[$mnum]->{"exp"}
         );
      if ($CREATE_LOG) {
        print R_LOG " to ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG "\n";
      }
      $mon[$mnum]->{"spd"} = $prn;
    }
    # Should we randomize the hit dice?
    if (int(rand(100)) < $HP_MOD_FREQ) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Changing hit dice for $mnum.\n";
        print R_LOG "    Old hit dice: ";
        print R_LOG $mon[$mnum]->{"hp1"};
        print R_LOG "d";
        print R_LOG $mon[$mnum]->{"hp2"};
      }
      # Does this monster have FORCE_MAXHP set?
      $fm = 
        &in_list
         (
          "FORCE_MAXHP", 
          "S", 
          @{$mon[$mnum]->{"f"}}
         );
      $old_val = 
        &d_ave
         (
          $mon[$mnum]->{"hp1"},
          $mon[$mnum]->{"hp2"},
          $fm
         );
      if ($CREATE_LOG) {
        print R_LOG " (ave=$old_val)\n";
        $min_val = $old_val * (1 - $HP_MOD_MINUS/100);
        $min_val = ($min_val < 1 ? 1 : $min_val);
        $max_val = $old_val * (1 + $HP_MOD_PLUS/100);
        print R_LOG "    Want new average in ($min_val, $max_val)\n";
      }
      # Get new hit dice.
      ($x,$y) =
        &random_dice
         (
          $mon[$mnum]->{"hp1"},
          $mon[$mnum]->{"hp2"},
          $fm,
          $HP_MOD_MINUS/100,
          $HP_MOD_PLUS/100
         );
      $mon[$mnum]->{"hp1"} = $x;
      $mon[$mnum]->{"hp2"} = $y;
      $new_val = &d_ave($x,$y,$fm);
      if ($CREATE_LOG) {
        print R_LOG "    New hit dice: ";
        print R_LOG $x;
        print R_LOG "d";
        print R_LOG $y;
        print R_LOG " (ave=$new_val).\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG " to ";
      }
      $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $HP_EXP_DEG);
      $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
      if ($CREATE_LOG) {
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG ".\n";
      }
    }
    # Should we randomize the aaf?
    if (int(rand(100)) < $AAF_MOD_FREQ) { # Yes.
      $old_val = $mon[$mnum]->{"aaf"};
      if ($CREATE_LOG) {
        print R_LOG "  Changing aaf for $mnum from ";
        print R_LOG $old_val;
        print R_LOG " to ";
      }
      # Get new aaf.
      $new_val = 
        &randint
         (
          $old_val-$AAF_MOD_MINUS,
          $old_val+$AAF_MOD_PLUS
         );
      if ($PRES_AAF_RANGE) { # Enforce current limits
        $new_val = &max($min_aaf,$new_val);
        $new_val = &min($max_aaf,$new_val);
      }
      # Enforce positive
      $new_val = &max(1,$new_val);
      $mon[$mnum]->{"aaf"} = $new_val;
      if ($CREATE_LOG) {
        print R_LOG $new_val;
        print R_LOG ".\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG " to ";
      }
      $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $AAF_EXP_DEG);
      $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
      if ($CREATE_LOG) {
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG ".\n";
      }
    }            
    # Should we randomize the ac?
    if (int(rand(100)) < $AC_MOD_FREQ) { # Yes.
      $old_val = $mon[$mnum]->{"ac"};
      if ($CREATE_LOG) {
        print R_LOG "  Changing ac for $mnum from ";
        print R_LOG $old_val;
        print R_LOG " to ";
      }
      # Get new ac.
      $new_val = 
        &randint
         (
          $old_val-$AC_MOD_MINUS,
          $old_val+$AC_MOD_PLUS
         );
      if ($PRES_AC_RANGE) { # Enforce current limits
        $new_val = &max($min_ac,$new_val);
        $new_val = &min($max_ac,$new_val);
      }
      # Enforce positive
      $new_val = &max(1,$new_val);
      $mon[$mnum]->{"ac"} = $new_val;
      if ($CREATE_LOG) {
        print R_LOG $new_val;
        print R_LOG ".\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG " to ";
      }
      $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $AC_EXP_DEG);
      $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
      if ($CREATE_LOG) {
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG ".\n";
      }
    }
    # Should we randomize the slp?
    if (int(rand(100)) < $SLP_MOD_FREQ) { # Yes.
      $old_val = $mon[$mnum]->{"ac"};
      if ($CREATE_LOG) {
        print R_LOG "  Changing slp for $mnum from ";
        print R_LOG $old_val;
        print R_LOG " to ";
      }
      # Get new slp.
      $new_val = &randint($min_slp,$max_slp);
      $mon[$mnum]->{"slp"} = $new_val;
      if ($CREATE_LOG) {
        print R_LOG $new_val;
        print R_LOG ".\n";
        print R_LOG "  Changing experience for $mnum from ";
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG " to ";
      }
      $mon[$mnum]->{"exp"} *= 
        (1+($SLP_EXP_DEG*($old_val-$new_val)/($max_slp-$min_slp+1)));
      $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
      if ($CREATE_LOG) {
        print R_LOG $mon[$mnum]->{"exp"};
        print R_LOG ".\n";
      }
    }
    # Should we randomize the damage dice?
    if (int(rand(100)) < $DAM_MOD_FREQ) { # Yes.
      if ($CREATE_LOG) {
        print R_LOG "  Changing damage dice for $mnum.\n";
      }
      $old_val = 0;  # Will store old average damage
      $new_val = 0;  # Will store new average damage
      for ($i=0; $i < @{$mon[$mnum]->{"b3"}}; $i++) {
        if ($mon[$mnum]->{"b3"}[$i]) {
          ($x,$y) = split /d/, $mon[$mnum]->{"b3"}[$i];
          $old_val += &d_ave($x,$y,0);
          if ($CREATE_LOG) {
            print R_LOG "    Changing ";
            print R_LOG $mon[$mnum]->{"b3"}[$i];
            print R_LOG " to ";
          }
          # Get new damage dice
          ($x,$y) =
            &random_dice
             (
              $x,
              $y,
              0,
              $DAM_MOD_MINUS/100,
              $DAM_MOD_PLUS/100
             );
          $mon[$mnum]->{"b3"}[$i] = $x . "d" . $y;
          $new_val += &d_ave($x,$y,0);
          if ($CREATE_LOG) {
            print R_LOG $mon[$mnum]->{"b3"}[$i];
            print R_LOG ".\n";
          }
        }
      }
      if ($old_val > 0) { # Damage dice were found
        if ($CREATE_LOG) {
          print R_LOG "    Changed average damage from ";
          print R_LOG "$old_val to $new_val.\n";
          print R_LOG "  Changing experience for $mnum from ";
          print R_LOG $mon[$mnum]->{"exp"};
          print R_LOG " to ";
        }
        $mon[$mnum]->{"exp"} *= (($new_val/$old_val) ** $DAM_EXP_DEG);
        $mon[$mnum]->{"exp"} = &lint($mon[$mnum]->{"exp"});
        if ($CREATE_LOG) {
          print R_LOG $mon[$mnum]->{"exp"};
          print R_LOG ".\n";
        }
      }
      elsif ($CREATE_LOG) {
        print R_LOG "    No damage dice were found.\n";
      }
    }
  }
  elsif ($CREATE_LOG) {
    print R_LOG "Not allowed to randomize $mnum.\n";
  }
  if ($SEE_PROGRESS) {
    print "m";
  }
}

###########################################
### begin writing r_info.new 
###########################################
if ($CREATE_LOG) {
  print R_LOG "Writing $r_new\n";
}
open R_NEW, ">$r_new";
foreach (@header_lines) {
 print R_NEW "$_\n";
}
for ($mnum = 0; $mnum < @mon; $mnum++) {
  print R_NEW "N:$mnum:";
  print R_NEW $mon[$mnum]->{"name"};
  print R_NEW "\n";
  print R_NEW "G:";
  print R_NEW $mon[$mnum]->{"char"};
  print R_NEW ":";
  print R_NEW $mon[$mnum]->{"attr"};
  print R_NEW "\n";
  if ($mnum > 0) { # Player doesn't have an "I" line.
    print R_NEW "I:";
    print R_NEW $mon[$mnum]->{"spd"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"hp1"};
    print R_NEW "d";
    print R_NEW $mon[$mnum]->{"hp2"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"aaf"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"ac"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"slp"};
    print R_NEW "\n";
  }
  if ($mnum > 0) { # Player doesn't have an "W" line.
    print R_NEW "W:";
    print R_NEW $mon[$mnum]->{"level"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"rarity"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"extra"};
    print R_NEW ":";
    print R_NEW $mon[$mnum]->{"exp"};
    print R_NEW "\n";
  }
  for ($i=0; $i < @{$mon[$mnum]->{"b1"}}; $i++) {
    print R_NEW "B:";
    print R_NEW $mon[$mnum]->{"b1"}[$i];
    if ($mon[$mnum]->{"b2"}[$i]) {
      print R_NEW ":";
      print R_NEW $mon[$mnum]->{"b2"}[$i];
      if ($mon[$mnum]->{"b3"}[$i]) {
        print R_NEW ":";
        print R_NEW $mon[$mnum]->{"b3"}[$i];
      }
    }
    print R_NEW "\n";
  }
  ### Begin "F" flags
  if (@{$mon[$mnum]->{"f"}}) {
    print R_NEW "F:";
    $j = 0;  # put at most 5 flags per line
    foreach (@{$mon[$mnum]->{"f"}}) {
      if ($j >= 5) {
        print R_NEW " |\nF:";
        $j = 0;
      }
      if ($j > 0) {
        print R_NEW " | ";
      }
      print R_NEW;
      $j++;
    }
    print R_NEW "\n";
  }
  ### Begin "S" flags
  if (@{$mon[$mnum]->{"s"}}) {
    print R_NEW "S:";
    print R_NEW $mon[$mnum]->{"sfreq"};
    $j = 1;  # put at most 5 flags per line
    foreach (@{$mon[$mnum]->{"s"}}) {
      if ($j >= 5) {
        print R_NEW " |\nS:";
        $j = 0;
      }
      if ($j > 0) {
        print R_NEW " | ";
      }
      print R_NEW;
      $j++;
    }
    print R_NEW "\n";
  }
  ### Begin "D" lines.
  foreach (@{$mon[$mnum]->{"text"}}) {
    print R_NEW "D:$_\n";
  }
  ### Begin "other" stuff.
  foreach (@{$mon[$mnum]->{"other"}}) {
    print R_NEW "$_\n";
  }
  if ($SEE_PROGRESS) {
    print "w";
  }
}
close R_NEW;

# Install the new file.
if ($USE_NEWFILE) {
  unlink $r_old;
  rename $r_new, $r_old;
  if ($CREATE_LOG) {
    print R_LOG "Replacing $r_old with $r_new.\n";
  }
  unlink $r_raw;
  if ($CREATE_LOG) {
    print R_LOG "Deleting $r_raw\n";
  }
}

if ($CREATE_LOG) {
  close R_LOG;
}

if ($SEE_PROGRESS) {
  print "\n";
}

#&print_frequencies;
