beefdump 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+