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
#==============================================================================
# ** 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
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.