Custom Action AI (Enemy Skill use AI)

0 Members and 1 Guest are viewing this topic.

pokeball TDSOffline
***
Rep:
Level 84
-T D S-
Silver - GIAW 11 (Hard)Silver - Game In A Week VII
Custom Action AI (Enemy Skill use AI)
Version: 1.4 (BETA)
Author: TDS
Date: January 17, 2013

Version History


  • <Version 1.0> 2013.01.17 - Beta Release

Description


This script gives enemies a basic AI that by process of elimination allows them to select targets. By using special tags you can make an enemy only use a healing skill when really needed or a skill which inflicts a state only when necessary.

Features

  • Adds a simple AI to enemies to decide which skill to use.

Instructions

Instructions are on the script with some examples, please note that this script requires some scripting knowledge to use.

Script


Code: [Select]
#==============================================================================
# ** TDS Custom Action AI (Skill Use AI)
#    Ver: 1.4 BETA
#------------------------------------------------------------------------------
#  * Description:
#  This script allows you to set requirements for enemies to use skills. Such
#  as its target not having a certain state or its HP  being above a certain
#  value
#------------------------------------------------------------------------------
#  * Features:
#  Adds a simple AI to enemies to decide which skill to use.
#------------------------------------------------------------------------------
#  * Instructions:
#  To add AI to a skill, put this in the enemy's notebox:
#
#    <Skill_Use_AI: ID>
#     AI_TAGS
#    </skill_use_AI>
#
#    ID = Is the ID of the skill to apply the AI to.
#    AI_TAGS = These are the tags that determine how the AI for the skill will
#              work. A list is below.
#
#  Examples:
#
#  Here are some examples of AI based on certain scenarios. (The default RMVXACE
#  is used for them)
#
#  Scenario:
#    The enemy has the skill "Heal II" (ID 27) and only wants to use it on
#    targets that do not have full HP. It also wants to select whoever has
#    the least HP to heal them.
#
#    Here is how it would translate into tags.
#
#    <Skill_Use_AI: 27>
#      Remove_If: (t.hp_rate * 100) == 100
#      Sort_by: t.hp_rate
#      Select_First
#    </Skill_Use_AI>
#
#
#  Scenario:
#    The enemy has the skill "Poison" (ID 27) and only wants to use it on
#    targets that are not already afflicted with the state Poison (ID 2). It
#    also wants to select whoever has the highest HP.
#   
#    Here is how it would translate into tags.
#
#    <Skill_Use_AI: 35>
#      Remove_If: t.state?(2)
#      Sort_by: t.hp
#      Select_First
#    </Skill_Use_AI>
#
#------------------------------------------------------------------------------
#  * AI Commands List:
#------------------------------------------------------------------------------
#  Condition Shorcuts:
#   Some tags use conditions as part of their process and these are some
#   shorcuts to relevant objects such as targets, user, switches and variables.
#------------------------------------------------------------------------------
#     t = Target within a block of code of targets array.
#     u = Action AI user. (Battler that is processing skill AI)
#     s = Switches. (s[1] would be the same as Switch ID 1 or $game_switches[1])
#     v = Variables. (v[1] would be the same as Variable ID 1 or $game_variables[1])
#
#     Examples:
#       t.hp (This would get you the HP of a target)
#       s[1] == true (This would be true if Switch ID 1 is ON)
#       v[10] < 100 (This would be true if the value of Variable ID 1 is less than 100)
#
#------------------------------------------------------------------------------
#  Requirements:
#    Requirement tags are required parameters from the user in order to use the
#    skill.
#------------------------------------------------------------------------------
#    Required_Turn: Turn_A, Turn_B
#    Req_HP/MP: Min, Max
#      ^ HP/MP Should be replaced with either.
#      ^ Min and Max are the ranges the HP or MP must be between. (As Float values)
#      ^ Example: Req_HP: 0.5, 1.0
#
#    Required_State: State_ID
#    Required_Party_Level: Level
#    Required_Switch: Switch_ID, State
#      ^ State is the state of the switch to check (ON or OFF)
#
#    Require: Condition
#      ^ Require that Condition code is true.
#
#    TRequire(_ALL):
#      ^ Targets must meet required conditions.
#      ^ If _All is part of the tag then all targets must meet the condition.
#      ^ Example: TRequire: t.hp > 100 or TRequire_All: t.hp < 100
#------------------------------------------------------------------------------
#  Remove:
#    Remove tags remove targets if the tag requirements are met.
#------------------------------------------------------------------------------
#    TRemove_If: Condition
#      ^ Remove target if conditions code is true.
#      ^ Example: t.hp > 100
#
#------------------------------------------------------------------------------
#   Select:
#     Select tags allow you select a specific target if necessary.
#------------------------------------------------------------------------------
#    Sort_By: Condition
#      ^ Sorts targets by condition code. (Must be a comparable value)
#      ^ This tag is meant to be used with others like "Select_First/Last"
#
#    Select_(First/Last)(: Min)
#     ^ First/Last Should be replaced with either for selection.
#     ^ Min is the minimun amount of random targets to select. (Optional)
#     ^ Example: Select_First or Select_First: 2
#
#    Select_Default
#      ^ Selects amounts of targets based on the scope of the skill.
#
#    Select_If: Condition
#      ^ Select Targets if condition code is true.
#      ^ Example: t.id == 20
#
#    Select_Random(: Min)
#      ^ Min is the minimun amount of random targets to select. (Optional)
#      ^ If no value is used then the default 1 will be used.
#      ^ Example: Select_Random or Select_Random: 3
#
#    Select_Targeting_User
#      ^ Selects targets whose actions are targeting the AI user.
#
#------------------------------------------------------------------------------
#  * Notes:
#  None.
#------------------------------------------------------------------------------
# WARNING:
#
# Do not release, distribute or change my work without my expressed written
# consent, doing so violates the terms of use of this work.
#
# If you really want to share my work please just post a link to the original
# site.
#
# * Not Knowing English or understanding these terms will not excuse you in any
#   way from the consequenses.
#==============================================================================
# * Import to Global Hash *
#==============================================================================
($imported ||= {})[:TDS_Custom_Action_AI] = true


