chingu 0.9rc7 → 0.9rc8
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.
- data/examples/example15_trait_timer2.rb +1 -1
- data/examples/example7_gfx_helpers.rb +23 -11
- data/examples/game1.rb +299 -15
- data/examples/high_score_list.yml +22 -22
- data/examples/media/city3.png +0 -0
- data/examples/media/enemy_plane.png +0 -0
- data/lib/chingu.rb +1 -1
- data/lib/chingu/animation.rb +58 -38
- data/lib/chingu/game_state_manager.rb +11 -41
- data/lib/chingu/game_states/edit.rb +5 -2
- data/lib/chingu/game_states/enter_name.rb +3 -3
- data/lib/chingu/game_states/network_client.rb +88 -67
- data/lib/chingu/game_states/network_server.rb +66 -103
- data/lib/chingu/game_states/network_state.rb +71 -0
- data/lib/chingu/gosu_ext/sample.rb +79 -0
- data/lib/chingu/gosu_ext/song.rb +96 -0
- data/lib/chingu/helpers/game_object.rb +5 -1
- data/lib/chingu/helpers/game_state.rb +2 -0
- data/lib/chingu/simple_menu.rb +2 -2
- data/lib/chingu/text.rb +25 -10
- data/lib/chingu/traits/sprite.rb +3 -3
- data/lib/chingu/viewport.rb +18 -14
- data/lib/chingu/window.rb +54 -0
- data/spec/chingu/animation_spec.rb +41 -3
- data/spec/chingu/game_state_manager_spec.rb +50 -3
- data/spec/chingu/network_spec.rb +144 -11
- metadata +20 -15
| @@ -1,34 +1,34 @@ | |
| 1 1 | 
             
            --- 
         | 
| 2 | 
            -
            - : | 
| 3 | 
            -
              :score: 10197
         | 
| 2 | 
            +
            - :score: 10197
         | 
| 4 3 | 
             
              :text: from example13.rb
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 4 | 
            +
              :name: NEW
         | 
| 5 | 
            +
            - :score: 10187
         | 
| 7 6 | 
             
              :text: from example13.rb
         | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 7 | 
            +
              :name: NEW
         | 
| 8 | 
            +
            - :score: 10177
         | 
| 10 9 | 
             
              :text: from example13.rb
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 10 | 
            +
              :name: NEW
         | 
| 11 | 
            +
            - :score: 10167
         | 
| 13 12 | 
             
              :text: from example13.rb
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 13 | 
            +
              :name: NEW
         | 
| 14 | 
            +
            - :score: 10157
         | 
| 16 15 | 
             
              :text: from example13.rb
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 16 | 
            +
              :name: NEW
         | 
| 17 | 
            +
            - :score: 10147
         | 
| 19 18 | 
             
              :text: from example13.rb
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 19 | 
            +
              :name: NEW
         | 
| 20 | 
            +
            - :score: 10137
         | 
| 22 21 | 
             
              :text: from example13.rb
         | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 22 | 
            +
              :name: NEW
         | 
| 23 | 
            +
            - :score: 10127
         | 
| 25 24 | 
             
              :text: from example13.rb
         | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 25 | 
            +
              :name: NEW
         | 
| 26 | 
            +
            - :score: 10117
         | 
| 28 27 | 
             
              :text: from example13.rb
         | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 28 | 
            +
              :name: NEW
         | 
| 29 | 
            +
            - :score: 10112
         | 
| 31 30 | 
             
              :text: from example13.rb
         | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 31 | 
            +
              :name: NEW
         | 
| 32 | 
            +
            - :score: 10107
         | 
| 34 33 | 
             
              :text: from example13.rb
         | 
| 34 | 
            +
              :name: NEW
         | 
| Binary file | 
| Binary file | 
    
        data/lib/chingu.rb
    CHANGED
    
    
    
        data/lib/chingu/animation.rb
    CHANGED
    
    | @@ -14,10 +14,13 @@ module Chingu | |
| 14 14 |  | 
| 15 15 | 
             
                #
         | 
| 16 16 | 
             
                # Create a new Animation. 
         | 
| 17 | 
            +
                # Must use :file OR :frames OR :image to create it.
         | 
| 17 18 | 
             
                #
         | 
| 18 19 | 
             
                #   - loop: [true|false]. After the last frame is used, start from the beginning.
         | 
| 19 20 | 
             
                #   - bounce: [true|false]. After the last frame is used, play it backwards untill the first frame is used again, then start playing forwards again.
         | 
| 20 21 | 
             
                #   - file:   Tile-file to cut up animation frames from. Could be a full path or just a name -- then it will look for media_path(file)
         | 
