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

Battler Traps (idea) #165

Closed Roguedeus closed 10 years ago

Roguedeus commented 10 years ago

This small bit of code is the basis for the idea I have for using custom enemies as traps and simply using the level system, along with Parameter Tables, to scale them to need.

I intend to expand this as needed to include selecting the enemies skills, but for now the skill used as the trap is manually provided.

class Game_Interpreter
  def battler_trap(enemy_id, skill_id = 1, trap_level = 1)
    # user = $data_enemies[enemy_id]
    user = Game_Enemy.new(0, enemy_id)
    item = $data_skills[skill_id]

    user.set_level(trap_level) if $imported["TH_EnemyLevels"]

    $game_party.battle_members.each do |party_member|
      puts(">>> Battler Trap: attacks #{party_member.name}")
      #Should include ALL normal skill details.
      party_member.item_apply(user, item)
    end

    user = nil #Dispose of enemy object just in case.
  end
end
HimeWorks commented 10 years ago

What would "all normal skill details" include?

Roguedeus commented 10 years ago

Just basic skill data. I was making mental notes as I built the test code. I am assuming that any special scripts applied via note tags should also function normally this way.

I've already started building on it. I am exploring the possibilities of modifying the user(enemy) in the same way that would occur in battle, so if a party member has trap countermeasures, they are applied to the user and thus effect the skill triggered by the trap.

So far this looks really cool.

HimeWorks commented 10 years ago

Basing the animation on the selected skill would be useful, since the trap itself could be arbitrary picked from a list of skills.

Playing the animation over the affected character would also be effective.

Roguedeus commented 10 years ago

Agreed. :) The more I poke this the more possibilities appear.

I am also thinking of possibly allowing the player to 'disarm' the trap via some sort of dialogue tree built out of the enemy stats. (Though very simple) Assuming the player has a trap removal skill or passive state of some kind.

Roguedeus commented 10 years ago

This is also making me wish that Yanlfy's Popups worked in the game map... :p

Roguedeus commented 10 years ago

This is making me wonder if I might be able to tackle that older suggestion I made about Battler States. :)

Roguedeus commented 10 years ago

I didn't realize animations where so easy to call.

  def battler_trap_animation(skill_id, wait = false)
    item = $data_skills[skill_id]
    character = $game_player
    character.animation_id = item.animation_id
    Fiber.yield while character.animation_id > 0 if wait
  end
Roguedeus commented 10 years ago

Can you possibly be able to tell me why this scripts popup method is super fast the first time its used, but then causes at least half a second of lag every time after?

I am looking it over and I am simply not familiar enough with RGSS/Ruby to determine.

http://galvs-scripts.com/galvs-event-pop-ups/

I am guessing its a simple oversight because otherwise why would it be so snappy on its first run, only to lag so badly after?

HimeWorks commented 10 years ago

I can't reproduce it. I had a "hello" popup appear and it doesn't lag or anything even when I make it appear multiple times.

I then had a potion icon appear and it works fine as well.

Roguedeus commented 10 years ago

Damn... Thanks for trying. Looks like I am going on a bug hunt, or yanking that particular part from the trap mechanic.

Roguedeus commented 10 years ago

Can you think of any particular part of that script that might lag naturally? The only thing I can think is the update_bitmap call every frame, but if that where causing the lag it would lag the whole time the popup is visible rather than just at its creation.

Roguedeus commented 10 years ago

Fixed it... It was not caching the @icon_sprite and re-creating it every time was causing the IconSet.png to be re-loaded each time. My IconSet is HUGE so it was causing issues.

Its even faster than before in a blank protect now.

Roguedeus commented 10 years ago

Well damn it... Wasn't that simple. Now it gets progressively slower and slower every time an attack animation occurs.

Issue with the actual popup lagging is gone. But a new lag has revealed itself.

Is it normal for the Interpreter called animation on the game map, not in battle, to begin lagging after about 20 or so calls?

