adjective-rpg-engine 0.0.2 → 0.0.3

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.
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