| 22 | 
            +
                #   - frames: Creates the animation from existing images which are the same size (Array<Gosu::Image>)
         | 
| 23 | 
            +
                #   - image: Image containing a strip of frames for the animation (Gosu::Image)
         | 
| 21 24 | 
             
                #   - width:  width of each frame in the tileanimation
         | 
| 22 25 | 
             
                #   - height:  width of each frame in the tileanimation
         | 
| 23 26 | 
             
                #   - size: [width, height]-Array or just one fixnum which will spez both height and width
         | 
| @@ -25,54 +28,71 @@ module Chingu | |
| 25 28 | 
             
                #   - step: [steps] move animation forward [steps] frames each time we call #next
         | 
| 26 29 | 
             
                #
         | 
| 27 30 | 
             
                def initialize(options)
         | 
| 28 | 
            -
                   | 
| 29 | 
            -
             | 
| 30 | 
            -
                  
         | 
| 31 | 
            +
                  options = {:step => 1, :loop => true, :bounce => false, :index => 0, :delay => 100}.merge!(options)
         | 
| 32 | 
            +
             | 
| 31 33 | 
             
                  @loop = options[:loop]
         | 
| 32 34 | 
             
                  @bounce = options[:bounce]
         | 
| 33 | 
            -
                   | 
| 35 | 
            +
                  file = options[:file]
         | 
| 36 | 
            +
                  image = options[:image]
         | 
| 37 | 
            +
                  @frames = options[:frames]
         | 
| 34 38 | 
             
                  @index = options[:index]
         | 
| 35 39 | 
             
                  @delay = options[:delay]
         | 
| 36 40 | 
             
                  @step = options[:step] || 1
         | 
| 37 41 | 
             
                  @dt = 0
         | 
| 38 | 
            -
             | 
| 42 | 
            +
             | 
| 39 43 | 
             
                  @sub_animations = {}
         | 
| 40 44 | 
             
                  @frame_actions = []
         | 
| 41 45 |  | 
| 42 | 
            -
                  unless  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 46 | 
            +
                  raise ArgumentError, "Must provide one of :frames, :image OR :file parameter" unless [@frames, file, image].compact.size == 1
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  if @frames        
         | 
| 49 | 
            +
                    raise ArgumentError, "Must provide at least one frame image with :frames" if @frames.empty?
         | 
| 50 | 
            +
                    raise ArgumentError, ":frames must consist of images only" unless @frames.all? {|i| i.is_a? Gosu::Image }
         | 
| 51 | 
            +
                    
         | 
| 52 | 
            +
                    @width, @height = @frames[0].width, @frames[0].height
         | 
| 53 | 
            +
                    
         | 
| 54 | 
            +
                    raise ArgumentError, ":frames must be of identical size" unless @frames[1..-1].all? {|i| i.width == @width and i.height == @height }
         | 
| 55 | 
            +
                    
         | 
| 56 | 
            +
                  else    
         | 
| 57 | 
            +
                    if file and not File.exists?(file)
         | 
| 58 | 
            +
                      Gosu::Image.autoload_dirs.each do |autoload_dir|
         | 
| 59 | 
            +
                        full_path = File.join(autoload_dir, file)
         | 
| 60 | 
            +
                        if File.exists?(full_path)
         | 
| 61 | 
            +
                          file = full_path
         | 
| 62 | 
            +
                          break
         | 
| 63 | 
            +
                        end
         | 
| 64 | 
            +
                      end      
         | 
| 49 65 | 
             
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    #
         | 
| 68 | 
            +
                    # Various ways of determening the framesize
         | 
| 69 | 
            +
                    #
         | 
| 70 | 
            +
                    if options[:height] && options[:width]
         | 
| 71 | 
            +
                      @height = options[:height]
         | 
| 72 | 
            +
                      @width = options[:width]
         | 
| 73 | 
            +
                    elsif options[:size] && options[:size].is_a?(Array)
         | 
| 74 | 
            +
                      @width = options[:size][0]
         | 
| 75 | 
            +
                      @height = options[:size][1]
         | 
| 76 | 
            +
                    elsif options[:size]
         | 
| 77 | 
            +
                      @width = options[:size]
         | 
| 78 | 
            +
                      @height = options[:size]
         | 
| 79 | 
            +
                    elsif file
         | 
| 80 | 
            +
                      if file =~ /_(\d+)x(\d+)/
         | 
| 81 | 
            +
                        # Auto-detect width/height from filename
         | 
