gosu_extensions 0.1.0
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/Rakefile +18 -0
- data/VERSION +1 -0
- data/lib/controls.rb +35 -0
- data/lib/extensions/module.rb +15 -0
- data/lib/extensions/numeric.rb +9 -0
- data/lib/game_window.rb +381 -0
- data/lib/gosu_extensions.rb +53 -0
- data/lib/layer.rb +11 -0
- data/lib/old_game_window.rb +249 -0
- data/lib/resources.rb +5 -0
- data/lib/scheduling.rb +48 -0
- data/lib/traits/attachable.rb +12 -0
- data/lib/traits/controllable.rb +43 -0
- data/lib/traits/damaging.rb +16 -0
- data/lib/traits/generator.rb +46 -0
- data/lib/traits/initializer_hooks.rb +34 -0
- data/lib/traits/it_is_a.rb +47 -0
- data/lib/traits/lives.rb +59 -0
- data/lib/traits/pod.rb +95 -0
- data/lib/traits/shooter.rb +113 -0
- data/lib/traits/shot.rb +16 -0
- data/lib/traits/targetable.rb +11 -0
- data/lib/traits/targeting/closest.rb +26 -0
- data/lib/traits/targeting.rb +1 -0
- data/lib/traits/turnable.rb +41 -0
- data/lib/units/moveable.rb +196 -0
- data/lib/units/short_lived.rb +20 -0
- data/lib/units/thing.rb +155 -0
- data/lib/vector_utilities.rb +11 -0
- data/lib/waves.rb +34 -0
- metadata +115 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
module Shooter
|
2
|
+
|
3
|
+
Shoot = :shoot
|
4
|
+
|
5
|
+
manual <<-MANUAL
|
6
|
+
Defines:
|
7
|
+
range <some range> # This is only needed for targeted shooting, e.g. automatic cannons
|
8
|
+
frequency <some shooting frequency> # TODO block
|
9
|
+
shoots <class:thing>
|
10
|
+
muzzle_position { position calculation } || frontal # a block
|
11
|
+
muzzle_velocity { velocity calculation }
|
12
|
+
muzzle_rotation { rotation calculation }
|
13
|
+
|
14
|
+
Example:
|
15
|
+
frequency 20
|
16
|
+
shoots Bullet
|
17
|
+
muzzle_position { self.position + self.rotation_vector.normalize*self.radius }
|
18
|
+
muzzle_velocity { |_| self.rotation_vector.normalize }
|
19
|
+
muzzle_rotation { |_| self.rotation }
|
20
|
+
MANUAL
|
21
|
+
|
22
|
+
attr_accessor :shot_type
|
23
|
+
attr_writer :shooting_range, :shooting_rate
|
24
|
+
|
25
|
+
def self.included base
|
26
|
+
base.extend ClassMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
def shooting_range
|
30
|
+
@shooting_range || @shooting_range = 300
|
31
|
+
end
|
32
|
+
|
33
|
+
def shooting_rate
|
34
|
+
@shooting_rate || @shooting_rate = (SUBSTEPS**2).to_f/2
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
def range amount
|
39
|
+
InitializerHooks.register self do
|
40
|
+
self.shooting_range = amount
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def frequency amount
|
44
|
+
InitializerHooks.register self do
|
45
|
+
self.shooting_rate = ((SUBSTEPS**2).to_f/amount)/2
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def shoots type
|
49
|
+
InitializerHooks.register self do
|
50
|
+
self.shot_type = type
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def muzzle_position &block
|
54
|
+
InitializerHooks.register self do
|
55
|
+
muzzle_position_func &block
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def muzzle_velocity &block
|
59
|
+
InitializerHooks.register self do
|
60
|
+
muzzle_velocity_func &block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def muzzle_rotation &block
|
64
|
+
InitializerHooks.register self do
|
65
|
+
muzzle_rotation_func &block
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def shot
|
71
|
+
self.shot_type.new @window
|
72
|
+
end
|
73
|
+
|
74
|
+
def muzzle_position
|
75
|
+
instance_eval &(@muzzle_position || @muzzle_position = lambda { |*| self.position + self.rotation_vector*self.radius })
|
76
|
+
end
|
77
|
+
def muzzle_velocity target = nil
|
78
|
+
instance_eval &(@muzzle_velocity || @muzzle_velocity = lambda { |*| self.rotation_vector })
|
79
|
+
end
|
80
|
+
def muzzle_rotation target = nil
|
81
|
+
instance_eval &(@muzzle_rotation || @muzzle_rotation = lambda { |*| self.rotation })
|
82
|
+
end
|
83
|
+
def muzzle_position_func &position
|
84
|
+
@muzzle_position = position
|
85
|
+
end
|
86
|
+
def muzzle_velocity_func &velocity
|
87
|
+
@muzzle_velocity = velocity
|
88
|
+
end
|
89
|
+
def muzzle_rotation_func &rotation
|
90
|
+
@muzzle_rotation = rotation
|
91
|
+
end
|
92
|
+
def shoot? target = nil
|
93
|
+
target.nil? ? true : target.distance_from(self) < self.range
|
94
|
+
end
|
95
|
+
def shoot target = nil
|
96
|
+
return unless shoot? target
|
97
|
+
|
98
|
+
sometimes :loading, self.shooting_rate do
|
99
|
+
projectile = self.shot.shoot_from self
|
100
|
+
projectile.rotation = self.muzzle_rotation
|
101
|
+
projectile.speed = self.muzzle_velocity * projectile.velocity + self.speed
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if self.kind_of?(::Targeting)
|
106
|
+
def target *targets
|
107
|
+
return if targets.empty?
|
108
|
+
target = acquire *targets
|
109
|
+
shoot target
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/lib/traits/shot.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Use this if you need a closest targeting mechanism.
|
2
|
+
#
|
3
|
+
module Targeting
|
4
|
+
|
5
|
+
module Closest
|
6
|
+
|
7
|
+
# Returns the closest target of all targets in the fire arc.
|
8
|
+
#
|
9
|
+
# TODO fire arc
|
10
|
+
#
|
11
|
+
def acquire *targets
|
12
|
+
closest = nil
|
13
|
+
lowest_distance = nil
|
14
|
+
|
15
|
+
targets.each do |target|
|
16
|
+
distance = (target.position - self.position).length
|
17
|
+
next if lowest_distance && distance > lowest_distance
|
18
|
+
lowest_distance = distance
|
19
|
+
closest = target
|
20
|
+
end
|
21
|
+
|
22
|
+
closest
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
module Targeting; end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
module Turnable
|
4
|
+
|
5
|
+
Left = :turn_left
|
6
|
+
Right = :turn_right
|
7
|
+
|
8
|
+
def self.included base
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO meta
|
13
|
+
#
|
14
|
+
module ClassMethods
|
15
|
+
def turn_speed amount
|
16
|
+
amount = amount.to_f / 2
|
17
|
+
define_method :turn_speed do
|
18
|
+
amount
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
#
|
25
|
+
def turn_speed
|
26
|
+
0.5 # Default turn speed
|
27
|
+
end
|
28
|
+
|
29
|
+
# Turns the thing left, depending on turn speed.
|
30
|
+
#
|
31
|
+
def turn_left
|
32
|
+
self.rotation -= self.turn_speed / (SUBSTEPS**2)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Turns the thing right, depending on turn speed.
|
36
|
+
#
|
37
|
+
def turn_right
|
38
|
+
self.rotation += self.turn_speed / (SUBSTEPS**2)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# A moveable has a shape, speed etc.
|
2
|
+
#
|
3
|
+
# TODO moveable should only have active components, like accelerate etc. Positioning etc. should go to Thing.
|
4
|
+
#
|
5
|
+
class Moveable < Thing
|
6
|
+
|
7
|
+
Accelerate = :accelerate
|
8
|
+
Left = :move_left
|
9
|
+
Right = :move_right
|
10
|
+
Up = :move_up
|
11
|
+
Down = :move_down
|
12
|
+
Backwards = :backwards
|
13
|
+
# Jump = :jump
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Initial setting.
|
18
|
+
#
|
19
|
+
def friction amount = nil, &block
|
20
|
+
to_execute = block_given? ? block : lambda { amount }
|
21
|
+
InitializerHooks.register self do
|
22
|
+
self.friction = to_execute[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def velocity amount = nil, &block
|
26
|
+
to_execute = block_given? ? block : lambda { amount }
|
27
|
+
InitializerHooks.register self do
|
28
|
+
self.velocity = to_execute[]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def rotation amount = nil, &block
|
32
|
+
to_execute = block_given? ? block : lambda { amount }
|
33
|
+
InitializerHooks.register self do
|
34
|
+
self.rotation = to_execute[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
# Directly set the position of our Moveable using a vector.
|
41
|
+
#
|
42
|
+
def warp vector
|
43
|
+
@shape.body.p = vector
|
44
|
+
end
|
45
|
+
|
46
|
+
# Directly set the position of our Moveable.
|
47
|
+
#
|
48
|
+
def warp_to x, y
|
49
|
+
@shape.body.p = CP::Vec2.new(x, y)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Directly set the position of our Moveable.
|
53
|
+
#
|
54
|
+
def position= position
|
55
|
+
@shape.body.p = position
|
56
|
+
end
|
57
|
+
def position
|
58
|
+
@shape.body.p
|
59
|
+
end
|
60
|
+
|
61
|
+
# Directly set the torque of our Moveable.
|
62
|
+
#
|
63
|
+
def torque= torque
|
64
|
+
@shape.body.t = torque
|
65
|
+
end
|
66
|
+
def torque
|
67
|
+
@shape.body.t
|
68
|
+
end
|
69
|
+
|
70
|
+
# Directly set the speed of our Moveable.
|
71
|
+
#
|
72
|
+
def speed= v
|
73
|
+
@shape.body.v = v
|
74
|
+
end
|
75
|
+
def speed
|
76
|
+
@shape.body.v
|
77
|
+
end
|
78
|
+
def current_speed
|
79
|
+
# TODO use built-in function
|
80
|
+
#
|
81
|
+
Math.sqrt(speed.x**2 + speed.y**2)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Directly set the rotation of our Moveable.
|
85
|
+
#
|
86
|
+
def rotation= rotation
|
87
|
+
@shape.body.a = rotation % (2*Math::PI)
|
88
|
+
end
|
89
|
+
def rotation
|
90
|
+
@shape.body.a
|
91
|
+
end
|
92
|
+
def drawing_rotation
|
93
|
+
self.rotation.radians_to_gosu
|
94
|
+
end
|
95
|
+
def rotation_vector
|
96
|
+
@shape.body.a.radians_to_vec2
|
97
|
+
end
|
98
|
+
|
99
|
+
def friction= friction
|
100
|
+
@shape.u = friction
|
101
|
+
end
|
102
|
+
def friction
|
103
|
+
@shape.u
|
104
|
+
end
|
105
|
+
|
106
|
+
# Length is the vector length you want.
|
107
|
+
#
|
108
|
+
# Note: radians_to_vec2
|
109
|
+
#
|
110
|
+
def rotation_as_vector length
|
111
|
+
rotation = -self.rotation + Math::PI / 2
|
112
|
+
x = Math.sin rotation
|
113
|
+
y = Math.cos rotation
|
114
|
+
total_length = Math.sqrt(x**2 + y**2)
|
115
|
+
multiplier = length / total_length
|
116
|
+
CP::Vec2.new(x * multiplier, y * multiplier)
|
117
|
+
end
|
118
|
+
|
119
|
+
def move
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# Methods for controls.
|
124
|
+
#
|
125
|
+
def accelerate strength = 1
|
126
|
+
self.speed += self.rotation_vector * strength/SUBSTEPS
|
127
|
+
end
|
128
|
+
def backwards strength = 1
|
129
|
+
accelerate -0.5*strength
|
130
|
+
end
|
131
|
+
def move_left strength = 1
|
132
|
+
self.speed += CP::Vec2.new(-strength.to_f/SUBSTEPS, 0)
|
133
|
+
end
|
134
|
+
def move_right strength = 1
|
135
|
+
self.speed += CP::Vec2.new(strength.to_f/SUBSTEPS, 0)
|
136
|
+
end
|
137
|
+
def move_up strength = 1
|
138
|
+
self.speed += CP::Vec2.new(0, -strength.to_f/SUBSTEPS)
|
139
|
+
end
|
140
|
+
def move_down strength = 1
|
141
|
+
self.speed += CP::Vec2.new(0, strength.to_f/SUBSTEPS)
|
142
|
+
end
|
143
|
+
# def jump strength = 100
|
144
|
+
# self.speed += CP::Vec2.new(0, -strength.to_f/SUBSTEPS) if self.current_speed <= 1
|
145
|
+
# end
|
146
|
+
|
147
|
+
# Movement rules
|
148
|
+
#
|
149
|
+
# Note: Call in method move.
|
150
|
+
#
|
151
|
+
def bounce_off_border_x elasticity = 1.0
|
152
|
+
if position.x > window.screen_width || position.x < 0
|
153
|
+
shape.body.v.x = -shape.body.v.x.to_f*elasticity
|
154
|
+
end
|
155
|
+
end
|
156
|
+
def bounce_off_border_y elasticity = 1.0
|
157
|
+
if position.y > window.screen_height || position.y < 0
|
158
|
+
shape.body.v.y = -shape.body.v.y.to_f*elasticity
|
159
|
+
end
|
160
|
+
end
|
161
|
+
def bounce_off_border elasticity = 1.0
|
162
|
+
bounce_off_border_x elasticity
|
163
|
+
bounce_off_border_y elasticity
|
164
|
+
end
|
165
|
+
def wrap_around_border_x
|
166
|
+
if position.x > window.screen_width
|
167
|
+
position.x -= window.screen_width
|
168
|
+
elsif position.x < 0
|
169
|
+
position.x += window.screen_width
|
170
|
+
end
|
171
|
+
end
|
172
|
+
def wrap_around_border_y
|
173
|
+
if position.y > window.screen_height
|
174
|
+
position.y -= window.screen_height
|
175
|
+
elsif position.y < 0
|
176
|
+
position.y += window.screen_height
|
177
|
+
end
|
178
|
+
end
|
179
|
+
def wrap_around_border
|
180
|
+
wrap_around_border_x
|
181
|
+
wrap_around_border_y
|
182
|
+
end
|
183
|
+
def obey_gravity
|
184
|
+
self.speed += window.gravity_vector
|
185
|
+
end
|
186
|
+
def on_hitting_x
|
187
|
+
yield if block_given? && position.x > window.screen_width || position.x < 0
|
188
|
+
end
|
189
|
+
def on_hitting_y
|
190
|
+
yield if block_given? && position.y > window.screen_height || position.y < 0
|
191
|
+
end
|
192
|
+
def rotate_towards_velocity
|
193
|
+
self.rotation = self.speed.to_angle
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class ShortLived < Moveable
|
2
|
+
|
3
|
+
def initialize window
|
4
|
+
super window
|
5
|
+
|
6
|
+
threaded self.lifetime do
|
7
|
+
self.destroy!
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def lifetime lifetime = nil, &block
|
14
|
+
block = lambda { lifetime } unless block_given?
|
15
|
+
define_method :lifetime, &block
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/units/thing.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
class Thing
|
2
|
+
|
3
|
+
include VectorUtilities
|
4
|
+
include InitializerHooks
|
5
|
+
include ItIsA
|
6
|
+
|
7
|
+
attr_writer :layer
|
8
|
+
attr_reader :window, :shape
|
9
|
+
|
10
|
+
# Every thing knows the window it is attached to.
|
11
|
+
#
|
12
|
+
def initialize window
|
13
|
+
@window = window
|
14
|
+
self.destroyed = false
|
15
|
+
after_initialize
|
16
|
+
end
|
17
|
+
|
18
|
+
# Default layer is Layer::Players.
|
19
|
+
#
|
20
|
+
def layer
|
21
|
+
@layer || Layer::Players
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
# TODO Move to module.
|
27
|
+
#
|
28
|
+
def image path, *args
|
29
|
+
InitializerHooks.register self do
|
30
|
+
@image = Gosu::Image.new self.window, File.join(Resources.root, path), *args
|
31
|
+
end
|
32
|
+
define_method :image do
|
33
|
+
@image
|
34
|
+
end
|
35
|
+
end
|
36
|
+
def sequenced_image path, width, height, frequency = 10, &block
|
37
|
+
InitializerHooks.register self do
|
38
|
+
@image_sequence_started = Time.now
|
39
|
+
@image = Gosu::Image::load_tiles self.window, File.join(Resources.root, path), width, height, false
|
40
|
+
end
|
41
|
+
# divider = 1000 / frequency
|
42
|
+
define_method :image do
|
43
|
+
@image[(block ? block : lambda { (Time.now - @image_sequence_started)*frequency % @image.size })[]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@@form_shape_class_mapping = { :circle => CP::Shape::Circle }
|
47
|
+
def shape form
|
48
|
+
form_shape_class_mapping = @@form_shape_class_mapping
|
49
|
+
InitializerHooks.append self do
|
50
|
+
shape_class = form_shape_class_mapping[form]
|
51
|
+
raise "Shape #{form} does not exist." unless shape_class
|
52
|
+
@shape = shape_class.new(CP::Body.new(self.mass, self.moment), self.radius, CP::Vec2.new(0.0, 0.0))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
def mass amount
|
56
|
+
define_method :mass do
|
57
|
+
amount || 1.0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
def moment amount
|
61
|
+
define_method :moment do
|
62
|
+
amount || 1.0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def radius amount
|
66
|
+
define_method :radius do
|
67
|
+
amount || 10.0
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def collision_type type
|
72
|
+
to_execute = lambda do |shape|
|
73
|
+
shape.collision_type = type
|
74
|
+
end
|
75
|
+
InitializerHooks.append self do
|
76
|
+
# Ensure @shape exists
|
77
|
+
#
|
78
|
+
InitializerHooks.append self.class, &to_execute unless @shape
|
79
|
+
to_execute[@shape]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def layer layer
|
84
|
+
InitializerHooks.register self do
|
85
|
+
self.layer = layer
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Plays a random sound of the given sounds.
|
90
|
+
#
|
91
|
+
def plays *paths
|
92
|
+
InitializerHooks.register self do
|
93
|
+
sound = Gosu::Sample.new self.window, File.join(Resources.root, paths[rand(paths.size)])
|
94
|
+
sound.play
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# Do something threaded.
|
101
|
+
#
|
102
|
+
# Default is: Instantly, in the next step.
|
103
|
+
#
|
104
|
+
def threaded time = 1, &code
|
105
|
+
self.window.threaded time, &code
|
106
|
+
end
|
107
|
+
|
108
|
+
# Destroy this thing.
|
109
|
+
#
|
110
|
+
attr_writer :destroyed
|
111
|
+
def destroyed?
|
112
|
+
@destroyed
|
113
|
+
end
|
114
|
+
def destroy!
|
115
|
+
return if self.destroyed?
|
116
|
+
self.window.unregister self
|
117
|
+
self.destroyed = true
|
118
|
+
end
|
119
|
+
|
120
|
+
# Some things you can only do every x time units.
|
121
|
+
#
|
122
|
+
# Example:
|
123
|
+
# sometimes :loading, self.frequency do
|
124
|
+
# projectile = self.shot.shoot_from self
|
125
|
+
# projectile.rotation = self.muzzle_rotation[target]
|
126
|
+
# projectile.speed = self.muzzle_velocity[target] * projectile.velocity
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
def sometimes variable, units = 1, &block
|
130
|
+
name = :"@#{variable}"
|
131
|
+
return if instance_variable_get(name)
|
132
|
+
instance_variable_set name, true
|
133
|
+
result = block.call
|
134
|
+
threaded units.to_i do
|
135
|
+
self.instance_variable_set name, false
|
136
|
+
end
|
137
|
+
result
|
138
|
+
end
|
139
|
+
|
140
|
+
# Add this thing to a space.
|
141
|
+
#
|
142
|
+
# Note: Adds the body and the shape.
|
143
|
+
#
|
144
|
+
def add_to space
|
145
|
+
space.add_body @shape.body
|
146
|
+
space.add_shape @shape
|
147
|
+
end
|
148
|
+
|
149
|
+
# TODO
|
150
|
+
#
|
151
|
+
def draw
|
152
|
+
self.image.draw_rot self.position.x, self.position.y, self.layer, self.drawing_rotation
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/waves.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
class Waves
|
4
|
+
|
5
|
+
#
|
6
|
+
#
|
7
|
+
def initialize window
|
8
|
+
@window = window
|
9
|
+
@waves = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
#
|
14
|
+
def add amount, type, time
|
15
|
+
@waves[time] ||= []
|
16
|
+
@waves[time] << [amount, type]
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
#
|
21
|
+
def check time
|
22
|
+
if wave? time
|
23
|
+
types = @waves[time]
|
24
|
+
types.each { |amount, type| amount.times { @window.randomly_add type } }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
#
|
30
|
+
def wave? time
|
31
|
+
!@waves[time].nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|