cyberarm_engine 0.13.1 → 0.17.1

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/Gemfile +1 -1
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/assets/textures/default.png +0 -0
  7. data/cyberarm_engine.gemspec +12 -9
  8. data/lib/cyberarm_engine.rb +20 -5
  9. data/lib/cyberarm_engine/animator.rb +6 -4
  10. data/lib/cyberarm_engine/background.rb +19 -15
  11. data/lib/cyberarm_engine/background_nine_slice.rb +125 -0
  12. data/lib/cyberarm_engine/bounding_box.rb +18 -18
  13. data/lib/cyberarm_engine/cache.rb +4 -0
  14. data/lib/cyberarm_engine/cache/download_manager.rb +121 -0
  15. data/lib/cyberarm_engine/common.rb +16 -16
  16. data/lib/cyberarm_engine/config_file.rb +2 -2
  17. data/lib/cyberarm_engine/game_object.rb +63 -72
  18. data/lib/cyberarm_engine/game_state.rb +6 -3
  19. data/lib/cyberarm_engine/model.rb +207 -0
  20. data/lib/cyberarm_engine/model/material.rb +21 -0
  21. data/lib/cyberarm_engine/model/model_object.rb +131 -0
  22. data/lib/cyberarm_engine/model/parser.rb +74 -0
  23. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -0
  24. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -0
  25. data/lib/cyberarm_engine/model_cache.rb +31 -0
  26. data/lib/cyberarm_engine/opengl.rb +28 -0
  27. data/lib/cyberarm_engine/opengl/light.rb +50 -0
  28. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -0
  29. data/lib/cyberarm_engine/opengl/perspective_camera.rb +38 -0
  30. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -0
  31. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +164 -0
  32. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +289 -0
  33. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +22 -0
  34. data/lib/cyberarm_engine/{shader.rb → opengl/shader.rb} +51 -43
  35. data/lib/cyberarm_engine/opengl/texture.rb +69 -0
  36. data/lib/cyberarm_engine/ray.rb +5 -5
  37. data/lib/cyberarm_engine/stats.rb +21 -0
  38. data/lib/cyberarm_engine/text.rb +45 -31
  39. data/lib/cyberarm_engine/timer.rb +1 -1
  40. data/lib/cyberarm_engine/transform.rb +43 -20
  41. data/lib/cyberarm_engine/ui/border_canvas.rb +4 -3
  42. data/lib/cyberarm_engine/ui/dsl.rb +49 -10
  43. data/lib/cyberarm_engine/ui/element.rb +73 -21
  44. data/lib/cyberarm_engine/ui/elements/button.rb +121 -28
  45. data/lib/cyberarm_engine/ui/elements/check_box.rb +25 -33
  46. data/lib/cyberarm_engine/ui/elements/container.rb +113 -33
  47. data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -0
  48. data/lib/cyberarm_engine/ui/elements/edit_line.rb +145 -45
  49. data/lib/cyberarm_engine/ui/elements/flow.rb +1 -3
  50. data/lib/cyberarm_engine/ui/elements/image.rb +32 -12
  51. data/lib/cyberarm_engine/ui/elements/label.rb +122 -16
  52. data/lib/cyberarm_engine/ui/elements/list_box.rb +82 -0
  53. data/lib/cyberarm_engine/ui/elements/progress.rb +6 -5
  54. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -0
  55. data/lib/cyberarm_engine/ui/elements/slider.rb +104 -0
  56. data/lib/cyberarm_engine/ui/elements/stack.rb +1 -3
  57. data/lib/cyberarm_engine/ui/elements/text_block.rb +156 -0
  58. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +40 -31
  59. data/lib/cyberarm_engine/ui/event.rb +7 -7
  60. data/lib/cyberarm_engine/ui/gui_state.rb +118 -6
  61. data/lib/cyberarm_engine/ui/style.rb +10 -9
  62. data/lib/cyberarm_engine/ui/theme.rb +84 -22
  63. data/lib/cyberarm_engine/vector.rb +33 -30
  64. data/lib/cyberarm_engine/version.rb +2 -2
  65. data/lib/cyberarm_engine/{engine.rb → window.rb} +32 -19
  66. metadata +87 -18
  67. data/lib/cyberarm_engine/gosu_ext/circle.rb +0 -9
@@ -2,28 +2,41 @@ module CyberarmEngine
2
2
  class Element
3
3
  class EditLine < Button
4
4
  def initialize(text, options = {}, block = nil)
5
+ @filter = options.delete(:filter)
5
6
  super(text, options, block)
6
7
 
7
8
  @type = default(:type)
8
9
 