| 82 | 
            +
                        # Tilefile foo_10x25.png would mean frame width 10px and height 25px
         | 
| 83 | 
            +
                        @width = $1.to_i
         | 
| 84 | 
            +
                        @height = $2.to_i
         | 
| 85 | 
            +
                      else
         | 
| 86 | 
            +
                        # Assume the shortest side of the actual file is the width/height for each frame
         | 
| 87 | 
            +
                        image = Gosu::Image.new($window, file)
         | 
| 88 | 
            +
                        @width = @height = (image.width < image.height) ? image.width : image.height
         | 
| 89 | 
            +
                       end
         | 
| 90 | 
            +
                    else
         | 
| 91 | 
            +
                      @width = @height = (image.width < image.height) ? image.width : image.height
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                    
         | 
| 94 | 
            +
                    @frames = Gosu::Image.load_tiles($window, image || file, @width, @height, true)
         | 
| 50 95 | 
             
                  end
         | 
| 51 | 
            -
                  
         | 
| 52 | 
            -
                  #
         | 
| 53 | 
            -
                  # Various ways of determening the framesize
         | 
| 54 | 
            -
                  #
         | 
| 55 | 
            -
                  if options[:height] && options[:width]
         | 
| 56 | 
            -
                    @height = options[:height]
         | 
| 57 | 
            -
                    @width = options[:width]
         | 
| 58 | 
            -
                  elsif options[:size] && options[:size].is_a?(Array)
         | 
| 59 | 
            -
                    @width = options[:size][0]
         | 
| 60 | 
            -
                    @height = options[:size][1]
         | 
| 61 | 
            -
                  elsif options[:size]
         | 
| 62 | 
            -
                    @width = options[:size]
         | 
| 63 | 
            -
                    @height = options[:size]
         | 
| 64 | 
            -
                  elsif @file =~ /_(\d+)x(\d+)/
         | 
| 65 | 
            -
                    # Auto-detect width/height from filename 
         | 
| 66 | 
            -
                    # Tilefile foo_10x25.png would mean frame width 10px and height 25px
         | 
| 67 | 
            -
                    @width = $1.to_i
         | 
| 68 | 
            -
                    @height = $2.to_i
         | 
| 69 | 
            -
                  else
         | 
| 70 | 
            -
                    # Assume the shortest side is the width/height for each frame
         | 
| 71 | 
            -
                    @image = Gosu::Image.new($window, @file)
         | 
| 72 | 
            -
                    @width = @height = (@image.width < @image.height) ? @image.width : @image.height
         | 
| 73 | 
            -
                  end
         | 
| 74 | 
            -
                  
         | 
| 75 | 
            -
                  @frames = Gosu::Image.load_tiles($window, @file, @width, @height, true)
         | 
| 76 96 | 
             
                end
         | 
| 77 97 |  | 
| 78 98 | 
             
                #
         | 
| @@ -247,4 +267,4 @@ module Chingu | |
| 247 267 | 
             
                  end
         | 
| 248 268 | 
             
                end		
         | 
| 249 269 | 
             
              end
         | 
| 250 | 
            -
            end
         | 
| 270 | 
            +
            end
         | 
| @@ -97,43 +97,13 @@ module Chingu | |
| 97 97 | 
             
                # Switch to a given game state, _replacing_ the current active one.
         | 
| 98 98 | 
             
                # By default setup() is called on the game state  we're switching _to_.
         | 
| 99 99 | 
             
                # .. and finalize() is called on the game state we're switching _from_.
         | 
| 100 | 
            -
                #
         | 
| 100 | 
            +
                #   
         | 
| 101 101 | 
             
                def switch_game_state(state, options = {})
         | 
| 102 | 
            -
                  options = {:setup => true, :finalize => true, :transitional => true}.merge(options)
         | 
| 103 | 
            -
                  
         | 
| 104 | 
            -
                  new_state = game_state_instance(state)
         | 
| 102 | 
            +
                  options = {:setup => true, :finalize => true, :transitional => true}.merge!(options)
         | 
| 105 103 |  | 
| 106 | 
            -
                   | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
                    
         | 
| 110 | 
            -
                    # Give the soon-to-be-disabled state a chance to clean up by calling finalize() on it.
         | 
| 111 | 
            -
                    current_game_state.finalize   if current_game_state.respond_to?(:finalize) && options[:finalize]
         | 
| 112 | 
            -
                    
         | 
| 113 | 
            -
                    # So BasicGameObject#create connects object to new state in its setup()
         | 
| 114 | 
            -
                    # Is this doubled in GameState.initialize() ?
         | 
