game_2d 0.0.2 → 0.0.3

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.
@@ -40,8 +40,8 @@ module Serializable
40
40
  def self.from_json(json)
41
41
  return nil unless json
42
42
  class_name = json[:class]
43
- raise "Suspicious class name: #{class_name}" unless
44
- (class_name == 'Player') ||
43
+ fail "No class name in #{json.inspect}" unless class_name
44
+ fail "Suspicious class name in #{json.inspect}: #{class_name}" unless
45
45
  (class_name.start_with? 'Entity::') ||
46
46
  (class_name.start_with? 'Move::')
47
47
  require "game_2d/#{class_name.pathize}"
@@ -14,11 +14,19 @@ class ServerConnection
14
14
  def initialize(port, game, server, id, remote_addr)
15
15
  @port, @game, @server, @id, @remote_addr = port, game, server, id, remote_addr
16
16
  puts "ServerConnection: New connection #{id} from #{remote_addr}"
17
+ @authenticated = false
17
18
  end
18
19
 
19
- attr_reader :id, :player_id
20
+ attr_reader :id, :player_name, :authenticated
21
+ attr_accessor :player_id
22
+ def authenticated?; @authenticated; end
20
23
 
21
24
  def answer_handshake(handshake)
25
+ if authenticated?
26
+ warn "#{self} cannot re-handshake"
27
+ disconnect!
28
+ return
29
+ end
22
30
  @player_name = handshake[:player_name]
23
31
  dh_public_key = handshake[:dh_public_key]
24
32
  client_public_key = handshake[:client_public_key]
@@ -32,23 +40,29 @@ class ServerConnection
32
40
  end
33
41
 
34
42
  def answer_login(b64_password_hash, b64_iv)
43
+ if authenticated?
44
+ warn "#{self} cannot re-login"
45
+ disconnect!
46
+ return
47
+ end
35
48
  password_hash = decrypt(
36
49
  strict_decode64(b64_password_hash),
37
50
  strict_decode64(b64_iv))
38
51
  player_data = @game.player_data(@player_name)
39
52
  if player_data
40
53
  unless password_hash == player_data[:password_hash]
41
- $stderr.puts "Wrong password for #{@player_name} (#{password_hash} != #{player_data[:password_hash]})"
54
+ warn "Wrong password for #{@player_name} (#{password_hash} != #{player_data[:password_hash]})"
42
55
  disconnect!
43
56
  return
44
57
  end
45
58
  else # new player
46
59
  @game.store_player_data @player_name, :password_hash => password_hash
47
60
  end
61
+ @authenticated = true
48
62
 
63
+ @port.register_player @player_name, self
49
64
  player = @game.add_player(@player_name)
50
65
  @player_id = player.registry_id
51
- @port.register_player @player_id, self
52
66
  puts "#{player} logs in from #{@remote_addr} at <#{@game.tick}>"
53
67
 
54
68
  # We don't send the registry here. The Game will do it after
@@ -82,15 +96,11 @@ class ServerConnection
82
96
 
83
97
  # Not called yet...
84
98
  def update_score(player, at_tick)
85
- send_record :update_score => { player.registry_id => player.score }, :at_tick => at_tick
99
+ send_record :update_score => { player.player_name => player.score }, :at_tick => at_tick
86
100
  end
87
101
 
88
102
  def close
89
- return unless @player_id
90
- @port.deregister_player @player_id
91
- toast = player
92
- puts "#{toast} -- #{@remote_addr} disconnected at <#{@game.tick}>"
93
- @game.delete_entity toast
103
+ @game.send_player_gone player
94
104
  end
95
105
 
96
106
  def on_packet(data, channel)
@@ -100,18 +110,18 @@ class ServerConnection
100
110
  answer_handshake(handshake)
101
111
  elsif password_hash = hash.delete(:password_hash)
102
112
  answer_login(password_hash, hash.delete(:iv))
103
- elsif hash.delete(:save)
104
- @game.save
105
113
  elsif ping = hash.delete(:ping)
106
114
  answer_ping ping
115
+ elsif !authenticated?
116
+ warn "Ignoring #{hash.inspect}, not authenticated"
117
+ elsif hash.delete(:save)
118
+ @game.save
107
119
  else
108
- if hash[:player_id] = @player_id
109
- @game.add_player_action hash
110
- # TODO: Validate
111
- @port.broadcast_player_action hash, channel
112
- else
113
- $stderr.puts "Ignoring move #{hash.inspect}, no player_id for this connection"
114
- end
120
+ hash[:player_name] = @player_name
121
+ hash[:player_id] = @player_id
122
+ @game.add_player_action hash
123
+ # TODO: Validate
124
+ @port.broadcast_player_action hash, channel
115
125
  end
