Submit Your Article


 
RPG Maker

Welcome Guest ( Log In | Register )


  Games Resources RPG Maker VX RPG Maker XP Scripts Tutorials Downloads

> 


———
Before you ask! Read! ;)

You must have 30+ Posts to create a topic here!

Thanks for reading!
———

 
Reply to this topicStart new topic
> Encounter system script
Tsukihime
post Apr 24 2012, 12:31 PM
Post #1


Level 25
Group Icon

Group: Revolutionary
Posts: 560
Type: None
RM Skill: Undisclosed
Rev Points: 25




Here's a script I am planning to work on but I'll put it here anyways.

Suppose you have 5 enemies on a map: slime, hornet, kitten, wolf, demon
By default, you specify which troops you encounter by selecting pre-defined troops from the database.

So I want every possible permutation of each enemy, up to 8 enemies per troop. So maybe I'll have 2 slimes and 3 hornets, or 5 kittens and 1 slime, etc.

Some simple math shows you that the number of possible combinations is ridiculous and makes no sense to store all of them in the database as separate troop objects.

The goal is to create a script that will allow you to

Dynamically generate troops based on a pre-defined set of rules

To make it easier, rules will be defined on a per-map basis (so you don't have to consider things like region tiles, day/night, etc), though you should be able to extend it.


The basic idea is pretty simple: just take all of the possible enemies that can show up in the map, toss them in a pool, and then grab a bunch of them and you have your troop ready for battle.

But what happens if the demon is really strong? You don't want 8 demons in a battle right? If you're absolutely unlucky you'll run into impossible battles.
So the user should be able to set encounter rates for each monster. For example, in my note box I might write

CODE
slime 70
hornet 70
kitten 50
wolf 30
demon 10


This shows that slimes have a very high chance of showing up, while demons have a very low chance.

Ok, so now you have a less likely chance of drawing 8 demons, but still, I think having 5 kittens in a battle is overkill.

So now I want to extend it to specify restrictions on how many enemies can show up at once

CODE
slime 70
hornet 70
kitten 50 3
wolf 30 2
demon 10 1


The optional 3rd element essentially limits you to only one demon per battle, or two wolves per battle. Of course, this doesn't restrict me from having 3 kittens, 2 wolves, and 1 demon in the same battle.

Users should be able to manipulate the likelihood that you will encounter 1 enemy, or 4 enemies, or 8 enemies.
For early dungeons, you may only want players to battle troops of 1 enemy, whereas in later dungeons 5 or 6 are fair game.

Requirements

1: implement a system that will randomly generate the number of enemies that will appear in a battle, and then randomly create a troop of that size.
2: Add functionality so that you are able to specify the encounter rates of each type of enemy (as shown in the above examples)
3: Add functionality so that you are able to specify a limit on the number of each monster that can appear in a single troop. Inequalities such as "at least" or "at most" or "equal to" should be available (perhaps kittens come in pairs)

Extensions

4: perhaps kittens like to hunt in packs, so there is a greater probability that you will run into 6 kittens, while demons usually roam alone so if you do run into a demon you'll probably only see one. Come up with a way to implement this functionality.

5: Perhaps kittens hate wolves so you will never encounter any battles with both kittens and wolves. Come up with a way to implement this functionality.

This post has been edited by Tsukihime: Apr 24 2012, 02:11 PM


__________________________
My Scripts
Go to the top of the page
 
+Quote Post
   
Night_Runner
post May 1 2012, 05:29 AM
Post #2


Level 50
Group Icon

Group: +Gold Member
Posts: 1,523
Type: Scripter
RM Skill: Undisclosed




I think I missed a small section of requriement 3, the bit about "at least", etc, but the rest of the requriements and extensions seem fulfilled happy.gif

  1. If you have a look in the spoiler below, you can set either the <MinEnemies=1, MaxEnemies=10> to have a random number of enemies, or you can forcefully specify <NumEnemies=5> to have 5 enemies appear
  2. The weight parameter is how likely the enemy will appear, so if enemy 1 has weight=100 and enemy 2 has weight = 10, then enemy 1 should statistically appear 10 times as often as enemy 2
  3. I've left a attribute, Max, which is the maximum amount of a specific enemy to have in a enemy troop.
    To have Kittens come in pairs, use the PackSize option to have 2 kittens appear at a time
  4. To have only 1 demon appear, set his spots taken in the troop equal to the to the NumEnemies, e.g. if there is room for 10 enemies (NumEnemies=10), and the Demon takes 10 spots (Spots=10) then there won't be room for any other monsters.
    Similarly, you could set Kittens to avoid Demons and have their spots equal to 1 for each Kitten, and it will only populate with kittens
  5. The way it's implemented, if Kittens avoid wolves, but wolves don't avoid kittens, it gets complicated.
    If the kitten is the first in battle, wolves will not appear
    If wolves appear in battle first, then kittens may join them.
    To overcome this, you may notice that in my example below Slimes and Wisps explicitly avoid each other.
    Think of it as a feature happy.gif


[Show/Hide] Example setup for troop

For your troop, make sure that the first command on the first page of the battle is a comment, and all sequential commands are straight after the first comment (i.e. not placed after several non-related commands).
The comment should be formatted like follows
CODE
<MinEnemies=1, MaxEnemies=1>
<Name=Slime, Weight=10000, Max=10, Spots=1, PackSize=2,
Avoid = Wisp>
<Name=Werewolf, Weight = 70, Max=2, Spots=1, PackOnly=true>
<Name=Demon God, Weight=10, Max=1, Spots=10>

CODE
<Name = Wisp, Weight = 50, Max = 2, Spots = 3, Avoid = Slime>


Note the name must be set, and for all other values default values are implied if they are not specified.
The default weight (if not stated) is 100, the default number of spots taken is 1, etc.




CODE
#==============================================================================
# ** VX Ace: Night_Runner's Random Enemy Battlers
#------------------------------------------------------------------------------
#  See http://www.rpgrevolution.com/forums/index.php?showtopic=56282
#  for examples and an explanation.
#==============================================================================



#==============================================================================
# ** Game_Troop
#------------------------------------------------------------------------------
#  Modified to have random battlers.
#==============================================================================

class Game_Troop
  #--------------------------------------------------------------------------
  # * Get Troop Objects
  #--------------------------------------------------------------------------
  def troop
    @troops
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  alias nr_encounter_system_setup  setup  unless $@
  def setup(troop_id)
    setup_enemies(troop_id)
    nr_encounter_system_setup(troop_id)
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  def setup_enemies(troop_id)
    # Get the enemy troop
    @troops = $data_troops[troop_id]
    # Check if the first command on the first page is a comment
    if @troops.pages[0].list[0].code == 108
      # Get the text in the comment
      text = ""
      index = 0
      begin
        text += @troops.pages[0].list[index].parameters[0].downcase
        index += 1
      end while [108, 408].include? @troops.pages[0].list[index].code
      # Get the number of enemies (do nothing if not stated)
      if text.include?('maxenemies')
        min_enemies = text.scan(/minenemies\s*\=\s*((\d)+)/)[0]
        max_enemies = text.scan(/maxenemies\s*=\s*((\d)+)/)[0][0].to_i
        if min_enemies == nil
          min_enemies = 1
        else
          min_enemies = min_enemies[0].to_i
        end
        num_enemies = Random.rand(max_enemies + 1 - min_enemies) + min_enemies
      elsif text.include?('numenemies')
        num_enemies = text.scan(/numenemies\s*=\s*((\d)+)/)[0][0].to_i
      else
        return
      end
      p "Maximum number of enemies: #{num_enemies}"
      # Parse the enemies
      enemies = []
      total_weighting = 0
      for line in text.split(/<([^<>]*)>/).map { |s| s + ',' }
        next if not line.include?('name')
        name = line.scan(/name\s*=\s*([^,]*),/)[0][0]
        weight = line.scan(/weight\s*=\s*([^,]*),/)[0]
        weight = weight == nil ? 100 : weight[0].to_i
        total_weighting += weight
        max = line.scan(/max\s*=\s*([^,]*),/)[0]
        max = max == nil ? num_enemies : max[0].to_i
        spots = line.scan(/spots\s*=\s*([^,]*),/)[0]
        spots = spots == nil ? 1 : spots[0].to_i
        packsize = line.scan(/packsize\s*=\s*([^,]*),/)[0]
        packsize = packsize == nil ? 1 : packsize[0].to_i
        avoid = line.scan(/avoid\s*=\s*([^,]*),/)[0]
        avoid = avoid == nil ? "" : avoid[0]
        packonly = line.scan(/packonly\s*=\s*([^,]*),/)[0]
        packonly = packonly == nil ? false : packonly[0] == "true"
        enemy = {
          :name     => name,
          :weight   => weight,
          :max      => max,
          :spots    => spots,
          :packsize => packsize,
          :avoid    => avoid,
          :packonly => packonly
        }
        enemies << enemy
      end
      # Generate the enemies
      members = []
      members_size = 0
      loops_since_member_added = 0
      max_wait_loops = 10
      begin
        loops_since_member_added += 1
        enemy_weight = Random.rand(total_weighting + 1) + 1
        for enemy in enemies
          enemy_weight -= enemy[:weight]
          if (enemy_weight <= 0) and (enemy[:weight] > 0)
            num_this_enemy = members.count(enemy)
            next if num_this_enemy > enemy[:max]
            en_spots = enemy[:spots] * enemy[:packsize]
            next if members_size + en_spots > num_enemies
            next if members.include?(enemy[:avoid])
            has_other_members = members.select { |en| enemy[:name] != en }
            next if (enemy[:packonly]) and not (has_other_members.empty?)
            # If this is a ocak animal, remove the rest from the enemy group\
            if enemy[:packonly]
              for e in enemies
                if e != enemy
                  total_weighting -= e[:weight]
                  e[:weight] = 0
                end
              end
            end
            loops_since_member_added = 0
            members += [enemy[:name]] * enemy[:packsize]
            members_size += en_spots
            if members.count(enemy[:name]) >= enemy[:max]
              total_weighting -= enemy[:weight]
              enemy[:weight] = 0
            end
          end
        end
      end while (members_size < num_enemies) and
                (loops_since_member_added < max_wait_loops)
      # Create the troops array
      @troops.members = []
      for index in 0...members.size
        enemy = members[index]
        enemy_ID = $data_enemies.select { |en|
          next if en.nil?; enemy == en.name.downcase }[0].id
        troop_member = RPG::Troop::Member.new
        troop_member.enemy_id = enemy_ID
        enemy_width = 128
        enemy_height = 96
        max_across = Graphics.width / enemy_width
        mid = (Graphics.width - [members.size, max_across].min * enemy_width) /2
        p troop_member.x = mid + (index % max_across) * enemy_width
        p troop_member.y = 280 - (index / max_across) * enemy_height
        @troops.members << troop_member
      end
    end
  end
end



#==============================================================================
# ** End of Script.
#==============================================================================


__________________________
K.I.S.S.
Want help with your scripting problems? Upload a demo! Or at the very least; provide links to the scripts in question.

Most important guide ever: Newbie's Guide to Switches
Go to the top of the page
 
+Quote Post
   
Tsukihime
post May 1 2012, 11:00 AM
Post #3


Level 25
Group Icon

Group: Revolutionary
Posts: 560
Type: None
RM Skill: Undisclosed
Rev Points: 25




Very nice. The solution is also designed well, being isolated to a single class with pretty much no changes to the existing system. I guess that would make it highly compatible with other scripts. It's also better than my map notebox idea cause you can re-use the same troop across multiple maps (like if a single area had several maps)

I'm probably going to re-factor the code so that there are more methods involved. For example, when drawing the actual enemy battlers, we can then replace the current algorithm with some sort of 2D bin-packing algorithm that will optimize image placement so that there is less overlap. I could then re-use most of the methods as well.

The initial idea was to extend the map troop management system to allow "random generation" as well a supporting functionality for the default system

1: It should, in theory, be able to function the same way as the default system, specifying only the troop and the weight. However, because we are working directly with enemy objects, it may be a little difficult to specify specific groups of enemies.

As a realistic situation, maybe if you were fighting against the army, they might come in groups of two swordsmen and one archer.
The obvious solution is to just create a separate troop for this case, but what if I always want archers to appear with swordsmen? For example, even if 3 swordsmen were generated, an archer would still tag along because it just likes to tag along. Well, unless there are too many swordsmen I guess.

So now we have two cases to consider
1. specifying pre-defined groups, like the default system
2. specifying enemies that should always appear with certain enemies (if possible)

Desired effect for the second case

Assuming max 4 has been specified, and one kitten always appears with slimes with some probability

CODE
1 slime, 1 kitten
2 slime, 1 kitten
3 slime, 1 kitten
4 slime, 0 kitten #no room for kitten!


The way I see it, you may specify a "includes" option that says which enemy (or enemies) should be included.

CODE
<Name=Slime, Weight=100, Max=10, Spots=1, PackSize=2, Include = Kitten>


2:

QUOTE
If the kitten is the first in battle, wolves will not appear
If wolves appear in battle first, then kittens may join them.


If wolves appear in battle first, and when the system chooses kittens, couldn't it just check whether wolves have been selected or not?
But ya, it definitely allows for more flexibility lol I wouldn't focus too much on this

3: For the at least/at most, the functionality is essentially to eliminate redundant entries. The above "1 slime, 2 slime, 3 slime, ..." example is kind of silly, but there probably isn't a good way to say "I only want 1 to 5 slimes to appear on the map, with no other monsters" without adding in a bunch of "avoid" entries.

4: On top of #3, maybe we can have an extra option like "final", which will forcibly stop the random selection process and just return whatever it chose. So if I really have a map with kittens, wolves, slime, and demon gods, but I wanted to make sure that if I encounter slime, I will only encounter slime, I don't have to put a bunch of avoid entries.

5: list inputs would be useful

CODE
<name=slime, avoid=kitten
<name=slime, avoid=werewolf>


collapses to

CODE
<name=slime, avoid=kitten; werewolf>


Of course, the delimiter would then need to be selected carefully since people might want to put semi-colons in their monster names. Maybe a pipe | ? lol

6:

Also, not sure if this is possible, but can you modify the regex to use string formatting?
To make it more user-friendly, each option should be customizable with its own name (eg: for abbreviation, translation, etc)

CODE
module Configs

   #Set name of options
   Max_Enemies = "maxenemies"
   Min_Enemies = "minenemies
   ...
end

class Game_Troop
   ...
   if text.include?(Config::Max_Enemies)
      min_enemies = text.scan(...)[0] #somehow get the Max_Enemies constant in
      ...
   end
end


The other solution is to just use regex for configuration options and then warn users to only edit the specific strings unless they know what they are doing.

This post has been edited by Tsukihime: May 1 2012, 11:52 AM


__________________________
My Scripts
Go to the top of the page
 
+Quote Post
   
Tsukihime
post May 2 2012, 09:56 PM
Post #4


Level 25
Group Icon

Group: Revolutionary
Posts: 560
Type: None
RM Skill: Undisclosed
Rev Points: 25




Just read about string interpolation so that I can allow users to configure the option strings.
#6 is resolved now since I can just use that to build the regex.

This post has been edited by Tsukihime: May 2 2012, 09:56 PM


__________________________
My Scripts
Go to the top of the page
 
+Quote Post
   
Night_Runner
post May 7 2012, 04:47 AM
Post #5


Level 50
Group Icon

Group: +Gold Member
Posts: 1,523
Type: Scripter
RM Skill: Undisclosed




You got very lucky, I have 1 day left of my trial happy.gif

I think a big feature I included was the PackOnly=true, which I may not have talked about.

If you want 1-5 slimes, make sure that you have a PackOnly=true, and all the other enemy types will be removed as soon as the slimes are selected.
And then to get the 1-5 randomly use the MaxEnemies/MinEnemies parameters.


I do agree the delimiters would be useful, and it was something that I though of while I was making the script, but I felt that it would be a fair bit of work.
I've found a simple solution, it only required to change 1 1/2 lines of code, and the current delimiter is the ';' character, but you can't have spaces between the enemy's name and the delimiter.
CODE
#==============================================================================
# ** VX Ace: Night_Runner's Random Enemy Battlers
#------------------------------------------------------------------------------
#  See http://www.rpgrevolution.com/forums/index.php?showtopic=56282
#  for examples and an explanation.
#==============================================================================



#==============================================================================
# ** Game_Troop
#------------------------------------------------------------------------------
#  Modified to have random battlers.
#==============================================================================

class Game_Troop
  #--------------------------------------------------------------------------
  # * Get Troop Objects
  #--------------------------------------------------------------------------
  def troop
    @troops
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  alias nr_encounter_system_setup  setup  unless $@
  def setup(troop_id)
    setup_enemies(troop_id)
    nr_encounter_system_setup(troop_id)
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  def setup_enemies(troop_id)
    # Get the enemy troop
    @troops = $data_troops[troop_id]
    # Check if the first command on the first page is a comment
    if @troops.pages[0].list[0].code == 108
      # Get the text in the comment
      text = ""
      index = 0
      begin
        text += @troops.pages[0].list[index].parameters[0].downcase
        index += 1
      end while [108, 408].include? @troops.pages[0].list[index].code
      # Get the number of enemies (do nothing if not stated)
      if text.include?('maxenemies')
        min_enemies = text.scan(/minenemies\s*\=\s*((\d)+)/)[0]
        max_enemies = text.scan(/maxenemies\s*=\s*((\d)+)/)[0][0].to_i
        if min_enemies == nil
          min_enemies = 1
        else
          min_enemies = min_enemies[0].to_i
        end
        num_enemies = Random.rand(max_enemies + 1 - min_enemies) + min_enemies
      elsif text.include?('numenemies')
        num_enemies = text.scan(/numenemies\s*=\s*((\d)+)/)[0][0].to_i
      else
        return
      end
      p "Maximum number of enemies: #{num_enemies}"
      # Parse the enemies
      enemies = []
      total_weighting = 0
      for line in text.split(/<([^<>]*)>/).map { |s| s + ',' }
        next if not line.include?('name')
        name = line.scan(/name\s*=\s*([^,]*),/)[0][0]
        weight = line.scan(/weight\s*=\s*([^,]*),/)[0]
        weight = weight == nil ? 100 : weight[0].to_i
        total_weighting += weight
        max = line.scan(/max\s*=\s*([^,]*),/)[0]
        max = max == nil ? num_enemies : max[0].to_i
        spots = line.scan(/spots\s*=\s*([^,]*),/)[0]
        spots = spots == nil ? 1 : spots[0].to_i
        packsize = line.scan(/packsize\s*=\s*([^,]*),/)[0]
        packsize = packsize == nil ? 1 : packsize[0].to_i
        avoid = line.scan(/avoid\s*=\s*([^,]*),/)[0]
        avoid = avoid == nil ? [] : avoid[0].split(";")
        packonly = line.scan(/packonly\s*=\s*([^,]*),/)[0]
        packonly = packonly == nil ? false : packonly[0] == "true"
        enemy = {
          :name     => name,
          :weight   => weight,
          :max      => max,
          :spots    => spots,
          :packsize => packsize,
          :avoid    => avoid,
          :packonly => packonly
        }
        enemies << enemy
      end
      # Generate the enemies
      members = []
      members_size = 0
      loops_since_member_added = 0
      max_wait_loops = 10
      begin
        loops_since_member_added += 1
        enemy_weight = Random.rand(total_weighting + 1) + 1
        for enemy in enemies
          enemy_weight -= enemy[:weight]
          if (enemy_weight <= 0) and (enemy[:weight] > 0)
            num_this_enemy = members.count(enemy)
            next if num_this_enemy > enemy[:max]
            en_spots = enemy[:spots] * enemy[:packsize]
            next if members_size + en_spots > num_enemies
            next if not (members & enemy[:avoid]).empty?
            has_other_members = members.select { |en| enemy[:name] != en }
            next if (enemy[:packonly]) and not (has_other_members.empty?)
            # If this is a pack enemy, remove the rest from the enemy group
            if enemy[:packonly]
              for e in enemies
                if e != enemy
                  total_weighting -= e[:weight]
                  e[:weight] = 0
                end
              end
            end
            loops_since_member_added = 0
            members += [enemy[:name]] * enemy[:packsize]
            members_size += en_spots
            if members.count(enemy[:name]) >= enemy[:max]
              total_weighting -= enemy[:weight]
              enemy[:weight] = 0
            end
          end
        end
      end while (members_size < num_enemies) and
                (loops_since_member_added < max_wait_loops)
      # Create the troops array
      @troops.members = []
      for index in 0...members.size
        enemy = members[index]
        enemy_ID = $data_enemies.select { |en|
          next if en.nil?; enemy == en.name.downcase }[0].id
        troop_member = RPG::Troop::Member.new
        troop_member.enemy_id = enemy_ID
        enemy_width = 128
        enemy_height = 96
        max_across = Graphics.width / enemy_width
        mid = (Graphics.width - [members.size, max_across].min * enemy_width) /2
        troop_member.x = mid + (index % max_across) * enemy_width
        troop_member.y = 280 - (index / max_across) * enemy_height
        @troops.members << troop_member
      end
    end
  end
end



#==============================================================================
# ** End of Script.
#==============================================================================


If you want to include the ';' as a customisable option in your config, feel the ';' is designated on line 78, and the other change was line 107, and just include those in your modular version happy.gif.



I've also implemented your recommendation for a finalenemy tag, which has lines inserted for lines 81, 82, 91, 129, 130, 131 and tweaks on line 90 to include a comma at the end of packonly.
CODE
#==============================================================================
# ** VX Ace: Night_Runner's Random Enemy Battlers
#------------------------------------------------------------------------------
#  See http://www.rpgrevolution.com/forums/index.php?showtopic=56282
#  for examples and an explanation.
#==============================================================================



#==============================================================================
# ** Game_Troop
#------------------------------------------------------------------------------
#  Modified to have random battlers.
#==============================================================================

class Game_Troop
  #--------------------------------------------------------------------------
  # * Get Troop Objects
  #--------------------------------------------------------------------------
  def troop
    @troops
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  alias nr_encounter_system_setup  setup  unless $@
  def setup(troop_id)
    setup_enemies(troop_id)
    nr_encounter_system_setup(troop_id)
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  def setup_enemies(troop_id)
    # Get the enemy troop
    @troops = $data_troops[troop_id]
    # Check if the first command on the first page is a comment
    if @troops.pages[0].list[0].code == 108
      # Get the text in the comment
      text = ""
      index = 0
      begin
        text += @troops.pages[0].list[index].parameters[0].downcase
        index += 1
      end while [108, 408].include? @troops.pages[0].list[index].code
      # Get the number of enemies (do nothing if not stated)
      if text.include?('maxenemies')
        min_enemies = text.scan(/minenemies\s*\=\s*((\d)+)/)[0]
        max_enemies = text.scan(/maxenemies\s*=\s*((\d)+)/)[0][0].to_i
        if min_enemies == nil
          min_enemies = 1
        else
          min_enemies = min_enemies[0].to_i
        end
        num_enemies = Random.rand(max_enemies + 1 - min_enemies) + min_enemies
      elsif text.include?('numenemies')
        num_enemies = text.scan(/numenemies\s*=\s*((\d)+)/)[0][0].to_i
      else
        return
      end
      p "Maximum number of enemies: #{num_enemies}"
      # Parse the enemies
      enemies = []
      total_weighting = 0
      for line in text.split(/<([^<>]*)>/).map { |s| s + ',' }
        next if not line.include?('name')
        name = line.scan(/name\s*=\s*([^,]*),/)[0][0]
        weight = line.scan(/weight\s*=\s*([^,]*),/)[0]
        weight = weight == nil ? 100 : weight[0].to_i
        total_weighting += weight
        max = line.scan(/max\s*=\s*([^,]*),/)[0]
        max = max == nil ? num_enemies : max[0].to_i
        spots = line.scan(/spots\s*=\s*([^,]*),/)[0]
        spots = spots == nil ? 1 : spots[0].to_i
        packsize = line.scan(/packsize\s*=\s*([^,]*),/)[0]
        packsize = packsize == nil ? 1 : packsize[0].to_i
        avoid = line.scan(/avoid\s*=\s*([^,]*),/)[0]
        avoid = avoid == nil ? [] : avoid[0].split(";")
        packonly = line.scan(/packonly\s*=\s*([^,]*),/)[0]
        packonly = packonly == nil ? false : packonly[0] == "true"
        finalenemy = line.scan(/finalenemy\s*=\s*([^,]*),/)[0]
        finalenemy = finalenemy == nil ? false : final_enemy[0] == "true"
        enemy = {
          :name       => name,
          :weight     => weight,
          :max        => max,
          :spots      => spots,
          :packsize   => packsize,
          :avoid      => avoid,
          :packonly   => packonly,
          :finalenemy => finalenemy,
        }
        enemies << enemy
      end
      # Generate the enemies
      members = []
      members_size = 0
      loops_since_member_added = 0
      max_wait_loops = 10
      begin
        loops_since_member_added += 1
        enemy_weight = Random.rand(total_weighting + 1) + 1
        for enemy in enemies
          enemy_weight -= enemy[:weight]
          if (enemy_weight <= 0) and (enemy[:weight] > 0)
            num_this_enemy = members.count(enemy)
            next if num_this_enemy > enemy[:max]
            en_spots = enemy[:spots] * enemy[:packsize]
            next if members_size + en_spots > num_enemies
            next if not (members & enemy[:avoid]).empty?
            has_other_members = members.select { |en| enemy[:name] != en }
            next if (enemy[:packonly]) and not (has_other_members.empty?)
            # If this is a pack enemy, remove the rest from the enemy group
            if enemy[:packonly]
              for e in enemies
                if e != enemy
                  total_weighting -= e[:weight]
                  e[:weight] = 0
                end
              end
            end
            loops_since_member_added = 0
            members += [enemy[:name]] * enemy[:packsize]
            members_size += en_spots
            if members.count(enemy[:name]) >= enemy[:max]
              total_weighting -= enemy[:weight]
              enemy[:weight] = 0
            end
            if enemy[:finalenemy] == true
              loops_since_member_added = max_wait_loops
            end
          end
        end
      end while (members_size < num_enemies) and
                (loops_since_member_added < max_wait_loops)
      # Create the troops array
      @troops.members = []
      for index in 0...members.size
        enemy = members[index]
        enemy_ID = $data_enemies.select { |en|
          next if en.nil?; enemy == en.name.downcase }[0].id
        troop_member = RPG::Troop::Member.new
        troop_member.enemy_id = enemy_ID
        enemy_width = 128
        enemy_height = 96
        max_across = Graphics.width / enemy_width
        mid = (Graphics.width - [members.size, max_across].min * enemy_width) /2
        troop_member.x = mid + (index % max_across) * enemy_width
        troop_member.y = 280 - (index / max_across) * enemy_height
        @troops.members << troop_member
      end
    end
  end
end



#==============================================================================
# ** End of Script.
#==============================================================================




Okay, I've just done a massive overhaul, and it's more modular now smile.gif
CODE
#==============================================================================
# ** VX Ace: Night_Runner's Random Enemy Battlers
#------------------------------------------------------------------------------
#  See http://www.rpgrevolution.com/forums/index.php?showtopic=56282
#  for examples and an explanation.
#==============================================================================



module NR_EncounterSystem_Config
  DefaultWeight     = 100
  DefaultMax        = 100
  DefaultSpots      = 1
  DefaultPackOnly   = false
  DefaultPackSize   = 1
  DefaultAvoid      = []
  DefaultFinalEnemy = false
  DefaultInclude    = []
  
  MaxEnemiesText    = 'maxenemies'
  MaxEnemiesRegex   = /minenemies\s*\=\s*((\d)+)/
  MinEnemiesRegex   = /maxenemies\s*=\s*((\d)+)/
  NumEnemiesText    = 'numenemies'
  NumEnemiesRegex   = /numenemies\s*=\s*((\d)+)/
  
  NameText          = 'name'
  NameRegex         = /name\s*=\s*([^,]*),/
  WeightRegex       = /weight\s*=\s*([^,]*),/
  MaxRegex          = /max\s*=\s*([^,]*),/
  SpotsRegex        = /spots\s*=\s*([^,]*),/
  PackSizeRegex     = /packsize\s*=\s*([^,]*),/
  AvoidRegex        = /avoid\s*=\s*([^,]*),/
  PackOnlyRegex     = /packonly\s*=\s*([^,]*),/
  FinalRegex        = /finalenemy\s*=\s*([^,]*),/
  IncludeRegex      = /include\s*=\s*([^,]*),/
  
  Delimiter = ';'
end




#==============================================================================
# ** Game_Troop
#------------------------------------------------------------------------------
#  Modified to have random battlers.
#==============================================================================

class Game_Troop
  #--------------------------------------------------------------------------
  # * Include Modules
  #--------------------------------------------------------------------------
  include NR_EncounterSystem_Config
  #--------------------------------------------------------------------------
  # * Get Troop Objects
  #--------------------------------------------------------------------------
  def troop
    @troops
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  alias nr_encounter_system_setup  setup  unless $@
  def setup(troop_id)
    setup_enemies(troop_id)
    nr_encounter_system_setup(troop_id)
  end
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  def setup_enemies(troop_id)
    # Get the enemy troop
    @troops = $data_troops[troop_id]
    # Check if the first command on the first page is a comment
    get_num_enemies
    return if @num_enemies <= 0
    p "Maximum number of enemies: #{@num_enemies}"
    # Parse possible enemies
    @enemies = []
    @total_weighting = 0
    parse_possible_enemies
    
    @members = []
    @members_size = 0
    generate_enemies
    
    create_enemy_troop
  end
  #--------------------------------------------------------------------------
  # * Get Number of Enemies
  #--------------------------------------------------------------------------
  def get_num_enemies
    if @troops.pages[0].list[0].code == 108
      # Get the text in the comment
      @text = ""
      index = 0
      begin
        @text += @troops.pages[0].list[index].parameters[0].downcase
        index += 1
      end while [108, 408].include? @troops.pages[0].list[index].code
      # Get the number of enemies (do nothing if not stated)
      if @text.include?(MaxEnemiesText)
        min_enemies = text.scan(MaxEnemiesRegex)[0]
        max_enemies = text.scan(MinEnemiesRegex)[0][0].to_i
        if min_enemies == nil
          min_enemies = 1
        else
          min_enemies = min_enemies[0].to_i
        end
        @num_enemies = Random.rand(max_enemies + 1 - min_enemies) + min_enemies
      elsif @text.include?(NumEnemiesText)
        @num_enemies = @text.scan(NumEnemiesRegex)[0][0].to_i
      else
        @num_enemies = -1
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Parse Possible Enemies
  #--------------------------------------------------------------------------
  def parse_possible_enemies
    # Parse the enemies
    for line in @text.split(/<([^<>]*)>/).map { |s| s + ',' }
      next if not line.include?(NameText)
      name = line.scan(NameRegex)[0][0]
      weight = line.scan(WeightRegex)[0]
      weight = weight == nil ? DefaultWeight : weight[0].to_i
      @total_weighting += weight
      max = line.scan(MaxRegex)[0]
      max = max == nil ? DefaultMax : max[0].to_i
      spots = line.scan(SpotsRegex)[0]
      spots = spots == nil ? DefaultSpots : spots[0].to_i
      packsize = line.scan(PackSizeRegex)[0]
      packsize = packsize == nil ? DefaultPackSize : packsize[0].to_i
      avoid = line.scan(AvoidRegex)[0]
      avoid = avoid == nil ? DefaultAvoid : avoid[0].split(Delimiter)
      packonly = line.scan(PackOnlyRegex)[0]
      packonly = packonly == nil ? DefaultPackOnly : packonly[0] == "true"
      final = line.scan(FinalRegex)[0]
      finalenemy = final == nil ? DefaultFinalEnemy : final[0] == "true"
      include = line.scan(IncludeRegex)[0]
      include = include == nil ? DefaultInclude : include[0].split(Delimiter)
      enemy = {
        :name       => name,
        :weight     => weight,
        :max        => max,
        :spots      => spots,
        :packsize   => packsize,
        :avoid      => avoid,
        :packonly   => packonly,
        :finalenemy => finalenemy,
        :include    => include,
      }
      @enemies << enemy
    end
  end
  #--------------------------------------------------------------------------
  # * Generate Enemies Array
  #--------------------------------------------------------------------------
  def generate_enemies
    # Generate the enemies
    @max_wait_loops = 10
    loops_since_member_added = 0
    begin
      loops_since_member_added += 1
      enemy_weight = Random.rand(@total_weighting + 1) + 1
      for enemy in @enemies
        enemy_weight -= enemy[:weight]
        if (enemy_weight <= 0) and (can_add_enemy?(enemy))
          # If this is a pack enemy, remove the rest from the enemy group
          if enemy[:packonly]
            for e in @enemies
              if e != enemy
                @total_weighting -= e[:weight]
                e[:weight] = -1
              end
            end
          end
          # Add the enemy to the party
          loops_since_member_added = 0
          add_enemy(enemy)
          # Add the enemy's allies
          for ally in enemy[:include]
            enemy = @enemies.find { |en| en[:name] == ally }
            add_enemy(enemy)
          end
          if enemy[:finalenemy] == true
            loops_since_member_added = @max_wait_loops
          end
        end
      end
    end while (@members_size < @num_enemies) and
              (loops_since_member_added < @max_wait_loops)
  end
  #--------------------------------------------------------------------------
  # * Can Add Enemy?
  #--------------------------------------------------------------------------
  def can_add_enemy?(enemy)
    # Skip is appropriate
    return false if enemy[:weight] < 0
    # Get the number of enemy spots for this enemy
    en_spots = enemy[:spots] * enemy[:packsize]
    # Skip if this will make too many enemies in the party
    return false if @members_size + en_spots > @num_enemies
    # Check if this will make too many of this enemy
    num_this_enemy = @members.count(enemy)
    return false if num_this_enemy + en_spots > enemy[:max]
    # Skip this enemy if it's avoid is in the members list
    return false if not (@members & enemy[:avoid]).empty?
    # Skip if packonly and other enemies are in the array
    has_other_members = @members.select { |en| enemy[:name] != en }
    return false if (enemy[:packonly]) and not (has_other_members.empty?)
    # Otherwise, it can be part of the party.
    return true
  end
  #--------------------------------------------------------------------------
  # * Add Enemy
  #--------------------------------------------------------------------------
  def add_enemy(enemy)
    @members += [enemy[:name]] * enemy[:packsize]
    @members_size += enemy[:spots] * enemy[:packsize]
    if @members.count(enemy[:name]) >= enemy[:max]
      @total_weighting -= enemy[:weight]
      enemy[:weight] = -1
    end
  end
  #--------------------------------------------------------------------------
  # * Create Enemy Troop
  #--------------------------------------------------------------------------
  def create_enemy_troop
    # Create the troops array
    @troops.members = []
    for index in 0...@members.size
      enemy = @members[index]
      enemy_ID = $data_enemies.select { |en|
        next if en.nil?; enemy == en.name.downcase }[0].id
      troop_member = RPG::Troop::Member.new
      troop_member.enemy_id = enemy_ID
      @troops.members << troop_member
    end
    reorganise_troops_positions
  end
  #--------------------------------------------------------------------------
  # * Reorganise Troops Positions
  #--------------------------------------------------------------------------
  def reorganise_troops_positions
    num_members = @troops.members.size
    for index in 0...num_members
      troop_member = @troops.members[index]
      enemy_width = 128
      enemy_height = 96
      max_across = Graphics.width / enemy_width
      left = (Graphics.width - [num_members, max_across].min * enemy_width) / 2
      troop_member.x = left + (index % max_across) * enemy_width
      troop_member.y = 280 - (index / max_across) * enemy_height
    end
  end
end



#==============================================================================
# ** End of Script.
#==============================================================================

And that allows the enemies to have allies using the Include command (e.g. Include = Bat;Wasp )
Just as a heads up, all of the can_add_enemy? checks (such as packonly, avoid, maximum number of enemies) are ignored when the allies are added to the enemy troop.
This is so you can have packonly monsters, and have their allies as well (e.g. you may have wolves as packonly, but you would probably want their includes= friends to be in battle with them).


__________________________
K.I.S.S.
Want help with your scripting problems? Upload a demo! Or at the very least; provide links to the scripts in question.

Most important guide ever: Newbie's Guide to Switches
Go to the top of the page
 
+Quote Post
   
Tsukihime
post May 7 2012, 06:58 AM
Post #6


Level 25
Group Icon

Group: Revolutionary
Posts: 560
Type: None
RM Skill: Undisclosed
Rev Points: 25




ah, so that's what packOnly was about lol I was wondering why those wolves in the sample just kept showing on their own.

There seems to be some statistical issues but that can probably be fixed if I adjust the calculations carefully.
I'll fix some logic errors, such as when spots > max, monster won' be added.

CODE
num_this_enemy = @members.count(enemy)
    return false if num_this_enemy + en_spots > enemy[:max]


I've fixed it to only return false when num_this_enemy > enemy[:max] since "spots" tracks how many troop slots it takes up.
Don't know if I should change the No Enemies behavior though since that occurs when it can't pick any of them lol

I'm also adding an extra option that will allow you to bias the number of enemies that is randomly selected towards a specific value along with a deviation. While this wasn't something I considered, after generating a couple dozens troops it probably isn't good if half the time you're drawing large numbers of enemies lol

But other than those, I think it captures the idea really well.

Here's the new config with string interpolation as mentioned

CODE
module NR_EncounterSystem_Config
  DefaultWeight     = 100
  DefaultMax        = 100
  DefaultSpots      = 1
  DefaultPackOnly   = false
  DefaultPackSize   = 1
  DefaultAvoid      = []
  DefaultFinalEnemy = false
  DefaultInclude    = []
  
  MaxEnemiesText    = 'maxenemies'
  MinEnemiesText    = 'minenemies'
  NumEnemiesText    = 'numenemies'
  NameText          = 'nom'
  WeightText        = 'weight'
  MaxText           = 'max'
  SpotsText         = 'spots'
  PackSizeText      = 'packsize'
  AvoidText         = 'avoid'
  PackOnlyText      = 'packonly'
  FinalText         = 'final'
  IncludeText       = 'include'
  
  Delimiter = ';'
end



module NR_EncounterSystem_Config
  MaxEnemiesRegex   = /#{MinEnemiesText}\s*\=\s*((\d)+)/
  MinEnemiesRegex   = /#{MaxEnemiesText}\s*=\s*((\d)+)/
  NumEnemiesRegex   = /#{NumEnemiesText}\s*=\s*((\d)+)/
  NameRegex         = /#{NameText}\s*=\s*([^,]*),/
  WeightRegex       = /#{WeightText}\s*=\s*([^,]*),/
  MaxRegex          = /#{MaxText}\s*=\s*([^,]*),/
  SpotsRegex        = /#{SpotsText}\s*=\s*([^,]*),/
  PackSizeRegex     = /#{PackSizeText}\s*=\s*([^,]*),/
  AvoidRegex        = /#{AvoidText}\s*=\s*([^,]*),/
  PackOnlyRegex     = /#{PackOnlyText}\s*(=\s*([^,]*)),/
  FinalRegex        = /#{FinalText}\s*,/
  IncludeRegex      = /#{IncludeText}\s*=\s*([^,]*),/
end


This post has been edited by Tsukihime: May 7 2012, 08:37 AM


__________________________
My Scripts
Go to the top of the page
 
+Quote Post
   

Reply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 

Lo-Fi Version Time is now: 22nd May 2013 - 01:53 PM
RPG RPG Revolution is an Privacy Policy and Legal
eXTReMe Tracker