nwn-lib 0.4.5 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/nwn/settings.rb CHANGED
@@ -1,7 +1,3 @@
1
- if ENV['NWN_LIB_INFER_DATA_FILE'] && ENV['NWN_LIB_INFER_DATA_FILE'] != ""
2
- NWN::Gff.load_struct_defaults(ENV['NWN_LIB_INFER_DATA_FILE'])
3
- end
4
-
5
1
  if ENV['NWN_LIB_2DA_LOCATION'] && ENV['NWN_LIB_2DA_LOCATION'] != ""
6
2
  NWN::TwoDA::Cache.setup ENV['NWN_LIB_2DA_LOCATION']
7
3
  end
data/lib/nwn/tlk.rb CHANGED
@@ -59,7 +59,7 @@ module NWN
59
59
 
60
60
  return @cache[id] if @cache[id]
61
61
 
62
- raise ArgumentError, "No such string ID: #{id.inspect}" if id >= self.highest_id || id < 0
62
+ raise ArgumentError, "No such string ID: #{id.inspect}" if id > self.highest_id || id < 0
63
63
  seek_to = HEADER_SIZE + (id) * DATA_ELEMENT_SIZE
64
64
  @io.seek(seek_to)
65
65
  data = @io.read(DATA_ELEMENT_SIZE)
data/lib/nwn/twoda.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'shellwords'
2
2
 
3
3
  class Integer
4
+ # Returns the level that this amount experience resolves to.
5
+ # Depends on a set-up TwoDA::Cache, and reads from <tt>exptable</tt>.
4
6
  def xp_to_level
5
7
  NWN::TwoDA.get('exptable').rows.each {|row|
6
8
  level, exp = row.Level.to_i, row.XP.to_i
@@ -9,6 +11,8 @@ class Integer
9
11
  return nil
10
12
  end
11
13
 
14
+ # Returns the amount of experience that this level resolves to.
15
+ # Depends on a set-up TwoDA::Cache, and reads from <tt>exptable</tt>.
12
16
  def level_to_xp
13
17
  NWN::TwoDA.get('exptable').by_col("XP", self - 1).to_i
14
18
  end
@@ -97,11 +101,22 @@ module NWN
97
101
 
98
102
  # Parses a string that represents a valid 2da definition.
99
103
  # Replaces any content this table may already have.
104
+ # This will cope with all misformatting in the same way
105
+ # that NWN1 itself does. NWN2 employs slightly different
106
+ # parsing rules, and may or may not be compatible in the
107
+ # fringe cases.
108
+ #
109
+ # Will raise an ArgumentError if the given +bytes+ do
110
+ # not contain a valid 2DA header, or the file is so badly
111
+ # misshaped that it will not ever be parsed correctly by NWN1.
112
+ #
113
+ # Will complain about everything mismatching it finds
114
+ # on $stderr if $DEBUG is set (-d).
100
115
  def parse bytes
101
116
  magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip }
102
117
 
103
- raise ArgumentError, "Not valid 2da: No valid header found" if
104
- magic != "2DA V2.0"
118
+ raise ArgumentError, "Not valid 2da: No valid header found (got: #{magic[0,20].inspect}..)" if
119
+ magic !~ /^2DA\s+V2.0$/
105
120
 
106
121
  # strip all empty lines; they are regarded as comments
107
122
  data.reject! {|ln| ln.strip == ""}
@@ -110,10 +125,7 @@ module NWN
110
125
 
111
126
  header = Shellwords.shellwords(header.strip)
112
127
  data.map! {|line|
113
- r = Shellwords.shellwords(line.strip)
114
- # The last cell can be without double quotes even if it contains whitespace
115
- r[header.size..-1] = r[header.size..-1].join(" ") if r.size > header.size
116
- r
128
+ Shellwords.shellwords(line.strip)
117
129
  }
118
130
 
119
131
  new_row_data = []
@@ -121,24 +133,30 @@ module NWN
121
133
  id_offset = 0
122
134
  idx_offset = 0
