Hime-Works / Requests

Bug reports and requests that may require longer discussions and is not suitable to leave on the blog
http://himeworks.com/
GNU General Public License v2.0
7 stars 9 forks source link

State Elements... #285

Closed Roguedeus closed 8 years ago

Roguedeus commented 9 years ago

We have Element Damage... And UsableItem Effects, like add state, etc... But nothing combined.

What if, a stun wasn't just a stun, but an ICE stun? All rates are effected first by element resistance, and then by effect resistance. Thus a target 50% resistance to ICE, and 50% Resistance to Stun, has a 25% total resistance against ICE Stun.

This is just a simple example. But it would make certain effects able to be applied much more liberally, when something Immune to an element, is by extension also immune to all that elements linked effects.

Meanwhile, an EARTH Stun, will work fine... etc.

Elements are purely cosmetic. It would be linked to element ID's and thus can be easily extended.

Now... How would the easiest way to implement it be?

If the effect is a state, the note tag could be on that state... But then, it would require every state of every element to be individually defined. What if the link were made on the UsableItem?

Thus, only one state is needed, and can be applied under different circumstances. Kind of like conditional effects.

example_2 This would mean that each blind effect could be assigned a different element ID dependency. Or the same dependency, under different conditions... etc...

Roguedeus commented 8 years ago

Is this an adequate benchmark? Do you see any inconsistencies? (I went a bit overboard on the class constructor though)

require "benchmark"

TIMES = 50
DATA = {}
def class_constructor(suffix)
  eval(
    "
    class Data_#{suffix}
      attr_reader :hp
      attr_reader :mp
      attr_reader :atk
      attr_reader :def
      attr_reader :mat
      attr_reader :mdf
      def initialize
        @hp   = rand(TIMES) + 1
        @mp   = rand(TIMES) + 1
        @atk  = rand(TIMES) + 1
        @def  = rand(TIMES) + 1
        @mat  = rand(TIMES) + 1
        @mdf  = rand(TIMES) + 1
      end
    end

    DATA[#{suffix}] = Data_#{suffix}.new
    "
    )
end
x = 0
TIMES.times {class_constructor(x); x+=1} #Builds TIMES number of Data objects.

$rebuild = false
class Test
  def data(var)
    DATA.inject(0) {|x,(key,val)| x + val.method(var).call }
  end
  def data_cached(var)
    return @data_cached if @prev_var == var && !$rebuild
    @prev_var =  var
    $rebuild = false
    @data_cached = DATA.inject(0) {|x,(key,val)| x + val.method(var).call }
    return @data_cached
  end
end

TIMES1    = 100000
x = 0
REBUILD_CHANCE = 0.25
obj = Test.new
Benchmark.bm(30) do |b|
  #----------------------------------------------
  b.report "Rebuild" do
    TIMES1.times do
      $rebuild = true if rand < REBUILD_CHANCE
      x = obj.data(:hp)
    end
    print("#{x}")
  end
  #----------------------------------------------
  b.report "Cached" do
    TIMES1.times do
      $rebuild = true if rand < REBUILD_CHANCE
      x = obj.data_cached(:hp)
    end
    print("#{x}")
  end
end
=begin
                                    user     system      total        real
Rebuild                       1322  3.089000   0.000000   3.089000 (  3.085176)
Cached                        1322  0.811000   0.000000   0.811000 (  0.812047)
[Finished in 4.1s]
=end
HimeWorks commented 8 years ago

I am not too sure how to perform effective benchmarks.

Roguedeus commented 8 years ago

I am reconsidering the nature of Armor as it is applied to battle resolution.

My previous paradigm, was type > nature where Each type applied separately, with its own strengths and weaknesses, and each nature had its own behaviors. Such as 'Active' nature being first to absorb damage and 'Passive' nature being fully restored each turn.

But now I am thinking I need something a little more intuitive and less complex, overall.

Armor.v2 is this... gl_armorv2_example_2

Magical on top (Passive > Active) Physical on bottom (Passive > Active)

I like this... I just don't like the arbitrary dichotomy of separate 'natures' defining behavior.

I also think that it would be more intuitive if there is a single ARMOR gauge. Like there is a single HEALTH gauge, regardless of the origin of that health...

class_hp = 100
equips_hp = 100
state_hp_buff = 2.0

MHP = (class_hp + equips_hp) * state_hp_buff
MHP = 400

However, if you lose that state buff, the MHP changes to 200, but the excess Health (while it lasted) was an added buffer to damage. Though the target may have only had 250 of that 400HP remaining, when the state ends the targets HP is 200 because @hp is capped by current @mhp regardless of the ratio before @mhp changed.

It can support any number of armor types, but they all accumulate into a single ARMOR total. Much like all health types accumulate into a single HEALTH total. And like losing one of those types changes the maximum health, certain conditions that invalidate an armor type, changes the maximum armor.

Also, just as there are actions that bypass DEF (invisibly) and deal direct HP loss, there will be actions that bypass ARMOR and deal direct HP loss. Only, it won't be so invisible. The player will be able to notice that an attack ignored their ARMOR.

I have to think about this some more... Perhaps write up a VERY SIMPLE Armor.v3 and do some testing.

Roguedeus commented 8 years ago

I really REALLY need to do this for every script I write... It is helping.

Roguedeus commented 8 years ago

I think I am in love with Armor.v3

Iteration for the win!

HimeWorks commented 8 years ago

What steps did you take to make it easier to work with?

Roguedeus commented 8 years ago

A complete reduction in all aspects.

Outlined class and intended functions before writing a single line.

Modular code. ;)
Somewhere along the way I forgot why this was important.