#==============================================================================
# ** Scene_Battle
#------------------------------------------------------------------------------
#  This class performs battle screen processing.
#==============================================================================

class Scene_Battle < Scene_Base
  #--------------------------------------------------------------------------
  # * Alias Listing
  #--------------------------------------------------------------------------
  alias tds_custom_action_ai_scene_battle_execute_action       execute_action   
  #--------------------------------------------------------------------------
  # * Execute Battle Actions
  #--------------------------------------------------------------------------
  def execute_action
    # Evaluate Enemy Battle AI
    evaluate_custom_action_ai(@subject) if @subject.enemy?
    # Run Original Method
    tds_custom_action_ai_scene_battle_execute_action
  end
  #--------------------------------------------------------------------------
  # * Evaluate Custom Action Battle AI
  #--------------------------------------------------------------------------
  def evaluate_custom_action_ai(battler)
    # Return if Battler is nil or Subject is not an enemy
    return if battler.nil?
    # Create AI Targets
    @ai_targets = []
    # Selected Skill
    skill = nil
    # Get All Battler Custom Skill AI
    ai_actions = battler.all_custom_skill_ai   
    # Go Through AI Actions
    ai_actions.each {|a|
      # Get Skill Object
      skill = $data_skills[a.at(0)]   
      # Next if Skill is not usable
      next if !battler.usable?(skill)
      # Process Battle Action AI
      break if process_battle_action_ai(battler, skill, a.at(1))       
    }   
    # Set Current Action to Selected Skill
    battler.current_action.set_skill(skill.id) if !skill.nil?     
    # Set Current Action Custom Action AI Targets
    battler.current_action.custom_action_ai_targets = @ai_targets   
  end
  #--------------------------------------------------------------------------
  # * Conver Match String value to proper Numerical
  #--------------------------------------------------------------------------
  def convert_match_value(string)
    # Return if string is not a number
    return nil if !(string =~ /^\d+/)
    # Convert String to float if it contains a period
    return string.to_f if string =~ /\./
    # Convert String to integer
    return string.to_i
  end
  #--------------------------------------------------------------------------
  # * Process Battle Action AI
  #--------------------------------------------------------------------------
  def process_battle_action_ai(battler, skill, ai_list)   
    # Clear AI Targets
    @ai_targets.clear
    # Return if AI List is empty
    return true if ai_list.empty?   
    # Get Targets Avaiable for skill
    @ai_targets = skill.for_opponent? ? battler.opponents_unit.members.dup : battler.friends_unit.members.dup
    # Delete Dead Members if Skill is not for dead friends
    @ai_targets.delete_if {|t| t.dead? and !skill.for_dead_friend?}
    # Go Through AI List
    ai_list.each {|ai|
      # Process Required, Remove and Select Action AI Tags
      process_battle_required_action_ai(battler, skill, ai) if !@ai_targets.empty?
      process_battle_remove_action_ai(battler, skill, ai)   if !@ai_targets.empty?
      process_battle_select_action_ai(battler, skill, ai)   if !@ai_targets.empty?
      # Break if Targets is empty
      break if @ai_targets.empty?     
    }   
    # Return if AI Targets is empty
    return false if @ai_targets.empty?
    # Return true
    return true
  end
  #--------------------------------------------------------------------------
  # * [Required] Process Battle Action AI
  #--------------------------------------------------------------------------
  def process_battle_required_action_ai(battler, skill, ai)   
    # Set Object Shorcuts
    v = $game_variables ; s = $game_switches ; u = battler
    # AI Case
    case ai
    # Require User conditions are met. (Condition)
    when /\bRequire\b: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear AI Targets (Breaking Loop)
      @ai_targets.clear if !eval(m[:condition])   
    # Require Target conditions are met for one or all targets. (Condition)
    when /\bTRequire[_]?(?'con'ALL)?: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear AI Targets (Breaking Loop)
      @ai_targets.clear if m[:con] =~ /ALL/i and !@ai_targets.all? {|t| eval(m[:condition])}     
      @ai_targets.clear if m[:con].nil? and !@ai_targets.any? {|t| eval(m[:condition])}
    # Required Turn to use skill (Turn A, Turn B)
    when /Required_Turn: (?'turn_a'[\d]+), (?'turn_b'[\d]+)/i
      # Get Match Information
      m = Regexp.last_match
      # Get Turns and A & B Parameters
      n = $game_troop.turn_count ; a = m[:turn_a].to_i ; b = m[:turn_b].to_i
      # Clear AI Targets (Breaking Loop)
      @ai_targets.clear if (b == 0 and n != a) or (b > 0 and (n < 1 or n < a or n % b != a % b))
    # Required HP/MP to use skill (Min, Max) rates
    when /Required_(?'type'HP|MP): (?'min'[\.\d]+), (?'max'[\.\d]+)/i
      # Get Match Information
      m = Regexp.last_match
      # Get Converted String Values
      min = convert_match_value(m[:min]) ; max = convert_match_value(m[:max])
      # Clear All AI Targets (Breaking the loop)
      @ai_targets.clear if m[:type] =~ /HP/i and !battler.hp_rate.between?(min, max)
      @ai_targets.clear if m[:type] =~ /MP/i and !battler.mp_rate.between?(min, max)       
    # Required State to have to use the skill (State ID)
    when /Required_State: (?'id'[\d]+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear All AI Targets (Breaking the loop)
      @ai_targets.clear if !battler.state?(m[:id].to_i)       
    # Minimun Party Level Required to use the skill (Level)
    when /Required_Party_Level: (?'level'[\d]+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear All AI Targets (Breaking the loop)
      @ai_targets.clear if $game_party.highest_level < m[:level].to_i
    # Required Active Switch to use the skill (Switch ID, State)
    when /Required_Switch: (?'id'[\d]+), (?'state'[ON|OFF]+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear All AI Targets (Breaking the loop)
      @ai_targets.clear if m[:state] =~ /ON/i  and $game_switches[m[:id].to_i] == false
      @ai_targets.clear if m[:state] =~ /OFF/i and $game_switches[m[:id].to_i] == true
    # Require Variable conditions are met. (Condition)
    when /Required_Variable: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Clear AI Targets (Breaking Loop)
      @ai_targets.clear if !eval(m[:condition])     
    # Require Code conditions are met. (Condition)       
    when /Required_Code: (?'condition'.+)/
      # Get Match Information
      m = Regexp.last_match     
      # Clear AI Targets (Breaking Loop)
      @ai_targets.clear if !eval(m[:condition])     
    end     
  end
  #--------------------------------------------------------------------------
  # * [Remove] Process Battle Action AI
  #--------------------------------------------------------------------------
  def process_battle_remove_action_ai(battler, skill, ai) 
    # Set Object Shorcuts
    v = $game_variables ; s = $game_switches ; u = battler   
    # AI Case
    case ai
    # Remove Dead Targets
    when /Remove_Dead/i ; @ai_targets.delete_if {|t| t.dead?}
    # Remove Alive Targets
    when /Remove_Alive/i ; @ai_targets.delete_if {|t| t.alive?}       
    # Remove Target if conditions are met. (Condition)
    when /Remove_If: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Process Target Removal Condition
      @ai_targets.delete_if {|t| eval(m[:condition])}
    end
  end 
  #--------------------------------------------------------------------------
  # * [Select] Process Battle Action AI
  #--------------------------------------------------------------------------
  def process_battle_select_action_ai(battler, skill, ai)
    # Set Object Shorcuts
    v = $game_variables ; s = $game_switches ; u = battler   
    # AI Case
    case ai
    # Sort Targets Array by condition
    when /Sort_by: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Sort Targets Array
      @ai_targets.sort_by! {|t| eval(m[:condition])}
    # Select Targets by Default Targetting Scope (Skill Range)
    when /Select_Default/i
      # Select User As Target if skill is for user
      @ai_targets = [battler] if skill.for_user?
      # If Skill is for one target (Select one randomly)
      @ai_targets = [@ai_targets.sample] if skill.for_one?
      # Select Random Targets
      @ai_targets = [@ai_targets.sample(skill.number_of_targets)].flatten if skill.for_random?
    # Select First or Last Targets in the Targets Array (Min Amount to Select)
    when /Select_(?'type'First|Last)[:]?(?'min'\s\d+)?/i
      # Get Match Information
      m = Regexp.last_match
      # Get First Targets from Target Array
      @ai_targets = @ai_targets.take([m[:min].to_i, 1].max) if m[:type] =~ /First/i
      # Get Last Targets from Target Array
      @ai_targets = @ai_targets.reverse.take([m[:min].to_i, 1].max) if m[:type] =~ /Last/i
    # Select Target if conditions are met. (Condition)
    when /Select_If: (?'condition'.+)/i
      # Get Match Information
      m = Regexp.last_match
      # Process Target Selection Condition
      @ai_targets.select! {|t| eval(m[:condition])}
    # Select Random Target (Min Random value)
    when /Select_Random[:]?(?'min'\s\d+)?/i
      # Get Match Information
      m = Regexp.last_match
      # Get Random AI Targets
      @ai_targets = [@ai_targets.sample([m[:min].to_i, 1].max)].flatten
    # Select Targets that are targeting the user with their action
    when /Select_Targeting_User/i
      # Get Match Information
      m = Regexp.last_match
      # Get Random AI Targets     
      @ai_targets.select! {|t| t.targeting_battler?(battler)}   
    end
  end
end


#==============================================================================
# ** Game_Battler
#------------------------------------------------------------------------------
#  A battler class with methods for sprites and actions added. This class
# is used as a super class of the Game_Actor class and Game_Enemy class.
#==============================================================================

class Game_Battler < Game_BattlerBase
  #--------------------------------------------------------------------------
  # * Get Battler Note Text
  #--------------------------------------------------------------------------
  def note ; return actor.note if actor? ; return enemy.note if enemy? ; '' end   
  #--------------------------------------------------------------------------
  # * Get Battler ID
  #--------------------------------------------------------------------------
  def id ; actor.id if actor? ; enemy.enemy_id if enemy? end   
  #--------------------------------------------------------------------------
  # * Get Battler Battle Actions
  #--------------------------------------------------------------------------
  def battle_actions
    return enemy.actions if enemy?
    []
  end 
  #--------------------------------------------------------------------------
  # * Get Custom Skill AI Array
  #     skill_id : ID of skill to check
  #--------------------------------------------------------------------------
  def custom_skill_ai(skill_id)
    # Match text to Get Custom AI for Skill Use
    note[/\<Skill_Use_AI: [#{skill_id}]+\>(.+?)<\/[^>]+>/im]
    # Return Match Stripped and Separated into individual lines
    return $1.nil? ? [] : $1.strip.split(/\r\n/)
  end 
  #--------------------------------------------------------------------------
  # * Get All Custom Skill AI Array
  #--------------------------------------------------------------------------
  def all_custom_skill_ai
    # Create Actions Array
    actions = []
    # Scan Notes for Skill Use AI Actions
    note.scan(/\<Skill_Use_AI: (\d+)\>(.+?)<\/[^>]+>/im) {|id, tags|
      # Strip and Remove Empty lines from tags
      tags = tags.strip.split(/\r\n/).delete_if {|s| s.empty?}
      # Add Skill ID and Tags to actions array
      actions << [id.to_i, tags]
    }   
    # Return Actions Array
    return actions
  end
  #--------------------------------------------------------------------------
  # * Determine if Targeting Battler
  #--------------------------------------------------------------------------
  def targeting_battler?(battler)
    # Return false if Current Action is nil
    return false if current_action.nil?
    # Check if Targeting battler
    return current_action.make_targets.compact.include?(battler)
  end 
end


#==============================================================================
# ** Game_Action
#------------------------------------------------------------------------------
#  This class handles battle actions. This class is used within the
# Game_Battler class.
#==============================================================================

class Game_Action
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :custom_action_ai_targets          # Custom AI Targets Array 
  #--------------------------------------------------------------------------
  # * Alias Listings
  #--------------------------------------------------------------------------
  alias tds_custom_action_ai_game_action_clear                   clear
  alias tds_custom_action_ai_game_action_make_targets            make_targets
  #--------------------------------------------------------------------------
  # * Clear
  #--------------------------------------------------------------------------
  def clear(*args, &block)
    # Run Original Method
    tds_custom_action_ai_game_action_clear(*args, &block)
    # Clear Custom Action AI Targets Array
    @custom_action_ai_targets = []
  end
  #--------------------------------------------------------------------------
  # * Create Target Array
  #--------------------------------------------------------------------------
  def make_targets(*args, &block)
    # Delete Custom Targets if dead and item is not for dead friend
    @custom_action_ai_targets.delete_if {|t| t.dead? and !item.for_dead_friend?}
    # Return Custom Action AI Targets If Custom Action AI Targets Array is not empty
    return @custom_action_ai_targets if !@custom_action_ai_targets.empty?
    # Run Original Method
    tds_custom_action_ai_game_action_make_targets(*args, &block)
  end
end

Credit


  • TDS

Support


On this topic.

Known Compatibility Issues

In theory it should work with any battle system that uses "execute_action" method.


Author's Notes


For now it's still in beta and requires some scripting knowledge to fully use. I will add simplified versions of popular tags if there is enough interest in the script and people suggest them.

The reason I have not added a lot of tags is because I am not making a game which would require them. So instead of waiting months and slowly working on it, I thought people could use this script in their game and make suggestions on the tags they use the most.

It should also be very easy for scripters to add their own tags into the selection process structure through aliasing methods.

Restrictions

Only for use in non-commercial games.
« Last Edit: January 20, 2013, 05:21:44 AM by TDS »

*
Rep: +0/-0Level 54
RMRK Junior
Looks like something I was just thinking about/looking for actually. One tag suggestion:

Prioritize targets that are targeting the enemy. For example if actor A targets monster A with a attack/spell while actor B targets a different monster, and monster A's ability is tagged, then he will attack actor A.

I'm also curious if this script keeps Target Rate in mind, the stat that makes it more likely for monsters to target specific actors.

pokeball TDSOffline
***
Rep:
Level 84
-T D S-
Silver - GIAW 11 (Hard)Silver - Game In A Week VII
Something like "Select_If_Targeting_User" that would pick whichever target is currently targeting the user? Also should it take into account skills that target everyone or just single target selection skills?

As for the target rate you should be able to easily sort targets with the current tags, but I think I will add it as a simplified one.

For example:

Code: [Select]
<Skill_Use_AI: ID>
  Sort_by: t.tgr
  Select_First
</Skill_Use_AI>

Edit:

Updated the script and added more documentation for the condition shortcuts, tag commands and some bug fixes. Also added the "Select_Targeting_User" which selects targets whose actions are targeting the AI user.

Here is an example that makes makes the enemy only attack whoever is targeting it.

Code: [Select]
<Skill_Use_AI: 1>
  Select_Targeting_User
  Select_Default
</Skill_Use_AI>

(Please note that the AI will use the default battler AI if no targets are found. This means that if you left "Attack" in the Action Patterns of the enemy it will still attack)

With this other example the enemy will wait if nothing is targeting it, even if it has a default AI.

Code: [Select]
<Skill_Use_AI: 1>
  Select_Targeting_User
  Select_Default
</Skill_Use_AI>

<Skill_Use_AI: 7>
</Skill_Use_AI>
« Last Edit: January 19, 2013, 04:53:08 AM by TDS »

*
*crack*
Rep:
Level 63
2012 Best Newbie2012 Most Unsung MemberFor frequently finding and reporting spam and spam bots
Definitely sounds like a script that will improve battles. I'm not making a game with Battles in it though, so I don't currently have a use for it. However wanted to show that I am at least watching the topic :)

pokeball jOffline
*
Rep: +0/-0Level 37
RMRK Junior
Hey, sorry for necroposting here. This script is really good, just one thing I don't understand: Required_turn.

Does it work the same way as in RMVXA where turns are evaluated using a formula? (for example, a + b(x)?