nwn-lib 0.3.6 → 0.4.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.
@@ -0,0 +1,201 @@
1
+ class NWN::Gff::Writer
2
+ include NWN
3
+ include NWN::Gff
4
+
5
+ attr_reader :bytes
6
+
7
+ def initialize(gff, data_type = nil)
8
+ @gff = gff
9
+ @data_type = data_type
10
+
11
+ @structs = []
12
+ @fields = []
13
+ @labels = []
14
+ @field_indices = []
15
+ @list_indices = []
16
+ @field_data = ""
17
+
18
+ write_all
19
+ end
20
+
21
+ # Takes a NWN::Gff::Gff object and dumps it as raw bytes,
22
+ # including the header.
23
+ def self.dump(gff, data_type = nil)
24
+ self.new(gff, data_type).bytes
25
+ end
26
+
27
+ private
28
+
29
+ def get_label_id_for_label str
30
+ @labels << str unless @labels.index(str)
31
+ @labels.index(str)
32
+ end
33
+
34
+ def add_data_field type, label, content
35
+ label_id = get_label_id_for_label label
36
+ @fields.push Types.index(type), label_id, content
37
+ (@fields.size - 1) / 3
38
+ end
39
+
40
+ def write_all
41
+ data = []
42
+ write_struct @gff
43
+
44
+ c_offset = 0
45
+ data << [
46
+ @data_type || @gff.data_type,
47
+ @gff.data_version,
48
+
49
+ # Offset of Struct array as bytes from the beginning of the file
50
+ c_offset += 56,
51
+ # Number of elements in Struct array
52
+ @structs.size / 3,
53
+
54
+ # Offset of Field array as bytes from the beginning of the file
55
+ fields_start = c_offset += @structs.size / 3 * 12,
56
+ # Number of elements in Field array
57
+ @fields.size / 3,
58
+
59
+ # Offset of Label array as bytes from the beginning of the file
60
+ c_offset += @fields.size / 3 * 12,
61
+ # Number of elements in Label array
62
+ @labels.size,
63
+
64
+ # Offset of Field Data as bytes from the beginning of the file
65
+ c_offset += @labels.size * 16,
66
+ # Number of bytes in Field Data block
67
+ @field_data.size,
68
+
69
+ # Offset of Field Indices array as bytes from the beginning of the file
70
+ c_offset += @field_data.size,
71
+ # Number of bytes in Field Indices array
72
+ @field_indices.size * 4,
73
+
74
+ # Offset of List Indices array as bytes from the beginning of the file
75
+ c_offset += @field_indices.size * 4,
76
+ # Number of bytes in List Indices array
77
+ @list_indices.size * 4
78
+
79
+ ].pack("A4a4 VV VV VV VV VV VV")
80
+
81
+ data << @structs.pack("V*")
82
+ data << @fields.pack("V*")
83
+ data << @labels.pack("a16" * @labels.size)
84
+ data << @field_data
85
+ data << @field_indices.pack("V*")
86
+ data << @list_indices.pack("V*")
87
+
88
+ @bytes = data.join("")
89
+ end
90
+
91
+ def write_struct struct
92
+ raise GffError, "struct invalid: #{struct.inspect}" unless struct.is_a?(NWN::Gff::Struct)
93
+ raise GffError, "struct_id missing from struct" unless struct.struct_id
94
+
95
+ # This holds all field label ids this struct has as a member
96
+ fields_of_this_struct = []
97
+
98
+ # This will hold the index of this struct
99
+ index = @structs.size / 3
100
+
101
+ @structs.push struct.struct_id, 0, 0
102
+
103
+ struct.sort.each {|k,v|
104
+ raise GffError, "Empty label." if !k || k == ""
105
+
106
+ case v.field_type
107
+ # simple data types
108
+ when :byte, :char, :word, :short, :dword, :int, :float
109
+ format = Formats[v.field_type]
110
+ fields_of_this_struct << add_data_field(v.field_type, k, [v.field_value].pack(format).unpack("V")[0])
111
+
112
+ # complex data types
113
+ when :dword64, :int64, :double, :void
114
+ fields_of_this_struct << add_data_field(v.field_type, k, @field_data.size)
115
+ format = Formats[v.field_type]
116
+ @field_data << case v.field_type
117
+ when :dword64
118
+ [
119
+ ( v.field_value / (2**32) ) & 0xffffffff,
120
+ v.field_value % (2**32)
121
+ ].pack("II")
122
+ when :void
123
+ [ v.field_value.size / 2, v.field_value ].pack("VH*")
124
+ else
125
+ [v.field_value].pack(format)
126
+ end
127
+
128
+ when :struct
129
+ raise GffError, "type = struct, but value not a hash" unless
130
+ v.field_value.is_a?(Gff::Struct)
131
+
132
+ fields_of_this_struct << add_data_field(v.field_type, k, write_struct(v.field_value))
133
+
134
+ when :list
135
+ raise GffError, "type = list, but value not an array" unless
136
+ v.field_value.is_a?(Array)
137
+
138
+ fields_of_this_struct << add_data_field(v.field_type, k, 4 * @list_indices.size)
139
+
140
+ count = v.field_value.size
141
+ tmp = @list_indices.size
142
+ @list_indices << count
143
+ count.times {
144
+ @list_indices << 0
145
+ }
146
+
147
+ v.field_value.each_with_index do |kk, idx|
148
+ vv = write_struct(kk)
149
+ @list_indices[ idx + tmp + 1 ] = vv
150
+ end
151
+
152
+ when :resref
153
+ fields_of_this_struct << add_data_field(v.field_type, k, @field_data.size)
154
+ @field_data << [v.field_value.size, v.field_value].pack("Ca*")
155
+
156
+ when :cexostr
157
+ fields_of_this_struct << add_data_field(v.field_type, k, @field_data.size)
158
+ @field_data << [v.field_value.size, v.field_value].pack("Va*")
159
+
160
+ when :cexolocstr
161
+ raise GffError, "type = cexolocstr, but value not a hash (#{v.field_value.class})" unless
162
+ v.field_value.is_a?(Hash)
163
+
164
+ fields_of_this_struct << add_data_field(v.field_type, k, @field_data.size)
165
+
166
+ # total size (4), str_ref (4), str_count (4)
167
+ total_size = 8
168
+ v.field_value.each {|kk,vv|
169
+ total_size += vv.size + 8
170
+ }
171
+ @field_data << [
172
+ total_size,
173
+ v.str_ref,
174
+ v.field_value.size
175
+ ].pack("VVV")
176
+
177
+ v.field_value.each {|k,v|
178
+ @field_data << [k, v.size, v].pack("VVa*")
179
+ }
180
+
181
+ else
182
+ raise GffError, "Unknown data type: #{v.field_type}"
183
+ end
184
+ }
185
+
186
+ # id/type, data_or_offset, nr_of_fields
187
+ @structs[3 * (index) + 2] = fields_of_this_struct.size
188
+
189
+ if fields_of_this_struct.size < 1
190
+ elsif fields_of_this_struct.size == 1
191
+ @structs[3 * (index) + 1] = fields_of_this_struct[0]
192
+ else
193
+ # Offset into field_indices starting where are number of nr_of_fields
194
+ # dwords as indexes into @fields
195
+ @structs[3 * (index) + 1] = 4 * (@field_indices.size)
196
+ @field_indices.push *fields_of_this_struct
197
+ end
198
+
199
+ index
200
+ end
201
+ end
@@ -2,38 +2,9 @@ require 'nwn/gff'
2
2
  require 'nwn/twoda'