123
135
  data.each_with_index {|row, idx|
124
- id = row.shift.to_i + id_offset
136
+ id = row.shift
137
+
138
+ $stderr.puts "Warning: invalid ID in line #{idx}: #{id.inspect}" if $DEBUG && $id !~ /^\d+$/
125
139
 
126
- raise ArgumentError, "Invalid ID in row #{idx}" unless id >= 0
140
+ id = id.to_i + id_offset
127
141
 
128
- # Its an empty row - this is actually valid; we'll fill it up and dump it later.
142
+ # Its an empty row - NWN strictly numbers by counted lines - then so do we.
129
143
  while id > idx + idx_offset
130
- new_row_data << Row.new([""] * header.size)
144
+ $stderr.puts "Warning: missing ID at #{id - id_offset}, fixing that for you." if $DEBUG
131
145
  idx_offset += 1
132
146
  end
133
147
 
134
148
  # NWN automatically increments duplicate IDs - so do we.
135
- while id + id_offset < idx
136
- $stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you."
149
+ while id < idx + idx_offset
150
+ $stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you." if $DEBUG
137
151
  id_offset += 1
138
152
  id += 1
139
153
  end
140
154
 
141
155
  # NWN fills in missing columns with an empty value - so do we.
156
+ $stderr.puts "Warning: row #{id} (real: #{id - id_offset}) misses " +
157
+ "#{header.size - row.size} columns at the end, fixed" if
158
+ row.size < header.size if $DEBUG
159
+
142
160
  row << "" while row.size < header.size
143
161
 
144
162
  new_row_data << k_row = Row.new(row)
@@ -146,15 +164,16 @@ module NWN
146
164
 
147
165
  k_row.map! {|cell|
148
166
  cell = case cell
149
- when nil; nil
167
+ when nil; raise "Bug in parser: nil-value for cell"
150
168
  when "****"; ""
151
169
  else cell
152
170
  end
153
171
  }
154
172
 
155
- raise ArgumentError,
156
- "Row #{idx} has too many cells for the given header (has #{k_row.size}, want <= #{header.size})" if
157
- k_row.size != header.size
173
+ $stderr.puts "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
174
+ $DEBUG && k_row.size > header.size
175
+
176
+ k_row.pop while k_row.size > header.size
158
177
  }
159
178
 
160
179
  @columns = header
@@ -277,7 +296,7 @@ module NWN
277
296
  when "2"
278
297
  "\r"
279
298
  when nil
280
- @newlines
299
+ @newline
281
300
  end)
282
301
  end
283
302
  end
data/lib/nwn/yaml.rb CHANGED
@@ -1,8 +1,10 @@
1
+ # This file contains all YAML-specific loading and dumping code.
1
2
  require 'yaml'
2
3
 
3
4
  # See http://www.taguri.org/ for the exact meaning of this.
4
5
  NWN::YAML_DOMAIN = "nwn-lib.elv.es,2008-12"
5
6
 
7
+ #:stopdoc:
6
8
  class Array
7
9
  attr_accessor :to_yaml_style
8
10
  end
@@ -45,14 +47,10 @@ module NWN::Gff::Struct
45
47
  }.size == 0
46
48
 
47
49
  map.add('__' + 'data_type', @data_type) if @data_type
48
- map.add('__' + 'data_version', @data_version) if @data_version && !can_infer_data_version?
49
- map.add('__' + 'struct_id', @struct_id) if @struct_id && !can_infer_struct_id?
50
+ map.add('__' + 'data_version', @data_version) if @data_version && @data_version != DEFAULT_DATA_VERSION
51
+ map.add('__' + 'struct_id', @struct_id) if @struct_id
50
52
 
