petermorphose 2.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.
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