tank_island 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/bin/tank_island +30 -0
- data/lib/entities/box.rb +28 -0
- data/lib/entities/bullet.rb +26 -0
- data/lib/entities/camera.rb +113 -0
- data/lib/entities/components/ai/gun.rb +114 -0
- data/lib/entities/components/ai/tank_chasing_state.rb +30 -0
- data/lib/entities/components/ai/tank_fighting_state.rb +47 -0
- data/lib/entities/components/ai/tank_fleeing_state.rb +50 -0
- data/lib/entities/components/ai/tank_motion_fsm.rb +102 -0
- data/lib/entities/components/ai/tank_motion_state.rb +84 -0
- data/lib/entities/components/ai/tank_navigating_state.rb +34 -0
- data/lib/entities/components/ai/tank_roaming_state.rb +83 -0
- data/lib/entities/components/ai/tank_stuck_state.rb +45 -0
- data/lib/entities/components/ai/vision.rb +109 -0
- data/lib/entities/components/ai_input.rb +70 -0
- data/lib/entities/components/box_graphics.rb +39 -0
- data/lib/entities/components/bullet_graphics.rb +13 -0
- data/lib/entities/components/bullet_physics.rb +65 -0
- data/lib/entities/components/bullet_sounds.rb +15 -0
- data/lib/entities/components/component.rb +32 -0
- data/lib/entities/components/damage_graphics.rb +20 -0
- data/lib/entities/components/explosion_graphics.rb +43 -0
- data/lib/entities/components/explosion_sounds.rb +16 -0
- data/lib/entities/components/health.rb +87 -0
- data/lib/entities/components/player_input.rb +100 -0
- data/lib/entities/components/player_sounds.rb +16 -0
- data/lib/entities/components/powerup_graphics.rb +22 -0
- data/lib/entities/components/powerup_sounds.rb +15 -0
- data/lib/entities/components/tank_graphics.rb +46 -0
- data/lib/entities/components/tank_health.rb +32 -0
- data/lib/entities/components/tank_physics.rb +179 -0
- data/lib/entities/components/tank_sounds.rb +43 -0
- data/lib/entities/components/tree_graphics.rb +69 -0
- data/lib/entities/damage.rb +26 -0
- data/lib/entities/explosion.rb +34 -0
- data/lib/entities/game_object.rb +54 -0
- data/lib/entities/hud.rb +79 -0
- data/lib/entities/map.rb +183 -0
- data/lib/entities/object_pool.rb +59 -0
- data/lib/entities/powerups/fire_rate_powerup.rb +14 -0
- data/lib/entities/powerups/health_powerup.rb +12 -0
- data/lib/entities/powerups/powerup.rb +35 -0
- data/lib/entities/powerups/powerup_respawn_queue.rb +23 -0
- data/lib/entities/powerups/repair_powerup.rb +14 -0
- data/lib/entities/powerups/tank_speed_powerup.rb +14 -0
- data/lib/entities/radar.rb +62 -0
- data/lib/entities/score_display.rb +35 -0
- data/lib/entities/tank.rb +64 -0
- data/lib/entities/tree.rb +18 -0
- data/lib/game_states/demo_state.rb +49 -0
- data/lib/game_states/game_state.rb +27 -0
- data/lib/game_states/menu_state.rb +60 -0
- data/lib/game_states/pause_state.rb +61 -0
- data/lib/game_states/play_state.rb +119 -0
- data/lib/misc/axis_aligned_bounding_box.rb +33 -0
- data/lib/misc/game_window.rb +30 -0
- data/lib/misc/names.rb +13 -0
- data/lib/misc/quad_tree.rb +91 -0
- data/lib/misc/stats.rb +55 -0
- data/lib/misc/stereo_sample.rb +96 -0
- data/lib/misc/utils.rb +145 -0
- data/media/armalite_rifle.ttf +0 -0
- data/media/boxes_barrels.json +60 -0
- data/media/boxes_barrels.png +0 -0
- data/media/bullet.png +0 -0
- data/media/c_dot.png +0 -0
- data/media/country_field.png +0 -0
- data/media/crash.ogg +0 -0
- data/media/damage1.png +0 -0
- data/media/damage2.png +0 -0
- data/media/damage3.png +0 -0
- data/media/damage4.png +0 -0
- data/media/decor.json +516 -0
- data/media/decor.png +0 -0
- data/media/decor.psd +0 -0
- data/media/explosion.mp3 +0 -0
- data/media/explosion.png +0 -0
- data/media/fire.mp3 +0 -0
- data/media/ground.json +492 -0
- data/media/ground.png +0 -0
- data/media/ground_units.json +900 -0
- data/media/ground_units.png +0 -0
- data/media/menu_music.mp3 +0 -0
- data/media/metal_interaction2.wav +0 -0
- data/media/names.txt +279 -0
- data/media/pickups.json +68 -0
- data/media/pickups.png +0 -0
- data/media/powerup.mp3 +0 -0
- data/media/respawn.wav +0 -0
- data/media/tank_driving.mp3 +0 -0
- data/media/top_secret.ttf +0 -0
- data/media/trees.png +0 -0
- data/media/trees_packed.json +388 -0
- data/media/trees_packed.png +0 -0
- data/media/water.png +0 -0
- data/spec/misc/aabb_spec.rb +85 -0
- data/spec/misc/quad_tree_spec.rb +137 -0
- data/tank_island.gemspec +29 -0
- metadata +223 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
class TankFightingState < TankMotionState
|
2
|
+
def initialize(object, vision)
|
3
|
+
super
|
4
|
+
@object = object
|
5
|
+
@vision = vision
|
6
|
+
end
|
7
|
+
|
8
|
+
def update
|
9
|
+
change_direction if should_change_direction?
|
10
|
+
if substate_expired?
|
11
|
+
rand > 0.1 ? drive : wait
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def change_direction
|
16
|
+
change = case rand(0..100)
|
17
|
+
when 0..20
|
18
|
+
-45
|
19
|
+
when 20..40
|
20
|
+
45
|
21
|
+
when 40..60
|
22
|
+
90
|
23
|
+
when 60..80
|
24
|
+
-90
|
25
|
+
when 80..90
|
26
|
+
135
|
27
|
+
when 90..100
|
28
|
+
-135
|
29
|
+
end
|
30
|
+
@object.physics.change_direction(
|
31
|
+
@object.direction + change)
|
32
|
+
@changed_direction_at = Gosu.milliseconds
|
33
|
+
@will_keep_direction_for = turn_time
|
34
|
+
end
|
35
|
+
|
36
|
+
def wait_time
|
37
|
+
rand(50..300)
|
38
|
+
end
|
39
|
+
|
40
|
+
def drive_time
|
41
|
+
rand(5000..10000)
|
42
|
+
end
|
43
|
+
|
44
|
+
def turn_time
|
45
|
+
rand(300..3000)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class TankFleeingState < TankMotionState
|
2
|
+
MAX_FLEE_TIME = 15 * 1000 # 15 seconds
|
3
|
+
|
4
|
+
def initialize(object, vision, gun)
|
5
|
+
super(object, vision)
|
6
|
+
@object = object
|
7
|
+
@vision = vision
|
8
|
+
@gun = gun
|
9
|
+
end
|
10
|
+
|
11
|
+
def can_flee?
|
12
|
+
return true unless @started_fleeing
|
13
|
+
Gosu.milliseconds - @started_fleeing < MAX_FLEE_TIME
|
14
|
+
end
|
15
|
+
|
16
|
+
def enter
|
17
|
+
@started_fleeing ||= Gosu.milliseconds
|
18
|
+
end
|
19
|
+
|
20
|
+
def update
|
21
|
+
change_direction if should_change_direction?
|
22
|
+
drive
|
23
|
+
end
|
24
|
+
|
25
|
+
def change_direction
|
26
|
+
closest_powerup = @vision.closest_powerup(
|
27
|
+
RepairPowerup, HealthPowerup)
|
28
|
+
if closest_powerup
|
29
|
+
angle = Utils.angle_between(
|
30
|
+
@object.x, @object.y,
|
31
|
+
closest_powerup.x, closest_powerup.y)
|
32
|
+
@object.physics.change_direction(
|
33
|
+
angle - angle % 45)
|
34
|
+
else
|
35
|
+
@object.physics.change_direction(
|
36
|
+
180 + @gun.desired_gun_angle -
|
37
|
+
@gun.desired_gun_angle % 45)
|
38
|
+
end
|
39
|
+
@changed_direction_at = Gosu.milliseconds
|
40
|
+
@will_keep_direction_for = turn_time
|
41
|
+
end
|
42
|
+
|
43
|
+
def drive_time
|
44
|
+
10000
|
45
|
+
end
|
46
|
+
|
47
|
+
def turn_time
|
48
|
+
rand(300..600)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class TankMotionFSM
|
2
|
+
STATE_CHANGE_DELAY = 500
|
3
|
+
LOCATION_CHECK_DELAY = 5000
|
4
|
+
|
5
|
+
def initialize(object, vision, gun)
|
6
|
+
@object = object
|
7
|
+
@vision = vision
|
8
|
+
@gun = gun
|
9
|
+
@roaming_state = TankRoamingState.new(object, vision)
|
10
|
+
@fighting_state = TankFightingState.new(object, vision)
|
11
|
+
@fleeing_state = TankFleeingState.new(object, vision, gun)
|
12
|
+
@chasing_state = TankChasingState.new(object, vision, gun)
|
13
|
+
@stuck_state = TankStuckState.new(object, vision, gun)
|
14
|
+
@navigating_state = TankNavigatingState.new(object, vision)
|
15
|
+
set_state(@roaming_state)
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_collision(with)
|
19
|
+
@current_state.on_collision(with)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_damage(amount)
|
23
|
+
if @current_state == @roaming_state
|
24
|
+
set_state(@fighting_state)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def draw(viewport)
|
29
|
+
if $debug
|
30
|
+
@image && @image.draw(
|
31
|
+
@object.x - @image.width / 2,
|
32
|
+
@object.y + @object.graphics.height / 2 -
|
33
|
+
@image.height, 100)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update
|
38
|
+
choose_state
|
39
|
+
@current_state.update
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_state(state)
|
43
|
+
return unless state
|
44
|
+
return if state == @current_state
|
45
|
+
@last_state_change = Gosu.milliseconds
|
46
|
+
@current_state = state
|
47
|
+
state.enter
|
48
|
+
if $debug
|
49
|
+
@image = Gosu::Image.from_text(
|
50
|
+
$window, state.class.to_s,
|
51
|
+
Gosu.default_font_name, 18)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def choose_state
|
56
|
+
unless @vision.can_go_forward?
|
57
|
+
unless @current_state == @stuck_state
|
58
|
+
set_state(@navigating_state)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# Keep unstucking itself for a while
|
62
|
+
change_delay = STATE_CHANGE_DELAY
|
63
|
+
if @current_state == @stuck_state
|
64
|
+
change_delay *= 5
|
65
|
+
end
|
66
|
+
now = Gosu.milliseconds
|
67
|
+
return unless now - @last_state_change > change_delay
|
68
|
+
if @last_location_update.nil?
|
69
|
+
@last_location_update = now
|
70
|
+
@last_location = @object.location
|
71
|
+
end
|
72
|
+
if now - @last_location_update > LOCATION_CHECK_DELAY
|
73
|
+
unless @last_location.nil? || @current_state.waiting?
|
74
|
+
if Utils.distance_between(*@last_location, *@object.location) < 20
|
75
|
+
set_state(@stuck_state)
|
76
|
+
@stuck_state.stuck_at = @object.location
|
77
|
+
return
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@last_location_update = now
|
81
|
+
@last_location = @object.location
|
82
|
+
end
|
83
|
+
if @gun.target
|
84
|
+
if @object.health.health > 40
|
85
|
+
if @gun.distance_to_target > BulletPhysics::MAX_DIST
|
86
|
+
new_state = @chasing_state
|
87
|
+
else
|
88
|
+
new_state = @fighting_state
|
89
|
+
end
|
90
|
+
else
|
91
|
+
if @fleeing_state.can_flee?
|
92
|
+
new_state = @fleeing_state
|
93
|
+
else
|
94
|
+
new_state = @fighting_state
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
new_state = @roaming_state
|
99
|
+
end
|
100
|
+
set_state(new_state)
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class TankMotionState
|
2
|
+
def initialize(object, vision)
|
3
|
+
@object = object
|
4
|
+
@vision = vision
|
5
|
+
end
|
6
|
+
|
7
|
+
def enter
|
8
|
+
# Override if necessary
|
9
|
+
end
|
10
|
+
|
11
|
+
def change_direction
|
12
|
+
# Override
|
13
|
+
end
|
14
|
+
|
15
|
+
def wait_time
|
16
|
+
# Override and return a number
|
17
|
+
end
|
18
|
+
|
19
|
+
def drive_time
|
20
|
+
# Override and return a number
|
21
|
+
end
|
22
|
+
|
23
|
+
def turn_time
|
24
|
+
# Override and return a number
|
25
|
+
end
|
26
|
+
|
27
|
+
def update
|
28
|
+
# Override
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait
|
32
|
+
@sub_state = :waiting
|
33
|
+
@started_waiting = Gosu.milliseconds
|
34
|
+
@will_wait_for = wait_time
|
35
|
+
@object.throttle_down = false
|
36
|
+
end
|
37
|
+
|
38
|
+
def waiting?
|
39
|
+
@sub_state == :waiting
|
40
|
+
end
|
41
|
+
|
42
|
+
def drive
|
43
|
+
@sub_state = :driving
|
44
|
+
@started_driving = Gosu.milliseconds
|
45
|
+
@will_drive_for = drive_time
|
46
|
+
@object.throttle_down = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def should_change_direction?
|
50
|
+
return true unless @vision.can_go_forward?
|
51
|
+
return true unless @changed_direction_at
|
52
|
+
Gosu.milliseconds - @changed_direction_at >
|
53
|
+
@will_keep_direction_for
|
54
|
+
end
|
55
|
+
|
56
|
+
def substate_expired?
|
57
|
+
now = Gosu.milliseconds
|
58
|
+
case @sub_state
|
59
|
+
when :waiting
|
60
|
+
true if now - @started_waiting > @will_wait_for
|
61
|
+
when :driving
|
62
|
+
true if now - @started_driving > @will_drive_for
|
63
|
+
else
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_collision(with)
|
69
|
+
change = case rand(0..100)
|
70
|
+
when 0..30
|
71
|
+
-90
|
72
|
+
when 30..60
|
73
|
+
90
|
74
|
+
when 60..70
|
75
|
+
135
|
76
|
+
when 80..90
|
77
|
+
-135
|
78
|
+
else
|
79
|
+
180
|
80
|
+
end
|
81
|
+
@object.physics.change_direction(
|
82
|
+
@object.direction + change)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class TankNavigatingState < TankMotionState
|
2
|
+
def initialize(object, vision)
|
3
|
+
@object = object
|
4
|
+
@vision = vision
|
5
|
+
end
|
6
|
+
|
7
|
+
def update
|
8
|
+
change_direction if should_change_direction?
|
9
|
+
drive
|
10
|
+
end
|
11
|
+
|
12
|
+
def change_direction
|
13
|
+
closest_free_path = @vision.closest_free_path
|
14
|
+
if closest_free_path
|
15
|
+
@object.physics.change_direction(
|
16
|
+
Utils.angle_between(
|
17
|
+
@object.x, @object.y, *closest_free_path))
|
18
|
+
end
|
19
|
+
@changed_direction_at = Gosu.milliseconds
|
20
|
+
@will_keep_direction_for = turn_time
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_time
|
24
|
+
rand(10..100)
|
25
|
+
end
|
26
|
+
|
27
|
+
def drive_time
|
28
|
+
rand(1000..2000)
|
29
|
+
end
|
30
|
+
|
31
|
+
def turn_time
|
32
|
+
rand(300..1000)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class TankRoamingState < TankMotionState
|
2
|
+
def initialize(object, vision)
|
3
|
+
super
|
4
|
+
@object = object
|
5
|
+
@vision = vision
|
6
|
+
end
|
7
|
+
|
8
|
+
def update
|
9
|
+
change_direction if should_change_direction?
|
10
|
+
if substate_expired?
|
11
|
+
rand > 0.2 ? drive : wait
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def required_powerups
|
16
|
+
required = []
|
17
|
+
health = @object.health.health
|
18
|
+
if @object.fire_rate_modifier < 2 && health > 50
|
19
|
+
required << FireRatePowerup
|
20
|
+
end
|
21
|
+
if @object.speed_modifier < 1.5 && health > 50
|
22
|
+
required << TankSpeedPowerup
|
23
|
+
end
|
24
|
+
if health < 100
|
25
|
+
required << RepairPowerup
|
26
|
+
end
|
27
|
+
if health < 190
|
28
|
+
required << HealthPowerup
|
29
|
+
end
|
30
|
+
required
|
31
|
+
end
|
32
|
+
|
33
|
+
def change_direction
|
34
|
+
closest_powerup = @vision.closest_powerup(
|
35
|
+
*required_powerups)
|
36
|
+
if closest_powerup
|
37
|
+
@seeking_powerup = true
|
38
|
+
angle = Utils.angle_between(
|
39
|
+
@object.x, @object.y,
|
40
|
+
closest_powerup.x, closest_powerup.y)
|
41
|
+
@object.physics.change_direction(
|
42
|
+
angle - angle % 45)
|
43
|
+
# Go away from inaccessable powerup
|
44
|
+
unless @vision.can_go_forward?
|
45
|
+
@seeking_powerup = false
|
46
|
+
@object.physics.change_direction(
|
47
|
+
@object.direction + 180)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@seeking_powerup = false
|
51
|
+
change = case rand(0..100)
|
52
|
+
when 0..30
|
53
|
+
-45
|
54
|
+
when 30..60
|
55
|
+
45
|
56
|
+
when 60..80
|
57
|
+
90
|
58
|
+
when 80..100
|
59
|
+
-90
|
60
|
+
end
|
61
|
+
@object.physics.change_direction(
|
62
|
+
@object.direction + change)
|
63
|
+
end
|
64
|
+
@changed_direction_at = Gosu.milliseconds
|
65
|
+
@will_keep_direction_for = turn_time
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_time
|
69
|
+
rand(50..400)
|
70
|
+
end
|
71
|
+
|
72
|
+
def drive_time
|
73
|
+
rand(1000..3000)
|
74
|
+
end
|
75
|
+
|
76
|
+
def turn_time
|
77
|
+
if @seeking_powerup
|
78
|
+
rand(100..300)
|
79
|
+
else
|
80
|
+
rand(1000..3000)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class TankStuckState < TankMotionState
|
2
|
+
attr_accessor :stuck_at
|
3
|
+
def initialize(object, vision, gun)
|
4
|
+
super(object, vision)
|
5
|
+
@object = object
|
6
|
+
@vision = vision
|
7
|
+
@gun = gun
|
8
|
+
end
|
9
|
+
|
10
|
+
def update
|
11
|
+
change_direction if should_change_direction?
|
12
|
+
drive
|
13
|
+
end
|
14
|
+
|
15
|
+
def change_direction
|
16
|
+
closest_free_path = @vision.closest_free_path_away_from(
|
17
|
+
@stuck_at)
|
18
|
+
if closest_free_path
|
19
|
+
@object.physics.change_direction(
|
20
|
+
Utils.angle_between(
|
21
|
+
@object.x, @object.y, *closest_free_path))
|
22
|
+
else
|
23
|
+
if @object.health.health > 50 && rand > 0.9
|
24
|
+
@object.shoot(*Utils.point_at_distance(
|
25
|
+
*@object.location,
|
26
|
+
@object.gun_angle,
|
27
|
+
150))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@changed_direction_at = Gosu.milliseconds
|
31
|
+
@will_keep_direction_for = turn_time
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait_time
|
35
|
+
rand(10..100)
|
36
|
+
end
|
37
|
+
|
38
|
+
def drive_time
|
39
|
+
rand(1000..2000)
|
40
|
+
end
|
41
|
+
|
42
|
+
def turn_time
|
43
|
+
rand(1000..2000)
|
44
|
+
end
|
45
|
+
end
|