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.
@@ -1,34 +1,34 @@
1
1
  ---
2
- - :name: NEW
3
- :score: 10197
2
+ - :score: 10197
4
3
  :text: from example13.rb
5
- - :name: NEW
6
- :score: 10187
4
+ :name: NEW
5
+ - :score: 10187
7
6
  :text: from example13.rb
8
- - :name: NEW
9
- :score: 10177
7
+ :name: NEW
8
+ - :score: 10177
10
9
  :text: from example13.rb
11
- - :name: NEW
12
- :score: 10167
10
+ :name: NEW
11
+ - :score: 10167
13
12
  :text: from example13.rb
14
- - :name: NEW
15
- :score: 10157
13
+ :name: NEW
14
+ - :score: 10157
16
15
  :text: from example13.rb
17
- - :name: NEW
18
- :score: 10147
16
+ :name: NEW
17
+ - :score: 10147
19
18
  :text: from example13.rb
20
- - :name: NEW
21
- :score: 10137
19
+ :name: NEW
20
+ - :score: 10137
22
21
  :text: from example13.rb
23
- - :name: NEW
24
- :score: 10127
22
+ :name: NEW
23
+ - :score: 10127
25
24
  :text: from example13.rb
26
- - :name: NEW
27
- :score: 10117
25
+ :name: NEW
26
+ - :score: 10117
28
27
  :text: from example13.rb
29
- - :name: NEW
30
- :score: 10112
28
+ :name: NEW
29
+ - :score: 10112
31
30
  :text: from example13.rb
32
- - :name: NEW
33
- :score: 10107
31
+ :name: NEW
32
+ - :score: 10107
34
33
  :text: from example13.rb
34
+ :name: NEW
Binary file
@@ -38,7 +38,7 @@ require_all "#{CHINGU_ROOT}/chingu/async_tasks"
38
38
  require_all "#{CHINGU_ROOT}/chingu"
39
39
 
40
40
  module Chingu
41
- VERSION = "0.9rc7"
41
+ VERSION = "0.9rc8"
42
42
 
43
43
  DEBUG_COLOR = Gosu::Color.new(0xFFFF0000)
44
44
  DEBUG_ZORDER = 9999
@@ -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
- #options = {:step => 1, :loop => true, :bounce => false, :width => 32, :height => 32, :index => 0, :delay => 100}.merge(options)
29
- options = {:step => 1, :loop => true, :bounce => false, :index => 0, :delay => 100}.merge(options)
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
- @file = options[:file]
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 File.exists?(@file)
43
- Gosu::Image.autoload_dirs.each do |autoload_dir|
44
- full_path = File.join(autoload_dir, @file)
45
- if File.exists?(full_path)
46
- @file = full_path
47
- break
48
- end
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
- if new_state
107
- # Make sure the game state knows about the manager
108
- new_state.game_state_manager = self
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.map {|s| s.class }.include? new_state
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
- @game_states.pop until current_game_state.is_a? new_state
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
- @game_states.pop while current_game_state != new_state
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 nonblocking polling TCP and YAML to communicate.
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(ip, port) # Start a nonblocking connection. only connect() uses previosly given ip:port
32
- # send_data(data) # Send raw data on the network, nonblocking
33
- # send_msg(whatever ruby data) # Will get YAML'd and sent to server
34
- # handle_incoming_data(max_size) # Nonblocking read of incoming server data
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[:ip], options[:port])
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 intergrate with the classic game loop, event machine wants its own loop
69
- # Rubys nonblocking sockets work, so why not keep it simple
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 < Chingu::GameState
73
- attr_reader :latency, :socket, :packet_counter, :packet_buffer, :ip, :port
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
- @timeout = options[:timeout] || 4
81
- @debug = options[:debug]
82
- @ip = options[:ip] || "0.0.0.0"
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
- @latency = 0
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
- begin
104
- # Start/Check on our nonblocking tcp connection
105
- @socket.connect_nonblock(@sockaddr)
106
- rescue Errno::EINPROGRESS #rescue IO::WaitWritable
107
- rescue Errno::EALREADY
108
- if IO.select([@socket],nil,nil,0.1).nil?
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 ip:port (the server)
130
- # Connect is done in a blocking manner.
131
- # Will timeout after 4 seconds
132
- #
133
- def connect(ip = nil, port = nil)
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
- @ip = ip if ip
137
- @port = port if 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, @ip)
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() #{@ip}:#{@port}]" if @debug
151
- connect(@ip, @port)
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() #{@ip}:#{@port}]" if @debug
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 #{@ip}:#{@port}]" if @debug
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
- @connected = false
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
- on_msg(Marshal.load(packet))
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
- begin
219
- @socket.write([data.length].pack(NetworkServer::PACKET_HEADER_FORMAT))
220
- @socket.write(data)
221
- rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
222
- @connected = false
223
- @socket = nil
224
- on_disconnect
225
- end
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