51
- reject {|k, v|
52
- # Dont emit fields that would result in their default values anyways.
53
- ENV['NWN_LIB_CLEAR_KNOWN_VALUES'] && v.can_infer_type? &&
54
- v.can_infer_str_ref? && v.can_infer_value?
55
- }.sort.each {|k,v|
53
+ sort.each {|k,v|
56
54
  map.add(k,v)
57
55
  }
58
56
  end
@@ -63,57 +61,12 @@ end
63
61
  module NWN::Gff::Field
64
62
 
65
63
  def to_yaml(opts = {})
66
-
67
- if !ENV['NWN_LIB_DONT_COMPACT_LIST_STRUCTS'] && field_type == :list && can_compact_as_list?
68
- YAML::quick_emit(nil, opts) do |out|
69
- out.seq("!", to_yaml_style) do |seq|
70
- field_value.each {|item|
71
- struct_id_of_item = NWN::Gff.get_struct_defaults_for(item.path, '__struct_id')
72
-
73
- calf = get_compact_as_list_field
74
- case calf
75
- when Array
76
- style = NWN::Gff.get_struct_defaults_for(item.path, '__inline')
77
-
78
- non_inferrable = (item.keys.reject {|x|
79
- item[x].can_infer_type? && item[x].can_infer_value?
80
- } - calf).sort
81
- raise NWN::Gff::GffError, "cannot compact list struct at #{item.path}, would " +
82
- "LOSE fields: #{non_inferrable.inspect}" if non_inferrable.size > 0
83
-
84
- ar = calf.map {|ik| item[ik] || NWN::Gff.get_struct_default_value(item.path, ik) }
85
- missing = ar.map {|x| x.nil? ? calf[ar.index(x)] : nil }.compact
86
-
87
- raise NWN::Gff::GffError, "cannot compact list-struct at #{item.path}, does not " +
88
- "have all compactable field values set or are inferrable (missing fields: #{missing.inspect})." if
89
- missing.size > 0
90
-
91
- ar.to_yaml_style = :inline if style
92
-
93
- ar.unshift(item.struct_id) if struct_id_of_item == 'inline'
94
- seq.add(ar)
95
- else
96
- isv = NWN::Gff.get_struct_defaults_for(self.path, '__inline')
97
- seq.style = :inline if isv.nil? || isv === true
98
-
99
- seq.add(item.struct_id) if struct_id_of_item == 'inline'
100
- seq.add(item[calf])
101
- end
102
- }
103
- end
104
- end
105
-
106
- elsif !ENV['NWN_LIB_DONT_COMPACT_FIELDS'] && can_compact_print?
107
- field_value_as_compact.to_yaml(opts)
108
-
109
- else
110
- YAML::quick_emit(nil, opts) do |out|
111
- out.map(taguri, to_yaml_style) do |map|
112
- map.style = :inline unless NWN::Gff::YAMLNonInlineableFields.index(self['type'])
113
- map.add('type', self['type']) unless can_infer_type?
114
- map.add('str_ref', self['str_ref']) unless can_infer_str_ref?
115
- map.add('value', self['value']) unless can_infer_value?
116
- end
64
+ YAML::quick_emit(nil, opts) do |out|
65
+ out.map(taguri, to_yaml_style) do |map|
66
+ map.style = :inline unless NWN::Gff::YAMLNonInlineableFields.index(self['type'])
67
+ map.add('type', self['type'])
68
+ map.add('str_ref', self['str_ref']) if has_str_ref?
69
+ map.add('value', self['value'])
117
70
  end
118
71
  end
119
72
  end
@@ -130,159 +83,21 @@ YAML.add_domain_type(NWN::YAML_DOMAIN,'struct') {|t,hash|
130
83
  struct.data_version = hash.delete('__data_version')
131
84
  struct.data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION
132
85
 
133
- if struct.struct_id.nil? && s_id = NWN::Gff.get_struct_defaults_for(struct.path, '__struct_id')
134
- struct.struct_id = s_id.to_i
135
- elsif struct.struct_id.nil?
136
- raise NWN::Gff::GffError, "Cannot infer implicit struct_id for struct at #{struct.path}."
137
- end
86
+ raise NWN::Gff::GffError, "no struct_id set for struct at #{struct.path}." if struct.struct_id.nil?
138
87
 
139
88
  hash.each {|label,element|
140
89
  label.freeze
141
- element = case element
142
- when Hash # already uncompacted or a compacted exolocstr
143
- default_type = NWN::Gff.get_struct_default_type(struct.path, label)
144
- raise NWN::Gff::GffError,
145
- "Cannot parse compacted hash with no contents and no infer data at #{struct.path}/#{label}." if
146
- element.size == 0 && default_type == nil
147
-
148
- # It has only got numbers as key, we *assume* its a cexoloc.
149
- # Thats okay, because type inferration will catch it later and bite us. Hopefully.
150
- if element.keys.select {|x| x.to_s !~ /^(str_ref|\d+)$/}.size == 0
151
- element = {
152
- 'type' => :cexolocstr,
153
- 'str_ref' => element.delete('str_ref') || NWN::Gff::Field::DEFAULT_STR_REF,
154
- 'value' => element,
155
- }
156
- end
157
-
158
- element
159
-
160
- when Array # compacted struct-list
161
- element = {
162
- 'type' => :list,
163
- 'value' => element,
164
- }
165
- path = struct.data_type + "/" + label
166
- unpack_struct_element = NWN::Gff.get_struct_defaults_for(path, '__compact')
167
- if unpack_struct_element
168
- # If it doesn't have unpack data, we need to assume its a compacted list itself.
169
- # Sorry.
170
- # Hope this wont bite anyone later on.
171
-
172
- #raise NWN::Gff::GffError,
173
- # "Cannot unpack compacted struct list at #{path}, no infer data available." unless
174
- # unpack_struct_element
175
-
176
- unpack_struct_element_struct_id = NWN::Gff.get_struct_defaults_for(path, "__struct_id")
177
-
178
- unpack_struct_elements = [unpack_struct_element].flatten.freeze
179
-
180
- unpack_struct_element_types = unpack_struct_elements.map {|unpack_struct_element|
181
- raise NWN::Gff::GffError, "While unpacking #{path}: " +
182
- "#{unpack_struct_element} is not a field-naime, dummy." unless
183
- unpack_struct_element.is_a?(String)
184
-
185
- unpack_struct_element_type =
186
- NWN::Gff.get_struct_default_type(path, unpack_struct_element)
187
-
188
- unpack_struct_element_type
189
- }.freeze
190
-
191
- last_struct_id = -1
192
- element['value'].map! {|kv|
193
- st = {}
194
- st.extend(NWN::Gff::Struct)
195
- st.struct_id = case unpack_struct_element_struct_id
196
- when 'iterative'
197
- last_struct_id += 1
198
- when 'inline'
199
- kv.shift
200
- when Fixnum
201
- unpack_struct_element_struct_id
202
- else
203
- kv['__struct_id'] || raise(NWN::Gff::GffError,
204
- "dont know how to handle struct_id #{unpack_struct_element_struct_id} at #{label} = #{kv.inspect}")
205
- end
206
- st.data_type = path
207
-
208
- unpack_struct_elements.each_with_index {|use, index|
209
- case kv
210
- when Hash
211
- if !kv[use]
212
- uset, usev = unpack_struct_element_types[index],
213
- NWN::Gff.get_struct_default_value(path, use)
214
- else
215
- uset = kv[use]['type']
216
- usev = kv[use]['value']
217
- end
218
- when Array
219
- uset = unpack_struct_element_types[index]
220
- usev = kv[index]
221
- if usev.is_a?(Hash)
222
- uset, usev = usev['type'], usev['value']
223
- end
224
- when String, Numeric, Symbol
225
- uset = unpack_struct_element_types[index]
226
- usev = kv
227
- else
228
- raise "Dont know how to unpack list-struct element: #{kv.inspect}"
229
- end
230
-
231
- raise NWN::Gff::GffError, "Cannot infer type of #{path}/#{use} (got: #{uset.inspect})" unless
232
- uset && NWN::Gff::Types.index(uset)
233
- raise NWN::Gff::GffError, "Cannot get or infer value of #{path}/#{use} (got: #{usev.inspect})" unless
234
- usev && (usev.is_a?(String) || usev.is_a?(Numeric) || usev.is_a?(Symbol))
235
-
236
- # Figure out the default value (which we'll need to re-add?) if
237
- # it was filtered while outputting.
238
- #kv[index] = NWN::Gff.get_struct_default_value(path, use) if !kv[index]
239
- #raise NWN::Gff::GffError, "field present in compacted struct-list, and cant " +
240
- # "infer default value at #{use}/#{index} (#{kv.inspect})" if !kv[index]
241
-
242
- el = st[use] = {
243
- 'label' => use,
244
- 'type' => uset,
245
- 'value' => usev
246
- }
247
- el.extend(NWN::Gff::Field)
248
- el.extend_meta_classes
249
- el.parent = st
250
- }
251
- st
252
- }
253
- end
254
- element
255
-
256
- when Numeric, String # compacted scalar
257
- {'value' => element}
258
-
259
- else
260
- fail "Don't know how to un-compact /#{label}: #{element.inspect}, klass #{element.class.to_s}"
261
- end
262
90
 
263
91
  element.extend(NWN::Gff::Field)
264
92
  element.field_label = label
265
93
  element.parent = struct
266
94
  element.str_ref ||= NWN::Gff::Field::DEFAULT_STR_REF if element.respond_to?('str_ref=')
267
95
 
268
- infer_field_type = NWN::Gff.get_struct_default_type(struct.path, element.field_label)
269
-
270
- if element.field_type && infer_field_type && infer_field_type != element.field_type
271
- raise NWN::Gff::GffError, "/#{label} has field_type #{element.field_type.inspect}, but infer data says #{infer_field_type.inspect}."
272
-
273
- elsif element.field_type.nil? && infer_field_type.nil?
274
- raise NWN::Gff::GffError, "Cannot infer implicit type for /#{label} while parsing struct-id #{struct.struct_id}."
275
-
276
- elsif element.field_type.nil? && infer_field_type
277
- element.field_type = infer_field_type
278
- end
279
-
280
-
281
96
  element.extend_meta_classes
97
+ element.validate
282
98
 
283
99
  struct[label] = element.taint
284
100
  }
285
101
 
286
- NWN::Gff.__yaml_postparse nil, struct if struct.data_type
287
102
  struct
288
103
  }
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Gff::Cexolocstr" do
4
+
5
+ it "should compact nil and empty strings" do
6
+ str = Gff::Field.new('Test', :cexolocstr, {})
7
+ str.v[0] = "hi"
8
+ str.v[1] = nil
9
+ str.v[4] = ""
10
+ str.v[8] = "test"
11
+ str.v.compact!
12
+ str.v.should == {0=>"hi", 8=>"test"}
13
+ end
14
+
15
+ end
data/spec/erf_spec.rb ADDED
@@ -0,0 +1,87 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ WELLFORMED_ERF = ([
4
+ "HAK", "V1.0",
5
+ locstr_count = 1, locstr_size = 14,
6
+ entry_count = 3,
7
+ offset_to_locstr = 160,
8
+ offset_to_keys = offset_to_locstr + locstr_size,
9
+ offset_to_res = offset_to_locstr + locstr_size + entry_count * 24,
10
+
11
+ 100, 126, # year, dayofyear
12
+ 0xdeadbeef, "" #description strref, 116 bytes 0-padding
13
+ ].pack("a4 a4 VV VV VV VV V a116") + [
14
+ 0, 6, "abcdef" # one locstr
15
+ ].pack("V V a*") + [
16
+ "resref", 0, 10, 0, # keylist: resref.txt, id = 0
17
+ "help", 1, 1, 0, # keylist: help.bmp, id = 1
18
+ "yegods", 2, 4, 0, # keylist: yegods.wav, id = 2
19
+ ].pack("a16 V v v" * entry_count) + [
20
+ offset_to_res + entry_count * 8, 6, # offset, size
21
+ offset_to_res + entry_count * 8 + 6, 4, # offset, size
22
+ offset_to_res + entry_count * 8 + 6 + 4, 6, # offset, size
23
+ ].pack("II" * entry_count) + [
24
+ "resref", "help", "yegods"
25
+ ].pack("a* a* a*")).freeze
26
+
27
+
28
+
29
+ describe "Erf::Erf" do
30
+
31
+ def wellformed_verify binary, expect_locstr = true
32
+ t = nil
33
+ t = Erf::Erf.new(StringIO.new binary)
34
+
35
+ t.file_type.should == "HAK"
36
+ t.file_version.should == "V1.0"
37
+ if expect_locstr
38
+ t.localized_strings.should == {0 => "abcdef"}
39
+ else
40
+ t.localized_strings.should == {}
41
+ end
42
+ t.content.size.should == 3
43
+ t.year.should == 100
44
+ t.day_of_year.should == 126
45
+ t.description_str_ref.should == 0xdeadbeef
46
+
47
+ t.content[0].filename.should == "resref.txt"
48
+ t.content[1].filename.should == "help.bmp"
49
+ t.content[2].filename.should == "yegods.wav"
50
+ t.content[0].size.should == 6
51
+ t.content[1].size.should == 4
52
+ t.content[2].size.should == 6
53
+ t.content[0].get.should == "resref"
54
+ t.content[1].get.should == "help"
55
+ t.content[2].get.should == "yegods"
56
+ end
57
+
58
+ it "reads wellformed ERF containers" do
59
+ wellformed_verify WELLFORMED_ERF
60
+ end
61
+
62
+ it "reproduces correct ERF binary data" do
63
+ t = Erf::Erf.new(StringIO.new WELLFORMED_ERF)
64
+ io = StringIO.new
65
+ t.write_to(io)
66
+ io.seek(0)
67
+ proc {
68
+ wellformed_verify io.read
69
+ }.should_not raise_error IOError
70
+ end
71
+
72
+ it "does not read ERF with locstr_size = 0 and locstr_count > 0" do
73
+ b = WELLFORMED_ERF.dup
74
+ b[12,4] = [0].pack("V")
75
+ proc {
76
+ wellformed_verify b
77
+ }.should raise_error IOError
78
+ end
79
+
80
+ it "reads ERF with locstr_size > 0 and locstr_count = 0" do
81
+ b = WELLFORMED_ERF.dup
82
+ b[8,4] = [0].pack("V")
83
+ proc {
84
+ wellformed_verify b, false
85
+ }.should_not raise_error IOError
86
+ end
87
+ end