| 115 | 
            -
                    self.inside_state = new_state
         | 
| 116 | 
            -
                    
         | 
| 117 | 
            -
                    # Call setup
         | 
| 118 | 
            -
                    new_state.setup               if new_state.respond_to?(:setup) && options[:setup]
         | 
| 119 | 
            -
                    
         | 
| 120 | 
            -
                    if @transitional_game_state && options[:transitional]
         | 
| 121 | 
            -
                      # If we have a transitional, switch to that instead, with new_state as first argument
         | 
| 122 | 
            -
                      transitional_game_state = @transitional_game_state.new(new_state, @transitional_game_state_options)
         | 
| 123 | 
            -
                      transitional_game_state.game_state_manager = self
         | 
| 124 | 
            -
                      self.push_game_state(transitional_game_state, :transitional => false)
         | 
| 125 | 
            -
                    else
         | 
| 126 | 
            -
                      if current_game_state.nil?
         | 
| 127 | 
            -
                        @game_states << new_state
         | 
| 128 | 
            -
                      else
         | 
| 129 | 
            -
                        # Replace last (active) state with new one
         | 
| 130 | 
            -
                        @game_states[-1] = new_state
         | 
| 131 | 
            -
                      end
         | 
| 132 | 
            -
                    end
         | 
| 133 | 
            -
                    ## MOVED: self.inside_state = current_game_state
         | 
| 134 | 
            -
                  end
         | 
| 135 | 
            -
                  
         | 
| 136 | 
            -
                  self.inside_state = nil   # no longer 'inside' (as in within initialize() etc) a game state
         | 
| 104 | 
            +
                  # Don't setup or finalize the underlying state, since it never becomes active.
         | 
| 105 | 
            +
                  pop_game_state(options.merge(:setup => false))
         | 
| 106 | 
            +
                  push_game_state(state, options.merge(:finalize => false))
         | 
| 137 107 | 
             
                end
         | 
| 138 108 | 
             
                alias :switch :switch_game_state
         | 
| 139 109 |  | 
| @@ -233,18 +203,18 @@ module Chingu | |
| 233 203 | 
             
                alias :clear :clear_game_states
         | 
| 234 204 |  | 
| 235 205 | 
             
                #
         | 
| 236 | 
            -
                # Pops through all game states until matching a given game state
         | 
| 237 | 
            -
                #
         | 
| 238 | 
            -
                def pop_until_game_state(new_state)
         | 
| 206 | 
            +
                # Pops through all game states until matching a given game state (takes either a class or instance to match).
         | 
| 207 | 
            +
                #    
         | 
| 208 | 
            +
                def pop_until_game_state(new_state, options = {})
         | 
| 239 209 | 
             
                  if new_state.is_a? Class
         | 
| 240 | 
            -
                    raise ArgumentError, "No state of given class is on the stack" unless @game_states. | 
| 210 | 
            +
                    raise ArgumentError, "No state of given class is on the stack" unless @game_states.any? {|s| s.is_a? new_state }
         | 
| 241 211 |  | 
| 242 | 
            -
                     | 
| 212 | 
            +
                    pop_game_state(options) until current_game_state.is_a? new_state
         | 
| 243 213 |  | 
| 244 214 | 
             
                  else
         | 
| 245 215 | 
             
                    raise ArgumentError, "State is not on the stack" unless @game_states.include? new_state
         | 
| 246 216 |  | 
| 247 | 
            -
                     | 
| 217 | 
            +
                    pop_game_state(options) until current_game_state == new_state
         | 
| 248 218 | 
             
                  end
         | 
| 249 219 | 
             
                end
         | 
| 250 220 |  | 
| @@ -136,6 +136,10 @@ module Chingu | |
| 136 136 |  | 
| 137 137 | 
             
                    @hud_height = 140
         | 
| 138 138 | 
             
                    @toolbar_icon_size = [32,32]
         | 
| 139 | 
            +
                    draw_toolbar_objects
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  def draw_toolbar_objects
         | 
| 139 143 | 
             
                    x = 20
         | 
| 140 144 | 
             
                    y = 60
         | 
| 141 145 | 
             
                    @classes.each do |klass|
         | 
| @@ -162,9 +166,8 @@ module Chingu | |
| 162 166 | 
             
                      rescue
         | 
| 163 167 | 
             
                        puts "Couldn't use #{klass} in editor: #{$!}"
         | 
| 164 168 | 
             
                      end
         | 
| 165 | 
            -
                    end
         | 
| 169 | 
            +
                    end        
         | 
| 166 170 | 
             
                  end
         | 
| 167 | 
            -
                  
         | 
