Help with creating an "Enemy Substitute" flag.

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
I would like to create a flag that causes an entity to do the 'substitute' thing for an enemy, as opposed to an ally.  (Reasons for this can include things like summons that protect the party, status effects that use baddies (or you!) as cover, etc. etc.)

I have been digging around a little bit in the scripts and I've done a lot of the work so far, but I definitely need help finishing the job.

class Game_Battler < Game_BattlerBase FLAG_ID_EnemySub = 4 endclass Scene_Battle #-------------------------------------------------------------------------- # * Invoke Skill/Item #-------------------------------------------------------------------------- def invoke_item(target, item) if rand < target.item_cnt(@subject, item) invoke_counter_attack(target, item) elsif rand < target.item_mrf(@subject, item) invoke_magic_reflection(target, item) else apply_item_effects(apply_substitute(target, item), item) else apply_item_effects(apply_enemysub(target, item), item) end @subject.last_target_index = target.index end #-------------------------------------------------------------------------- # * Apply EnemySubstitute #-------------------------------------------------------------------------- def apply_enemysub(target, item) if check_enemysub(target, item) enemysub = target.opponents_unit.substitute_battler if enemysub && target != enemysub @log_window.display_substitute(substitute, target) return enemysub end end target end endMy biggest concern right now is the conflict in "Invoke Skill / Item."  Since both flags are checked automatically by the way the game handles their coding, won't it cause conflicts?  I foresee something like both the EnemySub and the Substitute flag-carriers taking the damage, but I don't know what I'd do about that.

