rrb-common 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 339ea3353a55193acc1cbe6fcb8dc51f9731f72e9ffffeb864c10f4167c1cd84
4
- data.tar.gz: eff3b5a43e2efaed07990b15be1dfb42547bfbdc16cd980f6589f193cc91b258
3
+ metadata.gz: 6c0ce773dfdfb39eebd3eca206e2f583acc855bbbd4a00677d7bc0e4e7f3d228
4
+ data.tar.gz: e7054853f2f0f99977ecb8a64fa18cfcd6159b07af9cf4815ad08988275d6353
5
5
  SHA512:
6
- metadata.gz: 7e2a4ccef86a0fbea08fc8c2ab5a143e350225df42d6d1a8ca56e1d97723429c8294fc2c1802c5dbde2b683e7c5f09e88989f829e0ef73a8c23b03e6d0a2083d
7
- data.tar.gz: 58aca74385f3e52b34d9252954481ddc388bcadd47b2203a58ca46033db0f851efba59fc6fb11193f3e9ad7726d6cbe2155b0f328e31c7b1f105d41374566725
6
+ metadata.gz: 9a8566a6cccb99f6c084c2fb012e38b8d47893173de6305d4c48dd01cf07bbad3404e1e969512728c4358c41cf73fdb8d8e887d20d636e5f2a0a8cebb2546119
7
+ data.tar.gz: '024422087af5f9646086bda2549af7cbac08facf82eb3cb76df32665136fc0dabcdb8bcf5d983e89dbe374811dcbbbb817a7f9d51fe20f040127e8ab2958f46c'
@@ -0,0 +1,41 @@
1
+ Dir[File.dirname(__FILE__)].each { |file| $LOAD_PATH.unshift(file) if File.directory? file }
2
+
3
+ require 'console'
4
+ require 'dry/configurable'
5
+ require 'dry/container'
6
+ require 'securerandom'
7
+ require 'singleton'
8
+
9
+ # A game server written in Ruby targeting the 2006 era (or the 317-377 protocols) of the popular MMORPG, RuneScape.
10
+ #
11
+ # @title RuneRb
12
+ # @author Patrick W.
13
+ # @since 0.1.0
14
+ module RuneRb
15
+
16
+ # The Core module contains the core classes and modules of the Rune.rb framework.
17
+ module Core
18
+ autoload :Identifiable, 'rune/core/identifiable'
19
+ autoload :Logging, 'rune/core/logging'
20
+ autoload :Unit, 'rune/core/unit'
21
+ autoload :Buffer, 'rune/core/buffer'
22
+ autoload :Constants, 'rune/core/constants'
23
+ autoload :ISAAC, 'rune/core/isaac'
24
+ autoload :ReadableBuffer, 'rune/core/readable_buffer'
25
+ autoload :WriteableBuffer, 'rune/core/writeable_buffer'
26
+
27
+ include Constants
28
+ end
29
+
30
+ # The Patches module contains all common patches to core classes.
31
+ module Patches
32
+ autoload :IntegerRefinements, 'rune/patches/integer_refinements'
33
+ autoload :SetRefinements, 'rune/patches/set_refinements'
34
+ end
35
+
36
+ # The Logger utility for the RuneRb framework.
37
+ # @return [Console::Logger] the logger instance.
38
+ def self.logger
39
+ Console.logger
40
+ end
41
+ end
@@ -1,10 +1,9 @@
1
- module RuneRb::Network
1
+ module RuneRb::Core
2
2
 
3
3
  # A Buffer encapsulates raw data in a String instance. Depending on the mode, the buffer can be read from or written to.
4
4
  class Buffer
5
5
 
6
6
  # Constructs a new Buffer instance.
7
- # @param capacity [Integer] the capacity of the buffer.
8
7
  # @param mode [String] the mode of the buffer.
9
8
  def initialize(mode: 'rw')
10
9
  @data = String.new
@@ -21,6 +20,12 @@ module RuneRb::Network
21
20
  @data.dup
22
21
  end
23
22
 
23
+ # Is the {Buffer#data} empty?
24
+ # @return [Boolean] true if the {Buffer#data} is empty, false otherwise.
25
+ def empty?
26
+ @data.empty?
27
+ end
28
+
24
29
  # Returns the limit of the buffer.
25
30
  # @return [Integer] the limit of the buffer.
26
31
  def length
@@ -62,14 +67,14 @@ module RuneRb::Network
62
67
 
63
68
  # Enables read functions on the buffer instance. Bit Access is disabled by default. Sets the bit position to 0.
64
69
  def enable_readable
65
- singleton_class.include(RuneRb::Network::Readable)
70
+ singleton_class.include(RuneRb::Core::ReadableBuffer)
66
71
  @bit_access = false
