——— — Before you ask! Read! ;) —
You must have 30+ Posts to create a topic here!
— Thanks for reading! — ———
|
|
  |
Encounter system script |
|
|
|
|
Apr 24 2012, 12:31 PM
|
Level 25

Group: Revolutionary
Posts: 565
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 rulesTo 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. Requirements1: 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) Extensions4: 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 
|
|
|
|
|
|
|
|
|
May 1 2012, 05:29 AM
|

Level 50

Group: +Gold Member
Posts: 1,529
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  - 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
- 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
- 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 - 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 - 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 
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
|
|
|
|
|
|
|
|
|
May 1 2012, 11:00 AM
|
Level 25

Group: Revolutionary
Posts: 565
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 
|
|
|
|
|
|
|
|
|
May 2 2012, 09:56 PM
|
Level 25

Group: Revolutionary
Posts: 565
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 
|
|
|
|
|
|
|
|
|
May 7 2012, 04:47 AM
|

Level 50

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

|
You got very lucky, I have 1 day left of my trial  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  . 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  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
|
|
|
|
|
|
|
|
|
May 7 2012, 06:58 AM
|
Level 25

Group: Revolutionary
Posts: 565
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 
|
|
|
|
|
|
|
|
  |
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:
RPG RPG Revolution is an Privacy
Policy and Legal
|
|