beefdump 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ # BeefDump
2
+
3
+ A testbed for non-player character AI in role-playing games.
4
+
5
+ ## Install
6
+
7
+ Install Ruby 1.8 (with ZLIB and BASE64)
8
+
9
+ Install the required gems:
10
+
11
+ gem install gosu xml-simple
12
+
13
+ And you're done.
@@ -0,0 +1,16 @@
1
+ module Beefdump
2
+ # Bootstrap helpers
3
+ def self.load_dir(dir)
4
+ Dir.new(dir).each do |file|
5
+ next unless file =~ /\.rb$/
6
+ load "#{dir}/#{file}"
7
+ end
8
+ end
9
+
10
+ # Load path
11
+ LIB_PATH = File.expand_path(File.dirname(__FILE__))
12
+ $LOAD_PATH << LIB_PATH
13
+
14
+ # Monkey patches
15
+ load_dir("#{LIB_PATH}/beefdump/monkey_patches")
16
+ end
@@ -0,0 +1,35 @@
1
+ module Beefdump
2
+ require 'ostruct'
3
+
4
+ # Config
5
+ require 'beefdump/config/base'
6
+ CONFIG_PATH = "#{ROOT_PATH}/config"
7
+ CONFIG = Config::Base.new
8
+ # load beefdump config blueprint
9
+ require 'beefdump/config/blueprint'
10
+
11
+ base_config = "#{CONFIG_PATH}/base.rb"
12
+ load base_config if File.exist?(base_config)
13
+
14
+ # Logger
15
+ load CONFIG.logger.class
16
+ Logger.level = CONFIG.logger.level
17
+ Logger.info "Monkey patches, config and logger loaded. Let's get going!"
18
+
19
+ # Utils
20
+ UTILS_PATH = "#{LIB_PATH}/beefdump/utils"
21
+ Logger.info "Loading utils."
22
+ load_dir(UTILS_PATH)
23
+
24
+ # Game
25
+ Logger.info "Loading game kernel"
26
+ load "#{LIB_PATH}/beefdump/game/base.rb"
27
+
28
+ # Modules
29
+ Logger.info "Loading beefdump modules"
30
+ CONFIG.active_modules.each do |modul|
31
+ Logger.trace "Loading module #{modul}"
32
+ require "beefdump/#{modul}#{modul =~ /.+\/.+/ ? "" : "/#{modul}"}"
33
+ end
34
+ Logger.info "Loading of modules completed"
35
+ end
@@ -0,0 +1,33 @@
1
+ module Beefdump
2
+ module Client
3
+ # The most basic client which is kind of a client specification
4
+ class Base
5
+ attr_reader :player
6
+
7
+ # Initialize the client on connection to the server
8
+ def initialize(game, player = nil)
9
+ @game = game
10
+ @player = player
11
+ end
12
+
13
+ # Do whatever the client would like to do.
14
+ # This is called by the server in any game loop.
15
+ def act
16
+ end
17
+
18
+ # The server wants to close the connection to the client.
19
+ # Clean up the stuff in here
20
+ def disconnect
21
+ end
22
+
23
+ protected
24
+ def id
25
+ object_id
26
+ end
27
+
28
+ def log(level, message)
29
+ Logger.send(level, "Client #{id}: #{message}")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,94 @@
1
+ module Beefdump
2
+ module Client
3
+ module Graphical
4
+ class Client < Client::Base
5
+ require 'gosu'
6
+ require 'beefdump/client/graphical/window'
7
+
8
+ def initialize(game, *options)
9
+ super(game, *options)
10
+ @window = Window.new(self, Window::WIDTH, Window::HEIGHT, false)
11
+ @window.caption = Time.now
12
+
13
+ initialize_tilesets(game.map)
14
+
15
+ @player = @game.claim_player("Peter", self)
16
+ start_main_loop
17
+ end
18
+
19
+ def act
20
+ @window.caption = Time.now
21
+ end
22
+
23
+ def disconnect
24
+ @window.close
25
+ @window_thread.join
26
+ end
27
+
28
+ def update_window
29
+ return unless @game.should_run?
30
+
31
+ if @window.button_down? Gosu::Button::KbEscape
32
+ @game.shutdown!
33
+ end
34
+ handle_camera_movement
35
+ end
36
+
37
+ def draw_window
38
+ left = @player.position.x / @game.map.tile_width
39
+ top = @player.position.y / @game.map.tile_height
40
+ diff_x = @player.position.x % @game.map.tile_width
41
+ diff_y = @player.position.y % @game.map.tile_height
42
+ width = Window::WIDTH / @game.map.tile_width + 1
43
+ height = Window::HEIGHT / @game.map.tile_height + 1
44
+
45
+ tile_layers = @game.map.background_at(left, top, width, height)
46
+
47
+ tile_layers.each do |tile_layer|
48
+ tile_layer.each_with_index do |row, x|
49
+ row.each_with_index do |tile, y|
50
+ tileset = @game.map.tileset_for(tile)
51
+ next unless tileset
52
+ tile_id = tile - tileset.first_gid
53
+ @tileset_images[@game.map.tilesets.index(tileset)][tile_id].draw(x * @game.map.tile_width - diff_x, y * @game.map.tile_height - diff_y, 0)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ protected
60
+ def start_main_loop
61
+ Thread.abort_on_exception = true
62
+ @window_thread = Thread.new do
63
+ @window.show
64
+ end
65
+ end
66
+
67
+ def initialize_tilesets(map)
68
+ @tileset_images = []
69
+ map.tilesets.each do |tileset|
70
+ log(:trace, "Loading tileset image '#{tileset.image_file}'")
71
+ @tileset_images << Gosu::Image.load_tiles(@window, tileset.image_file, tileset.tile_width, tileset.tile_height, true)
72
+ log(:trace, "Loaded #{@tileset_images.last.size} tiles.")
73
+ end
74
+ end
75
+
76
+ def handle_camera_movement
77
+ if @window.button_down? Gosu::Button::KbRight
78
+ @player.position.x = [(@game.map.width * @game.map.tile_width) - Window::WIDTH, @player.position.x + 5].min
79
+ end
80
+ if @window.button_down? Gosu::Button::KbLeft
81
+ @player.position.x = [0, @player.position.x - 5].max
82
+ end
83
+
84
+ if @window.button_down? Gosu::Button::KbDown
85
+ @player.position.y = [(@game.map.height * @game.map.tile_height) - Window::HEIGHT, @player.position.y + 5].min
86
+ end
87
+ if @window.button_down? Gosu::Button::KbUp
88
+ @player.position.y = [0, @player.position.y - 5].max
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,24 @@
1
+ module Beefdump
2
+ module Client
3
+ module Graphical
4
+ require 'gosu'
5
+ class Window < Gosu::Window
6
+ WIDTH = 640
7
+ HEIGHT = 480
8
+
9
+ def initialize(client, *args)
10
+ super(*args)
11
+ @client = client
12
+ end
13
+
14
+ def update
15
+ @client.update_window
16
+ end
17
+
18
+ def draw
19
+ @client.draw_window
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module Beefdump
2
+ module Config
3
+ require 'ostruct'
4
+
5
+ class Base < OpenStruct
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Beefdump
2
+ CONFIG.active_modules = %w{world map client/base client/graphical/client}
3
+
4
+ CONFIG.logger = Config::Base.new(
5
+ :class => "beefdump/logger.rb",
6
+ :level => :info)
7
+ end
@@ -0,0 +1,95 @@
1
+ module Beefdump
2
+ module Game
3
+ class Base
4
+ require 'beefdump/game/state'
5
+ require 'beefdump/game/player'
6
+
7
+ CLIENT_UPDATE_RATE = 1.0 / 10 # 10 times a second
8
+ GAME_STATE_PATH = "#{ROOT_PATH}/game_state"
9
+ CURRENT_GAME_STATE_FILE = "#{GAME_STATE_PATH}/current.bin"
10
+
11
+ attr_reader :map
12
+
13
+ def initialize
14
+ load_static_data
15
+ load_map('test')
16
+ init_game_state
17
+
18
+ load_clients
19
+
20
+ @shutdown = false
21
+
22
+ game_loop
23
+ end
24
+
25
+ def load_map(new_map)
26
+ @map = Map.load(new_map)
27
+ end
28
+
29
+ def shutdown!
30
+ Logger.info "Received shutdown command from client. Quitting game."
31
+ save_game_state
32
+ @shutdown = true
33
+ end
34
+
35
+ def claim_player(name, client)
36
+ player = @state.players.select {|p| p.name == name}.first
37
+ raise "Player not found" unless player
38
+ player
39
+ end
40
+
41
+ def should_run?
42
+ !@shutdown
43
+ end
44
+
45
+ protected
46
+
47
+ def init_game_state
48
+ Logger.info("Loading game state.")
49
+
50
+ if File.exist?(CURRENT_GAME_STATE_FILE)
51
+ @state = File.open(CURRENT_GAME_STATE_FILE, 'r') {|f| Marshal.load(f)}
52
+ else
53
+ Logger.info("No saved game state found.")
54
+ initially_create_game_state!
55
+ end
56
+ end
57
+
58
+ def initially_create_game_state!
59
+ initial_state = @map.get_initial_state
60
+ @state = Game::State.from_xml(initial_state, self)
61
+ end
62
+
63
+ def save_game_state
64
+ Logger.info("Saving game state")
65
+ File.open(CURRENT_GAME_STATE_FILE, 'w') {|f| Marshal.dump(@state, f)}
66
+ end
67
+
68
+ def load_static_data
69
+ World.load
70
+ end
71
+
72
+ def load_clients
73
+ Logger.info "Loading clients."
74
+ @clients = []
75
+ @clients << Client::Graphical::Client.new(self)
76
+ end
77
+
78
+ def game_loop
79
+ Logger.info "All systems go! Starting main game loop."
80
+
81
+ while self.should_run?
82
+ start_time = Time.now.to_f
83
+
84
+ @clients.each do |client|
85
+ client.act
86
+ end
87
+
88
+ passed_time = Time.now.to_f - start_time
89
+ sleep passed_time > CLIENT_UPDATE_RATE ? 0 : CLIENT_UPDATE_RATE - passed_time
90
+ end
91
+ Logger.info("fin")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,12 @@
1
+ module Beefdump
2
+ module Game
3
+ class Player
4
+ attr_reader :name, :position
5
+
6
+ def initialize(name, position)
7
+ @name = name
8
+ @position = position
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ module Beefdump
2
+ module Game
3
+ class State
4
+ require 'ostruct'
5
+ attr_reader :players, :game
6
+
7
+ def initialize(players)
8
+ @players = players
9
+ end
10
+
11
+ def self.from_xml(xml, game)
12
+ players = []
13
+ xml['players'].first['player'].each do |player|
14
+ position = player['position'].first
15
+ players << Game::Player.new(player['name'], OpenStruct.new({:x => position['x'].to_i, :y => position['y'].to_i}))
16
+ end
17
+
18
+ self.new(players)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Beefdump
2
+ class Logger
3
+ LEVELS = [:trace, :info, :warn, :error]
4
+
5
+ def self.level=(new_level)
6
+ raise "No valid log level: '#{new_level}'!" unless @level = LEVELS.index(new_level)
7
+ end
8
+
9
+ def self.method_missing(method_name, *args)
10
+ return super.send(method_name, args) unless LEVELS.include?(method_name)
11
+ log(method_name, args.first) if LEVELS.index(method_name) >= @level
12
+ end
13
+
14
+ protected
15
+ def self.log(level, message)
16
+ puts "[#{level}] #{Time.now}: #{message}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ module Beefdump
2
+ module Map
3
+ class Layer
4
+ require 'zlib'
5
+ require 'base64'
6
+
7
+ attr_reader :name, :width, :height, :map, :field
8
+
9
+ def initialize(layer_data, map)
10
+ @map = map
11
+
12
+ load_layer_attributes!(layer_data)
13
+
14
+ load_data!(layer_data["data"].first)
15
+ end
16
+
17
+ protected
18
+ def load_layer_attributes!(layer_data)
19
+ @name = layer_data["name"]
20
+ @width = layer_data["width"].to_i
21
+ @height = layer_data["height"].to_i
22
+
23
+ Logger.trace "Loaded layer attributes: Name: #{@name} Width: #{@width} Height: #{@height}"
24
+ end
25
+
26
+ def load_data!(data)
27
+ Logger.trace "Loading layer data for layer '#{@name}'."
28
+
29
+ encoding = data["encoding"]
30
+ compression = data["compression"]
31
+
32
+ raise "Map layers without BASE64 enconding are unsupported!" unless encoding == "base64"
33
+ raise "Map layers without GZIP compression are unsupported!" unless compression == "gzip"
34
+
35
+ content = StringIO.new(Base64.decode64(data["content"].strip))
36
+ gz = Zlib::GzipReader.new(content)
37
+
38
+ @field = []
39
+ row = []
40
+ byte_arr = []
41
+ gz.each_byte do |byte|
42
+ byte_arr << byte
43
+ if byte_arr.size == 4
44
+ row << (byte_arr[0] | byte_arr[1] << 8 | byte_arr[2] << 16 | byte_arr[3] << 24)
45
+ byte_arr = []
46
+ @field << row and row = [] if row.size == @width
47
+ end
48
+ end
49
+ gz.close
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,136 @@
1
+ module Beefdump
2
+ module Map
3
+ MAP_PATH = "#{ROOT_PATH}/map"
4
+
5
+ class Base
6
+ require 'xmlsimple'
7
+ require 'beefdump/map/layer'
8
+ require 'beefdump/map/tileset'
9
+ require 'beefdump/map/object'
10
+
11
+ MAP_FORMAT_VERSION = "1.0"
12
+
13
+ attr_reader :width, :height, :tile_width, :tile_height, :layers, :tilesets, :objects, :name
14
+
15
+ def initialize(map_name, map_data)
16
+ @name = map_name
17
+
18
+ check_compatibility!(map_data)
19
+
20
+ load_map_attributes!(map_data)
21
+ load_layers!(map_data)
22
+ load_tilesets!(map_data)
23
+ load_objects!(map_data)
24
+
25
+ @raw_data = map_data
26
+ end
27
+
28
+ def background_at(x, y, width = 1, height = 1)
29
+ tiles = []
30
+ @layers.each do |layer|
31
+ field = Array.new(width).map {|e| Array.new(height).map {|e| nil}}
32
+ for diff_x in 0..(width - 1)
33
+ for diff_y in 0..(height - 1)
34
+ field[diff_x][diff_y] = layer.field[y + diff_y] ? layer.field[y + diff_y][x + diff_x] : nil
35
+ end
36
+ end
37
+ tiles << field
38
+ end
39
+
40
+ tiles
41
+ end
42
+
43
+ def objects_at(x, y)
44
+ objects = @objects.select {|o| o.is_at?(x, y)}
45
+ end
46
+
47
+ def tileset_for(gid)
48
+ return unless gid
49
+ chosen = nil
50
+ @tilesets.each do |tileset|
51
+ break if chosen && gid < tileset.first_gid
52
+ chosen = tileset if gid >= tileset.first_gid
53
+ end
54
+
55
+ chosen
56
+ end
57
+
58
+ def get_initial_state
59
+ Logger.info("Loading initial state for map '#{@name}'.")
60
+ initial_state_file = "#{MAP_PATH}/#{@name}.isx"
61
+ raise "Initial state file for map '#{@name}' not found!" unless File.exist?(initial_state_file)
62
+
63
+ XmlSimple.xml_in(initial_state_file)
64
+ end
65
+
66
+ protected
67
+ def check_compatibility!(map_data)
68
+ Logger.trace "Checking map format compatibility."
69
+
70
+ raise "Map format unsupported (#{map_data["orientation"]})!" unless map_data["orientation"] == "orthogonal"
71
+ Logger.warn("Map format version #{map_data['version']} is not supported - scary problems may occur! Supply maps in format version #{MAP_FORMAT_VERSION} to ensure smooth operation.") if map_data['version'] != MAP_FORMAT_VERSION
72
+ end
73
+
74
+ def load_map_attributes!(map_data)
75
+ @width = map_data["width"].to_i
76
+ @height = map_data["height"].to_i
77
+ @tile_width = map_data["tilewidth"].to_i
78
+ @tile_height = map_data["tileheight"].to_i
79
+
80
+ Logger.trace "Loaded map attributes: Width: #{@width} Height: #{@height} Tile Width: #{@tile_width} Tile Height: #{@tile_height}"
81
+ end
82
+
83
+ def load_layers!(map_data)
84
+ @layers = map_data["layer"].map {|layer_data| Layer.new(layer_data, self)}
85
+
86
+ Logger.info "Loaded #{@layers.size} map layers."
87
+ end
88
+
89
+ def load_tilesets!(map_data)
90
+ @tilesets = map_data["tileset"].map {|tileset_data| Tileset.new(tileset_data, self)}.sort {|a, b| a.first_gid <=> b.first_gid}
91
+
92
+ Logger.info "Loaded #{@tilesets.size} tilesets containing #{@tilesets.inject(0) {|a,t| a += t.tiles_count}} tiles at all."
93
+ end
94
+
95
+ def load_objects!(map_data)
96
+ @objects = []
97
+ map_data["objectgroup"].each do |object_group|
98
+ object_group["object"].each do |object|
99
+ new_object = Object.new(object)
100
+ raise "Duplicate object name on map: #{new_object.name}" if @objects.any? {|o| o.name == new_object.name}
101
+ @objects << new_object
102
+ end
103
+ end
104
+
105
+ check_object_reference_integrity
106
+
107
+ Logger.info "Loaded #{@objects.size} objects on the map."
108
+ end
109
+
110
+ def check_object_reference_integrity
111
+ Logger.info "Checking reference integrity on objects."
112
+
113
+ @objects.each do |object|
114
+ object.properties.each do |property, value|
115
+ if object.blueprint.properties[property].type == :name_reference
116
+ raise "Object '#{object.name}' references to an object named '#{value}' as it's '#{property}' value but such an object does not exist!" unless @objects.any? {|o| o.name == value}
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def self.load(map_name)
124
+ Logger.info "Trying to load map #{map_name}"
125
+
126
+ map_file = "#{MAP_PATH}/#{map_name}.tmx"
127
+ raise "Map does not exist: '#{map_file}'!" unless File.exist?(map_file)
128
+
129
+ map = Base.new(map_name, XmlSimple.xml_in(map_file))
130
+
131
+ Logger.info "Successfully loaded map."
132
+ map
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,75 @@
1
+ module Beefdump
2
+ module Map
3
+ class Object
4
+ attr_reader :name, :x, :y, :width, :height, :type, :properties, :blueprint
5
+
6
+ def initialize(object_data)
7
+ load_object_attributes!(object_data)
8
+
9
+ load_properties!(object_data["properties"])
10
+ end
11
+
12
+ def is_at?(x, y)
13
+ self.x >= x && self.x + self.width <= x && self.y >= y && self.y + self.height <= y
14
+ end
15
+
16
+ protected
17
+ def load_object_attributes!(object_data)
18
+ @type = object_data["type"].to_sym
19
+ @blueprint = World.objects[@type]
20
+ raise "Unsupported object type '#{@type}' on map!" unless @blueprint
21
+
22
+ @name = object_data["name"]
23
+ @x = object_data["x"]
24
+ @y = object_data["y"]
25
+ @width = object_data["width"]
26
+ @height = object_data["height"]
27
+
28
+ Logger.trace "Loaded object attributes: Name: #{@name} Position: #{@x}, #{@y} Width: #{@width} Height: #{@height} Type: #{@type}"
29
+ end
30
+
31
+ def load_properties!(properties_data)
32
+ @properties = {}
33
+ return unless properties_data
34
+
35
+ properties_data.first["property"].each do |property_data|
36
+ value = check_and_cast_property(property_data["property"])
37
+ @properties[property_data["name"].to_sym] = property_data["value"]
38
+ end
39
+
40
+ check_for_mandatory_properties
41
+
42
+ Logger.trace "Sucessfully loaded properties for object '#{@name}'."
43
+ end
44
+
45
+ def check_and_cast_property(property_data)
46
+ return unless property_data
47
+
48
+ property_name = property_data["name"].to_sym
49
+ property_blueprint = @blueprint.properties[property_name]
50
+ raise "Object type '#{@type}' has no '#{property_name}' property (on object '#{@name}')" unless property_blueprint
51
+
52
+ value = property_data["value"]
53
+ return nil unless value
54
+
55
+ case property_blueprint.type
56
+ when :number
57
+ raise "Value of property '#{property_name}' on object '#{@name}' must be numeric and not #{value}!" unless value.numeric?
58
+ value.to_f
59
+ when :string
60
+ value.to_s
61
+ when :name_reference
62
+ value.to_s
63
+ end
64
+ end
65
+
66
+ def check_for_mandatory_properties
67
+ @blueprint.properties.each do |type, property|
68
+ next unless property.mandatory?
69
+
70
+ raise "Mandatory property '#{property.name}' not set on object '#{@name}' of type #{@type}!" unless @properties[property.name]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,35 @@
1
+ module Beefdump
2
+ module Map
3
+ class Tileset
4
+ attr_reader :name, :first_gid, :tile_width, :tile_height, :image_file, :image_transparency_color, :map, :dimensions, :tiles_count
5
+
6
+ def initialize(tileset_data, map)
7
+ @map = map
8
+
9
+ load_tileset_attributes!(tileset_data)
10
+ load_image!(tileset_data["image"].first)
11
+ end
12
+
13
+ protected
14
+
15
+ def load_tileset_attributes!(tileset_data)
16
+ @name = tileset_data["name"]
17
+ @first_gid = tileset_data["firstgid"].to_i
18
+ @tile_width = tileset_data["tilewidth"].to_i
19
+ @tile_height = tileset_data["tileheight"].to_i
20
+
21
+ Logger.trace "Loaded tileset attributes: Name: '#{@name}' First GID: #{@first_gid} Tile Width: #{@tile_width} Tile Height: #{@tile_height}"
22
+ end
23
+
24
+ def load_image!(image_data)
25
+ @image_file = File.expand_path("#{MAP_PATH}/#{image_data["source"]}")
26
+ @image_transparency_color = image_data["trans"]
27
+
28
+ @dimensions = ImageUtil.dimensions(@image_file)
29
+ @tiles_count = @dimensions.first * @dimensions.last / (@tile_width * @tile_height)
30
+
31
+ Logger.trace "Loaded tileset image data: File '#{@image_file}' Mask Color: #{@image_transparency_color} Dimensions: #{@dimensions.join(" * ")} Tiles: #{@tiles_count}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ require 'ostruct'
2
+ class OpenStruct
3
+ undef class
4
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def numeric?
3
+ Float(self) rescue false
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ module Beefdump
2
+ class ImageUtil
3
+
4
+ # credits: http://snippets.dzone.com/posts/show/805
5
+ def self.dimensions(file)
6
+ case file
7
+ when /\.png$/i
8
+ IO.read(file)[0x10..0x18].unpack('NN')
9
+ when /\.jpg$/i
10
+ File.open(file, 'rb') do |io|
11
+ raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI
12
+
13
+ class << io
14
+ def readint; (readchar << 8) + readchar; end
15
+ def readframe; read(readint - 2); end
16
+ def readsof; [readint, readchar, readint, readint, readchar]; end
17
+ def next
18
+ c = readchar while c != 0xFF
19
+ c = readchar while c == 0xFF
20
+ c
21
+ end
22
+ end
23
+
24
+ while marker = io.next
25
+ case marker
26
+ when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
27
+ length, bits, height, width, components = io.readsof
28
+ raise 'malformed JPEG' unless length == 8 + components * 3
29
+ when 0xD9, 0xDA: break # EOI, SOS
30
+ when 0xFE: @comment = io.readframe # COM
31
+ when 0xE1: io.readframe # APP1, contains EXIF tag
32
+ else io.readframe # ignore frame
33
+ end
34
+ end
35
+ [width, height]
36
+ end
37
+ when /\.bmp$/i
38
+ d = IO.read(file)[14..28]
39
+ d[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS')
40
+ when /\.gif$/i
41
+ IO.read(file)[6..10].unpack('SS')
42
+ else
43
+ raise "Unsupported image type or 'wrong' file name suffix :/"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ module Beefdump
2
+ module World
3
+ class Entity
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ module Beefdump
2
+ module World
3
+ class Object < Entity
4
+ require 'beefdump/world/object_property'
5
+
6
+ attr_reader :type, :properties
7
+
8
+ def initialize(object_data)
9
+ load_object_attributes!(object_data)
10
+
11
+ load_properties!(object_data["property"])
12
+ end
13
+
14
+ protected
15
+ def load_object_attributes!(object_data)
16
+ raise "Objects must have a type!" unless @type = object_data["type"].to_sym
17
+ end
18
+
19
+ def load_properties!(properties)
20
+ return unless properties
21
+
22
+ @properties = {}
23
+ properties.each do |property|
24
+ load_property!(property)
25
+ end
26
+ end
27
+
28
+ def load_property!(property)
29
+ mandatory = property["mandatory"]
30
+ mandatory = mandatory.downcase == "true" if mandatory
31
+
32
+ name = property["name"].to_sym
33
+ @properties[name] = ObjectProperty.new(name, property["type"], mandatory)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ module Beefdump
2
+ module World
3
+ class ObjectProperty
4
+ TYPES = [:number, :string, :name_reference]
5
+
6
+ attr_reader :name, :type
7
+
8
+ def initialize(name, type, mandatory = false)
9
+ raise "Any object property need a name!" unless @name = name
10
+
11
+ raise "Object property '#{@name}' has not type. Any property needs a type!" unless type
12
+ type = type.to_sym
13
+ raise "Object property type '#{type}' is not supported! Use one of: #{TYPES.join(',')}." unless TYPES.include?(type)
14
+ @type = type
15
+
16
+ @mandatory = !!mandatory
17
+ end
18
+
19
+ def mandatory?
20
+ @mandatory
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module Beefdump
2
+ module World
3
+ class Player < Entity
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,25 @@
1
+ module Beefdump
2
+ module World
3
+ require 'xmlsimple'
4
+ require 'beefdump/world/entity'
5
+ require 'beefdump/world/object'
6
+ require 'beefdump/world/player'
7
+
8
+ # Loads the object descriptions from the objects.xml file in the config folder.
9
+ def self.load
10
+ objects_file = "#{CONFIG_PATH}/objects.xml"
11
+ raise "Objects configuration missing! Please supply '#{objects_file}'." unless File.exist?(objects_file)
12
+
13
+ raise "Object configuration format invalid!" unless objectset = XmlSimple.xml_in(objects_file)["object"]
14
+ @objects = {}
15
+ objectset.each do |object_data|
16
+ object = Object.new(object_data)
17
+ @objects[object.type] = object
18
+ end
19
+ end
20
+
21
+ def self.objects
22
+ @objects
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beefdump
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - "Thorben Schr\xC3\xB6der"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-07 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: xml-simple
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: A testbed for non-player character AI in role-playing games.
36
+ email: stillepost@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.md
43
+ files:
44
+ - lib/beefdump.rb
45
+ - lib/beefdump/boot.rb
46
+ - lib/beefdump/client/base.rb
47
+ - lib/beefdump/client/graphical/client.rb
48
+ - lib/beefdump/client/graphical/window.rb
49
+ - lib/beefdump/config/base.rb
50
+ - lib/beefdump/config/blueprint.rb
51
+ - lib/beefdump/game/base.rb
52
+ - lib/beefdump/game/player.rb
53
+ - lib/beefdump/game/state.rb
54
+ - lib/beefdump/logger.rb
55
+ - lib/beefdump/map/layer.rb
56
+ - lib/beefdump/map/map.rb
57
+ - lib/beefdump/map/object.rb
58
+ - lib/beefdump/map/tileset.rb
59
+ - lib/beefdump/monkey_patches/open_struct.rb
60
+ - lib/beefdump/monkey_patches/string.rb
61
+ - lib/beefdump/utils/image_util.rb
62
+ - lib/beefdump/world/entity.rb
63
+ - lib/beefdump/world/object.rb
64
+ - lib/beefdump/world/object_property.rb
65
+ - lib/beefdump/world/player.rb
66
+ - lib/beefdump/world/world.rb
67
+ - README.md
68
+ has_rdoc: true
69
+ homepage: http://github.com/walski/beefdump
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options:
74
+ - --charset=UTF-8
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.7
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: A testbed for non-player character AI in role-playing games.
102
+ test_files: []
103
+