67
72
  @bit_position = 0
68
73
  end
69
74
 
70
75
  # Enables write functions on the buffer instance.
71
76
  def enable_writeable
72
- singleton_class.include(RuneRb::Network::Writeable)
77
+ singleton_class.include(RuneRb::Core::WriteableBuffer)
73
78
  end
74
79
  end
75
80
  end
@@ -1,4 +1,4 @@
1
- module RuneRb::Network::Constants
1
+ module RuneRb::Core::Constants
2
2
  # Acceptable byte orders in which multi-byte values can be read.
3
3
  # @return [Array<Symbol>]
4
4
  BYTE_ORDERS = %i[BIG MIDDLE INVERSE_MIDDLE LITTLE].freeze
@@ -0,0 +1,47 @@
1
+ module RuneRb::Core::Identifiable
2
+ # @!attribute [r] id
3
+ # @return [String, Integer, Symbol] - The identifier of the object.
4
+ attr_reader :identifier
5
+
6
+ # The identifier of the object.
7
+ # @return [String, Integer, Symbol] - The identifier of the object.
8
+ def identifier
9
+ @identifier ||= SecureRandom.uuid
10
+ @identifier
11
+ end
12
+
13
+ alias id identifier
14
+ alias signature identifier
15
+ end
16
+
17
+ ############################################################################################################
18
+ # Copyright (c) 2023, Patrick W. #
19
+ # All rights reserved. #
20
+ # #
21
+ # Redistribution and use in source and binary forms, with or without #
22
+ # modification, are permitted provided that the following conditions are met: #
23
+ # #
24
+ # * Redistributions of source code must retain the above copyright notice, this #
25
+ # list of conditions and the following disclaimer. #
26
+ # #
27
+ # * Redistributions in binary form must reproduce the above copyright notice, #
28
+ # this list of conditions and the following disclaimer in the documentation #
29
+ # and/or other materials provided with the distribution. #
30
+ # #
31
+ # * Neither the name of the copyright holder nor the names of its #
32
+ # contributors may be used to endorse or promote products derived from #
33
+ # this software without specific prior written permission. #
34
+ # #
35
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" #
36
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #
37
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE #
38
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE #
39
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #
40
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR #
41
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER #
42
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, #
43
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #
44
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #
45
+ # #
46
+ ############################################################################################################
47
+
@@ -1,4 +1,4 @@
1
- module RuneRb::Network
1
+ module RuneRb::Core
2
2
  # An implementation of an ISAAC cipher used to generate random numbers for message interchange.
3
3
  class ISAAC
4
4
  using RuneRb::Patches::IntegerRefinements
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A module responsible for setting up logging functions.
4
+ module RuneRb::Core::Logging
5
+
6
+ # Write information to the log.
7
+ # @param lines [Array, String] the lines to write to the log.
8
+ def log(*lines)
9
+ RuneRb.logger.info(self.class.name) { lines.join("\n") }
10
+ end
11
+
12
+ alias info log
13
+
14
+ # Write a warning to the log.
15
+ # @param lines [Array, String] the lines to write to the log.
16
+ def log!(*lines)
17
+ RuneRb.logger.warn(self.class.name) { lines.join("\n") }
18
+ end
19
+
20
+ alias warn log!
21
+
22
+ # Write an error to the log.
23
+ # @param lines [Array, String] the lines to write to the log.
24
+ def err(*lines)
25
+ RuneRb.logger.error(self.class.name) { lines.join("\n") }
26
+ end
27
+
28
+ alias error err
29
+
30
+ # Write a fatal error to the log.
31
+ # @param lines [Array, String] the lines to write to the log.
32
+ def err!(*lines)
33
+ RuneRb.logger.fatal(self.class.name) { lines.join("\n") }
34
+ end
35
+
36
+ alias fatal err!
37
+ end
38
+
39
+ ############################################################################################################
40
+ # Copyright (c) 2023, Patrick W. #
41
+ # All rights reserved. #
42
+ # #
43
+ # Redistribution and use in source and binary forms, with or without #
44
+ # modification, are permitted provided that the following conditions are met: #
45
+ # #
46
+ # * Redistributions of source code must retain the above copyright notice, this #
47
+ # list of conditions and the following disclaimer. #
48
+ # #
49
+ # * Redistributions in binary form must reproduce the above copyright notice, #
50
+ # this list of conditions and the following disclaimer in the documentation #
51
+ # and/or other materials provided with the distribution. #
52
+ # #
53
+ # * Neither the name of the copyright holder nor the names of its #
54
+ # contributors may be used to endorse or promote products derived from #
55
+ # this software without specific prior written permission. #
56
+ # #
57
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" #
58
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #
59
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE #
60
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE #
61
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #
62
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR #
63
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER #
64
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, #
65
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #
66
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #
67
+ # #
68
+ ############################################################################################################
69
+
@@ -1,5 +1,5 @@
1
1
  # The Readable module contains the functions used to read data from a {Buffer} object.
