adjective-rpg-engine 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd16149c10e34afcb91fdcc8588d8c4060752aff2926a948bcac4b606b320feb
4
- data.tar.gz: 1f1852b092810dae02b68e99274c5ad4c2990b9d828ba58ac60fa20f7c884f8e
3
+ metadata.gz: 1e6db5b958ab81f3548404fa67db1822c9ca0c4a086e7eaf535dc0f917bc2ab7
4
+ data.tar.gz: 1e5174dd569949d858a020f4a1be2524b78d5c9800a59617f1b1f3bb67f381f7
5
5
  SHA512:
6
- metadata.gz: 6a8a97be51c2d6d3ffa76bab818b85d6f4d52d3672e2325ccd52cdb2c93d26cb457318b9ddc2bdb2b50349e79ea5fb86f135397a2fc6a9b317bb5650c535a6ae
7
- data.tar.gz: e0e74f3800caf3ae9040211266ff4adb7f9bddddfdf7ac3c57fa92ce4b472850fa7bcb2376421c7c75312d4657bf5ab6896403289e98e10f551c4ff03da57bee
6
+ metadata.gz: c15b279c70eac977095b596167e8905b851c5eec1d9bcaefc38fe11bafd398f26f9ab33ca8d987322d860e0d1728b6702be92607ce4280ef4661e539be7858f1
7
+ data.tar.gz: 15fb8216f0531aae3fad0e958975d79f3c5acee7102c846fcf13a09781fd970ae11bb26391ce0323a9c69bd6944245aee9706aba76ae86d8c1faa26ac41042b9
@@ -0,0 +1,69 @@
1
+ module Adjective
2
+
3
+ module Imbibable
4
+ # Able to be enhanced by experience
5
+
6
+ def initialize_experience(opts)
7
+ @level = opts[:level] ||= 1
8
+ @experience = opts[:initial_exp] ||= 0
9
+ # This may throw an error if the user decides to instantiate a class without a table present.
10
+ @active_exp_set = opts[:exp_table]
11
+ [:active_exp_set, :level].each {|attribute| self.class.send(:attr_reader, attribute)}
12
+ self.class.send(:attr_accessor, :experience)
13
+ set_experience_to_level_minimum
14
+ end
15
+
16
+ def set_experience_to_level_minimum
17
+ @experience = @active_exp_set[@level]
18
+ end
19
+
20
+ def level_up
21
+ until !can_level_up?
22
+ @level += 1
23
+ end
24
+ end
25
+
26
+ # Level down functionality should work the same way as simply setting a level.
27
+
28
+ def normalize_experience
29
+ @experience = 0 if @experience < 0
30
+ end
31
+
32
+ def max_level
33
+ @active_exp_set.length - 1
34
+ end
35
+
36
+ def max_level?
37
+ @active_exp_set.length - 1 <= @level
38
+ end
39
+
40
+ def can_level_up?
41
+ return false if max_level?
42
+ @experience >= @active_exp_set[@level+1]
43
+ end
44
+
45
+ def grant_experience(exp, opts = {})
46
+ return false if max_level?
47
+ @experience += exp
48
+ level_up if !opts[:suppress_level_up]
49
+ end
50
+
51
+ def set_level(num, opts = {})
52
+ @level = num
53
+ @experience = @active_exp_set[num] if !opts[:constrain_exp]
54
+ end
55
+
56
+ def grant_levels(num, opts = {})
57
+ @level += num
58
+ @experience = @active_exp_set[@level] if !opts[:constrain_exp]
59
+ end
60
+
61
+ def experience_to_next_level
62
+ return nil if max_level?
63
+ return @active_exp_set[@level+1] - @experience
64
+ end
65
+
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,79 @@
1
+ module Adjective
2
+
3
+ # I plan on having this module handle the messier side of buff/debuff processing
4
+ # It will need to include a way to manage the coupling between Status and the target model.
5
+ # It will need to be able to process and return values that can be easily passed into other
6
+ # methods.
7
+
8
+ module Statusable
9
+
10
+ def initialize_status_data
11
+ @statuses = []
12
+ self.class.send(:attr_accessor, :statuses)
13
+ end
14
+
15
+ def apply_status(status, &block)
16
+ validate_modifier_existence(status)
17
+ validate_initial_attributes(status.affected_attributes)
18
+ yield(self, status) if block_given?
19
+ @statuses.push(status)
20
+ return @statuses
21
+ end
22
+
23
+ # Actually has three cases
24
+ # 1: has and responds to given method PERIOD
25
+ def has_status?(attribute, match)
26
+ @statuses.each do |status|
27
+ if status.respond_to?(attribute)
28
+ return true if status.send(attribute) == match
29
+ end
30
+ end
31
+ return false
32
+ end
33
+
34
+ def tick_all(&block)
35
+ # Provides baseline functionality to + or - values from a given status, as this is
36
+ # the most common of implementations.
37
+ # Strings and other values are simply set.
38
+ # Yielding to an arbitrary block should curcumvent any issues with extension and post-effect hooks.
39
+ @statuses.each do |status|
40
+ validate_modifier_existence(status)
41
+ status.modifiers.each do |key, value|
42
+ attribute = key.to_s
43
+ if (value.is_a?(Integer) || value.is_a?(Float))
44
+ eval("self.#{attribute} += #{value}") if self.respond_to?(attribute+"=")
45
+ else
46
+ send("#{attribute}=", value) if self.respond_to?(attribute+"=")
47
+ end
48
+ end
49
+ status.tick
50
+ end
51
+ yield(self, @statuses) if block_given?
52
+ return @statuses
53
+ end
54
+
55
+ def clear_expired_statuses
56
+ unexpired = @statuses.select { |status| status.duration != 0 }
57
+ diff = @statuses - unexpired
58
+ @statuses = unexpired
59
+ return diff
60
+ end
61
+
62
+ private
63
+
64
+ def validate_initial_attributes(affected_attributes)
65
+ invalid = affected_attributes.select {|att| !instance_variable_defined?(att) }
66
+ warn_about_attributes if invalid.length > 0
67
+ end
68
+
69
+ def validate_modifier_existence(status)
70
+ raise RuntimeError, "Given status does not respond to #modifiers." if !status.respond_to?(:modifiers)
71
+ end
72
+
73
+ def warn_about_attributes
74
+ warn("[#{Time.now}]: Gave affected_attributes in a Status that are not present on #{self.class.name}: #{invalids}.")
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,134 @@
1
+ module Adjective
2
+
3
+ module Storable
4
+
5
+ def initialize_storage_data(items = [], opts = {})
6
+ @items = items
7
+ @initialized_at = Time.now
8
+ @max_size = opts[:max_size] ||= :unlimited
9
+ @default_sort_method = opts[:default_sort_method] ||= :object_id
10
+ validate_inventory_capacity
11
+ [:items, :initialized_at, :max_size, :default_sort_method].each {|attribute| self.class.send(:attr_accessor, attribute) }
12
+ end
13
+
14
+ # Utility Methods
15
+ def empty?
16
+ @items.length === 0
17
+ end
18
+
19
+ def full?
20
+ @max_size == :unlimited ? false : @items.length < @max_size
21
+ end
22
+
23
+ def can_store?(items)
24
+ return true if @max_size == :unlimited
25
+ return true if (Array(items).length + @items.length) <= @max_size
26
+ return false
27
+ end
28
+
29
+ def empty_slots
30
+ @max_size == :unlimited ? :unlimited : @max_size - @items.length
31
+ end
32
+
33
+ # Simple Search
34
+ # options for scope are: :all, :attributes, :values
35
+ def query(term, scope = :all)
36
+ validate_query_scope(scope)
37
+ matches = []
38
+ @items.each do |item|
39
+ chunks = []
40
+ attributes = item.instance_variables.map {|ivar| ivar.to_s.gsub("@", "").to_sym}
41
+ attributes.each do |attribute|
42
+ chunks.push(construct_query_data(attribute.to_s, item.send(attribute).to_s)[scope])
43
+ end
44
+ matches << item if chunks.join.include?(term)
45
+ end
46
+ return matches
47
+ end
48
+
49
+ # Store - Put
50
+ def store(items)
51
+ if can_store?(items)
52
+ Array(items).each do |item|
53
+ @items << item
54
+ end
55
+ return items
56
+ else
57
+ return false
58
+ end
59
+ end
60
+
61
+ # retrieve_by - Get
62
+ def retrieve_by(attribute, value)
63
+ @items.select do |item|
64
+ item if item.respond_to?(attribute) && item.send(attribute) === value
65
+ end
66
+ end
67
+
68
+ # Dump - Delete all
69
+ def dump
70
+ outbound_items = @items
71
+ @items = []
72
+ return outbound_items
73
+ end
74
+
75
+ # Dump selection - Selective delete
76
+ def dump_by(attribute, value)
77
+ outbound_items = retrieve_by(attribute, value)
78
+ @items = @items.select {|item| !outbound_items.include?(item) }
79
+ return outbound_items
80
+ end
81
+
82
+ # Sorting
83
+ def sort
84
+ @items.sort_by { |item| item.send(@default_sort_method) }
85
+ return @items
86
+ end
87
+
88
+ def sort!
89
+ @items = @items.sort_by { |item| item.send(@default_sort_method) }
90
+ return @items
91
+ end
92
+
93
+ def sort_by(attribute, order = :asc)
94
+ sorted = @items.sort_by(&attribute)
95
+ return order == :asc ? sorted : sorted.reverse
96
+ end
97
+
98
+ def sort_by!(attribute, order = :asc)
99
+ sorted = @items.sort_by(&attribute)
100
+ return order == :asc ? @items = sorted : @items = sorted.reverse
101
+ end
102
+
103
+ alias_method :deposit, :store
104
+ alias_method :put, :store
105
+ alias_method :get_by, :retrieve_by
106
+ alias_method :find_by, :retrieve_by
107
+ alias_method :clear, :dump
108
+ alias_method :clear_by, :dump_by
109
+ alias_method :search, :query
110
+
111
+ private
112
+
113
+ def validate_query_scope(scope)
114
+ raise ArgumentError, "[#{Time.now}]: Please provide :full, :attributes, or :values to the scope parameter: given #{scope}" if ![:all, :attributes, :values].include?(scope)
115
+ end
116
+
117
+ def construct_query_data(attribute, val)
118
+ # Delimiting with &: to avoid issues with intermingled data
119
+ return {
120
+ all: attribute + "&:" + val + "&:",
121
+ attributes: attribute + "&:",
122
+ values: val + "&:"
123
+ }
124
+ end
125
+
126
+ def validate_inventory_capacity
127
+ return true if @max_size == :unlimited
128
+ total_length = @items.length
129
+ raise ArgumentError, "#{Time.now}]: items argument length larger than max size: max_size: #{@max_size}, items_length: #{total_length}" if total_length > @max_size
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,94 @@
1
+ module Adjective
2
+
3
+ module Temporable
4
+
5
+ # This is to be used to emulate turn-based interactions or to emulate basic time within the context of a parent class.
6
+
7
+ # The idea behind setting an initial_duration is so you can see what the subject started out as
8
+ # instead of making an assumption. This could matter if multiple same-identity classes have the same attributes,
9
+ # but have different durations linked to them.
10
+
11
+ # Initialize module data for Temporable
12
+ # @param opts [Hash]
13
+ # @return [Object]
14
+ # @example
15
+ # initialize_temporality({max_duration: 5, remaining_duration: 4})
16
+ def initialize_temporality(opts={})
17
+ @max_duration = opts[:max_duration] ||= 1
18
+ @remaining_duration = opts[:remaining_duration] ||= @max_duration
19
+
20
+ throw_duration_theshold_error if invalid_durations?
21
+
22
+ [:max_duration, :remaining_duration].each do |attribute|
23
+ self.class.send(:attr_accessor, attribute)
24
+ end
25
+ normalize_remaining_duration
26
+ return self
27
+ end
28
+
29
+ # Checks if the status is at it's maximum duration
30
+ # @return [Boolean]
31
+ # @example
32
+ # SurrogateClass.max_duration? #=> True/False
33
+ def max_duration?
34
+ @max_duration == @remaining_duration
35
+ end
36
+
37
+ # Checks if remaining_duration is at 0.
38
+ # @return [Boolean]
39
+ # @example
40
+ # SurrogateClass.expired? #=> True/False
41
+ def expired?
42
+ @remaining_duration == 0
43
+ end
44
+
45
+ # Checks if remaining_duration is at 1.
46
+ # @return [Boolean]
47
+ # @example
48
+ # SurrogateClass.expired? #=> True/False
49
+ def expiring?
50
+ # This method seems like a meme, but I think it makes sense
51
+ @remaining_duration == 1
52
+ end
53
+
54
+ # Checks and sets remaining_duration if it is out of bounds.
55
+ # @return [Integer]
56
+ # @example
57
+ # SurrogateClass.normalize_remaining_duration
58
+ def normalize_remaining_duration
59
+ @remaining_duration = @max_duration if @remaining_duration > @max_duration
60
+ @remaining_duration = 0 if @remaining_duration < 0
61
+ end
62
+
63
+ # Extends the duration and keeps it within bounds.
64
+ # @param extension [Integer]
65
+ # @return [Integer]
66
+ # @example
67
+ # SurrogateClass.extend_by(2)
68
+ def extend_by(extension)
69
+ @remaining_duration += extension
70
+ normalize_remaining_duration
71
+ return @remaining_duration
72
+ end
73
+
74
+ private
75
+
76
+ # Triggers error to the thrown is thesholds are exceeed on initialization.
77
+ # @return [Boolean]
78
+ # @private
79
+ # @example
80
+ # SurrogateClass.throw_duration_threshold_error
81
+ def throw_duration_theshold_error
82
+ raise ArgumentError, "Provded initial_duration or remaining_duration values exceed max_duration: max: #{@max_duration}, remaining: #{@remaining_duration}."
83
+ end
84
+
85
+ # Checks if initial durations are valid.
86
+ # @return [Boolean]
87
+ # @example
88
+ # SurrogateClass.invalid_durations?
89
+ def invalid_durations?
90
+ @remaining_duration > @max_duration
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,41 @@
1
+ module Adjective
2
+
3
+ module Vulnerable
4
+ def initialize_vulnerability(hitpoints = 1, max_hitpoints = 10)
5
+ @hitpoints = hitpoints
6
+ @max_hitpoints = max_hitpoints
7
+ self.class.send(:attr_accessor, :hitpoints)
8
+ self.class.send(:attr_accessor, :max_hitpoints)
9
+ end
10
+
11
+ def take_damage(damage)
12
+ @hitpoints -= damage
13
+ normalize_hitpoints
14
+ return self
15
+ end
16
+
17
+ def heal_to_full
18
+ @hitpoints = @max_hitpoints
19
+ normalize_hitpoints
20
+ end
21
+
22
+ def heal_for(healing)
23
+ @hitpoints += healing
24
+ normalize_hitpoints
25
+ end
26
+
27
+ def alive?
28
+ @hitpoints > 0
29
+ end
30
+
31
+ def dead?
32
+ @hitpoints == 0
33
+ end
34
+
35
+ def normalize_hitpoints
36
+ @hitpoints = 0 if @hitpoints < 0
37
+ @hitpoints = @max_hitpoints if @hitpoints > @max_hitpoints
38
+ end
39
+ end
40
+
41
+ end
File without changes
@@ -0,0 +1,137 @@
1
+ module Adjective
2
+
3
+ # Status is different from something like an attack in that it applies
4
+ # to things that afflict the subject for one or more turns.
5
+ module Status
6
+
7
+ include Adjective::Temporable
8
+
9
+ # Initialize module data for Status
10
+ # @param opts [Hash]
11
+ # @return [Object]
12
+ # @example
13
+ # class SurrogateClass
14
+ # include Adjective::Status
15
+ # initialize_status({affected_attributes: { hitpoints: 3}, max_duration: 5})
16
+ # end
17
+ def initialize_status(opts = {})
18
+ attributes = opts[:affected_attributes]
19
+ @modifiers = attributes ||= {}
20
+ @affected_attributes = attributes.map{|entry| entry[0]}
21
+
22
+ # @applied_at Can be used to track simple object intantation if class is created when status is applied.
23
+ # TODO: If held in memory, opts will need to be given a :timestamp with a value comparable with a Time object. (Custom values should help?)
24
+ # If the user wishes to sort by a specific attribute in Statusable, then they should pass a block and do so there. (Maybe?)
25
+ @applied_at = opts[:timestamp] ||= Time.now
26
+
27
+ [:initialized_at, :affected_attributes, :modifiers].each do |attribute|
28
+ self.class.send(:attr_reader, attribute)
29
+ end
30
+
31
+ initialize_temporality(opts)
32
+ normalize_remaining_duration
33
+ assign_affected_attributes
34
+ return self
35
+ end
36
+
37
+ # Will perform tick functionality, whose default action is to reduce @remaining_duration (from Temporable) by 1.
38
+ # Otherwise, it will accept a block and bypass all default functionality.
39
+ # @param block [Block]
40
+ # @return [Object]
41
+ # @example
42
+ # SurrogateClass.tick
43
+ def tick(&block)
44
+ if block_given?
45
+ yield(self)
46
+ else
47
+ # Default
48
+ @remaining_duration -= 1
49
+ end
50
+ return self
51
+ end
52
+
53
+ # Checks if the status has a modifier present
54
+ # @return [Boolean]
55
+ # @example
56
+ # SurrogateClass.has_modifier?(:hitpoints)
57
+ def has_modifier?(attribute)
58
+ @modifiers.key?(attribute)
59
+ end
60
+
61
+ # Adds or updates the modifier hash.
62
+ # @param attribute [Symbol]
63
+ # @param value [Integer, Float, String]
64
+ # @return [Hash]
65
+ # @example
66
+ # SurrogateClass.add_or_update_modifer(:hitpoints, 10)
67
+ def add_or_update_modifier(attribute, value)
68
+ if has_modifier?(attribute)
69
+ @modifiers[attribute] = value
70
+ else
71
+ @modifiers.store(attribute, value)
72
+ end
73
+ assign_affected_attributes
74
+ return @modifiers
75
+ end
76
+
77
+ # Updates the modifier in @modifiers. Will warn and NOT amend if modifier does not exist.
78
+ # @param attribute [Symbol]
79
+ # @param value [Integer, Float, String]
80
+ # @return [Hash]
81
+ # @example
82
+ # SurrogateClass.update_modifier(:hitpoints, 12)
83
+ def update_modifier(attribute, value)
84
+ if has_modifier?(attribute)
85
+ @modifiers[attribute] = value
86
+ assign_affected_attributes
87
+ else
88
+ warn("[#{Time.now}]: Attempted to update a modifier that wasn't present: #{attribute}. Use #add_modifier or #add_or_update_modifier instead.")
89
+ end
90
+ return @modifiers
91
+ end
92
+
93
+ # Adds to the modifier to @modifiers. Will warn and NOT amend if modifier already exists.
94
+ # @param attribute [Symbol]
95
+ # @param value [Integer, Float, String]
96
+ # @return [Hash]
97
+ # @example
98
+ # SurrogateClass.add_modifer(:strength, 20)
99
+ def add_modifier(attribute, value)
100
+ if !has_modifier?(attribute)
101
+ @modifiers.store(attribute, value)
102
+ assign_affected_attributes
103
+ else
104
+ warn("[#{Time.now}]: Attempted to add duplicate modifier: #{attribute}. The new value has NOT been set. (Currently '#{@modifiers[attribute]}'.")
105
+ end
106
+ return @modifiers
107
+ end
108
+
109
+ # Removes the specified modifier from @modifers.
110
+ # @param attribute [Symbol]
111
+ # @param value [Integer, Float, String]
112
+ # @return [Hash]
113
+ # @example
114
+ # SurrogateClass.add_modifer(:strength, 20)
115
+ def remove_modifier(attribute)
116
+ if has_modifier?(attribute)
117
+ temp = {}.store(attribute, modifiers[attribute])
118
+ @modifiers.delete(attribute)
119
+ else
120
+ warn("[#{Time.now}]: Attempted to remove modifier that does not exist: #{attribute}")
121
+ end
122
+ return temp
123
+ end
124
+
125
+ private
126
+
127
+ # Converts the modifier hash into a digestable array for other modules to use. Includes '@' in modifier value,
128
+ # as the format for the metaprogramming in other modules requires the attribute to be called and set explicitly if not publicly writable.
129
+ # This should most likely be left alone and not called outside of the implementation here.
130
+ # @private
131
+ # @return [Hash]
132
+ def assign_affected_attributes
133
+ @affected_attributes.map!{|attribute| ("@"+attribute.to_s).to_sym }
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,73 @@
1
+ require 'yaml'
2
+
3
+ module Adjective
4
+ class Table
5
+ attr_accessor :name
6
+ attr_reader :data
7
+
8
+ def initialize(dir, name = nil)
9
+ file_existence_catch(dir)
10
+ @name = name
11
+ @data = YAML.load_file(dir)
12
+ @_created_at = Time.now
13
+ end
14
+
15
+ def load(dir)
16
+ file_existence_catch(dir)
17
+ @data = YAML.load_file(dir)
18
+ end
19
+
20
+ def set_exists?(name)
21
+ @data.key?(name)
22
+ end
23
+
24
+ private
25
+
26
+ def file_existence_catch(dir)
27
+ raise RuntimeError, "#{Time.now}]: Invalid path to YAML file: #{dir}" if !File.exist?(dir)
28
+ end
29
+
30
+ end
31
+
32
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
33
+ # Will separate out when I get the dir structures set up properly.
34
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
35
+
36
+ class Table::Experience < Table
37
+ # This class sets a 'by index' paradigm and expects an ordered set
38
+ # to be present after being given the parsed YAML file data.
39
+
40
+ # This is primarily to keep the access points standardized and would only require
41
+ # someone consuming the library to call something like level.to_i to maintain the convention.
42
+
43
+ attr_reader :thresholds
44
+
45
+ def initialize(dir, name = nil)
46
+ # raise ArgumentError
47
+ super(dir, name)
48
+ @thresholds = @data[@name]
49
+
50
+ if !@thresholds.is_a?(Array)
51
+ raise RuntimeError, "#{Time.now}]: Experience table '#{@name}' is not an Array: #{@exp_thresholds.class}"
52
+ elsif threshold_sorted?
53
+ raise RuntimeError, "#{Time.now}]: Experience table '#{@name}' is not sequential: #{@exp_thresholds}"
54
+ end
55
+ end
56
+
57
+ def at_level(level)
58
+ # Convenience methods to translate string cases might be worth it... but the
59
+ # general convention is that you pass through whole integers to grab data that is
60
+ # more reliable within the structure of the code itself. Going to just keep to
61
+ # convention for the moment.
62
+ raise RuntimeError, "#{Time.now}]: Level provided is not an Integer: #{level}" if !level.is_a?(Integer)
63
+ return @thresholds[level]
64
+ end
65
+
66
+ private
67
+
68
+ def threshold_sorted?
69
+ @thresholds != @thresholds.sort
70
+ end
71
+
72
+ end
73
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adjective-rpg-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Maze
@@ -45,6 +45,14 @@ extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
47
  - lib/adjective.rb
48
+ - lib/adjective/concerns/imbibable.rb
49
+ - lib/adjective/concerns/statusable.rb
50
+ - lib/adjective/concerns/storable.rb
51
+ - lib/adjective/concerns/temporable.rb
52
+ - lib/adjective/concerns/vulnerable.rb
53
+ - lib/adjective/item.rb
54
+ - lib/adjective/status.rb
55
+ - lib/adjective/table.rb
48
56
  homepage: http://rubygems.org/gems/adjective-rpg-engine
49
57
  licenses:
50
58
  - MIT