9
10
  @caret_width = default(:caret_width)
10
- @caret_height= @text.height
11
+ @caret_height = @text.textobject.height
11
12
  @caret_color = default(:caret_color)
12
13
  @caret_interval = default(:caret_interval)
13
14
  @caret_last_interval = Gosu.milliseconds
14
- @show_caret = true
15
+ @show_caret = true
15
16
 
16
17
  @text_input = Gosu::TextInput.new
17
18
  @text_input.text = text
19
+ @last_text_value = text
20
+
21
+ if @filter && @filter.respond_to?(:call)
22
+ @text_input.instance_variable_set(:@filter, @filter)
23
+
24
+ def @text_input.filter(text_in)
25
+ @filter.call(text_in)
26
+ end
27
+ end
18
28
 
19
29
  @offset_x = 0
30
+ @offset_y = 0
20
31
 
21
- return self
32
+ event(:begin_drag)
33
+ event(:drag_update)
34
+ event(:end_drag)
22
35
  end
23
36
 
24
37
  def render
25
- Gosu.clip_to(@text.x, @text.y, @style.width, @text.height) do
26
- Gosu.translate(-@offset_x, 0) do
38
+ Gosu.clip_to(@text.x, @text.y, @width, @height) do
39
+ Gosu.translate(-@offset_x, -@offset_y) do
27
40
  draw_selection
28
41
  draw_caret if @focus && @show_caret
29
42
  draw_text
@@ -31,6 +44,10 @@ module CyberarmEngine
31
44
  end
32
45
  end
33
46
 
47
+ def draw_text
48
+ @text.draw(:draw_text)
49
+ end
50
+
34
51
  def draw_caret
35
52
  Gosu.draw_rect(caret_position, @text.y, @caret_width, @caret_height, @caret_color, @z)
36
53
  end
@@ -42,10 +59,18 @@ module CyberarmEngine
42
59
  end
43
60
 
44
61
  def update
45
- if @type == :password
46
- @text.text = default(:password_character) * @text_input.text.length
47
- else
48
- @text.text = @text_input.text
62
+ @text.text = if @type == :password
63
+ default(:password_character) * @text_input.text.length
64
+ else
65
+ @text_input.text
66
+ end
67
+
68
+ if @last_text_value != value
69
+ @last_text_value = value
70
+ @show_caret = true
71
+ @caret_last_interval = Gosu.milliseconds
72
+
73
+ publish(:changed, value)
49
74
  end
50
75
 
51
76
  if Gosu.milliseconds >= @caret_last_interval + @caret_interval
@@ -57,15 +82,60 @@ module CyberarmEngine
57
82
  keep_caret_visible
58
83
  end
59
84
 
60
- def move_caret_to_mouse(mouse_x)
61
- 1.upto(@text.text.length) do |i|
62
- if mouse_x < @text.x + @text.textobject.text_width(@text.text[0...i])
63
- @text_input.caret_pos = @text_input.selection_start = i - 1;
64
- return
85
+ def button_down(id)
86
+ handle_keyboard_shortcuts(id)
87
+ end
88
+
89
+ def handle_keyboard_shortcuts(id)
90
+ return unless @focus && @enabled
91
+
92
+ if Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
93
+ case id
94
+ when Gosu::KB_A
95
+ @text_input.selection_start = 0
96
+ @text_input.caret_pos = @text_input.text.length
97
+
98
+ when Gosu::KB_C
99
+ if @text_input.selection_start < @text_input.caret_pos
100
+ Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
101
+ else
102
+ Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
103
+ end
104
+
105
+ when Gosu::KB_X
106
+ chars = @text_input.text.chars
107
+
108
+ if @text_input.selection_start < @text_input.caret_pos
109
+ Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
110
+ chars.slice!(@text_input.selection_start, @text_input.caret_pos)
111
+ else
112
+ Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
113
+ chars.slice!(@text_input.caret_pos, @text_input.selection_start)
114
+ end
115
+
116
+ @text_input.text = chars.join
117
+
118
+ when Gosu::KB_V
119
+ if instance_of?(EditLine) # EditLine assumes a single line of text
120
+ @text_input.text = @text_input.text.insert(@text_input.caret_pos,
121
+ Clipboard.paste.encode("UTF-8").gsub("\n", ""))
122
+ else
123
+ @text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8"))
124
+ end
65
125
  end
66
126
  end
127
+ end
67
128
 