Secondly, I don't fully understand what's going on in the substitute code.  I copy-pasted and tweaked the lines as appropriate, but I'm certain that won't work.  (Also, it doesn't.)  I am keying in on the line "if enemysub && target != enemysub" and thinking that's extraneous (that is, that was a line created to prevent substituters from substituting themselves), but I'm not certain.

Additionally, how would I go about telling the game to apply this flag to an actor / enemy / etc.?  The usual 'flag drop-box' doesn't seem to update with the new flag option.

(Before any one tries this script as-is: it is pretty far away from working.)
 
Last edited by a moderator:

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
Well, firstly: you have double else in the invoke_item 's if clause there. That cannot work...

It is easier to alias apply_substitute, like so:  

class Scene_Battle < Scene_Base  alias :enemy_sub_apply_substitute :apply_substitute  def apply_substitute(target, item)    if check_substitute(target, item)      substitute = target.opponents_unit.substitute_battler      if substitute        @log_window.display_substitute(substitute, target)        return substitute end    end    return enemy_sub_apply_substitute(target, item)  endendThis way, we first check if the battler has enemy that has substitute flag first, and failing that, check if there is allied substitute. But with this, we treat the original substitute flag as universal substitute flag, meaning that all states that have substitute will make the enemy benefit from substituting.

If we want to make a specialized substitute enemy flag, we need to check differently. The target.opponents_unit.substitute_battler will return the battlers with the normal substitute flag. So, we need a new method for that.

class Scene_Battle < Scene_Base  alias :enemy_sub_apply_substitute :apply_substitute  def apply_substitute(target, item)    if check_substitute(target, item)      substitute = target.opponents_unit.substitute_enemy      if substitute        @log_window.display_substitute(substitute, target)        return substitute end    end    return enemy_sub_apply_substitute(target, item)  endendSo we now change the substitute_battler for substitute_enemy.

Now, as the substitute_enemy method doesn't yet exist, we need to make substitute_enemy method in Game_Unit (the class of opponents_unit), which will acquire the battler with substitute enemy flag.

class Game_Unit  def substitute_enemy    members.find {|member| member.substitute_enemy? }  endendHere we check for each members if one can become a substitute for his enemies. From here, we need the method substitute_enemy? in either Game_Battler or Game_BattlerBase.

class Game_BattlerBase  def substitute_enemy?    states.each { |state| return true if state.substitute_enemy }    return false  endendHere we check if any of the battler's state has substitute_enemy flag set to true. For this, we need the State class to have substitute_enemy. So, we create it.

class RPG::State < RPG::BaseItem  def substitute_enemy return @substitute_enemy  endendNow for putting the value of @substitute_enemy, we can utilize several means to accomplish that. We can set up an array that contains all state id that will become a substitute enemy state, or we can use the popular notetag method.


So if we want to use notetag, we need to make the code actually read the notetag. So we make some more methods to read them.

class RPG::State < RPG::BaseItem  def load_notetags_substitute_enemy    @substitute_enemy = false    @substitute_enemy = true if self.note =~ /<substitute enemy>/i  end  def substitute_enemy return @substitute_enemy  endendHere we set @substitute_enemy to false by default, and set it to true if the state's note tag contains <substitute enemy>.

We need some more codes to read notetags:

module DataManager  class <<self; alias :load_database_substitute_enemy :load_database; end  def self.load_database    load_database_substitute_enemy    load_notetags_substitute_enemy  end    def self.load_notetags_substitute_enemy    for state in $data_states      next if state.nil?      state.load_notetags_substitute_enemy    end  endendNow, the complete script will look like this:

module DataManager  class <<self; alias :load_database_substitute_enemy :load_database; end  def self.load_database    load_database_substitute_enemy    load_notetags_substitute_enemy  end    def self.load_notetags_substitute_enemy    for state in $data_states      next if state.nil?      state.load_notetags_substitute_enemy    end  endendclass RPG::State < RPG::BaseItem  def load_notetags_substitute_enemy    @substitute_enemy = false    @substitute_enemy = true if self.note =~ /<substitute enemy>/i  end    def substitute_enemy return @substitute_enemy  endendclass Game_BattlerBase  def substitute_enemy?    states.each { |state| return true if state.substitute_enemy }    return false  endendclass Game_Unit  def substitute_enemy    members.find {|member| member.substitute_enemy? }  endendclass Scene_Battle < Scene_Base  alias :enemy_sub_apply_substitute :apply_substitute  def apply_substitute(target, item)    if check_substitute(target, item)      substitute = target.opponents_unit.substitute_enemy      if substitute        @log_window.display_substitute(substitute, target)        return substitute      end    end    return enemy_sub_apply_substitute(target, item)  endend

Now we can write in a state's notetags <substitute enemy>, and that state will become a state that force the affected battlers to substitute for their enemies.

I haven't tested the script myself though.
 
Last edited by a moderator:

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
I finally got around to testing this, and while it does exactly what it says it does, I'd appreciate some minor tweaking on it.

It does indeed do the enemy substitution thing when the actor being attacked has low health.  I'd like to remove that requirement, and have it apply whenever the state is active.  Is that possible?

Also, thanks!  I appear to have been way, way off in how I was doing it.  I'd still be spinning my wheels if you hadn't come along!
 
Last edited by a moderator:

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
It does indeed do the enemy substitution thing when the actor being attacked has low health.  I'd like to remove that requirement, and have it apply whenever the state is active.  Is that possible?
It can easily be done. The part where low health check is performed is in apply_substitute, more precisely in

if check_substitute(target, item)The original check_substitute is this:

def check_substitute(target, item) target.hp < target.mhp / 4 && (!item || !item.certain?) endit compares the attack target's hp with his maxhp, and checks if the item (skill/item) used is not a certain hit.

We can change check_substitute to a new method, or (if it is always valid) just remove the line. If the check is removed though, it will always substitute for any skills, even healing ones (the ones that are usually set as certain hit).

So, just change the line to

if check_substitute_enemy(target, item)and add new method called check_substitute_enemy, under the apply_substitute, still inside Scene_Battle:

Code:
def check_substitute_enemy(target, item)  return !item || !item.certain? # Checks only if item is not certainend
 

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
It seems to be working perfectly!

You are a true scholar and gentleman.  I have only thanks to give you and perhaps the recommendation that you throw it on your blog?
 

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
Right. I suppose I'll post this to my blog after some improvements and proper documentation.
 

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
One minor improvement would be to add in an enemy note tag for it.  It'd simplify things in some situations.
 

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
I've upgraded the script quite a bit. You can tag any actors, classes, enemies, weapons, armors, or states with it now.

The script is now posted on my blog here.
 
Last edited by a moderator:

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
I've discovered a compatibility bug, which is weird because I wasn't experiencing it before but certainly am now.

I recently discovered a bug in Battle Symphony (link unavailable right this second because the site appears down) where the substituter wouldn't take damage while substituting.  I deleted two or three 'unrelated' scripts that I realised I wasn't going to use (Yanfly's doppelgangers is the only one I remember - but that wasn't it, I checked), and all of a sudden the "substitute damage immunity" thing popped up again for the enemy substitutes.   I checked with the 'old' script and the same thing is occurring - enemy goes to attack player, enemy substitute kicks in, "failed / null" appears over it (even with 1 def).

Hmmm.  This is vague and probably not helping.

EDIT:: The quickfix that Yami provided can be found here: https://gist.github.com/suppayami/9220474 .  It's the Battle Symphony version with the fixed substitute, which I just checked and is still working.  I don't know what to say except that something funky is going on with whatever it's doing to the substitute... method? (I think it's the method) that is being changed there.

Actually, that gave me an idea.  I put EnemySub at the very bottom of the scripts and... had mixed success.  Sometimes it's working and sometimes it isn't.  Just ignore this and allow me to investigate further.  I suspect it's a damage formula thing and me overlooking whatever is causing that.

EDIT2:  Now it's not working at all and I am pretty sure I reverted it entirely to what was half working earlier.  I give up; I need some sleep.  (Maybe I imagined / mistook regular substitute / something.)  Definitely not the damage formula.  Attacks with 999 atk vs 1def + 1000% vulnerability should not be nullified.
 
Last edited by a moderator:

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
This is quite...complicated. It's because BattleSymphony (which I also use in my project) assigns hit value before executing items. As it assigns only for normal substitute, the enemy substitute will always null.

Here is my solution. Put my script above Symphony, and add this snippet below Symphony:

class Scene_Battle < Scene_Base def action_skill_effect return unless @subject return unless @subject.alive? return unless @subject.current_action.item targets = @action_targets.uniq #--- substitute --- substitutes = [] targets.each { |target| substitutes += target.opponents_unit.members.collect { |a| a.substitute_enemy? } substitutes += target.friends_unit.members.collect { |a| a.substitute? } #substitutes.push(target.opponents_unit.substitute_enemy) #substitutes.push(target.friends_unit.substitute_battler) } substitutes = substitutes.uniq #--- item = @subject.current_action.item #--- if @action_values.include?("CLEAR") targets.each { |target| target.result.set_calc; target.result.clear } return end #--- if @action_values.include?("COUNTER CHECK") targets.each { |target| target.result.set_counter } return elsif @action_values.include?("REFLECT CHECK") targets.each { |target| target.result.set_reflection } return end #--- array = [] array.push("calc") if @action_values.include?("CALC") array = ["perfect"] if @action_values.include?("PERFECT") @action_values.each {|value| array.push(value.downcase) unless ["PERFECT", "CALC"].include?(value)} array = ["calc", "dmg", "effect"] if @action_values.include?("WHOLE") || @action_values.size == 0 #--- substitute flag --- if substitutes substitutes.each { |substitute| next unless substitute substitute.result.clear_bes_flag array.each {|value| str = "substitute.result.set_#{value}"; eval(str)} } end #--- targets.each { |target| target.result.clear_bes_flag array.each {|value| str = "target.result.set_#{value}"; eval(str)} item.repeats.times { invoke_item(target, item) } target.result.clear_change_target @substitute_subject.result.clear_change_target if @substitute_subject } endendDo note that this will overwrite the Symphony's action_skill_effect.

Actually, how the normal script evaluate substitute battler makes it that when there are more than one substitutes, when the first substitute die, next substitutes will take no damage. I also tried to fix that.
 

TSED

Underfoot
Member
Joined
Feb 12, 2014
Messages
28
Reaction score
9
First Language
English
Primarily Uses
Script 'Shad3Light EnemySubPatch' line 41: NoMethodError occurred.  

undefined method 'result' for true:TrueClass.
I am not going to try to fix it as I am a little intoxicated right now.  Just figured I'd throw that out there.  It's probably me copy pasting it wrong.

(Original EnemySub is above BatSymph, for sure, and that patch is definitely below it.  That's all I can say because rum.)

Line 41:

       substitute.result.clear_bes_flag
 
Last edited by a moderator:

ShadowLurk

Tanoshii~
Veteran
Joined
Feb 14, 2014
Messages
226
Reaction score
53
Primarily Uses
Whoops, sorry, meant to use select there, why collect....

Anyway, this is the correct patch.

Code:
class Scene_Battle < Scene_Base  def action_skill_effect    return unless @subject    return unless @subject.alive?    return unless @subject.current_action.item    targets = @action_targets.uniq    #--- substitute ---    substitutes = []    targets.each { |target|      substitutes += target.opponents_unit.members.select { |a| a.substitute_enemy? }      substitutes += target.friends_unit.members.select { |a| a.substitute? }      #substitutes.push(target.opponents_unit.substitute_enemy)      #substitutes.push(target.friends_unit.substitute_battler)    }    substitutes = substitutes.uniq    #---    item = @subject.current_action.item    #---    if @action_values.include?("CLEAR")      targets.each { |target| target.result.set_calc; target.result.clear }      return    end    #---    if @action_values.include?("COUNTER CHECK")      targets.each { |target| target.result.set_counter }      return    elsif @action_values.include?("REFLECT CHECK")      targets.each { |target| target.result.set_reflection }      return    end    #---    array = []    array.push("calc") if @action_values.include?("CALC")    array = ["perfect"] if @action_values.include?("PERFECT")    @action_values.each {|value| array.push(value.downcase) unless ["PERFECT", "CALC"].include?(value)}    array = ["calc", "dmg", "effect"] if @action_values.include?("WHOLE") || @action_values.size == 0    #--- substitute flag ---    if substitutes      substitutes.each { |substitute|        next unless substitute        substitute.result.clear_bes_flag        array.each {|value| str = "substitute.result.set_#{value}"; eval(str)}      }    end    #---    targets.each { |target|       target.result.clear_bes_flag      array.each {|value| str = "target.result.set_#{value}"; eval(str)}      item.repeats.times { invoke_item(target, item) }       target.result.clear_change_target      @substitute_subject.result.clear_change_target if @substitute_subject    }  endend
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.
Do you Find Tilesetting or Looking for Tilesets/Plugins more fun? Personally I like making my tileset for my Game (Cretaceous Park TM) xD
How many parameters is 'too many'??

Forum statistics

Threads
105,862
Messages
1,017,049
Members
137,569
Latest member
Shtelsky
Top