HimeWorks commented 8 years ago

Thinking like an engineer On Aug 31, 2015 5:50 PM, "Roguedeus" notifications@github.com wrote:

_A complete reduction in all aspects. _

  • Restarted with one question... Do I need that to process the simplest result?

Outlined class and intended functions before writing a single line.

  • This helped me recognize redundant behavior and identify ways to simplify complex behavior.

Modular code. ;)

Somewhere along the way I forgot why this was important.

— Reply to this email directly or view it on GitHub https://github.com/Hime-Works/Requests/issues/285#issuecomment-136511177 .

Roguedeus commented 8 years ago

Its how I approach most things that I keep at arms length... My personal projects seem to get the better of me at times.

On other news...

Did you know that release_unequippable_items completes successfully for each empty equip slot on an actor, every time refresh occurs?

That is a lot of extra, completely unnecessary, checks. There has to be a simple way to stop this.

edit The culprit is that harmless empty slots are always equippable?(FALSE)

  #--------------------------------------------------------------------------
  # * Determine if Equippable
  #--------------------------------------------------------------------------
  def equippable?(item)
    return false unless item.is_a?(RPG::EquipItem)
    return false if equip_type_sealed?(item.etype_id)
    return equip_wtype_ok?(item.wtype_id) if item.is_a?(RPG::Weapon)
    return equip_atype_ok?(item.atype_id) if item.is_a?(RPG::Armor)
    return false
  end
HimeWorks commented 8 years ago

What do you mean by completes successfully? If theres nothing in the slot it should just move on On Sep 1, 2015 8:52 PM, "Roguedeus" notifications@github.com wrote:

Its how I approach most things that I keep at arms length... My personal projects seem to get the better of me at times.

On other news...

Did you know that release_unequippable_items completes successfully for each empty equip slot on an actor, every time refresh occurs?

That is a lot of extra, completely unnecessary, checks. There has to be a simple way to stop this.

— Reply to this email directly or view it on GitHub https://github.com/Hime-Works/Requests/issues/285#issuecomment-136904451 .

Roguedeus commented 8 years ago

A few of your scripts touch empty equip slots. Not sure why it matters but Effects Manager freaks out if the Game_BaseItem,object is nil...

Otherwise a simple next unless item.object would stop trade_item_with_party getting called a bazillion times every turn update.

Roguedeus commented 8 years ago

gl_armorv3_progress_3b

Roguedeus commented 8 years ago

And that is just ONE actor with only 4 equip slots... It would be well over 5 times that in a full party with full slots.

Roguedeus commented 8 years ago

Every empty slot tries to trade its item.object with the party every turn, because empty slots are always unequippable.

  def release_unequippable_items(item_gain = true)
    loop do
      last_equips = equips.dup
      @equips.each_with_index do |item, i|
        if !equippable?(item.object) || item.object.etype_id != equip_slots[i]
          trade_item_with_party(nil, item.object) if item_gain
          item.object = nil
        end
      end
      return if equips == last_equips
    end
  end