68
- @text_input.caret_pos = @text_input.selection_start = @text_input.text.length
129
+ def caret_position_under_mouse(mouse_x)
130
+ 1.upto(@text.text.length) do |i|
131
+ return i - 1 if mouse_x < @text.x - @offset_x + @text.width(@text.text[0...i])
132
+ end
133
+
134
+ @text_input.text.length
135
+ end
136
+
137
+ def move_caret_to_mouse(mouse_x, _mouse_y)
138
+ @text_input.caret_pos = @text_input.selection_start = caret_position_under_mouse(mouse_x)
69
139
  end
70
140
 
71
141
  def keep_caret_visible
@@ -74,25 +144,21 @@ module CyberarmEngine
74
144
  @last_text ||= "/\\"
75
145
  @last_pos ||= -1
76
146
 
77
- puts "caret pos: #{caret_pos}, width: #{@width}, offset: #{@offset_x}" if (@last_text != @text.text) || (@last_pos != caret_pos)
78
-
79
147
  @last_text = @text.text
80
148
  @last_pos = caret_pos
81
149
 
82
-
83
150
  if caret_pos.between?(@offset_x, @width + @offset_x)
84
151
  # Do nothing
85
152
 
86
153
  elsif caret_pos < @offset_x
87
- if caret_pos > @width
88
- @offset_x = caret_pos + @width
89
- else
90
- @offset_x = 0
91
- end
154
+ @offset_x = if caret_pos > @width
155
+ caret_pos + @width
156
+ else
157
+ 0
158
+ end
92
159
 
93
160
  elsif caret_pos > @width
94
161
  @offset_x = caret_pos - @width
95
- puts "triggered"
96
162
 
97
163
  else
98
164
  # Reset to Zero
@@ -100,6 +166,22 @@ module CyberarmEngine
100
166
  end
101
167
  end
102
168
 
169
+ def caret_position
170
+ text_input_position_for(:caret_pos)
171
+ end
172
+
173
+ def selection_start_position
174
+ text_input_position_for(:selection_start)
175
+ end
176
+
177
+ def text_input_position_for(method)
178
+ if @type == :password
179
+ @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length)
180
+ else
181
+ @text.x + @text.width(@text_input.text[0...@text_input.send(method)])
182
+ end
183
+ end
184
+
103
185
  def left_mouse_button(sender, x, y)
104
186
  super
105
187
  window.text_input = @text_input
@@ -107,12 +189,12 @@ module CyberarmEngine
107
189
  @caret_last_interval = Gosu.milliseconds
108
190
  @show_caret = true
109
191
 
110
- move_caret_to_mouse(x)
192
+ move_caret_to_mouse(x, y)
111
193
 
112
- return :handled
194
+ :handled
113
195
  end
114
196
 
115
- def enter(sender)
197
+ def enter(_sender)
116
198
  if @focus
117
199
  @style.background_canvas.background = default(:active, :background)
118
200
  @text.color = default(:active, :color)
@@ -121,40 +203,54 @@ module CyberarmEngine
121
203
  @text.color = default(:hover, :color)
122
204
  end
123
205
 
124
- return :handled
206
+ :handled
125
207
  end
126
208
 
127
209
  def leave(sender)
128
- unless @focus
129
- super
130
- end
210
+ super unless @focus
211
+
212
+ :handled
213
+ end
214
+
215
+ def focus(sender)
216
+ @focus = true
217
+ @style.background_canvas.background = default(:active, :background)
218
+ @text.color = default(:active, :color)
219
+ window.text_input = @text_input
220
+ @text_input.caret_pos = @text_input.selection_start = @text_input.text.length
131
221
 
132
- return :handled
222
+ :handled
133
223
  end
134
224
 
135
- def blur(sender)
225
+ def blur(_sender)
136
226
  @focus = false
137
227
  @style.background_canvas.background = default(:background)
138
228
  @text.color = default(:color)
139
229
  window.text_input = nil
140
230
 
141
- return :handled
231
+ :handled
142
232
  end
143
233
 
144
- def caret_position
145
- text_input_position_for(:caret_pos)
234
+ def draggable?(button)
235
+ button == :left
146
236
  end
147
237
 
148
- def selection_start_position
149
- text_input_position_for(:selection_start)
238
+ def begin_drag(_sender, x, _y, _button)
239
+ @drag_start = x
240
+ @offset_drag_start = @offset_x
241
+ @drag_caret_position = @text_input.caret_pos
242
+
243
+ :handled
150
244
  end
151
245
 
152
- def text_input_position_for(method)
153
- if @type == :password
154
- @text.x + @text.textobject.text_width(default(:password_character) * @text_input.text[0..@text_input.send(method)].length)
155
- else
156
- @text.x + @text.textobject.text_width(@text_input.text[0..@text_input.send(method)])
157
- end
246
+ def drag_update(_sender, x, _y, _button)
247
+ @text_input.caret_pos = caret_position_under_mouse(x)
248
+
249
+ :handled
250
+ end
251
+
252
+ def end_drag(_sender, _x, _y, _button)
253
+ :handled
158
254
  end