| 168 171 | 
             
                  def display_help
         | 
| 169 172 | 
             
            text = <<END_OF_STRING
         | 
| 170 173 | 
             
              F1: This help screen
         | 
| @@ -63,7 +63,7 @@ module Chingu | |
| 63 63 | 
             
                      end
         | 
| 64 64 | 
             
                    end
         | 
| 65 65 |  | 
| 66 | 
            -
                    @texts[@index].color = Color::RED
         | 
| 66 | 
            +
                    @texts[@index].color = ::Gosu::Color::RED
         | 
| 67 67 | 
             
                    @name = Text.create("", :rotaion_center => :top_center, :x => $window.width/2, :y => 60, :size => 80)
         | 
| 68 68 | 
             
                  end
         | 
| 69 69 |  | 
| @@ -94,8 +94,8 @@ module Chingu | |
| 94 94 | 
             
                    new_value = @index + amount
         | 
| 95 95 | 
             
                    @index = new_value  if new_value < @letters.size && new_value >= 0
         | 
| 96 96 |  | 
| 97 | 
            -
                    @texts.each { |text| text.color = Color::WHITE }
         | 
| 98 | 
            -
                    @texts[@index].color = Color::RED
         | 
| 97 | 
            +
                    @texts.each { |text| text.color = ::Gosu::Color::WHITE }
         | 
| 98 | 
            +
                    @texts[@index].color = ::Gosu::Color::RED
         | 
| 99 99 |  | 
| 100 100 | 
             
                    sleep(0.15)
         | 
| 101 101 | 
             
                  end
         | 
| @@ -18,20 +18,19 @@ | |
| 18 18 | 
             
            # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            #++
         | 
| 21 | 
            -
             | 
| 22 21 | 
             
            module Chingu
         | 
| 23 22 | 
             
              module GameStates  
         | 
| 24 23 | 
             
                #
         | 
| 25 24 | 
             
                # A game state for a client in a multiplayer game, suitable for smaller/middle sized games.
         | 
| 26 25 | 
             
                # Used in combination with game state NetworkServer.
         | 
| 27 26 | 
             
                #
         | 
| 28 | 
            -
                # Uses  | 
| 27 | 
            +
                # Uses non-blocking polling TCP and marshal to communicate.
         | 
| 29 28 | 
             
                # If your game state inherits from NetworkClient you'll have the following methods available:
         | 
| 30 29 | 
             
                #
         | 
| 31 | 
            -
                #   connect( | 
| 32 | 
            -
                #   send_data(data)                 # Send raw data on the network,  | 
| 33 | 
            -
                #   send_msg(whatever ruby data)    # Will get  | 
| 34 | 
            -
                #   handle_incoming_data(max_size)  #  | 
| 30 | 
            +
                #   connect(address, port)          # Start a non-blocking connection. only connect() uses previosly given ip:port
         | 
| 31 | 
            +
                #   send_data(data)                 # Send raw data on the network, non-blocking
         | 
| 32 | 
            +
                #   send_msg(whatever ruby data)    # Will get marshalled and sent to server
         | 
| 33 | 
            +
                #   handle_incoming_data(max_size)  # Non-blocking read of incoming server data
         | 
| 35 34 | 
             
                #   disconnect_from_server          # Shuts down all network connections
         | 
| 36 35 | 
             
                #   
         | 
| 37 36 | 
             
                # The following callbacks can be overwritten to add your game logic:
         | 
| @@ -46,7 +45,7 @@ module Chingu | |
| 46 45 | 
             
                #   PlayState < Chingu::GameStates::NetworkClient
         | 
| 47 46 | 
             
                #     def initialize(options = {})
         | 
| 48 47 | 
             
                #       super   # this is always needed!
         | 
| 49 | 
            -
                #       connect(options[: | 
| 48 | 
            +
                #       connect(options[:address], options[:port])
         | 
| 50 49 | 
             
                #     end
         | 
| 51 50 | 
             
                #     
         | 
| 52 51 | 
             
                #     def on_connect
         | 
| @@ -65,29 +64,25 @@ module Chingu | |
| 65 64 | 
             
                #
         | 
| 66 65 | 
             
                # So why not EventMachine? No doubt in my mind that EventMachine is a hell of a library Chingu rolls its own for 2 reasons:
         | 
| 67 66 | 
             
                #
         | 
| 68 | 
            -
                #   AFAIK EventMachine can be hard to  | 
| 69 | 
            -
                #   Rubys  | 
| 67 | 
            +
                #   AFAIK EventMachine can be hard to integrate with the classic game loop, event machine wants its own loop
         | 