The lag is gone after a map transition.

HimeWorks commented 10 years ago

Does it happen in a new project?

If not, then no.

Roguedeus commented 10 years ago

Found it too... Its Yanfly's Event Window. Once it gets larger then about 15 entries it begins to noticeably lag.

Go figure.

Clearing it every 10 entries clears up every bit of lag.

  class Game_Temp
    def add_event_window_data(text)
      @event_window_data = [] if @event_window_data.nil?
      clear_event_window_data if @event_window_data.length > 10
      return if text == ""
      @event_window_data.push(text)
    end
  end

After conceding that there is no way a core feature of the game could cause it, it took less then a few moments to realize I was also updating the event window with trap results, in case the player disables the popup.

HimeWorks commented 10 years ago

Hmm...but then if you think about how the item scene works, you could possibly have dozens of items being drawn and that doesn't really cause a problem.

rubydragon44 commented 10 years ago

Do you have or plan to make a public script for this, Roguedeus? I would be very interested in it, although I'm unsure how it works.

Roguedeus commented 10 years ago

Its not public at the moment. It is still very rough. Also it incorporates the use of several authors scripts (Galv, Yanfly, Hime) it is also more of an in house thing.

In the mean time the OP code can be used to completely event your traps normally. Just place the code as a stand alone script and call that method in a an event script command at the proper time.

The trap_level parameter wont work unless you are using Hime's Enemy Levels though.

rubydragon44 commented 10 years ago

What does it do? Say I want to have it so that when an enemy uses a contact move against the battler of a given slot id, the trap will activate, unless the enemy has the Above Ground state, because that particular trap is a ground trap. Or you could have a floating magic mine trap that work against anything besides things like snakes or slime, which can slide under. {And the trap is a certain skill?} that can either continue even if the original placer is out of the party or dead. Or perhaps certain moves against that slot id will cause the trap to deactivate. Yay, ideas. Lol.

Roguedeus commented 10 years ago

Its not a battle, its only a single shot attack against every active party member. The battler only exists as a means of processing the skills data, so any effects that are applied to the battler will only serve to change his stats before the attack is processed. After which the battler is destroyed in code.

The player never sees the battler, as it only exists as an instance object in code, in order to get data from it.

However, anything you choose to have the event do, before or after that process occurs, is totally up to you.

Again, I am not sure if this script will ever be released for public use in its complete form as it requires the use of several other scripts and its unreasonable to assume that people will be able to handle that without trouble. And then they would want me to walk them through it each time... Its easier to just not release it, or release little more than what you see in the OP.

Roguedeus commented 10 years ago

Ok, it just occurred to me yesterday that I can expand this script to include a few concepts I've got milling around in my head.

First, I can expand the traps script via special skills that are limited to reference only, not usable in menu or battle, that apply ONLY to traps. Having the skill has them applied to a trap when the party 'acts' on the trap. The batter that's created for the trap is then, ATTACKED by all battler members that have those skills, and if the battler dies, the trap is deactivated.

Conversely, the system can be adapted to collectable items like ores and treasures.

The same process will occur, but with 'Gathering' skills that attack the collectible battler the same way, resulting in loot (the collectible items) if the battler dies.

HimeWorks commented 10 years ago

I think turning events into battlers on the map would slowly lead to on-map battle systems lol

First the events simply use a skill. Now you can defeat them and they will drop items.

What's next? They move? lol

Roguedeus commented 10 years ago

Yeah. I have begun using this script for several things, including effects based on the enemy assigned to them. It started as just a trap system, but has morphed to where traps are only half the function of it.

I've allowed for actors to have passive states that mitigate trap effects and passive skills that only trigger against battler traps as a mechanic for disabling.

I have not had a need to develop the battler state idea yet... But I might soon. ;)

Roguedeus commented 10 years ago

Returning to this because the lag caused by Event Window is pretty significant... Its super noticeable now that I have managed to streamline much of the rest of the lag issues I had.

