├── .rvmrc ├── README.md ├── escape.rb └── escape ├── art.rb ├── assets ├── gui │ └── logo.png ├── level │ ├── crypt.png │ ├── dungeon.png │ ├── ice.png │ ├── overworld.png │ ├── start.png │ └── temple.png ├── snd │ ├── altar.wav │ ├── bosskill.wav │ ├── click1.wav │ ├── click2.wav │ ├── crumble.wav │ ├── cut.wav │ ├── death.wav │ ├── hit.wav │ ├── hurt.wav │ ├── hurt2.wav │ ├── key.wav │ ├── kill.wav │ ├── ladder.wav │ ├── pickup.wav │ ├── potion.wav │ ├── roll.wav │ ├── shoot.wav │ ├── slide.wav │ ├── splash.wav │ ├── thud.wav │ └── treasure.wav └── tex │ ├── floors.png │ ├── font.png │ ├── items.png │ ├── panel.png │ ├── sky.png │ ├── sprites.png │ └── walls.png ├── component.rb ├── entities ├── bat_boss_entity.rb ├── bat_entity.rb ├── boulder_entity.rb ├── bullet.rb ├── enemy_entity.rb ├── entity.rb ├── eye_boss_entity.rb ├── eye_entity.rb ├── item.rb ├── key_entity.rb └── player.rb ├── escape.rb ├── game.rb ├── gui ├── bitmap.rb ├── bitmap3d.rb ├── poof_sprite.rb ├── rubble_sprite.rb ├── screen.rb └── sprite.rb ├── input_handler.rb ├── level ├── block │ ├── bars_block.rb │ ├── block.rb │ ├── chest_block.rb │ ├── door_block.rb │ ├── ice_block.rb │ ├── ladder_block.rb │ ├── locked_door_block.rb │ ├── loot_block.rb │ ├── pit_block.rb │ ├── pressure_plate_block.rb │ ├── solid_block.rb │ ├── torch_block.rb │ ├── vanish_block.rb │ └── water_block.rb ├── dungeon_level.rb ├── ice_level.rb ├── level.rb ├── overworld_level.rb └── start_level.rb ├── menu ├── about_menu.rb ├── got_loot_menu.rb ├── instructions_menu.rb ├── menu.rb ├── pause_menu.rb └── title_menu.rb └── sound.rb /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use jruby-1.6.3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prelude of the Chambered (JRuby port) 2 | 3 | This is a JRuby port of notch's 48 hour "Ludum Dare" game competition entry from 2011: _Prelude of the Chambered_. 4 | 5 | ![potc](https://user-images.githubusercontent.com/118/200089149-eb291cd7-6c5e-4b18-a03c-f3a67efc7974.jpg) 6 | 7 | Notch's Java code is pretty straightforward and I've been looking for an excuse to do a "real" project in JRuby for ages. This seemed an ideal opportunity. 8 | 9 | The port remains unfinished but the first few levels of the game are playable (start level, dungeon, overworld, and ice level - no bosses). 10 | 11 | Controls are W, A, S, D and space. Or arrow keys. To get started, turn around and punch the "broken" wall. Best place to head is along the corridor, to the left, and down into the dungeon. You'll find boulders to punch, magic gloves, and bats to kill down there. 12 | 13 | ## Running 14 | 15 | To run this code, get JRuby installed and then (from the root folder): 16 | 17 | ruby escape.rb 18 | 19 | ## Known bugs 20 | 21 | * If you take focus away from the game and then come back, the left and right controls stop working 22 | 23 | ## Credits 24 | 25 | _Prelude of the Chambered_ is a game by Markus "Notch" Persson and Mojang 26 | 27 | This JRuby port was started by, and is maintained by, Peter Cooper. 28 | 29 | Contributions by: 30 | 31 | * Charles Nutter (@headius) 32 | * Tim Felgentreff (github:timfel) 33 | * Andrew Chalkley (@chalkers) 34 | 35 | ## Other links 36 | 37 | Romain Lods has been working on a C#-powered .NET port of the game - http://github.com/rlods/PocDotNet 38 | 39 | ## License 40 | 41 | Unsure as yet, not sure of the license for the original game. 42 | 43 | Original game and content is Copyright 2011 to Mojang so don't use Notch's graphical assets for your own blockbuster hit until he says it's OK :-) 44 | -------------------------------------------------------------------------------- /escape.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path(File.dirname(__FILE__)) 2 | require 'escape/escape' 3 | 4 | Component.main 5 | -------------------------------------------------------------------------------- /escape/art.rb: -------------------------------------------------------------------------------- 1 | java_import java.awt.image.BufferedImage 2 | java_import javax.imageio.ImageIO 3 | 4 | class Art < java.lang.Object 5 | def self.load_bitmap(file_name) 6 | url = java.net.URL.new("file://" + ASSETS_DIR + file_name) # nasty little hack due to borked get_resource (means applet won't be easy..) 7 | img = ImageIO.read(url) 8 | 9 | w = img.width 10 | h = img.height 11 | 12 | result = Bitmap.new(w, h) 13 | 14 | h.times do |y| 15 | w.times do |x| 16 | result.pixels[y * w + x] = img.getRGB(x, y) 17 | end 18 | end 19 | 20 | result.pixels.length.times do |i| 21 | inp = result.pixels[i] + (2 ** 32) 22 | 23 | col = (inp.to_i & 0xf) >> 2 24 | col = -1 if inp == 0xffff00ff 25 | result.pixels[i] = col 26 | end 27 | 28 | result 29 | end 30 | 31 | def self.get_col(c) 32 | r = (c >> 16) & 0xff 33 | g = (c >> 8) & 0xff 34 | b = (c) & 0xff 35 | 36 | r = r * 0x55 / 0xff 37 | g = g * 0x55 / 0xff 38 | b = b * 0x55 / 0xff 39 | 40 | (r << 16 | g << 8 | b) 41 | end 42 | 43 | 44 | %w{walls floors sprites font panel items sky}.each do |name| 45 | const_set name.upcase, load_bitmap("/tex/#{name}.png") 46 | end 47 | 48 | LOGO = load_bitmap("/gui/logo.png") 49 | end -------------------------------------------------------------------------------- /escape/assets/gui/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/gui/logo.png -------------------------------------------------------------------------------- /escape/assets/level/crypt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/crypt.png -------------------------------------------------------------------------------- /escape/assets/level/dungeon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/dungeon.png -------------------------------------------------------------------------------- /escape/assets/level/ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/ice.png -------------------------------------------------------------------------------- /escape/assets/level/overworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/overworld.png -------------------------------------------------------------------------------- /escape/assets/level/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/start.png -------------------------------------------------------------------------------- /escape/assets/level/temple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/level/temple.png -------------------------------------------------------------------------------- /escape/assets/snd/altar.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/altar.wav -------------------------------------------------------------------------------- /escape/assets/snd/bosskill.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/bosskill.wav -------------------------------------------------------------------------------- /escape/assets/snd/click1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/click1.wav -------------------------------------------------------------------------------- /escape/assets/snd/click2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/click2.wav -------------------------------------------------------------------------------- /escape/assets/snd/crumble.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/crumble.wav -------------------------------------------------------------------------------- /escape/assets/snd/cut.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/cut.wav -------------------------------------------------------------------------------- /escape/assets/snd/death.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/death.wav -------------------------------------------------------------------------------- /escape/assets/snd/hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/hit.wav -------------------------------------------------------------------------------- /escape/assets/snd/hurt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/hurt.wav -------------------------------------------------------------------------------- /escape/assets/snd/hurt2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/hurt2.wav -------------------------------------------------------------------------------- /escape/assets/snd/key.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/key.wav -------------------------------------------------------------------------------- /escape/assets/snd/kill.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/kill.wav -------------------------------------------------------------------------------- /escape/assets/snd/ladder.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/ladder.wav -------------------------------------------------------------------------------- /escape/assets/snd/pickup.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/pickup.wav -------------------------------------------------------------------------------- /escape/assets/snd/potion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/potion.wav -------------------------------------------------------------------------------- /escape/assets/snd/roll.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/roll.wav -------------------------------------------------------------------------------- /escape/assets/snd/shoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/shoot.wav -------------------------------------------------------------------------------- /escape/assets/snd/slide.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/slide.wav -------------------------------------------------------------------------------- /escape/assets/snd/splash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/splash.wav -------------------------------------------------------------------------------- /escape/assets/snd/thud.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/thud.wav -------------------------------------------------------------------------------- /escape/assets/snd/treasure.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/snd/treasure.wav -------------------------------------------------------------------------------- /escape/assets/tex/floors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/floors.png -------------------------------------------------------------------------------- /escape/assets/tex/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/font.png -------------------------------------------------------------------------------- /escape/assets/tex/items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/items.png -------------------------------------------------------------------------------- /escape/assets/tex/panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/panel.png -------------------------------------------------------------------------------- /escape/assets/tex/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/sky.png -------------------------------------------------------------------------------- /escape/assets/tex/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/sprites.png -------------------------------------------------------------------------------- /escape/assets/tex/walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterc/potc-jruby/33aa6feadb6737e39c62895176f1ee366b12eb99/escape/assets/tex/walls.png -------------------------------------------------------------------------------- /escape/component.rb: -------------------------------------------------------------------------------- 1 | java_import java.awt.Canvas 2 | java_import java.awt.BorderLayout 3 | java_import java.awt.Dimension 4 | java_import javax.swing.JFrame 5 | java_import javax.swing.JPanel 6 | java_import java.awt.Toolkit 7 | java_import java.awt.image.BufferedImage 8 | java_import java.awt.image.BufferStrategy 9 | java_import java.awt.Point 10 | java_import java.lang.System 11 | 12 | class Component < Canvas 13 | include java.lang.Runnable 14 | 15 | SERIAL_VERSION_UID = 1 16 | WIDTH = 160 17 | HEIGHT = 120 18 | SCALE = 4 19 | SCALED_WIDTH = WIDTH * SCALE 20 | SCALED_HEIGHT = HEIGHT * SCALE 21 | 22 | attr_accessor :running 23 | 24 | def initialize 25 | super 26 | 27 | size = Dimension.new(SCALED_WIDTH, SCALED_HEIGHT) 28 | self.minimum_size = self.maximum_size = self.preferred_size = self.size = size 29 | 30 | @game = Game.new 31 | @screen = Screen.new(WIDTH, HEIGHT) 32 | @img = BufferedImage.new(WIDTH, HEIGHT, BufferedImage::TYPE_INT_RGB) 33 | @pixels = @img.raster.data_buffer.data 34 | @input_handler = InputHandler.new 35 | add_key_listener @input_handler 36 | add_focus_listener @input_handler 37 | add_mouse_listener @input_handler 38 | add_mouse_motion_listener @input_handler 39 | @empty_cursor = Toolkit.default_toolkit.create_custom_cursor(BufferedImage.new(16, 16, BufferedImage::TYPE_INT_ARGB), Point.new(0, 0), "empty") 40 | @default_cursor = cursor 41 | @running = false 42 | @had_focus = false 43 | end 44 | 45 | def start 46 | return if @running 47 | @running = true 48 | @thread = java.lang.Thread.new(self) 49 | @thread.start 50 | end 51 | 52 | def stop 53 | return unless @running 54 | @running = false 55 | begin 56 | @thread.join 57 | rescue => e 58 | p e 59 | end 60 | end 61 | 62 | def run 63 | frames = 0 64 | unprocessed_seconds = 0 65 | last_time = System.nano_time 66 | seconds_per_tick = 1 / 60.0 67 | tick_count = 0 68 | 69 | request_focus 70 | 71 | while running 72 | now = System.nano_time 73 | passed_time = now - last_time 74 | last_time = now 75 | passed_time = 0 if passed_time < 0 76 | passed_time = 100000000 if passed_time > 100000000 77 | 78 | unprocessed_seconds += passed_time / 1000000000.0 79 | ticked = false 80 | 81 | while unprocessed_seconds > seconds_per_tick 82 | tick 83 | unprocessed_seconds -= seconds_per_tick 84 | ticked = true 85 | 86 | tick_count += 1 87 | if tick_count % 60 == 0 88 | System.out.println frames.to_s + " fps" 89 | #last_time += 1000 90 | frames = 0 91 | end 92 | end 93 | 94 | if ticked 95 | render 96 | frames += 1 97 | else 98 | begin 99 | java.lang.Thread.sleep 1 100 | rescue => e 101 | p e 102 | end 103 | end 104 | end 105 | end 106 | 107 | # A place for experiments - this will run ticks and renders together flat out at max 60 fps 108 | def naive_run 109 | frames = 0 110 | last_time = Time.now.to_f 111 | ticks = 0 112 | request_focus 113 | 114 | while running 115 | now = Time.now.to_f 116 | passed_time = now - last_time 117 | 118 | if passed_time >= 0.0167 119 | tick 120 | render 121 | ticks += 1 122 | last_time = now 123 | end 124 | end 125 | end 126 | 127 | def tick 128 | @game.tick(@input_handler.keys) 129 | end 130 | 131 | def render 132 | if @had_focus != has_focus 133 | has_focus = !@had_focus 134 | set_cursor @had_focus ? @empty_cursor : @default_cursor 135 | end 136 | 137 | bs = get_buffer_strategy 138 | unless bs 139 | create_buffer_strategy 2 140 | return 141 | end 142 | 143 | @screen.render(@game, has_focus) 144 | 145 | i, max_i = 0, WIDTH * HEIGHT - 1 146 | while i <= max_i 147 | @pixels[i] = @screen.pixels[i] 148 | i += 1 149 | end 150 | # Not having much luck here but leaving for now.. 151 | #@img.raster.set_pixels(0, 0, WIDTH, HEIGHT, @screen.pixels) 152 | #@img.raster.set_data_elements(0, 0, WIDTH, HEIGHT, @screen.pixels) 153 | 154 | g = bs.draw_graphics 155 | g.fill_rect 0, 0, WIDTH, HEIGHT 156 | g.draw_image @img, 0, 0, SCALED_WIDTH, SCALED_HEIGHT, nil 157 | g.dispose 158 | bs.show 159 | end 160 | 161 | def self.main 162 | game = new 163 | frame = JFrame.new("Prelude of the Chambered!") 164 | panel = JPanel.new(BorderLayout.new) 165 | panel.add(game, BorderLayout::CENTER) 166 | frame.content_pane = panel 167 | frame.pack 168 | frame.resizable = false 169 | frame.location_relative_to = nil 170 | frame.default_close_operation = JFrame::EXIT_ON_CLOSE 171 | frame.visible = true 172 | game.start 173 | end 174 | end 175 | 176 | -------------------------------------------------------------------------------- /escape/entities/bat_boss_entity.rb: -------------------------------------------------------------------------------- 1 | class BatBossEntity < EnemyEntity 2 | def initialize(x, z) 3 | super(x, z, 4 * 8, Art.get_col(0xffff00)) 4 | @x = x 5 | @z = z 6 | @health = 5 7 | @r = 0.3 8 | @flying = true 9 | end 10 | 11 | def die 12 | Sound::BOSSKILL.play 13 | @level.add_entity KeyEntity.new(@x, @z) 14 | end 15 | 16 | def tick 17 | super 18 | if rand(20) == 0 19 | xx = @x + (rand() - 0.5) * 2 20 | zz = @z + (rand() - 0.5) * 2 21 | 22 | bat_entity = BatEntity.new(xx, zz) 23 | bat_entity.level = @level 24 | bat_entity.x = -999 25 | bat_entity.z = -999 26 | 27 | @level.add_entity bat_entity if bat_entity.is_free(xx, zz) 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /escape/entities/bat_entity.rb: -------------------------------------------------------------------------------- 1 | class BatEntity < EnemyEntity 2 | def initialize(x, z) 3 | super(x, z, 4 * 8, Art.get_col(0x82666e)) 4 | @x = x 5 | @z = z 6 | @health = 2 7 | @r = 0.3 8 | @flying = true 9 | end 10 | end -------------------------------------------------------------------------------- /escape/entities/boulder_entity.rb: -------------------------------------------------------------------------------- 1 | class BoulderEntity < Entity 2 | COLOR = Art.get_col(0xAFA293) 3 | 4 | def initialize(x, z) 5 | super() 6 | @x = x 7 | @z = z 8 | @sprite = Sprite.new(0, 0, 0, 16, COLOR) 9 | @sprites << @sprite 10 | @roll_dist = 0 11 | end 12 | 13 | def tick 14 | @roll_dist += Math.sqrt(@xa * @xa + @za * @za); 15 | @sprite.tex = 8 + ((@roll_dist * 4).to_i & 1) 16 | xao = @xa 17 | zao = @za 18 | move 19 | @xa = -xao * 0.3 if @xa == 0 && xao != 0 20 | @za = -zao * 0.3 if @za == 0 && zao != 0 21 | @xa *= 0.98 22 | @za *= 0.98 23 | @xa = @za = 0 if (@xa * @xa + @za * @za < 0.0001) 24 | end 25 | 26 | def use(source, item) 27 | return false if item != Item::POWER_GLOVE 28 | Sound::ROLL.play 29 | 30 | @xa += Math.sin(source.rot) * 0.1 31 | @za += Math.cos(source.rot) * 0.1 32 | true 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /escape/entities/bullet.rb: -------------------------------------------------------------------------------- 1 | class Bullet < Entity 2 | attr_accessor :owner 3 | 4 | def initialize(owner, x, z, rot, pow, sprite, col) 5 | super() 6 | 7 | @r = 0.01; 8 | @owner = owner 9 | 10 | @xa = Math.sin(rot) * 0.2 * pow 11 | @za = Math.cos(rot) * 0.2 * pow 12 | @x = x - za / 2 13 | @z = z + xa / 2 14 | 15 | @sprites << Sprite.new(0, 0, 0, 8 * 3 + sprite, Art.get_col(col)) 16 | 17 | @flying = true 18 | end 19 | 20 | def tick 21 | xao = @xa 22 | zao = @za 23 | move 24 | 25 | remove if (@xa == 0 && @za == 0) || @xa != xao || @za != zao 26 | end 27 | 28 | def blocks(entity, x2, z2, r2) 29 | return false if entity.is_a?(Bullet) 30 | return false if entity == @owner 31 | super 32 | end 33 | 34 | def collide(entity); end 35 | end -------------------------------------------------------------------------------- /escape/entities/enemy_entity.rb: -------------------------------------------------------------------------------- 1 | class EnemyEntity < Entity 2 | attr_accessor :sprite, :default_tex, :default_color, :hurt_time, :anim_time, :health, :spin_speed, :run_speed 3 | 4 | def initialize(x, z, default_tex, default_color) 5 | super() 6 | @hurt_time = 0 7 | @anim_time = 0 8 | @health = 3 9 | @spin_speed = 0.1 10 | @run_speed = 1 11 | @x = x 12 | @z = z 13 | @default_tex = default_tex 14 | @default_color = default_color 15 | @sprite = Sprite.new(0, 0, 0, 4 * 8, default_color) 16 | @sprites << @sprite 17 | @r = 0.3 18 | end 19 | 20 | def tick 21 | if @hurt_time > 0 22 | @hurt_time -= 1 23 | @sprite.col = @default_color if @hurt_time == 0 24 | end 25 | @anim_time += 1 26 | @sprite.tex = @default_tex + @anim_time / 10 % 2 27 | move 28 | @rota += (rand * rand) * 0.3 if @xa == 0 || @za == 0 29 | 30 | @rota += (rand * rand) * @spin_speed 31 | @rot += @rota 32 | @rota *= 0.8 33 | @xa *= 0.8 34 | @za *= 0.8 35 | 36 | @xa += Math.sin(@rot) * 0.004 * @run_speed 37 | @za += Math.cos(@rot) * 0.004 * @run_speed 38 | end 39 | 40 | def use(source, item) 41 | return false if @hurt_time > 0 42 | return false if item != Item::POWER_GLOVE 43 | 44 | hurt(Math.sin(source.rot), Math.cos(source.rot)) 45 | 46 | true 47 | end 48 | 49 | def hurt(xd, zd) 50 | @sprite.col = Art.get_col(0xff0000) 51 | @hurt_time = 15 52 | 53 | dd = Math.sqrt(xd * xd + zd * zd) 54 | @xa += xd / dd * 0.2 55 | @za += zd / dd * 0.2 56 | Sound::HURT2.play 57 | @health -= 1 58 | if @health <= 0 59 | xt = (@x + 0.5).to_i 60 | zt = (@z + 0.5).to_i 61 | @level.get_block(xt, zt).add_sprite(PoofSprite.new(@x - xt, 0, @z - zt)) 62 | die 63 | remove 64 | Sound::KILL.play 65 | end 66 | end 67 | 68 | def die; end 69 | 70 | def collide(entity) 71 | if entity.is_a?(Bullet) 72 | bullet = entity 73 | return if bullet.owner.class == self.class 74 | return if @hurt_time > 0 75 | entity.remove 76 | hurt(entity.xa, entity.za) 77 | end 78 | if entity.is_a?(Player) 79 | entity.hurt(self, 1) 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /escape/entities/entity.rb: -------------------------------------------------------------------------------- 1 | class Entity 2 | attr_accessor :x, :z, :r, :rot, :xa, :za, :rota, :r, :flying, :removed, :x_tileo, :z_tileo, :level, :sprites 3 | 4 | def initialize 5 | @r = 0.4 6 | @x_tileo = -1 7 | @z_tileo = -1 8 | @removed = false 9 | @flying = false 10 | @level = nil 11 | @sprites = [] 12 | @xa = @za = @x = @z = @rot = @rota = 0.0 13 | end 14 | 15 | def update_pos 16 | x_tile = (x + 0.5).to_i 17 | z_tile = (z + 0.5).to_i 18 | if x_tile != @x_tileo || z_tile != @z_tileo 19 | @level.get_block(@x_tileo, @z_tileo).remove_entity self 20 | @x_tileo = x_tile 21 | @z_tileo = z_tile 22 | @level.get_block(@x_tileo, @z_tileo).add_entity(self) unless @removed 23 | end 24 | end 25 | 26 | def is_removed 27 | @removed 28 | end 29 | 30 | def remove 31 | @level.get_block(@x_tileo, @z_tileo).remove_entity self 32 | @removed = true 33 | end 34 | 35 | def move 36 | x_steps = ((xa * 100).abs + 1).to_i 37 | x_steps.downto(1) do |i| 38 | xxa = @xa 39 | if is_free(@x + xxa * i / x_steps, @z) 40 | @x += xxa * i / x_steps 41 | break 42 | end 43 | 44 | @xa = 0 45 | end 46 | 47 | z_steps = ((@za * 100).abs + 1).to_i 48 | z_steps.downto(1) do |i| 49 | zza = @za 50 | if is_free(@x, @z + zza * i / z_steps) 51 | @z += zza * i / z_steps 52 | break 53 | end 54 | 55 | @za = 0 56 | end 57 | end 58 | 59 | def is_free(xx, yy) 60 | x0 = (xx + 0.5 - r).floor 61 | x1 = (xx + 0.5 + r).floor 62 | y0 = (yy + 0.5 - r).floor 63 | y1 = (yy + 0.5 + r).floor 64 | 65 | return false if @level.get_block(x0, y0).blocks self 66 | return false if @level.get_block(x1, y0).blocks self 67 | return false if @level.get_block(x0, y1).blocks self 68 | return false if @level.get_block(x1, y1).blocks self 69 | 70 | xc = (xx + 0.5).floor 71 | zc = (yy + 0.5).floor 72 | 73 | rr = 2 74 | 75 | (zc - rr).upto(zc + rr) do |z| 76 | (xc - rr).upto(xc + rr) do |x| 77 | level.get_block(x, z).entities.each do |e| 78 | next if e == self 79 | 80 | if (!e.blocks(self, @x, @z, @r) && e.blocks(self, xx, yy, r)) 81 | e.collide self 82 | collide e 83 | return false 84 | end 85 | end 86 | end 87 | end 88 | 89 | true 90 | end 91 | 92 | def collide(entity); end 93 | 94 | def blocks(entity, x2, z2, r2) 95 | return false if entity.is_a?(Bullet) && entity.owner == self 96 | 97 | # TODO: Can be tightened down to a single expression! 98 | return false if @x + @r <= x2 - r2 99 | return false if @x - @r >= x2 + r2 100 | return false if @z + @r <= z2 - r2 101 | return false if @z - @r >= z2 + r2 102 | 103 | true 104 | end 105 | 106 | def contains(x2, z2) 107 | # TODO: Can be tightened down to a single expression! 108 | return false if @x + @r <= x2 109 | return false if @x - @r >= x2 110 | return false if @z + @r <= z2 111 | return false if @z - @r >= z2 112 | 113 | true 114 | end 115 | 116 | def is_inside(x0, z0, x1, z1) 117 | # TODO: Can be tightened down to a single expression! 118 | return false if @x + @r <= x0 119 | return false if @x - @r >= x1 120 | return false if @z + @r <= z0 121 | return false if @z - @r >= z1 122 | 123 | true 124 | end 125 | 126 | def use(source, item) 127 | false 128 | end 129 | 130 | def tick; end 131 | end -------------------------------------------------------------------------------- /escape/entities/eye_boss_entity.rb: -------------------------------------------------------------------------------- 1 | class EyeBossEntity < EnemyEntity 2 | def initialize(x, z) 3 | super(x, z, 4 * 8 + 4, Art.get_col(0xFFFF00)) 4 | @health = 10 5 | @r = 0.3 6 | @run_speed = 4 7 | @spin_speed *= 1.5 8 | @flying = true 9 | end 10 | 11 | def die 12 | Sound::BOSSKILL.play 13 | @level.add_entity KeyEntity.new(@x, @z) 14 | end 15 | end -------------------------------------------------------------------------------- /escape/entities/eye_entity.rb: -------------------------------------------------------------------------------- 1 | class EyeEntity < EnemyEntity 2 | def initialize(x, z) 3 | super(x, z, 4 * 8 + 4, Art.get_col(0x84ECFF)) 4 | @health = 4 5 | @r = 0.3 6 | @run_speed = 2 7 | @spin_speed *= 1.5 8 | @flying = true 9 | end 10 | end -------------------------------------------------------------------------------- /escape/entities/item.rb: -------------------------------------------------------------------------------- 1 | class Item 2 | attr_accessor :icon, :color, :name, :description 3 | 4 | def initialize(icon, color, name, description) 5 | @icon = icon 6 | @color = color 7 | @name = name 8 | @description = description 9 | end 10 | 11 | NONE = new(-1, 0xFFC363, "", "") 12 | POWER_GLOVE = new( 0, 0xFFC363, "Power Glove", "Smaaaash!!") 13 | PISTOL = new( 1, 0xEAEAEA, "Pistol", "Pew, pew, pew!") 14 | FLIPPERS = new( 2, 0x7CBBFF, "Flippers", "Splish splash!") 15 | CUTTERS = new( 3, 0xCCCCCC, "Cutters", "Snip, snip!") 16 | SKATES = new( 4, 0xAE70FF, "Skates", "Sharp!") 17 | KEY = new( 5, 0xFF4040, "Key", "How did you get this?") 18 | POTION = new( 6, 0x4AFF47, "Potion", "Healthy!") 19 | end -------------------------------------------------------------------------------- /escape/entities/key_entity.rb: -------------------------------------------------------------------------------- 1 | class KeyEntity < Entity 2 | attr_accessor :y, :ya, :sprite 3 | 4 | COLOR = Art.get_col(0x00ffff) 5 | 6 | def initialize(x, z) 7 | super() 8 | @x = x 9 | @z = z 10 | @y = 0.5 11 | @ya = 0.025 12 | @sprite = Sprite.new(0, 0, 0, 16 + 3, COLOR) 13 | end 14 | 15 | def tick 16 | move 17 | @y += @ya 18 | @y = 0 if @y < 0 19 | @ya -= 0.005 20 | @sprite.y = @y 21 | end 22 | 23 | def collide(entity) 24 | return unless entity.is_a?(Player) 25 | 26 | Sound::KEY.play 27 | entity.keys += 1 28 | remove 29 | end 30 | end -------------------------------------------------------------------------------- /escape/entities/player.rb: -------------------------------------------------------------------------------- 1 | class Player < Entity 2 | attr_accessor :bob, :bob_phase, :turn_bob, :selected_slot, :item_use_time, :y, :ya, :hurt_time, :health, :keys, :loot, :dead, :dead_time, :ammo, :potions, :last_block, :sliding, :items 3 | 4 | def initialize 5 | super 6 | @selected_slot = 0 7 | @hurt_time = 0 8 | @health = 20 9 | @keys = 0 10 | @loot = 0 11 | @dead = false 12 | @dead_time = 0 13 | @ammo = 0 14 | @potions = 0 15 | @r = 0.3 16 | @y = 0.0 17 | @bob = @bob_phase = @turn_bob = 0.0 18 | @ya = 0.0 19 | @time = 0 20 | @item_use_time = 0 21 | @sliding = false 22 | @items = Array.new(8) { Item::NONE } 23 | end 24 | 25 | def tick(up, down, left, right, turn_left, turn_right) 26 | if @dead 27 | up = down = left = right = turn_left = turn_right = false 28 | @dead_time += 1 29 | @level.lose if @dead_time > 60 * 2 30 | else 31 | @time += 1 32 | end 33 | 34 | @item_use_time -= 1 if @item_use_time > 0 35 | @hurt_time -= 1 if @hurt_time > 0 36 | 37 | on_block = @level.get_block((x + 0.5).to_i, (z + 0.5).to_i) 38 | fh = on_block.get_floor_height self 39 | Sound::SPLASH.play if on_block.is_a?(WaterBlock) && !@last_block.is_a?(WaterBlock) 40 | @last_block = on_block 41 | 42 | fh -= 0.6 if @dead 43 | 44 | if fh > @y 45 | @y += (fh - y) * 0.2 46 | @ya = 0 47 | else 48 | @ya -= 0.01 49 | @y += @ya 50 | if @y < fh 51 | @y = fh 52 | @ya = 0 53 | end 54 | end 55 | 56 | rot_speed = 0.05 57 | walk_speed = 0.03 * on_block.get_walk_speed(self) 58 | 59 | @rota += rot_speed if turn_left 60 | @rota -= rot_speed if turn_right 61 | 62 | xm = 0 63 | zm = 0 64 | zm -= 1 if up 65 | zm += 1 if down 66 | xm -= 1 if left 67 | xm += 1 if right 68 | dd = xm * xm + zm * zm 69 | dd = dd > 0 ? Math.sqrt(dd) : 1 70 | xm /= dd 71 | zm /= dd 72 | 73 | @bob *= 0.6 74 | @turn_bob *= 0.8 75 | @turn_bob += @rota 76 | @bob += Math.sqrt(xm * xm + zm * zm) 77 | @bob_phase += Math.sqrt(xm * xm + zm * zm) * on_block.get_walk_speed(self) 78 | was_sliding = @sliding 79 | @sliding = false 80 | 81 | if on_block.is_a?(IceBlock) && get_selected_item != Item::SKATES 82 | if @xa * @xa > @za * @za 83 | @sliding = true 84 | @za = 0 85 | @xa = @xa > 0 ? 0.08 : -0.08 86 | @z += ((@z + 0.5).to_i - @z) * 0.2 87 | elsif @xa * @xa < @za * @za 88 | @sliding = true 89 | @xa = 0 90 | @za = @za > 0 ? 0.08 : -0.08 91 | @x += ((@x + 0.5).to_i - @x) * 0.2 92 | else 93 | @xa -= (xm * Math.cos(@rot) + zm * Math.sin(@rot)) * 0.1 94 | @za -= (zm * Math.cos(@rot) - xm * Math.sin(@rot)) * 0.1 95 | end 96 | 97 | Sound::SLIDE.play if !was_sliding && @sliding 98 | else 99 | @xa -= (xm * Math.cos(@rot) + zm * Math.sin(@rot)) * walk_speed 100 | @za -= (zm * Math.cos(@rot) - xm * Math.sin(@rot)) * walk_speed 101 | end 102 | 103 | move 104 | 105 | friction = on_block.get_friction self 106 | @xa *= friction 107 | @za *= friction 108 | @rot += @rota 109 | @rota *= 0.4 110 | end 111 | 112 | def activate 113 | return if @dead 114 | return if @item_use_time > 0 115 | item = @items[@selected_slot] 116 | 117 | if item == Item::PISTOL 118 | if @ammo > 0 119 | Sound::SHOOT.play 120 | @item_use_time = 10 121 | @level.add_entity Bullet.new(self, @x, @z, @rot, 1, 0, 0xffffff) 122 | @ammo -= 1 123 | end 124 | return 125 | end 126 | 127 | if item == Item::POTION 128 | if @potions > 0 && @health < 20 129 | Sound::POTION.play 130 | @item_use_time = 20 131 | @health += 5 + rand(6) 132 | @health = 20 if @health > 20 133 | @potions -= 1 134 | end 135 | return 136 | end 137 | 138 | @item_use_time = 10 if item == Item::KEY || item == Item::POWER_GLOVE || item == Item::CUTTERS 139 | 140 | xa = 2 * Math.sin(@rot) 141 | za = 2 * Math.cos(@rot) 142 | 143 | rr = 3 144 | xc = (@x + 0.5).to_i 145 | zc = (@z + 0.5).to_i 146 | 147 | possible_hits = [] 148 | 149 | (zc - rr).upto(zc + rr) do |z2| 150 | (xc - rr).upto(xc + rr) do |x2| 151 | @level.get_block(x2, z2).entities.each do |e| 152 | possible_hits << e unless e == self 153 | end 154 | end 155 | end 156 | 157 | divs = 100 158 | 159 | divs.times do |i| 160 | xx = @x + xa * i / divs 161 | zz = @z + za * i / divs 162 | possible_hits.each { |e| return if e.contains(xx, zz) && e.use(self, @items[@selected_slot]) } 163 | xt = (xx + 0.5).to_i 164 | zt = (zz + 0.5).to_i 165 | if xt != (@x + 0.5).to_i || zt != (@z + 0.5).to_i 166 | block = @level.get_block(xt, zt) 167 | return if block.use(level, @items[@selected_slot]) 168 | return if block.blocks(self) 169 | end 170 | end 171 | end 172 | 173 | #def blocks(entity, x2, z2, r2) 174 | # super(entity, x2, z2, r2) 175 | #end 176 | 177 | def get_selected_item 178 | @items[@selected_slot] 179 | end 180 | 181 | def add_loot(item) 182 | @ammo += 20 if item == Item::PISTOL 183 | @potions += 1 if item == Item::POTION 184 | 185 | @items.each do |my_item| 186 | if my_item == item 187 | @level.show_loot_screen(item) if @level 188 | return 189 | end 190 | end 191 | 192 | @items.each_with_index do |my_item, i| 193 | if my_item == Item::NONE 194 | @items[i] = item 195 | @selected_slot = i 196 | @item_use_time = 0 197 | @level.show_loot_screen(item) if @level 198 | return 199 | end 200 | end 201 | end 202 | 203 | def hurt(enemy, dmg) 204 | return if @hurt_time > 0 || @dead 205 | 206 | @hurt_time = 40 207 | @health -= dmg 208 | 209 | if @health <= 0 210 | @health = 0 211 | Sound::DEATH.play 212 | @dead = true 213 | end 214 | 215 | Sound::HURT.play 216 | 217 | xd = enemy.x - @x 218 | zd = enemy.z - @z 219 | dd = Math.sqrt(xd * xd + zd * zd) 220 | @xa -= xd / dd * 0.1 221 | @za -= zd / dd * 0.1 222 | @rota += (rand - 0.5) * 0.2 223 | end 224 | 225 | def collide(entity) 226 | if entity.is_a?(Bullet) 227 | bullet = entity 228 | return if bullet.owner.class == self.class 229 | return if @hurt_time > 0 230 | entity.remove 231 | hurt(entity, 1) 232 | end 233 | end 234 | 235 | def win 236 | @level.win 237 | end 238 | end -------------------------------------------------------------------------------- /escape/escape.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path(File.dirname(__FILE__)) 2 | 3 | ASSETS_DIR = File.expand_path(File.dirname(__FILE__) + "/assets") 4 | 5 | require 'java' 6 | require 'jruby/synchronized' 7 | 8 | require 'input_handler' 9 | require 'component' 10 | require 'game' 11 | 12 | require 'entities/item' 13 | 14 | require 'level/block/block' 15 | require 'level/block/solid_block' 16 | require 'level/block/loot_block' 17 | require 'level/block/chest_block' 18 | require 'level/block/water_block' 19 | require 'level/block/ice_block' 20 | require 'level/block/door_block' 21 | require 'level/block/locked_door_block' 22 | require 'level/block/vanish_block' 23 | require 'level/block/torch_block' 24 | require 'level/block/bars_block' 25 | require 'level/block/ladder_block' 26 | require 'level/block/pressure_plate_block' 27 | require 'level/block/pit_block' 28 | 29 | require 'level/level' 30 | require 'level/start_level' 31 | require 'level/overworld_level' 32 | require 'level/dungeon_level' 33 | require 'level/ice_level' 34 | 35 | require 'gui/bitmap' 36 | require 'gui/bitmap3d' 37 | require 'gui/screen' 38 | require 'gui/sprite' 39 | require 'gui/rubble_sprite' 40 | require 'gui/poof_sprite' 41 | 42 | require 'menu/menu' 43 | require 'menu/title_menu' 44 | require 'menu/instructions_menu' 45 | require 'menu/about_menu' 46 | require 'menu/pause_menu' 47 | require 'menu/got_loot_menu' 48 | 49 | require 'art' 50 | require 'sound' 51 | 52 | require 'entities/entity' 53 | require 'entities/player' 54 | require 'entities/bullet' 55 | require 'entities/key_entity' 56 | require 'entities/enemy_entity' 57 | require 'entities/bat_entity' 58 | require 'entities/bat_boss_entity' 59 | require 'entities/boulder_entity' 60 | require 'entities/eye_entity' 61 | require 'entities/eye_boss_entity' 62 | 63 | -------------------------------------------------------------------------------- /escape/game.rb: -------------------------------------------------------------------------------- 1 | java_import java.awt.event.KeyEvent 2 | 3 | class Game 4 | attr_accessor :menu, :player, :time, :level, :pause_time 5 | 6 | def initialize 7 | @pause_time = 0 8 | @time = 0 9 | @player = nil 10 | @menu = TitleMenu.new 11 | end 12 | 13 | def new_game 14 | Level.clear 15 | @level = Level.load_level(self, "start") 16 | 17 | @player = Player.new 18 | @player.level = @level 19 | @level.player = @player 20 | @player.x = @level.x_spawn 21 | @player.z = @level.y_spawn 22 | @level.add_entity(@player) 23 | @player.rot = Math::PI + 0.4 24 | end 25 | 26 | def switch_level(name, id) 27 | @pause_time = 30 28 | @level.remove_entity_immediately(@player) 29 | @level = Level.load_level(self, name) 30 | @level.find_spawn(id) 31 | @player.x = @level.x_spawn 32 | @player.z = @level.y_spawn 33 | @level.get_block(@level.x_spawn, @level.y_spawn).wait = true 34 | @player.x += Math.sin(@player.rot) * 0.2 35 | @player.z += Math.cos(@player.rot) * 0.2 36 | @level.add_entity(@player) 37 | end 38 | 39 | def tick(keys) 40 | if @pause_time > 0 41 | @pause_time -= 1 42 | return 43 | end 44 | 45 | @time += 1 46 | 47 | strafe = keys[KeyEvent::VK_CONTROL] || keys[KeyEvent::VK_ALT] || keys[KeyEvent::VK_ALT_GRAPH] || keys[KeyEvent::VK_SHIFT] 48 | 49 | lk = keys[KeyEvent::VK_LEFT] || keys[KeyEvent::VK_NUMPAD4] 50 | rk = keys[KeyEvent::VK_RIGHT] || keys[KeyEvent::VK_NUMPAD6] 51 | 52 | up = keys[KeyEvent::VK_W] || keys[KeyEvent::VK_UP] || keys[KeyEvent::VK_NUMPAD8] 53 | down = keys[KeyEvent::VK_S] || keys[KeyEvent::VK_DOWN] || keys[KeyEvent::VK_NUMPAD2] 54 | left = keys[KeyEvent::VK_A] || (strafe && lk) 55 | right = keys[KeyEvent::VK_D] || (strafe && rk) 56 | 57 | turn_left = keys[KeyEvent::VK_Q] || (!strafe && lk) 58 | turn_right = keys[KeyEvent::VK_E] || (!strafe && rk) 59 | 60 | use = keys[KeyEvent::VK_SPACE] 61 | 62 | i = 0 63 | while i < 8 64 | next(i += 1) unless keys[KeyEvent::VK_1 + i] 65 | keys[KeyEvent::VK_1 + i] = false 66 | if @player 67 | @player.selected_slot = i 68 | @player.item_use_time = 0 69 | end 70 | i += 1 71 | end 72 | 73 | if keys[KeyEvent::VK_ESCAPE] 74 | keys[KeyEvent::VK_ESCAPE] = false 75 | set_menu PauseMenu.new unless menu 76 | end 77 | 78 | keys[KeyEvent::VK_SPACE] = false if use 79 | 80 | if menu 81 | keys[KeyEvent::VK_W] = keys[KeyEvent::VK_UP] = keys[KeyEvent::VK_NUMPAD8] = false 82 | keys[KeyEvent::VK_S] = keys[KeyEvent::VK_DOWN] = keys[KeyEvent::VK_NUMPAD2] = false 83 | keys[KeyEvent::VK_A] = false 84 | keys[KeyEvent::VK_D] = false 85 | 86 | menu.tick self, up, down, left, right, use 87 | else 88 | player.tick up, down, left, right, turn_left, turn_right 89 | player.activate if use 90 | 91 | level.tick 92 | end 93 | end 94 | 95 | def get_loot(item) 96 | @player.add_loot(item) 97 | end 98 | 99 | def win(player) 100 | set_menu(WinMenu.new(player)) 101 | end 102 | 103 | def set_menu(menu) 104 | @menu = menu 105 | end 106 | 107 | def lose(player) 108 | set_menu(LoseMenu.new(player)) 109 | end 110 | end -------------------------------------------------------------------------------- /escape/gui/bitmap.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | class Bitmap 3 | attr_accessor :pixels 4 | attr_reader :height, :width 5 | 6 | CHARS = "" + 7 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ.,!?\"'/\\<>()[]{}" + 8 | "abcdefghijklmnopqrstuvwxyz_ " + 9 | "0123456789+-=*:;ÖÅÄå " + 10 | ""; 11 | 12 | def initialize(width, height) 13 | @width = width 14 | @height = height 15 | @pixels = Array.new(width * height, 0) 16 | end 17 | 18 | def draw(bitmap, x_offs, y_offs) 19 | y, max_y = 0, bitmap.height 20 | while y < max_y 21 | y_pix = y + y_offs 22 | next(y += 1) if (y_pix < 0 || y_pix >= @height) 23 | 24 | x, max_x = 0, bitmap.width 25 | while x < max_x 26 | x_pix = x + x_offs 27 | next(x += 1) if (x_pix < 0 || x_pix >= @width) 28 | 29 | src = bitmap.pixels[x + y * bitmap.width] 30 | @pixels[x_pix + y_pix * width] = src 31 | x += 1 32 | end 33 | y += 1 34 | end 35 | end 36 | 37 | def flip_draw(bitmap, x_offs, y_offs) 38 | y, max_y = 0, bitmap.height 39 | while y < max_y 40 | y_pix = y + y_offs 41 | next(y += 1) if (y_pix < 0 || y_pix >= @height) 42 | 43 | x, max_x = 0, bitmap.width 44 | while x < max_x 45 | x_pix = x_offs + bitmap.width - x - 1 46 | next(x += 1) if (x_pix < 0 || x_pix >= @width) 47 | 48 | src = bitmap.pixels[x + y * bitmap.width] 49 | @pixels[x_pix + y_pix * width] = src 50 | x += 1 51 | end 52 | y += 1 53 | end 54 | end 55 | 56 | def draw_bitmap(bitmap, x_offs, y_offs, xo, yo, w, h, col) 57 | y, max_y = 0, h 58 | while y < max_y 59 | y_pix = y + y_offs 60 | next(y += 1) if (y_pix < 0 || y_pix >= @height) 61 | 62 | x, max_x = 0, w 63 | while x < max_x 64 | x_pix = x + x_offs 65 | next(x += 1) if (x_pix < 0 || x_pix >= @width) 66 | 67 | src = bitmap.pixels[(x + xo) + (y + yo) * bitmap.width] 68 | 69 | if src >= 0 70 | @pixels[x_pix + y_pix * width] = src * col 71 | end 72 | x += 1 73 | end 74 | y += 1 75 | end 76 | end 77 | 78 | def scale_draw(bitmap, scale, x_offs, y_offs, xo, yo, w, h, col) 79 | y, max_y = 0, (h * scale) 80 | while y < max_y 81 | y_pix = y + y_offs 82 | next(y += 1) if (y_pix < 0 || y_pix >= @height) 83 | 84 | x, max_x = 0, (w * scale) 85 | while x < max_x 86 | x_pix = x + x_offs 87 | next(x += 1) if (x_pix < 0 || x_pix >= @width) 88 | 89 | src = bitmap.pixels[(x / scale + xo) + (y / scale + yo) * bitmap.width] 90 | 91 | if src >= 0 92 | @pixels[x_pix + y_pix * width] = src * col 93 | end 94 | x += 1 95 | end 96 | y += 1 97 | end 98 | end 99 | 100 | def draw_string(string, x, y, col) 101 | string.chars.each_with_index do |char, i| 102 | ch = CHARS.index(char) 103 | next if ch < 0 104 | 105 | xx = ch % 42 106 | yy = ch / 42 107 | draw_bitmap(Art::FONT, x + i * 6, y, xx * 6, yy * 8, 5, 8, col) 108 | end 109 | end 110 | 111 | def fill(x0, y0, x1, y1, color) 112 | y0.upto(y1 - 1) do |y| 113 | x0.upto(x1 - 1) do |x| 114 | @pixels[x + y * @width] = color 115 | end 116 | end 117 | end 118 | end -------------------------------------------------------------------------------- /escape/gui/bitmap3d.rb: -------------------------------------------------------------------------------- 1 | class Bitmap3D < Bitmap 2 | attr_accessor :z_buffer, :z_buffer_wall, :x_cam, :y_cam, :z_cam, :r_cos, :r_sin, :fov, :x_center, :y_center, :rot 3 | 4 | def initialize(width, height) 5 | super(width, height) 6 | end 7 | 8 | def render(game) 9 | @z_buffer = Array.new(width * height, 10000) 10 | @z_buffer_wall = Array.new(width, 0.0) 11 | 12 | @rot = game.player.rot 13 | @x_cam = game.player.x - Math.sin(@rot) * 0.3 14 | @y_cam = game.player.z - Math.cos(@rot) * 0.3 15 | @z_cam = -0.2 + Math.sin(game.player.bob_phase * 0.4) * 0.01 * game.player.bob - game.player.y 16 | 17 | @x_center = @width / 2.0 18 | @y_center = @height / 3.0 19 | 20 | @r_cos = Math.cos(@rot) 21 | @r_sin = Math.sin(@rot) 22 | 23 | @fov = @height 24 | 25 | level = game.level 26 | r = 6 27 | 28 | x_center = @x_cam.floor 29 | z_center = @y_cam.floor 30 | 31 | zb, max_zb = (z_center - r), (z_center + r) 32 | while zb <= max_zb 33 | xb, max_xb = (x_center - r), (x_center + r) 34 | while xb <= max_xb 35 | c = level.get_block(xb, zb) 36 | e = level.get_block(xb + 1, zb) 37 | s = level.get_block(xb, zb + 1) 38 | 39 | if c.is_a?(DoorBlock) 40 | rr = 1 / 8.0 41 | openness = 1 - c.openness * 7 / 8.0 42 | 43 | if e.solid_render 44 | render_wall(xb + openness, zb + 0.5 - rr, xb, zb + 0.5 - rr, c.tex, (c.col & 0xfefefe) >> 1, 0, openness) 45 | render_wall(xb, zb + 0.5 + rr, xb + openness, zb + 0.5 + rr, c.tex, (c.col & 0xfefefe) >> 1, openness, 0) 46 | render_wall(xb + openness, zb + 0.5 + rr, xb + openness, zb + 0.5 - rr, c.tex, c.col, 0.5 - rr, 0.5 + rr) 47 | else 48 | render_wall(xb + 0.5 - rr, zb, xb + 0.5 - rr, zb + openness, c.tex, c.col, openness, 0) 49 | render_wall(xb + 0.5 + rr, zb + openness, xb + 0.5 + rr, zb, c.tex, c.col, 0, openness) 50 | render_wall(xb + 0.5 - rr, zb + openness, xb + 0.5 + rr, zb + openness, c.tex, (c.col & 0xfefefe) >> 1, 0.5 - rr, 0.5 + rr) 51 | end 52 | end 53 | 54 | if c.solid_render 55 | render_wall(xb + 1, zb + 1, xb + 1, zb, c.tex, c.col) if !e.solid_render 56 | render_wall(xb, zb + 1, xb + 1, zb + 1, c.tex, (c.col & 0xfefefe) >> 1) if !s.solid_render 57 | else 58 | render_wall(xb + 1, zb, xb + 1, zb + 1, e.tex, e.col) if e.solid_render 59 | render_wall(xb + 1, zb + 1, xb, zb + 1, s.tex, (s.col & 0xfefefe) >> 1) if s.solid_render 60 | end 61 | xb += 1 62 | end 63 | zb += 1 64 | end 65 | 66 | zb, max_zb = (z_center - r), (z_center + r) 67 | while zb <= max_zb 68 | xb, max_xb = (x_center - r), (x_center + r) 69 | while xb <= max_xb 70 | c = level.get_block(xb, zb) 71 | 72 | c.entities.each do |e| 73 | e.sprites.each do |sprite| 74 | render_sprite(e.x + sprite.x, 0 - sprite.y, e.z + sprite.z, sprite.tex, sprite.col) 75 | end 76 | end 77 | 78 | c.sprites.each do |sprite| 79 | render_sprite(xb + sprite.x, 0 - sprite.y, zb + sprite.z, sprite.tex, sprite.col) 80 | end 81 | xb += 1 82 | end 83 | zb += 1 84 | end 85 | 86 | render_floor level 87 | end 88 | 89 | def render_wall(x0, y0, x1, y1, tex, color, xt0 = 0, xt1 = 1) 90 | xc0 = ((x0 - 0.5) - @x_cam) * 2 91 | yc0 = ((y0 - 0.5) - @y_cam) * 2 92 | 93 | xx0 = xc0 * @r_cos - yc0 * @r_sin 94 | u0 = ((-0.5) - @z_cam) * 2 95 | l0 = ((+0.5) - @z_cam) * 2 96 | zz0 = yc0 * @r_cos + xc0 * @r_sin 97 | 98 | xc1 = ((x1 - 0.5) - @x_cam) * 2 99 | yc1 = ((y1 - 0.5) - @y_cam) * 2 100 | 101 | xx1 = xc1 * @r_cos - yc1 * @r_sin 102 | u1 = ((-0.5) - @z_cam) * 2 103 | l1 = ((+0.5) - @z_cam) * 2 104 | zz1 = yc1 * @r_cos + xc1 * @r_sin 105 | 106 | xt0 *= 16 107 | xt1 *= 16 108 | 109 | z_clip = 0.2 110 | 111 | return if zz0 < z_clip && zz1 < z_clip 112 | 113 | if (zz0 < z_clip) 114 | p = (z_clip - zz0) / (zz1 - zz0) 115 | zz0 = zz0 + (zz1 - zz0) * p 116 | xx0 = xx0 + (xx1 - xx0) * p 117 | xt0 = xt0 + (xt1 - xt0) * p 118 | end 119 | 120 | if (zz1 < z_clip) 121 | p = (z_clip - zz0) / (zz1 - zz0) 122 | zz1 = zz0 + (zz1 - zz0) * p 123 | xx1 = xx0 + (xx1 - xx0) * p 124 | xt1 = xt0 + (xt1 - xt0) * p 125 | end 126 | 127 | x_pixel0 = @x_center - (xx0 / zz0 * fov) 128 | x_pixel1 = @x_center - (xx1 / zz1 * fov) 129 | 130 | return if x_pixel0 >= x_pixel1 131 | xp0 = x_pixel0.ceil 132 | xp1 = x_pixel1.ceil 133 | xp0 = 0 if xp0 < 0 134 | xp1 = @width if xp1 > @width 135 | 136 | y_pixel00 = u0 / zz0 * fov + @y_center 137 | y_pixel01 = l0 / zz0 * fov + @y_center 138 | y_pixel10 = u1 / zz1 * fov + @y_center 139 | y_pixel11 = l1 / zz1 * fov + @y_center 140 | 141 | iz0 = 1 / zz0 142 | iz1 = 1 / zz1 143 | 144 | iza = iz1 - iz0 145 | 146 | ixt0 = xt0 * iz0 147 | ixta = xt1 * iz1 - ixt0 148 | iw = 1 / (x_pixel1 - x_pixel0) 149 | 150 | x, max_x = xp0, xp1 - 1 151 | while x <= max_x 152 | pr = (x - x_pixel0) * iw 153 | iz = iz0 + iza * pr 154 | 155 | next(x += 1) if @z_buffer_wall[x] > iz 156 | @z_buffer_wall[x] = iz 157 | x_tex = ((ixt0 + ixta * pr) / iz).to_i 158 | 159 | y_pixel0 = y_pixel00 + (y_pixel10 - y_pixel00) * pr - 0.5 160 | y_pixel1 = y_pixel01 + (y_pixel11 - y_pixel01) * pr 161 | 162 | yp0 = y_pixel0.ceil 163 | yp1 = y_pixel1.ceil 164 | yp0 = 0 if yp0 < 0 165 | yp1 = @height if yp1 > @height 166 | 167 | ih = 1 / (y_pixel1 - y_pixel0) 168 | 169 | y, max_y = yp0, yp1 - 1 170 | while y <= max_y 171 | pry = (y - y_pixel0) * ih 172 | y_tex = (16 * pry).to_i 173 | @pixels[x + y * @width] = (Art::WALLS.pixels[((x_tex) + (tex % 8) * 16) + (y_tex + tex / 8 * 16) * 128] * color) 174 | @z_buffer[x + y * @width] = 1 / iz * 4 175 | y += 1 176 | end 177 | x += 1 178 | end 179 | end 180 | 181 | def render_floor(level) 182 | y, max_y = 0, @height 183 | while y < max_y 184 | yd = ((y + 0.5) - @y_center) / @fov 185 | 186 | floor = true 187 | zd = (4 - @z_cam * 8) / yd 188 | if yd < 0 189 | floor = false 190 | zd = (4 + @z_cam * 8) / -yd 191 | end 192 | 193 | x, max_x = 0, @width 194 | while x < max_x 195 | next(x += 1) if @z_buffer[x + y * @width] <= zd 196 | 197 | xd = (@x_center - x) / fov 198 | xd *= zd 199 | 200 | xx = xd * @r_cos + zd * @r_sin + (@x_cam + 0.5) * 8 201 | yy = zd * @r_cos - xd * @r_sin + (@y_cam + 0.5) * 8 202 | 203 | x_pix = (xx * 2).to_i 204 | y_pix = (yy * 2).to_i 205 | x_tile = x_pix >> 4 206 | y_tile = y_pix >> 4 207 | 208 | block = level.get_block(x_tile, y_tile) 209 | col = block.floor_col 210 | tex = block.floor_tex 211 | unless floor 212 | col = block.ceil_col 213 | tex = block.ceil_tex 214 | end 215 | 216 | if tex < 0 217 | @z_buffer[x + y * @width] = -1 218 | else 219 | @z_buffer[x + y * @width] = zd 220 | @pixels[x + y * @width] = Art::FLOORS.pixels[((x_pix & 15) + (tex % 8) * 16) + ((y_pix & 15) + (tex / 8) * 16) * 128] * col 221 | end 222 | x += 1 223 | end 224 | y += 1 225 | end 226 | end 227 | 228 | def render_sprite(x, y, z, tex, color) 229 | xc = (x - @x_cam) * 2 - @r_sin * 0.2 230 | yc = (y - @z_cam) * 2 231 | zc = (z - @y_cam) * 2 - @r_cos * 0.2 232 | 233 | xx = xc * @r_cos - zc * @r_sin 234 | yy = yc 235 | zz = zc * @r_cos + xc * @r_sin 236 | 237 | return if zz < 0.1 238 | 239 | x_pixel = @x_center - (xx / zz * fov) 240 | y_pixel = yy / zz * fov + @y_center 241 | 242 | x_pixel0 = x_pixel - @height / zz 243 | x_pixel1 = x_pixel + @height / zz 244 | 245 | y_pixel0 = y_pixel - @height / zz 246 | y_pixel1 = y_pixel + @height / zz 247 | 248 | xp0 = x_pixel0.ceil 249 | xp1 = x_pixel1.ceil 250 | yp0 = y_pixel0.ceil 251 | yp1 = y_pixel1.ceil 252 | 253 | xp0 = 0 if xp0 < 0 254 | xp1 = @width if xp1 > @width 255 | yp0 = 0 if yp0 < 0 256 | yp1 = @height if yp1 > @height 257 | zz *= 4 258 | 259 | yp, max_yp = yp0, yp1 - 1 260 | while yp <= max_yp 261 | ypr = (yp - y_pixel0) / (y_pixel1 - y_pixel0) 262 | yt = (ypr * 16).to_i 263 | xp, max_xp = xp0, xp1 - 1 264 | while xp <= max_xp 265 | xpr = (xp - x_pixel0) / (x_pixel1 - x_pixel0) 266 | xt = (xpr * 16).to_i 267 | if (@z_buffer[xp + yp * @width] > zz) 268 | col = Art::SPRITES.pixels[(xt + tex % 8 * 16) + (yt + (tex / 8) * 16) * 128] 269 | if col >= 0 270 | @pixels[xp + yp * @width] = col * color 271 | @z_buffer[xp + yp * @width] = zz 272 | end 273 | end 274 | xp += 1 275 | end 276 | yp += 1 277 | end 278 | end 279 | 280 | def post_process(level) 281 | i, max = 0, width * height 282 | while i < max 283 | zl = @z_buffer[i] 284 | if zl < 0 285 | xx = ((i % width) - rot * 512 / (Math::PI * 2)).floor & 511 286 | yy = i / width 287 | @pixels[i] = Art::SKY.pixels[xx + yy * 512] * 0x444455 288 | else 289 | xp = i % @width 290 | yp = (i / @width) * 14 291 | 292 | xx = ((i % @width - @width / 2.0) / @width) 293 | col = @pixels[i] 294 | brightness = (300 - zl * 6 * (xx * xx * 2 + 1)).to_i 295 | brightness = brightness + ((xp + yp) & 3) * 4 >> 4 << 4 296 | brightness = 0 if brightness < 0 297 | brightness = 255 if brightness > 255 298 | 299 | r = (col >> 16) & 0xff 300 | g = (col >> 8) & 0xff 301 | b = (col) & 0xff 302 | 303 | r = r * brightness / 255 304 | g = g * brightness / 255 305 | b = b * brightness / 255 306 | 307 | @pixels[i] = r << 16 | g << 8 | b 308 | end 309 | i += 1 310 | end 311 | end 312 | end -------------------------------------------------------------------------------- /escape/gui/poof_sprite.rb: -------------------------------------------------------------------------------- 1 | class PoofSprite < Sprite 2 | def initialize(x, y, z) 3 | super(x, y, z, 5, 0x222222) 4 | @life = 20 5 | end 6 | 7 | def tick 8 | @life -= 1 9 | @removed = true if @life <= 0 10 | end 11 | end -------------------------------------------------------------------------------- /escape/gui/rubble_sprite.rb: -------------------------------------------------------------------------------- 1 | class RubbleSprite < Sprite 2 | def initialize 3 | super(rand - 0.5, rand * 0.8, rand - 0.5, 2, 0x555555) 4 | @xa = rand - 0.5 5 | @ya = rand 6 | @za = rand - 0.5 7 | end 8 | 9 | def tick 10 | @x += @xa * 0.03 11 | @y += @ya * 0.03 12 | @z += @za * 0.03 13 | @ya -= 0.1 14 | if @y < 0 15 | @y = 0 16 | @xa *= 0.8 17 | @za *= 0.8 18 | @removed = true if rand < 0.04 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /escape/gui/screen.rb: -------------------------------------------------------------------------------- 1 | class Screen < Bitmap 2 | PANEL_HEIGHT = 29 3 | 4 | def initialize(width, height) 5 | super(width, height) 6 | @viewport = Bitmap3D.new(width, height - PANEL_HEIGHT) 7 | end 8 | 9 | def render(game, has_focus) 10 | if game.level 11 | item_used = game.player.item_use_time > 0 12 | item = game.player.items[game.player.selected_slot] 13 | 14 | if game.pause_time > 0 15 | render_entering(game) 16 | else 17 | @viewport.render game 18 | @viewport.post_process game.level 19 | 20 | block = game.level.get_block((game.player.x + 0.5).to_i, (game.player.z + 0.5).to_i) 21 | if block.messages && has_focus 22 | render_messages(block) 23 | end 24 | 25 | draw @viewport, 0, 0 26 | xx = (game.player.turn_bob * 32).to_i 27 | yy = (Math.sin(game.player.bob_phase * 0.4) * 1 * game.player.bob + game.player.bob * 2).to_i 28 | 29 | xx = yy = 0 if item_used 30 | xx += @width / 2 31 | yy += @height - PANEL_HEIGHT - 15 * 3 32 | if item != Item::NONE 33 | scale_draw(Art::ITEMS, 3, xx, yy, 16 * item.icon + 1, 16 + 1 + (item_used ? 16 : 0), 15, 15, Art.get_col(item.color)) 34 | end 35 | 36 | if game.player.hurt_time > 0 || game.player.dead 37 | offs = 1.5 - game.player.hurt_time / 30.0 38 | rnd = rand(111) 39 | offs = 0.5 if game.player.dead 40 | i, max_i = 0, @pixels.length 41 | while i < max_i 42 | xp = ((i % @width) - @viewport.width / 2.0) / @width * 2 43 | yp = ((i / @width) - @viewport.height / 2.0) / @viewport.height * 2 44 | @pixels[i] = (rand(5) / 4) * 0x550000 if (rand + offs < Math.sqrt(xp * xp + yp * yp)) 45 | i += 1 46 | end 47 | end 48 | end 49 | 50 | draw_bitmap(Art::PANEL, 0, @height - PANEL_HEIGHT, 0, 0, @width, PANEL_HEIGHT, Art.get_col(0x707070)) 51 | 52 | draw_string("K", 3, @height - 26 + 0, 0x00ffff) 53 | draw_string(game.player.keys.to_s + "/4", 10, @height - 26 + 0, 0xffffff) 54 | draw_string("T", 3, @height - 26 + 8, 0xffff00) 55 | draw_string(game.player.loot.to_s, 10, @height - 26 + 8, 0xffffff) 56 | draw_string("H", 3, @height - 26 + 16, 0xff0000) 57 | draw_string(game.player.health.to_s, 10, @height - 26 + 16, 0xffffff) 58 | 59 | render_slots(game) 60 | 61 | draw_bitmap(Art::ITEMS, 30 + game.player.selected_slot * 16, @height - PANEL_HEIGHT + 2, 0, 48, 17, 17, Art.get_col(0xffffff)); 62 | 63 | draw_string(item.name, 26 + (8 * 16 - item.name.length() * 4) / 2, @height - 9, 0xffffff); 64 | else 65 | fill 0, 0, @width, @height, 0 66 | end 67 | 68 | if game.menu 69 | render_menu(game) 70 | end 71 | 72 | # Think it's a bad idea to bother detecting focus on a desktop-only version (for now) 73 | # 74 | #unless has_focus 75 | # @pixels.length.times do |i| 76 | # @pixels[i] = (@pixels[i] & 0xfcfcfc) >> 2 77 | # end 78 | # 79 | # if java.lang.System.current_time_millis / 450 % 2 != 0 80 | # msg = "Click to focus!" 81 | # draw_string msg, (@width - msg.length() * 6) / 2, @height / 3 + 4, 0xffffff 82 | # end 83 | #end 84 | end 85 | 86 | def render_entering(game) 87 | fill 0, 0, @width, @height, 0 88 | messages = "Entering " + game.level.name 89 | messages.lines.each_with_index do |msg, y| 90 | draw_string(msg, (@width - msg.length * 6) / 2, (@viewport.height - messages.length * 8) / 2 + y * 8 + 1, 0x111111) 91 | draw_string(msg, (@width - msg.length * 6) / 2, (@viewport.height - messages.length * 8) / 2 + y * 8, 0x555544) 92 | end 93 | end 94 | 95 | def render_messages(block) 96 | block.messages.each_with_index do |msg, y| 97 | draw_string(msg, (@width - msg.length * 6) / 2, (@viewport.height - block.messages.length * 8) / 2 + y * 8 + 1, 0x111111) 98 | draw_string(msg, (@width - msg.length * 6) / 2, (@viewport.height - block.messages.length * 8) / 2 + y * 8, 0x555544) 99 | end 100 | end 101 | 102 | def render_slots(game) 103 | 8.times do |i| 104 | slot_item = game.player.items[i] 105 | if slot_item != Item::NONE 106 | draw_bitmap(Art::ITEMS, 30 + i * 16, @height - PANEL_HEIGHT + 2, slot_item.icon * 16, 0, 16, 16, Art.get_col(slot_item.color)) 107 | 108 | if slot_item == Item::PISTOL 109 | str = game.player.ammo.to_s 110 | draw_string(str, 30 + i * 16 + 17 - str.length() * 6, @height - PANEL_HEIGHT + 1 + 10, 0x555555) 111 | end 112 | 113 | if slot_item == Item::POTION 114 | str = game.player.potions.to_s 115 | draw_string(str, 30 + i * 16 + 17 - str.length() * 6, @height - PANEL_HEIGHT + 1 + 10, 0x555555) 116 | end 117 | end 118 | end 119 | end 120 | 121 | def render_menu(game) 122 | @pixels.length.times do |i| 123 | @pixels[i] = (@pixels[i] & 0xfcfcfc) >> 2 124 | end 125 | game.menu.render self 126 | end 127 | end -------------------------------------------------------------------------------- /escape/gui/sprite.rb: -------------------------------------------------------------------------------- 1 | class Sprite 2 | attr_accessor :removed, :x, :y, :z, :tex, :col 3 | 4 | def initialize(x, y, z, tex, color) 5 | @x = x 6 | @y = y 7 | @z = z 8 | @tex = tex 9 | @col = color || 0x202020 10 | @removed = false 11 | end 12 | 13 | def tick; end 14 | end -------------------------------------------------------------------------------- /escape/input_handler.rb: -------------------------------------------------------------------------------- 1 | java_import java.awt.event.KeyListener 2 | java_import java.awt.event.FocusListener 3 | java_import java.awt.event.MouseListener 4 | java_import java.awt.event.MouseMotionListener 5 | 6 | class InputHandler 7 | include KeyListener 8 | include FocusListener 9 | include MouseListener 10 | include MouseMotionListener 11 | 12 | attr_accessor :keys 13 | 14 | def initialize 15 | super 16 | @keys = Array.new(65536, false) 17 | end 18 | 19 | def mouse_dragged(arg); end 20 | def mouse_moved(arg); end 21 | def mouse_clicked(arg); end 22 | def mouse_entered(arg); end 23 | def mouse_exited(arg); end 24 | def mouse_pressed(arg); end 25 | def mouse_released(arg); end 26 | def focus_gained(arg); end 27 | 28 | def focus_lost(arg) 29 | keys.each_with_index { |k, i| @keys[i] = false } 30 | end 31 | 32 | def key_pressed(e) 33 | code = e.key_code 34 | @keys[code] = true if code > 0 && code < keys.length 35 | end 36 | 37 | def key_released(e) 38 | code = e.key_code 39 | @keys[code] = false if code > 0 && code < keys.length 40 | end 41 | 42 | def key_typed(arg); end 43 | end -------------------------------------------------------------------------------- /escape/level/block/bars_block.rb: -------------------------------------------------------------------------------- 1 | class BarsBlock < Block 2 | def initialize 3 | super 4 | @sprite = Sprite.new(0, 0, 0, 0, 0x202020) 5 | add_sprite @sprite 6 | @blocks_motion = true 7 | @open = false 8 | end 9 | 10 | def use(level, item) 11 | return false if @open 12 | 13 | if item == Item::CUTTERS 14 | Sound::CUT.play 15 | @sprite.tex = 1 16 | @open = true 17 | end 18 | 19 | true 20 | end 21 | 22 | def blocks(entity) 23 | return false if @open && entity.is_a?(Player) 24 | return false if @open && entity.is_a?(Bullet) 25 | @blocks_motion 26 | end 27 | end -------------------------------------------------------------------------------- /escape/level/block/block.rb: -------------------------------------------------------------------------------- 1 | class Block 2 | attr_accessor :tex, :col, :id, :x, :y, :level, :sprites, :entities, :floor_tex, :ceil_tex, :floor_col, :ceil_col, :messages, :solid_render 3 | 4 | def solid_wall 5 | SolidBlock.new 6 | end 7 | 8 | def initialize 9 | @blocks_motion = false 10 | @solid_render = false 11 | @messages = [] 12 | @sprites = [] 13 | @entities = [] 14 | @tex = -1 15 | @col = -1 16 | @floor_col = -1 17 | @ceil_col = -1 18 | @floor_tex = -1 19 | @ceil_tex = -1 20 | @id = @x = @y = @level = nil 21 | end 22 | 23 | def add_sprite(sprite) 24 | @sprites << sprite 25 | end 26 | 27 | def use(level, item) 28 | false 29 | end 30 | 31 | def tick 32 | sprites.each do |sprite| 33 | sprite.tick 34 | sprites.delete(sprite) if sprite.removed 35 | end 36 | end 37 | 38 | def remove_entity(entity) 39 | entities.delete(entity) 40 | end 41 | 42 | def add_entity(entity) 43 | entities << entity 44 | end 45 | 46 | def blocks(entity) 47 | @blocks_motion 48 | end 49 | 50 | def decorate(level, x, y); end 51 | 52 | def get_floor_height(e) 53 | 0 54 | end 55 | 56 | def get_walk_speed(player) 57 | 1 58 | end 59 | 60 | def get_friction(player) 61 | 0.6 62 | end 63 | 64 | def trigger(pressed); end 65 | end -------------------------------------------------------------------------------- /escape/level/block/chest_block.rb: -------------------------------------------------------------------------------- 1 | class ChestBlock < Block 2 | attr_accessor :sprite 3 | 4 | def initialize 5 | super 6 | @tex = 1 7 | @blocks_motion = true 8 | @open = false 9 | 10 | @sprite = Sprite.new(0, 0, 0, 8 * 2, Art.get_col(0xffff00)) 11 | add_sprite @sprite 12 | end 13 | 14 | def use(level, item) 15 | return false if @open 16 | 17 | @sprite.tex += 1 18 | @open = true 19 | 20 | level.get_loot id 21 | Sound::TREASURE.play 22 | 23 | true 24 | end 25 | end -------------------------------------------------------------------------------- /escape/level/block/door_block.rb: -------------------------------------------------------------------------------- 1 | class DoorBlock < Block 2 | attr_accessor :openness 3 | 4 | def initialize 5 | super 6 | @tex = 4 7 | @open = false 8 | @openness = 0 9 | @solid_render = false 10 | end 11 | 12 | def use(level, item) 13 | @open = !@open 14 | if @open 15 | Sound::CLICK1.play 16 | else 17 | Sound::CLICK2.play 18 | end 19 | true 20 | end 21 | 22 | def tick 23 | super 24 | 25 | @openness += @open ? 0.2 : -0.2 26 | @openness = 0 if @openness < 0 27 | @openness = 1 if @openness > 1 28 | 29 | open_limit = 7 / 8.0 30 | if @openness < open_limit && !@open && !@blocks_motion 31 | if @level.contains_blocking_entity(@x - 0.5, @y - 0.5, @x + 0.5, @y + 0.5) 32 | @openness = open_limit 33 | return 34 | end 35 | end 36 | 37 | @blocks_motion = @openness < open_limit 38 | end 39 | 40 | def blocks(entity) 41 | open_limit = 7 / 8.0 42 | return @blocks_motion if entity.is_a?(Player) && @openness >= open_limit 43 | return @blocks_motion if entity.is_a?(Bullet) && @openness >= open_limit 44 | # TODO: Uncomment this when the ogre turns up 45 | #return @blocks_motion if entity.is_a?(OgreEntity) && @openness >= open_limit 46 | true 47 | end 48 | end -------------------------------------------------------------------------------- /escape/level/block/ice_block.rb: -------------------------------------------------------------------------------- 1 | class IceBlock < Block 2 | def initialize 3 | super 4 | @blocks_motion = false 5 | @floor_tex = 16 6 | end 7 | 8 | def tick 9 | super 10 | @floor_col = Art.get_col(0x8080ff) 11 | end 12 | 13 | def get_walk_speed(player) 14 | player.get_selected_item == Item::SKATES ? 0.05 : 1.4 15 | end 16 | 17 | def get_friction(player) 18 | player.get_selected_item == Item::SKATES ? 0.98 : 1 19 | end 20 | 21 | def blocks(entity) 22 | return false if entity.is_a?(Player) 23 | return false if entity.is_a?(Bullet) 24 | # TODO: Uncomment these when these types of entity exist in the game 25 | #return false if entity.is_a?(EyeBossEntity) 26 | #return false if entity.is_a?(EyeEntity) 27 | true 28 | end 29 | end -------------------------------------------------------------------------------- /escape/level/block/ladder_block.rb: -------------------------------------------------------------------------------- 1 | class LadderBlock < Block 2 | attr_accessor :wait 3 | 4 | LADDER_COLOR = 0xDB8E53 5 | 6 | def initialize(down) 7 | super() 8 | 9 | @wait = false 10 | 11 | if down 12 | @floor_tex = 1 13 | add_sprite Sprite.new(0, 0, 0, 8 + 3, Art.get_col(LADDER_COLOR)) 14 | else 15 | @ceil_tex = 1 16 | add_sprite Sprite.new(0, 0, 0, 8 + 4, Art.get_col(LADDER_COLOR)) 17 | end 18 | end 19 | 20 | def remove_entity(entity) 21 | super 22 | @wait = false if entity.is_a?(Player) 23 | end 24 | 25 | def add_entity(entity) 26 | super 27 | if !@wait && entity.is_a?(Player) 28 | @level.switch_level(@id) 29 | Sound::LADDER.play 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /escape/level/block/locked_door_block.rb: -------------------------------------------------------------------------------- 1 | class LockedDoorBlock < DoorBlock 2 | def initialize 3 | super 4 | @tex = 5 5 | end 6 | 7 | def use(level, item) 8 | false 9 | end 10 | 11 | def trigger(pressed) 12 | @open = pressed 13 | end 14 | end -------------------------------------------------------------------------------- /escape/level/block/loot_block.rb: -------------------------------------------------------------------------------- 1 | class LootBlock < Block 2 | attr_accessor :sprite 3 | 4 | def initialize 5 | super 6 | @taken = false 7 | @sprite = Sprite.new(0, 0, 0, 16 + 2, Art.get_col(0xffff80)) 8 | add_sprite @sprite 9 | @blocks_motion = true 10 | end 11 | 12 | def add_entity(entity) 13 | super entity 14 | if !@taken && entity.is_a?(Player) 15 | @sprite.removed = true 16 | @taken = true 17 | @blocks_motion = false 18 | entity.loot += 1 19 | Sound::PICKUP.play 20 | end 21 | end 22 | 23 | def blocks(entity) 24 | return false if entity.is_a?(Player) 25 | @blocks_motion 26 | end 27 | end -------------------------------------------------------------------------------- /escape/level/block/pit_block.rb: -------------------------------------------------------------------------------- 1 | class PitBlock < Block 2 | def initialize 3 | super 4 | @filled = false 5 | @floor_tex = 1 6 | @blocks_motion = true 7 | end 8 | 9 | def add_entity(entity) 10 | super 11 | return unless !@filled && entity.is_a?(BoulderEntity) 12 | entity.remove 13 | @filled = true 14 | @blocks_motion = false 15 | add_sprite Sprite.new(0, 0, 0, 8 + 2, BoulderEntity::COLOR) 16 | Sound::THUD.play 17 | end 18 | 19 | def blocks(entity) 20 | entity.is_a?(BoulderEntity) ? false : @blocks_motion 21 | end 22 | end -------------------------------------------------------------------------------- /escape/level/block/pressure_plate_block.rb: -------------------------------------------------------------------------------- 1 | class PressurePlateBlock < Block 2 | attr_accessor :pressed 3 | 4 | def initialize 5 | super 6 | @floor_tex = 2 7 | @pressed = false 8 | end 9 | 10 | def tick 11 | super 12 | r = 0.2 13 | stepped_on = @level.contains_blocking_non_flying_entity(@x - r, @y - r, @x + r, @y + r) 14 | if stepped_on != @pressed 15 | @pressed = stepped_on 16 | @floor_tex = @pressed ? 3 : 2 17 | @level.trigger(@id, @pressed) 18 | if @pressed 19 | Sound::CLICK1.play 20 | else 21 | Sound::CLICK2.play 22 | end 23 | end 24 | end 25 | 26 | def get_floor_height(e) 27 | @pressed ? -0.02 : 0.02 28 | end 29 | end -------------------------------------------------------------------------------- /escape/level/block/solid_block.rb: -------------------------------------------------------------------------------- 1 | class SolidBlock < Block 2 | def initialize 3 | super 4 | @solid_render = true 5 | @blocks_motion = true 6 | @tex = 0 7 | end 8 | end -------------------------------------------------------------------------------- /escape/level/block/torch_block.rb: -------------------------------------------------------------------------------- 1 | class TorchBlock < Block 2 | def initialize 3 | super 4 | @torch_sprite = Sprite.new(0 ,0, 0, 3, Art.get_col(0xffff00)) 5 | @sprites << @torch_sprite 6 | end 7 | 8 | def decorate(level, x, y) 9 | r = 0.4 10 | 11 | # eugh.. I don't like this at all but I'm just straight porting for now, cleanups later! :-) 12 | 1000.times do |i| 13 | face = rand(4) 14 | if face == 0 && level.get_block(x - 1, y).solid_render 15 | @torch_sprite.x -= r 16 | break 17 | end 18 | if face == 1 && level.get_block(x, y - 1).solid_render 19 | @torch_sprite.z -= r 20 | break 21 | end 22 | if face == 2 && level.get_block(x + 1, y).solid_render 23 | @torch_sprite.x += r 24 | break 25 | end 26 | if face == 3 && level.get_block(x, y + 1).solid_render 27 | @torch_sprite.z += r 28 | break 29 | end 30 | end 31 | end 32 | 33 | def tick 34 | super 35 | @torch_sprite.tex = 3 + rand(2) if rand(4) == 0 36 | end 37 | end -------------------------------------------------------------------------------- /escape/level/block/vanish_block.rb: -------------------------------------------------------------------------------- 1 | class VanishBlock < SolidBlock 2 | def initialize 3 | super 4 | @tex = 1 5 | end 6 | 7 | def use(level, item) 8 | return false if @gone 9 | 10 | @gone = true 11 | @blocks_motion = false 12 | @solid_render = false 13 | Sound::CRUMBLE.play 14 | 15 | 32.times do |i| 16 | sprite = RubbleSprite.new 17 | sprite.col = @col 18 | add_sprite sprite 19 | end 20 | 21 | true 22 | end 23 | end -------------------------------------------------------------------------------- /escape/level/block/water_block.rb: -------------------------------------------------------------------------------- 1 | class WaterBlock < Block 2 | def initialize 3 | super 4 | @blocks_motion = true 5 | @steps = 0 6 | end 7 | 8 | def tick 9 | super 10 | @steps -= 1 11 | if @steps <= 0 12 | @floor_text = 8 + rand(3) 13 | @floor_col = Art.get_col(0x0000ff) 14 | @steps = 16 15 | end 16 | end 17 | 18 | def blocks(entity) 19 | return false if entity.is_a?(Player) && entity.get_selected_item == Item::FLIPPERS 20 | return false if entity.is_a?(Bullet) 21 | @blocks_motion 22 | end 23 | 24 | def get_floor_height(e) 25 | -0.5 26 | end 27 | 28 | def get_walk_speed(player) 29 | 0.4 30 | end 31 | end -------------------------------------------------------------------------------- /escape/level/dungeon_level.rb: -------------------------------------------------------------------------------- 1 | class DungeonLevel < Level 2 | def initialize 3 | super 4 | @wall_col = 0xC64954 5 | @floor_col = 0x8E4A51 6 | @ceil_col = 0x8E4A51 7 | @name = "The Dungeons" 8 | end 9 | 10 | def init(game, name, w, h, pixels) 11 | super 12 | trigger(6, true) 13 | trigger(7, true) 14 | end 15 | 16 | def switch_level(id) 17 | @game.switch_level("start", 2) if id == 1 18 | end 19 | 20 | def get_loot(id) 21 | super 22 | @game.get_loot(Item::POWER_GLOVE) if (id == 1) 23 | end 24 | 25 | def trigger(id, pressed) 26 | super 27 | trigger(6, !pressed) if id == 5 28 | trigger(7, !pressed) if id == 4 29 | end 30 | end -------------------------------------------------------------------------------- /escape/level/ice_level.rb: -------------------------------------------------------------------------------- 1 | class IceLevel < Level 2 | def initialize 3 | super 4 | @floor_col = 0xB8DBE0 5 | @ceil_col = 0xB8D8E0 6 | @wall_col = 0x6BE8FF 7 | @name = "The Frost Cave" 8 | end 9 | 10 | def switch_level(id) 11 | @game.switch_level("overworld", 5) if id == 1 12 | end 13 | 14 | def get_loot(id) 15 | super 16 | @game.get_loot(Item::SKATES) if id == 1 17 | end 18 | end -------------------------------------------------------------------------------- /escape/level/level.rb: -------------------------------------------------------------------------------- 1 | class Level 2 | attr_accessor :x_spawn, :y_spawn, :blocks, :width, :height, :entities, :game, :name, :player, :solid_wall 3 | 4 | def initialize 5 | @wall_col = 0xB3CEE2 6 | @floor_col = 0x9CA09B 7 | @ceil_col = 0x9CA09B 8 | @wall_tex = 0 9 | @floor_tex = 0 10 | @ceil_tex = 0 11 | @name = '' 12 | end 13 | 14 | def init(game, name, w, h, pixels) 15 | @game = game 16 | @player = game.player 17 | 18 | @solid_wall = SolidBlock.new 19 | @solid_wall.col = Art.get_col(@wall_col) 20 | @solid_wall.tex = Art.get_col(@wall_tex) 21 | 22 | @width = w 23 | @height = h 24 | @blocks = Array.new(w * h) { Block.new } 25 | @entities = [] 26 | 27 | h.times do |y| 28 | w.times do |x| 29 | col = pixels[x + y * w] & 0xffffff 30 | id = 255 - ((pixels[x + y * w] >> 24) & 0xff) 31 | block = get_block_by_color(x, y, col) 32 | block.id = id 33 | 34 | block.tex = @wall_text if block.tex == -1 35 | block.floor_tex = @floor_tex if block.floor_tex == -1 36 | block.ceil_tex = @ceil_tex if block.ceil_tex == -1 37 | block.col = Art.get_col(@wall_col) if block.col == -1 38 | block.floor_col = Art.get_col(@floor_col) if block.floor_col == -1 39 | block.ceil_col = Art.get_col(@ceil_col) if block.ceil_col == -1 40 | 41 | blocks[x + y * w] = block 42 | block.level = self 43 | block.x = x 44 | block.y = y 45 | end 46 | end 47 | 48 | h.times do |y| 49 | w.times do |x| 50 | col = pixels[x + y * w] & 0xffffff 51 | decorate_block(x, y, @blocks[x + y * w], col) 52 | end 53 | end 54 | 55 | end 56 | 57 | def add_entity(e) 58 | entities << e 59 | e.level = self 60 | e.update_pos 61 | end 62 | 63 | def remove_entity_immediately(player) 64 | entities.delete player 65 | get_block(player.x_tileo, player.z_tileo).remove_entity player 66 | end 67 | 68 | def decorate_block(x, y, block, col) 69 | block.decorate self, x, y 70 | 71 | if col == 0xFFFF00 72 | @x_spawn = x 73 | @y_spawn = y 74 | end 75 | 76 | add_entity BoulderEntity.new(x, y) if col == 0xAA5500 77 | add_entity BatEntity.new(x, y) if col == 0xff0000 78 | add_entity BatBossEntity.new(x, y) if col == 0xff0001 79 | #add_entity OgreEntity.new(x, y) if col == 0xff0002 80 | #add_entity BossOgre.new(x, y) if col == 0xff0003 81 | add_entity EyeEntity.new(x, y) if col == 0xff0004 82 | #add_entity EyeBossEntit.newy(x, y) if col == 0xff0005 83 | #add_entity GhostEntity.new(x, y) if col == 0xff0006 84 | #add_entity GhostBossEntity.new(x, y) if col == 0xff0007 85 | 86 | if col == 0x1A2108 || col == 0xff0007 87 | block.floor_tex = 7 88 | block.ceil_tex = 7 89 | end 90 | 91 | block.col = Art.get_col(0xa0a0a0) if col == 0xC6C6C6 92 | block.col = Art.get_col(0xa0a0a0) if col == 0xC6C697 93 | 94 | if col == 0x653A00 95 | block.floor_col = Art.get_col(0xB56600) 96 | block.floor_tex = 3 * 8 + 1 97 | end 98 | 99 | if col == 0x93FF9B 100 | block.col = Art.get_col(0x2AAF33) 101 | block.tex = 8 102 | end 103 | end 104 | 105 | def get_block_by_color(x, y, col) 106 | # This could do with being made more Ruby-like! :-) 107 | return SolidBlock.new if col == 0x93FF9B 108 | return PitBlock.new if col == 0x009300 109 | return SolidBlock.new if col == 0xFFFFFF 110 | return VanishBlock.new if col == 0x00FFFF 111 | return ChestBlock.new if col == 0xFFFF64 112 | return WaterBlock.new if col == 0x0000FF 113 | return TorchBlock.new if col == 0xFF3A02 114 | return BarsBlock.new if col == 0x4C4C4C 115 | return LadderBlock.new(false) if col == 0xFF66FF 116 | return LadderBlock.new(true) if col == 0x9E009E 117 | return LootBlock.new if col == 0xC1C14D 118 | return DoorBlock.new if col == 0xC6C6C6 119 | #return SwitchBlock.new if col == 0x00FFA7 120 | return PressurePlateBlock.new if col == 0x009380 121 | return IceBlock.new if col == 0xff0005 122 | return IceBlock.new if col == 0x3F3F60 123 | return LockedDoorBlock.new if col == 0xC6C697 124 | #return AltarBlock.new if col == 0xFFBA02 125 | #return SpiritWallBlock.new if col == 0x749327 126 | return Block.new if col == 0x1A2108 127 | #return FinalUnlockBlock.new if col == 0x00C2A7 128 | #return WinBlock.new if col == 0x000056 129 | Block.new 130 | end 131 | 132 | def get_block(x, y) 133 | return @solid_wall if x < 0 || y < 0 || x >= @width || y >= @height 134 | return @blocks[x + y * @width] 135 | end 136 | 137 | def self.clear 138 | @loaded = {} 139 | end 140 | 141 | def self.load_level(game, name) 142 | @loaded ||= {} 143 | return @loaded[name] if @loaded[name] 144 | 145 | url = java.net.URL.new("file://" + ASSETS_DIR + '/level/' + name + '.png') # nasty little hack due to borked get_resource (means applet won't be easy..) 146 | img = ImageIO.read(url) 147 | 148 | w = img.width 149 | h = img.height 150 | 151 | pixels = [] 152 | h.times do |y| 153 | w.times do |x| 154 | pixels[y * w + x] = img.getRGB(x, y) 155 | end 156 | end 157 | 158 | level = Level.by_name(name) 159 | level.init(game, name, w, h, pixels) 160 | @loaded[name] = level 161 | 162 | level 163 | end 164 | 165 | def self.by_name(name) 166 | const_get((name.capitalize + "Level").to_sym).new 167 | end 168 | 169 | def contains_blocking_entity(x0, y0, x1, y1) 170 | xc = ((x1 + x0) / 2).floor 171 | zc = ((y1 + y0) / 2).floor 172 | rr = 2 173 | (zc - rr).upto(zc + rr - 1) do |z| 174 | (xc - rr).upto(xc + rr - 1) do |x| 175 | get_block(x, z).entities.each do |e| 176 | return true if e.is_inside(x0, y0, x1, y1) 177 | end 178 | end 179 | end 180 | false 181 | end 182 | 183 | # TODOMUCHLATER: DRY this up! :-) 184 | def contains_blocking_non_flying_entity(x0, y0, x1, y1) 185 | xc = ((x1 + x0) / 2).floor 186 | zc = ((y1 + y0) / 2).floor 187 | rr = 2 188 | (zc - rr).upto(zc + rr - 1) do |z| 189 | (xc - rr).upto(xc + rr - 1) do |x| 190 | get_block(x, z).entities.each do |e| 191 | return true if e.is_inside(x0, y0, x1, y1) && !e.flying 192 | end 193 | end 194 | end 195 | false 196 | end 197 | 198 | def tick 199 | entities.each_with_index do |e, i| 200 | e.tick if e.method(:tick).arity == 0 # FIXME: A hack because of some Java confusion.. 201 | e.update_pos 202 | entities.delete(e) if e.is_removed # beware: this might be wrong.. original removes element i--.. may not be necessary with Ruby's iterators 203 | end 204 | 205 | @height.times do |y| 206 | @width.times do |x| 207 | @blocks[x + y * width].tick 208 | end 209 | end 210 | end 211 | 212 | def trigger(id, pressed) 213 | @height.times do |y| 214 | @width.times do |x| 215 | b = @blocks[x + y * @width] 216 | b.trigger(pressed) if b.id == id 217 | end 218 | end 219 | end 220 | 221 | def switch_level(id); end 222 | 223 | def find_spawn(id) 224 | @height.times do |y| 225 | @width.times do |x| 226 | b = @blocks[x + y * @width] 227 | if b.id == id && b.is_a?(LadderBlock) 228 | @x_spawn = x 229 | @y_spawn = y 230 | end 231 | end 232 | end 233 | end 234 | 235 | def get_loot(id) 236 | game.get_loot(Item::PISTOL) if id == 20 237 | game.get_loot(Item::PISTOL) if id == 21 238 | end 239 | 240 | def win 241 | @game.win @player 242 | end 243 | 244 | def lose 245 | @game.lose @player 246 | end 247 | 248 | def show_loot_screen(item) 249 | @game.set_menu GotLootMenu.new(item) 250 | end 251 | end -------------------------------------------------------------------------------- /escape/level/overworld_level.rb: -------------------------------------------------------------------------------- 1 | class OverworldLevel < Level 2 | def initialize 3 | super 4 | @ceil_tex = -1 5 | @floor_col = 0x508253 6 | @floor_tex = 8 * 3 7 | @wall_col = 0xa0a0a0 8 | @name = "The Island" 9 | end 10 | 11 | def switch_level(id) 12 | @game.switch_level("start", 1) if id == 1 13 | @game.switch_level("crypt", 1) if id == 2 14 | @game.switch_level("temple", 1) if id == 3 15 | @game.switch_level("ice", 1) if id == 5 16 | end 17 | 18 | def get_loot(id) 19 | super 20 | @game.get_loot(Item::CUTTERS) if id == 1 21 | end 22 | end -------------------------------------------------------------------------------- /escape/level/start_level.rb: -------------------------------------------------------------------------------- 1 | class StartLevel < Level 2 | def initialize 3 | super 4 | @name = "The Prison" 5 | end 6 | 7 | def switch_level(id) 8 | @game.switch_level("overworld", 1) if id == 1 9 | @game.switch_level("dungeon", 1) if id == 2 10 | end 11 | end -------------------------------------------------------------------------------- /escape/menu/about_menu.rb: -------------------------------------------------------------------------------- 1 | class AboutMenu < Menu 2 | def initialize 3 | super 4 | @tick_delay = 30 5 | end 6 | 7 | def render(target) 8 | target.fill(0, 0, 160, 120, 0) 9 | 10 | target.draw_string("About", 60, 8, Art.get_col(0xffffff)) 11 | 12 | lines = [ 13 | "Prelude of the Chambered", 14 | "by Markus Persson.", 15 | "Made Aug 2011 for the", 16 | "21st Ludum Dare compo.", # Fixed a typo here from the original 17 | "", 18 | "This game is freeware,", 19 | "and was made from scratch", 20 | "in just 48 hours.", 21 | ] 22 | 23 | lines.length.times do |i| 24 | target.draw_string(lines[i], 4, 28+i*8, Art.get_col(0xa0a0a0)) 25 | end 26 | 27 | target.draw_string("-> Continue", 40, target.height - 16, Art.get_col(0xffff80)) if @tick_delay == 0 28 | end 29 | 30 | def tick(game, up, down, left, right, use) 31 | if @tick_delay > 0 32 | @tick_delay -= 1 33 | elsif use 34 | Sound::CLICK1.play 35 | game.set_menu(TitleMenu.new) 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /escape/menu/got_loot_menu.rb: -------------------------------------------------------------------------------- 1 | class GotLootMenu < Menu 2 | attr_accessor :item 3 | 4 | def initialize(item) 5 | super() 6 | @item = item 7 | @tick_delay = 30 8 | end 9 | 10 | def render(target) 11 | str = "You found the #{@item.name}!" 12 | target.scale_draw(Art::ITEMS, 3, target.width / 2 - 8 * 3, 2, @item.icon * 16, 0, 16, 16, Art.get_col(@item.color)) 13 | target.draw_string(str, (target.width - str.length * 6) / 2 + 2, 60 - 10, Art.get_col(0xffff80)) 14 | 15 | str = @item.description 16 | target.draw_string(str, (target.width - str.length * 6) / 2 + 2, 60, Art.get_col(0xa0a0a0)) 17 | 18 | target.draw_string("-> Continue", 40, target.height - 40, Art.get_col(0xffff80)) if @tick_delay == 0 19 | end 20 | 21 | def tick(game, up, down, left, right, use) 22 | if @tick_delay > 0 23 | @tick_delay -= 1 24 | elsif use 25 | game.set_menu(nil) 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /escape/menu/instructions_menu.rb: -------------------------------------------------------------------------------- 1 | class InstructionsMenu < Menu 2 | def initialize 3 | super 4 | @tick_delay = 30 5 | end 6 | 7 | def render(target) 8 | target.fill(0, 0, 160, 120, 0) 9 | 10 | target.draw_string("Instructions", 40, 8, Art.get_col(0xffffff)) 11 | 12 | lines = [ 13 | "Use W,A,S,D to move, and", 14 | "the arrow keys to turn.", 15 | "", 16 | "The 1-8 keys select", 17 | "items from the inventory", 18 | "", 19 | "Space uses items", 20 | ] 21 | 22 | lines.length.times do |i| 23 | target.draw_string(lines[i], 4, 32+i*8, Art.get_col(0xa0a0a0)) 24 | end 25 | 26 | target.draw_string("-> Continue", 40, target.height - 16, Art.get_col(0xffff80)) if @tick_delay == 0 27 | end 28 | 29 | def tick(game, up, down, left, right, use) 30 | if @tick_delay > 0 31 | @tick_delay -= 1 32 | elsif use 33 | Sound::CLICK1.play 34 | game.set_menu(TitleMenu.new) 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /escape/menu/menu.rb: -------------------------------------------------------------------------------- 1 | class Menu 2 | def render(target); end 3 | def tick(game, up, down, left, right, use); end 4 | end -------------------------------------------------------------------------------- /escape/menu/pause_menu.rb: -------------------------------------------------------------------------------- 1 | class PauseMenu < Menu 2 | def initialize 3 | super 4 | @selected = 1 5 | @options = ["Abort game", "Continue"] 6 | end 7 | 8 | def render(target) 9 | target.draw_bitmap(Art::LOGO, 0, 8, 0, 0, 160, 36, Art.get_col(0xffffff)) 10 | 11 | @options.each_with_index do |msg, i| 12 | col = 0x909090 13 | if @selected == i 14 | msg = "-> " + msg 15 | col = 0xffff80 16 | end 17 | target.draw_string(msg, 40, 60 + i * 10, Art.get_col(col)) 18 | end 19 | 20 | end 21 | 22 | def tick(game, up, down, left, right, use) 23 | Sound::CLICK2.play if up || down 24 | @selected -= 1 if up 25 | @selected += 1 if down 26 | @selected = 0 if @selected < 0 27 | @selected = @options.length - 1 if @selected >= @options.length 28 | if use 29 | Sound::CLICK1.play 30 | if @selected == 0 31 | game.set_menu TitleMenu.new 32 | elsif @selected == 1 33 | game.set_menu nil 34 | end 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /escape/menu/title_menu.rb: -------------------------------------------------------------------------------- 1 | class TitleMenu < Menu 2 | 3 | def initialize 4 | super 5 | @first_tick = true 6 | @selected = 0 7 | @options = ["New game", "Instructions", "About"] 8 | end 9 | 10 | def render(target) 11 | target.fill(0, 0, 160, 120, 0) 12 | target.draw_bitmap(Art::LOGO, 0, 8, 0, 0, 160, 36, Art.get_col(0xffffff)) 13 | 14 | @options.each_with_index do |msg, i| 15 | col = 0x909090 16 | if @selected == i 17 | msg = "-> " + msg 18 | col = 0xffff80 19 | end 20 | target.draw_string(msg, 40, 60 + i * 10, Art.get_col(col)) 21 | end 22 | 23 | target.draw_string("Copyright (C) 2011 Mojang", 1+4, 120 - 9, Art.get_col(0x303030)) 24 | end 25 | 26 | def tick(game, up, down, left, right, use) 27 | if @first_tick 28 | @first_tick = false 29 | Sound::ALTAR.play() 30 | end 31 | 32 | Sound::CLICK2.play if up || down 33 | @selected -= 1 if up 34 | @selected += 1 if down 35 | 36 | @selected = 0 if @selected < 0 37 | @selected = @options.length - 1 if @selected >= @options.length 38 | 39 | if use 40 | Sound::CLICK1.play 41 | if @selected == 0 42 | game.menu = nil 43 | game.new_game 44 | elsif @selected == 1 45 | game.set_menu(InstructionsMenu.new) 46 | elsif @selected == 2 47 | game.set_menu(AboutMenu.new) 48 | end 49 | end 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /escape/sound.rb: -------------------------------------------------------------------------------- 1 | java_import javax.sound.sampled.AudioSystem 2 | java_import javax.sound.sampled.Clip 3 | java_import javax.sound.sampled.DataLine 4 | 5 | class Sound 6 | attr_accessor :clip 7 | 8 | def self.load_sound(file_name) 9 | sound = new 10 | 11 | url = java.net.URL.new("file://" + ASSETS_DIR + file_name) # nasty little hack due to borked get_resource (means applet won't be easy..) 12 | ais = AudioSystem.get_audio_input_stream(url) 13 | info = DataLine::Info.new(Clip.java_class, ais.format) 14 | clip = AudioSystem.get_line(info) 15 | clip.open(ais) 16 | clip.extend JRuby::Synchronized 17 | sound.clip = clip 18 | 19 | sound 20 | end 21 | 22 | def play 23 | if clip 24 | Thread.new do 25 | clip.stop 26 | clip.frame_position = 0 27 | clip.start 28 | end 29 | end 30 | end 31 | 32 | %w{altar bosskill click1 click2 hit hurt hurt2 kill death splash key pickup roll shoot treasure crumble slide cut thud ladder potion}.each do |name| 33 | const_set name.upcase, load_sound("/snd/#{name}.wav") 34 | end 35 | end --------------------------------------------------------------------------------