I've been trying to work on my scripting skills and I've had a couple of issues with getting methods in new classes to be called. I dont understand what the difference between alias and the reserved word super is supposed to be. I've tried reading the RTP scripts to see, but when I try to emulate them and modify something, my methods never get called.
I played around with changing the color of the title HP in Window_BattleStatus so that when in battle, 'HP' is shown green. this was just a simple way to try to alias methods, though Im not sure if this is done correctly.
This DOES change the Name of 'HP', but I am curious if this would be no-no for compatibility? Essentially, I re-wrote def refresh so that it calls my method draw_colored_hp instead of draw_actor_hp.
Basically, in terms of compatibility and/or scripting ettiquette, is this acceptable?
HP_COLOR = Color.new(128, 255, 128, 255) # Green
# this constant is just here to make it configurable, even though
# it could be just placed below...
class Window_BattleStatus
# I doubt if I even need the alias, as the new refresh method
# directly calls the color edit method.
alias draw_colored_hp draw_actor_hp
def draw_colored_hp(actor, x, y, width = 144)
draw_actor_hp(actor, x, y, width = 144)
self.contents.font.color = HP_COLOR
self.contents.draw_text(x, y, 32, 32, $data_system.words.hp)
# This is to change the color of 'HP' or whatever the game
# calls it. As this is a test, I am just
# trying to see if this is acceptable practice.
end
def refresh
# This method directly calls the color edit method, so I don't
# think I need the alias above. However, if I do not change
# the refresh method to call my method, then mine never gets
# called, alias or not... I don't understand the right way to
# use alias maybe?
self.contents.clear
@item_max = $game_party.actors.size
for i in 0...$game_party.actors.size
actor = $game_party.actors
actor_x = i * 160 + 4
draw_actor_name(actor, actor_x, 0)
draw_colored_hp(actor, actor_x, 32, 120) #changed from draw_actor_hp
draw_actor_sp(actor, actor_x, 64, 120)
if @level_up_flags
self.contents.font.color = normal_color
self.contents.draw_text(actor_x, 96, 120, 32, "LEVEL UP!")
else
draw_actor_state(actor, actor_x, 96)
end
end
end
end
Using the super method calls the method from the parent class.
class Parent
def refresh
#this is the parent method
do stuff
end
end
class Child < Parent
def refresh
super # calls the refresh method from the parent class so do stuff would get called
do other stuff
end
end
Using alias is to avoid overwriting existing methods within a class. Aliasing stores the old method. That way if you have a method overwriting another one then you can simply call the old method.
class Stuff
def initialize
set_important_variables_here
end
end
class Stuff
alias call_old_init_method initialize
def initialize
set_more_important_variables
call_old_init_method # This calls the old initialize method
end
end
I tried to alias these methods to improve compatibility, as I am adding code to change the font color. However, when I test with this modified script, I get a NameError saying 'actor' is an undefined local variable or method. I hate to be asking the same n00bish question over and over again, but what would be the correct way to do this?
HP_COLOR = Color.new(128, 255, 128, 255) # Green
class Window_Base
alias draw_colored_hp draw_actor_hp #not sure if I need to add the arguments
#here or not.
def draw_actor_hp(actor, x, y, width = 144)
self.contents.font.color = HP_COLOR
self.contents.draw_text(x, y, 32, 32, $data_system.words.hp)
draw_colored_hp(actor, x, y, width = 144) #do I need this for calling the
#rest of the old method?
end
end
class Window_BattleStatus < Window_Base
alias refresh_with_color refresh
def refresh
draw_colored_hp(actor, x, y, width = 144) #this line is the cause of the
#error. If I remove the arguments
#though, I get an error that
#says "wrong number of arguments
#(0 for 3)!"
refresh_with_color
end
end
This is because there is no existing variable "actor" within that method. The way you are attempting to do it is most likely not going to work. So the best thing to do in some situations is to just rewrite/overwrite the method. In some cases its not bad. Also you wouldn't call draw_colored_hp, you would sitll call draw_actor_hp. draw_colored_hp isn't going to get your code called, its only going to call the old method. So aliasing the method in the battle status window is kinda pointless.
All you need is the rewrite for Window_Base's draw_actor_hp. Now when draw_actor_hp is called it will call your code. Also place the draw_colored_hp above all other code in the method. And replace the width = 144 with just width.
Thanks g_g! Finally, after a week of trying to understand alias, I finally get it. The reason I was messing up was because of the placement of draw_colored_hp in the aliased method. That made me overthink everything else and get a huge mess of code which in the end only showed the normal hp color. Now I can take this and apply it to the script I've been trying to write!
Correct code:
HP_COLOR = Color.new(128, 255, 128, 255) # Green
class Window_Base
alias draw_colored_hp draw_actor_hp
def draw_actor_hp(actor, x, y, width = 144)
draw_colored_hp(actor, x, y, width)
self.contents.font.color = HP_COLOR
self.contents.draw_text(x, y, 32, 32, $data_system.words.hp)
end
end
Also, now it makes sense why aliasing methods is good. Not only does it improve compatibility, it also shortens how much code you have to write...
You will find that once you get the hang of aliasing, so many new things are now possible.
I remember being all excited about it, too. :D
Well, I seem to be having the same sort of issue. This time I am aliasing 4 methods of Scene_Item. For some reason, it gives an error "undefined method 'refresh' for Nil:NilClass". There most certainly IS a refresh method, so I don't understand the problem!
Here is my script. Like other detail scripts, it waits until you are in Scene_Item and press Shift on a highlighted item. Then it is supposed to show the Window_Details. instead, it goes into error...
class Window_Details < Window_Base
#initialize method does the window setup.
def initialize
#call from Window_Base to create a window
super(0, 0, 640, 480)
#set up the text area inside the window
self.contents = Bitmap.new(width - 32, height - 32)
#now setup font size, type, and color
self.contents.font.name = 'Tahoma'
self.contents.font.size = 20
self.contents.font.color = normal_color #white
#z-layer makes the detail window appear overtop all the item windows
self.z += 10
@details = [] #declares a variable to hold the details information later
#call the refresh_reset method
refresh_reset
end
#Refresh_Reset clears the window so text doesnt overlap
# if you browse 2 or more details
def refresh_reset
#clears the window
self.contents.clear
end
# Refresh updates the @details variable with new info
def refresh
#set a local variable to the global $selected_item value
item = $selected_item.id
#tests to see if item is an Item
if $selected_item.is_a?(RPG::Item)
#get the item's name
item_name = $data_items[item].name
#get the item's icon
item_icon = $data_items[item].icon_name
#and set a variable to hold the file name. named the same as other
#detail scripts in case you want to use this one (for some reason)
#and dont want to re-write all your lore (though your formatting
#will likely need to be edited.)
@detail_file = File.open("Data/Item_Detail.rxdata")
#tests to see if the item is a Weapon
elsif $selected_item.is_a?(RPG::Weapon)
#get the name, icon, and file name as before
item_name = $data_weapons[item].name
item_icon = $data_weapons[item].icon_name
@detail_file = File.open("Data/Weapon_Detail.rxdata")
#tests to see if the item is an Armor
elsif $selected_item.is_a?(RPG::Armor)
#and again, fetch name and icon
item_name = $data_armors[item].name
item_icon = $data_armors[item].icon_name
@detail_file = File.open("Data/Armor_Detail.rxdata")
end
#sets a variable to hold the icon
bitmap = RPG::Cache.icon(item_icon) #should not need .png extension as
#another scripter mentioned that RMXP
#automatically loads the correct file
#format. this will only conflict (in
#theory) if there are multiple icons
#with the same name, but different formats.
#now draw the icon!
self.contents.blt(5, 8, bitmap, Rect.new(0, 0, 24, 24), 255)
self.contents.draw_text(32, 8, 256, 24, item_name)
#now call the method that actually gets and displays the detailed info!
fetch_details(item)
show_details(5, 22)
end
#this method here grabs the details from the files. Similar to Jackatrade's
#original script and DerVVulfman's Grouping and Details script, this uses
# three different files for details. Each description can hold 10 lines of
#text, though in the next version, I will try to update this so it can tell
#automagically how many lines to use, and also make the window scrollable !!!
#Note: due to limitations (my current scripting ability) I can not add sweet
#features to make this desirable. Keep in mind that 1.0 of this is only to
#be a simplistic and very basic system. I hope to add more later!
def fetch_details(item)
#Im not really familiar with arrays. so Im just setting one up that can
#store the detail lines thouth I doubt its used efficiently. I'm sure that
#other scripters can see how to do this better.
#Here I cheated and looked at Jackatrade's original script to see how he
#set this up.
#array that holds details:
description =
[
item * 10,
item * 10 + 1,
item * 10 + 2,
item * 10 + 3,
item * 10 + 4,
item * 10 + 5,
item * 10 + 6,
item * 10 + 7,
item * 10 + 8,
item * 10 + 9,
]
#stores all lines into variable
@details = @detail_file.readlines
#now set up !?Another?! variable used for processing the data.
@desc = []
#now set it to hold only 10 lines at a time!
for i in 0..9
@desc[i] = @details[description[i]]
end
#finished! now run the display details method!
return
end
def show_details(x, y)
#draw the Description title (next version this will be easier to change!)
self.contents.draw_text(x, y - 20, 640, 32, "Description:")
#draw all 10 lines normally
for i in 0..9
self.contents.draw_text(x, y, 640, 32, @desc[i])
y += 20
end
#method end
end
#class end
end
#==============================================================================#
class Scene_Item
alias detail_main main
def main
detail_main
@detail_window = Window_Details.new
@detail_window.visible = false
@detail_window.active = false
@detail_window.dispose
end
alias detail_update update
def update
detail_update
$selected_item = @item_window.item
if @item_window.active
update_item
return
end
if @detail_window.active
update_details
return
end
end
def update_details
if Input.trigger?(Input::B)
$game_system.se_play($data_system.cancel_se)
unless $game_party.item_can_use?(@item.id)
@item_window.refresh
end
@item_window.active = true
@detail_window.visible = false
@detail_window.active = false
@detail_window.refresh_reset
return
end
end
alias new_update_item update_item
def update_item
new_update_item
if Input.trigger?(Input::A)
# Below if-then statement checks for blank item, and cancels
# input effect is true.
if $selected_item == nil
return
end
$game_system.se_play($data_system.decision_se)
@detail_window.refresh #for some reason, it crashes here
@item_window.active = false
@detail_window.visible = true
@detail_window.active = true
end
end
end
Any ideas on what I am doing wrong?
These lines here
@detail_window = Window_Details.new
@detail_window.visible = false
@detail_window.active = false
Will need to go above detail_main.
In some cases you'll need to call the alias first, other cases last, and some cases you'll need it in between code.
@detail_window = Window_Details.new
@detail_window.visible = false
@detail_window.active = false
detail_main
@detail_window.dispose
The old method "detail_main" Contains a loop statement. So its going to keep updating until the scene is changed. When it breaks out of the loop, that is when your detail window would have been made.
Thanks again g_g! Now it works enough that I can fine tune it, and slowly build on it.
But how do you know when to place an alias after or between code?
If you're aliasing scenes and you're adding new windows, place it in the middle and the window call above it and the dispose below.
If you're doing edits to battle algorithms or special skills you'll probably want to put the alias at the bottom. Now if you're redrawing something like in your battle window then put it above. Its kinda a trial and error too ya know?
think of an alias as exactly that and object exists independently of it's name just as a person dose. so using alias you can give an object a new name and then give that old name to a new object. which can in turn call the old object via the new name.
use alias when you need to add code to the begin or end of an existing function instead of overriding it.
now I get an error saying it can't convert nil to String for this line:
self.contents.draw_text(x, y, 640, 32, @desc[i])
I've spent the last two hours trying to get this to work by tweaking different parts of the script, but to no avail. its driving me nuts...
@desc equals nil. You are either using an index that is out of range, or whatever sets the values is buggy. Start backtracing that variable, and place "p @desc" at certain intervals to see what that variable is equal to at different points, and see where it goes wrong. I would actually start with having it print "i" in the enumeration to see whats going on there.
Thanks Zer0. so what is happening is the script is trying to find the data in the text file. if the file has blank lines being read, the system interprets those as nil. also, if the text file doesnt contain text for a topic, it will also yeild nil.
so now I am going to figure out a way to read an index of @desc and see if it is nil. if it is, I will turn it into an empty string like ' 'in the for loop that held the error I just had to:
#code for show_details(x, y)
for i in 0..9
if @desc[i] == nil #check and see if iteration is nil
@desc[i] = ' ' #if it is, change it to string
else #but if not, carry on as normal!
self.contents.draw_text(x, y, 640, 32, @desc[i])
end
end
#rest of show_details(x, y)
@desc.each_index {|i| self.contents.draw_text(x, y, 640, 32, @desc[i] == nil ? ' ' : @desc[i])}
There's a slightly shorter way of doing it, though it will not actually change the value in @desc. It will simply use an empty string if the value is nil.