2
- module RuneRb::Network::Readable
2
+ module RuneRb::Core::ReadableBuffer
3
3
  using RuneRb::Patches::IntegerRefinements
4
4
 
5
5
  # @!attribute [r] bit_access
@@ -30,12 +30,12 @@ module RuneRb::Network::Readable
30
30
  end
31
31
  end
32
32
 
33
- # Enables or Disables bit reading by setting the {Buffer#bit_access} variable.
33
+ # Enables or Disables bit reading by setting the {Readable#bit_access} variable.
34
34
  def switch_access
35
35
  @bit_access = !@bit_access
36
36
  end
37
37
 
38
- # Completes a bit access by setting the {Buffer#bit_position} to the start of the next byte, then toggling {bit_access}.
38
+ # Completes a bit access by setting the {Readable#bit_position} to the start of the next byte, then toggling {Readable#bit_access}.
39
39
  def finish_access
40
40
  @bit_position = (@bit_position + 7) / 8
41
41
  switch_access
@@ -0,0 +1,28 @@
1
+ module RuneRb::Core
2
+ # A Unit is a {Dry::Container} that encapsulates specific logic for a given system. Units are used to construct and run certain parts of the game. All {Unit} instances should include the {Singletons}.
3
+ # @abstract
4
+ class Unit
5
+ extend Dry::Configurable
6
+ include Dry::Container::Mixin
7
+ include RuneRb::Core::Logging
8
+ include Singleton
9
+
10
+ # Constructs a new instance of {Unit} and sets the name to the class name.
11
+ def initialize
12
+ @name = self.class.name.split('::')[-2]
13
+ super
14
+ end
15
+
16
+ # Prepares the unit for use. Providers are loaded and imported based on the configuration.
17
+ def prepare
18
+ info "Preparing #{@name} unit."
19
+
20
+ # Determine directory based on the class of the current instance
21
+ class_directory = File.dirname(self.class.instance_method(:prepare).source_location.first)
22
+ providers_directory = File.join(class_directory, 'providers', '*.rb')
23
+
24
+ # Load providers
25
+ Dir[providers_directory].sort.each { |provider| require provider }
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  # The Writeable module contains functions for writing data to a {Buffer} object.
2
- module RuneRb::Network::Writeable
2
+ module RuneRb::Core::WriteableBuffer
3
3
 
4
4
  # Write data to the payload.
5
5
  # @param value [Integer, String, Message, Array] the value to write.
@@ -107,11 +107,11 @@ module RuneRb::Network::Writeable
107
107
  def write_long(value, mutation: :STD, signed: false, order: :BIG)
108
108
  case order
109
109
  when :BIG
110
- (RuneRb::Network::BYTE_SIZE * 7).downto(0) { |div| ((div % 8).zero? and div.positive?) ? write_byte(value >> div) : next }
110
+ (RuneRb::Core::BYTE_SIZE * 7).downto(0) { |div| ((div % 8).zero? and div.positive?) ? write_byte(value >> div) : next }
111
111
  write_byte(value, mutation: mutation, signed: signed)
112
112
  when :LITTLE
113
113
  write_byte(value, mutation: mutation, signed: signed)
114
- (0).upto(RuneRb::Network::BYTE_SIZE * 7) { |div| ((div % 8).zero? and div.positive?) ? write_byte(value >> div) : next }
114
+ (0).upto(RuneRb::Core::BYTE_SIZE * 7) { |div| ((div % 8).zero? and div.positive?) ? write_byte(value >> div) : next }
115
115
  else raise "Unrecognized byte order: #{order}"
116
116
  end
117
117
  end
@@ -141,8 +141,7 @@ module RuneRb::Network::Writeable
141
141
  def write_bytes(values)
142
142
  case values
143
143
  when Array then values.each { |byte| write_byte(byte.to_i) }
144
- when RuneRb::Network::Packet then send(:put, values.snapshot)
145
- when RuneRb::Network::Buffer then send(:put, values)
144
+ when RuneRb::Core::Buffer then send(:put, values)
146
145
  when String then send(:<<, values)
147
146
  end
148
147
  end
@@ -173,8 +172,8 @@ module RuneRb::Network::Writeable
173
172
 
174
173
  while amount > bit_offset
175
174
  @buffer[byte_pos] = [0].pack('c') if @buffer[byte_pos].nil?