116
126
  end
117
127
 
@@ -133,4 +143,8 @@ class ServerConnection
133
143
  def disconnect!
134
144
  @server.disconnect_client(@id)
135
145
  end
146
+
147
+ def to_s
148
+ "#{@player_name || '??'} ##{@id} from #{@remote_addr}"
149
+ end
136
150
  end
@@ -10,7 +10,7 @@ class ServerPort
10
10
  puts "ENet server listening on #{port_number}"
11
11
 
12
12
  @clients = {}
13
- @player_connections = {}
13
+ @player_connections = {} # player_name => ServerConnection
14
14
  @new_players = Set.new
15
15
 
16
16
  @server.on_connection method(:on_connection)
@@ -34,17 +34,26 @@ class ServerPort
34
34
  def on_disconnection(id)
35
35
  puts "ENet connection #{id} disconnected"
36
36
  gone = @clients.delete id
37
- gone.close
37
+ deregister_player(gone)
38
38
  puts "Remaining connection IDs: #{@clients.keys.sort.join(', ')}"
39
39
  end
40
40
 
41
- def register_player(player_id, conn)
42
- @player_connections[player_id] = conn
43
- @new_players << player_id
41
+ def register_player(player_name, conn)
42
+ if old_conn = @player_connections[player_name]
43
+ warn "Disconnecting old connection for #{player_name} (#{old_conn})"
44
+ old_conn.disconnect!
45
+ end
46
+ @player_connections[player_name] = conn
47
+ @new_players << player_name
44
48
  end
45
49
 
46
- def deregister_player(player_id)
47
- @player_connections.delete player_id
50
+ def deregister_player(conn)
51
+ player_name = conn.player_name
52
+ if conn.authenticated? && @player_connections[player_name] == conn
53
+ puts "Player #{player_name} logged out at <#{@game.tick}>"
54
+ @player_connections.delete player_name
55
+ conn.close
56
+ end
48
57
  end
49
58
 
50
59
  def new_players
@@ -53,17 +62,17 @@ class ServerPort
53
62
  copy
54
63
  end
55
64
 
56
- def player_connection(player_id)
57
- @player_connections[player_id]
65
+ def player_name_connection(player_name)
66
+ @player_connections[player_name]
58
67
  end
59
68
 
60
69
  # Re-broadcast to everyone except the original sender
61
- def broadcast_player_action(hash, channel)
62
- sender_player_id = hash[:player_id]
63
- fail "No player_id in #{hash.inspect}" unless sender_player_id
70
+ def broadcast_player_action(hash, channel=0)
71
+ sender_player_name = hash[:player_name]
72
+ fail "No player_name in #{hash.inspect}" unless sender_player_name
64
73
  data = hash.to_json
65
- @player_connections.each do |player_id, conn|
66
- @server.send_packet(conn.id, data, false, channel) unless player_id == sender_player_id
74
+ @player_connections.each do |player_name, conn|
75
+ @server.send_packet(conn.id, data, false, channel) unless player_name == sender_player_name
67
76
  end
68
77
  @server.flush
69
78
  end
@@ -1,41 +1,62 @@
1
1
  require 'game_2d/entity'
2
+ require 'game_2d/entity/base'
2
3
  require 'game_2d/entity/block'
3
- require 'game_2d/entity/titanium'
4
+ require 'game_2d/entity/destination'
5
+ require 'game_2d/entity/ghost'
6
+ require 'game_2d/entity/hole'
4
7
  require 'game_2d/entity/owned_entity'
8
+ require 'game_2d/entity/slime'
5
9
  require 'game_2d/entity/teleporter'
6
- require 'game_2d/entity/destination'
10
+ require 'game_2d/entity/titanium'
7
11
  require 'game_2d/wall'
8
12
 
9
13
  module Transparency
10
14
  def transparent?(one, two)
11
- # Walls and titanium: transparent to absolutely nothing
15
+ # Walls: transparent to absolutely nothing
12
16
  return false if wall?(one) || wall?(two)
13
17
 
14
- # Teleporter destinations: transparent to everything
15
- return true if destination?(one) || destination?(two)
18
+ # Ghosts: transparent to everything except a wall
19
+ return true if ghost?(one) || ghost?(two)
20
+
21
+ # Titanium: transparent to nothing except ghosts
22
+ return false if titanium?(one) || titanium?(two)
23
+
24
+ # Holes and teleporter destinations: transparent to
25
+ # everything except walls and titanium
26
+ return true if transparent_to_most?(one) || transparent_to_most?(two)
16
27
 