| 68 | 
            +
                #   Rubys non-blocking sockets work, so why not keep it simple
         | 
| 70 69 | 
             
                #
         | 
| 71 70 | 
             
                #
         | 
| 72 | 
            -
                class NetworkClient <  | 
| 73 | 
            -
                  attr_reader : | 
| 74 | 
            -
                  alias_method :address, :ip
         | 
| 71 | 
            +
                class NetworkClient < NetworkState
         | 
| 72 | 
            +
                  attr_reader :socket, :timeout
         | 
| 75 73 |  | 
| 76 74 | 
             
                  def connected?; @connected; end
         | 
| 77 75 |  | 
| 78 76 | 
             
                  def initialize(options = {})
         | 
| 79 | 
            -
                    super
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    @ | 
| 82 | 
            -
             | 
| 83 | 
            -
                    @port = options[:port] || NetworkServer::DEFAULT_PORT
         | 
| 77 | 
            +
                    super(options)
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    @timeout = options[:timeout] || 4000
         | 
| 80 | 
            +
             | 
| 84 81 | 
             
                    @max_read_per_update = options[:max_read_per_update] || 50000
         | 
| 85 82 |  | 
| 86 83 | 
             
                    @socket = nil
         | 
| 87 84 | 
             
                    @connected = false
         | 
| 88 | 
            -
                    @ | 
| 89 | 
            -
                    @packet_counter = 0
         | 
| 90 | 
            -
                    @packet_buffer = NetworkServer::PacketBuffer.new
         | 
| 85 | 
            +
                    @packet_buffer = PacketBuffer.new
         | 
| 91 86 | 
             
                  end
         | 
| 92 87 |  | 
| 93 88 | 
             
                  #
         | 
| @@ -98,47 +93,56 @@ module Chingu | |
| 98 93 | 
             
                  # 4) #on_data(data) will call #on_msgs(msg)
         | 
| 99 94 | 
             
                  #
         | 
| 100 95 | 
             
                  def update
         | 
| 101 | 
            -
             | 
| 96 | 
            +
             | 
| 102 97 | 
             
                    if @socket and not @connected
         | 
| 103 | 
            -
                       | 
| 104 | 
            -
                         | 
| 105 | 
            -
                         | 
| 106 | 
            -
                       | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 98 | 
            +
                      if Gosu::milliseconds >= @connect_times_out_at
         | 
| 99 | 
            +
                        @socket = nil
         | 
| 100 | 
            +
                        on_timeout
         | 
| 101 | 
            +
                      else
         | 
| 102 | 
            +
                        begin
         | 
| 103 | 
            +
                          # Start/Check on our nonblocking tcp connection
         | 
| 104 | 
            +
                          @socket.connect_nonblock(@sockaddr)
         | 
| 105 | 
            +
                        rescue Errno::EINPROGRESS   #rescue IO::WaitWritable
         | 
| 106 | 
            +
                        rescue Errno::EALREADY
         | 
| 107 | 
            +
                        rescue Errno::EISCONN
         | 
| 108 | 
            +
                          @connected = true
         | 
| 109 | 
            +
                          on_connect
         | 
| 110 | 
            +
                        rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EINVAL
         | 
| 109 111 | 
             
                          @socket = nil
         | 
| 110 112 | 
             
                          on_connection_refused
         | 
| 113 | 
            +
                        rescue Errno::ETIMEDOUT
         | 
| 114 | 
            +
                          @socket = nil
         | 
| 115 | 
            +
                          on_timeout
         | 
| 111 116 | 
             
                        end
         | 
| 112 | 
            -
                      rescue Errno::EISCONN
         | 
| 113 | 
            -
                        @connected = true
         | 
| 114 | 
            -
                        on_connect
         | 
| 115 | 
            -
                      rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EINVAL
         | 
| 116 | 
            -
                        @socket = nil
         | 
| 117 | 
            -
                        on_connection_refused
         | 
| 118 | 
            -
                      rescue Errno::ETIMEDOUT
         | 
| 119 | 
            -
                        @socket = nil
         | 
| 120 | 
            -
                        on_timeout
         | 
| 121 117 | 
             
                      end
         | 
| 122 118 | 
             
                    end
         | 
| 123 119 |  | 
| 124 | 
            -
                    handle_incoming_data
         | 
| 120 | 
            +
                    handle_incoming_data if @connected
         | 
| 121 | 
            +
             | 
| 125 122 | 
             
                    super
         | 
| 126 123 | 
             
                  end
         | 
