Module: NWN::Gff::Struct

Defined in:
lib/nwn/gff/struct.rb,
lib/nwn/json_support.rb,
lib/nwn/yaml_support.rb

Overview

A Gff::Struct is a hash of label->Element pairs with some meta-information in local variables.

Constant Summary collapse

DEFAULT_DATA_VERSION =
"V3.2"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *av, &block) ⇒ Object

:nodoc:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/nwn/gff/struct.rb', line 109

def method_missing meth, *av, &block # :nodoc:
  if meth.to_s =~ /^add_(.+)$/
    if NWN::Gff::Types.key($1.to_sym)
      av.size >= 1 || av.size <= 2 or raise(NoMethodError,
        "undefined method #{meth} (requires two arguments to infer add_any)")
      t = $1.to_sym
      b = av[1] || NWN::Gff::Field::DEFAULT_VALUES[t] || raise(NoMethodError,
        "undefined method #{meth} (type #{t.inspect} requires explicit value)")
      f = add_field(av[0], t, b, &block)
      return f
    else
      super
    end
  end

  super
end

Instance Attribute Details

#data_versionObject

The file version. Usually “V3.2”. If not given in a source format, DEFAULT_DATA_VERSION is inferred and set for all structs.



8
9
10
# File 'lib/nwn/gff/struct.rb', line 8

def data_version
  @data_version
end

#elementObject

The field this struct is value of. It is most likely a Field of :list, or :nil if it is the root struct. Setting this to a value detaches this struct from the old parent (though the old parent Field may still point to this object).



19
20
21
# File 'lib/nwn/gff/struct.rb', line 19

def element
  @element
end

#struct_idObject

GFF struct type. The default is 0xffffffff.



11
12
13
# File 'lib/nwn/gff/struct.rb', line 11

def struct_id
  @struct_id
end

Class Method Details

.new(struct_id = 0xffffffff, data_type = nil, data_version = DEFAULT_DATA_VERSION) {|s| ... } ⇒ Object

Create a new struct. Usually, you can leave out data_type and data_version for non-root structs, because that will be guess-inherited based on the existing associations.

You can pass a block to this method, which will receive the newly-created Struct as the only argument.

Yields:

  • (s)


68
69
70
71
72
73
74
75
# File 'lib/nwn/gff/struct.rb', line 68

def self.new struct_id = 0xffffffff, data_type = nil, data_version = DEFAULT_DATA_VERSION
  s = {}.extend(self)
  s.struct_id = struct_id
  s.data_type = data_type
  s.data_version = data_version
  yield(s) if block_given?
  s
end

.unbox!(o, parent = nil) ⇒ Object

Deep-unboxes a Hash, e.g. iterating down, converting it to the native charset.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/nwn/gff/struct.rb', line 228

def self.unbox! o, parent = nil
  o.extend(NWN::Gff::Struct)
  o.element = parent if parent
  o.struct_id = o.delete('__struct_id')
  o.data_type = o.delete('__data_type')
  o.data_version = o.delete('__data_version')
  o.data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION

  NWN.log_debug("Unboxed without a root data type") if
    !parent && !o.data_type
  NWN.log_debug("Unboxed with explicit data type #{o.data_type.inspect}") if
    parent && o.data_type

  o.each {|label,element|
    o[label] = NWN::Gff::Field.unbox!(element, label, o)
  }

  o
end

Instance Method Details

#/(path) ⇒ Object

An alias for by_path.



222
223
224
# File 'lib/nwn/gff/struct.rb', line 222

def / path
  by_path(path)
end

#add_field(label, type, value = nil) {|| ... } ⇒ Object

Create a new field. Alternatively, you can use the shorthand methods:

add_#{type} - add_int, add_byte, ..

For example:

some_struct.add_field 'ID', :byte, 5

is equivalent to:

some_struct.add_byte 'ID', 5

You can pass a block to this method, which will receive the newly-created Field as an argument.

This allows for code like this:

Gff::Struct.new(0) do |s|
  s.add_byte "Byte", 5
  s.add_list "Some_List", [] do |l|
    l.v << Gff::Struct.new ...
    ..
  end
end

Yields:

  • ()


96
97
98
99
100
101
102
103
104
105
106
# File 'lib/nwn/gff/struct.rb', line 96

def add_field label, type, value = nil, &block
  value ||= NWN::Gff::Field::DEFAULT_VALUES[type] || raise(ArgumentError,
    "type #{type.inspect} requires explicit value")
  self[label] = NWN::Gff::Field.new(label, type, value)
  self[label].parent = self
  yield(self[label]) if block_given?
  if self[label].field_value.is_a?(NWN::Gff::Struct)
    self[label].field_value.element = self[label]
  end
  self[label]
end

#boxObject

Returns a hash of this Struct without the API calls mixed in, converting it from the native charset.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/nwn/gff/struct.rb', line 250

def box
  t = Hash[self]
  t.merge!({
    '__struct_id' => self.struct_id
  })
  t.merge!({
    '__data_version' => self.data_version,
  }) if self.data_version && self.data_version !=
    NWN::Gff::Struct::DEFAULT_DATA_VERSION
  t.merge!({
    '__data_type' => self.data_type
  }) if @data_type
  t
end

#by_path(path) ⇒ Object

Retrieve an object from within the given tree. Path is a slash-separated destination, given as a string

Prefixed/postfixed slashes are optional.

You can retrieve CExoLocString values by giving the language ID as the last label:

/FirstName/0

You can retrieve list values by specifying the index in square brackets:

/SkillList[0]
/SkillList[0]/Rank   => {"Rank"=>{"label"=>"Rank", "value"=>0, "type"=>:byte}}

You can directly retrieve field values and types instead of the field itself:

/SkillList[0]/Rank$  => 0
/SkillList[0]/Rank?   => :byte

This will raise an error for non-field paths, naturally:

SkillList[0]$        => undefined method `field_value' for {"Rank"=>{"label"=>"Rank", "value"=>0, "type"=>:byte}}:Hash
SkillList[0]?        => undefined method `field_type' for {"Rank"=>{"label"=>"Rank", "value"=>0, "type"=>:byte}}:Hash

For CExoLocStrings, you can retrieve the str_ref:

FirstName%           => 4294967295

This will return DEFAULT_STR_REF (0xffffffff) if the given path does not have a str_ref.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/nwn/gff/struct.rb', line 173

def by_path path
  struct = self
  current_path = ""
  path = path.split('/').map {|v| v.strip }.reject {|v| v.empty?}.join('/')

  path, mod = $1, $2 if path =~ /^(.+?)([\$\?%])?$/

  path.split('/').each_with_index {|v, path_index|
    if struct.is_a?(NWN::Gff::Field) && struct.field_type == :cexolocstr &&
        v =~ /^\d+$/ && path_index == path.split('/').size - 1
      struct = struct.field_value[v.to_i]
      break
    end

    v, index = $1, $2 if v =~ /^(.+?)\[(\d+)\]$/

    struct = struct.v if struct.is_a?(NWN::Gff::Field) &&
      struct.field_type == :struct

    struct = struct[v]
    if index
      struct.field_type == :list or raise NWN::Gff::GffPathInvalidError,
        "Specified a list offset for a non-list item: #{v}[#{index}]."

      struct = struct.field_value[index.to_i]
    end


    raise NWN::Gff::GffPathInvalidError,
      "Cannot find a path to /#{path} (at: #{current_path})." unless struct

    current_path += "/" + v
    current_path += "[#{index}]" if index
  }

  case mod
    when "$"
      struct.field_value
    when "?"
      struct.field_type
    when "%"
      struct.has_str_ref? ? struct.str_ref :
        NWN::Gff::Cexolocstr::DEFAULT_STR_REF
    else
      struct
  end
end

#data_typeObject

Each Gff::Struct has a data_type, which describes the type of data the struct contains. For top-level structs, this equals the data type written to the GFF file (“UTI”, for example); for sub structures, this is usually nil, but can be overriden by users explicitly to carry some meta-data (e.g. explicitly set UTC/ItemList to UTI). This is not done automatically, however.

Scripts could use this, for example, to reliably re-attach a Item within /ItemList/ somewhere else, or export it as .uti.



37
38
39
# File 'lib/nwn/gff/struct.rb', line 37

def data_type
  @data_type
end

#data_type=(k) ⇒ Object

Overrides the data type (used by the built-in file format readers).



42
43
44
45
46
47
# File 'lib/nwn/gff/struct.rb', line 42

def data_type= k
  k = nil if k == ""
  NWN.log_debug("Setting explicit data_type for parented element") if k &&
    defined?(@element) && !@element.nil?
  @data_type = k
end

#each_by_flat_path(prefix = "/", &block) ⇒ Object

Example: “/AddCost” => ..



137
138
139
140
141
142
143
# File 'lib/nwn/gff/struct.rb', line 137

def each_by_flat_path prefix = "/", &block
  sort.each {|label, field|
    field.each_by_flat_path do |ll, lv|
      yield(prefix + label + ll, lv)
    end
  }
end

#pathObject



21
22
23
24
25
26
27
# File 'lib/nwn/gff/struct.rb', line 21

def path
  if defined?(@element) && !@element.nil?
    @element.path
  else
    "/"
  end
end

#to_gff(data_type = nil) ⇒ Object

Dump this struct as GFF binary data.

Optionally specify data_type and data_version



58
59
60
# File 'lib/nwn/gff/struct.rb', line 58

def to_gff data_type = nil
  NWN::Gff::Writer.dump(self, data_type)
end

#to_json(*a) ⇒ Object



4
5
6
# File 'lib/nwn/json_support.rb', line 4

def to_json(*a)
  box.to_json(*a)
end

#to_sObject



127
128
129
# File 'lib/nwn/gff/struct.rb', line 127

def to_s
  "<NWN::Gff::Struct #{self.data_type}/#{self.data_version}, #{self.keys.size} fields>"
end

#to_yaml(opts = {}) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/nwn/yaml_support.rb', line 55

def to_yaml(opts = {})
  YAML::quick_emit(nil, opts) do |out|
    out.map(taguri, to_yaml_style) do |map|
      # Inline certain structs that are small enough.
      map.style = :inline if self.size <= 1 &&
        self.values.select {|x|
          NWN::Gff::Handler::YAML::NonInlineableFields.index(x['type'])
        }.size == 0

      map.add('__' + 'data_type', @data_type) if @data_type
      map.add('__' + 'data_version', @data_version) if @data_version && @data_version != DEFAULT_DATA_VERSION
      map.add('__' + 'struct_id', @struct_id) if @struct_id

      sort.each {|k,v|
        map.add(k,v)
      }
    end
  end
end

#to_yaml_typeObject



51
52
53
# File 'lib/nwn/yaml_support.rb', line 51

def to_yaml_type
  "!#{NWN::Gff::Handler::YAML::Domain}/struct"
end