petermorphose 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/COPYING +3 -0
  2. data/README.md +31 -0
  3. data/bin/petermorphose +3 -0
  4. data/levels/flgr_Der_Alkohol_und_seine_Folgen.pml +447 -0
  5. data/levels/jr_Am_Tempel_des_Harlow-Karlow.pml +413 -0
  6. data/levels/jr_Auf_der_Flucht.pml +456 -0
  7. data/levels/jr_Die_zwei_Baeume.pml +447 -0
  8. data/levels/jr_Feuertauchen.pml +343 -0
  9. data/levels/jr_Gemuetlicher_Aufstieg.pml +329 -0
  10. data/levels/jr_Gruenhuegelshausen.pml +423 -0
  11. data/levels/jr_Gruselgrotte.pml +421 -0
  12. data/levels/jr_Hoch_hinaus.pml +265 -0
  13. data/levels/jr_Vom_Ozean_in_die_Traufe.pml +342 -0
  14. data/levels/jr_Weg_durchs_Feuer.pml +544 -0
  15. data/levels/sl_Heimweg_zu_Henk.pml +307 -0
  16. data/media/arg1.wav +0 -0
  17. data/media/arg2.wav +0 -0
  18. data/media/arrow_hit.wav +0 -0
  19. data/media/blocker_break.wav +0 -0
  20. data/media/bow.wav +0 -0
  21. data/media/break1.wav +0 -0
  22. data/media/break2.wav +0 -0
  23. data/media/buttons.png +0 -0
  24. data/media/collect_ammo.wav +0 -0
  25. data/media/collect_freeze.wav +0 -0
  26. data/media/collect_health.wav +0 -0
  27. data/media/collect_key.wav +0 -0
  28. data/media/collect_points.wav +0 -0
  29. data/media/collect_star.wav +0 -0
  30. data/media/danger.png +0 -0
  31. data/media/death.wav +0 -0
  32. data/media/dialogs.bmp +0 -0
  33. data/media/door1.wav +0 -0
  34. data/media/door2.wav +0 -0
  35. data/media/eat.wav +0 -0
  36. data/media/effects.bmp +0 -0
  37. data/media/enemies.bmp +0 -0
  38. data/media/explosion.wav +0 -0
  39. data/media/game.ogg +0 -0
  40. data/media/gui.bmp +0 -0
  41. data/media/help1.wav +0 -0
  42. data/media/help2.wav +0 -0
  43. data/media/jump.wav +0 -0
  44. data/media/lava.wav +0 -0
  45. data/media/lever.wav +0 -0
  46. data/media/menu.ogg +0 -0
  47. data/media/morph.wav +0 -0
  48. data/media/player.bmp +0 -0
  49. data/media/player_arg.wav +0 -0
  50. data/media/shshsh.wav +0 -0
  51. data/media/skies.png +0 -0
  52. data/media/slime1.wav +0 -0
  53. data/media/slime2.wav +0 -0
  54. data/media/slime3.wav +0 -0
  55. data/media/stairs.wav +0 -0
  56. data/media/stairs_steps.wav +0 -0
  57. data/media/stuff.bmp +0 -0
  58. data/media/sword_whoosh.wav +0 -0
  59. data/media/tiles.bmp +0 -0
  60. data/media/title.png +0 -0
  61. data/media/title_dark.png +0 -0
  62. data/media/turbo.wav +0 -0
  63. data/media/water1.wav +0 -0
  64. data/media/water2.wav +0 -0
  65. data/media/whoosh.wav +0 -0
  66. data/media/whoosh_back.wav +0 -0
  67. data/media/won.bmp +0 -0
  68. data/media/yippie.wav +0 -0
  69. data/src/const.rb +301 -0
  70. data/src/en.yml +62 -0
  71. data/src/gosu-preview.rb +116 -0
  72. data/src/helpers/audio.rb +9 -0
  73. data/src/helpers/graphics.rb +24 -0
  74. data/src/helpers/input.rb +28 -0
  75. data/src/ini_file.rb +25 -0
  76. data/src/level_info.rb +63 -0
  77. data/src/localization.rb +19 -0
  78. data/src/main.rb +86 -0
  79. data/src/map.rb +136 -0
  80. data/src/objects/collectible_object.rb +174 -0
  81. data/src/objects/effect_object.rb +120 -0
  82. data/src/objects/game_object.rb +363 -0
  83. data/src/objects/living_object.rb +657 -0
  84. data/src/objects/object_def.rb +45 -0
  85. data/src/script.rb +207 -0
  86. data/src/states/credits.rb +24 -0
  87. data/src/states/game.rb +463 -0
  88. data/src/states/help.rb +268 -0
  89. data/src/states/level_selection.rb +43 -0
  90. data/src/states/menu.rb +45 -0
  91. data/src/states/options.rb +81 -0
  92. data/src/states/state.rb +11 -0
  93. data/src/states/title.rb +17 -0
  94. data/src/states/victory.rb +20 -0
  95. metadata +188 -0
@@ -0,0 +1,174 @@
1
+ class CollectibleObject < GameObject
2
+ def update
3
+ if not [ID_EDIBLE_FISH, ID_EDIBLE_FISH_2].include? pmid and not xdata.nil? and xdata[1, 1] == '1' then
4
+ fall
5
+ check_tile
6
+ end
7
+
8
+ # Burn in lava
9
+ if y + ObjectDef[pmid].rect.bottom > game.map.lava_pos then
10
+ game.cast_fx 2, 2, 0, x, y, 16, 16, 0, -3, 1
11
+ kill
12
+ emit_sound :shshsh
13
+ game.lose("Du hast verloren, weil eine Gefangene verbrannt ist.") if pmid == ID_CAROLIN
14
+ end
15
+
16
+ if pmid == ID_CAROLIN and game.frame % 20 == 0 and rand(4) == 0 then
17
+ emit_sound "help#{rand(2) + 1}"
18
+ end
19
+
20
+ if [ID_EDIBLE_FISH, ID_EDIBLE_FISH_2].include? pmid then
21
+ if not in_water? then
22
+ if xdata.is_a? String and xdata[0, 1] == '1' then
23
+ fall
24
+ check_tile
25
+ end
26
+ else
27
+ if pmid == ID_EDIBLE_FISH then
28
+ self.x -= 2
29
+ self.pmid = ID_EDIBLE_FISH_2 if blocked? DIR_LEFT
30
+ else
31
+ self.x += 2
32
+ self.pmid = ID_EDIBLE_FISH if blocked? DIR_RIGHT
33
+ end
34
+ game.create_object ID_FX_WATER_BUBBLE, x, y - 3, nil if rand(30) == 0
35
+ end
36
+ end
37
+
38
+ # Cannot be collected...
39
+ return if game.player.action >= ACT_DEAD
40
+
41
+ if collide_with? game.player.rect(2, 2) then
42
+ case pmid
43
+ when ID_CAROLIN then
44
+ game.cast_objects ID_FX_FLYING_CHAIN, 8, 0, -1, 3, rect(1, -1)
45
+ fc = game.create_object ID_FX_FLYING_CAROLIN, x, y, nil
46
+ fc.vx, fc.vy = -7 + rand(15), -15,
47
+ sound(:yippie).play
48
+ game.score += 100
49
+ kill
50
+ when ID_KEY then
51
+ sound(:collect_key).play
52
+ game.player.emit_text "#{t ObjectDef[pmid].name}!"
53
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
54
+ game.score += 2
55
+ game.keys += 1
56
+ kill
57
+ when ID_EDIBLE_FISH, ID_EDIBLE_FISH_2 then
58
+ sound(:collect_health).play
59
+ sound(:eat).play
60
+ game.player.emit_text '+1'
61
+ game.score += 2
62
+ game.player.life += 1
63
+ kill
64
+ when ID_MORE_TIME, ID_MORE_TIME_2 then
65
+ return if game.player.pmid == ID_PLAYER
66
+ sound(:morph).play
67
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
68
+ if pmid == ID_MORE_TIME then
69
+ game.player.emit_text "+1 #{t 'Sekunde'}"
70
+ game.score += 5
71
+ game.time_left += 30
72
+ else
73
+ game.player.emit_text "+3,5 #{t 'Sekunden'}"
74
+ game.score += 10
75
+ game.time_left += 110
76
+ end
77
+ kill
78
+ when ID_HEALTH, ID_HEALTH_2 then
79
+ if pmid == ID_HEALTH then amount = 1 else amount = 4 end
80
+ sound(:collect_health).play
81
+ game.player.emit_text '+1'
82
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
83
+ game.score += amount
84
+ game.player.life += amount
85
+ kill
86
+ when ID_STAR..ID_STAR_3 then
87
+ sound(:collect_star).play Gosu::random(0.5, 0.7), Gosu::random(0.9, 1.1)
88
+ game.score += 2
89
+ game.stars += 1
90
+ if game.stars < game.stars_goal then
91
+ game.player.emit_text t("Noch %d") % (game.stars_goal - game.stars)
92
+ elsif game.stars == game.stars_goal then
93
+ game.player.emit_text t("Genug gesammelt!")
94
+ end
95
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
96
+ kill
97
+ when ID_POINTS..ID_POINTS_MAX then
98
+ sound(:collect_points).play
99
+ game.player.emit_text "*#{ObjectDef[pmid].life}*"
100
+ game.cast_objects ID_FX_SPARKLE, 3, 0, 0, 0, rect
101
+ game.score += ObjectDef[pmid].life
102
+ kill
103
+ when ID_MUNITION_GUN, ID_MUNITION_GUN_2 then
104
+ sound(:collect_ammo).play
105
+ amount = 1 + (pmid - ID_MUNITION_GUN) * 2
106
+ game.player.emit_text "+#{amount}"
107
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
108
+ game.score += amount
109
+ game.ammo += amount
110
+ kill
111
+ when ID_MUNITION_BOMBER, ID_MUNITION_BOMBER_2 then
112
+ sound(:collect_ammo).play
113
+ amount = 1 + (pmid - ID_MUNITION_BOMBER) * 2
114
+ game.player.emit_text "+#{amount}"
115
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
116
+ game.score += amount
117
+ game.bombs += amount
118
+ kill
119
+ when ID_COOKIE then
120
+ sound(:eat).play
121
+ game.cast_objects ID_FX_SPARKLE, 1, 0, 0, 0, rect
122
+ emit_text t(xdata.split('|')[1] || 'Der Keks war leer...'), :slow
123
+ game.score += 10
124
+ kill
125
+ when ID_SLOW_DOWN then
126
+ if game.map.lava_mode == 0 and game.map.lava_speed == 48 then
127
+ elsif game.map.lava_mode == 1 and game.map.lava_speed == 1 then
128
+ game.map.lava_mode = 0
129
+ game.map.lava_speed = 2
130
+ elsif game.map.lava_mode == 0 then
131
+ game.map.lava_speed += 1
132
+ else
133
+ game.map.lava_speed -= 1
134
+ end
135
+ sound(:collect_freeze).play
136
+ game.player.emit_text t('Lava verlangsamt!')
137
+ game.cast_objects ID_FX_SPARKLE, 3, 0, 0, 0, rect
138
+ kill
139
+ when ID_CRYSTAL then
140
+ sound(:collect_freeze).play
141
+ game.player.emit_text t('Lava angehalten!'), :slow
142
+ game.map.lava_time_left += 80
143
+ game.cast_objects ID_FX_SPARKLE, 4, 0, 0, 0, rect
144
+ kill
145
+ when ID_MORPH_FIGHTER..ID_MORPH_MAX then
146
+ target_id = ID_PLAYER_FIGHTER + pmid - ID_MORPH_FIGHTER
147
+ if game.player.pmid != target_id then
148
+ sound(:morph).play
149
+ game.player.pmid = target_id
150
+ game.player.emit_text "#{t ObjectDef[game.player.pmid].name}!", :slow
151
+ game.player.action = ACT_JUMP
152
+ game.cast_fx 8, 0, 0, x, y, 24, 24, 0, -1, 4
153
+ game.time_left = ObjectDef[game.player.pmid].life
154
+ game.cast_objects ID_FX_SPARKLE, 5, 0, 0, 0, rect
155
+ kill
156
+ end
157
+ when ID_SPEED, ID_JUMP, ID_FLY then
158
+ sound(:morph).play
159
+ game.player.emit_text "#{t ObjectDef[pmid].name}!", :slow
160
+ case pmid
161
+ when ID_SPEED then game.speed_time_left = 330
162
+ when ID_JUMP then game.jump_time_left = 330
163
+ when ID_FLY then game.fly_time_left = 88
164
+ end
165
+ game.cast_fx 8, 0, 0, game.player.x, game.player.y - 10, 24, 24, 0, -1, 4
166
+ game.cast_objects ID_FX_SPARKLE, 2, 0, 0, 0, rect
167
+ kill
168
+ when ID_SEAMINE then
169
+ game.explosion x, y, 50, false
170
+ kill
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,120 @@
1
+ class EffectObject < GameObject
2
+ def initialize *args
3
+ super
4
+
5
+ @phase = 0
6
+ end
7
+
8
+ def self.images
9
+ @images ||= Gosu::Image.load_tiles 'media/effects.bmp', -7, -7
10
+ end
11
+
12
+ def draw
13
+ case pmid
14
+ when ID_FX_SMOKE then
15
+ EffectObject.images[[ 0, @phase - 1].max].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(128), :additive
16
+ when ID_FX_FLAME then
17
+ EffectObject.images[[ 7, @phase + 6].max].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(160), :additive
18
+ when ID_FX_SPARK then
19
+ EffectObject.images[[14, @phase + 13].max].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(128), :additive
20
+ when ID_FX_BUBBLE then
21
+ EffectObject.images[[21, @phase + 20].max].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(192), :additive
22
+ when ID_FX_RICOCHET then
23
+ EffectObject.images[19 + xdata.to_i].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha([255 - @phase * 3, 0].max)
24
+ when ID_FX_LINE then
25
+ EffectObject.images[28].draw x, y - 11 - game.view_pos, Z_EFFECTS, xdata.to_f / EffectObject.images.first.width, 1, alpha(255 - @phase), :additive
26
+ when ID_FX_BLOCKER_PARTS then
27
+ EffectObject.images[29].draw_rot x, y - game.view_pos, Z_EFFECTS, x * 10, 0.5, 0.5, 1, 1, alpha(255 - @phase), :additive
28
+ when ID_FX_BREAK, ID_FX_BREAK_2 then
29
+ EffectObject.images[30 + (ID_FX_BREAK_2 - pmid)].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(@phase) # TODO :subtractive
30
+ when ID_FX_BREAKING_PARTS then
31
+ EffectObject.images[32].draw_rot x, y - game.view_pos, Z_EFFECTS, x * 10, 0.5, 0.5, 1, 1, alpha(255 - @phase)
32
+ when ID_FX_BLOOD then
33
+ EffectObject.images[33].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(250 - @phase)
34
+ when ID_FX_FIRE then
35
+ EffectObject.images[34].draw x, y - game.view_pos, Z_EFFECTS, 1, 1, alpha(@phase)
36
+ when ID_FX_FLYING_CAROLIN then
37
+ EffectObject.images[35].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS
38
+ when ID_FX_FLYING_CHAIN then
39
+ EffectObject.images[36].draw_rot x, y - game.view_pos, Z_EFFECTS, x * 10 % 360, 0.5, 0.5, 1, 1, alpha(255 - @phase)
40
+ when ID_FX_FLYING_BLOB then
41
+ EffectObject.images[37].draw_rot x, y - game.view_pos, Z_EFFECTS, x * 10, 0.5, 0.5, 1, 1, alpha(255 - @phase)
42
+ when ID_FX_WATER_BUBBLE then
43
+ EffectObject.images[46].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(100 + rand(29))
44
+ when ID_FX_WATER then
45
+ EffectObject.images[47].draw_rot x, y - game.view_pos, Z_EFFECTS, x * 10, 0.5, 0.5, 1, 1, alpha(255 - @phase)
46
+ when ID_FX_SPARKLE then
47
+ EffectObject.images[48].draw x - 11, y - 11 - game.view_pos, Z_EFFECTS, 1, 1, alpha(255 - @phase), :additive
48
+ when ID_FX_TEXT, ID_FX_SLOW_TEXT then
49
+ # TODO: Force text inside portion @ x=0..576
50
+ draw_centered_string xdata, x, y - 7 - game.view_pos, 255 - @phase
51
+ end
52
+ end
53
+
54
+ def update
55
+ case pmid
56
+ when ID_FX_SMOKE, ID_FX_FLAME then
57
+ self.x += vx
58
+ self.y += vy
59
+ @phase += 1
60
+ kill if @phase > 7
61
+ when ID_FX_SPARK then
62
+ self.x += vx
63
+ self.y += vy
64
+ @phase += 1 if game.frame % 3 == 0
65
+ kill if @phase > 5
66
+ when ID_FX_BUBBLE then
67
+ self.x += vx
68
+ self.y = game.map.lava_pos - 12
69
+ @phase += 1 if game.frame % 2 == 0
70
+ kill if @phase > 7
71
+ when ID_FX_BLOCKER_PARTS then
72
+ self.x += vx
73
+ self.y += vy
74
+ self.vy += 1
75
+ @phase += 15
76
+ kill if @phase == 255
77
+ when ID_FX_WATER then
78
+ self.x += vx
79
+ self.vy += 1
80
+ self.y += vy
81
+ @phase += 25
82
+ kill if @phase == 250
83
+ when ID_FX_BREAK, ID_FX_BREAK_2 then
84
+ @phase += 15
85
+ if @phase == 255 then
86
+ game.map[x / TILE_SIZE, y / TILE_SIZE] = TILE_HOLE + ((y / TILE_SIZE % 2 + x / TILE_SIZE) % 2) * 16
87
+ game.cast_objects ID_FX_BREAKING_PARTS, 20, 0, 5, 2,
88
+ ObjectDef::Rect.new(x / (TILE_SIZE/2) * (TILE_SIZE/2), y / (TILE_SIZE/2) * (TILE_SIZE/2), 24, 24)
89
+ emit_sound "break#{rand(2) + 1}"
90
+ kill
91
+ return
92
+ end
93
+ when ID_FX_FIRE then
94
+ @phase += 15
95
+ if @phase == 255 then
96
+ game.map[x / TILE_SIZE, y / TILE_SIZE] = TILE_BIG_BLOCKER_BROKEN
97
+ game.explosion x + 12, y + 12, 30, true
98
+ kill
99
+ end
100
+ when ID_FX_BREAKING_PARTS, ID_FX_TEXT, ID_FX_RICOCHET, ID_FX_LINE,
101
+ ID_FX_BLOOD, ID_FX_FLYING_CHAIN, ID_FX_FLYING_BLOB, ID_FX_SLOW_TEXT then
102
+ self.x += vx
103
+ self.y += vy
104
+ @phase += 15
105
+ @phase -= 10 if pmid == ID_FX_SLOW_TEXT
106
+ kill if @phase == 255
107
+ when ID_FX_FLYING_CAROLIN then
108
+ self.x += vx
109
+ self.y += vy
110
+ kill if y < game.view_pos - HEIGHT
111
+ when ID_FX_WATER_BUBBLE then
112
+ self.y -= 1
113
+ self.x += 1 - rand(3) if rand(4) == 0
114
+ kill if not in_water?
115
+ when ID_FX_SPARKLE then
116
+ @phase += 25
117
+ kill if @phase == 250
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,363 @@
1
+ class NilClass
2
+ def existence_as_int
3
+ 0
4
+ end
5
+ end
6
+
7
+ class GameObject
8
+ attr_reader :game
9
+ attr_accessor :pmid, :x, :y, :xdata, :vx, :vy
10
+
11
+ def marked?
12
+ @marked
13
+ end
14
+
15
+ def existence_as_int
16
+ marked? ? 0 : 1
17
+ end
18
+
19
+ def kill
20
+ 0.upto(game.obj_vars.size) do |i|
21
+ game.obj_vars[i] = nil if game.obj_vars[i] == self
22
+ end
23
+ @marked = true
24
+ end
25
+
26
+ def emit_text string, speed = :fast
27
+ if speed == :slow then id = ID_FX_SLOW_TEXT else id = ID_FX_TEXT end
28
+ game.create_object(id, x, y - 10, string).vy = -1
29
+ end
30
+
31
+ def initialize game, pmid, x, y, xdata
32
+ @game, @pmid, @x, @y, @xdata = game, pmid, x, y, xdata
33
+ @vx = @vy = 0
34
+ @last_frame_in_water = in_water?
35
+ end
36
+
37
+ def emit_sound name
38
+ game.emit_sound name, y
39
+ end
40
+
41
+ ALL_WATER_TILES = (TILE_WATER..TILE_WATER_4).to_a + [TILE_WATER_5]
42
+ def in_water?
43
+ ALL_WATER_TILES.include? game.map[x / TILE_SIZE, y / TILE_SIZE]
44
+ end
45
+
46
+ def update
47
+ if [ID_FIREWALL_1, ID_FIREWALL_2, ID_FIRE].include? pmid then
48
+ if y + ObjectDef[pmid].rect.bottom - 11 > game.map.lava_pos then
49
+ game.cast_fx 4, 4, 0, x, y, 16, 16, 0, -3, 1
50
+ kill
51
+ emit_sound(:shshsh)
52
+ else
53
+ self.xdata = ((game.frame * 7.5).to_i % 256 - 128).abs.to_s
54
+ rect = self.rect
55
+ game.objects.each do |obj|
56
+ if obj.pmid <= ID_LIVING_MAX and obj.pmid != ID_ENEMY_BERSERKER and rect.include? obj then
57
+ obj.hit
58
+ game.cast_fx rand(3), rand(2), 0, x, y, 12, 12, 0, 0, 2
59
+ end
60
+ end
61
+ return
62
+ end
63
+ end
64
+
65
+ # Hint arrows
66
+ kill if pmid == ID_HELP_ARROW and rect(10, 20).include? game.player
67
+
68
+ # Fish
69
+ if pmid == ID_FISH or pmid == ID_FISH_2 then
70
+ if not in_water? then
71
+ fall
72
+ check_tile
73
+ else
74
+ if pmid == ID_FISH then
75
+ self.x -= 2
76
+ self.pmid = ID_FISH_2 if blocked? DIR_LEFT
77
+ else
78
+ self.x += 2
79
+ self.pmid = ID_FISH if blocked? DIR_RIGHT
80
+ end
81
+ if rand(30) == 0 then
82
+ game.create_object ID_FX_WATER_BUBBLE, x, y - 3, nil
83
+ end
84
+ end
85
+ end
86
+
87
+ # Fused bomb
88
+ if pmid == ID_FUSING_BOMB then
89
+ # Count up and possibly explode
90
+ time = (xdata || 1).to_i + 1
91
+ self.xdata = time.to_s
92
+ if time >= 25 then
93
+ kill
94
+ game.explosion x, y, 50, true
95
+ return
96
+ end
97
+
98
+ # Also explode on touching an enemy
99
+ game.objects.each do |obj|
100
+ if obj.pmid >= ID_ENEMY and obj.pmid <= ID_ENEMY_MAX and obj.collide_with? rect(1, 1) and
101
+ not [ACT_DEAD, ACT_INV_UP, ACT_INV_UP].include? obj.action then
102
+ obj.hurt true
103
+ kill
104
+ game.explosion x, y, 50, true
105
+ return
106
+ end
107
+ end
108
+
109
+ fall
110
+ check_tile
111
+ end
112
+
113
+ # Rocks fall down
114
+ if (ID_TRASH..ID_TRASH_4).include? pmid then
115
+ fall
116
+ check_tile
117
+ end
118
+
119
+ # Get roasted by lava
120
+ if y + ObjectDef[pmid].rect.bottom > game.map.lava_pos then
121
+ game.cast_fx 4, 4, 0, x, y, 16, 16, 0, -3, 1
122
+ kill
123
+ emit_sound :shshsh
124
+ end
125
+ end
126
+
127
+ def draw
128
+ @@stuff_images ||= Gosu::Image.load_tiles 'media/stuff.bmp', -16, -3
129
+ color = 0xffffffff
130
+ mode = :default
131
+
132
+ if [ID_FIREWALL_1, ID_FIREWALL_2, ID_FIRE].include? pmid then
133
+ color = alpha(127 + (xdata && xdata.to_i || 128))
134
+ mode = :additive
135
+ elsif pmid == ID_HELP_ARROW then
136
+ color = alpha(127 + (game.frame / 8 % 2) * 64)
137
+ end
138
+ @@stuff_images[pmid - ID_OTHER_OBJECTS_MIN].draw x - 11, y - 11 - game.view_pos, 0, 1, 1, color, mode
139
+ if pmid == ID_CAROLIN then
140
+ if xdata and xdata.length > 2 then
141
+ name = xdata[2..-1]
142
+ else
143
+ name = 'Carolin'
144
+ end
145
+ draw_centered_string name, x, y + 16 - game.view_pos, 128
146
+ end
147
+ end
148
+
149
+ def fall
150
+ if in_water? and not @last_frame_in_water then
151
+ game.cast_objects ID_FX_WATER, 5, -vx / 2, -5, 3, rect(1, 1)
152
+ emit_sound "water#{rand(2) + 1}"
153
+ end
154
+ @last_frame_in_water = in_water?
155
+
156
+ # Gravity
157
+ self.vy += 1 if (pmid > ID_PLAYER_MAX or game.fly_time_left == 0)# and not in_water?
158
+
159
+ if in_water? then
160
+ self.vy -= 1 if vy > +1
161
+ self.vy += 1 if vy < -1
162
+ self.vx -= 1 if vx > +2
163
+ self.vx += 1 if vx < -2
164
+ end
165
+
166
+ if pmid <= ID_PLAYER_MAX and not blocked? DIR_DOWN then
167
+ if vx.abs < 5 then
168
+ self.vx = (self.vx / 2.0).to_i
169
+ else
170
+ self.vx = (self.vx / 1.03).to_i if game.frame % 3 == 0
171
+ end
172
+ end
173
+
174
+ # Velocity is limited to +- TILE_SIZE
175
+ self.vx = [[vx, -TILE_SIZE].max, TILE_SIZE].min
176
+ self.vy = [[vy, -TILE_SIZE].max, TILE_SIZE].min
177
+
178
+ if vy > 0 then
179
+ vy.times do
180
+ break if blocked? DIR_DOWN
181
+ self.y += 1
182
+ end
183
+ elsif vy < 0 then
184
+ vy.abs.times do
185
+ break if blocked? DIR_UP
186
+ self.y -= 1
187
+ end
188
+ end
189
+
190
+ if blocked? DIR_DOWN then
191
+ if is_a? LivingObject and vx > 10 and not [ACT_DEAD, ACT_ACTION_1, ACT_ACTION_2].include? action then
192
+ self.action = ACT_IMPACT_1 + [vy - 11, 4].min
193
+ end
194
+ self.vy = 0
195
+ # Conveyor belts are neither implemented nor used
196
+ # if (Data.Map.Tile(PosX + Data.Defs[ID].Rect.Left, PosY + Data.Defs[ID].Rect.Top + Data.Defs[ID].Rect.Bottom + 1) = Tile_PullLeft) and (not Blocked(Dir_Left)) then Dec(PosX);
197
+ # if (Data.Map.Tile(PosX + Data.Defs[ID].Rect.Left, PosY + Data.Defs[ID].Rect.Top + Data.Defs[ID].Rect.Bottom + 1) = Tile_PullRight) and (not Blocked(Dir_Right)) then Inc(PosX);
198
+ # if (Data.Map.Tile(PosX + Data.Defs[ID].Rect.Left + Data.Defs[ID].Rect.Right, PosY + Data.Defs[ID].Rect.Top + Data.Defs[ID].Rect.Bottom + 1) = Tile_PullLeft) and (not Blocked(Dir_Left)) then Dec(PosX);
199
+ # if (Data.Map.Tile(PosX + Data.Defs[ID].Rect.Left + Data.Defs[ID].Rect.Right, PosY + Data.Defs[ID].Rect.Top + Data.Defs[ID].Rect.Bottom + 1) = Tile_PullRight) and (not Blocked(Dir_Right)) then Inc(PosX);
200
+ end
201
+
202
+ if blocked? DIR_UP and game.fly_time_left == 0 then
203
+ self.vy = 1
204
+ #self.vx /= +2
205
+ end
206
+
207
+ if vx < 0 then
208
+ vx.abs.times do
209
+ if blocked? DIR_LEFT then
210
+ self.vx = 0
211
+ break
212
+ else
213
+ self.x -= 1
214
+ end
215
+ end
216
+ elsif vx > 0 then
217
+ vx.times do
218
+ if blocked? DIR_RIGHT then
219
+ self.vx = 0
220
+ break
221
+ else
222
+ self.x += 1
223
+ end
224
+ end
225
+ end
226
+
227
+ if (pmid > ID_PLAYER_MAX or game.fly_time_left == 0) and blocked? DIR_DOWN then
228
+ self.vx -= 1 if vx > 0
229
+ self.vx -= 1 if vx > +1
230
+ self.vx -= 1 if pmid <= ID_ENEMY_MAX and vx > +ObjectDef[pmid].speed
231
+ self.vx += 1 if vx < 0
232
+ self.vx += 1 if vx < -1
233
+ self.vx += 1 if pmid <= ID_ENEMY_MAX and vx < -ObjectDef[pmid].speed
234
+
235
+ if game.map[x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE].between? TILE_SLIME, TILE_SLIME_3 then
236
+ (4 + game.frame % 2).times do
237
+ self.vx -= 1 if vx > 0
238
+ self.vx += 1 if vx < 0
239
+ end
240
+ (2 + game.frame % 2).times do
241
+ self.vx -= 1 if pmid <= ID_ENEMY_MAX and vx > +ObjectDef[pmid].speed
242
+ self.vx += 1 if pmid <= ID_ENEMY_MAX and vx < -ObjectDef[pmid].speed
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ def fling vx, vy, randomness, fixed, malign
249
+ # TODO the "randomness" here smells because it's only towards the bottom right?!
250
+
251
+ return if pmid <= ID_PLAYER_MAX and malign and (game.inv_time_left > 0 or pmid == ID_PLAYER_BERSERKER)
252
+
253
+ randomness += 1 # what
254
+
255
+ if fixed then
256
+ self.vx = vx + rand(randomness)
257
+ self.vy = vy + rand(randomness)
258
+ else
259
+ self.vx += vx + rand(randomness)
260
+ self.vy += vy + rand(randomness)
261
+ end
262
+ end
263
+
264
+ def stuck?
265
+ rect = self.rect
266
+ map.solid?(rect.left, rect.top) or map.solid?(rect.right, rect.top) or
267
+ map.solid?(rect.left, rect.bottom) or map.solid?(rect.right, rect.bottom)
268
+ end
269
+
270
+ def blocked? direction
271
+ rect = ObjectDef[pmid].rect
272
+ case direction
273
+ when DIR_LEFT then
274
+ game.map.solid? x + rect.left - 1, y + rect.top or
275
+ game.map.solid? x + rect.left - 1, y + rect.bottom
276
+ when DIR_RIGHT then
277
+ game.map.solid? x + rect.right + 1, y + rect.top or
278
+ game.map.solid? x + rect.right + 1, y + rect.bottom
279
+ when DIR_UP then
280
+ game.map.solid? x + rect.left, y + rect.top - 1 or
281
+ game.map.solid? x + rect.right, y + rect.top - 1
282
+ when DIR_DOWN then
283
+ game.map.solid? x + rect.left, y + rect.bottom + 1 or
284
+ game.map.solid? x + rect.right, y + rect.bottom + 1
285
+ end
286
+ end
287
+
288
+ def check_tile
289
+ case game.map[x / TILE_SIZE, y / TILE_SIZE]
290
+ when TILE_AIR_ROCKET_UP, TILE_AIR_ROCKET_UP_2, TILE_AIR_ROCKET_UP_3 then
291
+ emit_sound :turbo
292
+ fling 0, -21, 0, true, false
293
+ self.y -= 1 unless blocked? DIR_UP
294
+ self.x = x / 24 * 24 + 11
295
+ self.vx = direction.dir_to_vx if pmid.between? ID_ENEMY, ID_ENEMY_MAX
296
+ game.cast_fx 0, 0, 10, x, y, 24, 24, 0, -10, 1
297
+ when TILE_AIR_ROCKET_UP_LEFT then
298
+ emit_sound :turbo
299
+ self.y -= 1 unless blocked? DIR_UP
300
+ fling -10, -16, 0, true, false
301
+ self.y = y / TILE_SIZE + TILE_SIZE + 11
302
+ TILE_SIZE.times { if stuck? then self.vy -= 1 else break end }
303
+ game.cast_fx 0, 0, 10, x, y, 24, 24, -8, -8, 1
304
+ when TILE_AIR_ROCKET_UP_RIGHT then
305
+ emit_sound :turbo
306
+ self.y -= 1 unless blocked? DIR_UP
307
+ fling +10, -16, 0, true, false
308
+ self.y = y / TILE_SIZE + TILE_SIZE + 11
309
+ TILE_SIZE.times { if stuck? then self.vy -= 1 else break end }
310
+ game.cast_fx 0, 0, 10, x, y, 24, 24, +8, -8, 1
311
+ when TILE_AIR_ROCKET_LEFT then
312
+ emit_sound :turbo
313
+ fling -20, -3, 0, true, false
314
+ self.y = y / TILE_SIZE + TILE_SIZE + 11
315
+ TILE_SIZE.times { if stuck? then self.vy -= 1 else break end }
316
+ game.cast_fx 0, 0, 10, x, y, 24, 24, -10, 0, 1
317
+ when TILE_AIR_ROCKET_RIGHT then
318
+ emit_sound :turbo
319
+ fling +20, -3, 0, true, false
320
+ self.y = y / TILE_SIZE + TILE_SIZE + 11
321
+ TILE_SIZE.times { if stuck? then self.vy -= 1 else break end }
322
+ game.cast_fx 0, 0, 10, x, y, 24, 24, +10, 0, 1
323
+ when TILE_AIR_ROCKET_DOWN then
324
+ emit_sound :turbo
325
+ fling 0, 15, 0, true, false
326
+ self.y = y / TILE_SIZE + TILE_SIZE + 11
327
+ self.vx = direction.dir_to_vx if pmid.between? ID_ENEMY, ID_ENEMY_MAX
328
+ game.cast_fx 0, 0, 10, x, y, 24, 24, -10, 0, 1
329
+ when TILE_SLOW_ROCKET_UP then
330
+ game.cast_fx 0, 0, 1, x, y, 24, 24, 0, -2, 1
331
+ self.vy -= 4
332
+ if pmid.between? ID_ENEMY, ID_ENEMY_MAX then
333
+ self.vx = direction.dir_to_vx
334
+ else
335
+ self.vx /= 2
336
+ end
337
+ self.action = ACT_JUMP if pmid <= ID_LIVING_MAX
338
+ when TILE_SPIKES then
339
+ if pmid <= ID_LIVING_MAX and (y + ObjectDef[pmid].rect.bottom) % 24 > 8 then
340
+ hit
341
+ self.vx = 0
342
+ self.vy = -10
343
+ end
344
+ when TILE_SPIKES_TOP then
345
+ if pmid <= ID_LIVING_MAX and (y + ObjectDef[pmid].rect.bottom) % 24 < 14 then
346
+ hit
347
+ self.vx = 0
348
+ self.vy = 5
349
+ end
350
+ end
351
+ end
352
+
353
+ def collide_with? other
354
+ other = other.rect unless other.is_a? ObjectDef::Rect
355
+ rect.collide_with? other
356
+ end
357
+
358
+ def rect(extra_width = 0, extra_height = 0)
359
+ rect = ObjectDef[pmid].rect
360
+ ObjectDef::Rect.new(x + rect.left - extra_width, y + rect.top - extra_height,
361
+ rect.width + extra_width * 2, rect.height + extra_height * 2)
362
+ end
363
+ end