Roguedeus commented 8 years ago

I can't imagine that skipping the trade for already empty slots would break anything... But when I do it, your Effects Manager errors in this method.

  # This method basically checks the effects for all effect objects
  def check_effects(objects, trigger, user=nil, item=nil)
    objects.each {|obj|
      type = type || Effect_Manager::Effect_Types[obj.class]
      obj.effects.each {|effect|
        effect_apply(user, obj, effect, type, trigger)
      }
    }
  end

because of this check...

  def check_equip_effects(equip)
    super
    check_effects([equip], "equip")
  end

  def check_unequip_effects(equip)
    super
    check_effects([equip], "unequip")
  end

edit Or... maybe I am mistaken.

Removing the Effect Manager stops any problems with this fix...

  def release_unequippable_items(item_gain = true)
    loop do
      last_equips = equips.dup
      @equips.each_with_index do |item, i|
        next unless item.object                         #<------
        if !equippable?(item.object) || item.object.etype_id != equip_slots[i]
          trade_item_with_party(nil, item.object) if item_gain
          item.object = nil
        end
      end
      return if equips == last_equips
    end
  end
Roguedeus commented 8 years ago

Can you think of a reason why my fix would cause trouble?

Seems reasonable that a trade should only be tried if there is something in the slot that needed to be unequipped in the first place.

HimeWorks commented 8 years ago

All of those checks only occur when effect manager is in the project right? This is not an issue in the default project?

Roguedeus commented 8 years ago

The checks occur regardless. Even in the clean install. (due to refresh)

The error for my fix only occurs with effect manager.

HimeWorks commented 8 years ago

Would i be able to test the issue with just effect manager and your snippet On Sep 1, 2015 9:52 PM, "Roguedeus" notifications@github.com wrote:

The checks occur regardless. Even in the clean install.

The error for my fix only occurs with effect manager.

— Reply to this email directly or view it on GitHub https://github.com/Hime-Works/Requests/issues/285#issuecomment-136912955 .

Roguedeus commented 8 years ago

Yeah. Here you go. Made a demo. http://www.roguedeus.com/Stuff/Test%20Trade%20and%20EffMngr.zip

Roguedeus commented 8 years ago

Just walk back and forth till the 20 step on turn fires refresh.

HimeWorks commented 8 years ago

What's interesting is that

next unless item.object

Fails, but

next unless item.object.nil?

Works.

?????

Roguedeus commented 8 years ago

lol... I've run into a few oddities lately with ruby syntax.

HimeWorks commented 8 years ago

Oh, I think I have an idea why that might be happening.

If you print out the object itself in the effect manager, you'll get a nil. However, if you use my snippet, you'll see that it's casual clothes.

That's pretty weird, considering that Eric's wearing casual clothes the whole time.

HimeWorks commented 8 years ago

No, my analysis is wrong. Nil is being passed to the equip check, and that's where the script is crashing because it assumes only valid equips will be passed in.

Now the problem is, why does that happen.

HimeWorks commented 8 years ago

We have this:

def armors
  @equips.select {|item| item.is_armor? }.collect {|item| item.object }
end

First, a Game_BaseItem object is only an armor if it actually holds an armor. It seems like your snippet has allowed objects to pass this test.

HimeWorks commented 8 years ago

And...here it is

def set_equip(is_weapon, item_id)
  @class = is_weapon ? RPG::Weapon : RPG::Armor
  @item_id = item_id
end

Game_BaseItem assumes that if it's not a weapon, then it's an armor. Even if has an ID of 0, which is a nil object.

This bug will be hidden when the release_unequippable_equips call goes through because it will take all the nil equips and set them to nil appropriately.

Roguedeus commented 8 years ago

That was quick. It likely would have taken me several hours AT MINIMUM to figure that out. I know that I couldn't see it the first time I looked. A few days ago.

Roguedeus commented 8 years ago

So should I just use item.is_armor? || item.is_weapon? rather than item.object?

HimeWorks commented 8 years ago

Use .nil? On Sep 4, 2015 12:57 PM, "Roguedeus" notifications@github.com wrote:

So should I just use item.is_armor? || item.is_weapon? rather than item.object?