17
28
  # Teleporters: transparent to everything except other
18
- # teleporters, and destinations
19
- return teleporter_ok?(one, two) if teleporter?(one)
20
- return teleporter_ok?(two, one) if teleporter?(two)
29
+ # teleporters
30
+ return teleporter_ok?(two) if teleporter?(one)
31
+ return teleporter_ok?(one) if teleporter?(two)
21
32
 
22
33
  # Owned entities are transparent to the owner, and other
23
34
  # objects with the same owner
24
35
  return related_by_owner?(one, two) if owned?(one)
25
36
  return related_by_owner?(two, one) if owned?(two)
26
37
 
27
- # Should only get here if both objects are players
38
+ # Bases are transparent to players, only
39
+ return player?(two) if base?(one)
40
+ return player?(one) if base?(two)
41
+
42
+ # Default case: opaque
43
+ # Should only get here if both objects are non-ghost players,
44
+ # or slime
28
45
  fail("Huh? one=#{one}, two=#{two}") unless
29
- one.is_a?(Player) && two.is_a?(Player)
46
+ normal?(one) && normal?(two)
30
47
  false
31
48
  end
32
49
 
33
50
  private
34
51
  def wall?(entity)
35
- entity.is_a?(Wall) || entity.is_a?(Entity::Titanium)
52
+ entity.is_a?(Wall)
53
+ end
54
+
55
+ def titanium?(entity)
56
+ entity.is_a?(Entity::Titanium)
36
57
  end
37
58
 
38
- def teleporter_ok?(tp, other)
59
+ def teleporter_ok?(other)
39
60
  !teleporter?(other)
40
61
  end
41
62
 
@@ -43,8 +64,8 @@ module Transparency
43
64
  entity.is_a?(Entity::Teleporter)
44
65
  end
45
66
 
46
- def destination?(entity)
47
- entity.is_a?(Entity::Destination)
67
+ def transparent_to_most?(entity)
68
+ entity.is_a?(Entity::Destination) || entity.is_a?(Entity::Hole)
48
69
  end
49
70
 
50
71
  def related_by_owner?(o, other)
@@ -56,4 +77,20 @@ module Transparency
56
77
  def owned?(entity)
57
78
  entity.is_a? Entity::OwnedEntity
58
79
  end