| 127 124 |  | 
| 128 125 | 
             
                  #
         | 
| 129 | 
            -
                  # Connect to a given  | 
| 130 | 
            -
                  # Connect is done in a blocking manner. | 
| 131 | 
            -
                  #  | 
| 132 | 
            -
                   | 
| 133 | 
            -
             | 
| 126 | 
            +
                  # Connect to a given address:port (the server)
         | 
| 127 | 
            +
                  # Connect is done in a non-blocking manner.
         | 
| 128 | 
            +
                  # May pass :address and :port, which will overwrite any existing values.
         | 
| 129 | 
            +
                  def connect(options = {})
         | 
| 130 | 
            +
                    options = {
         | 
| 131 | 
            +
                      :address => @address,
         | 
| 132 | 
            +
                      :port => @port,
         | 
| 133 | 
            +
                      :reconnect => false, # Doesn't reset the timeout timer; used internally.
         | 
| 134 | 
            +
                    }.merge! options
         | 
| 135 | 
            +
                    
         | 
| 134 136 | 
             
                    return if @socket
         | 
| 135 137 |  | 
| 136 | 
            -
                    @ | 
| 137 | 
            -
                    @port = port | 
| 138 | 
            +
                    @address = options[:address]
         | 
| 139 | 
            +
                    @port = options[:port]
         | 
| 138 140 |  | 
| 139 141 | 
             
                    # Set up our @socket, update() will handle the actual nonblocking connection
         | 
| 140 142 | 
             
                    @socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
         | 
