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,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
|