petermorphose 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,657 @@
|
|
1
|
+
class LivingObject < GameObject
|
2
|
+
attr_accessor :life, :action, :direction
|
3
|
+
|
4
|
+
def initialize *args
|
5
|
+
super
|
6
|
+
|
7
|
+
@life = ObjectDef[pmid].life
|
8
|
+
@action = 0
|
9
|
+
@direction = rand(2)
|
10
|
+
end
|
11
|
+
|
12
|
+
def act
|
13
|
+
return if game.frame == -1
|
14
|
+
|
15
|
+
case pmid
|
16
|
+
when ID_PLAYER_FIGHTER then
|
17
|
+
return if action > ACT_LAND
|
18
|
+
sound(:sword_whoosh).play
|
19
|
+
self.action = ACT_ACTION_1
|
20
|
+
tile_x = (x + 10 * direction.dir_to_vx) / TILE_SIZE
|
21
|
+
tile_y = y / TILE_SIZE
|
22
|
+
if game.map[tile_x, tile_y].between? TILE_BLOCKER, TILE_BLOCKER_3 then
|
23
|
+
if game.map[tile_x, tile_y] != TILE_BLOCKER_3 then
|
24
|
+
game.map[tile_x, tile_y] = TILE_BLOCKER_BROKEN
|
25
|
+
else
|
26
|
+
game.map[tile_x, tile_y] = TILE_BLOCKER_3_BROKEN
|
27
|
+
end
|
28
|
+
game.cast_objects ID_FX_BLOCKER_PARTS, 10, 0, -2, 5,
|
29
|
+
ObjectDef::Rect.new(tile_x * TILE_SIZE, tile_y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
30
|
+
sound(:blocker_break).play
|
31
|
+
end
|
32
|
+
when ID_PLAYER_GUN
|
33
|
+
self.action = ACT_ACTION_1 if action < ACT_LAND and game.ammo > 0
|
34
|
+
when ID_PLAYER_BOMBER
|
35
|
+
self.action = ACT_ACTION_1 if action < ACT_LAND and game.bombs > 0
|
36
|
+
end;
|
37
|
+
end
|
38
|
+
|
39
|
+
def busy?
|
40
|
+
not blocked? DIR_DOWN or not action.between? ACT_STAND, ACT_WALK_4
|
41
|
+
end
|
42
|
+
|
43
|
+
def draw
|
44
|
+
return if [ACT_INV_UP, ACT_INV_DOWN].include? action
|
45
|
+
|
46
|
+
case pmid
|
47
|
+
when ID_PLAYER..ID_PLAYER_BOMBER then
|
48
|
+
# Animated wings while flying
|
49
|
+
if game.fly_time_left > 0 then
|
50
|
+
color = alpha([game.fly_time_left * 2 + 16, 255].min)
|
51
|
+
case direction
|
52
|
+
when DIR_LEFT then
|
53
|
+
EffectObject.images[38 + (game.frame / 2) % 4].draw x - 18, y - 12 - game.view_pos, 0, 0.75, 1, color, :additive
|
54
|
+
EffectObject.images[42 + (game.frame / 2) % 4].draw x, y - 12 - game.view_pos, 0, 1.00, 1, color, :additive
|
55
|
+
when DIR_RIGHT then
|
56
|
+
EffectObject.images[38 + (game.frame / 2) % 4].draw x - 24, y - 12 - game.view_pos, 0, 1.00, 1, color, :additive
|
57
|
+
EffectObject.images[42 + (game.frame / 2) % 4].draw x, y - 12 - game.view_pos, 0, 0.75, 1, color, :additive
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# Transparency while invincible
|
61
|
+
if game.inv_time_left == 0 or type = ID_PLAYER_BERSERKER then
|
62
|
+
color = 0xffffffff
|
63
|
+
else
|
64
|
+
color = 0xa0ffffff
|
65
|
+
end
|
66
|
+
@@player_images ||= Gosu::Image.load_tiles 'media/player.bmp', -ACT_NUM, -10
|
67
|
+
@@player_images[ACT_NUM * (direction + (pmid - ID_PLAYER) * 2) + action].draw x - 11, y - 11 - game.view_pos, 0, 1, 1, color
|
68
|
+
when ID_ENEMY..ID_ENEMY_MAX then
|
69
|
+
if pmid == ID_ENEMY_GUN then
|
70
|
+
dir = x > game.player.x ? DIR_LEFT : DIR_RIGHT
|
71
|
+
else
|
72
|
+
dir = direction
|
73
|
+
end
|
74
|
+
@@enemy_images ||= Gosu::Image.load_tiles 'media/enemies.bmp', -ACT_NUM, -10
|
75
|
+
@@enemy_images[ACT_NUM * (dir + (pmid - ID_ENEMY) * 2) + action].draw x - 11, y - 11 - game.view_pos, 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def hurt from_explosion
|
80
|
+
return if action == ACT_DEAD or pmid == ID_PLAYER_BERSERKER
|
81
|
+
damage = 3
|
82
|
+
damage -= 1 if pmid == ID_PLAYER_FIGHTER
|
83
|
+
if pmid <= ID_PLAYER_MAX then
|
84
|
+
if game.inv_time_left > 0 then
|
85
|
+
damage = from_explosion ? 1 : 0
|
86
|
+
end
|
87
|
+
if from_explosion or game.inv_time_left == 0 then
|
88
|
+
game.inv_time_left = [25, game.inv_time_left].max
|
89
|
+
end
|
90
|
+
end
|
91
|
+
self.life -= damage
|
92
|
+
|
93
|
+
if life < 1 then
|
94
|
+
self.action = ACT_DEAD
|
95
|
+
self.life = 0
|
96
|
+
else
|
97
|
+
self.action = ACT_PAIN_1 + rand(2)
|
98
|
+
end
|
99
|
+
case pmid
|
100
|
+
when ID_PLAYER..ID_PLAYER_MAX then
|
101
|
+
sound(:player_arg).play
|
102
|
+
when ID_ENEMY..ID_ENEMY_MAX then
|
103
|
+
emit_sound "arg#{rand(2) + 1}"
|
104
|
+
# TODO or Death sound
|
105
|
+
end
|
106
|
+
|
107
|
+
# if Data.OptBlood = 1 then CastObjects(ID_FXBlood, ToDoDamage * 8, 0, 2, 2, Data.OptEffects, GetRect(0, 0), Data.ObjEffects);
|
108
|
+
|
109
|
+
if pmid == ID_ENEMY_BOMBER and action == ACT_DEAD then
|
110
|
+
kill
|
111
|
+
game.cast_fx 10, 30, 10, x, y, 10, 10, 0, -10, 5
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def hit
|
116
|
+
return if action == ACT_DEAD or pmid == ID_PLAYER_BERSERKER
|
117
|
+
return if pmid == ID_PLAYER_FIGHTER and rand(2) == 0
|
118
|
+
|
119
|
+
if pmid <= ID_PLAYER_MAX then
|
120
|
+
return if game.inv_time_left > 0
|
121
|
+
game.inv_time_left = [25, game.inv_time_left].max
|
122
|
+
end
|
123
|
+
|
124
|
+
self.life -= 1
|
125
|
+
|
126
|
+
if life < 1 then
|
127
|
+
self.action = ACT_DEAD
|
128
|
+
self.life = 0
|
129
|
+
else
|
130
|
+
self.action = ACT_PAIN_1 + rand(2)
|
131
|
+
end
|
132
|
+
case pmid
|
133
|
+
when ID_PLAYER..ID_PLAYER_MAX then
|
134
|
+
sound(:player_arg).play
|
135
|
+
when ID_ENEMY..ID_ENEMY_MAX then
|
136
|
+
emit_sound "arg#{rand(2) + 1}"
|
137
|
+
# TODO or Death sound
|
138
|
+
end
|
139
|
+
|
140
|
+
# if Data.OptBlood = 1 then CastObjects(ID_FXBlood, 8, 0, 2, 2, Data.OptEffects, GetRect(0, 0), Data.ObjEffects);
|
141
|
+
|
142
|
+
if pmid == ID_ENEMY_BOMBER and action == ACT_DEAD then
|
143
|
+
kill
|
144
|
+
game.cast_fx 10, 30, 10, x, y, 10, 10, 0, -10, 5
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def update
|
149
|
+
# Runterfallen
|
150
|
+
fall if action < ACT_INV_UP
|
151
|
+
|
152
|
+
# Roasted by lava
|
153
|
+
if y + ObjectDef[pmid].rect.bottom > game.map.lava_pos then
|
154
|
+
game.cast_fx 8, 8, 0, x, y, 16, 16, 0, -4, 1
|
155
|
+
kill
|
156
|
+
emit_sound :shshsh
|
157
|
+
if action != ACT_DEAD then
|
158
|
+
emit_sound :player_arg if pmid <= ID_PLAYER_MAX
|
159
|
+
emit_sound "arg#{rand(2) + 1}" if pmid.between? ID_ENEMY, ID_ENEMY_MAX
|
160
|
+
# TODO bloody: emit_sound :death if pmid.between? ID_ENEMY, ID_ENEMY_MAX
|
161
|
+
end
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
# Ascending staircase
|
166
|
+
except_open_doors = (0..TILE_STAIRS_UP_LOCKED).to_a + [TILE_STAIRS_DOWN_LOCKED]
|
167
|
+
if action == ACT_INV_UP then
|
168
|
+
emit_sound :stairs_steps if rand(8) == 0
|
169
|
+
2.times do
|
170
|
+
tile_below = game.map[x / TILE_SIZE, (y + 12) / TILE_SIZE]
|
171
|
+
tile_above = game.map[x / TILE_SIZE, (y - 9) / TILE_SIZE]
|
172
|
+
if except_open_doors.include? tile_below or except_open_doors.include? tile_above then
|
173
|
+
self.y -= 2
|
174
|
+
else
|
175
|
+
self.x = self.x / TILE_SIZE * TILE_SIZE + TILE_SIZE / 2 - 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
tile_below = game.map[x / TILE_SIZE, (y + 12) / TILE_SIZE]
|
179
|
+
tile_above = game.map[x / TILE_SIZE, (y - 9) / TILE_SIZE]
|
180
|
+
if except_open_doors.include? tile_below or except_open_doors.include? tile_above then
|
181
|
+
self.y -= 2
|
182
|
+
return
|
183
|
+
else
|
184
|
+
self.x = self.x / TILE_SIZE * TILE_SIZE + TILE_SIZE / 2
|
185
|
+
end
|
186
|
+
elsif action == ACT_INV_DOWN then
|
187
|
+
emit_sound :stairs_steps if rand(7) == 0
|
188
|
+
3.times do
|
189
|
+
tile_below = game.map[x / TILE_SIZE, (y + 12) / TILE_SIZE]
|
190
|
+
tile_above = game.map[x / TILE_SIZE, (y - 9) / TILE_SIZE]
|
191
|
+
if except_open_doors.include? tile_below or except_open_doors.include? tile_above then
|
192
|
+
self.y += 2
|
193
|
+
else
|
194
|
+
self.x = self.x / TILE_SIZE * TILE_SIZE + TILE_SIZE / 2 - 1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
tile_below = game.map[x / TILE_SIZE, (y + 12) / TILE_SIZE]
|
198
|
+
tile_above = game.map[x / TILE_SIZE, (y - 9) / TILE_SIZE]
|
199
|
+
if except_open_doors.include? tile_below or except_open_doors.include? tile_above then
|
200
|
+
self.y += 2
|
201
|
+
return
|
202
|
+
else
|
203
|
+
self.x = self.x / TILE_SIZE * TILE_SIZE + TILE_SIZE / 2
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
check_tile if action != ACT_DEAD
|
208
|
+
|
209
|
+
# Break tiles where due
|
210
|
+
rect = self.rect(0, 2)
|
211
|
+
break_floor rect.left, rect.bottom
|
212
|
+
break_floor rect.right, rect.bottom
|
213
|
+
|
214
|
+
return if action == ACT_DEAD
|
215
|
+
|
216
|
+
if in_water? then
|
217
|
+
game.create_object ID_FX_WATER_BUBBLE, x, y - 7, nil if rand(30) == 0
|
218
|
+
if pmid == ID_PLAYER_BERSERKER then
|
219
|
+
self.pmid = ID_PLAYER
|
220
|
+
game.cast_fx 8, 0, 0, x, y, 24, 24, 0, -1, 4
|
221
|
+
elsif pmid == ID_ENEMY_BERSERKER then
|
222
|
+
self.pmid = ID_ENEMY
|
223
|
+
game.cast_fx 8, 0, 0, x, y, 24, 24, 0, -1, 4
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Open doors
|
228
|
+
if pmid <= ID_PLAYER_MAX and game.keys > 0 then
|
229
|
+
# Left
|
230
|
+
tile_x, tile_y = (x + ObjectDef[pmid].rect.left - 1) / TILE_SIZE, y / TILE_SIZE
|
231
|
+
if game.map[tile_x, tile_y].between? TILE_CLOSED_DOOR, TILE_CLOSED_DOOR_3 then
|
232
|
+
game.map[tile_x, tile_y] -= (TILE_CLOSED_DOOR - TILE_OPEN_DOOR)
|
233
|
+
game.keys -= 1
|
234
|
+
sound("door#{rand(2) + 1}").play
|
235
|
+
end
|
236
|
+
|
237
|
+
# Right
|
238
|
+
tile_x, tile_y = (x + ObjectDef[pmid].rect.right + 1) / TILE_SIZE, y / TILE_SIZE
|
239
|
+
if game.map[tile_x, tile_y].between? TILE_CLOSED_DOOR, TILE_CLOSED_DOOR_3 then
|
240
|
+
game.map[tile_x, tile_y] -= (TILE_CLOSED_DOOR - TILE_OPEN_DOOR)
|
241
|
+
game.keys -= 1
|
242
|
+
sound("door#{rand(2) + 1}").play
|
243
|
+
end
|
244
|
+
|
245
|
+
# Up
|
246
|
+
tile_x, tile_y = x / TILE_SIZE, (y + ObjectDef[pmid].rect.top - 1) / TILE_SIZE
|
247
|
+
if game.map[tile_x, tile_y].between? TILE_CLOSED_DOOR, TILE_CLOSED_DOOR_3 then
|
248
|
+
game.map[tile_x, tile_y] -= (TILE_CLOSED_DOOR - TILE_OPEN_DOOR)
|
249
|
+
game.keys -= 1
|
250
|
+
sound("door#{rand(2) + 1}").play
|
251
|
+
end
|
252
|
+
|
253
|
+
# Down
|
254
|
+
tile_x, tile_y = x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE
|
255
|
+
if game.map[tile_x, tile_y].between? TILE_CLOSED_DOOR, TILE_CLOSED_DOOR_3 then
|
256
|
+
game.map[tile_x, tile_y] -= (TILE_CLOSED_DOOR - TILE_OPEN_DOOR)
|
257
|
+
game.keys -= 1
|
258
|
+
sound("door#{rand(2) + 1}").play
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Special actions
|
263
|
+
|
264
|
+
# Plain Peter: Use levers
|
265
|
+
if pmid == ID_PLAYER and action == ACT_ACTION_3 and game.frame % 2 == 0 then
|
266
|
+
flip_lever
|
267
|
+
end
|
268
|
+
|
269
|
+
# Lord Peter: Stab
|
270
|
+
if pmid == ID_PLAYER_FIGHTER and action.between? ACT_ACTION_1, ACT_ACTION_5 then
|
271
|
+
rect = ObjectDef::Rect.new(x - 11 + direction.dir_to_vx * 6, y - 16, 22, 32)
|
272
|
+
if target = game.find_living(ID_ENEMY, ID_ENEMY_MAX, 0, ACT_PAIN_1 - 1, rect) then
|
273
|
+
target.hit
|
274
|
+
target.fling 5 * direction.dir_to_vx, -4, 1, true, true
|
275
|
+
if target.action == ACT_DEAD then
|
276
|
+
game.score += score = ObjectDef[target.pmid].life * 3
|
277
|
+
target.emit_text "*#{score}*"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Archer Peter: Shoot at ACT_ACTION_5
|
283
|
+
# TODO or better 4, because the "action progress" code translation below is FUBAR
|
284
|
+
if pmid == ID_PLAYER_GUN and action == ACT_ACTION_4 and game.frame % 2 == 0 then
|
285
|
+
game.ammo -= 1
|
286
|
+
if target = game.launch_projectile(x, y + 2, direction, ID_ENEMY, ID_ENEMY_MAX) then
|
287
|
+
target.hurt(true)
|
288
|
+
target.fling 3 * direction.dir_to_vx * 3, -3, 1, true, true
|
289
|
+
if target.action == ACT_DEAD then
|
290
|
+
game.score += score = ObjectDef[target.pmid].life * 3
|
291
|
+
target.emit_text "*#{score}*"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
if pmid == ID_PLAYER_BERSERKER then
|
297
|
+
game.cast_fx rand(2), 2 + rand(2), 0, x, y, 18, 24, 0, -3, 2
|
298
|
+
rect = self.rect(2, 2)
|
299
|
+
game.objects.each do |obj|
|
300
|
+
if obj.pmid.between? ID_ENEMY, ID_ENEMY_MAX and obj.action < ACT_DEAD and obj.collide_with? rect then
|
301
|
+
obj.hurt(false)
|
302
|
+
if obj.action = ACT_DEAD then
|
303
|
+
game.score += score = ObjectDef[obj.pmid].life * 3
|
304
|
+
obj.emit_text "*#{score}*"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
if pmid == ID_ENEMY_BERSERKER then
|
311
|
+
game.cast_fx 0, rand(3), 0, x, y, 18, 24, 0, -2, 3
|
312
|
+
if game.player.action < ACT_DEAD and game.player.collide_with? rect(5, 2) then
|
313
|
+
game.player.hit
|
314
|
+
game.player.fling 8 * direction.dir_to_vx * 8, -3, 0, true, true
|
315
|
+
self.fling -8 * direction.dir_to_vx * 8, -4, 0, true, false
|
316
|
+
return
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
if pmid == ID_PLAYER_BOMBER and action == ACT_ACTION_4 and game.frame % 3 == 0 then
|
321
|
+
game.bombs -= 1
|
322
|
+
bomb = game.create_object(ID_FUSING_BOMB, x + direction.dir_to_vx * 5, y + 2, '0')
|
323
|
+
bomb.vx = direction.dir_to_vx * 8
|
324
|
+
bomb.vy = -3
|
325
|
+
end
|
326
|
+
|
327
|
+
if pmid <= ID_PLAYER_MAX and not busy? then
|
328
|
+
self.direction = DIR_LEFT if vx < 0
|
329
|
+
self.direction = DIR_RIGHT if vx > 0
|
330
|
+
end;
|
331
|
+
|
332
|
+
if pmid == ID_ENEMY_GUN and action == ACT_ACTION_4 then
|
333
|
+
self.action = ACT_ACTION_5 # TODO this was better before?!
|
334
|
+
shoot_dir = game.player.x < x ? DIR_LEFT : DIR_RIGHT
|
335
|
+
if target = game.launch_projectile(x, y + 2, shoot_dir, ID_PLAYER, ID_PLAYER_MAX) then
|
336
|
+
target.hit
|
337
|
+
target.fling 3 * shoot_dir.dir_to_vx, -3, 1, true, true
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# "AI"
|
342
|
+
if pmid >= ID_ENEMY and not busy? then
|
343
|
+
self.direction = direction.other_dir if blocked? direction
|
344
|
+
|
345
|
+
# Run around
|
346
|
+
if pmid == ID_ENEMY_FIGHTER and game.frame % 100 < 70 or
|
347
|
+
pmid == ID_ENEMY_GUN and game.frame % 100 > 15 or
|
348
|
+
[ID_ENEMY, ID_ENEMY_BERSERKER, ID_ENEMY_BOMBER].include? pmid then
|
349
|
+
if game.map.solid?(x + direction.dir_to_vx * 7, y + ObjectDef[pmid].rect.bottom + 1)
|
350
|
+
self.vx += ObjectDef[pmid].speed * direction.dir_to_vx
|
351
|
+
else
|
352
|
+
if xdata and xdata[0, 1] == '1' then
|
353
|
+
jump
|
354
|
+
else
|
355
|
+
self.direction = direction.other_dir
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Occasionally use a floor tile
|
361
|
+
return use_tile if rand(100) == 0 and xdata and xdata[2, 1] == '1'
|
362
|
+
|
363
|
+
# Ambush player
|
364
|
+
if pmid == ID_ENEMY_FIGHTER and
|
365
|
+
game.player.action < ACT_DEAD and
|
366
|
+
game.player.collide_with? ObjectDef::Rect.new(x - 120 + direction * 120, y - 24, 120, 48) and
|
367
|
+
game.map.solid?(x + direction.dir_to_vx * 7, y + ObjectDef[pmid].rect.bottom + 1) then
|
368
|
+
self.vx = ObjectDef[pmid].speed * 2 * direction.dir_to_vx
|
369
|
+
end
|
370
|
+
|
371
|
+
# Shoot player if line of sight is free
|
372
|
+
if pmid == ID_ENEMY_GUN and game.player.action < ACT_DEAD and
|
373
|
+
rect(320, 1).include? game.player then
|
374
|
+
tiles_to_check = (game.player.x - x).abs / TILE_SIZE
|
375
|
+
if (0..tiles_to_check).none? { |i| game.map.solid?(i * TILE_SIZE + [game.player.x, x].min, y) } then
|
376
|
+
self.action = ACT_ACTION_1
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Collide with player while running
|
381
|
+
if game.player.action < ACT_PAIN_1 and collide_with? game.player.rect(1, -1) then
|
382
|
+
if pmid == ID_ENEMY_BOMBER then
|
383
|
+
game.player.hurt(true)
|
384
|
+
game.cast_fx 10, 30, 10, x, y, 10, 20, 0, -10, 5
|
385
|
+
kill
|
386
|
+
else
|
387
|
+
game.player.hit
|
388
|
+
fling -6 * direction.dir_to_vx, -2, 0, true, false
|
389
|
+
end
|
390
|
+
game.player.fling 8 * direction.dir_to_vx, -3, 0, true, true
|
391
|
+
return
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Collide with player while airborne
|
396
|
+
if pmid >= ID_ENEMY and [ACT_JUMP, ACT_LAND].include? action and
|
397
|
+
collide_with? game.player.rect(0, -1) then
|
398
|
+
if pmid == ID_ENEMY_BOMBER then
|
399
|
+
game.player.hurt(true)
|
400
|
+
game.cast_fx 10, 30, 10, x, y, 10, 0, -10, 5
|
401
|
+
kill
|
402
|
+
else
|
403
|
+
game.player.hit
|
404
|
+
fling -6 * direction.dir_to_vx, -2, 0, true, false
|
405
|
+
end
|
406
|
+
game.player.fling 8 * direction.dir_to_vx, -3, 0, true, true
|
407
|
+
return
|
408
|
+
end
|
409
|
+
|
410
|
+
# Fly
|
411
|
+
if pmid <= ID_PLAYER_MAX and game.fly_time_left > 0 and not (ACT_ACTION_1..ACT_ACTION_5).include? action then
|
412
|
+
self.action = vy < 0 ? ACT_JUMP : ACT_LAND
|
413
|
+
return
|
414
|
+
end
|
415
|
+
|
416
|
+
# Hurt player cannot recover until on the ground
|
417
|
+
return if [ACT_PAIN_1, ACT_PAIN_2].include? action and not blocked? DIR_DOWN and not in_water?
|
418
|
+
|
419
|
+
# Hingefallen, weiter aufstehen
|
420
|
+
if action.between? ACT_IMPACT_1, ACT_IMPACT_5 then
|
421
|
+
self.action -= 1 unless game.frame % 2 == 0
|
422
|
+
return unless action == ACT_IMPACT_1 and game.frame % 2 == 1
|
423
|
+
end
|
424
|
+
|
425
|
+
# Continue with special action
|
426
|
+
if action.between? ACT_ACTION_1, ACT_ACTION_5 then
|
427
|
+
case pmid
|
428
|
+
when ID_PLAYER, ID_PLAYER_GUN, ID_ENEMY_FIGHTER then slowness = 2
|
429
|
+
when ID_ENEMY_GUN then slowness = 8
|
430
|
+
when ID_PLAYER_BOMBER then slowness = 3
|
431
|
+
else
|
432
|
+
slowness = 1
|
433
|
+
end
|
434
|
+
|
435
|
+
return if game.frame % slowness != 0
|
436
|
+
self.action += 1
|
437
|
+
return unless action == ACT_ACTION_5
|
438
|
+
end
|
439
|
+
|
440
|
+
if not blocked? DIR_DOWN then
|
441
|
+
self.action = vy < 0 ? ACT_JUMP : ACT_LAND
|
442
|
+
return
|
443
|
+
end
|
444
|
+
|
445
|
+
if pmid <= ID_ENEMY_MAX and
|
446
|
+
game.map[x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE].between? TILE_SLIME, TILE_SLIME_3 then
|
447
|
+
# Only when walking...
|
448
|
+
if (pmid <= ID_PLAYER_MAX and (left_pressed? or right_pressed?)) or
|
449
|
+
(pmid == ID_ENEMY_FIGHTER and game.frame % 100 < 70 or pmid == ID_ENEMY_GUN and game.frame % 100 > 15) then
|
450
|
+
self.action = ACT_WALK_1 + game.frame % 12 / 3
|
451
|
+
if pmid <= ID_PLAYER_MAX then
|
452
|
+
self.direction = DIR_LEFT if left_pressed?
|
453
|
+
self.direction = DIR_RIGHT if right_pressed?
|
454
|
+
end
|
455
|
+
if (y - game.player.y).abs < HEIGHT and rand(5) == 0 then
|
456
|
+
blob = game.create_object(ID_FX_FLYING_BLOB, x, y + ObjectDef[pmid].rect.bottom, nil)
|
457
|
+
blob.vx = rand(3) - 1
|
458
|
+
blob.vy = rand(3)
|
459
|
+
emit_sound "slime#{rand(3) + 1}"
|
460
|
+
end
|
461
|
+
return
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# Walking animation
|
466
|
+
if blocked? DIR_DOWN and vx.abs > 0 then
|
467
|
+
self.action = ACT_WALK_1 + game.frame % 8 / 2
|
468
|
+
return
|
469
|
+
end
|
470
|
+
|
471
|
+
# "idlezeiten von ueber 4 wochen sind absolut eleet"
|
472
|
+
self.action = ACT_STAND
|
473
|
+
end
|
474
|
+
|
475
|
+
def jump
|
476
|
+
# Cannot jump when dead
|
477
|
+
return if action >= ACT_DEAD
|
478
|
+
|
479
|
+
if in_water? then
|
480
|
+
# Cannot jump when in deep water
|
481
|
+
return if ALL_WATER_TILES.include? game.map[x / TILE_SIZE, (y - 3) / TILE_SIZE]
|
482
|
+
else
|
483
|
+
# Cannot jump when busy
|
484
|
+
return if busy?
|
485
|
+
end
|
486
|
+
|
487
|
+
dir = DIR_UP
|
488
|
+
if pmid <= ID_PLAYER_MAX then
|
489
|
+
dir = self.direction = DIR_LEFT if left_pressed?
|
490
|
+
dir = self.direction = DIR_RIGHT if right_pressed?
|
491
|
+
elsif pmid.between? ID_ENEMY, ID_ENEMY_MAX then
|
492
|
+
dir = direction
|
493
|
+
end
|
494
|
+
|
495
|
+
if pmid <= ID_PLAYER_MAX and game.jump_time_left > 0 then
|
496
|
+
self.vy = (ObjectDef[pmid].jump_y * 1.7).round - 1
|
497
|
+
game.cast_objects ID_FX_SMOKE, 2, 0, 3, 2, rect(1, 0)
|
498
|
+
sound(:turbo).play
|
499
|
+
dir = DIR_UP
|
500
|
+
else
|
501
|
+
self.vy = ObjectDef[pmid].jump_y - 1
|
502
|
+
end
|
503
|
+
|
504
|
+
if dir == DIR_UP then
|
505
|
+
self.vx = 0
|
506
|
+
else
|
507
|
+
self.vx = dir.dir_to_vx * [ObjectDef[pmid].jump_x, vx.abs / 2].max
|
508
|
+
|
509
|
+
if game.map[x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE].between? TILE_SLIME, TILE_SLIME_3 then
|
510
|
+
self.vx / 3
|
511
|
+
self.vy -= -2
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
if in_water? then
|
516
|
+
self.vx /= 3
|
517
|
+
self.vy += 1
|
518
|
+
emit_sound "water#{rand(2) + 1}"
|
519
|
+
end
|
520
|
+
|
521
|
+
self.action = ACT_JUMP
|
522
|
+
sound(:jump).play if pmid <= ID_PLAYER_MAX
|
523
|
+
end
|
524
|
+
|
525
|
+
def can_reach_lever?
|
526
|
+
game.find_object ID_LEVER, ID_LEVER_RIGHT, rect(10, 3)
|
527
|
+
end
|
528
|
+
|
529
|
+
def flip_lever
|
530
|
+
if target = can_reach_lever? then
|
531
|
+
sound(:lever).play
|
532
|
+
target.pmid =
|
533
|
+
case target.pmid
|
534
|
+
when ID_LEVER then ID_LEVER_DOWN
|
535
|
+
when ID_LEVER_LEFT then ID_LEVER_RIGHT
|
536
|
+
when ID_LEVER_RIGHT then ID_LEVER_LEFT
|
537
|
+
end
|
538
|
+
|
539
|
+
if not target.xdata.nil? and not target.xdata.empty? then
|
540
|
+
if target.xdata =~ /^[0-9A-F]/ then
|
541
|
+
target.xdata[0, 1].to_i(16).times do |i|
|
542
|
+
tile_x = target.xdata[2 + i * 10, 2].to_i(16)
|
543
|
+
tile_y = target.xdata[5 + i * 10, 3].to_i(16)
|
544
|
+
new_tile = target.xdata[9 + i * 10, 2].to_i(16)
|
545
|
+
old_tile = game.map[tile_x, tile_y]
|
546
|
+
game.map[tile_x, tile_y] = new_tile
|
547
|
+
target.xdata[9 + i * 10, 2] = '%02X' % old_tile
|
548
|
+
game.cast_fx 8, 0, 0, tile_x * TILE_SIZE + 10, tile_y * TILE_SIZE + 12, 24, 24, 0, 0, 2
|
549
|
+
end
|
550
|
+
elsif not target.xdata.nil? then
|
551
|
+
game.execute_script target.xdata[2..-1], 'do'
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def use_tile
|
558
|
+
return if busy?
|
559
|
+
|
560
|
+
# Lever behind player
|
561
|
+
if pmid <= ID_PLAYER_MAX and can_reach_lever? then
|
562
|
+
if pmid == ID_PLAYER then
|
563
|
+
# only the normal player has an animation for that
|
564
|
+
self.action = ACT_ACTION_1
|
565
|
+
else
|
566
|
+
flip_lever
|
567
|
+
end
|
568
|
+
self.vx = 0
|
569
|
+
return
|
570
|
+
end
|
571
|
+
|
572
|
+
# Tile below player (magic floor tiles)
|
573
|
+
case map_tile = game.map[x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE]
|
574
|
+
when TILE_ROCKET_UP, TILE_ROCKET_UP_2, TILE_ROCKET_UP_3 then
|
575
|
+
sound(:jump).play if pmid <= ID_PLAYER_MAX
|
576
|
+
emit_sound :turbo
|
577
|
+
self.vx = 0
|
578
|
+
self.vy = -21
|
579
|
+
self.y -= 1 unless blocked? DIR_UP
|
580
|
+
self.action = ACT_JUMP
|
581
|
+
game.cast_fx 0, 0, 10, x, y, 24, 24, 0, -10, 1
|
582
|
+
return
|
583
|
+
when TILE_ROCKET_UP_LEFT, TILE_ROCKET_UP_LEFT_2, TILE_ROCKET_UP_LEFT_3 then
|
584
|
+
sound(:jump).play if pmid <= ID_PLAYER_MAX
|
585
|
+
emit_sound :turbo
|
586
|
+
self.vx = -15
|
587
|
+
self.vy = -15
|
588
|
+
self.y -= 1 unless blocked? DIR_UP
|
589
|
+
self.action = ACT_JUMP
|
590
|
+
self.direction = DIR_LEFT
|
591
|
+
game.cast_fx 0, 0, 10, x, y, 24, 24, -8, -8, 1
|
592
|
+
return
|
593
|
+
when TILE_ROCKET_UP_RIGHT, TILE_ROCKET_UP_RIGHT_2, TILE_ROCKET_UP_RIGHT_3 then
|
594
|
+
sound(:jump).play if pmid <= ID_PLAYER_MAX
|
595
|
+
emit_sound :turbo
|
596
|
+
self.vx = +15
|
597
|
+
self.vy = -15
|
598
|
+
self.y -= 1 unless blocked? DIR_UP
|
599
|
+
self.action = ACT_JUMP
|
600
|
+
self.direction = DIR_RIGHT
|
601
|
+
game.cast_fx 0, 0, 10, x, y, 24, 24, +8, -8, 1
|
602
|
+
return
|
603
|
+
when TILE_MORPH_FIGHTER..TILE_MORPH_MAX
|
604
|
+
if pmid <= ID_PLAYER_MAX then
|
605
|
+
game.map[x / TILE_SIZE, (y + ObjectDef[pmid].rect.bottom + 1) / TILE_SIZE] = TILE_MORPH_EMPTY
|
606
|
+
sound(:morph).play
|
607
|
+
self.pmid = ID_PLAYER_FIGHTER + map_tile - TILE_MORPH_FIGHTER
|
608
|
+
game.time_left = ObjectDef[pmid].life unless pmid == ID_PLAYER
|
609
|
+
game.cast_fx 8, 0, 0, x, y, 24, 24, 0, -1, 4
|
610
|
+
emit_text "#{ObjectDef[pmid].name}!"
|
611
|
+
return
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
# Tile right behind player (doors etc.)
|
616
|
+
case game.map[x / TILE_SIZE, y / TILE_SIZE]
|
617
|
+
when TILE_STAIRS_UP_LOCKED then
|
618
|
+
return if pmid > ID_PLAYER_MAX or game.keys == 0
|
619
|
+
game.map[x / TILE_SIZE, y / TILE_SIZE] = TILE_STAIRS_UP
|
620
|
+
game.keys -= 1
|
621
|
+
sound("door#{rand(2) + 1}").play
|
622
|
+
use_tile
|
623
|
+
when TILE_STAIRS_UP..TILE_STAIRS_UP_2 then
|
624
|
+
return if not game.map.stairs_passable? x / TILE_SIZE, y / TILE_SIZE and pmid > ID_PLAYER_MAX
|
625
|
+
self.y = y / TILE_SIZE * TILE_SIZE
|
626
|
+
self.action = ACT_INV_UP
|
627
|
+
self.vx = self.vy = 0
|
628
|
+
emit_sound :stairs
|
629
|
+
when TILE_STAIRS_DOWN_LOCKED then
|
630
|
+
return if pmid > ID_PLAYER_MAX or game.keys == 0
|
631
|
+
game.map[x / TILE_SIZE, y / TILE_SIZE] = TILE_STAIRS_DOWN
|
632
|
+
game.keys -= 1
|
633
|
+
sound("door#{rand(2) + 1}").play
|
634
|
+
use_tile
|
635
|
+
when TILE_STAIRS_DOWN..TILE_STAIRS_DOWN_2 then
|
636
|
+
return if not game.map.stairs_passable? x / TILE_SIZE, y / TILE_SIZE
|
637
|
+
self.y = y / TILE_SIZE * TILE_SIZE + 13
|
638
|
+
self.action = ACT_INV_DOWN
|
639
|
+
self.vx = self.vy = 0
|
640
|
+
emit_sound :stairs
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
private
|
645
|
+
|
646
|
+
def break_floor x, y
|
647
|
+
debug binding if pmid == ID_PLAYER and $window.button_down? Gosu::KbD
|
648
|
+
if (TILE_BRIDGE..TILE_BRIDGE_4).include? game.map[x / TILE_SIZE, y / TILE_SIZE] and
|
649
|
+
not game.find_object(ID_FX_BREAK, ID_FX_BREAK_2,
|
650
|
+
ObjectDef::Rect.new(x / TILE_SIZE * TILE_SIZE, y / TILE_SIZE * TILE_SIZE, 24, 24)) then
|
651
|
+
game.create_object ID_FX_BREAK + rand(2),
|
652
|
+
x / TILE_SIZE * TILE_SIZE + 11,
|
653
|
+
y / TILE_SIZE * TILE_SIZE + 11, nil
|
654
|
+
emit_sound "break#{rand(2) + 1}"
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|