First, read SCRIPTING. Since an example is more worth than a thousand words, here is a quick introduction in the form of a script that handles input files from ARGV, does nothing with them, and then saves them back to the same file from which they came from.
#!/usr/bin/env nwn-dsl ARGV.each_with_index {|file, index| # The script will abort hard if file is not an ARE. # This looks up the data_type, which are usually the # first four bytes of a file. gff = need file, :are # This will save gff back to 'file'. save gff # This will prefix all lines printed with log with # a percentage. It's usage is optional, and the sole # reason for using each_with_index instead of each above. progress index }
To run this example, type the following in a directory containing some .are files, with script.rb being the name you saved the above script in.
nwn-dsl path/to/script.rb *.are
The shebang (#!/..) is for unixoid systems and Cygwin, and as such optional.
For all available commands to nwn-dsl scripts, see NWN::Gff::Scripting.
All code snippets shown here assume that you put include NWN
in your application. It is not needed for nwn-dsl, which imports that namespace for you.
You can access GFF paths within structs by using the overloaded division operator.
#!/usr/bin/env nwn-dsl ARGV.each_with_index {|file, index| gff = need file, :are log (gff / 'Name/0') progress index }
For a full reference of the path syntax, see NWN::Gff::Struct#by_path.
You cannot use paths to assign new values to structs - you can only modify existing values:
(gff / 'Name').v[0] = "New Localized Name with Language ID 0"
Wrong, will raise error:
(gff / 'Name/0') = "New .."
Please note that using the [] method will NOT evaluate paths, just access labels in the CURRENT struct (which is actually a hash).
You can add new fields to existing structs via NWN::Gff::Struct#add_field:
gff.add_field 'LocalVersion', :int, 1 # This will print "1" log (gff / 'LocalVersion$')
You can also the dynamic methods to save some typing:
gff.add_int 'LocalVersion', 1
You can just as well create whole GFF structures on the fly:
Gff::Struct.new do |s| s.add_byte 'ImaByte', :int, 1 list = s.add_list 'ImaList', [] do |l| l.add_struct(1) do |ss| ss.add_byte 'ImaByteToo', 2 end end end
Further reading: NWN::Gff::Struct#add_field, NWN::Gff::List#add_struct.
Working with TwoDA files is easy and painless:
#!/usr/bin/env nwn-dsl data = IO.read('path/to/baseitems.2da') table = TwoDA::Table.parse(data) # This will print "twobladedsword" with NWN1 1.69 log table[12].Label # You can re-format (and save) any valid table: File.open("/tmp/out", "wb") {|f| f.write(table.to_2da) # OR table.write_to(f) }
For more documentation, see NWN::TwoDA::Table and NWN::TwoDA::Row.
You can also set up a default location for 2da files, after which you can simply use the following to read 2da files:
table = TwoDA.get('baseitems')
You can set up the TwoDA::Cache by either setting the environment variable (recommended, see SETTINGS), or like this (see NWN::TwoDA::Cache.setup):
TwoDA::Cache.setup("path_a:path_b/blah:path_c")
You can access individual .tlk files, or use a TlkSet, which will emulate the way NWN1/2 reads it’s .tlk files.
Read a simple .tlk file:
io = File.open("/path/to/dialog.tlk", "rb") tlk = NWN::Tlk::Tlk.new(io)
Note that Tlk::Tlk seeks and reads from io
as needed, so if you close the file handle, any further accesses will fail.
# Retrieve strref 12 tlk[12][:text] # Retrieve the attached sound resref, if any: tlk[12][:sound] # prints the highest strref used log tlk.highest_id # Add a new strrref. new_strref = tlk.add 'New text' # And save the new TLK somewhere else. File.open("/tmp/new.tlk", "wb") {|another_io| tlk.write_to(another_io) }
Now read-only access with a TlkSet:
# The arguments are dialog.tlk, dialogf.tlk, # custom.tlk and a customf.tlk each wrapped in # a Tlk::Tlk as shown above. set = Tlk::TlkSet.new(tlk, tlkf, custom, customf) # Retrieve str_ref 12 as the female variant (dialogf.tlk) # if present, :male otherwise. log set[12, :female]
You cannot use TlkSet to write out .tlk files or modify existing entries - it is merely a wrapper.
A key file is an index into all shipped game resources.
key = Key::Key.new(File.new("/path/to/chitin.key", "r"), "/path/to/")
This will lookup all indexed bif files and the resources contained within.
The resource manager can be used to simulate a NWN-style resource manager to read files from .key/bifs, override, haks, and similar sources. Files are located in the reverse order that their containers are added to the Manager.
Example detailing the usual NWN lookup procedure:
nwn_path = "/path/to/nwn/" mgr = Resources::Manager.new # First, all the base data files. for key in %w{chitin.key xp1.key xp1patch.key xp2.key xp2patch.key xp3.key} mgr.add_container(Key::Key.new(File.new(nwn_path + key, "r"), nwn_path)) end # Override mgr.add_container(Resources::DirectoryContainer.new(nwn_path + "override")) # All custom haks for hak in %w{a.hak b.hak c.hak} mgr.add_container(Erf::Erf.new(File.new(nwn_path + "hak/" + hak, "r"))) end # Now you can retrieve any indexed file: puts mgr.get("actions.2da")
Note that initialising the whole Resource Manager this way takes a few seconds depending on IO and CPU speed.