adventure_rl 0.0.1.pre.ld42
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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +31 -0
- data/Rakefile +11 -0
- data/adventure_rl.gemspec +48 -0
- data/bin/console +7 -0
- data/bin/mkaudio +196 -0
- data/bin/mkclip +223 -0
- data/bin/rdoc +9 -0
- data/bin/setup +8 -0
- data/bin/vimall +5 -0
- data/doc/Mask.md +183 -0
- data/doc/Point.md +95 -0
- data/doc/Window.md +139 -0
- data/lib/AdventureRL/Animation.rb +63 -0
- data/lib/AdventureRL/Audio.rb +75 -0
- data/lib/AdventureRL/AudioPlayer.rb +65 -0
- data/lib/AdventureRL/Button.rb +51 -0
- data/lib/AdventureRL/Clip.rb +91 -0
- data/lib/AdventureRL/ClipPlayer.rb +187 -0
- data/lib/AdventureRL/Deltatime.rb +51 -0
- data/lib/AdventureRL/EventHandlers/Buttons.rb +225 -0
- data/lib/AdventureRL/EventHandlers/EventHandler.rb +62 -0
- data/lib/AdventureRL/EventHandlers/MouseButtons.rb +142 -0
- data/lib/AdventureRL/Events/Event.rb +69 -0
- data/lib/AdventureRL/Events/Mouse.rb +60 -0
- data/lib/AdventureRL/FileGroup.rb +100 -0
- data/lib/AdventureRL/FileGroupPlayer.rb +226 -0
- data/lib/AdventureRL/Helpers/Error.rb +68 -0
- data/lib/AdventureRL/Helpers/MethodHelper.rb +20 -0
- data/lib/AdventureRL/Helpers/PipeMethods.rb +26 -0
- data/lib/AdventureRL/Image.rb +77 -0
- data/lib/AdventureRL/Layer.rb +273 -0
- data/lib/AdventureRL/Mask.rb +462 -0
- data/lib/AdventureRL/Menu.rb +92 -0
- data/lib/AdventureRL/Modifiers/Gravity.rb +60 -0
- data/lib/AdventureRL/Modifiers/Inventory.rb +104 -0
- data/lib/AdventureRL/Modifiers/Pusher.rb +61 -0
- data/lib/AdventureRL/Modifiers/Solid.rb +302 -0
- data/lib/AdventureRL/Modifiers/Velocity.rb +163 -0
- data/lib/AdventureRL/Point.rb +188 -0
- data/lib/AdventureRL/Quadtree.rb +237 -0
- data/lib/AdventureRL/Rectangle.rb +62 -0
- data/lib/AdventureRL/Settings.rb +80 -0
- data/lib/AdventureRL/SolidsManager.rb +170 -0
- data/lib/AdventureRL/Textbox.rb +195 -0
- data/lib/AdventureRL/TimingHandler.rb +225 -0
- data/lib/AdventureRL/Window.rb +152 -0
- data/lib/AdventureRL/misc/extensions.rb +80 -0
- data/lib/AdventureRL/misc/require_files.rb +45 -0
- data/lib/AdventureRL/version.rb +3 -0
- data/lib/adventure_rl.rb +22 -0
- data/lib/default_settings.yml +20 -0
- data/vimrc +4 -0
- metadata +237 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
module AdventureRL
|
2
|
+
class Point
|
3
|
+
# This array will be filled with any created Points.
|
4
|
+
# Just so they won't get garbage collected
|
5
|
+
# <em>(not sure how garbage collection works)</em>.
|
6
|
+
POINTS = []
|
7
|
+
|
8
|
+
# Initialize with two arguments:
|
9
|
+
# <tt>x</tt>:: x position
|
10
|
+
# <tt>y</tt>:: y position
|
11
|
+
# <tt>args = {}</tt>:: Optional hash with extra options.
|
12
|
+
# Currently, the only valid hash key is <tt>:assign_to</tt>,
|
13
|
+
# to assign this Point to an object upon initialization.
|
14
|
+
def initialize x, y, args = {}
|
15
|
+
POINTS << self
|
16
|
+
@position = {
|
17
|
+
x: x,
|
18
|
+
y: y
|
19
|
+
}
|
20
|
+
@assigned_to = []
|
21
|
+
assign_to args[:assign_to] if (args[:assign_to])
|
22
|
+
@layer = nil
|
23
|
+
@real_point = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def assign_to object
|
27
|
+
Helpers::PipeMethods.pipe_methods_from object, to: self
|
28
|
+
@assigned_to << object
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns all objects this Point was assigned to.
|
32
|
+
def get_assigned
|
33
|
+
return @assigned_to
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if the Point has been
|
37
|
+
# assigned to the passed <tt>object</tt>.
|
38
|
+
def assigned_to? object
|
39
|
+
return @assigned_to.include? object
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns self.
|
43
|
+
def get_point
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_point?
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
def x
|
52
|
+
return get_position :x
|
53
|
+
end
|
54
|
+
|
55
|
+
def y
|
56
|
+
return get_position :y
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_position target = :all
|
60
|
+
target = target.to_sym
|
61
|
+
return @position if (target == :all)
|
62
|
+
return @position[target] if (@position.keys.include?(target))
|
63
|
+
return nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the new position with the given arguments.
|
67
|
+
# <tt>args</tt> may be:
|
68
|
+
# Two integers, representing the <tt>x</tt> and <tt>y</tt> axes, respectively.
|
69
|
+
# A hash containing one or both of the keys <tt>:x</tt> and <tt>:y</tt>.
|
70
|
+
def set_position *args
|
71
|
+
@real_point = nil
|
72
|
+
new_position = parse_position *args
|
73
|
+
@position[:x] = new_position[:x] if (new_position.key? :x)
|
74
|
+
@position[:y] = new_position[:y] if (new_position.key? :y)
|
75
|
+
return get_position
|
76
|
+
end
|
77
|
+
alias_method :move_to, :set_position
|
78
|
+
|
79
|
+
# Move the Point relative to the given arguments.
|
80
|
+
# <tt>args</tt> may be:
|
81
|
+
# Two integers, representing the <tt>x</tt> and <tt>y</tt> axes, respectively.
|
82
|
+
# A hash containing one or both of the keys <tt>:x</tt> and <tt>:y</tt>.
|
83
|
+
def move_by *args
|
84
|
+
@real_point = nil
|
85
|
+
incremental_position = parse_position *args
|
86
|
+
@position[:x] += incremental_position[:x] if (incremental_position.key? :x)
|
87
|
+
@position[:y] += incremental_position[:y] if (incremental_position.key? :y)
|
88
|
+
return get_position
|
89
|
+
end
|
90
|
+
|
91
|
+
def collides_with? other
|
92
|
+
return collides_with_mask? other if (defined? other.has_mask?)
|
93
|
+
return collides_with_point? other if (defined? other.has_point?)
|
94
|
+
return collides_with_hash? other if (other.is_a?(Hash))
|
95
|
+
end
|
96
|
+
|
97
|
+
def collides_with_mask? mask
|
98
|
+
return mask.collides_with_point? self
|
99
|
+
end
|
100
|
+
|
101
|
+
def collides_with_point? point
|
102
|
+
return get_real_position == point.get_real_position
|
103
|
+
end
|
104
|
+
|
105
|
+
def collides_with_hash? hash
|
106
|
+
if (hash.keys.include_all?(:x, :y))
|
107
|
+
point = Point.new hash[:x], hash[:y]
|
108
|
+
return collides_with_point? point
|
109
|
+
end
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def keys
|
114
|
+
sorted_keys = [:x, :y]
|
115
|
+
return @position.keys.sort do |axis|
|
116
|
+
next sorted_keys.index axis
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def values
|
121
|
+
return @position.sort_by_keys(keys).values
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set the parent Layer.
|
125
|
+
def set_layer layer
|
126
|
+
error(
|
127
|
+
"Passed argument `layer' must be an instance of `Layer', but got",
|
128
|
+
"`#{layer.inspect}:#{layer.class.name}'."
|
129
|
+
) unless (layer.is_a? Layer)
|
130
|
+
@layer = layer
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the parent Layer.
|
134
|
+
def get_layer
|
135
|
+
return @layer
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns true if this Point has a parent Layer.
|
139
|
+
def has_layer?
|
140
|
+
return !!@layer
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns a new Point with the real window position of this Point.
|
144
|
+
def get_real_point
|
145
|
+
return self unless (has_layer?)
|
146
|
+
return @real_point if (@real_point)
|
147
|
+
layer_point = get_layer.get_real_corner :left, :top
|
148
|
+
@real_point = Point.new(
|
149
|
+
(layer_point.x + x),
|
150
|
+
(layer_point.y + y)
|
151
|
+
)
|
152
|
+
return @real_point
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns the real window position of this Point as a Hash.
|
156
|
+
def get_real_position
|
157
|
+
return get_real_point.get_position
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def parse_position *args
|
163
|
+
position = {}
|
164
|
+
args[0] = args[0].get_position if (args[0].is_a? Point)
|
165
|
+
case args.size
|
166
|
+
when 2
|
167
|
+
position[:x] = args[0]
|
168
|
+
position[:y] = args[1]
|
169
|
+
when 1
|
170
|
+
Helpers::Error.error(
|
171
|
+
"Ambiguous argument `#{args[0]}' for Point##{__method__}"
|
172
|
+
) unless (args[0].is_a?(Hash))
|
173
|
+
Helpers::Error.error(
|
174
|
+
"Hash must include either :x, :y, or both keys for Point##{__method__}"
|
175
|
+
) unless (args[0].keys.include_any?(:x, :y))
|
176
|
+
position[:x] = args[0][:x] if (args[0][:x])
|
177
|
+
position[:y] = args[0][:y] if (args[0][:y])
|
178
|
+
else
|
179
|
+
Helpers::Error.error(
|
180
|
+
"Invalid amount of arguments for Point##{__method__}.",
|
181
|
+
"Pass either two arguments representing the x and y axes, respectively, or",
|
182
|
+
"pass a single hash with the keys :x and :y with their respective axes values."
|
183
|
+
)
|
184
|
+
end
|
185
|
+
return position
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
module AdventureRL
|
2
|
+
class Quadtree < Mask
|
3
|
+
include Helpers::MethodHelper
|
4
|
+
|
5
|
+
DEFAULT_SETTINGS = Settings.new(
|
6
|
+
max_objects: 4,
|
7
|
+
position: {
|
8
|
+
x: 0,
|
9
|
+
y: 0
|
10
|
+
},
|
11
|
+
size: {
|
12
|
+
width: 960,
|
13
|
+
height: 540
|
14
|
+
},
|
15
|
+
origin: {
|
16
|
+
x: :left,
|
17
|
+
y: :top
|
18
|
+
}
|
19
|
+
)
|
20
|
+
|
21
|
+
def self.get_default_settings
|
22
|
+
window = Window.get_window
|
23
|
+
return Settings.new(
|
24
|
+
position: (window ? window.get_position : DEFAULT_SETTINGS.get(:window, :position) || DEFAULT_SETTINGS[:position]),
|
25
|
+
size: (window ? window.get_size : DEFAULT_SETTINGS.get(:window, :size) || DEFAULT_SETTINGS[:size]),
|
26
|
+
origin: (window ? window.get_origin : DEFAULT_SETTINGS.get(:window, :origin) || DEFAULT_SETTINGS[:origin]),
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize settings = {}
|
31
|
+
@settings = DEFAULT_SETTINGS.merge(Quadtree.get_default_settings).merge(settings)
|
32
|
+
super @settings
|
33
|
+
@max_objects = @settings.get :max_objects
|
34
|
+
@quadtrees = {
|
35
|
+
top_left: nil,
|
36
|
+
top_right: nil,
|
37
|
+
bottom_left: nil,
|
38
|
+
bottom_right: nil
|
39
|
+
}
|
40
|
+
@objects = []
|
41
|
+
add_object [@settings.get(:objects)].flatten.compact
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add the given Mask <tt>object</tt>(s) into the Quadtree,
|
45
|
+
# and split into smaller quadtrees if necessary.
|
46
|
+
def add_object object
|
47
|
+
objects = [object].flatten
|
48
|
+
objects.each do |obj|
|
49
|
+
validate_object_has_mask_or_point obj
|
50
|
+
add_object_to_quadtree obj
|
51
|
+
end
|
52
|
+
end
|
53
|
+
alias_method :add, :add_object
|
54
|
+
|
55
|
+
def add_object_to_quadtree object
|
56
|
+
return false unless (collides_with? object)
|
57
|
+
return false if (@objects.include? object)
|
58
|
+
|
59
|
+
if (@objects.size < @max_objects)
|
60
|
+
@objects << object
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
|
64
|
+
split_quadtrees unless (has_quadtrees?)
|
65
|
+
|
66
|
+
return get_quadtrees.map do |quadtree|
|
67
|
+
next quadtree.add_object_to_quadtree(object)
|
68
|
+
end .any?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns <tt>true</tt> if the given <tt>object</tt>
|
72
|
+
# collides with any other object and <tt>false</tt> if not.
|
73
|
+
def collides? object
|
74
|
+
validate_object_has_mask_or_point object
|
75
|
+
return collides_for?(object)
|
76
|
+
end
|
77
|
+
|
78
|
+
def collides_for? object
|
79
|
+
return false unless (collides_with? object)
|
80
|
+
return (
|
81
|
+
@objects.any? do |obj|
|
82
|
+
next obj != object && obj.collides_with?(object)
|
83
|
+
end ||
|
84
|
+
get_quadtrees.any? do |quadtree|
|
85
|
+
next quadtree.collides_for?(object)
|
86
|
+
end
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns all objects, that collide with <tt>object</tt>.
|
91
|
+
def get_colliding_objects object
|
92
|
+
validate_object_has_mask_or_point object
|
93
|
+
return get_colliding_objects_for(object)
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_colliding_objects_for object
|
97
|
+
colliding_objects = []
|
98
|
+
return colliding_objects unless (collides_with? object)
|
99
|
+
colliding_objects.concat(@objects.select do |obj|
|
100
|
+
next obj != object && obj.collides_with?(object)
|
101
|
+
end)
|
102
|
+
get_quadtrees.each do |quadtree|
|
103
|
+
colliding_objects.concat quadtree.get_colliding_objects_for(object)
|
104
|
+
end
|
105
|
+
return colliding_objects
|
106
|
+
end
|
107
|
+
|
108
|
+
# Reset this and all child Quadtrees.
|
109
|
+
# Removes all stored objects.
|
110
|
+
def reset
|
111
|
+
@objects.clear
|
112
|
+
get_quadtrees.each &:reset
|
113
|
+
end
|
114
|
+
|
115
|
+
# Remove and (try to) re-add the given <tt>object</tt>(s) (single or multiple).
|
116
|
+
def reset_object object
|
117
|
+
objects = [object].flatten
|
118
|
+
objects.each do |obj|
|
119
|
+
@objects.delete obj
|
120
|
+
add_object_to_quadtree obj
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Remove the given <tt>object</tt>(s) (single or multiple) from the Quadtree (or any children).
|
125
|
+
def remove_object object
|
126
|
+
objects = [object].flatten
|
127
|
+
objects.each do |obj|
|
128
|
+
@objects.delete obj
|
129
|
+
get_quadtrees.each do |quadtree|
|
130
|
+
quadtree.remove_object obj
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def validate_object_has_mask_or_point object
|
138
|
+
object.has_point? rescue error( # NOTE: #has_point? method must be available for both Point and Mask
|
139
|
+
"Expected an instance of Mask/Point or an object that has a Mask/Point, but got",
|
140
|
+
"`#{object.inspect}:#{object.class.name}'."
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns all the children Quadtrees.
|
145
|
+
def get_quadtrees
|
146
|
+
return @quadtrees.values.compact
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns <tt>true</tt> if this Quadtree has already been split
|
150
|
+
# and has children Quadtrees.
|
151
|
+
def has_quadtrees?
|
152
|
+
return @quadtrees.values.all?
|
153
|
+
end
|
154
|
+
|
155
|
+
def split_quadtrees
|
156
|
+
@quadtrees = @quadtrees.keys.map do |corner|
|
157
|
+
new_quadtree = get_split_quadtree_for_corner corner
|
158
|
+
next [corner, new_quadtree] if (new_quadtree)
|
159
|
+
next nil
|
160
|
+
end .compact.to_h
|
161
|
+
#move_objects_to_quadtrees if (@objects.any?) # NOTE: Doing this will break stuff.
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_split_quadtree_for_corner corner
|
165
|
+
method_name = "get_split_quadtree_#{corner.to_s}".to_sym
|
166
|
+
error(
|
167
|
+
"Method `#{method_name.to_s}' doesn't exist for `#{self.inspect}:#{self.class}'."
|
168
|
+
) unless (method_exists? method_name)
|
169
|
+
return method(method_name).call
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_split_quadtree_top_left
|
173
|
+
return Quadtree.new(Settings.new(
|
174
|
+
position: get_position,
|
175
|
+
size: get_size.map do |side, size|
|
176
|
+
next [side, (size.to_f * 0.5).round]
|
177
|
+
end .to_h,
|
178
|
+
origin: get_origin,
|
179
|
+
max_objects: @max_objects
|
180
|
+
))
|
181
|
+
end
|
182
|
+
|
183
|
+
def get_split_quadtree_top_right
|
184
|
+
return Quadtree.new(Settings.new(
|
185
|
+
position: get_position.map do |axis, pos|
|
186
|
+
next [axis, pos + (get_size(:width).to_f * 0.5).round] if (axis == :x)
|
187
|
+
next [axis, pos]
|
188
|
+
end .to_h,
|
189
|
+
size: get_size.map do |side, size|
|
190
|
+
next [side, (size.to_f * 0.5).round]
|
191
|
+
end .to_h,
|
192
|
+
origin: get_origin,
|
193
|
+
max_objects: @max_objects
|
194
|
+
))
|
195
|
+
end
|
196
|
+
|
197
|
+
def get_split_quadtree_bottom_left
|
198
|
+
return Quadtree.new(Settings.new(
|
199
|
+
position: get_position.map do |axis, pos|
|
200
|
+
next [axis, pos + (get_size(:height).to_f * 0.5).round] if (axis == :y)
|
201
|
+
next [axis, pos]
|
202
|
+
end .to_h,
|
203
|
+
size: get_size.map do |side, size|
|
204
|
+
next [side, (size.to_f * 0.5).round]
|
205
|
+
end .to_h,
|
206
|
+
origin: get_origin,
|
207
|
+
max_objects: @max_objects
|
208
|
+
))
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_split_quadtree_bottom_right
|
212
|
+
return Quadtree.new(Settings.new(
|
213
|
+
position: get_position.map do |axis, pos|
|
214
|
+
next [axis, pos + (get_size(:width).to_f * 0.5).round] if (axis == :x)
|
215
|
+
next [axis, pos + (get_size(:height).to_f * 0.5).round] if (axis == :y)
|
216
|
+
end .to_h,
|
217
|
+
size: get_size.map do |side, size|
|
218
|
+
next [side, (size.to_f * 0.5).round]
|
219
|
+
end .to_h,
|
220
|
+
origin: get_origin,
|
221
|
+
max_objects: @max_objects
|
222
|
+
))
|
223
|
+
end
|
224
|
+
|
225
|
+
# NOTE: Shouldn't be used, breaks stuff currently.
|
226
|
+
# Life is easier without this method.
|
227
|
+
def move_objects_to_quadtrees
|
228
|
+
return if (@objects.empty?)
|
229
|
+
@objects.each do |object|
|
230
|
+
get_quadtrees.detect do |quadtree|
|
231
|
+
next quadtree.add_object_to_quadtree(object)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
@objects.clear
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module AdventureRL
|
2
|
+
class Rectangle < Mask
|
3
|
+
include Helpers::Error
|
4
|
+
|
5
|
+
# Default settings for Rectangle.
|
6
|
+
# <tt>settings</tt> passed to #new take precedence.
|
7
|
+
DEFAULT_SETTINGS = Settings.new(
|
8
|
+
color: 0xff_ffffff,
|
9
|
+
z_index: 0,
|
10
|
+
position: {
|
11
|
+
x: 0,
|
12
|
+
y: 0
|
13
|
+
},
|
14
|
+
size: {
|
15
|
+
width: 128,
|
16
|
+
height: 128
|
17
|
+
},
|
18
|
+
origin: {
|
19
|
+
x: :left,
|
20
|
+
y: :top
|
21
|
+
}
|
22
|
+
)
|
23
|
+
|
24
|
+
# Initialize with a Settings object <tt>settings</tt>.
|
25
|
+
def initialize settings = {}
|
26
|
+
@settings = DEFAULT_SETTINGS.merge settings
|
27
|
+
super @settings
|
28
|
+
@color = nil
|
29
|
+
@color_temporary = nil
|
30
|
+
@color_original = @settings.get :color
|
31
|
+
@z_index = @settings.get :z_index
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_color color
|
35
|
+
@color = color
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set the color only for the next frame.
|
39
|
+
def set_temporary_color color
|
40
|
+
@color_temporary = color
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_color
|
44
|
+
return @color_temporary || @color || @color_original
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset_color
|
48
|
+
@color = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def draw
|
52
|
+
corner = get_corner :left, :top
|
53
|
+
Gosu.draw_rect(
|
54
|
+
corner.x, corner.y,
|
55
|
+
get_size(:width), get_size(:height),
|
56
|
+
get_color,
|
57
|
+
@z_index
|
58
|
+
)
|
59
|
+
@color_temporary = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module AdventureRL
|
2
|
+
class Settings
|
3
|
+
include Helpers::Error
|
4
|
+
attr_reader :content
|
5
|
+
|
6
|
+
# Initialize Settings with either a string representing
|
7
|
+
# a path to a YAML file, or a hash with your settings.
|
8
|
+
def initialize arg
|
9
|
+
if ([String, Pathname].include? arg.class)
|
10
|
+
@file = Pathname.new arg
|
11
|
+
validate_file_exists @file
|
12
|
+
@content = get_file_content(@file).keys_to_sym
|
13
|
+
elsif (arg.is_a? Hash)
|
14
|
+
@content = arg.keys_to_sym
|
15
|
+
elsif (arg.is_a? Settings)
|
16
|
+
@content = arg.get
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the settings, following the structure of the passed <tt>keys</tt>.
|
21
|
+
# Similar to <tt>Hash#dig</tt>
|
22
|
+
def get *keys
|
23
|
+
current_content = @content
|
24
|
+
keys.each do |key|
|
25
|
+
key = key.to_sym if (key.is_a? String)
|
26
|
+
if (current_content.is_a?(Hash) && !current_content[key].nil?)
|
27
|
+
current_content = current_content[key]
|
28
|
+
else
|
29
|
+
current_content = nil
|
30
|
+
break
|
31
|
+
end
|
32
|
+
end
|
33
|
+
return current_content
|
34
|
+
end
|
35
|
+
|
36
|
+
# Merge self Settings content with other_settings Settings content or Hash.
|
37
|
+
# Can pass unlimited optional arguments as keys.
|
38
|
+
# If <tt>keys</tt> are given, then it will only merge the content
|
39
|
+
# from the keys <tt>keys</tt> for both Settings instances.
|
40
|
+
# Returns a new Settings object where the values of the <tt>keys</tt> keys
|
41
|
+
# are its settings content.
|
42
|
+
def merge other_settings, *keys
|
43
|
+
merged_settings = nil
|
44
|
+
if (other_settings.is_a? Settings)
|
45
|
+
merged_settings = Settings.new get(*keys).merge(other_settings.get(*keys))
|
46
|
+
elsif (other_settings.is_a? Hash)
|
47
|
+
merged_settings = Settings.new get(*keys).merge(other_settings)
|
48
|
+
else
|
49
|
+
error(
|
50
|
+
"Argument needs to be an instance of `AdventureRL::Settings' or a Hash",
|
51
|
+
"but got a `#{other_settings.class.name}'"
|
52
|
+
)
|
53
|
+
end
|
54
|
+
return merged_settings
|
55
|
+
end
|
56
|
+
|
57
|
+
def each
|
58
|
+
return get.each
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def validate_file_exists file = @file
|
64
|
+
error_no_file file unless (file_exists? file)
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_file_content file = @file
|
68
|
+
file = Pathname.new file unless (file.is_a? Pathname)
|
69
|
+
begin
|
70
|
+
return YAML.load_file(file.to_path) || {}
|
71
|
+
rescue
|
72
|
+
begin
|
73
|
+
return JSON.parse(file.read, symbolize_names: true)
|
74
|
+
rescue
|
75
|
+
error "Couldn't load settings file: '#{file.to_path}'", 'Is it a valid YAML or JSON file?'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|