176
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~RuneRb::Network::BIT_MASK_OUT[bit_offset])].pack('c')
177
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | (value >> (amount - bit_offset)) & RuneRb::Network::BIT_MASK_OUT[bit_offset])].pack('c')
175
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~RuneRb::Core::BIT_MASK_OUT[bit_offset])].pack('c')
176
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | (value >> (amount - bit_offset)) & RuneRb::Core::BIT_MASK_OUT[bit_offset])].pack('c')
178
177
  byte_pos += 1
179
178
  amount -= bit_offset
180
179
  bit_offset = 8
@@ -183,11 +182,11 @@ module RuneRb::Network::Writeable
183
182
  @buffer[byte_pos] = [0].pack('c') if @buffer[byte_pos].nil?
184
183
 
185
184
  if amount == bit_offset
186
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~RuneRb::Network::BIT_MASK_OUT[bit_offset])].pack('c')
187
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | (value & Rune::Network::BIT_MASK_OUT[bit_offset]))].pack('c')
185
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~RuneRb::Core::BIT_MASK_OUT[bit_offset])].pack('c')
186
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | (value & RuneRb::Core::BIT_MASK_OUT[bit_offset]))].pack('c')
188
187
  else
189
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~(RuneRb::Network::BIT_MASK_OUT[amount] << (bit_offset - amount)))].pack('c')
190
- @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | ((value & RuneRb::Network::BIT_MASK_OUT[amount]) << (bit_offset - amount)))].pack('c')
188
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') & ~(RuneRb::Core::BIT_MASK_OUT[amount] << (bit_offset - amount)))].pack('c')
189
+ @buffer[byte_pos] = [(@buffer[byte_pos].unpack1('c') | ((value & RuneRb::Core::BIT_MASK_OUT[amount]) << (bit_offset - amount)))].pack('c')
191
190
  end
192
191
  end
193
192
  end
metadata CHANGED
@@ -1,43 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rrb-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick W.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-10 00:00:00.000000000 Z
11
+ date: 2023-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: nio4r
14
+ name: dotenv
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.5.9
19
+ version: '2.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.5.9
26
+ version: '2.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: dotenv
28
+ name: dry-configurable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.8'
33
+ version: '0.16'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.8'
40
+ version: '0.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-container
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: console
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: singleton
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.2'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: benchmark
43
85
  requirement: !ruby/object:Gem::Requirement
@@ -114,12 +156,15 @@ executables: []
114
156
  extensions: []
115
157
  extra_rdoc_files: []
116
158
  files:
117
- - lib/rune.rb
118
- - lib/rune/network/buffer.rb
119
- - lib/rune/network/constants.rb
120
- - lib/rune/network/isaac.rb
121
- - lib/rune/network/readable.rb
122
- - lib/rune/network/writeable.rb
159
+ - lib/rune/common.rb
160
+ - lib/rune/core/buffer.rb
161
+ - lib/rune/core/constants.rb
162
+ - lib/rune/core/identifiable.rb
163
+ - lib/rune/core/isaac.rb
164
+ - lib/rune/core/logging.rb
165
+ - lib/rune/core/readable_buffer.rb
166
+ - lib/rune/core/unit.rb
167
+ - lib/rune/core/writeable_buffer.rb
123
168
  - lib/rune/patches/integer_refinements.rb
124
169
  - lib/rune/patches/set_refinements.rb
125
170
  homepage:
data/lib/rune.rb DELETED
@@ -1,29 +0,0 @@
1
- Dir[File.dirname(__FILE__)].each { |file| $LOAD_PATH.unshift(file) if File.directory? file }
2
-
3
- require 'nio/bytebuffer'
4
-
5
- # <h1>RuneRb</h1>
6
- # <p>A game server written in Ruby targeting the 2006 era (or the 317-377 protocols) of the popular MMORPG, RuneScape.</p>
7
- #
8
- #
9
- # @author Patrick W.
10
- # @since 0.0.1
11
- module RuneRb
12
-
13
- # The Network module contains all common network related classes and modules.
14
- module Network
15
- autoload :Buffer, 'rune/network/buffer'
16
- autoload :Constants, 'rune/network/constants'
17
- autoload :ISAAC, 'rune/network/isaac'
18
- autoload :Readable, 'rune/network/readable'
19
- autoload :Writeable, 'rune/network/writeable'
20
-
21
- include Constants
22
- end
23
-
24
- # The Patches module contains all common patches to core classes.
25
- module Patches
26
- autoload :IntegerRefinements, 'rune/patches/integer_refinements'
27
- autoload :SetRefinements, 'rune/patches/set_refinements'
28
- end
29
- end