shattered_pack 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/shattered_controller.rb +3 -0
- data/lib/shattered_controller/actor/actor.rb +107 -0
- data/lib/shattered_controller/base.rb +108 -0
- data/lib/shattered_controller/keyboard_input/key_converter.rb +43 -0
- data/lib/shattered_controller/keyboard_input/keyboard_input.rb +106 -0
- data/lib/shattered_controller/mock_camera.rb +9 -0
- data/lib/shattered_controller/runner.rb +33 -0
- data/lib/shattered_controller/state.rb +59 -0
- data/lib/shattered_model.rb +3 -0
- data/lib/shattered_model/base.rb +24 -0
- data/lib/shattered_model/fuzzy_logic.rb +188 -0
- data/lib/shattered_model/linear_interpolator.rb +29 -0
- data/lib/shattered_pack.rb +11 -0
- data/lib/shattered_pack/base.rb +127 -0
- data/lib/shattered_pack/pre_initialize/pre_initialize.rb +105 -0
- data/lib/shattered_pack/runner.rb +11 -0
- data/lib/shattered_pack/timer/timed_event.rb +77 -0
- data/lib/shattered_pack/timer/timer.rb +75 -0
- data/lib/shattered_view.rb +7 -0
- data/lib/shattered_view/base.rb +151 -0
- data/lib/shattered_view/camera.rb +7 -0
- data/lib/shattered_view/extensions.rb +10 -0
- data/lib/shattered_view/light.rb +28 -0
- data/lib/shattered_view/mesh/animation.rb +20 -0
- data/lib/shattered_view/mesh/mesh.rb +135 -0
- data/lib/shattered_view/node.rb +113 -0
- data/lib/shattered_view/resources.rb +47 -0
- data/lib/shattered_view/rmaterial.rb +43 -0
- data/lib/shattered_view/runner.rb +48 -0
- data/lib/shattered_view/utilities.rb +7 -0
- data/lib/shattered_view/vector.rb +242 -0
- metadata +75 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module ShatteredModel #:nodoc:
|
2
|
+
def self.append_features(base)
|
3
|
+
super
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
module ClassMethods #:nodoc:
|
7
|
+
def fuzzy_logic( file )
|
8
|
+
before_init_set("self", {:fuzzy_logic => [file]})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
# Models encompass all of the game play logic and the game specific data.
|
12
|
+
#
|
13
|
+
# Models are useful for unit tests and for making game rules.
|
14
|
+
# They are where all logic (AI/collisions/etc) go.
|
15
|
+
class Base < ShatteredPack::Base
|
16
|
+
def fuzzy_logic=(file) #:nodoc:
|
17
|
+
@fuzzy_logic = FuzzyLogic.new
|
18
|
+
@fuzzy_logic.parse_fuzzy_file(File.dirname(__FILE__)+"/#{file}")
|
19
|
+
end
|
20
|
+
def update_fuzzy_logic #:nodoc:
|
21
|
+
@fuzzy_logic.update(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
# This provides basic fuzzy logic. That is, it will tell you what properties any given
|
3
|
+
# attribute contains based upon previous definition.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# player_logic.add_classification( :hp, :dieing, :hp, :max => 5, :min => 45 )
|
7
|
+
# player_logic.add_classification( :hp, :healthy, :max => 80, :min => 65 )
|
8
|
+
# player_logic.hp(15) => [[:dieing, 0.75], [:healthy, 0.0]]
|
9
|
+
class FuzzyLogic #:nodoc:
|
10
|
+
attr_reader :consequences
|
11
|
+
def initialize
|
12
|
+
@attributes = {}
|
13
|
+
@consequences = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_consequence( consequence, classifications={} )
|
17
|
+
@consequences[classifications] ||= []
|
18
|
+
@consequences[classifications] += [consequence]
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_classification( attribute, classification, options = {} )
|
22
|
+
define_attribute(attribute) if(@attributes[attribute].nil?)
|
23
|
+
define_interpolator(attribute,classification,options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def consequences_of(options={})
|
27
|
+
# transform the passed in attributes to probability sets
|
28
|
+
valid_sequences = []
|
29
|
+
options.each_pair do |attribute, value|
|
30
|
+
valid_sequences << {attribute => classifications_of(attribute,value)}
|
31
|
+
end
|
32
|
+
# combine the probability sets
|
33
|
+
valid_combinations = compile_consequence_combinations(valid_sequences)
|
34
|
+
# realize the consequences of the combined probability sets
|
35
|
+
return consequences_of_combinations(valid_combinations)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_fuzzy_file( file_name )
|
39
|
+
file_name += ".yaml" if not(file_name =~ /\.yaml$/)
|
40
|
+
raise ArgumentError, "Cannot find #{file_name}" if not(File.file? file_name)
|
41
|
+
begin
|
42
|
+
fuzzy_file = YAML::load(File.open(file_name))
|
43
|
+
rescue ArgumentError => bang
|
44
|
+
raise ArgumentError, "Error when loading #{file_name} : #{bang.message}"
|
45
|
+
end
|
46
|
+
parse_attributes(fuzzy_file)
|
47
|
+
add_consequences_from_parsed(parse_consequences(fuzzy_file['consequences']))
|
48
|
+
end
|
49
|
+
|
50
|
+
# The algorithm always performs an action on update.
|
51
|
+
def update(object)
|
52
|
+
status = {}
|
53
|
+
@attributes.each_pair do |attribute, classifications|
|
54
|
+
status.merge!({attribute => object.send(attribute)})
|
55
|
+
end
|
56
|
+
consequences = consequences_of(status)
|
57
|
+
total_probability = 0
|
58
|
+
consequences.each_pair do |actions, probability|
|
59
|
+
total_probability+=probability
|
60
|
+
end
|
61
|
+
action_at_probability = rand*total_probability
|
62
|
+
total_probability = 0
|
63
|
+
consequences.each_pair do |actions, probability|
|
64
|
+
total_probability += probability
|
65
|
+
if( action_at_probability < total_probability )
|
66
|
+
actions.each do |action|
|
67
|
+
object.send(action)
|
68
|
+
end
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# This function recursively moves through each valid combination of probabilities.
|
77
|
+
# It sums the probabilities by multiplication.
|
78
|
+
def compile_consequence_combinations(valid_sequences, compiled={}, probability=1.0)
|
79
|
+
retv = []
|
80
|
+
attribute = valid_sequences[0].keys[0]
|
81
|
+
classifications = valid_sequences[0][attribute]
|
82
|
+
classifications.each do |classification, chance|
|
83
|
+
next if chance == 0
|
84
|
+
nprobability=probability*chance
|
85
|
+
compiled[attribute]=classification
|
86
|
+
if(valid_sequences.length == 1)
|
87
|
+
retv << [compiled.dup, nprobability]
|
88
|
+
else
|
89
|
+
retv += compile_consequence_combinations(valid_sequences[1..-1],compiled,nprobability)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return retv
|
93
|
+
end
|
94
|
+
|
95
|
+
def consequences_of_combinations(combinations)
|
96
|
+
consequences = {}
|
97
|
+
combinations.each do |combination, probability|
|
98
|
+
# where a = { {1 => 2}, 3 } and b = {1,2}, a[b] != 3
|
99
|
+
# in order to circumvent this (error?) we use the fact that
|
100
|
+
# a.keys[0] == b
|
101
|
+
@consequences.keys.each do |index|
|
102
|
+
next if index != combination
|
103
|
+
consequences[@consequences[index]] ||= 0
|
104
|
+
consequences[@consequences[index]]+=probability
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return consequences
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_attributes(yaml)
|
111
|
+
yaml['attributes'].each_pair do |attribute, classifications|
|
112
|
+
classifications.each_pair do |classification, options|
|
113
|
+
options.keys.each do |key|
|
114
|
+
options[key.to_sym]=options.delete key
|
115
|
+
end
|
116
|
+
add_classification( attribute.to_sym, classification.to_sym, options )
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def parse_consequences(consequences,result={})
|
122
|
+
compilation = []
|
123
|
+
consequences.each_pair do |consequence, action|
|
124
|
+
nresult = result.dup
|
125
|
+
nresult.merge!(attribute_for(consequence.to_sym) => consequence.to_sym)
|
126
|
+
if action.is_a? String
|
127
|
+
compilation += [nresult => action.to_sym]
|
128
|
+
else
|
129
|
+
compilation+=parse_consequences(action,nresult)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
return compilation
|
133
|
+
end
|
134
|
+
|
135
|
+
def add_consequences_from_parsed(compilation)
|
136
|
+
compilation.each do |item|
|
137
|
+
item.each_pair do |index, action|
|
138
|
+
add_consequence(action, index)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def attribute_for(unknown_classification)
|
144
|
+
@attributes.each_pair do |attribute, classifications|
|
145
|
+
classifications.keys.each do |classification|
|
146
|
+
return attribute if classification == unknown_classification
|
147
|
+
end
|
148
|
+
end
|
149
|
+
return nil
|
150
|
+
end
|
151
|
+
|
152
|
+
def define_attribute(attribute)
|
153
|
+
@attributes[attribute] = {}
|
154
|
+
eval <<-EOF
|
155
|
+
class << self
|
156
|
+
define_method(:#{attribute}) do |at|
|
157
|
+
classifications_of(:#{attribute},at)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
EOF
|
161
|
+
end
|
162
|
+
|
163
|
+
def define_interpolator( attribute, classification,options )
|
164
|
+
interpolator = LinearInterpolator.new
|
165
|
+
@attributes[attribute][classification] = interpolator
|
166
|
+
|
167
|
+
add_point(interpolator, options[:max],1)
|
168
|
+
add_point(interpolator, options[:min],0)
|
169
|
+
end
|
170
|
+
|
171
|
+
def classifications_of( attribute, at )
|
172
|
+
retv = []
|
173
|
+
@attributes[attribute].each_pair do |classification,interpolator|
|
174
|
+
retv << [ classification, interpolator.value_at(at) ]
|
175
|
+
end
|
176
|
+
return retv.sort_by { |classification| (1.0-classification[1]) }
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_point( interpolator, points, value )
|
180
|
+
if(points.is_a? Array)
|
181
|
+
points.each do |x|
|
182
|
+
interpolator.add_point(x,value)
|
183
|
+
end
|
184
|
+
else
|
185
|
+
interpolator.add_point(points,value)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This provides basic linear interpolation between any number of arbitrarily defined points.
|
2
|
+
# It is used for fuzzy logic. Although, fuzzy logic based on a statistically normal curve
|
3
|
+
# provides best results, linear interpolation provides a "gud 'nuf" approach.
|
4
|
+
class LinearInterpolator #:nodoc:
|
5
|
+
def add_point(x,y)
|
6
|
+
@points ||= []
|
7
|
+
@points << [x,y]
|
8
|
+
@points = @points.sort_by { |x| x[0] }
|
9
|
+
end
|
10
|
+
def value_at(x)
|
11
|
+
return 0 if @points.nil?
|
12
|
+
return @points[0][1] if x < @points[0][0]
|
13
|
+
return @points[-1][1] if x > @points[-1][0]
|
14
|
+
|
15
|
+
after,before=nil,nil
|
16
|
+
@points.each_with_index do |point,i|
|
17
|
+
if(point[0] >= x)
|
18
|
+
after = point
|
19
|
+
before = @points[i-1]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
m=(before[1]-after[1])/(before[0]-after[0]).to_f
|
24
|
+
x-=before[0]
|
25
|
+
b=before[1]
|
26
|
+
|
27
|
+
return m*x+b
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
%w( base runner ).each do |component|
|
4
|
+
require "shattered_pack/#{component}"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'shattered_support'
|
8
|
+
require 'shattered_ogre'
|
9
|
+
require 'shattered_model'
|
10
|
+
require 'shattered_view'
|
11
|
+
require 'shattered_controller'
|
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
3
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
require 'timer/timer'
|
6
|
+
require 'pre_initialize/pre_initialize'
|
7
|
+
|
8
|
+
module ShatteredPack #:nodoc:
|
9
|
+
|
10
|
+
class Base
|
11
|
+
attr_accessor :time_elapsed
|
12
|
+
alias_method :per_second, :time_elapsed
|
13
|
+
|
14
|
+
# This is overwritten to allow for a pre_initialize before initialize
|
15
|
+
def self.new(*options) #:nodoc
|
16
|
+
new_base = allocate
|
17
|
+
new_base.pre_initialize
|
18
|
+
new_base.send(:initialize, *options)
|
19
|
+
return new_base
|
20
|
+
end
|
21
|
+
|
22
|
+
# Retrieve the current state
|
23
|
+
def state
|
24
|
+
Configuration.environment[:state]
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO - is this called anymore?
|
28
|
+
def update_event(time_elapsed) #:nodoc:
|
29
|
+
actors.each do |actor|
|
30
|
+
actor.update_actors(time_elapsed)
|
31
|
+
actor.update(time_elapsed)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# remove_from_scene? should just be defined in model.
|
36
|
+
# Refactor? TODO
|
37
|
+
def remove_from_scene? #:nodoc:
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO outdated?
|
42
|
+
def update_actors(time_elapsed) #:nodoc:
|
43
|
+
update_event time_elapsed
|
44
|
+
remove_dead_actors
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO outdated?
|
48
|
+
def remove_dead_actors #:nodoc:
|
49
|
+
actors.each do |actor|
|
50
|
+
remove_from_scene actor if actor.remove_from_scene?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO outdated?
|
55
|
+
def remove_from_scene(actor)
|
56
|
+
actors.delete actor
|
57
|
+
actor.unload!
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
public
|
62
|
+
|
63
|
+
# attr helpers. These are instance level attr_* functions.
|
64
|
+
def attr_reader(*args)
|
65
|
+
name, value = args
|
66
|
+
attr_define(:reader, name, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
# attr helpers. These are instance level attr_* functions.
|
70
|
+
# attr_writer accepts a value as the second argument
|
71
|
+
def attr_writer(*args)
|
72
|
+
name, value = args
|
73
|
+
attr_define(:writer, name, value)
|
74
|
+
end
|
75
|
+
|
76
|
+
# attr helpers. These are instance level attr_* functions.
|
77
|
+
# attr_accessor accepts a value as the second argument
|
78
|
+
def attr_accessor(*args)
|
79
|
+
name, value = args
|
80
|
+
attr_define(:accessor, name, value)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Define a block to execute when an object is unloaded.
|
84
|
+
def when_unloaded(&block)
|
85
|
+
@unloaded_events ||= []
|
86
|
+
@unloaded_events << block
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def attr_define(accessor_level, name, value)
|
92
|
+
self.class.send("attr_#{accessor_level}".to_sym, "#{name}".to_sym)
|
93
|
+
instance_variable_set("@#{name}".to_sym, value) if !value.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def disabled?
|
99
|
+
@unloaded_events == nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def unload!
|
103
|
+
return if disabled?
|
104
|
+
@unloaded_events.each do |event|
|
105
|
+
event.call
|
106
|
+
end
|
107
|
+
@unloaded_events = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class Error < StandardError # :nodoc:
|
113
|
+
end
|
114
|
+
|
115
|
+
class RetossError < StandardError # :nodoc:
|
116
|
+
attr_accessor :message
|
117
|
+
def initialize(error, message)
|
118
|
+
self.message = message
|
119
|
+
set_backtrace error.backtrace
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
ShatteredPack::Base.class_eval do
|
125
|
+
include ShatteredPack::Timer
|
126
|
+
include ShatteredPack::PreInitialize
|
127
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module ShatteredPack
|
2
|
+
module PreInitialize #:nodoc:all
|
3
|
+
def self.append_features(base)
|
4
|
+
super
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
base.send(:include, InstanceMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
BEFORE_INIT_CALL_VALUES = :before_init_call_values
|
10
|
+
BEFORE_INIT_SET_VALUES = :before_init_set_values
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
# In the pre_initialization phase (see #before_init_call), send an object a bunch of sets.
|
15
|
+
#
|
16
|
+
# This is used in actor, camera, mesh, and virtually everywhere to support the format of:
|
17
|
+
# mesh "ruby", :position => v(1,0,0)
|
18
|
+
#
|
19
|
+
# becomes
|
20
|
+
# before_init_set( :ruby, {:position => v(1,0,0) } )
|
21
|
+
# becomes
|
22
|
+
# ruby.position=v(1,0,0)
|
23
|
+
# when the obect is initialized.
|
24
|
+
def before_init_set(variable, options={})
|
25
|
+
self.write_inheritable_array(BEFORE_INIT_SET_VALUES, [[ variable, options ]] )
|
26
|
+
end
|
27
|
+
|
28
|
+
# All shattered objects have a pre_initialization phase. Use this to specify that
|
29
|
+
# a function should be called when the object is created.
|
30
|
+
#
|
31
|
+
# _Example_:
|
32
|
+
# class BulletModel < ShatteredModel::Base
|
33
|
+
# before_init_call(:calculate_trajectory, v(0,0,0), v(0,0,1))
|
34
|
+
# def calculate_trajectory
|
35
|
+
# #called before initialize is
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def before_init_call(function, *arguments)
|
40
|
+
self.write_inheritable_array(BEFORE_INIT_CALL_VALUES, [[function, [*arguments]]])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
module InstanceMethods
|
44
|
+
# This function is called after an object is allocated, but before it's initialized.
|
45
|
+
#
|
46
|
+
# See ShatteredPack::ClassMethods#before_init_call for usage.
|
47
|
+
def pre_initialize #:nodoc:
|
48
|
+
pre_initialize_set
|
49
|
+
pre_initialize_call
|
50
|
+
end
|
51
|
+
|
52
|
+
# Used in pre_initialize
|
53
|
+
def each_init_value(name) #:nodoc:
|
54
|
+
startup_attributes = self.class.read_inheritable_attribute( name ) || []
|
55
|
+
startup_attributes.each do |actor, options|
|
56
|
+
next if options.nil?
|
57
|
+
yield actor, options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Used in pre_initialize for before_init_set
|
62
|
+
def pre_initialize_set #:nodoc:
|
63
|
+
each_init_value(BEFORE_INIT_SET_VALUES) do |variable, options|
|
64
|
+
call_object_function_for_each_key( variable, options )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Used in pre_initialize for before_init_call
|
69
|
+
def pre_initialize_call #:nodoc:
|
70
|
+
each_init_value(BEFORE_INIT_CALL_VALUES) do |function, args|
|
71
|
+
call_object_function( :self, function, args )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_object_function_for_each_key( actor, options )
|
76
|
+
options.each_pair do |action, params|
|
77
|
+
call_object_function actor, "#{action}=", params
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def call_object_function(actor, action, params)
|
82
|
+
begin
|
83
|
+
if params.is_a? Symbol #|| (params.length == 1 && params[0].is_a?(Symbol)) this will allow substitution in before_init_call. This may not be intended behavior.
|
84
|
+
params = eval(params.to_s)
|
85
|
+
end
|
86
|
+
rescue NameError
|
87
|
+
puts "It is not advisable to pass #{params.inspect} to #{action.inspect} for #{actor.inspect}."
|
88
|
+
puts " It will try to be evaluated. Use a string instead."
|
89
|
+
end
|
90
|
+
begin
|
91
|
+
sendee = eval(actor.to_s)
|
92
|
+
if(params.is_a? Array)
|
93
|
+
sendee.send( action.to_sym, *params )
|
94
|
+
else
|
95
|
+
sendee.send( action.to_sym, params )
|
96
|
+
end
|
97
|
+
rescue NoMethodError, ArgumentError => bang
|
98
|
+
message="Error upon #{actor.to_s}.send(#{action.to_sym.inspect}, #{params.inspect}):\n\r #{bang.class}: \n\r\t #{bang.message}\n\r #{bang.backtrace[0]}\n\r"
|
99
|
+
bang = ShatteredPack::RetossError.new(bang,message)
|
100
|
+
raise bang
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|