rvpacker 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,34 +1,34 @@
1
- *.gem
2
- *.rbc
3
- /.config
4
- /coverage/
5
- /InstalledFiles
6
- /pkg/
7
- /spec/reports/
8
- /test/tmp/
9
- /test/version_tmp/
10
- /tmp/
11
-
12
- ## Specific to RubyMotion:
13
- .dat*
14
- .repl_history
15
- build/
16
-
17
- ## Documentation cache and generated files:
18
- /.yardoc/
19
- /_yardoc/
20
- /doc/
21
- /rdoc/
22
-
23
- ## Environment normalisation:
24
- /.bundle/
25
- /lib/bundler/man/
26
-
27
- # for a library or gem, you might want to ignore these files since the code is
28
- # intended to run in multiple environments; otherwise, check them in:
29
- # Gemfile.lock
30
- # .ruby-version
31
- # .ruby-gemset
32
-
33
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
- .rvmrc
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rvpacker.gemspec
4
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rvpacker.gemspec
4
+ gemspec
data/LICENSE CHANGED
@@ -1,17 +1,17 @@
1
- Copyright (c) 2013 Howard Jeng
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
- software and associated documentation files (the "Software"), to deal in the Software
5
- without restriction, including without limitation the rights to use, copy, modify, merge,
6
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
- to whom the Software is furnished to do so, subject to the following conditions:
8
-
9
- The above copyright notice and this permission notice shall be included in all copies or
10
- substantial portions of the Software.
11
-
12
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17
- DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2013 Howard Jeng
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
+ software and associated documentation files (the "Software"), to deal in the Software
5
+ without restriction, including without limitation the rights to use, copy, modify, merge,
6
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
+ to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or
10
+ substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17
+ DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,29 +1,78 @@
1
- # Rvpacker
2
-
3
- TODO: Write a gem description
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- gem 'rvpacker'
10
-
11
- And then execute:
12
-
13
- $ bundle
14
-
15
- Or install it yourself as:
16
-
17
- $ gem install rvpacker
18
-
19
- ## Usage
20
-
21
- TODO: Write usage instructions here
22
-
23
- ## Contributing
24
-
25
- 1. Fork it ( https://github.com/[my-github-username]/rvpacker/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create a new Pull Request
1
+ rvpacker
2
+ ======================
3
+
4
+ A tool to unpack & pack RPGMaker data files into text so they can be version controlled & collaborated on
5
+
6
+ rvpacker consists of 3 parts:
7
+
8
+ * RPG library (stub classes for serialization of RPGMaker game data)
9
+ * RGSS library (some more classes for RPGMaker serialization)
10
+ * rvpacker (the script you call on the frontend)
11
+
12
+ Credit to SiCrane
13
+ =================
14
+
15
+ The RPG and RGSS libraries were originally taken from SiCrane's YAML importer/exporter on the gamedev forums. I initially just put them in github so I wouldn't lose them, and added the rvpacker script frontend. They are starting to drift a bit, but SiCrane still gets original credit for the grand majority of the work that rvpacker does.
16
+
17
+ http://www.gamedev.net/topic/646333-rpg-maker-vx-ace-data-conversion-utility/
18
+
19
+ Installation
20
+ ============
21
+
22
+ rvpacker is bundled as a rubygem
23
+
24
+ []$ gem install rvpacker
25
+
26
+ ... dependencies will be handled automatically.
27
+
28
+ If you are a windows user and rvpacker complains about being unable to install psych, try downloading the rvpacker source and doing 'bundle install'. That, for some reason, works. (Don't ask me, I think ruby is retarded here.)
29
+
30
+ Usage
31
+ =====
32
+
33
+ $ rvpacker --help
34
+ Options:
35
+ --action, -a <s>: Action to perform on project (unpack|pack)
36
+ --project, -d <s>: RPG Maker Project directory
37
+ --force, -f: Update target even when source is older than target
38
+ --project-type, -t <s>: Project type (vx|ace|xp)
39
+ --help, -h: Show this message
40
+
41
+ For example, to unpack a RPG Maker VX Ace project in ~/Documents/RPGVXAce/Project1:
42
+
43
+ rvpacker --action unpack --project ~/Documents/RPGVXAce/Project1 --project-type ace
44
+
45
+ ... This will expand all Data/* files into (PROJECT)/YAML/ as YAML files (YAML is used because the object serialization data is retained, which ruby's YAML parser is very good at - otherwise I would have changed it to JSON). The Scripts will be unpacked as individual .rb files into (PROJECT)/Scripts/.
46
+
47
+ To take a previously unpacked project, and pack it back up:
48
+
49
+ rvpacker --action pack --project ~/Documents/RPGVXAce/Project1 --project-type ace
50
+
51
+ ... This will take all of the yaml files in (PROJECT)/YAML and all the scripts in (PROJECT)/Scripts, and repack all of your (PROJECT)/Data/* files. You can trust this to completely reassemble your Data/ directory, so long as the Scripts/ and YAML/ directories remain intact.
52
+
53
+ Workflow
54
+ ========
55
+
56
+ This is great for teams that are collaborating on an RPG Maker project. Just add a few steps to your existing workflow:
57
+
58
+ * Checkout the project from version control
59
+ * Run 'rvpacker --action pack' on the project to repack it for the RPG Maker tool
60
+ * Load up RPG Maker and do whatever you're going to do; save the project
61
+ * Run 'rvpacker --action unpack' on the project
62
+ * Commit everything to version control (ignore the Data directory since you don't need it anymore; use .gitignore or .hgignore or whatever)
63
+
64
+ ... Now your project can be forked/merged in a much more safe/sane way, and you don't have to have someone bottlenecking the entire process.
65
+
66
+ Automatic ID generation
67
+ =======================
68
+
69
+ You can add new elements to the YAML files manually, and leave their 'id:' field set to 'null'. This will cause the rvpacker pack action to automatically assign them a new ID number at the end of the sequence (e.g., if you have 17 items, the new one becomes ID 18). This is mainly handy for adding new scripts to the project without having to open the RPG maker and paste the script in; just make the new script file, add its entry in YAML/Scripts.yaml, and the designer will have your script accessible the next time they repack and open the project.
70
+
71
+ Also, the rvpacker tool sets the ID of script files to an autoincrementing integer. The scripts exist in the database with a magic number that I can't recreate, and nothing in the editor (RPG VX Ace anyway) seems to care if the magic number changes. It doesn't even affect the ordering. So in order to support adding new scripts with null IDs, like everything else, the magic numbers on scripts are disregarded and a new ID number is forced on the scripts when the rvpacker pack action occurs.
72
+
73
+ Psych 2.0.0 Dependency
74
+ ======================
75
+
76
+ From SiCrane:
77
+
78
+ I used cygwin's ruby 1.9.3 and the Psych 2.0.0 ruby gem, which appears to be the most recent version. However, Psych 2.0.0 has some bugs that impacted the generated YAML (one major and one minor) which I monkey patched, and since I was already rewriting the Psych code, I added some functionality to make the generated YAML prettier. Long story short, this code probably won't work with any version of Psych but 2.0.0.
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "bundler/gem_tasks"
2
-
1
+ require "bundler/gem_tasks"
2
+
@@ -10,6 +10,7 @@ opts = Trollop::options do
10
10
  opt :force, "Update target even when source is older than target", :short => "f"
11
11
  opt :project_type, "Project type (vx|ace|xp)", :short => "t", :type => String
12
12
  opt :verbose, "Print verbose information while processing", :short => "V"
13
+ opt :database, "Only work on the given database", :short => "D", :type => String
13
14
  end
14
15
 
15
16
  directions = {
@@ -28,7 +29,8 @@ RGSS.serialize(projecttypes[opts[:project_type]],
28
29
  opts[:project],
29
30
  { :force => (opts[:force] ? true : false),
30
31
  :line_width => -1,
31
- :table_width => -1
32
+ :table_width => -1,
33
+ :database => opts[:database]
32
34
  }
33
35
  )
34
36
 
@@ -1,320 +1,320 @@
1
- =begin
2
- Copyright (c) 2013 Howard Jeng
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
5
- software and associated documentation files (the "Software"), to deal in the Software
6
- without restriction, including without limitation the rights to use, copy, modify, merge,
7
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8
- to whom the Software is furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in all copies or
11
- substantial portions of the Software.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
- DEALINGS IN THE SOFTWARE.
19
- =end
20
-
21
- require 'scanf'
22
-
23
- class Table
24
- def initialize(bytes)
25
- @dim, @x, @y, @z, items, *@data = bytes.unpack('L5 S*')
26
- raise "Size mismatch loading Table from data" unless items == @data.length
27
- raise "Size mismatch loading Table from data" unless @x * @y * @z == items
28
- end
29
-
30
- MAX_ROW_LENGTH = 20
31
-
32
- def encode_with(coder)
33
- coder.style = Psych::Nodes::Mapping::BLOCK
34
-
35
- coder['dim'] = @dim
36
- coder['x'] = @x
37
- coder['y'] = @y
38
- coder['z'] = @z
39
-
40
- if @x * @y * @z > 0
41
- stride = @x < 2 ? (@y < 2 ? @z : @y) : @x
42
- rows = @data.each_slice(stride).to_a
43
- if MAX_ROW_LENGTH != -1 && stride > MAX_ROW_LENGTH
44
- block_length = (stride + MAX_ROW_LENGTH - 1) / MAX_ROW_LENGTH
45
- row_length = (stride + block_length - 1) / block_length
46
- rows = rows.collect{|x| x.each_slice(row_length).to_a}.flatten(1)
47
- end
48
- rows = rows.collect{|x| x.collect{|y| "%04x" % y}.join(" ")}
49
- coder['data'] = rows
50
- else
51
- coder['data'] = []
52
- end
53
- end
54
-
55
- def init_with(coder)
56
- @dim = coder['dim']
57
- @x = coder['x']
58
- @y = coder['y']
59
- @z = coder['z']
60
- @data = coder['data'].collect{|x| x.split(" ").collect{|y| y.hex}}.flatten
61
- items = @x * @y * @z
62
- raise "Size mismatch loading Table from YAML" unless items == @data.length
63
- end
64
-
65
- def _dump(*ignored)
66
- return [@dim, @x, @y, @z, @x * @y * @z, *@data].pack('L5 S*')
67
- end
68
-
69
- def self._load(bytes)
70
- Table.new(bytes)
71
- end
72
- end
73
-
74
- class Color
75
- def initialize(bytes)
76
- @r, @g, @b, @a = *bytes.unpack('D4')
77
- end
78
-
79
- def _dump(*ignored)
80
- return [@r, @g, @b, @a].pack('D4')
81
- end
82
-
83
- def self._load(bytes)
84
- Color.new(bytes)
85
- end
86
- end
87
-
88
- class Tone
89
- def initialize(bytes)
90
- @r, @g, @b, @a = *bytes.unpack('D4')
91
- end
92
-
93
- def _dump(*ignored)
94
- return [@r, @g, @b, @a].pack('D4')
95
- end
96
-
97
- def self._load(bytes)
98
- Tone.new(bytes)
99
- end
100
- end
101
-
102
- class Rect
103
- def initialize(bytes)
104
- @x, @y, @width, @height = *bytes.unpack('i4')
105
- end
106
-
107
- def _dump(*ignored)
108
- return [@x, @y, @width, @height].pack('i4')
109
- end
110
-
111
- def self._load(bytes)
112
- Rect.new(bytes)
113
- end
114
- end
115
-
116
- module RGSS
117
- def self.remove_defined_method(scope, name)
118
- scope.send(:remove_method, name) if scope.instance_methods(false).include?(name)
119
- end
120
-
121
- def self.reset_method(scope, name, method)
122
- remove_defined_method(scope, name)
123
- scope.send(:define_method, name, method)
124
- end
125
-
126
- def self.reset_const(scope, sym, value)
127
- scope.send(:remove_const, sym) if scope.const_defined?(sym)
128
- scope.send(:const_set, sym, value)
129
- end
130
-
131
- def self.array_to_hash(arr, &block)
132
- h = {}
133
- arr.each_with_index do |val, index|
134
- r = block_given? ? block.call(val) : val
135
- h[index] = r unless r.nil?
136
- end
137
- if arr.length > 0
138
- last = arr.length - 1
139
- h[last] = nil unless h.has_key?(last)
140
- end
141
- return h
142
- end
143
-
144
- def self.hash_to_array(hash)
145
- arr = []
146
- hash.each do |k, v|
147
- arr[k] = v
148
- end
149
- return arr
150
- end
151
-
152
- require 'RGSS/BasicCoder'
153
- require 'RPG'
154
-
155
- # creates an empty class in a potentially nested scope
156
- def self.process(root, name, *args)
157
- if args.length > 0
158
- process(root.const_get(name), *args)
159
- else
160
- root.const_set(name, Class.new) unless root.const_defined?(name, false)
161
- end
162
- end
163
-
164
- # other classes that don't need definitions
165
- [ # RGSS data structures
166
- [:RPG, :Actor], [:RPG, :Animation], [:RPG, :Animation, :Frame],
167
- [:RPG, :Animation, :Timing], [:RPG, :Area], [:RPG, :Armor], [:RPG, :AudioFile],
168
- [:RPG, :BaseItem], [:RPG, :BaseItem, :Feature], [:RPG, :BGM], [:RPG, :BGS],
169
- [:RPG, :Class], [:RPG, :Class, :Learning], [:RPG, :CommonEvent], [:RPG, :Enemy],
170
- [:RPG, :Enemy, :Action], [:RPG, :Enemy, :DropItem], [:RPG, :EquipItem],
171
- [:RPG, :Event], [:RPG, :Event, :Page], [:RPG, :Event, :Page, :Condition],
172
- [:RPG, :Event, :Page, :Graphic], [:RPG, :Item], [:RPG, :Map],
173
- [:RPG, :Map, :Encounter], [:RPG, :MapInfo], [:RPG, :ME], [:RPG, :MoveCommand],
174
- [:RPG, :MoveRoute], [:RPG, :SE], [:RPG, :Skill], [:RPG, :State],
175
- [:RPG, :System, :Terms], [:RPG, :System, :TestBattler], [:RPG, :System, :Vehicle],
176
- [:RPG, :System, :Words], [:RPG, :Tileset], [:RPG, :Troop], [:RPG, :Troop, :Member],
177
- [:RPG, :Troop, :Page], [:RPG, :Troop, :Page, :Condition], [:RPG, :UsableItem],
178
- [:RPG, :UsableItem, :Damage], [:RPG, :UsableItem, :Effect], [:RPG, :Weapon],
179
- # Script classes serialized in save game files
180
- [:Game_ActionResult], [:Game_Actor], [:Game_Actors], [:Game_BaseItem],
181
- [:Game_BattleAction], [:Game_CommonEvent], [:Game_Enemy], [:Game_Event],
182
- [:Game_Follower], [:Game_Followers], [:Game_Interpreter], [:Game_Map],
183
- [:Game_Message], [:Game_Party], [:Game_Picture], [:Game_Pictures], [:Game_Player],
184
- [:Game_System], [:Game_Timer], [:Game_Troop], [:Game_Screen], [:Game_Vehicle],
185
- [:Interpreter]
186
- ].each {|x| process(Object, *x)}
187
-
188
- def self.setup_system(version, options)
189
- # convert variable and switch name arrays to a hash when serialized
190
- # if round_trip isn't set change version_id to fixed number
191
- if options[:round_trip]
192
- iso = ->(val) { return val }
193
- reset_method(RPG::System, :reduce_string, iso)
194
- reset_method(RPG::System, :map_version, iso)
195
- reset_method(Game_System, :map_version, iso)
196
- else
197
- reset_method(RPG::System, :reduce_string, ->(str) {
198
- return nil if str.nil?
199
- stripped = str.strip
200
- return stripped.empty? ? nil : stripped
201
- })
202
- # These magic numbers should be different. If they are the same, the saved version
203
- # of the map in save files will be used instead of any updated version of the map
204
- reset_method(RPG::System, :map_version, ->(ignored) { return 12345678 })
205
- reset_method(Game_System, :map_version, ->(ignored) { return 87654321 })
206
- end
207
- end
208
-
209
- def self.setup_interpreter(version)
210
- # Game_Interpreter is marshalled differently in VX Ace
211
- if version == :ace
212
- reset_method(Game_Interpreter, :marshal_dump, ->{
213
- return @data
214
- })
215
- reset_method(Game_Interpreter, :marshal_load, ->(obj) {
216
- @data = obj
217
- })
218
- else
219
- remove_defined_method(Game_Interpreter, :marshal_dump)
220
- remove_defined_method(Game_Interpreter, :marshal_load)
221
- end
222
- end
223
-
224
- def self.setup_event_command(version, options)
225
- # format event commands to flow style for the event codes that aren't move commands
226
- if options[:round_trip]
227
- reset_method(RPG::EventCommand, :clean, ->{})
228
- else
229
- reset_method(RPG::EventCommand, :clean, ->{
230
- @parameters[0].rstrip! if @code == 401
231
- })
232
- end
233
- reset_const(RPG::EventCommand, :MOVE_LIST_CODE, version == :xp ? 209 : 205)
234
- end
235
-
236
- def self.setup_classes(version, options)
237
- setup_system(version, options)
238
- setup_interpreter(version)
239
- setup_event_command(version, options)
240
- BasicCoder.set_ivars_methods(version)
241
- end
242
-
243
- FLOW_CLASSES = [Color, Tone, RPG::BGM, RPG::BGS, RPG::MoveCommand, RPG::SE]
244
-
245
- SCRIPTS_BASE = 'Scripts'
246
-
247
- ACE_DATA_EXT = '.rvdata2'
248
- VX_DATA_EXT = '.rvdata'
249
- XP_DATA_EXT = '.rxdata'
250
- YAML_EXT = '.yaml'
251
- RUBY_EXT = '.rb'
252
-
253
- def self.get_data_directory(base)
254
- return File.join(base, 'Data')
255
- end
256
-
257
- def self.get_yaml_directory(base)
258
- return File.join(base, 'YAML')
259
- end
260
-
261
- def self.get_script_directory(base)
262
- return File.join(base, 'Scripts')
263
- end
264
-
265
- class Game_Switches
266
- include RGSS::BasicCoder
267
-
268
- def encode(name, value)
269
- return array_to_hash(value)
270
- end
271
-
272
- def decode(name, value)
273
- return hash_to_array(value)
274
- end
275
- end
276
-
277
- class Game_Variables
278
- include RGSS::BasicCoder
279
-
280
- def encode(name, value)
281
- return array_to_hash(value)
282
- end
283
-
284
- def decode(name, value)
285
- return hash_to_array(value)
286
- end
287
- end
288
-
289
- class Game_SelfSwitches
290
- include RGSS::BasicCoder
291
-
292
- def encode(name, value)
293
- return Hash[value.collect {|pair|
294
- key, value = pair
295
- next ["%03d %03d %s" % key, value]
296
- }]
297
- end
298
-
299
- def decode(name, value)
300
- return Hash[value.collect {|pair|
301
- key, value = pair
302
- next [key.scanf("%d %d %s"), value]
303
- }]
304
- end
305
- end
306
-
307
- class Game_System
308
- include RGSS::BasicCoder
309
-
310
- def encode(name, value)
311
- if name == 'version_id'
312
- return map_version(value)
313
- else
314
- return value
315
- end
316
- end
317
- end
318
-
319
- require 'RGSS/serialize'
320
- end
1
+ =begin
2
+ Copyright (c) 2013 Howard Jeng
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
5
+ software and associated documentation files (the "Software"), to deal in the Software
6
+ without restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8
+ to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or
11
+ substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
+ DEALINGS IN THE SOFTWARE.
19
+ =end
20
+
21
+ require 'scanf'
22
+
23
+ class Table
24
+ def initialize(bytes)
25
+ @dim, @x, @y, @z, items, *@data = bytes.unpack('L5 S*')
26
+ raise "Size mismatch loading Table from data" unless items == @data.length
27
+ raise "Size mismatch loading Table from data" unless @x * @y * @z == items
28
+ end
29
+
30
+ MAX_ROW_LENGTH = 20
31
+
32
+ def encode_with(coder)
33
+ coder.style = Psych::Nodes::Mapping::BLOCK
34
+
35
+ coder['dim'] = @dim
36
+ coder['x'] = @x
37
+ coder['y'] = @y
38
+ coder['z'] = @z
39
+
40
+ if @x * @y * @z > 0
41
+ stride = @x < 2 ? (@y < 2 ? @z : @y) : @x
42
+ rows = @data.each_slice(stride).to_a
43
+ if MAX_ROW_LENGTH != -1 && stride > MAX_ROW_LENGTH
44
+ block_length = (stride + MAX_ROW_LENGTH - 1) / MAX_ROW_LENGTH
45
+ row_length = (stride + block_length - 1) / block_length
46
+ rows = rows.collect{|x| x.each_slice(row_length).to_a}.flatten(1)
47
+ end
48
+ rows = rows.collect{|x| x.collect{|y| "%04x" % y}.join(" ")}
49
+ coder['data'] = rows
50
+ else
51
+ coder['data'] = []
52
+ end
53
+ end
54
+
55
+ def init_with(coder)
56
+ @dim = coder['dim']
57
+ @x = coder['x']
58
+ @y = coder['y']
59
+ @z = coder['z']
60
+ @data = coder['data'].collect{|x| x.split(" ").collect{|y| y.hex}}.flatten
61
+ items = @x * @y * @z
62
+ raise "Size mismatch loading Table from YAML" unless items == @data.length
63
+ end
64
+
65
+ def _dump(*ignored)
66
+ return [@dim, @x, @y, @z, @x * @y * @z, *@data].pack('L5 S*')
67
+ end
68
+
69
+ def self._load(bytes)
70
+ Table.new(bytes)
71
+ end
72
+ end
73
+
74
+ class Color
75
+ def initialize(bytes)
76
+ @r, @g, @b, @a = *bytes.unpack('D4')
77
+ end
78
+
79
+ def _dump(*ignored)
80
+ return [@r, @g, @b, @a].pack('D4')
81
+ end
82
+
83
+ def self._load(bytes)
84
+ Color.new(bytes)
85
+ end
86
+ end
87
+
88
+ class Tone
89
+ def initialize(bytes)
90
+ @r, @g, @b, @a = *bytes.unpack('D4')
91
+ end
92
+
93
+ def _dump(*ignored)
94
+ return [@r, @g, @b, @a].pack('D4')
95
+ end
96
+
97
+ def self._load(bytes)
98
+ Tone.new(bytes)
99
+ end
100
+ end
101
+
102
+ class Rect
103
+ def initialize(bytes)
104
+ @x, @y, @width, @height = *bytes.unpack('i4')
105
+ end
106
+
107
+ def _dump(*ignored)
108
+ return [@x, @y, @width, @height].pack('i4')
109
+ end
110
+
111
+ def self._load(bytes)
112
+ Rect.new(bytes)
113
+ end
114
+ end
115
+
116
+ module RGSS
117
+ def self.remove_defined_method(scope, name)
118
+ scope.send(:remove_method, name) if scope.instance_methods(false).include?(name)
119
+ end
120
+
121
+ def self.reset_method(scope, name, method)
122
+ remove_defined_method(scope, name)
123
+ scope.send(:define_method, name, method)
124
+ end
125
+
126
+ def self.reset_const(scope, sym, value)
127
+ scope.send(:remove_const, sym) if scope.const_defined?(sym)
128
+ scope.send(:const_set, sym, value)
129
+ end
130
+
131
+ def self.array_to_hash(arr, &block)
132
+ h = {}
133
+ arr.each_with_index do |val, index|
134
+ r = block_given? ? block.call(val) : val
135
+ h[index] = r unless r.nil?
136
+ end
137
+ if arr.length > 0
138
+ last = arr.length - 1
139
+ h[last] = nil unless h.has_key?(last)
140
+ end
141
+ return h
142
+ end
143
+
144
+ def self.hash_to_array(hash)
145
+ arr = []
146
+ hash.each do |k, v|
147
+ arr[k] = v
148
+ end
149
+ return arr
150
+ end
151
+
152
+ require 'RGSS/BasicCoder'
153
+ require 'RPG'
154
+
155
+ # creates an empty class in a potentially nested scope
156
+ def self.process(root, name, *args)
157
+ if args.length > 0
158
+ process(root.const_get(name), *args)
159
+ else
160
+ root.const_set(name, Class.new) unless root.const_defined?(name, false)
161
+ end
162
+ end
163
+
164
+ # other classes that don't need definitions
165
+ [ # RGSS data structures
166
+ [:RPG, :Actor], [:RPG, :Animation], [:RPG, :Animation, :Frame],
167
+ [:RPG, :Animation, :Timing], [:RPG, :Area], [:RPG, :Armor], [:RPG, :AudioFile],
168
+ [:RPG, :BaseItem], [:RPG, :BaseItem, :Feature], [:RPG, :BGM], [:RPG, :BGS],
169
+ [:RPG, :Class], [:RPG, :Class, :Learning], [:RPG, :CommonEvent], [:RPG, :Enemy],
170
+ [:RPG, :Enemy, :Action], [:RPG, :Enemy, :DropItem], [:RPG, :EquipItem],
171
+ [:RPG, :Event], [:RPG, :Event, :Page], [:RPG, :Event, :Page, :Condition],
172
+ [:RPG, :Event, :Page, :Graphic], [:RPG, :Item], [:RPG, :Map],
173
+ [:RPG, :Map, :Encounter], [:RPG, :MapInfo], [:RPG, :ME], [:RPG, :MoveCommand],
174
+ [:RPG, :MoveRoute], [:RPG, :SE], [:RPG, :Skill], [:RPG, :State],
175
+ [:RPG, :System, :Terms], [:RPG, :System, :TestBattler], [:RPG, :System, :Vehicle],
176
+ [:RPG, :System, :Words], [:RPG, :Tileset], [:RPG, :Troop], [:RPG, :Troop, :Member],
177
+ [:RPG, :Troop, :Page], [:RPG, :Troop, :Page, :Condition], [:RPG, :UsableItem],
178
+ [:RPG, :UsableItem, :Damage], [:RPG, :UsableItem, :Effect], [:RPG, :Weapon],
179
+ # Script classes serialized in save game files
180
+ [:Game_ActionResult], [:Game_Actor], [:Game_Actors], [:Game_BaseItem],
181
+ [:Game_BattleAction], [:Game_CommonEvent], [:Game_Enemy], [:Game_Event],
182
+ [:Game_Follower], [:Game_Followers], [:Game_Interpreter], [:Game_Map],
183
+ [:Game_Message], [:Game_Party], [:Game_Picture], [:Game_Pictures], [:Game_Player],
184
+ [:Game_System], [:Game_Timer], [:Game_Troop], [:Game_Screen], [:Game_Vehicle],
185
+ [:Interpreter]
186
+ ].each {|x| process(Object, *x)}
187
+
188
+ def self.setup_system(version, options)
189
+ # convert variable and switch name arrays to a hash when serialized
190
+ # if round_trip isn't set change version_id to fixed number
191
+ if options[:round_trip]
192
+ iso = ->(val) { return val }
193
+ reset_method(RPG::System, :reduce_string, iso)
194
+ reset_method(RPG::System, :map_version, iso)
195
+ reset_method(Game_System, :map_version, iso)
196
+ else
197
+ reset_method(RPG::System, :reduce_string, ->(str) {
198
+ return nil if str.nil?
199
+ stripped = str.strip
200
+ return stripped.empty? ? nil : stripped
201
+ })
202
+ # These magic numbers should be different. If they are the same, the saved version
203
+ # of the map in save files will be used instead of any updated version of the map
204
+ reset_method(RPG::System, :map_version, ->(ignored) { return 12345678 })
205
+ reset_method(Game_System, :map_version, ->(ignored) { return 87654321 })
206
+ end
207
+ end
208
+
209
+ def self.setup_interpreter(version)
210
+ # Game_Interpreter is marshalled differently in VX Ace
211
+ if version == :ace
212
+ reset_method(Game_Interpreter, :marshal_dump, ->{
213
+ return @data
214
+ })
215
+ reset_method(Game_Interpreter, :marshal_load, ->(obj) {
216
+ @data = obj
217
+ })
218
+ else
219
+ remove_defined_method(Game_Interpreter, :marshal_dump)
220
+ remove_defined_method(Game_Interpreter, :marshal_load)
221
+ end
222
+ end
223
+
224
+ def self.setup_event_command(version, options)
225
+ # format event commands to flow style for the event codes that aren't move commands
226
+ if options[:round_trip]
227
+ reset_method(RPG::EventCommand, :clean, ->{})
228
+ else
229
+ reset_method(RPG::EventCommand, :clean, ->{
230
+ @parameters[0].rstrip! if @code == 401
231
+ })
232
+ end
233
+ reset_const(RPG::EventCommand, :MOVE_LIST_CODE, version == :xp ? 209 : 205)
234
+ end
235
+
236
+ def self.setup_classes(version, options)
237
+ setup_system(version, options)
238
+ setup_interpreter(version)
239
+ setup_event_command(version, options)
240
+ BasicCoder.set_ivars_methods(version)
241
+ end
242
+
243
+ FLOW_CLASSES = [Color, Tone, RPG::BGM, RPG::BGS, RPG::MoveCommand, RPG::SE]
244
+
245
+ SCRIPTS_BASE = 'Scripts'
246
+
247
+ ACE_DATA_EXT = '.rvdata2'
248
+ VX_DATA_EXT = '.rvdata'
249
+ XP_DATA_EXT = '.rxdata'
250
+ YAML_EXT = '.yaml'
251
+ RUBY_EXT = '.rb'
252
+
253
+ def self.get_data_directory(base)
254
+ return File.join(base, 'Data')
255
+ end
256
+
257
+ def self.get_yaml_directory(base)
258
+ return File.join(base, 'YAML')
259
+ end
260
+
261
+ def self.get_script_directory(base)
262
+ return File.join(base, 'Scripts')
263
+ end
264
+
265
+ class Game_Switches
266
+ include RGSS::BasicCoder
267
+
268
+ def encode(name, value)
269
+ return array_to_hash(value)
270
+ end
271
+
272
+ def decode(name, value)
273
+ return hash_to_array(value)
274
+ end
275
+ end
276
+
277
+ class Game_Variables
278
+ include RGSS::BasicCoder
279
+
280
+ def encode(name, value)
281
+ return array_to_hash(value)
282
+ end
283
+
284
+ def decode(name, value)
285
+ return hash_to_array(value)
286
+ end
287
+ end
288
+
289
+ class Game_SelfSwitches
290
+ include RGSS::BasicCoder
291
+
292
+ def encode(name, value)
293
+ return Hash[value.collect {|pair|
294
+ key, value = pair
295
+ next ["%03d %03d %s" % key, value]
296
+ }]
297
+ end
298
+
299
+ def decode(name, value)
300
+ return Hash[value.collect {|pair|
301
+ key, value = pair
302
+ next [key.scanf("%d %d %s"), value]
303
+ }]
304
+ end
305
+ end
306
+
307
+ class Game_System
308
+ include RGSS::BasicCoder
309
+
310
+ def encode(name, value)
311
+ if name == 'version_id'
312
+ return map_version(value)
313
+ else
314
+ return value
315
+ end
316
+ end
317
+ end
318
+
319
+ require 'RGSS/serialize'
320
+ end