Jump to content

kal

Member
  • Content Count

    123
  • Joined

  • Last visited

  • Days Won

    1

Everything posted by kal

  1. Here's one to start with: What exactly is the purpose of using doing an unless $@ check when aliasing a method? I know now that the $@ variable is an array that holds the backtrace of the last exception. So when you press F12 to enter debug mode, it generates an exception so $@ will evaluate to a true value (is that right?) and the aliasing won't happen. So why is it a bad idea to alias methods when you enter debug mode? And a somewhat related question: do "stack level too deep" errors occur when the same method aliasing code is run more than once? If so why is this?
  2. So as you probably know Enterbrain disabled editing in the Script Editor for the trial version of Ace. Do not despair though! We can bypass this quite easily because Ruby is such a dynamic language with very powerful metaprogramming capabilities. I'll explain how to edit the built RGSS3 engine and your own original scripts to the trial. For this example, I'll do a very simple and quick edit: boost the experience characters receive by 100x. First create a new project (or use an existing one) and navigate to the Project root folder (where Game.exe is located). Now create a new folder here and call it Scripts. Go inside the Scripts folder and create a new file called "boost_exp.rb". Open this file in a text editor (Notepad works fine). Now let's code our exp booster: puts "EXP booster loaded" module BattleManager def self.gain_exp $game_party.all_members.each do |actor| actor.gain_exp($game_troop.exp_total * 100) end wait_for_message end end I'll explain the code and what's going on here. The first line, puts "EXP booster loaded" , uses a new feature in Ace: the console. It simply prints the "EXP booster loaded" to the console window. This useful because you will know that the file was loaded successfully. To enable the console, go in the Game menu (that one with (G)) and then click on the entry with ©. (thanks Cremno for finding this) Next, we have the code that overrides the self.gain_exp method in the class BattleManager. It is pretty simple. The only thing this code changed from the default version is that we multiply $game_troop.exp_total by 100, which will result in a all EXP being increased by a factor of 100. OK, now to the final step: loading this file into our Ace project! First, go to your player start map and create a new event. Set the even to autorun (it's the second to last option in the lower right menu). Next create a script event command (the very last event command) and type in the following in the dialog box: path = File.join(Dir.getwd, "/Scripts/*.rb") Dir[path].each do |file| load file end This code iterates over all the files in the [project root folder]\Scripts folder that end with ".rb" (the Ruby file extension). On the third line you see load file which will simply load the contents of all the file and evaluate it as Ruby code. The final step here is to add an "Erase event" command after the Script command. (Erase event is the last command in the second "box" on page 2 in the command editor). We do this to make sure our script is loaded only once. That's it! Go ahead and start the project. If you enabled the console you should see "EXP booster loaded" when the event executed. Now go and fight a slime and gain like 10 levels. Using this approach we can start playing around with the script engine right now and start developing prototypes. I'm pretty sure you could even get a lot of VX scripts working using this method. Edit: I just found out that Ace deletes everything in the Project folder when you close the application so it's better to put your scripts in another folder! Say for example that you make a folder called C:\AceScripts where you put your scripts then change the event load code to the following: path = "C:/AceScripts/*.rb" Dir[path].each do |file| load file end
  3. kal

    Random Ruby and RGSS3 Tutorials

    Tutorial 2 - Fun with ObjectSpace Today we're gonna look at a cool module called ObjectSpace that was added to Ruby 1.9. ObjectSpace allows you to see how many objects have been created by the Ruby interpreter and what their types are. With ObjectSpace you can also iterate through every object known to the Ruby interpreter like if they were in one big array which can sometimes be handy as we'll see later on in this tutorial. First here here's how to see how many objects have been created in total: ObjectSpace.count_objects => {:TOTAL=>49106, :FREE=>6590, :T_OBJECT=>5543, :T_CLASS=>807, :T_MODULE=>38, : T_FLOAT=>744, :T_STRING=>13174, :T_REGEXP=>27, :T_ARRAY=>13512, :T_HASH=>71, :T_ STRUCT=>24, :T_BIGNUM=>1, :T_FILE=>4, :T_DATA=>5889, :T_MATCH=>12, :T_COMPLEX=>1, :T_NODE=>2650, :T_ICLASS=>19} TOTAL is how many object have been created during the entire run-time of the program and FREE is how many have been garbage collected. Then there is the ObjectSpace#each_object method which can be used to iterate over every alive object in the program. For example: ObjectSpace.each_object do |object| puts object end You can pass a class name to each_object to only iterate over objects that inherit from that class. For example to iterate over all the strings in the program do this: ObjectSpace.each_object(String) do |string| puts string end Now let's say we want to store a reference to each string in the program in an array for future use. One way to do it is this: strings = [] ObjectSpace.each_object(String) do |object| strings << string end But there's an easier way to do this because calling each_object without a block it returns an enumerator. I won't go in to exactly what enumerators are and how to use them in this tutorial, but in short they are external iterators. If you have programmed in Java they are similar to Java's Iterator class. Anyway, an enumerator can be directly converted to an array with to_a, so we can simplify the above code like this: strings = ObjectSpace.each_object(String).to_a Now that you know the basics of ObjectSpace let's build something that we can use in Ace with it! What I had in mind was a class that can get Window instances given a coordinate (an X and Y value). So the class should work like this: window_info = WindowInfo.new some_window = window_info.window_at(100, 100) # the values here are the X and Y coordinate, respectively, of a point that is within the bounds of the window When you start to write a class it's sometimes helpful to use "scripting by assumption" which means that you start at some level of abstraction and write code as if you had already implemented all the lower level methods. Using this approach, we can write the window_at method like this: class WindowInfo def window_at(x, y) windows = get_windows_at_position(x, y) sorted = windows.sort_by { |window| window.z } sorted.first end end The window_at method first gets an array of all window objects at the given coordinate using the helper method get_windows_at_position. After that is done we need to return one window only (because there could be more than one window at a given screen coordinate) so we want to return the window that has the highest Z coordinate i.e. the one that is "on top". We can do this by sorting the windows using their Z value and then simply returning the first element of the sorted array. So next let's implement get_windows_at_position (again employing scripting by assumption): class WindowInfo def window_at(x, y) windows = get_windows_at_position(x, y) sorted = windows.sort_by { |window| window.z } sorted.first end private def get_windows_at_position(x, y) windows = get_all_windows windows.find_all do |window| !window.disposed? && within_bounds?(window, x, y) end end end Here we delegate the job of getting all the window objects to another method, get_all_windows. Next once we have all the windows we want to filter out the ones that are disposed and the ones that are not within the bounds of the coordinate. We can do that by calling the find_all method on the window array. find_all will return an array containing only the elements for which the block returned true. In the block we return true only if the window is not disposed, and if the window is within the bounds of the coordinate. Now let's code our two remaining helpers, get_all_windows and within_bounds? class WindowInfo def window_at(x, y) windows = get_windows_at_position(x, y) sorted = windows.sort_by { |window| window.z } sorted.first end private def get_windows_at_position(x, y) windows = get_all_windows windows.find_all do |window| !window.disposed? && within_bounds?(window, x, y) end end def get_all_windows ObjectSpace.each_object(Window).to_a end def within_bounds?(window, x, y) x_range = (window.x)..(window.x + window.width) y_range = (window.y)..(window.y + window.height) x_range.include?(x) && y_range.include?(y) end end As you can see, get_all_windows simply uses the ObjectSpace module's each_object method to return an array of all Window objects (that is, all Window objects and all objects which inherits from Window). within_bounds? is a little more involved. It starts by creating two range (http://ruby-doc.org/core-1.9.3/Range.html) objects using the .. syntax. These ranges make up the rectangle of the window as it is drawn on the screen. The window.x and window.y values is where the upper left corner of the window is placed on the screen, so if we start at that point and add the width and height we get the window's rectangle. Then it's simply a matter of checking if the x and y coordinates are withing the x and y ranges. Now our WindowInfo class is complete! However, it's kind of hard to try it out right now. Sure, we can use it inside a script in the editor but it's not that useful. To make it more interesting we'll use Alex, an interactive console I wrote some time ago. You can get Alex here: https://gist.github.com/kl/2044957 Alex works like a poor man's version of the awesome Pry library: http://pryrepl.org/ It has a few features you can read about in the script comments but it basically allows you to pause script execution at any time and type in code that will be execute in the current context. Once you have Alex installed, you can start an Alex session by either calling binding.alex from anywhere inside any script, or by pressing the D key on the keyboard when the game is running. We'll use the latter approach in this tutorial. So with Alex and our newly created WindowInfo class ready to go, start the game and go into the menu screen (and make sure that you enabled the RGSS console window). Now if you press D you should see that the game freezes and the console window should print out alex: Now you can start typing in some code and it will evaluate and print out the return value. For example: alex: 2 + 2 => 4 so let's use Alex to get a reference to the Window_MenuCommand window (the window in the upper left corner). alex: $wmc = WindowInfo.new.window_at(100, 100) => #<Window_MenuCommand:0x83c5e1c> So we give the coordinate (100,100) and our WindowInfo class gives back the Window_MenuCommand object that occupies that coordinate! Yay! Note that I am using a global variable here, something which is not normally advisable, and that's i because it's an easy way to make sure we can get to the object later on. I wouldn't do this in production code, but for simple testing like this it's fine. Now that we have a reference to the window object, we can play around with it a bit. For example we can make it invisible: alex: $wmc.visible = false => false Now when you type this in you won't see a change in the game window because the game is currently in a frozen state. To switch back to the game exit out of Alex by typing exit: alex: exit Now the window should disappear. Or what about changing it's position (to get back to Alex just press D again): alex: $wmc.visible = true => true alex: $wmc.y = 150 => 150 That concludes this tutorial on ObjectSpace and how it can be used to hold of object references! You could extend this example a lot further if you wanted, for exampling if you have a mouse script installed you could create a debugging system where you could click on the screen to select windows and even re-position windows with the mouse.
  4. Really cool idea! This could bring a whole new dimension of tactics to the battle system but there will be some challenges as this type of system will need some kind of enemy AI or else it would probably be easy to exploit. I've written a very basic WIP prototype that you can play around with: https://gist.github.com/kl/5815417 Right now the position system is implemented for enemies (not yet for actors) and it does not yet support negative values. The default behavior for enemies is simple: move forward until there is a skill that can reach an actor and then keep using that skill(s). Later on this behavior could be expanded on to make the enemies smarter. To use the script you have to create a skill that represents the moving forward action. You can set the skill id in the script configuration. Then for the skill in the database you should make it Skill Type: None, Scope: The User, and add some kind of dummy effect (like Recover HP 0 %) You can set the range for enemy skills by adding a tag like this: <bp_skill_range NUMBER>
  5. kal

    Show image in equip window!

    Hi, try this: https://gist.github.com/kl/5787548
  6. kal

    Instrument Script (patly completed)

    When it says expecting keyword_end it means you have missed an end somewhere. Every class, module, method, block etc should have an end to indicate where it ends. It looks like the class Scene_Instrument is missing it's end keyword.
  7. ^^^ I also felt compelled to do something so I wrote this : module CommonEventOnHitKal TAG_NAME = "ceoh" class IDExtractor def get_common_event_id_for(user) rpg_actor = $data_actors[user.id] extract_common_event_id(rpg_actor.note) end private def extract_common_event_id(note) id = note[/<#{TAG_NAME}\s+(\d+)\s*>/, 1] id.nil? ? nil : id.to_i end end class CommonEventRunner def run(common_event_id) page = make_page(common_event_id) $game_troop.interpreter.setup(page.list) end private def make_page(id) page = RPG::Troop::Page.new # 117 => common event code, 0 => indent, id => id of common event page.list.unshift(RPG::EventCommand.new(117, 0, [id])) page end end @id_extractor = IDExtractor.new @common_event_runner = CommonEventRunner.new def self.run_event_for(user) event_id = @id_extractor.get_common_event_id_for(user) @common_event_runner.run(event_id) if event_id end end class Game_Battler alias_method :item_apply_ceoh_kal, :item_apply def item_apply(user, item) item_apply_ceoh_kal(user, item) # item id 1 is Attack if user.is_a?(Game_Actor) && item.id == 1 && @result.hit? CommonEventOnHitKal.run_event_for(user) end end end Actually this is a variation of this request by Kcaz64 http://www.rpgmakervxace.net/topic/16007-common-event-when-state-ends-is-removed/ So maybe a more general script that can run common events in many different situations would be useful? To use this script create a tag in the actor's note box like this: <ceoh 4> "ceoh" is the name of the tag (you can change this in the script if you want) and the number is the id of the common event that should run when the actor hits with the Attack skill.
  8. kal

    Instrument Script (patly completed)

    I can see one problem right away and that is that you are using integer symbols as keys in the hash. A symbol cannot start with an integer and that's why you get the syntax error. Let me try and explain symbols real quick. A symbol is almost like a string, and a string is a sequence of characters. You write strings in double or single quotes in Ruby and symbols start with a colon. "string" :symbol for now just think of a symbol as a string but you are only interested in it's name. For example, when using the symbol :symbol we only ever care about the symbol's value (it's name), and we will never add characters to the symbol, change lowercase to uppercase, look at individual characters and so on, all which you can do with a string. So back to your code. You have this {:1 => :A} which is not legal because a symbol cannot start with a number, it must start with a letter. So to fix this you could do this: {:s1 => :A} or this {"1" => :A} in the former snippet I added a letter to the start of the symbol making it legal. In the latter I change the symbol to a string and strings are allowed to start with anything so "1" is a perfectly legal string. That should fix the first error but there has to be more to the script because what you posted will not do much of anything by itself.
  9. Here's give this a go: module StateRemoveCommonEventsKal TAG_NAME = "srce" class IDExtractor def get_common_event_id_for_state(state_id) rpg_state = get_rpg_state(state_id) extract_common_event_id(rpg_state.note) end private def get_rpg_state(state_id) $data_states.find do |rpg_state| rpg_state && rpg_state.id == state_id end end def extract_common_event_id(note) id = note[/<#{TAG_NAME}\s+(\d+)\s*>/, 1] id.nil? ? nil : id.to_i end end class CommonEventRunner def run(common_event_id) page = make_page(common_event_id) $game_troop.interpreter.setup(page.list) end private def make_page(id) page = RPG::Troop::Page.new # 117 => common event code, 0 => indent, id => id of common event page.list.unshift(RPG::EventCommand.new(117, 0, [id])) page end end @id_extractor = IDExtractor.new @common_event_runner = CommonEventRunner.new def self.run_event_from_state(state_id) event_id = @id_extractor.get_common_event_id_for_state(state_id) @common_event_runner.run(event_id) if event_id end end class Game_BattlerBase alias_method :erase_state_srce_kal, :erase_state def erase_state(state_id) StateRemoveCommonEventsKal.run_event_from_state(state_id) erase_state_srce_kal(state_id) end end make a state and put a tag in the state notebox like this: <srce [common event id]> For example: <srce 4> will run the common event with id 4 when the state is removed. If you want to use another tag name simply change the TAG variable in the script. Let me know if it works or not.
  10. Well there are a couple of things you need to consider. First how exactly did you change the variable from the event commands? Are you changing the move speed of the player or of some event (which both inherit from Game_CharacterBase)? So let's assume that you want to change the move speed of the player. You can get to the player object by using the global variable $game_player. Then to set the move speed do this: $game_player.move_speed = 4.3 Only this will fail until you add an attribute_accessor to the Game_CharacterBase class (or you could add it to Game_Player if you only wish to change the move speed of the player). So attribute_accessor basically means that you can access (that is get the value of or change the value of) an instance variable from outside the object, which is what we are doing when we write $game_player.move_speed = 4.3 So go to Game_CharacterBase and add the following line attr_accessor :move_speed Then you can set the move speed by typing $game_player.move_speed = 4.3 If you want to change it for events you first need to get a reference to the event object you want to change, and then do the same thing.
  11. Try this: def run_common_event(id) # 117 => common event code, 0 => indent, id => id of common event list = [RPG::EventCommand.new(117, 0, [id])] $game_troop.interpreter.setup(list) end Basically what you want to do is to make a call to $game_troop.interpreter.setup and pass along and array that consists of the RPG::EventCommand that will in turn call your common event. When you do this a new fiber is created in $game_troop's interpreter object and this fiber (a fiber is like a lightweight thread) executes the common event in parallel to the main thread that runs the battle. This is all you need to do execute a common event in battle. Take a look at RPG::Troop::Page and RPG::EventCommand for a better understanding of how it works. One thing though, if you want your common event to execute after the battle is supposed to end, say for example after your party killed all the enemies you need to take a few extra steps. The default implementation will just exit the battle even if a fiber (that could be executing for example a common event) is running. To change this first make the fiber instance variable readable in the Game_Interpreter class like this: class Game_Interpreter # Make fiber readable so we can check if an event is running # before finishing the battle. attr_reader :fiber end Then alias and change one method in BattleManager like this: alias_method :process_victory_cevent, :process_victory def process_victory return unless $game_troop.interpreter.fiber.nil? process_victory_cevent end Now the original process_victory is only run if there is no fiber running in $game_troop.interpreter.
  12. So what you are doing is that you are overriding the default implementation of the module method level in Vocab. You can call module methods in two ways: # first way Vocab.level # second way Vocab::level Your first line (puts Vocab::level(0)) will fail because the code will go in to the elsif statement and attempt to call the module method level again (in other words, it will try to call itself). However, this fails because there is no argument for that line, and it needs one argument because that's how you defined the level method. Your second line succeeds because it calls the module method level_a which does not require any argument (as defined in the editor). Personally, I prefer to use the dot syntax when calling a module method and not :: which is used for accessing constants in a module.
  13. Ok I think I know what you're missing. I don't think you are checking the enemy note tags correctly. Also, when is the play_snowbgm method called in your script? It looks like it's not called at all. So when you put text in an enemy's note box, that text is saved along with various other information about that enemy and put into a RPG::Enemy object at the start of the game. Notice that this RPG::Enemy object is not the same as the Game_Enemy object. You can think of the RPG::Enemy object as the data object that keeps track of all the data associated with a particular enemy (for example the note box text) and the Game_Enemy as the game object that has the state (variables), methods etc. So what you need to do is to check the note-box text for the enemies that you are about to fight, see if the text contains "snow_bgm" and call the play_snowbgm method if it does. Here's some code that does that: $game_troop.members.each do |game_enemy| id = game_enemy.enemy_id rpg_enemy = $data_enemies[id] note = rpg_enemy.note if note.include?("snow_bgm") play_snowbgm break end end So basically this code iterates over all Game_Enemy objects in $game_troop, gets their associated RPG::Enemy object (each Game_Enemy instance has one RPG::Enemy instance), gets the note text and finally checks if the text includes "snow_bgm" and calles play_snowbgm if it does. Put this code at the beginning of the pre_battle_scene method and I think that will work.
  14. Could you post your script? It's easier to see what you did wrong that way.
  15. kal

    Problem with map animations

    Try on a new project without any other scripts. If that doesn't work something is wrong with your VXA.
  16. kal

    Problem with map animations

    Are you sure about that? Paste this in the editor and do it below Sprite_Base class Sprite_Base alias update_animation_bugfix update_animation def update_animation set_animation_origin if animation? update_animation_bugfix end end
  17. kal

    Problem with map animations

    The version I'm talking about (link in the first post I made) is just copying the set_animation_origin method at the beginning of the update method. That's were I got my original version from. Still don't understand why it's so smooth in VX and choppy in Ace, but something must have changed one way or another.
  18. kal

    Problem with map animations

    Ah I see I missed that. Now looking at both version for a some time it seems like they are both slightly choppy, though yours is less so than mine. It seems like the animation is playing a little smoother when the screen is not moving in both cases. It's weird because in woratana's VX version I didn't notice any choppiness at all.
  19. kal

    Problem with map animations

    I don't know if you saw the edit I did or not so I'm gonna post again here.
  20. kal

    Problem with map animations

    Ah I see, you are right. Sorry, my bad. Looks like we have a solution then! Alright, I see what you mean with the slash now. It seems to me that there are some situations where it makes more sense for it to follow the event, and other's where it doesn't, so it's good to have both options available. What about posting this in the completed scripts section? Because I'm sure people are going to ask the same question about this animation quirk down the line. edit: hold on a sec It should be the same. Look at this: def animation? @animation != nil end Therefore, saying set_animation_origin if animation? is the exact same thing as saying set_animation_origin unless @animation.nil? Actually now I'm getting kinda confused here. Trying this again, it seems to me that there is no noticeable difference between the version I posted and the one Guile posted. I don't know why I thought it looked choppy before, it doesn't seem that way now. Can somebody else test this?
  21. kal

    Problem with map animations

    So a slash makes more sense if it moves away from the target as the player scrolls the screen? If I set an animation to be played on an event, then I expect it to stay in one place, and not move if the screen starts to move. At the very least there needs to be an option to choose whether the animation should stay in on place or move with the screen. Also, why post that code when it's the exact equivalent to what I posted?
  22. kal

    Problem with map animations

    That's funny I was just trying to fix this bug myself this morning. It is indeed annoying and after looking around a bit it seems like this bug was present in VX too. Here's a thread where Woratana posted a fix for VX: http://www.rpgrevolution.com/forums/?showtopic=14160 I tried this in VX and it works. I also adapted it for Ace, and while it does work, it is kinda choppy, and not as smooth as on VX. I haven't figured out why yet. I'll study the Sprite_Base class more and see if I can figure out why. If you want to try the code paste this in to the editor: class Sprite_Base alias_method :update_anim_fix_kal, :update def update set_animation_origin unless @animation.nil? update_anim_fix_kal end end
  23. Thanks I'm glad you guys found some use for it! I made a tiny update to the autoreload script in the OP. Now the code that is evaluated from the interactive console will be rescued if it throws an error, and the error message will be printed to the console instead of crashing the game. Because it is really really easy to do something wrong and crash the game when you play around with the console. (haha I have no idea why I didn't think of putting this in until now...)
  24. Basically with this you can send script commands to your game in real time without exiting the game, editing your scripts and then restarting the game. It's a little hard to explain, so I went ahead and made a video showing it here: The video quality isn't the best, but hopefully you can see what's going on.
  25. It looks like you ran into a pitfall with the way Ruby handles copying objects. This line: GF::ITEMBONUS::DEFAULT_GROWTH.clone will retrun a new copy of the DEFAULT_GROWTH hash, but the nested arrays inside the hash are shallow copies (meaning the reference to that object is copied, and you will not get a new object). Therefore, even though each item has it's own unique copy of the DEFAULT_GROWTH hash, when you change one of the values you access the nested arrays, which are shared among all growth hashes. That's why setting one value on an item will affect all the other items too. There are some tricks you can do to prevent this (for example you could serialize the hash and then deserialize it to achieve a deep copy) but in this case it's easier to just remove the nested arrays because you don't really need them. If you have a hash where both keys and values are plain old data (like numbers for example) everything will be copied when you do DEFAULT_GROWTH.clone. Here's a good article where you can learn more about duplicating objects in Ruby: http://ruby.about.co.../a/deepcopy.htm I went ahead and changed the script so have a look at the following to see how I did it: https://gist.github.com/1716887 I also made two other changes. First I changed your level_up_sum method to make it a little clearer (I prefer to use the enumerator methods (like each) to iterate over something rather than the for structure) and I removed the duplication you had in the RPG::Weapon and RPG::Armor classes. Remember duplication is evil! And in this case the solution is really simple, just put the code in their parent class RPG::EquipItem So I think the script should work as you intended now, though I didn't test it extensively.
×
Top ArrowTop Arrow Highlighted