After staring at the code and poking at it, I have determined that adding text to the window is the BIGGEST culprit. However, NOTHING I can find would indicate why. I am left wondering if it has to do with the fact that yanfly chose Window_EventWindow < Window_Selectable Window Selectable as the basis for the event window.

Something tells me that Window Select must not have the same disposal actions as other regularly updated windows, which is causing the lag to get worse and worse, every time a new string is added to the contents.

Of course, this is just a guess, as I am not very familiar with windows. I'm going to try and see what alternatives I can find, or other similar scripts and how they've avoided the lag... Or not.

Any feedback might help.

Roguedeus commented 10 years ago

To be clear... Its not just the event window causing lag. But it's damn close to half of it. The other half I haven't quite gotten a handle on. But I am guessing its the animations & sounds...

Roguedeus commented 10 years ago

Looks like I was mistaken... EventWindow isn't a significant part of the problem... /facepalm It just looked like the reasonable explanation until I isolated it.

This is sooo much fun.

HimeWorks commented 10 years ago

Yes, isolating things reveals a whole new world. This is pretty much why I ask people to create a new project.

Roguedeus commented 10 years ago

I swear... It feels like this game engine operates on the edge of acceptable performance at all times and the slightest nudge sends it off balance.

I just realized that I am doing way way way to much with states... If there are more than 2 active states on any actors, the game begins to lag... And a large part of the trap lag issue, was the fact that by the time the party had passed through a half dozen of them, there were a hand full of states active, as a result...

This wouldn't be a problem in the middle of battle, where lag is pretty hard to notice. But when the fluidity of the map scrolling begins to hitch, its SUPER noticeable.

So... I get to make a run through all the scripts that touch states and try to figure out how I can optimize them...

Lately, every time I think I have reached a point were I can begin to crunch out some real content, I either discover something I'd like to do, or have to do, to prevent it.

Its almost as if being really productive is a catch 22. The more productive I am, the more I find I need to do... :p

HimeWorks commented 10 years ago

Sounds like your passive states working against you again.

Roguedeus commented 10 years ago

You are right... At least now I know which script to re-write. I should probably just use yours to plug into instead.

The passive state plugin I made was a large part but not all of it. I will still have to once over every state script... /sigh

Roguedeus commented 10 years ago

Holy crap... Nested while loops are nearly half the speed of nested FOR loops... But straight FOR loops are about 30% SLOWER than straight while loops?!?

for ... in                      0.093000   0.000000   0.093000 (  0.088005)
while                           0.063000   0.000000   0.063000 (  0.062003)
for for                         0.062000   0.000000   0.062000 (  0.074005)
while while                     0.125000   0.000000   0.125000 (  0.121006)
[Finished in 0.4s]

My mistake... There is ZERO performance difference.

for ... in                      0.093000   0.000000   0.093000 (  0.088005)
while                           0.047000   0.000000   0.047000 (  0.059003)
for for                         0.078000   0.000000   0.078000 (  0.075005)
while while                     0.078000   0.000000   0.078000 (  0.075004)
[Finished in 0.4s]
HimeWorks commented 10 years ago

lol you should also show the code you're testing.

Roguedeus commented 10 years ago

I made sure they created the same arrays... (The first time used the wrong array in the while while test...) TIMES = 10000 ARRAY2 = Array.new(5) { Array.new(2) {1} }

                                    user     system      total        real
for for                         0.015000   0.000000   0.015000 (  0.023002)
while while                     0.032000   0.000000   0.032000 (  0.025001)
[Finished in 0.1s]
  b.report "for for" do
    TIMES.times do |i|
      @array_f = []
      for obj in ARRAY2
        next unless obj #States don't have passive states...
        for id in obj
          @array_f.push(id)
        end
      end
    end
    # puts(@array_f.to_s)
  end

  b.report "while while" do
    TIMES.times do |i|
      @array_w = []
      sub_cnt = 0
      sub_lim = 0
      cnt = 0

      lim = ARRAY2.size
      while cnt < lim
        sub_cnt = 0
        sub_lim = ARRAY2[cnt].size
        while sub_cnt < sub_lim
          @array_w.push(ARRAY2[cnt][sub_cnt])
          sub_cnt += 1
        end
        cnt += 1
      end
    end #TIMES
    # puts(@array_w.to_s)
  end
