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 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.2.2"
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"
@@ -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
- format = :kivinen
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 + "/" + vv.language.to_s, vv.text.gsub(/([\000-\037\177-\377%])/) {|v| "%" + v.unpack("H2")[0] })
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 < Hash
375
+ class NWN::Gff::Struct
376
376
  attr_accessor :struct_id
377
- def initialize *a
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
- # Attributes:
386
- # [+language+] The language ID
387
- # [+text+] The text for this language.
388
- #
389
- # ExoLocStrings in the wild are usually arrays of NWN::Gff:CExoLocString
390
- # (one for each language supplied).
391
- # Note that a CExoLocString is NOT a GFF list, although both are
392
- # represented as arrays.
393
- class NWN::Gff::CExoLocString < Struct.new(:language, :text)
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
- r << Gff::CExoLocString.new(id, str)
580
+ exostr[id] = str
554
581
  }
555
582
  len = total_size + 4
556
- r
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 an array" unless
767
- v.value.is_a?(Array)
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 + v.value.inject(0) {|t,x| t + x.text.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 {|s|
780
- @field_data << [s.language, s.text.size, s.text].pack("VVa*")
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
@@ -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
- raise ArgumentError,
48
- "Not avalid 2da: Second line should be empty." unless
49
- empty == ""
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
- raise ArgumentError, "2da non-continoous: row #{idx} has a non-matching ID #{row[0]}." if idx != row[0].to_i
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[idx] = row = row[1..-1]
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.2.2
7
- date: 2008-07-03 00:00:00 +02:00
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: []