tank_island 1.0.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.
- 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
|