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.
- data/README.md +13 -0
- data/lib/beefdump.rb +16 -0
- data/lib/beefdump/boot.rb +35 -0
- data/lib/beefdump/client/base.rb +33 -0
- data/lib/beefdump/client/graphical/client.rb +94 -0
- data/lib/beefdump/client/graphical/window.rb +24 -0
- data/lib/beefdump/config/base.rb +8 -0
- data/lib/beefdump/config/blueprint.rb +7 -0
- data/lib/beefdump/game/base.rb +95 -0
- data/lib/beefdump/game/player.rb +12 -0
- data/lib/beefdump/game/state.rb +22 -0
- data/lib/beefdump/logger.rb +19 -0
- data/lib/beefdump/map/layer.rb +53 -0
- data/lib/beefdump/map/map.rb +136 -0
- data/lib/beefdump/map/object.rb +75 -0
- data/lib/beefdump/map/tileset.rb +35 -0
- data/lib/beefdump/monkey_patches/open_struct.rb +4 -0
- data/lib/beefdump/monkey_patches/string.rb +5 -0
- data/lib/beefdump/utils/image_util.rb +47 -0
- data/lib/beefdump/world/entity.rb +6 -0
- data/lib/beefdump/world/object.rb +37 -0
- data/lib/beefdump/world/object_property.rb +24 -0
- data/lib/beefdump/world/player.rb +6 -0
- data/lib/beefdump/world/world.rb +25 -0
- metadata +103 -0
data/README.md
ADDED
data/lib/beefdump.rb
ADDED
@@ -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,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,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,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,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,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
|
+
|