nwn-lib 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []