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.
- data/COPYING +3 -0
- data/README.md +31 -0
- data/bin/petermorphose +3 -0
- data/levels/flgr_Der_Alkohol_und_seine_Folgen.pml +447 -0
- data/levels/jr_Am_Tempel_des_Harlow-Karlow.pml +413 -0
- data/levels/jr_Auf_der_Flucht.pml +456 -0
- data/levels/jr_Die_zwei_Baeume.pml +447 -0
- data/levels/jr_Feuertauchen.pml +343 -0
- data/levels/jr_Gemuetlicher_Aufstieg.pml +329 -0
- data/levels/jr_Gruenhuegelshausen.pml +423 -0
- data/levels/jr_Gruselgrotte.pml +421 -0
- data/levels/jr_Hoch_hinaus.pml +265 -0
- data/levels/jr_Vom_Ozean_in_die_Traufe.pml +342 -0
- data/levels/jr_Weg_durchs_Feuer.pml +544 -0
- data/levels/sl_Heimweg_zu_Henk.pml +307 -0
- data/media/arg1.wav +0 -0
- data/media/arg2.wav +0 -0
- data/media/arrow_hit.wav +0 -0
- data/media/blocker_break.wav +0 -0
- data/media/bow.wav +0 -0
- data/media/break1.wav +0 -0
- data/media/break2.wav +0 -0
- data/media/buttons.png +0 -0
- data/media/collect_ammo.wav +0 -0
- data/media/collect_freeze.wav +0 -0
- data/media/collect_health.wav +0 -0
- data/media/collect_key.wav +0 -0
- data/media/collect_points.wav +0 -0
- data/media/collect_star.wav +0 -0
- data/media/danger.png +0 -0
- data/media/death.wav +0 -0
- data/media/dialogs.bmp +0 -0
- data/media/door1.wav +0 -0
- data/media/door2.wav +0 -0
- data/media/eat.wav +0 -0
- data/media/effects.bmp +0 -0
- data/media/enemies.bmp +0 -0
- data/media/explosion.wav +0 -0
- data/media/game.ogg +0 -0
- data/media/gui.bmp +0 -0
- data/media/help1.wav +0 -0
- data/media/help2.wav +0 -0
- data/media/jump.wav +0 -0
- data/media/lava.wav +0 -0
- data/media/lever.wav +0 -0
- data/media/menu.ogg +0 -0
- data/media/morph.wav +0 -0
- data/media/player.bmp +0 -0
- data/media/player_arg.wav +0 -0
- data/media/shshsh.wav +0 -0
- data/media/skies.png +0 -0
- data/media/slime1.wav +0 -0
- data/media/slime2.wav +0 -0
- data/media/slime3.wav +0 -0
- data/media/stairs.wav +0 -0
- data/media/stairs_steps.wav +0 -0
- data/media/stuff.bmp +0 -0
- data/media/sword_whoosh.wav +0 -0
- data/media/tiles.bmp +0 -0
- data/media/title.png +0 -0
- data/media/title_dark.png +0 -0
- data/media/turbo.wav +0 -0
- data/media/water1.wav +0 -0
- data/media/water2.wav +0 -0
- data/media/whoosh.wav +0 -0
- data/media/whoosh_back.wav +0 -0
- data/media/won.bmp +0 -0
- data/media/yippie.wav +0 -0
- data/src/const.rb +301 -0
- data/src/en.yml +62 -0
- data/src/gosu-preview.rb +116 -0
- data/src/helpers/audio.rb +9 -0
- data/src/helpers/graphics.rb +24 -0
- data/src/helpers/input.rb +28 -0
- data/src/ini_file.rb +25 -0
- data/src/level_info.rb +63 -0
- data/src/localization.rb +19 -0
- data/src/main.rb +86 -0
- data/src/map.rb +136 -0
- data/src/objects/collectible_object.rb +174 -0
- data/src/objects/effect_object.rb +120 -0
- data/src/objects/game_object.rb +363 -0
- data/src/objects/living_object.rb +657 -0
- data/src/objects/object_def.rb +45 -0
- data/src/script.rb +207 -0
- data/src/states/credits.rb +24 -0
- data/src/states/game.rb +463 -0
- data/src/states/help.rb +268 -0
- data/src/states/level_selection.rb +43 -0
- data/src/states/menu.rb +45 -0
- data/src/states/options.rb +81 -0
- data/src/states/state.rb +11 -0
- data/src/states/title.rb +17 -0
- data/src/states/victory.rb +20 -0
- 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
|