Roguedeus commented 10 years ago

Over all I found some interesting things... Several built in loops are actually equal to their raw while loop counterparts. While at least one is actually faster. (will double check it later.)

[].select and [].collect are sgnificantly faster than a while loop replacement... At least, as I tested them. Its possible I did something wrong.

  #-----------------------------------------------------------------------------
  #                                     user     system      total        real
  # collect                         0.125000   0.000000   0.125000 (  0.120007)
  # while                           0.156000   0.000000   0.156000 (  0.156009)
  # [Finished in 0.4s]  
  #-----------------------------------------------------------------------------
  b.report "collect" do
    TIMES.times do |i|
      @a = []
      @a = ARRAY3.collect {|x| x <= 0}
    end
    # print("#{a}")
  end

  b.report "while" do
    TIMES.times do |i|
      @a = []
      cnt = 0
      lim = ARRAY3.size
      while cnt < lim
        if ARRAY3[cnt] <= 0
          @a.push(true) 
        else
          @a.push(false) 
        end
        cnt += 1
      end
    end
    # print("#{@a}")
  end
  #-----------------------------------------------------------------------------
  #                                     user     system      total        real
  # select                          0.110000   0.000000   0.110000 (  0.116007)
  # while                           0.187000   0.000000   0.187000 (  0.182010)
  # [Finished in 0.4s]
  #-----------------------------------------------------------------------------
  b.report "select" do
    TIMES.times do |i|
      @a = []
      @a = ARRAY3.select {|x| x > 0}
    end
    # print("#{a}")
  end

  b.report "while" do
    TIMES.times do |i|
      @a = []
      cnt = 0
      lim = ARRAY3.size
      while cnt < lim
        if ARRAY3[cnt] > 0
          @a.push(ARRAY3[cnt]) 
        end
        cnt += 1
      end
    end
    # print("#{@a}")
  end
Roguedeus commented 10 years ago

Another example... Please tell me if this is not an apples to apples comparison.

require "benchmark"

class State
  attr_accessor :id
  attr_accessor :duration_min
  attr_accessor :duration_max
  attr_accessor :incaps

  def initialize(id)
    @id = id
    @duration_min = rand(3) + 1
    @duration_max = rand(4) + @duration_min
    @incaps = []
  end
end

TIMES   = 100_000

STATE_ARRAY   = Array.new(100) {|i| State.new(i)}
STATE_IDS   = (1..100).to_a

ARRAY10   = [91,71,8,41,5,3,6,21,0,11] #10
ARRAY20   = [1,4,5,73,5,3,63,5,13,4,53,7,5,3,6,99,43,5,2,0] #20

Benchmark.bm(30) do |b|
  b.report "collect" do
    TIMES.times do |i|
    #----------------------------------
      @result = ARRAY10.collect {|id| STATE_ARRAY[id]}
    #----------------------------------
    end
  end
  # puts(@result.to_s)

  b.report "while" do
    TIMES.times do |i|
    #----------------------------------
      @result = []
      cnt = 0
      lim = ARRAY10.size
      while cnt < lim
        @result.push(STATE_ARRAY[ARRAY10[cnt]])
        cnt += 1
      end
    #----------------------------------
    end
  end
  # puts(@result.to_s)
end

#                                    user     system      total        real
#collect                         0.156000   0.000000   0.156000 (  0.155009)
#while                           0.172000   0.000000   0.172000 (  0.173009)
#[Finished in 0.4s]
Roguedeus commented 10 years ago

I've made sure I am running 1.9.2 so its applicable to RGSS3.