| 141 | 
            -
                    @sockaddr = Socket.sockaddr_in(@port, @ | 
| 143 | 
            +
                    @sockaddr = Socket.sockaddr_in(@port, @address)
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    @connect_times_out_at = Gosu::milliseconds + @timeout unless options[:reconnect]
         | 
| 142 146 |  | 
| 143 147 | 
             
                    return self
         | 
| 144 148 | 
             
                  end
         | 
| @@ -147,23 +151,22 @@ module Chingu | |
| 147 151 | 
             
                  # Called when connect() fails with connection refused (closed port)
         | 
| 148 152 | 
             
                  #
         | 
| 149 153 | 
             
                  def on_connection_refused
         | 
| 150 | 
            -
                    puts "[on_connection_refused() #{@ | 
| 151 | 
            -
                    connect( | 
| 154 | 
            +
                    puts "[on_connection_refused() #{@address}:#{@port}]"  if @debug
         | 
| 155 | 
            +
                    connect(:reconnect => true)
         | 
| 152 156 | 
             
                  end
         | 
| 153 157 |  | 
| 154 158 | 
             
                  #
         | 
| 155 159 | 
             
                  # Called when connect() recieves no initial answer from server
         | 
| 156 160 | 
             
                  #
         | 
| 157 161 | 
             
                  def on_timeout
         | 
| 158 | 
            -
                    puts "[on_timeout() #{@ | 
| 159 | 
            -
                    connect(@ip, @port)
         | 
| 162 | 
            +
                    puts "[on_timeout() #{@address}:#{@port}]"  if @debug
         | 
| 160 163 | 
             
                  end
         | 
| 161 164 |  | 
| 162 165 | 
             
                  #
         | 
| 163 166 | 
             
                  # on_connect will be called when client successfully makes a connection to server
         | 
| 164 167 | 
             
                  #
         | 
| 165 168 | 
             
                  def on_connect
         | 
| 166 | 
            -
                    puts "[Connected to Server #{@ | 
| 169 | 
            +
                    puts "[Connected to Server #{@address}:#{@port}]"  if @debug
         | 
| 167 170 | 
             
                  end
         | 
| 168 171 |  | 
| 169 172 | 
             
                  #
         | 
| @@ -178,16 +181,14 @@ module Chingu | |
| 178 181 | 
             
                  # handle_incoming_data will call on_data(raw_data) when stuff comes on on the socket.
         | 
| 179 182 | 
             
                  #
         | 
| 180 183 | 
             
                  def handle_incoming_data(amount = @max_read_per_update)
         | 
| 181 | 
            -
                    return unless @socket
         | 
| 184 | 
            +
                    return unless @socket and connected?
         | 
| 182 185 |  | 
| 183 186 | 
             
                    if IO.select([@socket], nil, nil, 0.0)
         | 
| 184 187 | 
             
                      begin
         | 
| 185 188 | 
             
                        packet, sender = @socket.recvfrom(amount)
         | 
| 186 189 | 
             
                        on_data(packet)        
         | 
| 187 190 | 
             
                      rescue Errno::ECONNABORTED, Errno::ECONNRESET
         | 
| 188 | 
            -
                         | 
| 189 | 
            -
                        @socket = nil
         | 
| 190 | 
            -
                        on_disconnect
         | 
| 191 | 
            +
                        disconnect_from_server
         | 
| 191 192 | 
             
                      end
         | 
| 192 193 | 
             
                    end
         | 
| 193 194 | 
             
                  end
         | 
| @@ -198,14 +199,26 @@ module Chingu | |
| 198 199 | 
             
                  def on_data(data)
         | 
| 199 200 | 
             
                    @packet_buffer.buffer_data data
         | 
| 200 201 |  | 
| 202 | 
            +
                    @bytes_received += data.length
         | 
| 203 | 
            +
             | 
| 201 204 | 
             
                    while packet = @packet_buffer.next_packet
         | 
| 202 | 
            -
                       | 
| 205 | 
            +
                      @packets_received += 1
         | 
| 206 | 
            +
                      begin
         | 
| 207 | 
            +
                        on_msg(Marshal.load(packet))
         | 
| 208 | 
            +
                      rescue TypeError
         | 
| 209 | 
            +
                        disconnect_from_server
         | 
| 210 | 
            +
                        break
         | 
| 211 | 
            +
                      end
         | 
| 203 212 | 
             
                    end
         | 
| 204 213 | 
             
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # Handler when message packets are received. Should be overriden in your code.
         | 
| 216 | 
            +
                  def on_msg(packet)
         | 
| 217 | 
            +
                    # should be overridden.
         | 
| 218 | 
            +
                  end
         | 
| 205 219 |  | 
| 206 220 | 
             
                  #
         | 
| 207 221 | 
             
                  # Send a msg to the server
         | 
| 208 | 
            -
                  # Can be whatever ruby-structure that responds to #to_yaml
         | 
| 209 222 | 
             
                  #
         | 
| 210 223 | 
             
                  def send_msg(msg)
         | 
| 211 224 | 
             
                    send_data(Marshal.dump(msg))
         | 
| @@ -213,28 +226,36 @@ module Chingu | |
| 213 226 |  | 
| 214 227 | 
             
                  #
         | 
| 215 228 | 
             
                  # Send whatever raw data to the server
         | 
| 216 | 
            -
                  #
         | 
| 229 | 
            +
                  # Returns amount of data sent, including header.
         | 
| 217 230 | 
             
                  def send_data(data)
         | 
| 218 | 
            -
                     | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 221 | 
            -
                     | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
                     | 
| 231 | 
            +
                    length = @socket.write([data.length].pack(NetworkServer::PACKET_HEADER_FORMAT))
         | 
| 232 | 
            +
                    length += @socket.write(data)
         | 
| 233 | 
            +
                    @packets_sent += 1
         | 
| 234 | 
            +
                    @bytes_sent += length
         | 
| 235 | 
            +
                    length
         | 
| 236 | 
            +
                  rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
         | 
| 237 | 
            +
                    disconnect_from_server
         | 
| 238 | 
            +
                    0
         | 
| 226 239 | 
             
                  end
         | 
| 227 240 |  | 
| 228 241 | 
             
                  # Ensure that the buffer is cleared of data to write (call at the end of update or, at least after all sends).
         | 
| 229 242 | 
             
                  def flush
         | 
| 230 | 
            -
                    @socket.flush
         | 
| 243 | 
            +
                    @socket.flush if @socket
         | 
| 244 | 
            +
                  rescue IOError
         | 
| 245 | 
            +
                    disconnect_from_server
         | 
| 231 246 | 
             
                  end
         | 
| 232 247 |  | 
| 233 248 | 
             
                  #
         | 
| 234 249 | 
             
                  # Shuts down all communication (closes socket) with server
         | 
| 235 250 | 
             
                  #
         | 
| 236 251 | 
             
                  def disconnect_from_server
         | 
| 237 | 
            -
                    @socket.close
         | 
| 252 | 
            +
                    @socket.close if @socket and not @socket.closed?
         | 
| 253 | 
            +
                  rescue Errno::ENOTCONN
         | 
| 254 | 
            +
                  ensure
         | 
| 255 | 
            +
                    @socket = nil
         | 
| 256 | 
            +
                    was_connected = @connected
         | 
| 257 | 
            +
                    @connected = false
         | 
| 258 | 
            +
                    on_disconnect if was_connected
         | 
| 238 259 | 
             
                  end
         | 
| 239 260 | 
             
                  alias close disconnect_from_server
         | 
| 240 261 | 
             
                  alias stop disconnect_from_server
         |