3
3
 
4
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
5
  module Gff
35
- module Helpers
36
6
 
7
+ module Helpers
37
8
  # This sets up the IPRP cache. Used internally; no need to call this yourself.
38
9
  def self._ip_cache_setup #:nodoc:
39
10
  return if defined? @costtables
@@ -0,0 +1,125 @@
1
+ module NWN::Gff
2
+ @YAMLStructDefaults = {}
3
+
4
+ #:stopdoc:
5
+ # This gets called for each parsed struct to set their container element
6
+ # values (see also gff/reader.rb, line 233-ish).
7
+ def self.__yaml_postparse parent, struct
8
+ struct.each {|label,element|
9
+ case element.field_type
10
+ when :list, :struct
11
+ [element.field_value].flatten.each {|item|
12
+ __yaml_postparse(element, item)
13
+ item.element = element
14
+ }
15
+ end
16
+ }
17
+ end
18
+ #:startdoc:
19
+
20
+ # This loads structs defaults from the given file, which will
21
+ # be used for field_type inferring and autocompletion/filtering of default values.
22
+ # A sample file has been provided with nwn-lib, called gff-bioware.yml
23
+ def self.load_struct_defaults file
24
+ @YAMLStructDefaults = YAML.load(IO.read(file))
25
+ new = {}
26
+ @YAMLStructDefaults.each {|k,v|
27
+ new[Regexp.new(k + '$')] = v
28
+ }
29
+ @YAMLStructDefaults = new
30
+ end
31
+
32
+ def self.get_struct_defaults
33
+ @YAMLStructDefaults || {}
34
+ end
35
+
36
+ def self.get_struct_defaults_for path, key
37
+ sd = NWN::Gff.get_struct_defaults
38
+ sd.keys.each {|rx|
39
+ next unless path =~ rx
40
+ return sd[rx][key] if sd[rx][key] != nil
41
+ }
42
+ nil
43
+ end
44
+
45
+ def self.get_struct_default_type path, key
46
+ dd = get_struct_defaults_for(path, key)
47
+ dd.is_a?(Array) ? dd[0] : dd
48
+ end
49
+
50
+ def self.get_struct_default_value path, key
51
+ dd = get_struct_defaults_for(path, key)
52
+ dd.is_a?(Array) ? dd[1] : nil
53
+ end
54
+ end
55
+
56
+ module NWN::Gff::Struct
57
+ # Returns true if we can later infer the struct_id with the given path.
58
+ def can_infer_struct_id?
59
+ v = NWN::Gff.get_struct_defaults_for(self.path, '__struct_id')
60
+ v == @struct_id || v == "iterative" || v == "inline"
61
+ end
62
+
63
+ # Returns true if we can infer the data version of this struct (if it has parent).
64
+ def can_infer_data_version?
65
+ @data_version == DEFAULT_DATA_VERSION || (
66
+ @element && @element.parent && @element.parent.data_version == @data_version
67
+ )
68
+ end
69
+ end
70
+
71
+ module NWN::Gff::Cexolocstr
72
+ def field_value_as_compact
73
+ !can_infer_str_ref? ? field_value.merge({'str_ref' => str_ref}) : field_value
74
+ end
75
+ end
76
+
77
+ module NWN::Gff::Field
78
+ YAMLCompactableFields = [:byte, :char, :word, :short, :dword, :int, :void,
79
+ :dword64, :int64, :float, :double, :cexostr, :resref, :cexolocstr, :list]
80
+
81
+ # Returns true if we can later infer the field type.
82
+ def can_infer_type?
83
+ expected = NWN::Gff.get_struct_default_type(@parent.path, field_label)
84
+
85
+ raise NWN::Gff::GffError, "#{field_label} has field_type " +
86
+ "#{field_type.inspect}, but infer data says #{expected.inspect}." if
87
+ expected && expected != field_type
88
+
89
+ expected == field_type
90
+ end
91
+
92
+ # Returns true if we can later infer the default value.
93
+ def can_infer_value?
94
+ NWN::Gff.get_struct_default_value(@parent.path, field_label) == field_value
95
+ end
96
+
97
+ # Returns true if we can infer the str ref later on.
98
+ def can_infer_str_ref?
99
+ !has_str_ref? || (d = NWN::Gff.get_struct_defaults_for(@parent.path, field_label) && d && d[2] != nil)
100
+ end
101
+
102
+ # Can we print this field without any syntactic gizmos?
103
+ def can_compact_print?
104
+
105
+ YAMLCompactableFields.index(field_type) &&
106
+ # exolocs print their str_ref along with their language keys
107
+ (field_type == :cexolocstr || can_infer_str_ref?) &&
108
+ can_infer_type?
109
+ end
110
+
111
+ def can_compact_as_list?
112
+ NWN::Gff.get_struct_defaults_for(self.path, '__compact') != nil &&
113
+ field_value.reject {|x|
114
+ x.can_infer_struct_id?
115
+ }.size == 0
116
+ end
117
+
118
+ def get_compact_as_list_field
119
+ NWN::Gff.get_struct_defaults_for(self.path, '__compact')
120
+ end
121
+
122
+ def field_value_as_compact
123
+ field_value
124
+ end
125
+ end
@@ -0,0 +1,55 @@
1
+ module NWN::Gff::Struct
2
+
3
+ # yield (key, value) for each element, recursing into substructs.
4
+
5
+ # Parses +s+ as an arbitary GFF object and yields for each field found,
6
+ # with the proper prefix.
7
+ #
8
+ # [+prefix+] Supply a prefix to add to the output.
9
+ # [+types_too+] Yield type definitions as well (gffprint.pl -t).
10
+ # [+add_prefix+] Add a prefix <tt>(unknown type)</tt> of no type information can be derived from the input.
11
+ # [+file_type+] File type override. If non-null, add a global struct header with the given file type (useful for passing to gffencode.pl)
12
+ # [+struct_id+] Provide a struct_id override (if printing a struct).
13
+ def kivinen_format types_too = false, add_prefix = true, file_type = nil, struct_id = nil, &block
14
+
15
+ if types_too
16
+ yield("/", "")
17
+
18
+ ftype = file_type ? file_type : self.data_type
19
+ yield("/ ____file_type", ftype) if ftype
20
+ yield("/ ____file_version", self.data_version) if self.data_version
21
+
22
+ yield("/ ____struct_type", self.struct_id)
23
+ end
24
+
25
+ self.each_by_flat_path {|path, field|
26
+ case field
27
+ when String
28
+ yield(path, field)
29
+
30
+ when NWN::Gff::Struct
31
+ yield(path + "/", path)
32
+ yield(path + "/ ____struct_type", field.struct_id)
33
+
34
+ when NWN::Gff::Field
35
+
36
+ case field.field_type
37
+ when :list
38
+ when :struct
39
+ yield(path + "/", path)
40
+ yield(path + "/ ____struct_type", field.field_value.struct_id)
41
+ when :cexolocstr
42
+ else
43
+ yield(path, field.field_value)
44
+ end
45
+
46
+ yield(path + ". ____string_ref",field.str_ref) if
47
+ field.has_str_ref? || field.field_type == :cexolocstr
48
+
49
+ yield(path + ". ____type", NWN::Gff::Types.index(field.field_type)) if
50
+ types_too
51
+
52
+ end
53
+ }
54
+ end
55
+ end
@@ -0,0 +1,129 @@
1
+ # This module contains scripting functions and helpers.
2
+ #
3
+ # Include this if you want to eval nwn-gff-dsl scripts.
4
+ module NWN::Gff::Scripting
5
+
6
+ class Sandbox
7
+ include NWN
8
+ include NWN::Gff::Scripting
9
+ end
10
+
11
+ # Run a script in a sandboxish environ.
12
+ # Returns true if the code modified run_on in any way.
13
+ def self.run_script code, run_on = nil, arguments = []
14
+ $code = code
15
+ $argv = arguments
16
+ $standalone = run_on.nil?
17
+ run_on ||= Sandbox.new
18
+
19
+ $script_obj_hash = run_on.hash
20
+ catch(:exit) {
21
+ begin
22
+ run_on.instance_eval $code
23
+ rescue => e
24
+ raise
25
+ end
26
+ }
27
+ $script_obj_hash != run_on.hash
28
+ end
29
+
30
+ # This script only runs for the following conditions (see #satisfy).
31
+ def want *what
32
+ obj = satisfy(*what)
33
+ unless obj
34
+ log "Wants #{what.inspect}, cannot satisfy - continuing."
35
+ throw(:exit)
36
+ end
37
+ obj
38
+ end
39
+
40
+ # Same as want, but error out (don't continue processing).
41
+ def need *what
42
+ satisfy(*what) or raise ArgumentError, "Needs #{what.inspect}, cannot satisfy - aborting."
43
+ end
44
+
45
+ # Call this to prevent nwn-gff from emitting output.
46
+ def stop_output
47
+ if $standalone
48
+ log "warn: no need to stop_output on standalone scripts"
49
+ else
50
+ log "#{$base_script}: not emitting any data."
51
+ end
52
+ $stop_output = true
53
+ end
54
+
55
+ def will_output?
56
+ !$stop_output
57
+ end
58
+
59
+ # This checks if the currently-operating field or struct
60
+ # satisfies one of the given conditions.
61
+ #
62
+ # When you're running in standalone mode, the first argument
63
+ # is expected to be a file or IO stream that needs to satisfy
64
+ # the given conditions.
65
+ #
66
+ # Example:
67
+ # need ARGV.shift, :bic, :utc, :uti
68
+ # will require the user to supply a filename as the first argument
69
+ # to the standalone script, which needs to resolve to a bic, utc, or
70
+ # uti data_type.
71
+ #
72
+ # Conditions can be:
73
+ # * A symbol describing the data_type, eg :utc
74
+ # * A symbol describing the field_type, eg :int
75
+ # * A module or class name
76
+ #
77
+ # Returns the object that satisfies the asked-for conditions,
78
+ # or nil if none can be given.
79
+ def satisfy *what
80
+ if $standalone
81
+ fn = what.shift
82
+ io = case fn
83
+ when String
84
+ IO.read(fn)
85
+ when IO
86
+ fn
87
+ else
88
+ return nil
89
+ #raise ArgumentError, "When running in standalone mode, " +
90
+ # "`need', `want' and `satisfy' need a filename or a IO " +
91
+ # "object to read from (usually the first script argument)."
92
+ end
93
+ obj = NWN::Gff.read(io, NWN::Gff.guess_file_format(fn))
94
+ else
95
+ obj = self
96
+ end
97
+
98
+ what.each {|w|
99
+ case w
100
+ when Class, Module
101
+ return obj if obj.is_a?(w)
102
+
103
+ when Symbol
104
+ case obj
105
+ when NWN::Gff::Struct
106
+ return obj if obj.data_type.downcase == w.to_s.downcase
107
+ when NWN::Gff::Field
108
+ return obj if obj.field_type.to_sdowncase == w.to_s.downcase
109
+ end
110
+ end
111
+
112
+ }
113
+
114
+ return nil
115
+ end
116
+
117
+
118
+ # Log a friendly message to stderr.
119
+ # Use this instead of puts, since SAFE levels greater than 0
120
+ # will prevent you from doing logging yourself.
121
+ def log *args
122
+ if $options
123
+ $stderr.puts [$base_script, " on ", $options[:infile], ": ", *args].join("")
124
+ else
125
+ $stderr.puts [$base_script, ": ", *args].join("")
126
+ end
127
+ end
128
+
129
+ end