Roguedeus commented 10 years ago

It seems that nearly every example of each is slower than the while alternative. But sometimes, not by much more than about 10%. At most I've seen about 25 to 30%.

require "benchmark"

class State
  attr_accessor :id
  attr_accessor :duration_min
  attr_accessor :duration_max
  attr_accessor :incaps

  def initialize(id)
    @id = id
    @duration_min = rand(3) + 1
    @duration_max = rand(4) + @duration_min
    @incaps = []
  end
end

TIMES   = 150_000

STATE_ARRAY   = Array.new(100) {|i| State.new(i)}
STATE_IDS   = (1..100).to_a

ARRAY10   = [91,71,8,41,5,3,6,21,0,11] #10
ARRAY20   = [1,4,5,73,5,3,63,5,13,4,53,7,5,3,6,99,43,5,2,0] #20
HASH10    = {1=>91,2=>71,3=>8,4=>41,5=>5,6=>3,7=>6,8=>21,9=>0,10=>11} #10

Benchmark.bm(30) do |b|
  b.report "each" do
    TIMES.times do |i|
    #----------------------------------
    @result = []
    HASH10.each do |i, id|
      @result[i] = State.new(id)
    end
    #----------------------------------
    end
  end
  # puts(@result.to_s)

  b.report "while" do
    TIMES.times do |i|
    #----------------------------------
      @result = []
      cnt = 0
      lim = HASH10.size
      while cnt < lim
        cnt += 1
        @result[cnt] = State.new(HASH10[cnt])
      end
    #----------------------------------
    end
  end
  # puts(@result.to_s)
end

#                                    user     system      total        real
#each                            1.732000   0.000000   1.732000 (  1.741100)
#while                           1.435000   0.000000   1.435000 (  1.436082)
#[Finished in 3.3s]
Roguedeus commented 10 years ago

I've also begun to cache method results, used in loops, that assemble arrays every time they are called. It seems silly not to do so and even more silly that the engine makers didn't use a name scheme that indicated that's what the method is doing.

I'm not mistaken am I?

y = method #This caches the value
i.times do |z|
  y.include?(xxx) #This checks the cache every loop?
end

i.times do |z|
  method.include?(xxx) #This assembles the array for checking every loop?
end

def make_array
  puts("Make")
  return [1,2] + [3]
end

y = make_array
3.times{puts(y.push(4).to_s)}  
puts("-------------------")
3.times{puts(make_array.push(4).to_s)}

#Make
#[1, 2, 3, 4]
#[1, 2, 3, 4, 4]
#[1, 2, 3, 4, 4, 4]
#-------------------
#Make
#[1, 2, 3, 4]
#Make
#[1, 2, 3, 4]
#Make
#[1, 2, 3, 4]
#[Finished in 0.1s]

Yeah, I sometimes get myself mixed up over which assignments are pointers to a value and which are actual values... I never studied computer science and all that so pretty much 99% of my work is simply force of will, trial and error, and the many times I've managed to have someone like you humor me for a while... ;)

Roguedeus commented 10 years ago

Well... I've managed to get a full 5 battle member party, each 25+ states, to RUN not walk smoothly around the map.

In doing so I managed to figure out that I no longer needed about 4 scripts (replacing yanfly's passive states with yours) and will instead use my new Passive Aura States script. Which doesn't appear to cause any lag at all even when every party member has an aura on top of their 25+ states.

I really... REALLY, don't want to have to do this again any time soon. Though on the bright side, I did learn new things about ruby that I likely wouldn't have otherwise.

HimeWorks commented 10 years ago

I did notice that they do not cache their results locally. One reason for this is perhaps to account for real-time changes?

What I mean is, suppose you have 100 states. By the time you've checked half of them, one of the states in the other half have changed already, and the game needs to correctly account for this.

Or maybe they just didn't want to clutter their code. One example I can think of is the way they call equips everytime they want to check parameters...