59
- end
80
+
81
+ def base?(entity)
82
+ entity.is_a? Entity::Base
83
+ end
84
+
85
+ def ghost?(entity)
86
+ entity.is_a? Entity::Ghost
87
+ end
88
+
89
+ def player?(entity)
90
+ entity.is_a?(Player)
91
+ end
92
+
93
+ def normal?(entity)
94
+ player?(entity) || entity.is_a?(Entity::Slime)
95
+ end
96
+ end
@@ -1,3 +1,3 @@
1
1
  module Game2d
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
Binary file
Binary file
File without changes
File without changes
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,158 @@
1
+ require 'game_2d/game_space'
2
+ require 'game_2d/entity/block'
3
+ require 'game_2d/entity/titanium'
4
+
5
+ describe Entity::Block do
6
+ let(:world) { GameSpace.new(nil).establish_world('lump', nil, 3, 3) }
7
+
8
+ subject { world << Entity::Block.new(400, 400) }
9
+
10
+ def add_titanium(x, y)
11
+ world << Entity::Titanium.new(x, y)
12
+ end
13
+
14
+ def expect_new_height(height)
15
+ expect(subject.y).to eq(height)
16
+ end
17
+
18
+ context "when 5 HP or less" do
19
+ before(:each) { subject.hp = 1 }
20
+
21
+ it "is level 0 (dirt)" do
22
+ expect(subject.level_name).to eq('dirt')
23
+ expect(subject.level).to eq(0)
24
+ end
25
+ it "falls when nothing underneath" do
26
+ add_titanium 400, 0 # above
27
+ add_titanium 0, 400 # on left
28
+ add_titanium 800, 400 # on right
29
+ add_titanium 0, 800 # lower-left
30
+ add_titanium 800, 800 # lower-right
31
+ world.update; expect_new_height 401
32
+ end
33
+ it "doesn't fall when something directly underneath" do
34
+ add_titanium 400, 800
35
+ world.update; expect_new_height 400
36
+ end
37
+ it "doesn't fall when something slightly underneath" do
38
+ add_titanium 1, 800
39
+ world.update; expect_new_height 400
40
+ end
41
+ end
42
+
43
+ context "when 6 to 10 HP" do
44
+ before(:each) { subject.hp = 10 }
45
+
46
+ it "is level 1 (brick)" do
47
+ expect(subject.level_name).to eq('brick')
48
+ expect(subject.level).to eq(1)
49
+ end
50
+ it "falls when supported on left only" do
51
+ add_titanium 400, 0 # above
52
+ add_titanium 0, 400 # on left
53
+ world.update; expect_new_height 401
54
+ end
55
+ it "falls when supported on right only" do
56
+ add_titanium 400, 0 # above
57
+ add_titanium 800, 400 # on right
58
+ world.update; expect_new_height 401
59
+ end
60
+ it "doesn't fall when supported on both sides" do
61
+ add_titanium 0, 400 # on left
62
+ add_titanium 800, 400 # on right
63
+ world.update; expect_new_height 400
64
+ end
65
+ it "falls if the supports don't line up with each other" do
66
+ add_titanium 0, 400 # on left, perfect
67
+ add_titanium 800, 401 # on right, a bit low
68
+ world.update; expect_new_height 401
69
+ world.update; expect_new_height 403
70
+ end
71
+ it "falls until the supports line up with it" do
72
+ add_titanium 0, 402 # on left, a bit below
73
+ add_titanium 800, 402 # on right, same distance below
74
+ world.update; expect_new_height 401
75
+ world.update; expect_new_height 402
76
+ world.update; expect_new_height 402
77
+ end
78
+ it "rises until the supports line up with it" do
79
+ subject.y_vel = -10
80
+ add_titanium 0, 385 # on left, a bit above
81
+ add_titanium 800, 385 # on right, same distance above
82
+ world.update; expect_new_height 391
83
+ world.update; expect_new_height 385
84
+ world.update; expect_new_height 385
85
+ end
86
+ end
87
+
88
+ context "when 11 to 15 HP" do
89
+ before(:each) { subject.hp = 11 }
90
+
91
+ it "is level 2 (cement)" do
92
+ expect(subject.level_name).to eq('cement')
93
+ expect(subject.level).to eq(2)
94
+ end
95
+ it "falls when supported on top only" do
96
+ add_titanium 400, 0 # above
97
+ world.update; expect_new_height 401
98
+ end
99
+ it "doesn't fall when supported on left only" do
100
+ add_titanium 0, 400 # on left
101
+ world.update; expect_new_height 400
102
+ end
103
+ it "doesn't fall when supported on right only" do
104
+ add_titanium 800, 400 # on right
105
+ world.update; expect_new_height 400
106
+ end
107
+ it "doesn't fall when supported on both sides" do
108
+ add_titanium 0, 400 # on left
109
+ add_titanium 800, 400 # on right
110
+ world.update; expect_new_height 400
111
+ end
112
+ it "falls if the support doesn't line up" do
113
+ add_titanium 0, 398 # on left, a bit high
114
+ world.update; expect_new_height 401
115
+ end
116
+ it "falls until the support lines up" do
117
+ add_titanium 0, 402 # on left, a bit below
118
+ add_titanium 800, 403 # on right, a bit further below
119
+ world.update; expect_new_height 401
120
+ world.update; expect_new_height 402
121
+ world.update; expect_new_height 402
122
+ end
123
+ end
124
+
125
+ context "when 16 to 20 HP" do
126
+ before(:each) { subject.hp = 20 }
127
+
128
+ it "is level 3 (steel)" do
129
+ expect(subject.level_name).to eq('steel')
130
+ expect(subject.level).to eq(3)
131
+ end
132
+ it "doesn't fall when supported on top only, directly" do
133
+ add_titanium 400, 0 # above
134
+ world.update; expect_new_height 400
135
+ end
136
+ it "doesn't fall when supported on top only, slightly touching" do
137
+ add_titanium 0, 1 # above, far over
138
+ world.update; expect_new_height 400
139
+ end
140
+ it "falls when not touching anything" do
141
+ add_titanium 0, 0 # upper-left
142
+ world.update; expect_new_height 401
143
+ end
144
+ end
145
+
146
+ context "when abve 20 HP" do
147
+ before(:each) { subject.hp = 21 }
148
+
149
+ it "is level 4 (unlikelium)" do
150
+ expect(subject.level_name).to eq('unlikelium')
151
+ expect(subject.level).to eq(4)
152
+ end
153
+ it "doesn't fall ever" do
154
+ world.update; expect_new_height 400
155
+ end
156
+ end
157
+
158
+ end