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,45 @@
|
|
1
|
+
class ObjectDef < Struct.new(:name, :life, :rect, :speed, :jump_x, :jump_y)
|
2
|
+
# TODO This has become a general purpose class, should be moved out of this file
|
3
|
+
class Rect < Struct.new(:left, :top, :width, :height)
|
4
|
+
def right
|
5
|
+
left + width
|
6
|
+
end
|
7
|
+
|
8
|
+
def bottom
|
9
|
+
top + height
|
10
|
+
end
|
11
|
+
|
12
|
+
def collide_with? other
|
13
|
+
left < other.right and right > other.left and
|
14
|
+
top < other.bottom and bottom > other.top
|
15
|
+
end
|
16
|
+
|
17
|
+
def include? point
|
18
|
+
point.x >= left and point.y >= top and
|
19
|
+
point.x < right and point.y < bottom
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.[](pmid)
|
24
|
+
@all ||= begin
|
25
|
+
ini = INIFile.new('objects.ini')
|
26
|
+
(0..ID_MAX).map do |id|
|
27
|
+
ObjectDef.new.tap do |obj_def|
|
28
|
+
hex_id = "%02X" % id
|
29
|
+
obj_def.name = ini['ObjName', hex_id] || '<no name>'
|
30
|
+
obj_def.life = (ini['ObjLife', hex_id] || 3).to_i
|
31
|
+
rect_string = ini['ObjRect', hex_id] || '10102020'
|
32
|
+
obj_def.rect = Rect.new
|
33
|
+
obj_def.rect.left = -rect_string[0, 2].to_i(16)
|
34
|
+
obj_def.rect.top = -rect_string[2, 2].to_i(16)
|
35
|
+
obj_def.rect.width = +rect_string[4, 2].to_i(16)
|
36
|
+
obj_def.rect.height = +rect_string[6, 2].to_i(16)
|
37
|
+
obj_def.speed = (ini['ObjSpeed', hex_id] || 3).to_i
|
38
|
+
obj_def.jump_x = (ini['ObjJump', "#{hex_id}X"] || 0).to_i
|
39
|
+
obj_def.jump_y = (ini['ObjJump', "#{hex_id}Y"] || 0).to_i
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@all[pmid]
|
44
|
+
end
|
45
|
+
end
|
data/src/script.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
module PMScript
|
2
|
+
def letter_to_object letter
|
3
|
+
letter == 'P' ? player : obj_vars[letter.to_i(16)]
|
4
|
+
end
|
5
|
+
|
6
|
+
def convert_sound_name name
|
7
|
+
case name.downcase
|
8
|
+
when /^(.*)Collect/ then
|
9
|
+
"collect_#{$1}"
|
10
|
+
when "jeepee" then
|
11
|
+
"yippie"
|
12
|
+
when "stairsrnd" then
|
13
|
+
"stairs_steps"
|
14
|
+
when "swordwoosh" then
|
15
|
+
"sword_whoosh"
|
16
|
+
when "blockerbreak" then
|
17
|
+
"blocker_break"
|
18
|
+
when "arrowhit" then
|
19
|
+
"arrow_hit"
|
20
|
+
else
|
21
|
+
if (sound("#{name.downcase}2") rescue nil) then
|
22
|
+
"#{name.downcase}1"
|
23
|
+
else
|
24
|
+
name.downcase
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def shortcut_to_message shortcut
|
30
|
+
case shortcut
|
31
|
+
when 'ex' then :existence_as_int
|
32
|
+
when 'px' then :x
|
33
|
+
when 'py' then :y
|
34
|
+
when 'vx' then :vx
|
35
|
+
when 'vy' then :vy
|
36
|
+
when 'id' then :pmid
|
37
|
+
when 'lf' then :life
|
38
|
+
when 'ac' then :action
|
39
|
+
when 'dr' then :direction
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_var var
|
44
|
+
case var
|
45
|
+
when /^var(.)$/ then
|
46
|
+
map.vars[$1.to_i(16)]
|
47
|
+
when /\?(...)/ then
|
48
|
+
rand($1.to_i(16) + 1)
|
49
|
+
when /^\$(.)(..)/ then
|
50
|
+
receiver = letter_to_object($1)
|
51
|
+
message = shortcut_to_message($2)
|
52
|
+
receiver.send(message)
|
53
|
+
when 'keys' then keys
|
54
|
+
when 'ammo' then ammo
|
55
|
+
when 'bomb' then bombs
|
56
|
+
when 'star' then stars
|
57
|
+
when 'scor' then score
|
58
|
+
|
59
|
+
when 'time' then time_left
|
60
|
+
when 'tspd' then speed_time_left
|
61
|
+
when 'tjmp' then jump_time_left
|
62
|
+
when 'tfly' then fly_time_left
|
63
|
+
|
64
|
+
when 'lpos' then map.lava_pos
|
65
|
+
when 'lspd' then map.lava_speed
|
66
|
+
when 'lmod' then map.lava_mode
|
67
|
+
else
|
68
|
+
throw "Getting unknown variable #{var}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_var var, value
|
73
|
+
case var
|
74
|
+
when /^var(.)$/ then
|
75
|
+
map.vars[$1.to_i(16)] = value
|
76
|
+
when /^\$(.)(..)/ then
|
77
|
+
receiver = letter_to_object($1)
|
78
|
+
message = shortcut_to_message($2)
|
79
|
+
receiver.send "#{message}=", value
|
80
|
+
when 'keys' then self.keys = value
|
81
|
+
when 'ammo' then self.ammo = value
|
82
|
+
when 'bomb' then self.bombs = value
|
83
|
+
when 'star' then self.stars = value
|
84
|
+
when 'scor' then self.score = value
|
85
|
+
|
86
|
+
when 'time' then self.time_left = value
|
87
|
+
when 'tspd' then self.speed_time_left = value
|
88
|
+
when 'tjmp' then self.jump_time_left = value
|
89
|
+
when 'tfly' then self.fly_time_left = value
|
90
|
+
|
91
|
+
when 'lpos' then map.lava_pos = value
|
92
|
+
when 'lspd' then map.lava_speed = value
|
93
|
+
when 'lmod' then map.lava_mode = value
|
94
|
+
else
|
95
|
+
throw "Setting unknown variable #{var}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def evaluate_param param
|
100
|
+
if param.length == 5 then
|
101
|
+
evaluate_param(param[1..-1]) * (param[0, 1] == '-' ? -1 : +1)
|
102
|
+
else
|
103
|
+
if param =~ /[0-9A-Fa-f]{4}/ then
|
104
|
+
param.to_i(16)
|
105
|
+
else
|
106
|
+
get_var param
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def evaluate_condition condition
|
112
|
+
return true if condition == 'always'
|
113
|
+
left_var = evaluate_param(condition[0, 4])
|
114
|
+
right_var = evaluate_param(condition[5, 4])
|
115
|
+
|
116
|
+
case condition[4, 1]
|
117
|
+
when '=' then return left_var == right_var
|
118
|
+
when '!' then return left_var != right_var
|
119
|
+
when '<' then return left_var < right_var
|
120
|
+
when '>' then return left_var > right_var
|
121
|
+
when '"' then return (left_var - right_var).abs <= 16
|
122
|
+
when "'" then return (left_var - right_var).abs > 16
|
123
|
+
when '{' then return left_var <= right_var
|
124
|
+
when '}' then return left_var >= right_var
|
125
|
+
else
|
126
|
+
"Unknown comparison: #{condition[4, 1]}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def execute_command command, caller
|
131
|
+
return if command.empty?
|
132
|
+
|
133
|
+
if command[0, 1] == '_' then
|
134
|
+
# Magic condition repeat - return if last condition was false
|
135
|
+
return if not @last_cond
|
136
|
+
action = command[1..-1]
|
137
|
+
elsif not command =~ /^#{caller}\(/ then
|
138
|
+
# Wrong caller - counts as false condition
|
139
|
+
@last_cond = false
|
140
|
+
return
|
141
|
+
else
|
142
|
+
# Evaluate conditions
|
143
|
+
@last_cond = true
|
144
|
+
|
145
|
+
conditions = command[/\([^)]*\)/]
|
146
|
+
conditions = conditions[1..-2].split('&')
|
147
|
+
@last_cond = conditions.all? &method(:evaluate_condition)
|
148
|
+
return if not @last_cond
|
149
|
+
action = command[(command.index('):') + 2)..-1]
|
150
|
+
end
|
151
|
+
|
152
|
+
case action
|
153
|
+
when /^set (....) (.....)$/ then
|
154
|
+
set_var $1, evaluate_param($2)
|
155
|
+
when /^add (....) (.....)$/ then
|
156
|
+
set_var $1, get_var($1) + evaluate_param($2)
|
157
|
+
when /^mul (....) (.....)$/ then
|
158
|
+
set_var $1, get_var($1) * evaluate_param($2)
|
159
|
+
when /^dev (....) (.....)$/ then
|
160
|
+
set_var $1, get_var($1) / evaluate_param($2)
|
161
|
+
when /^kill \$(.)$/ then
|
162
|
+
letter_to_object($1).kill rescue nil
|
163
|
+
when /^mapsolid (....) (....) (....)$/ then
|
164
|
+
set_var $1, (map.solid?(evaluate_param($2), evaluate_param($3)) ? 1 : 0)
|
165
|
+
when /^hit \$(.)$/ then
|
166
|
+
letter_to_object($1).hit rescue nil
|
167
|
+
when /^hurt \$(.)$/ then
|
168
|
+
letter_to_object($1).hurt rescue nil
|
169
|
+
when /^createobject (....) (....) (....) (..)$/ then
|
170
|
+
obj = create_object(evaluate_param($1), evaluate_param($2), evaluate_param($3), '')
|
171
|
+
obj_vars[$4[1, 1].to_i(16)] = obj unless $4 == 'no'
|
172
|
+
when /^setxd \$(.) (.*)$/ then
|
173
|
+
letter_to_object($1).xdata = $2
|
174
|
+
when /^message (.*)$/ then
|
175
|
+
@message_text = t($1)
|
176
|
+
@message_opacity = 255
|
177
|
+
when /^message2 (.*)$/ then
|
178
|
+
@message_text = t($1).gsub(/\^..../) { |term| evaluate_param(term[1..-1]) }
|
179
|
+
@message_opacity = 255
|
180
|
+
when /^sound (.*)$/ then
|
181
|
+
sound(convert_sound_name($1)).play
|
182
|
+
when /^casteffects (....) (....) (....) (....) (....)$/ then
|
183
|
+
cast_fx evaluate_param($1), evaluate_param($2), evaluate_param($3),
|
184
|
+
evaluate_param($4), evaluate_param($5), TILE_SIZE, TILE_SIZE, 0, 0, 5
|
185
|
+
when /^casteffects2 (....) (....) (....) (....) (....) (....) (.....) (.....) (....)$/ then
|
186
|
+
cast_fx evaluate_param($1), evaluate_param($2), evaluate_param($3),
|
187
|
+
evaluate_param($4), evaluate_param($5), evaluate_param($6), evaluate_param($7),
|
188
|
+
evaluate_param($8), evaluate_param($9), evaluate_param($10)
|
189
|
+
when /^changetile (....) (....) (....)$/ then
|
190
|
+
map[evaluate_param($1), evaluate_param($2)] = evaluate_param($3)
|
191
|
+
when /^explosion (....) (....) (....)$/ then
|
192
|
+
explosion evaluate_param($1), evaluate_param($2), evaluate_param($3)
|
193
|
+
when /^find \$(.) (....) (....) (....) (....) (....) (....)$/
|
194
|
+
obj_vars[$1.to_i(16)] = find_object evaluate_param($2), evaluate_param($3),
|
195
|
+
ObjectDef::Rect.new(evaluate_param($4), evaluate_param($5), evaluate_param($6), evaluate_param($7))
|
196
|
+
else
|
197
|
+
throw "Don't know how to #{action}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def execute_script script, caller
|
202
|
+
return if script.nil? or script.empty?
|
203
|
+
|
204
|
+
@cond = true
|
205
|
+
script.split('\\').each { |command| execute_command command, caller }
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# TODO translate to Ruby
|
2
|
+
|
3
|
+
=begin
|
4
|
+
State_Credits: begin
|
5
|
+
if not (DXDraw.CanDraw and Application.Active) then Exit;
|
6
|
+
DXImageList.Items[Image_TitleDark].Draw(DXDraw.Surface, 0, 0, 0);
|
7
|
+
|
8
|
+
DrawBMPText('Spielidee, Programmierung, Grafiken, Sounds', 100, 40, 192, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
9
|
+
DrawBMPText('Julian Raschke, julian@raschke.de', 200, 80, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
10
|
+
DrawBMPText('http://www.petermorphose.de/', 200, 100, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
11
|
+
DrawBMPText('Zus‰tzliche Sounds und Himmel', 100, 140, 192, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
12
|
+
DrawBMPText('Sandro Mascia, xxsgrrs-@gmx.de', 200, 160, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
13
|
+
DrawBMPText('Sebastian Ludwig, usludwig@t-online.de', 200, 180, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
14
|
+
DrawBMPText('Sˆren Bevier, soeren.bevier@dtp-isw.de', 200, 200, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
15
|
+
DrawBMPText('Sebastian Burkhart, buggi@buggi.com', 200, 220, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
16
|
+
DrawBMPText('Florian Grofl, flgr@gmx.de', 200, 240, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
17
|
+
DrawBMPText('Holger Biermann, kaiserbiddy@web.de', 200, 260, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
18
|
+
DrawBMPText('Musik', 100, 300, 192, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
19
|
+
DrawBMPText('Steffen Wenz, s.wenz@t-online.de', 200, 340, 255, DXImageList.Items[Image_Font], DXDraw.Surface, 1);
|
20
|
+
|
21
|
+
DrawBMPText('Willst du zur¸ck zum Hauptmen¸, dr¸cke Escape.', 113, 435, 0, DXImageList.Items[Image_Font], DXDraw.Surface, 0);
|
22
|
+
DXDraw.Flip;
|
23
|
+
end;
|
24
|
+
=end
|
data/src/states/game.rb
ADDED
@@ -0,0 +1,463 @@
|
|
1
|
+
class Game < State
|
2
|
+
include PMScript
|
3
|
+
|
4
|
+
# @result can be: nil (running), :lost, :won
|
5
|
+
# @paused can be: true, false
|
6
|
+
|
7
|
+
attr_reader :player, :map, :objects
|
8
|
+
attr_reader :view_pos, :frame
|
9
|
+
attr_accessor :time_left, :inv_time_left, :speed_time_left, :jump_time_left, :fly_time_left
|
10
|
+
attr_accessor :score, :keys, :stars, :ammo, :bombs
|
11
|
+
attr_reader :stars_goal
|
12
|
+
attr_reader :obj_vars
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"#<Game>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def lose reason
|
19
|
+
@result = :lost
|
20
|
+
@reason = reason
|
21
|
+
end
|
22
|
+
|
23
|
+
def explosion x, y, radius, do_score
|
24
|
+
emit_sound :explosion, y
|
25
|
+
|
26
|
+
(10 + radius / 2).times do
|
27
|
+
angle = Gosu::random(0, 360)
|
28
|
+
slowdown = Gosu::random(5, 11)
|
29
|
+
fx = create_object(ID_FX_SMOKE + rand(2), x, y, nil)
|
30
|
+
fx.vx = Gosu::offset_x(angle, radius / slowdown).to_i
|
31
|
+
fx.vy = Gosu::offset_y(angle, radius / slowdown).to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
# Damage nearby livings
|
35
|
+
objects.each do |obj|
|
36
|
+
next if not obj.is_a? LivingObject or obj.action == ACT_DEAD
|
37
|
+
|
38
|
+
dist = Gosu::distance(x, y, obj.x, obj.y)
|
39
|
+
next if dist >= radius
|
40
|
+
|
41
|
+
if dist < radius / 3 then
|
42
|
+
obj.hurt(true)
|
43
|
+
else
|
44
|
+
obj.hit
|
45
|
+
end
|
46
|
+
|
47
|
+
if obj.action == ACT_DEAD and do_score and obj.pmid.between? ID_ENEMY, ID_ENEMY_MAX then
|
48
|
+
@score += score = ObjectDef[obj.pmid].life * 3
|
49
|
+
obj.emit_text "*#{score}*"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Damage creates
|
54
|
+
((x - radius) / TILE_SIZE).upto((x + radius) / TILE_SIZE) do |tile_x|
|
55
|
+
((y - radius) / TILE_SIZE).upto((y + radius) / TILE_SIZE) do |tile_y|
|
56
|
+
next if Gosu::distance(x, y, tile_x * TILE_SIZE + 12, tile_y * TILE_SIZE + 12) > radius
|
57
|
+
|
58
|
+
case map[tile_x, tile_y]
|
59
|
+
when TILE_BIG_BLOCKER, TILE_BIG_BLOCKER_2 then
|
60
|
+
if not find_object ID_FX_FIRE, ID_FX_FIRE, ObjectDef::Rect.new(tile_x * TILE_SIZE - 1, tile_y * TILE_SIZE - 1, 2, 2) then
|
61
|
+
create_object ID_FX_FIRE, tile_x * TILE_SIZE, tile_y * TILE_SIZE, nil
|
62
|
+
end
|
63
|
+
when TILE_BIG_BLOCKER_3 then
|
64
|
+
map[tile_x, tile_y] = 0
|
65
|
+
emit_sound "break#{rand(2) + 1}", y
|
66
|
+
cast_objects ID_FX_BREAKING_PARTS, 20, 0, 3, 3, ObjectDef::Rect.new(tile_x * TILE_SIZE, tile_y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize level_info
|
73
|
+
song(:game).play
|
74
|
+
|
75
|
+
@view_pos = TILES_Y * TILE_SIZE - HEIGHT # TODO
|
76
|
+
|
77
|
+
@player_top_pos = 1024
|
78
|
+
@lava_top_pos = 1024
|
79
|
+
@message_text = ''
|
80
|
+
@message_opacity = 0
|
81
|
+
@frame = -1
|
82
|
+
@frame_fading_box = 16
|
83
|
+
@time_left = @inv_time_left = @speed_time_left = @jump_time_left = @fly_time_left = 0
|
84
|
+
@score = @keys = @stars = @ammo = @bombs = 0
|
85
|
+
|
86
|
+
@lava_frame = 0
|
87
|
+
@lava_time_left = 0
|
88
|
+
|
89
|
+
@objects = []
|
90
|
+
@obj_vars = [nil] * 16
|
91
|
+
|
92
|
+
@map = Map.new(self, level_info.ini_file)
|
93
|
+
@stars_goal = (level_info.ini_file['Map', 'StarsGoal'] || 100).to_i
|
94
|
+
|
95
|
+
player_id = (level_info.ini_file['Objects', 'PlayerID'] || 0).to_i
|
96
|
+
player_x = (level_info.ini_file['Objects', 'PlayerX'] || 288).to_i
|
97
|
+
player_y = (level_info.ini_file['Objects', 'PlayerY'] || 24515).to_i
|
98
|
+
@player = LivingObject.new(self, player_id, player_x, player_y, nil)
|
99
|
+
player.vx = (level_info.ini_file['Objects', 'PlayerVX'] || 0).to_i
|
100
|
+
player.vy = (level_info.ini_file['Objects', 'PlayerVY'] || 0).to_i
|
101
|
+
player.life = (level_info.ini_file['Objects', 'PlayerLife'] || ObjectDef[ID_PLAYER].life).to_i
|
102
|
+
player.direction = (level_info.ini_file['Objects', 'PlayerDirection'] || rand(2)).to_i
|
103
|
+
player.action = ACT_STAND
|
104
|
+
@objects << player
|
105
|
+
|
106
|
+
# If the player starts as a Special Peter, give him some time
|
107
|
+
@time_left = player.pmid == ID_PLAYER ? 0 : ObjectDef[player.pmid].life
|
108
|
+
|
109
|
+
i = 0
|
110
|
+
while obj_string = level_info.ini_file['Objects', i] do
|
111
|
+
pmid, x, y = obj_string.split('|').map { |str| str.to_i(16) }
|
112
|
+
create_object pmid, x, y, level_info.ini_file['Objects', "#{i}Y"]
|
113
|
+
i += 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
MAX_SOUND_DISTANCE = 500.0
|
118
|
+
|
119
|
+
def emit_sound name, y
|
120
|
+
distance = (y - player.y).abs
|
121
|
+
return if distance > MAX_SOUND_DISTANCE
|
122
|
+
sound(name).play 1 - distance / MAX_SOUND_DISTANCE
|
123
|
+
end
|
124
|
+
|
125
|
+
def update
|
126
|
+
debug binding if $window.button_down? Gosu::KbD
|
127
|
+
|
128
|
+
if @result.nil? and (player.action == ACT_DEAD or player.marked?) then
|
129
|
+
lose t("Du bist gestorben.")
|
130
|
+
elsif @result.nil? and player.y < map.level_top then
|
131
|
+
if stars < stars_goal then
|
132
|
+
lose t("Du hast verloren, weil du nicht genug Sterne gesammelt hast.")
|
133
|
+
elsif find_object(ID_CAROLIN, ID_CAROLIN, ObjectDef::Rect.new(0, 0, 576, 24576)) then
|
134
|
+
lose t("Du hast verloren, weil du nicht alle Gefangenen befreit hast.")
|
135
|
+
else
|
136
|
+
@result = :won
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
return if not @result.nil?
|
141
|
+
|
142
|
+
@frame = (@frame + 1) % 2400
|
143
|
+
@message_opacity -= 3 if @message_opacity > 0
|
144
|
+
|
145
|
+
if map.lava_pos / TILE_SIZE < @lava_top_pos then
|
146
|
+
@lava_top_pos = map.lava_pos / TILE_SIZE
|
147
|
+
execute_script map.scripts[@lava_top_pos], 'lava'
|
148
|
+
end
|
149
|
+
|
150
|
+
if player.y / TILE_SIZE < @player_top_pos then
|
151
|
+
@player_top_pos = player.y / TILE_SIZE
|
152
|
+
execute_script map.scripts[@lava_top_pos], 'player'
|
153
|
+
end
|
154
|
+
|
155
|
+
execute_script map.timers[frame % 10], 'do'
|
156
|
+
execute_script map.timers[10], 'do'
|
157
|
+
|
158
|
+
# Rising lava
|
159
|
+
if map.lava_time_left == 0 then
|
160
|
+
if map.lava_speed != 0 then
|
161
|
+
map.lava_pos -= 1 if map.lava_mode == 0 and frame % map.lava_speed == 0
|
162
|
+
map.lava_pos -= map.lava_speed if map.lava_mode == 1
|
163
|
+
map.lava_frame = (map.lava_frame + 1) % 120
|
164
|
+
emit_sound :lava, map.lava_pos if frame % 10 == 0 and rand(10) == 0
|
165
|
+
end
|
166
|
+
else
|
167
|
+
map.lava_time_left -= 1
|
168
|
+
end
|
169
|
+
|
170
|
+
if player.pmid != ID_PLAYER then
|
171
|
+
@time_left -= 1
|
172
|
+
if @time_left == 0 then
|
173
|
+
player.pmid = ID_PLAYER
|
174
|
+
cast_fx 8, 0, 0, player.x, player.y, 24, 24, 0, -1, 4
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@inv_time_left -= 1 if @inv_time_left > 0
|
178
|
+
@view_pos = [[map.lava_pos - 432, player.y - 240, 24096].min, map.level_top].max
|
179
|
+
|
180
|
+
if fly_time_left == 0 and not player.in_water? then
|
181
|
+
if left_pressed? then
|
182
|
+
player.instance_eval do
|
183
|
+
if not busy? and vx > -ObjectDef[pmid].speed * 1.75 then
|
184
|
+
self.vx -= ObjectDef[pmid].speed + (game.speed_time_left > 0 ? 6 : 0)
|
185
|
+
end
|
186
|
+
if [ACT_JUMP, ACT_LAND, ACT_PAIN_1, ACT_PAIN_2].include? action then
|
187
|
+
(ObjectDef[pmid].jump_x * 2).times { self.x -= 1 unless blocked? DIR_LEFT }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
if right_pressed? then
|
192
|
+
player.instance_eval do
|
193
|
+
if not busy? and vx < +ObjectDef[pmid].speed * 1.75 then
|
194
|
+
self.vx += ObjectDef[pmid].speed + (game.speed_time_left > 0 ? 6 : 0)
|
195
|
+
end
|
196
|
+
if [ACT_JUMP, ACT_LAND, ACT_PAIN_1, ACT_PAIN_2].include? action then
|
197
|
+
(ObjectDef[pmid].jump_x * 2).times { self.x += 1 unless blocked? DIR_RIGHT }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
else
|
202
|
+
player.instance_eval do
|
203
|
+
4.times { self.vy -= 1 if up_pressed? and vy > -4 }
|
204
|
+
2.times { self.vy += 1 if down_pressed? and vy < +4 }
|
205
|
+
4.times { self.vx -= 1 if left_pressed? and vx > -6 }
|
206
|
+
4.times { self.vx += 1 if right_pressed? and vx < +6 }
|
207
|
+
if not in_water? then
|
208
|
+
self.vx -= 1 if vx > 0
|
209
|
+
self.vx += 1 if vx < 0
|
210
|
+
self.vy -= 1 if vy > 0
|
211
|
+
self.vy += 1 if vy < 0
|
212
|
+
elsif vx + vy > 1 and game.frame % 3 == 0 and rand(5) == 0 then
|
213
|
+
sound("water#{rand(2) + 1}").play
|
214
|
+
end
|
215
|
+
self.direction = DIR_LEFT if vx < 0
|
216
|
+
self.direction = DIR_RIGHT if vx > 0
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
if @speed_time_left > 0 then
|
221
|
+
@speed_time_left -= 1
|
222
|
+
cast_objects ID_FX_SPARK, rand(2), 0, 0, 1, player.rect(1, 1)
|
223
|
+
end
|
224
|
+
@jump_time_left -= 1 if @jump_time_left > 0
|
225
|
+
@fly_time_left -= 1 if @fly_time_left > 0
|
226
|
+
|
227
|
+
if frame > 2 then
|
228
|
+
player.jump if jump_pressed?
|
229
|
+
#player.use_tile if use_pressed?
|
230
|
+
player.act if action_pressed?
|
231
|
+
end
|
232
|
+
|
233
|
+
@objects.each &:update
|
234
|
+
@objects.reject! &:marked?
|
235
|
+
|
236
|
+
if map.lava_time_left == 0 then
|
237
|
+
cast_fx rand(2) + 1, rand(2) + 1, 0, 288, map.lava_pos, 576, 8, 1, -3, 1
|
238
|
+
if rand(15) == 0 then
|
239
|
+
create_object(ID_FX_BUBBLE, rand(576), map.lava_pos - 12, nil).vx = 1 - rand(3)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def draw
|
245
|
+
@map.draw
|
246
|
+
@objects.each &:draw
|
247
|
+
|
248
|
+
# Lava
|
249
|
+
@@danger ||= Gosu::Image.load_tiles 'media/danger.png', -2, -2
|
250
|
+
offset = if map.lava_time_left == 0 then frame / 2 % 2 else 0 end
|
251
|
+
-1.upto(4) do |x|
|
252
|
+
@@danger[map.lava_time_left == 0 ? 0 : 1].draw x * 120 + map.lava_frame + offset, map.lava_pos - view_pos, Z_LAVA
|
253
|
+
end
|
254
|
+
if map.lava_pos < map.level_top + 432 then
|
255
|
+
-1.upto(4) do |x|
|
256
|
+
0.upto((map.level_top + 432 - map.lava_pos) / 48 + 1) do |y|
|
257
|
+
@@danger[map.lava_time_left == 0 ? 2 : 3].draw x * 120 + map.lava_frame + offset, map.lava_pos - view_pos + 48 + y * 48, Z_LAVA
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
draw_status_bar
|
263
|
+
|
264
|
+
# Optional progress indicator
|
265
|
+
# if Data.OptShowStatus = 1 then begin
|
266
|
+
# DXDraw.Surface.FillRectAlpha(Bounds(2, 38, 8, 404), clGray, 64);
|
267
|
+
# if Data.ObjPlayers.Next.ID > -1 then DXDraw.Surface.FillRectAlpha(Bounds(0, 38 + Round(((Data.ObjPlayers.Next.PosY / 24 - Data.Map.LevelTop / 24) / (LevelBottom - Data.Map.LevelTop / 24)) * 400), 12, 4), clNavy, 192);
|
268
|
+
# if (Data.Map.LavaPos <= 24576) and (Data.Map.LavaTimeLeft = 0) then DXDraw.Surface.FillRectAlpha(Bounds(0, 38 + Round(((Data.Map.LavaPos / 24 - Data.Map.LevelTop / 24) / (LevelBottom - Data.Map.LevelTop / 24)) * 400), 12, 4), clYellow, 128);
|
269
|
+
# if (Data.Map.LavaPos <= 24576) and (Data.Map.LavaTimeLeft > 0) then DXDraw.Surface.FillRectAlpha(Bounds(0, 38 + Round(((Data.Map.LavaPos / 24 - Data.Map.LevelTop / 24) / (LevelBottom - Data.Map.LevelTop / 24)) * 400), 12, 4), clAqua, 128);
|
270
|
+
# end;
|
271
|
+
|
272
|
+
if @result.nil? and @message_opacity > 0 then
|
273
|
+
draw_centered_string @message_text, WIDTH / 2, 230, @message_opacity
|
274
|
+
end
|
275
|
+
|
276
|
+
@@dialogs ||= Gosu::Image.load_tiles 'media/dialogs.bmp', -1, -3
|
277
|
+
if not @result.nil? then
|
278
|
+
@frame_fading_box += 1
|
279
|
+
@frame_fading_box = 1 if @frame_fading_box == 33
|
280
|
+
|
281
|
+
if @result == :lost then
|
282
|
+
draw_centered_string @reason, WIDTH / 2, 220, (16 - @frame_fading_box).abs * 15
|
283
|
+
else
|
284
|
+
@@dialogs[1].draw 200, 160, Z_UI, 1, 1, alpha((16 - @frame_fading_box).abs * 15), :additive
|
285
|
+
end
|
286
|
+
elsif @paused then
|
287
|
+
@@dialogs[2].draw 200, 120, Z_UI, 1, 1, alpha(255), :additive
|
288
|
+
end
|
289
|
+
|
290
|
+
draw_centered_string "#{t 'Punkte'}: #{score}", WIDTH / 2, 5
|
291
|
+
end
|
292
|
+
|
293
|
+
def button_down id
|
294
|
+
case @result
|
295
|
+
when nil then
|
296
|
+
if menu_cancel? id then
|
297
|
+
State.current = LevelSelection.new
|
298
|
+
sound(:whoosh).play
|
299
|
+
elsif @paused then
|
300
|
+
@paused = false if id == Gosu::KbP
|
301
|
+
else
|
302
|
+
@paused = true if id == Gosu::KbP
|
303
|
+
player.jump if jump? id and fly_time_left == 0
|
304
|
+
player.use_tile if use? id and fly_time_left == 0
|
305
|
+
player.act if action? id
|
306
|
+
end
|
307
|
+
when :lost then
|
308
|
+
if menu_cancel? id then
|
309
|
+
State.current = LevelSelection.new
|
310
|
+
sound(:whoosh).play
|
311
|
+
end
|
312
|
+
when :won then
|
313
|
+
if menu_confirm? id or menu_cancel? id then
|
314
|
+
State.current = LevelSelection.new
|
315
|
+
sound(:whoosh).play
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def launch_projectile x, y, direction, min_id, max_id
|
321
|
+
emit_sound :bow, y
|
322
|
+
orig_x = x
|
323
|
+
while x > 2 and x < 573 do
|
324
|
+
target = find_living(min_id, max_id, 0, ACT_DEAD - 1, ObjectDef::Rect.new(x - 4, y - 2, 8, 4))
|
325
|
+
|
326
|
+
if target or map.solid?(x, y) then
|
327
|
+
create_object ID_FX_RICOCHET, x, y - 1 + rand(3), direction.to_s
|
328
|
+
emit_sound :arrow_hit, y
|
329
|
+
return target
|
330
|
+
end
|
331
|
+
|
332
|
+
x += direction.dir_to_vx * 4
|
333
|
+
end
|
334
|
+
ensure
|
335
|
+
create_object ID_FX_LINE, [x, orig_x].min, y, (x - orig_x).abs.to_s
|
336
|
+
end
|
337
|
+
|
338
|
+
include PMScript
|
339
|
+
|
340
|
+
def cast_objects pmid, num, vx, vy, randomness, rect
|
341
|
+
num.times do
|
342
|
+
obj = create_object pmid, rect.left + rand(rect.width), rect.top + rand(rect.height), nil
|
343
|
+
obj.vx = vx - randomness + rand(randomness * 2 + 1)
|
344
|
+
obj.vy = vy - randomness + rand(randomness * 2 + 1)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def cast_fx smoke, flames, sparks, x, y, *args
|
349
|
+
return if (view_pos + HEIGHT / 2 - y).abs > HEIGHT
|
350
|
+
|
351
|
+
cast_single_fx ID_FX_SMOKE, smoke, x, y, *args
|
352
|
+
cast_single_fx ID_FX_FLAME, flames, x, y, *args
|
353
|
+
cast_single_fx ID_FX_SPARK, sparks, x, y, *args
|
354
|
+
end
|
355
|
+
|
356
|
+
def create_object pmid, x, y, xdata
|
357
|
+
cls = case pmid
|
358
|
+
when 0..ID_LIVING_MAX then LivingObject
|
359
|
+
when ID_OTHER_OBJECTS_MIN..ID_OTHER_OBJECTS_MAX then GameObject
|
360
|
+
when ID_COLLECTIBLE_MIN..ID_COLLECTIBLE_MAX then CollectibleObject
|
361
|
+
when ID_FX_MIN..ID_FX_MAX then EffectObject
|
362
|
+
end
|
363
|
+
|
364
|
+
obj = cls.new(self, pmid, x, y, xdata)
|
365
|
+
@objects << obj
|
366
|
+
obj
|
367
|
+
end
|
368
|
+
|
369
|
+
def find_object min_id, max_id, rect
|
370
|
+
@objects.find { |o| o.pmid >= min_id and o.pmid <= max_id and rect.include? o }
|
371
|
+
end
|
372
|
+
|
373
|
+
def find_living min_id, max_id, min_act, max_act, rect
|
374
|
+
objects.find do |obj|
|
375
|
+
obj.pmid.between? min_id, max_id and obj.action.between? min_act, max_act and obj.collide_with? rect
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
private
|
380
|
+
|
381
|
+
def draw_status_bar
|
382
|
+
@@gui ||= Gosu::Image.load_tiles 'media/gui.bmp', -4, -11
|
383
|
+
Gosu::translate(576, 0) do
|
384
|
+
tile_w = @@gui.first.width
|
385
|
+
tile_h = @@gui.first.height
|
386
|
+
|
387
|
+
draw_digits = lambda do |num, row|
|
388
|
+
left_digit = [num, 99].min / 10 * 2 + 20
|
389
|
+
right_digit = [num, 99].min % 10 * 2 + 21
|
390
|
+
@@gui[left_digit].draw tile_w * 2, tile_h * row, Z_UI
|
391
|
+
@@gui[right_digit].draw tile_w * 3, tile_h * row, Z_UI
|
392
|
+
end
|
393
|
+
|
394
|
+
# Game logo and spacing
|
395
|
+
0.upto(3) do |x|
|
396
|
+
@@gui[x + 0].draw tile_w * x, tile_h * 0, Z_UI
|
397
|
+
@@gui[x + 4].draw tile_w * x, tile_h * 1, Z_UI
|
398
|
+
end
|
399
|
+
# Health
|
400
|
+
if true then # TODO player.alive?
|
401
|
+
@@gui[8].draw tile_w * 0, tile_h * 2, Z_UI
|
402
|
+
@@gui[9].draw tile_w * 1, tile_h * 2, Z_UI
|
403
|
+
draw_digits.call player.life, 2
|
404
|
+
else
|
405
|
+
0.upto(3) { |x| @@gui[x + 4].draw tile_w * x, tile_h * 2, Z_UI }
|
406
|
+
end
|
407
|
+
# Keys
|
408
|
+
@@gui[10].draw tile_w * 0, tile_h * 3, Z_UI
|
409
|
+
@@gui[11].draw tile_w * 1, tile_h * 3, Z_UI
|
410
|
+
draw_digits.call keys, 3
|
411
|
+
# Stars
|
412
|
+
@@gui[12].draw tile_w * 0, tile_h * 4, Z_UI
|
413
|
+
@@gui[13].draw tile_w * 1, tile_h * 4, Z_UI
|
414
|
+
draw_digits.call stars, 4
|
415
|
+
if stars > stars_goal then
|
416
|
+
# Enough stars - draw checkmark
|
417
|
+
@@gui[42].draw tile_w * 2, tile_h * 4, Z_UI
|
418
|
+
@@gui[43].draw tile_w * 3, tile_h * 4, Z_UI
|
419
|
+
end
|
420
|
+
if stars_goal == 0 then
|
421
|
+
# Or blank the line out of no stars are needed
|
422
|
+
0.upto(3) { |x| @@gui[x + 4].draw tile_w * x, tile_h * 4, Z_UI }
|
423
|
+
end
|
424
|
+
# Remaining special peter time
|
425
|
+
if player.pmid == ID_PLAYER then
|
426
|
+
0.upto(3) { |x| @@gui[x + 4].draw tile_w * x, tile_h * 5, Z_UI }
|
427
|
+
else
|
428
|
+
@@gui[14].draw tile_w * 0, tile_h * 5, Z_UI
|
429
|
+
@@gui[15].draw tile_w * 1, tile_h * 5, Z_UI
|
430
|
+
draw_digits.call time_left / TARGET_FPS, 5
|
431
|
+
end
|
432
|
+
# Ammo
|
433
|
+
@@gui[16].draw tile_w * 0, tile_h * 6, Z_UI
|
434
|
+
@@gui[17].draw tile_w * 1, tile_h * 6, Z_UI
|
435
|
+
draw_digits.call ammo, 6
|
436
|
+
# Bombs
|
437
|
+
@@gui[18].draw tile_w * 0, tile_h * 7, Z_UI
|
438
|
+
@@gui[19].draw tile_w * 1, tile_h * 7, Z_UI
|
439
|
+
draw_digits.call bombs, 7
|
440
|
+
# Remaining time for frozen lava
|
441
|
+
if map.lava_time_left == 0 then
|
442
|
+
0.upto(3) { |x| @@gui[x + 4].draw tile_w * x, tile_h * 8, Z_UI }
|
443
|
+
else
|
444
|
+
@@gui[40].draw tile_w * 0, tile_h * 8, Z_UI
|
445
|
+
@@gui[41].draw tile_w * 1, tile_h * 8, Z_UI
|
446
|
+
draw_digits.call map.lava_time_left / TARGET_FPS, 8
|
447
|
+
end
|
448
|
+
# Spacing
|
449
|
+
0.upto(3) { |x| @@gui[x + 4].draw tile_w * x, tile_h * 9, Z_UI }
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# TODO Merge into cast_objects
|
454
|
+
def cast_single_fx pmid, num, x, y, w, h, vx, vy, randomness
|
455
|
+
num.times do
|
456
|
+
x = [[x - w / 2 + rand(w + 1), 0].max, 575].min
|
457
|
+
y = y - h / 2 + rand(h + 1)
|
458
|
+
fx = create_object(pmid, x, y, nil)
|
459
|
+
fx.vx = vx - randomness + rand(randomness * 2 + 1)
|
460
|
+
fx.vy = vy - randomness + rand(randomness * 2 + 1)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|