— Reply to this email directly or view it on GitHub https://github.com/Hime-Works/Requests/issues/285#issuecomment-137790599 .

Roguedeus commented 8 years ago

Ok, I'm confused.

Maybe I am misunderstanding you. But wouldn't that require me to check the opposite of nil? next if item.object.nil? rather than next unless item.object.nil?...

edit: item.is_armor? || item.is_weapon? is working. But I would rather use a more efficient solution. If for no other reason than to get use to it. The more I use it correctly, the less I have to think about it. ;)

HimeWorks commented 8 years ago

You're right.

Well the main problem is the class being initialized to Armor because that method will always be called at the beginning.

If the class is correctly applied then it should not be an issue.

Roguedeus commented 8 years ago

Yeah. That did strike me as really weird when I discovered the issue. It is likely why Yanfly added his equip fix to the battle engine. For the last year, every time I saw that method I went, HUH?!?

HimeWorks commented 8 years ago

What did it do?

Roguedeus commented 8 years ago

Presumably, it was to prevent crashing when changing classes with different equip slots. I am only guessing, since I haven't analyzed it to the end, that this wouldn't have been required if the issue you discovered didn't exist.

I wouldn't be surprised if I am wrong. ;)

  #--------------------------------------------------------------------------
  # alias method: force_change_equip
  #--------------------------------------------------------------------------
  alias game_actor_force_change_equip_aee force_change_equip
  def force_change_equip(slot_id, item)
    @equips[slot_id] = Game_BaseItem.new if @equips[slot_id].nil?
    game_actor_force_change_equip_aee(slot_id, item)
  end

  #--------------------------------------------------------------------------
  # alias method: weapons
  #--------------------------------------------------------------------------
  alias game_actor_weapons_aee weapons
  def weapons
    anti_crash_equips
    return game_actor_weapons_aee
  end

  #--------------------------------------------------------------------------
  # alias method: armors
  #--------------------------------------------------------------------------
  alias game_actor_armors_aee armors
  def armors
    anti_crash_equips
    return game_actor_armors_aee
  end

  #--------------------------------------------------------------------------
  # alias method: equips
  #--------------------------------------------------------------------------
  alias game_actor_equips_aee equips
  def equips
    anti_crash_equips
    return game_actor_equips_aee
  end

  #--------------------------------------------------------------------------
  # new method: equips
  #--------------------------------------------------------------------------
  def anti_crash_equips
    for i in 0...@equips.size
      next unless @equips[i].nil?
      @equips[i] = Game_BaseItem.new
    end
  end
HimeWorks commented 8 years ago

There may have been scripts that would explicitly delete the entire object instead of setting it to nil like this

item.object = nil

I don't understand the point of that code.

Roguedeus commented 8 years ago

I noticed it was causing issues with some things I had done a long time ago and commented it out.

So far, its not been an issue.

Roguedeus commented 8 years ago

My armor mechanic is mostly functional. I am resisting the urge to define to much of the advanced features I have planned before I am certain how I want them to work, but the gist of the 'core' feature set is pretty damn cool... If I am allowed to pat myself on the back. ;)

I also wrote it in less than half the space this time.

I just finished up an Attack Elements data class that will eventually become the backbone of my element damage implementation of damage delivery. Which itself is a mechanic based on 'How' damage is dealt and all the ramifications of it... Such as by bladed weapon, missile weapon, thrown weapon, effect blast, effect beam, area of effect, etc...

I think I'll write up a few blog posts, while I rest my brain and determine which is next to check off my TODO: list.

Roguedeus commented 8 years ago

I am doing my best to design a deterministic battle hit and damage mechanic... The only 'random' aspects are the decisions made.

A battlers current state (of existence) vs. your potential action, is 100% predictable. Until it acts.

Roguedeus commented 8 years ago

I wonder how difficult it would be to add some extra details to the help window in battle?

Currently in Yanfly's engine, enemy name (and its states/buffs) appears in the window upon selection. No reason why more info can't be added.

HimeWorks commented 8 years ago

It is not difficult, but it also depends on what you want to add, when you want it to add, how you want it to be presented, whether it will be animated or not, etc.

Roguedeus commented 8 years ago

I am thinking the most pertinent details will be there, as well as their effects (slightly transparent).

Such as:

But I will settle for the first two. The third can be done in time. It will be for 'Test' purposes. So it wouldn't need to be perfect. The player won't be privy to any more info than they normally get from Yanfly's enemy info script. It works great as is.

...

I still need to finish deigning this... I have some good ideas from the previous two scripts I wrote that change the way actions resolve. such as make_damage_value and apply_item...

At the moment I am thinking a four step process can adequately replace random chance while still maintaining a since of mystery to results.

It looks something like this... (highly abbreviated)

Core Damage(actor/class) + Damage Nature(Piercing, Crushing, Blasting) + Damage Delivery(Projectile/Melee vs. Target Mobility) + Special Attack Details(vs. Armor) + Element Rates

This will make the likelihood of being able to accurately track and predict the result difficult, especially in group vs. group conflicts, as multiple actions can wildly offset future expectations... Predictability would get better and better as battlers are reduced in number.

edit: Forgot to add Element Rates, to the damage flow.

Roguedeus commented 8 years ago

Here is a little bit of the design...

  #------------------------------------
  BATTLE_FATIGUE:
    Now, all battlers count their number of total maneuver that turn as 
    fatigue. 

    A battle maneuver is any execution of an offensive or defensive event,
    including applying items (per repeat) and defenses like EVA and CEV. 

      Note: Any time EVA or CEV result in a success, a battle maneuver occurred.
      Note: 'Linked' actions are only included if they are not free.
      Note: 'Instant' actions cost +1 fatigue (or more).

    The beginning of the next turn, restores fatigue according to the
    battlers parameters. Over time, this can cause a battler to get so
    fatigued that they can no longer adequately function in battle.

      Note: Fatigue can be a condition in enemy action determination.

    Generally, one battle maneuver, causes +1 fatigue. However, when a battler 
    is fighting an enemy of greater level (even enemies) the count increases
    faster. 

          Level Difference = Count Value
                      # 1 +10% their difference...
                      +1   = 1.10
                      +2   = 1.20
                      # Half their difference...
                      +3   = 1.50
                      +4   = 2.00
                      +5   = 2.50
                      +6   = 3.00
                       >   = +.50

    The fatigue adjustment of the action.

        <fatigue: formula> (Usable Item)

        Note: Remember, all actions normally generate a base fatigue. This
          note tag, merely adjusts that base value. 

    The fatigue regenerated each turn.

        <fatigue_regen: formula> (Feature Object)

    The action will not generate fatigue.

        <no_fatigue> (Usable Item)
Roguedeus commented 8 years ago

BTW: My armor mechanic is pretty damn sweet. I am VERY GLAD I re wrote it. The gui is still rough, as I am not yet sure how much of what info I wish to share with the player (or why). So its UGLY. But functional.

From the preliminaries, this third rewrite of the combat mechanics (not to be confused with the battle system) is going equally well. It is hard to imagine what I was thinking (or not thinking) back when I wrote the first two versions... I think I was just so intimidated by the code I couldn't get past the limited scope I was working in. That, and I still couldn't SEE where the code was going.

Roguedeus commented 8 years ago

Just had an idea about crafting... Regarding fame, quests, and popularity.

HimeWorks commented 8 years ago

Crafting is affected by fame?

Roguedeus commented 8 years ago

The most rare items would require some kind of fame + quest benchmark... Maybe even a solid alignment (good or evil, etc...), when you are talking godlike equipment.

Want to craft a Legendary Sword? You need to have completed at least one Legendary Quest line, have a Fame of 'X' or greater... Plus, collect all the materials and the skill to craft it.

Without meeting the prerequisites, the player would have a cap of a certain item rarity in their recipe list.

HimeWorks commented 8 years ago

Sounds like something that would be easy to implement with variables and switches.

Roguedeus commented 8 years ago

Yeah. I was thinking the same thing. The crux is having a good crafting script.

Of course, there are a few details specific to my play philosophy and chosen mechanics that will likely be best with a plugin of some sort.

HimeWorks commented 8 years ago

Hmm, what would you consider to be "good" crafting script? I see a crafting system as follows

  1. Your inputs: how do devs set up the crafting system
  2. Your interface: if people want to change the UI, how easy would it be to do so?
  3. The crafting workflow: perhaps some people want to have different crafting processes?

Naturally, that's just a vague description of crafting.