159
255
 
160
256
  def recalculate
@@ -167,6 +263,10 @@ module CyberarmEngine
167
263
  def value
168
264
  @text_input.text
169
265
  end
266
+
267
+ def value=(string)
268
+ @text_input.text = string
269
+ end
170
270
  end
171
271
  end
172
- end
272
+ end
@@ -1,8 +1,6 @@
1
1
  module CyberarmEngine
2
2
  class Element
3
3
  class Flow < Container
4
- include Common
5
-
6
4
  def layout
7
5
  @children.each do |child|
8
6
  if fits_on_line?(child)
@@ -14,4 +12,4 @@ module CyberarmEngine
14
12
  end
15
13
  end
16
14
  end
17
- end
15
+ end
@@ -1,12 +1,15 @@
1
1
  module CyberarmEngine
2
2
  class Element
3
3
  class Image < Element
4
- def initialize(path, options = {}, block = nil)
4
+ def initialize(path_or_image, options = {}, block = nil)
5
5
  super(options, block)
6
- @path = path
6
+ @path = path_or_image if path_or_image.is_a?(String)
7
7
 
8
- @image = Gosu::Image.new(path, retro: @options[:image_retro])
9
- @scale_x, @scale_y = 1, 1
8
+ @image = Gosu::Image.new(path_or_image, retro: @options[:retro], tileable: @options[:tileable]) if @path
9
+ @image = path_or_image unless @path
10
+
11
+ @scale_x = 1
12
+ @scale_y = 1
10
13
  end
11
14
 
12
15
  def render
@@ -14,18 +17,19 @@ module CyberarmEngine
14
17
  @style.border_thickness_left + @style.padding_left + @x,
15
18
  @style.border_thickness_top + @style.padding_top + @y,
16
19
  @z + 2,
17
- @scale_x, @scale_y) # TODO: Add color support?
20
+ @scale_x, @scale_y, @style.color
21
+ )
18
22
  end
19
23
 
20
- def clicked_left_mouse_button(sender, x, y)
24
+ def clicked_left_mouse_button(_sender, _x, _y)
21
25
  @block.call(self) if @block
22
26
 
23
- return :handled
27
+ :handled
24
28
  end
25
29
 
26
30
  def recalculate
27
31
  _width = dimensional_size(@style.width, :width)
28
- _height= dimensional_size(@style.height,:height)
32
+ _height = dimensional_size(@style.height, :height)
29
33
 
30
34
  if _width && _height
31
35
  @scale_x = _width.to_f / @image.width
@@ -37,16 +41,32 @@ module CyberarmEngine
37
41
  @scale_y = _height.to_f / @image.height
38
42
  @scale_x = @scale_y
39
43
  else
40
- @scale_x, @scale_y = 1, 1
44
+ @scale_x = 1
45
+ @scale_y = 1
41
46
  end
42
47
 
43
- @width = _width ? _width : @image.width.round * @scale_x
44
- @height= _height ? _height : @image.height.round * @scale_y
48
+ @width = _width || @image.width.round * @scale_x
49
+ @height = _height || @image.height.round * @scale_y
50
+
51
+ update_background
45
52
  end
46
53
 
47
54
  def value
55
+ @image
56
+ end
57
+
58
+ def value=(path_or_image, retro: false, tileable: false)
59
+ @path = path_or_image if path_or_image.is_a?(String)
60
+
61
+ @image = Gosu::Image.new(path_or_image, retro: retro, tileable: tileable) if @path
62
+ @image = path_or_image unless @path
63
+
64
+ recalculate
65
+ end
66
+
67
+ def path
48
68
  @path
49
69
  end
50
70
  end
51
71
  end
52
- end
72
+ end
@@ -1,50 +1,156 @@
1
1
  module CyberarmEngine
2
2
  class Element
3
- class Label < Element
3
+ class TextBlock < Element
4
4
  def initialize(text, options = {}, block = nil)
5
5
  super(options, block)
6
6
 
7
- @text = Text.new(text, font: @options[:font], z: @z, color: @options[:color], size: @options[:text_size], shadow: @options[:text_shadow])
7
+ @text = Text.new(
8
+ text, font: @options[:font], z: @z, color: @options[:color],
9
+ size: @options[:text_size], shadow: @options[:text_shadow],
10
+ shadow_size: @options[:text_shadow_size],
11
+ shadow_color: @options[:text_shadow_color]
12
+ )
13
+
14
+ @raw_text = text
8
15
  end
