nwn-lib 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/BINARIES +35 -0
- data/CHANGELOG +9 -0
- data/CHEATSHEET +50 -0
- data/README +1 -4
- data/Rakefile +4 -4
- data/bin/nwn-gff-import +54 -0
- data/bin/nwn-gff-irb +10 -0
- data/bin/nwn-gff-print +7 -1
- data/lib/nwn/gff.rb +52 -23
- data/lib/nwn/helpers.rb +180 -0
- data/lib/nwn/twoda.rb +18 -6
- metadata +9 -2
data/BINARIES
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
== nwn-gff-print
|
2
|
+
|
3
|
+
Usage: nwn-gff-print [options] file/- [path]
|
4
|
+
-y, --yaml Dump as yaml
|
5
|
+
-k, --kivinen Dump as kivinens dump format (like the perl tools)
|
6
|
+
-f, --kivinen-full-path Print the full path (implies -k)
|
7
|
+
-m, --marshal Native ruby marshalling. Warning: raw bytes
|
8
|
+
-t, --types Dump types as well (only applies to -k, for now)
|
9
|
+
--type filetype Override the file type (implies --struct 0xffffffff)
|
10
|
+
--struct id Override struct id (as hex, please)
|
11
|
+
|
12
|
+
This prints out the given file (or stdin, if -) as the specified format (-k, -f, -y, -m).
|
13
|
+
[-k] is the same as gffprint.pl
|
14
|
+
[-f] is the same as gffprint.pl -t
|
15
|
+
[-m] is the native ruby marshal format (_Marshal.dump(obj)_)
|
16
|
+
[-y] is standard YAML
|
17
|
+
|
18
|
+
== nwn-gff-import
|
19
|
+
|
20
|
+
Usage: nwn-gff-import [options] file/- outfile/-
|
21
|
+
-y, --yaml Import as yaml
|
22
|
+
-m, --marshal Import as native ruby marshal data
|
23
|
+
|
24
|
+
This is the equivalent to gffencode.pl, and takes all output formats that nwn-gff-print
|
25
|
+
provides. This can be used to transform marshalled gff data back into gff binary data.
|
26
|
+
|
27
|
+
Example (save to run on a shell, prints out hex data):
|
28
|
+
|
29
|
+
nwn-gff-print -y my_item.uti | nwn-gff-import.rb -y - - | xxd
|
30
|
+
|
31
|
+
== nwn-gff-irb
|
32
|
+
|
33
|
+
Usage: nwn-gff-irb file
|
34
|
+
|
35
|
+
nwn-gff-irb allows interactive editing of gff files. There are some examples on the CHEATSHEET.
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== 0.3.0
|
2
|
+
|
3
|
+
* Binary: nwn-gff-import
|
4
|
+
* Binary: nwn-gff-print supports -m, ruby marshalling
|
5
|
+
* helper: NWN::Gff::Helpers.item_property
|
6
|
+
* TwoDA: Cache, more compatibility to slightly broken 2da files
|
7
|
+
* CExoLocString: now has its own proper class for a more convenient API
|
8
|
+
* Gff::Struct: has changed its internal API (transparent to the outside)
|
9
|
+
|
1
10
|
=== 0.2.2
|
2
11
|
|
3
12
|
* Binary: nwn-gff-irb: interactive gff inspection/editing shell
|
data/CHEATSHEET
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
==== Extract item from creature inventory
|
2
|
+
|
3
|
+
nwn-gff-print --type UTI -k -t creature.bic 'ItemList[12]' | gffencode.pl -o item.uti
|
4
|
+
|
5
|
+
==== Quick-edit a item
|
6
|
+
|
7
|
+
your/prompt$ nwn-gff-irb my_item.uti
|
8
|
+
Your GFF file is in `GFF' (type: "UTI ").
|
9
|
+
Type `save' to save to the filename it came from (make a backup!), `exit' (or Ctrl+D) to exit (without saving).
|
10
|
+
To save to a different location, type `save "path/to/new/location.ext"'.
|
11
|
+
|
12
|
+
irb(main):001:0> GFF.root_struct.keys
|
13
|
+
=> ["ModelPart2", "Cursed", "ModelPart3", "Cost", "StackSize", "Charges", "Comment", "PaletteID", "BaseItem", "Tag", "DescIdentified", "Identified", "Plot", "TemplateResRef", "PropertiesList", "AddCost", "Stolen", "Description", "LocalizedName", "ModelPart1"]
|
14
|
+
|
15
|
+
irb(main):002:0> GFF['LocalizedName/4']
|
16
|
+
=> "Mundane Feuerpfeile (aus Eibe)"
|
17
|
+
|
18
|
+
irb(main):003:0> GFF['LocalizedName/4'] = 'New Name'
|
19
|
+
=> "New Name"
|
20
|
+
|
21
|
+
irb(main):004:0> GFF['LocalizedName']
|
22
|
+
=> #<NWN::Gff::Element:0xb790641c @type=:cexolocstr, @value=[#<struct NWN::Gff::CExoLocString language=4, text="New Name">, #<struct NWN::Gff::CExoLocString language=0, text="Pfeil">], @_str_ref=1517, @label="LocalizedName">
|
23
|
+
|
24
|
+
irb(main):005:0> save
|
25
|
+
Saving to `/home/elven/code/nwn/nwn-lib.git/my_item.uti' ..
|
26
|
+
saved.
|
27
|
+
=> nil
|
28
|
+
|
29
|
+
irb(main):006:0> exit
|
30
|
+
|
31
|
+
==== Add a item property, the semi-human way
|
32
|
+
|
33
|
+
irb(main):001:0> Helpers.item_property('Cast_Spell')
|
34
|
+
ArgumentError: Property Cast_Spell needs subtype of type IPRP_SPELLS, but none given.
|
35
|
+
from /var/lib/gems/1.8/gems/nwn-lib-0.2.3/lib/nwn/helpers.rb:108:in `item_property'
|
36
|
+
from (irb):1
|
37
|
+
|
38
|
+
irb(main):002:0> Helpers.item_property('Cast_Spell', 'Invisib')
|
39
|
+
ArgumentError: Cannot resolve invisib. Partial matches: ["Improved_Invisibility", "Invisibility", "Invisibility_Purge", "Invisibility_Sphere", "See_Invisibility"].
|
40
|
+
from /var/lib/gems/1.8/gems/nwn-lib-0.2.3/lib/nwn/helpers.rb:79:in `resolve_or_match_partial'
|
41
|
+
from /var/lib/gems/1.8/gems/nwn-lib-0.2.3/lib/nwn/helpers.rb:116:in `item_property'
|
42
|
+
from (irb):2
|
43
|
+
|
44
|
+
irb(main):003:0> Helpers.item_property('Cast_Spell', 'Invisibility')
|
45
|
+
ArgumentError: Property Cast_Spell requires a cost value of type IPRP_CHARGECOST, but none given
|
46
|
+
from /var/lib/gems/1.8/gems/nwn-lib-0.2.3/lib/nwn/helpers.rb:122:in `item_property'
|
47
|
+
from (irb):3
|
48
|
+
|
49
|
+
irb(main):004:0> Helpers.item_property('Cast_Spell', 'Invisibility', 'Unlimi')
|
50
|
+
=> {"PropertyName"=>#<NWN::Gff::Element:0xb75f4e68 ..
|
data/README
CHANGED
@@ -5,12 +5,9 @@ This package provides a library for reading, changing, and writing common file f
|
|
5
5
|
They should work with NWN2 just as well, since the file format specifications did not change.
|
6
6
|
|
7
7
|
There are various things included in this distribution:
|
8
|
-
* some binaries under bin/
|
8
|
+
* some binaries under bin/ (see BINARIES)
|
9
9
|
* the actual library under lib/nwn
|
10
10
|
|
11
|
-
== Supplied binaries
|
12
|
-
* nwn-gff-print
|
13
|
-
|
14
11
|
== Quickstart
|
15
12
|
|
16
13
|
It is easiest to just use the gem, which is available through rubyforge (<tt>sudo gem install nwn-lib</tt>).
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ include FileUtils
|
|
9
9
|
# Configuration
|
10
10
|
##############################################################################
|
11
11
|
NAME = "nwn-lib"
|
12
|
-
VERS = "0.
|
12
|
+
VERS = "0.3.0"
|
13
13
|
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
|
14
14
|
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
15
15
|
'nwn-lib: a ruby library for accessing NWN resource files', \
|
@@ -18,7 +18,7 @@ RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
|
18
18
|
Rake::RDocTask.new do |rdoc|
|
19
19
|
rdoc.rdoc_dir = "rdoc"
|
20
20
|
rdoc.options += RDOC_OPTS
|
21
|
-
rdoc.rdoc_files.add ["README", "CHEATSHEET", "CHANGELOG", "COPYING", "doc/*.rdoc", "lib/**/*.rb"]
|
21
|
+
rdoc.rdoc_files.add ["README", "BINARIES", "CHEATSHEET", "CHANGELOG", "COPYING", "doc/*.rdoc", "lib/**/*.rb"]
|
22
22
|
end
|
23
23
|
|
24
24
|
desc "Packages up nwn-lib"
|
@@ -30,14 +30,14 @@ spec = Gem::Specification.new do |s|
|
|
30
30
|
s.version = VERS
|
31
31
|
s.platform = Gem::Platform::RUBY
|
32
32
|
s.has_rdoc = true
|
33
|
-
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"] + Dir["doc/*.rdoc"]
|
33
|
+
s.extra_rdoc_files = ["README", "BINARIES", "CHEATSHEET", "CHANGELOG", "COPYING"] + Dir["doc/*.rdoc"]
|
34
34
|
s.rdoc_options += RDOC_OPTS + ["--exclude", "^(examples|extras)\/"]
|
35
35
|
s.summary = "a ruby library for accessing Neverwinter Nights resource files"
|
36
36
|
s.description = s.summary
|
37
37
|
s.author = "Bernhard Stoeckner"
|
38
38
|
s.email = "elven@swordcoast.net"
|
39
39
|
s.homepage = "http://nwn-lib.elv.es"
|
40
|
-
s.executables = ["nwn-gff-print", "nwn-gff-irb"]
|
40
|
+
s.executables = ["nwn-gff-print", "nwn-gff-irb", "nwn-gff-import"]
|
41
41
|
s.required_ruby_version = ">= 1.8.4"
|
42
42
|
s.files = %w(COPYING CHANGELOG README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
43
43
|
s.require_path = "lib"
|
data/bin/nwn-gff-import
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'optparse'
|
4
|
+
require 'nwn/gff'
|
5
|
+
require 'nwn/helpers'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
format = nil
|
9
|
+
|
10
|
+
OptionParser.new do |o|
|
11
|
+
o.banner = "Usage: nwn-gff-import [options] file/- outfile/-"
|
12
|
+
o.on "-y", "--yaml", "Import as yaml" do
|
13
|
+
format = :yaml
|
14
|
+
end
|
15
|
+
o.on "-m", "--marshal", "Import as native ruby marshal data" do
|
16
|
+
format = :marshal
|
17
|
+
end
|
18
|
+
end.parse!
|
19
|
+
|
20
|
+
infile = ARGV.shift or begin
|
21
|
+
$stderr.puts "Required argument: filename to read, or - for stdin (try -h)."
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
outfile = ARGV.shift or begin
|
25
|
+
$stderr.puts "Required argument: filename to write, or - for stdout (try -h)."
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
if infile == "-"
|
30
|
+
inbytes = $stdin.read
|
31
|
+
else
|
32
|
+
inbytes = IO.read(infile)
|
33
|
+
end
|
34
|
+
|
35
|
+
data = nil
|
36
|
+
|
37
|
+
case format
|
38
|
+
when :yaml
|
39
|
+
data = YAML.load(inbytes)
|
40
|
+
when :marshal
|
41
|
+
data = Marshal.load(inbytes)
|
42
|
+
else
|
43
|
+
raise ArgumentError, "Unknown format; try -h"
|
44
|
+
end
|
45
|
+
|
46
|
+
raise ArgumentError, "Input stream is NOT a valid gff object" unless
|
47
|
+
data.is_a?(NWN::Gff::Gff)
|
48
|
+
|
49
|
+
outbytes = NWN::Gff::Writer.dump(data)
|
50
|
+
if outfile == "-"
|
51
|
+
puts outbytes
|
52
|
+
else
|
53
|
+
File.open(outfile, "w") {|f| f.write(outbytes) }
|
54
|
+
end
|
data/bin/nwn-gff-irb
CHANGED
@@ -5,6 +5,7 @@ require 'nwn/gff'
|
|
5
5
|
require 'nwn/twoda'
|
6
6
|
require 'yaml'
|
7
7
|
require 'irb'
|
8
|
+
require 'irb/completion'
|
8
9
|
|
9
10
|
OptionParser.new do |o|
|
10
11
|
o.banner = "Usage: nwn-gff-irb file"
|
@@ -34,7 +35,16 @@ def save destination = nil
|
|
34
35
|
puts "saved."
|
35
36
|
end
|
36
37
|
|
38
|
+
if __cache_location = ENV['NWN2DAHOME']
|
39
|
+
require 'nwn/helpers'
|
40
|
+
puts "Setting up 2da cache at #{__cache_location} .."
|
41
|
+
NWN::TwoDA::Cache.setup __cache_location
|
42
|
+
else
|
43
|
+
puts "Warning: Environment variable `NWN2DAHOME' is not available, not using 2da cache (helpers not available)."
|
44
|
+
end
|
45
|
+
|
37
46
|
include NWN::Gff
|
47
|
+
include NWN::TwoDA
|
38
48
|
|
39
49
|
puts "Your GFF file is in `GFF' (type: #{GFF.type.inspect})."
|
40
50
|
puts "Type `save' to save to the filename it came from (make a backup!), `exit' (or Ctrl+D) to exit (without saving)."
|
data/bin/nwn-gff-print
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'rubygems'
|
3
3
|
require 'optparse'
|
4
4
|
require 'nwn/gff'
|
5
|
+
require 'nwn/helpers'
|
5
6
|
require 'yaml'
|
6
7
|
|
7
8
|
format = nil
|
@@ -19,9 +20,12 @@ OptionParser.new do |o|
|
|
19
20
|
format = :kivinen
|
20
21
|
end
|
21
22
|
o.on "-f", "--kivinen-full-path", "Print the full path (implies -k)" do
|
22
|
-
|
23
|
+
format = :kivinen
|
23
24
|
full = true
|
24
25
|
end
|
26
|
+
o.on "-m", "--marshal", "Native ruby marshalling. Warning: raw bytes" do
|
27
|
+
format = :marshal
|
28
|
+
end
|
25
29
|
o.on "-t", "--types", "Dump types as well (only applies to -k, for now)" do
|
26
30
|
types_too = true
|
27
31
|
end
|
@@ -69,6 +73,8 @@ case format
|
|
69
73
|
NWN::Gff.kivinen_format g, "/", types_too, full, file_type, struct_id do |label, value|
|
70
74
|
puts "%s:\t%s" % [label, value]
|
71
75
|
end
|
76
|
+
when :marshal
|
77
|
+
puts Marshal.dump(g)
|
72
78
|
else
|
73
79
|
puts "Unknown format; try -h"
|
74
80
|
end
|
data/lib/nwn/gff.rb
CHANGED
@@ -98,8 +98,8 @@ module NWN
|
|
98
98
|
|
99
99
|
when :cexolocstr
|
100
100
|
|
101
|
-
s.value.each {|vv|
|
102
|
-
yield(prefix + s.label + "/" +
|
101
|
+
s.value.each {|kk,vv|
|
102
|
+
yield(prefix + s.label + "/" + kk.to_s, vv.gsub(/([\000-\037\177-\377%])/) {|v| "%" + v.unpack("H2")[0] })
|
103
103
|
}
|
104
104
|
yield(prefix + s.label + ". ____string_ref", s._str_ref)
|
105
105
|
|
@@ -372,25 +372,52 @@ end
|
|
372
372
|
|
373
373
|
# A Gff::Struct is a hash of label->Element pairs,
|
374
374
|
# with an added +.struct_id+.
|
375
|
-
class NWN::Gff::Struct
|
375
|
+
class NWN::Gff::Struct
|
376
376
|
attr_accessor :struct_id
|
377
|
-
|
377
|
+
attr_accessor :hash
|
378
|
+
|
379
|
+
def initialize
|
378
380
|
@struct_id = 0
|
381
|
+
@hash = {}
|
379
382
|
super
|
380
383
|
end
|
384
|
+
|
385
|
+
def method_missing meth, *a
|
386
|
+
@hash.method(meth).call(*a)
|
387
|
+
end
|
381
388
|
end
|
382
389
|
|
383
390
|
# A CExoLocString is a localised CExoString.
|
384
|
-
#
|
385
|
-
#
|
386
|
-
#
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
391
|
+
# It contains pairs of language => text pairs,
|
392
|
+
# where language is a language_id as specified in the GFF
|
393
|
+
# documentation pdf.
|
394
|
+
class NWN::Gff::CExoLocString
|
395
|
+
attr_reader :languages
|
396
|
+
def initialize
|
397
|
+
@languages = {}
|
398
|
+
end
|
399
|
+
|
400
|
+
# Retrieve the text for a given language.
|
401
|
+
# Returns "" if no text is set for the given
|
402
|
+
# language.
|
403
|
+
def [] language_id
|
404
|
+
@languages[language_id.to_i] || ""
|
405
|
+
end
|
406
|
+
|
407
|
+
# Sets a new language text.
|
408
|
+
def []= language_id, text
|
409
|
+
@languages[language_id.to_i] = text
|
410
|
+
end
|
411
|
+
|
412
|
+
def size
|
413
|
+
@languages.reject {|k,v| v == ""}.size
|
414
|
+
end
|
415
|
+
|
416
|
+
def each
|
417
|
+
@languages.reject {|k,v| v == ""}.each {|k,v|
|
418
|
+
yield k, v
|
419
|
+
}
|
420
|
+
end
|
394
421
|
end
|
395
422
|
|
396
423
|
# A class that parses binary GFF bytes into ruby-friendly data structures.
|
@@ -540,20 +567,20 @@ class NWN::Gff::Reader
|
|
540
567
|
@field_data[data_or_offset + 1, len]
|
541
568
|
|
542
569
|
when :cexolocstr
|
570
|
+
exostr = Gff::CExoLocString.new
|
543
571
|
total_size, str_ref, str_count =
|
544
572
|
@field_data[data_or_offset, 12].unpack("VVV")
|
545
573
|
all = @field_data[data_or_offset + 12, total_size]
|
546
574
|
field._str_ref = str_ref
|
547
575
|
|
548
|
-
r = []
|
549
576
|
str_count.times {
|
550
577
|
id, len = all.unpack("VV")
|
551
578
|
str = all[8, len].unpack("a*")[0]
|
552
579
|
all = all[(8 + len)..-1]
|
553
|
-
|
580
|
+
exostr[id] = str
|
554
581
|
}
|
555
582
|
len = total_size + 4
|
556
|
-
|
583
|
+
exostr
|
557
584
|
|
558
585
|
when :void
|
559
586
|
len = @field_data[data_or_offset, 4].unpack("V")[0]
|
@@ -763,24 +790,26 @@ private
|
|
763
790
|
@field_data << [v.value.size, v.value].pack("Va*")
|
764
791
|
|
765
792
|
when :cexolocstr
|
766
|
-
raise GffError, "type = cexolocstr, but value not
|
767
|
-
v.value.is_a?(
|
793
|
+
raise GffError, "type = cexolocstr, but value not a exolocstr" unless
|
794
|
+
v.value.is_a?(NWN::Gff::CExoLocString)
|
768
795
|
|
769
796
|
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
770
797
|
|
771
798
|
# total size (4), str_ref (4), str_count (4)
|
772
|
-
total_size = 8
|
799
|
+
total_size = 8
|
800
|
+
v.value.each {|kk,vv|
|
801
|
+
total_size += vv.size + 8
|
802
|
+
}
|
773
803
|
@field_data << [
|
774
804
|
total_size,
|
775
805
|
v._str_ref,
|
776
806
|
v.value.size
|
777
807
|
].pack("VVV")
|
778
808
|
|
779
|
-
v.value.each {|
|
780
|
-
@field_data << [
|
809
|
+
v.value.each {|k,v|
|
810
|
+
@field_data << [k, v.size, v].pack("VVa*")
|
781
811
|
}
|
782
812
|
|
783
|
-
|
784
813
|
else
|
785
814
|
raise GffError, "Unknown data type: #{v.type}"
|
786
815
|
end
|
data/lib/nwn/helpers.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'nwn/gff'
|
2
|
+
require 'nwn/twoda'
|
3
|
+
|
4
|
+
module NWN
|
5
|
+
|
6
|
+
module TwoDA
|
7
|
+
|
8
|
+
# This is a simple 2da cache.
|
9
|
+
module Cache
|
10
|
+
|
11
|
+
@_cache = {}
|
12
|
+
@_root = nil
|
13
|
+
|
14
|
+
# Set the file system path where all 2da files reside.
|
15
|
+
# Call this on application startup.
|
16
|
+
def self.setup root
|
17
|
+
@_root = root
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the 2da file with the given name. +name+ is without extension.
|
21
|
+
def self.get(name)
|
22
|
+
raise Exception, "You need to set up the cache first through Cache.setup." unless @_root
|
23
|
+
@_cache[name.downcase] ||= read_2da(name.downcase)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.read_2da name # :nodoc:
|
27
|
+
Table.parse IO.read(@_root + '/' + name + '.2da')
|
28
|
+
end
|
29
|
+
|
30
|
+
private_class_method :read_2da
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Gff
|
35
|
+
module Helpers
|
36
|
+
|
37
|
+
# This sets up the IPRP cache. Used internally; no need to call this yourself.
|
38
|
+
def self._ip_cache_setup #:nodoc:
|
39
|
+
return if defined? @costtables
|
40
|
+
@costtables = {}
|
41
|
+
@paramtables = {}
|
42
|
+
@costtable_index = NWN::TwoDA::Cache.get('iprp_costtable')
|
43
|
+
@paramtable_index = NWN::TwoDA::Cache.get('iprp_paramtable')
|
44
|
+
@costtable_index.by_col('Name').each_with_index {|p,idx|
|
45
|
+
next if @costtables[p.downcase]
|
46
|
+
@costtables[p.downcase] = @costtables[idx] = NWN::TwoDA::Cache.get(p.downcase)
|
47
|
+
}
|
48
|
+
@paramtable_index.by_col('TableResRef').each_with_index {|p,idx|
|
49
|
+
next if @paramtables[p.downcase]
|
50
|
+
@paramtables[p.downcase] = @paramtables[idx] = NWN::TwoDA::Cache.get(p.downcase)
|
51
|
+
}
|
52
|
+
@properties = NWN::TwoDA::Cache.get('itemprops')
|
53
|
+
@propdef = NWN::TwoDA::Cache.get('itempropdef')
|
54
|
+
@subtypes = []
|
55
|
+
@propdef.by_col('SubTypeResRef').each_with_index {|st, idx|
|
56
|
+
@subtypes[idx] = NWN::TwoDA::Cache.get(st.downcase) if st != "****"
|
57
|
+
}
|
58
|
+
@prop_id_to_costtable = []
|
59
|
+
@propdef.by_col('CostTableResRef').each_with_index {|st, idx|
|
60
|
+
@prop_id_to_costtable[idx] = st.to_i if st != "****"
|
61
|
+
}
|
62
|
+
@prop_id_to_param1 = []
|
63
|
+
@propdef.by_col('Param1ResRef').each_with_index {|st, idx|
|
64
|
+
@prop_id_to_param1[idx] = st.to_i if st != "****"
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.resolve_or_match_partial name_spec, list #:nodoc:
|
69
|
+
name_spec = name_spec.downcase
|
70
|
+
|
71
|
+
list.each {|l|
|
72
|
+
return l if l.downcase == name_spec
|
73
|
+
}
|
74
|
+
|
75
|
+
substrings = list.select {|l| l.downcase.index(name_spec) }
|
76
|
+
if substrings.size == 1
|
77
|
+
return substrings[0]
|
78
|
+
elsif substrings.size > 1
|
79
|
+
raise ArgumentError, "Cannot resolve #{name_spec}. Partial matches: #{substrings.inspect}."
|
80
|
+
end
|
81
|
+
|
82
|
+
raise ArgumentError, "Cannot resolve #{name_spec}."
|
83
|
+
end
|
84
|
+
|
85
|
+
# This creates a NWN::Gff::Struct describing the item property in question.
|
86
|
+
#
|
87
|
+
# [+name+] The iprp name to resolve, for example <tt>Damage_Bonus_vs_Racial_Group</tt>
|
88
|
+
# [+subtype+] The iprp subtype, for example <tt>Elf</tt>
|
89
|
+
# [+value+] The iprp value, for example <tt>2d12</tt>
|
90
|
+
# [+param+] The iprp param1, for example <tt>Acid</tt>
|
91
|
+
# [+chance+] The iprp appearance chance (whats this?)
|
92
|
+
#
|
93
|
+
# Depends on the 2da cache set up correctly.
|
94
|
+
#
|
95
|
+
# Note that the given arguments can be resolved with partial matches as well, as long
|
96
|
+
# as they are unique. (<tt>Fir -> Fire</tt>)
|
97
|
+
#
|
98
|
+
# Arguments are case-insensitive.
|
99
|
+
def self.item_property name, subtype = nil, value = nil, param1 = nil, chance_appear = 100
|
100
|
+
self._ip_cache_setup
|
101
|
+
|
102
|
+
struct = NWN::Gff::Struct.new
|
103
|
+
|
104
|
+
name = resolve_or_match_partial name, @properties.by_col('Label')
|
105
|
+
index = @properties.by_col('Label').index(name)
|
106
|
+
raise ArgumentError, "Cannot find property #{name}" unless index
|
107
|
+
|
108
|
+
raise ArgumentError, "Property #{name} needs subtype of type #{NWN::TwoDA::Cache.get('itempropdef').by_col('SubTypeResRef', index)}, but none given." if
|
109
|
+
@subtypes[index] && !subtype
|
110
|
+
raise ArgumentError, "Property #{name} does not need subtype, but subtype given." if
|
111
|
+
!@subtypes[index] && subtype
|
112
|
+
|
113
|
+
subindex = 255
|
114
|
+
|
115
|
+
if subtype
|
116
|
+
subtype = resolve_or_match_partial subtype, @subtypes[index].by_col('Label')
|
117
|
+
|
118
|
+
subindex = @subtypes[index].by_col('Label').index(subtype)
|
119
|
+
raise ArgumentError, "Cannot find subtype #{subtype} for property #{name}" unless
|
120
|
+
subindex
|
121
|
+
|
122
|
+
raise ArgumentError, "Property #{name} requires a cost value of type #{@costtable_index.by_row(@prop_id_to_costtable[index], 'Name')}, but none given" if
|
123
|
+
!value && @prop_id_to_costtable[index]
|
124
|
+
raise ArgumentError, "Property #{name} does not require a cost value, but value given" if
|
125
|
+
value && !@prop_id_to_costtable[index]
|
126
|
+
end
|
127
|
+
|
128
|
+
_cost = 255
|
129
|
+
_cost_value = 0
|
130
|
+
|
131
|
+
if value
|
132
|
+
ct = @prop_id_to_costtable[index]
|
133
|
+
value = resolve_or_match_partial value, @costtables[ct.to_i].by_col('Label')
|
134
|
+
|
135
|
+
costvalue = @costtables[ct.to_i].by_col('Label').index(value)
|
136
|
+
raise ArgumentError, "Cannot find CostValue for #{value}" unless costvalue
|
137
|
+
_cost = ct
|
138
|
+
_cost_value = costvalue
|
139
|
+
end
|
140
|
+
struct.merge!({
|
141
|
+
'CostTable' => Element.new('CostTable', :byte, _cost),
|
142
|
+
'CostValue' => Element.new('CostValue', :word, _cost_value)
|
143
|
+
})
|
144
|
+
|
145
|
+
|
146
|
+
raise ArgumentError, "Property #{name} requires a param1 value of type #{@paramtable_index.by_row(@prop_id_to_param1[index], 'TableResRef')}, but none given" if
|
147
|
+
!param1 && @prop_id_to_param1[index]
|
148
|
+
raise ArgumentError, "Property #{name} does not require a param1 value, but value given" if
|
149
|
+
param1 && !@prop_id_to_param1[index]
|
150
|
+
|
151
|
+
_param1 = 255
|
152
|
+
_param1_value = 0
|
153
|
+
|
154
|
+
if param1
|
155
|
+
pt = @prop_id_to_param1[index]
|
156
|
+
param1 = resolve_or_match_partial param1, @paramtables[pt.to_i].by_col('Label')
|
157
|
+
|
158
|
+
param1value = @paramtables[pt.to_i].by_col('Label').index(param1)
|
159
|
+
raise ArgumentError, "Cannot find Param1 for #{param1}" unless param1value
|
160
|
+
_param1 = pt
|
161
|
+
_param1_value = param1value
|
162
|
+
end
|
163
|
+
struct.merge!({
|
164
|
+
'Param1' => Element.new('Param1', :byte, _param1),
|
165
|
+
'Param1Value' => Element.new('Param1Value', :byte, _param1_value)
|
166
|
+
})
|
167
|
+
|
168
|
+
struct.merge!({
|
169
|
+
'PropertyName' => Element.new('PropertyName', :word, index),
|
170
|
+
'Subtype' => Element.new('Subtype', :word, subindex),
|
171
|
+
'ChanceAppear' => Element.new('ChanceAppear', :byte, chance_appear)
|
172
|
+
})
|
173
|
+
|
174
|
+
struct.struct_id = 0
|
175
|
+
struct
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/nwn/twoda.rb
CHANGED
@@ -44,22 +44,34 @@ module NWN
|
|
44
44
|
raise ArgumentError, "Not valid 2da: No valid header found" if
|
45
45
|
magic != "2DA V2.0"
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
|
48
|
+
if empty != ""
|
49
|
+
$stderr.puts "Warning: second line of 2da not empty, continuing anyways."
|
50
|
+
data = [header].concat(data)
|
51
|
+
header = empty
|
52
|
+
end
|
50
53
|
|
51
54
|
header = Shellwords.shellwords(header.strip)
|
52
55
|
data.map! {|line|
|
53
56
|
Shellwords.shellwords(line.strip)
|
54
57
|
}
|
55
58
|
|
59
|
+
data.reject! {|line|
|
60
|
+
line.size == 0
|
61
|
+
}
|
62
|
+
|
63
|
+
offset = 0
|
56
64
|
data.each_with_index {|row, idx|
|
57
|
-
|
65
|
+
if (idx + offset) != row[0].to_i
|
66
|
+
$stderr.puts "Warning: row #{idx} has a non-matching ID #{row[0]} (while parsing #{row[0,3].join(' ')})."
|
67
|
+
offset += (row[0].to_i - idx)
|
68
|
+
end
|
69
|
+
|
58
70
|
# [1..-1]: Strip off the ID
|
59
|
-
data[
|
71
|
+
data[row[0].to_i] = row = row[1..-1]
|
60
72
|
|
61
73
|
raise ArgumentError,
|
62
|
-
"Row #{idx} does not have the appropriate amount of cells (has: #{row.size}, want: #{header.size})." if
|
74
|
+
"Row #{idx} does not have the appropriate amount of cells (has: #{row.size}, want: #{header.size}) (while parsing #{row[0,3].join(' ')})." if
|
63
75
|
row.size != header.size
|
64
76
|
}
|
65
77
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: nwn-lib
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2008-07-
|
6
|
+
version: 0.3.0
|
7
|
+
date: 2008-07-08 00:00:00 +02:00
|
8
8
|
summary: a ruby library for accessing Neverwinter Nights resource files
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -35,11 +35,15 @@ files:
|
|
35
35
|
- Rakefile
|
36
36
|
- bin/nwn-gff-irb
|
37
37
|
- bin/nwn-gff-print
|
38
|
+
- bin/nwn-gff-import
|
38
39
|
- spec/spec.opts
|
39
40
|
- spec/rcov.opts
|
40
41
|
- lib/nwn
|
41
42
|
- lib/nwn/twoda.rb
|
42
43
|
- lib/nwn/gff.rb
|
44
|
+
- lib/nwn/helpers.rb
|
45
|
+
- BINARIES
|
46
|
+
- CHEATSHEET
|
43
47
|
test_files: []
|
44
48
|
|
45
49
|
rdoc_options:
|
@@ -54,11 +58,14 @@ rdoc_options:
|
|
54
58
|
- ^(examples|extras)/
|
55
59
|
extra_rdoc_files:
|
56
60
|
- README
|
61
|
+
- BINARIES
|
62
|
+
- CHEATSHEET
|
57
63
|
- CHANGELOG
|
58
64
|
- COPYING
|
59
65
|
executables:
|
60
66
|
- nwn-gff-print
|
61
67
|
- nwn-gff-irb
|
68
|
+
- nwn-gff-import
|
62
69
|
extensions: []
|
63
70
|
|
64
71
|
requirements: []
|