chingu 0.9rc7 → 0.9rc8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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