9
16
 
10
17
  def render
11
18
  @text.draw
12
19
  end
13
20
 
14
- def clicked_left_mouse_button(sender, x, y)
15
- @block.call(self) if @block
21
+ def clicked_left_mouse_button(_sender, _x, _y)
22
+ @block&.call(self) if @enabled
16
23
 
17
- return :handled
24
+ # return :handled
18
25
  end
19
26
 
20
27
  def recalculate
21
- @width, @height = 0, 0
28
+ @width = 0
29
+ @height = 0
30
+
31
+ _width = dimensional_size(@style.width, :width)
32
+ _height = dimensional_size(@style.height, :height)
22
33
 
23
- _width = dimensional_size(@style.width, :width)
24
- _height= dimensional_size(@style.height,:height)
34
+ handle_text_wrapping(_width)
25
35
 
26
- @width = _width ? _width : @text.width.round
27
- @height= _height ? _height : @text.height.round
36
+ @width = _width || @text.width.round
37
+ @height = _height || @text.height.round
28
38
 
29
- @text.x = @style.border_thickness_left + @style.padding_left + @x
30
- @text.y = @style.border_thickness_top + @style.padding_top + @y
39
+ @text.y = @style.border_thickness_top + @style.padding_top + @y
31
40
  @text.z = @z + 3
32
41
 
42
+ if (text_alignment = @options[:text_align])
43
+ case text_alignment
44
+ when :left
45
+ @text.x = @style.border_thickness_left + @style.padding_left + @x
46
+ when :center
47
+ @text.x = if @text.width <= outer_width
48
+ @x + outer_width / 2 - @text.width / 2
49
+ else # Act as left aligned
50
+ @style.border_thickness_left + @style.padding_left + @x
51
+ end
52
+ when :right
53
+ @text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
54
+ end
55
+ end
56
+
33
57
  update_background
34
58
  end
35
59
 
60
+ def handle_text_wrapping(max_width)
61
+ max_width ||= @parent&.width
62
+ max_width ||= @x - (window.width + noncontent_width)
63
+ wrap_behavior = style.text_wrap
64
+ copy = @raw_text.to_s.dup
65
+
66
+ if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none
67
+ breaks = []
68
+ line_start = 0
69
+ line_end = copy.length
70
+
71
+ while line_start != copy.length
72
+ if line_width(copy[line_start...line_end]) > max_width
73
+ line_end = ((line_end - line_start) / 2.0)
74
+ elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
75
+ # To small, grow!
76
+ # TODO: find a more efficient way
77
+ line_end += 1
78
+
79
+ else # FOUND IT!
80
+ entering_line_end = line_end.floor
81
+ max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63
82
+ reach = 0
83
+
84
+ if wrap_behavior == :word_wrap
85
+ max_reach.times do |i|
86
+ reach = i
87
+ break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
88
+ end
89
+
90
+ puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
91
+ line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
92
+ end
93
+
94
+ breaks << line_end.floor
95
+ line_start = line_end.floor
96
+ line_end = copy.length
97
+
98
+ break if entering_line_end == copy.length || reach == max_reach
99
+ end
100
+ end
101
+
102
+ breaks.each_with_index do |pos, index|
103
+ copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
104
+ end
105
+ end
106
+
107
+ @text.text = copy
108
+ end
109
+
110
+ def line_width(text)
111
+ (@x + @text.textobject.markup_width(text) + noncontent_width)
112
+ end
113
+
36
114
  def value
37
- @text.text
115
+ @raw_text
38
116
  end
39
117
 
40
118
  def value=(value)
41
- @text.text = value
119
+ @raw_text = value.to_s.chomp
42
120
 
43
- old_width, old_height = width, height
121
+ old_width = width
122
+ old_height = height
44
123
  recalculate
45
124
 
46
125
  root.gui_state.request_recalculate if old_width != width || old_height != height
126
+
127
+ publish(:changed, self.value)
47
128
  end
48
129
  end
130
+
131
+ class Banner < TextBlock
132
+ end
133
+
134
+ class Title < TextBlock
135
+ end
136
+
137
+ class Subtitle < TextBlock
138
+ end
139
+
140
+ class Tagline < TextBlock
141
+ end
142
+
143
+ class Caption < TextBlock
144
+ end
145
+
146
+ class Para < TextBlock
147
+ end
148
+
149
+ class Inscription < TextBlock
150
+ end
151
+
152
+ # TODO: Remove in version 0.16.0+
153
+ class Label < TextBlock
154
+ end
49
155
  end
50
- end
156
+ end