pathfinder-dnd-tools 0.1.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/bin/pathfinder +53 -0
- data/lib/pathfinder_dnd.rb +5 -0
- data/lib/pathfinder_dnd/character_sheet.rb +145 -0
- data/lib/pathfinder_dnd/oauth.rb +93 -0
- data/lib/pathfinder_dnd/state_manager.rb +67 -0
- data/lib/pathfinder_dnd/tools.rb +88 -0
- metadata +99 -0
data/bin/pathfinder
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Run skill checks against the stats from your Google Drive character
|
4
|
+
# sheet.
|
5
|
+
|
6
|
+
require "pathfinder_dnd"
|
7
|
+
require "pry" # my fav console evar
|
8
|
+
|
9
|
+
# OAuth flow
|
10
|
+
state = Pathfinder::StateManager.new
|
11
|
+
|
12
|
+
# Set up the env...
|
13
|
+
character = nil
|
14
|
+
while character.nil?
|
15
|
+
begin
|
16
|
+
character = state.get_character_sheet
|
17
|
+
rescue GoogleDrive::AuthenticationError => error
|
18
|
+
puts "There was an error authenticating with Google Drive:\n"
|
19
|
+
puts error.to_s
|
20
|
+
puts "\n\nRe-linking with Google Drive..."
|
21
|
+
state.authorize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Boom, UI.
|
26
|
+
Pry.config.prompt = [ proc { 'd&d>> ' }, proc { ' | ' } ]
|
27
|
+
|
28
|
+
class Fixnum
|
29
|
+
# Calculate the ability modifier from an integer
|
30
|
+
# @example strength.modifier
|
31
|
+
def modifier
|
32
|
+
((self - 10) / 2).to_i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# all skills & most stats included
|
37
|
+
puts '--- Pathfinder Character Tools 2: Revenge of Google Docs ---
|
38
|
+
version 0.2 "Beware of OAuth"
|
39
|
+
|
40
|
+
You are in a fully-interactive ruby console.
|
41
|
+
You can `ls` to see methods.
|
42
|
+
|
43
|
+
Roll basic skill checks and saving throws by typing `check <bonus>`
|
44
|
+
You can roll a 20-sided dice using `check` without a bonus.
|
45
|
+
Roll dice with `roll <count>, <sides>`
|
46
|
+
Tab completion is enabled.
|
47
|
+
|
48
|
+
type `help` for an overview of console usage
|
49
|
+
type `show-doc <method>` to view help for that specific method
|
50
|
+
|
51
|
+
'
|
52
|
+
|
53
|
+
character.pry
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# D&D functions
|
2
|
+
require "pathfinder_dnd/tools"
|
3
|
+
|
4
|
+
module Pathfinder
|
5
|
+
# The whole of the Pathfinder Spreadsheet-tools environment.
|
6
|
+
# The character-sheet provides one-word lookup of almost all essential
|
7
|
+
# character statistics.
|
8
|
+
#
|
9
|
+
# On a technical level, a CharacterSheet wraps access to a
|
10
|
+
# GoogleDrive::Worksheet with friendly helper methods. Skill accessor
|
11
|
+
# methods are dynamically added at object instantiation.
|
12
|
+
#
|
13
|
+
# CharacterSheet is currently the gameplay interface, so it includes
|
14
|
+
# Pathfinder::Tools for easy dice rolling, skill checks, and ninja
|
15
|
+
# business.
|
16
|
+
class CharacterSheet
|
17
|
+
|
18
|
+
# include standard D&D tools
|
19
|
+
include Pathfinder::Tools
|
20
|
+
|
21
|
+
# What sheet title to pull stats from?
|
22
|
+
STATS_SHEET = 'Stats, Skills, Weapons'
|
23
|
+
|
24
|
+
attr_reader :doc, :stats
|
25
|
+
attr_accessor :hp
|
26
|
+
|
27
|
+
# reads a cell directly from the spreadsheet.
|
28
|
+
def self.cell_reader(name, row_or_coord, col = nil, sheet_index = 0)
|
29
|
+
define_method(name) do
|
30
|
+
sheets = instance_variable_get('@sheets')
|
31
|
+
sheet = sheets[sheet_index]
|
32
|
+
|
33
|
+
if row_or_coord.is_a? Numeric
|
34
|
+
return sheet[row_or_coord, col].to_i
|
35
|
+
else
|
36
|
+
return sheet[row_or_coord].to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
###############################
|
42
|
+
# @!group Stats and Abilities
|
43
|
+
# access modifiers like `strength.modifier`
|
44
|
+
|
45
|
+
cell_reader :name, 'M2'
|
46
|
+
cell_reader :level, 'V5'
|
47
|
+
|
48
|
+
# Ability stats
|
49
|
+
cell_reader :strength, 'E14'
|
50
|
+
cell_reader :dexterity, 'E17'
|
51
|
+
cell_reader :constitution, 'E20'
|
52
|
+
cell_reader :intelligence, 'E23'
|
53
|
+
cell_reader :wisdom, 'E26'
|
54
|
+
cell_reader :charisma, 'E29'
|
55
|
+
|
56
|
+
# Defence
|
57
|
+
cell_reader :max_hp, 'U11'
|
58
|
+
cell_reader :ac, 'E33'
|
59
|
+
cell_reader :touch_ac, 'E36'
|
60
|
+
|
61
|
+
# Saving throws
|
62
|
+
cell_reader :fortitude, 'H40'
|
63
|
+
cell_reader :reflex, 'H42'
|
64
|
+
cell_reader :will, 'H44'
|
65
|
+
|
66
|
+
# Combat stuff
|
67
|
+
cell_reader :initiative, 'W30'
|
68
|
+
cell_reader :bab, 'L46'
|
69
|
+
cell_reader :cmb, 'E50'
|
70
|
+
cell_reader :cmd, 'E52'
|
71
|
+
|
72
|
+
# @!endgroup
|
73
|
+
################################
|
74
|
+
|
75
|
+
|
76
|
+
# session: a google_drive session
|
77
|
+
# key: the Drive identifier for the character sheet document
|
78
|
+
# (it's the key= query param when you load the char sheet in the browser)
|
79
|
+
def initialize(session, key)
|
80
|
+
|
81
|
+
# This is where failure will occur if Oauth is fucked
|
82
|
+
@doc = session.spreadsheet_by_key(key)
|
83
|
+
|
84
|
+
|
85
|
+
# all we need for now
|
86
|
+
@stats = @doc.worksheet_by_title(STATS_SHEET)
|
87
|
+
@sheets = [@stats]
|
88
|
+
|
89
|
+
if @stats.nil?
|
90
|
+
raise "Couldn't load the Stats charsheet"
|
91
|
+
end
|
92
|
+
|
93
|
+
# set starting HP
|
94
|
+
@hp = self.max_hp
|
95
|
+
|
96
|
+
# write in skill values
|
97
|
+
inject_instance_properties(get_raw_skills())
|
98
|
+
end
|
99
|
+
|
100
|
+
# Refeshes this instance with new data from the online character sheet,
|
101
|
+
# including updates to skills.
|
102
|
+
def refresh
|
103
|
+
@sheets.each {|s| s.reload()}
|
104
|
+
inject_instance_properties(get_raw_skills())
|
105
|
+
end
|
106
|
+
|
107
|
+
# Builds a <String> => <Integer> hash of skill scores from the character spreadsheet.
|
108
|
+
def get_raw_skills(start_loc = 'AH16', offset = 6)
|
109
|
+
# scrape the spreadsheet skills list
|
110
|
+
skills = {}
|
111
|
+
start, names_col = @stats.cell_name_to_row_col(start_loc)
|
112
|
+
skills_col = names_col + offset
|
113
|
+
(start..start+38).each do |row|
|
114
|
+
# more clear to split this up
|
115
|
+
skill_name = @stats[row, names_col]
|
116
|
+
skill_val = @stats[row, skills_col]
|
117
|
+
skills[skill_name] = skill_val.to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
skills
|
121
|
+
end
|
122
|
+
|
123
|
+
# Injects a <String> => <Any> hash of properties into this instance as attribute accessors.
|
124
|
+
# If the accessor methods already exist, then the local variables they wrap are updated.
|
125
|
+
#
|
126
|
+
# This method is used in conjuction with `get_raw_skill` to populate the intance with skill
|
127
|
+
# fields at runtime.
|
128
|
+
def inject_instance_properties(props)
|
129
|
+
# for adding these properties to only THIS instance
|
130
|
+
metaclass = (class << self; self; end)
|
131
|
+
|
132
|
+
props.each do |name, value|
|
133
|
+
safe_name = name.downcase.gsub(/\s/, '_').gsub(/[^a-zA-Z0-9_]/, '').to_sym
|
134
|
+
|
135
|
+
# define the accessor for this skill if we haven't already
|
136
|
+
if not self.respond_to? safe_name
|
137
|
+
metaclass.class_eval { attr_reader safe_name }
|
138
|
+
end
|
139
|
+
|
140
|
+
# update the skill value
|
141
|
+
instance_variable_set("@#{safe_name}", value)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "oauth2"
|
2
|
+
|
3
|
+
module Pathfinder
|
4
|
+
# Abstracts over the process to OAuth a command-line client with Google.
|
5
|
+
# Saves the user's OAuth refresh token in whatever transactional Pstore-like
|
6
|
+
# bucket you provide.
|
7
|
+
class OAuth
|
8
|
+
# TODO: move into ENV
|
9
|
+
#CLIENT_ID = ENV['CLIENT_ID']
|
10
|
+
#CLIENT_SECRET = ENV['CLIENT_SECRET']
|
11
|
+
|
12
|
+
CLIENT_ID = '539096868219.apps.googleusercontent.com'
|
13
|
+
CLIENT_SECRET = 'HSJKoPRwscFvYvkP-6HhmEaH'
|
14
|
+
|
15
|
+
# this tells Google what permissions we are requesting
|
16
|
+
# I'd prefer to use ReadOnly, but I don't want to rewrite this gem
|
17
|
+
# SCOPE = 'https://www.googleapis.com/auth/drive.readonly'
|
18
|
+
SCOPE = "https://docs.google.com/feeds/ " +
|
19
|
+
"https://docs.googleusercontent.com/ " +
|
20
|
+
"https://spreadsheets.google.com/feeds/"
|
21
|
+
|
22
|
+
# REDIRECT_URI = 'http://localhost' # see the Google API console
|
23
|
+
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
24
|
+
|
25
|
+
attr_reader :access_token
|
26
|
+
|
27
|
+
# Pass in a PSTORE to use for refresh_token storage
|
28
|
+
def initialize(storage)
|
29
|
+
@client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, {
|
30
|
+
:site => 'https://accounts.google.com',
|
31
|
+
:authorize_url => '/o/oauth2/auth',
|
32
|
+
:token_url => '/o/oauth2/token'
|
33
|
+
})
|
34
|
+
|
35
|
+
@storage = storage
|
36
|
+
end
|
37
|
+
|
38
|
+
# Round-trip the user throught the Google OAuth process
|
39
|
+
def authorize()
|
40
|
+
# Step 1
|
41
|
+
puts "\n\nOpen this URL into your browser to connect this app with Google: "
|
42
|
+
puts @client.auth_code.authorize_url(
|
43
|
+
:scope => SCOPE,
|
44
|
+
:access_type => 'offline',
|
45
|
+
:redirect_uri => REDIRECT_URI,
|
46
|
+
:approval_prompt => 'force'
|
47
|
+
)
|
48
|
+
|
49
|
+
# Step 2 is performed in the browser by the user
|
50
|
+
|
51
|
+
# Step 3
|
52
|
+
puts "\n\nPaste the `code` parameter from the redirect URL here to finish authorization: "
|
53
|
+
code = gets.chomp
|
54
|
+
|
55
|
+
@access_token = @client.auth_code.get_token(code, {
|
56
|
+
:redirect_uri => REDIRECT_URI,
|
57
|
+
:token_method => :post
|
58
|
+
})
|
59
|
+
end
|
60
|
+
|
61
|
+
# write the refresh token to storage for future use
|
62
|
+
def save_token()
|
63
|
+
if @access_token.nil?
|
64
|
+
raise 'No access token to store'
|
65
|
+
end
|
66
|
+
|
67
|
+
# wow look at the saftey!
|
68
|
+
@storage.transaction do
|
69
|
+
@storage[:refesh_token] = @access_token.refresh_token
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# create a new session token from whatever refresh token is in the storage.
|
75
|
+
def load_token()
|
76
|
+
token = nil
|
77
|
+
refresh = nil
|
78
|
+
|
79
|
+
@storage.transaction do
|
80
|
+
refresh = @storage[:refesh_token]
|
81
|
+
end
|
82
|
+
|
83
|
+
if refresh.nil?
|
84
|
+
puts 'Could not load OAuth token from storage'
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# fuck this
|
89
|
+
@access_token = OAuth2::AccessToken.from_hash(@client, :refresh_token => refresh).refresh!
|
90
|
+
@access_token
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pstore"
|
4
|
+
|
5
|
+
require "google_drive"
|
6
|
+
|
7
|
+
require "pathfinder_dnd/oauth"
|
8
|
+
require "pathfinder_dnd/character_sheet"
|
9
|
+
|
10
|
+
module Pathfinder
|
11
|
+
|
12
|
+
# Default storage place
|
13
|
+
STORAGE = PStore.new(File.expand_path('~/.config/pathfinder.pstore'))
|
14
|
+
|
15
|
+
# Sets up the OAuth connection to Google Drive and manages user settings
|
16
|
+
# such as the Google Drive key/id of the character sheet document.
|
17
|
+
class StateManager
|
18
|
+
|
19
|
+
# for ease of use
|
20
|
+
attr_accessor :token, :session, :auth
|
21
|
+
attr_reader :doc_id
|
22
|
+
|
23
|
+
# create a StateManager with a optional storage backend.
|
24
|
+
# Loads data including OAuth tokens and the document identifier from the PStore
|
25
|
+
# By default all StateMangers read/write to the Pathfinder settings
|
26
|
+
# PStore in ~/.config/pathfinder.pstore
|
27
|
+
def initialize(storage = Pathfinder::STORAGE)
|
28
|
+
@storage = storage
|
29
|
+
@auth = Pathfinder::OAuth.new(@storage)
|
30
|
+
@token = @auth.load_token
|
31
|
+
|
32
|
+
@storage.transaction do
|
33
|
+
@doc_id = @storage[:doc]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Round-trip the user through Google's OAuth process
|
38
|
+
def authorize
|
39
|
+
@auth.authorize
|
40
|
+
@auth.save_token
|
41
|
+
@token = @auth.access_token
|
42
|
+
end
|
43
|
+
|
44
|
+
def doc_id=(k)
|
45
|
+
@doc_id = k
|
46
|
+
@storage.transaction do
|
47
|
+
@storage[:doc] = @doc_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Perform all necessary input to get a character sheet from a user's Google Drive.
|
52
|
+
def get_character_sheet
|
53
|
+
if @token.nil?
|
54
|
+
self.authorize()
|
55
|
+
end
|
56
|
+
|
57
|
+
if @doc_id.nil?
|
58
|
+
puts "\n\nPaste the `key=` parameter from your character's Google Drive URL here:"
|
59
|
+
self.doc_id = gets.chomp
|
60
|
+
end
|
61
|
+
|
62
|
+
@session = GoogleDrive.login_with_oauth(@token)
|
63
|
+
|
64
|
+
return Pathfinder::CharacterSheet.new(@session, @doc_id)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Pathfinder
|
2
|
+
|
3
|
+
# This is where all the in-game functions are defined, like rolling dice.
|
4
|
+
module Tools
|
5
|
+
|
6
|
+
# Deep sum arrays of integers and arrays.
|
7
|
+
# @param array [Array] the list to sum up.
|
8
|
+
# @return [Integer] the total
|
9
|
+
def sum(array)
|
10
|
+
res = 0
|
11
|
+
array.each do |i|
|
12
|
+
if i.respond_to? :each
|
13
|
+
res += sum(i)
|
14
|
+
else
|
15
|
+
res += i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
res
|
19
|
+
end
|
20
|
+
|
21
|
+
# Roll one dice with N sides.
|
22
|
+
# When rolling 20-sided dice, alerts on very high or low rolls.
|
23
|
+
#
|
24
|
+
# @param sides [Integer] number of sides on the dice.
|
25
|
+
# @param crit_level [Integer] alert the user to dice
|
26
|
+
# rolls at or above this level when rolling 20-sided dice. Default 19.
|
27
|
+
# @param failure_level [integer] alert the user to dice rolls
|
28
|
+
# at or below this level when rolling 20-sided dice. Default 1.
|
29
|
+
# @return [Integer] result of the dice roll
|
30
|
+
def single_roll(sides, crit_level = 19, failure_level = 1)
|
31
|
+
res = 1 + rand(sides)
|
32
|
+
if sides == 20 and res >= crit_level
|
33
|
+
puts "Crit: rolled #{res}"
|
34
|
+
end
|
35
|
+
|
36
|
+
if sides == 20 and res <= failure_level
|
37
|
+
puts "Low roll: rolled #{res}"
|
38
|
+
end
|
39
|
+
|
40
|
+
res
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Roll a number of dice
|
45
|
+
# @param dice [Integer] number of dice to roll. Default 1.
|
46
|
+
# @param sides [Integer] number of sides on each die. Default 6.
|
47
|
+
# @see #single_roll
|
48
|
+
# @return [Array<Integer>] list of dice roll results
|
49
|
+
def roll(dice = 1, sides = 6, crit_level = 19, failure_level = 1)
|
50
|
+
(1..dice).to_a.map{ |_| single_roll(sides, crit_level, failure_level) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Roll a 20-sided dice and add an optional skill bonus
|
54
|
+
# @param skill [Integer] your skill-check or saving-throw bonus. Default 0.
|
55
|
+
# @return [Integer]
|
56
|
+
def check(skill = 0)
|
57
|
+
sum(roll(1, 20)) + skill
|
58
|
+
end
|
59
|
+
|
60
|
+
# roll to hit
|
61
|
+
# Rolls a basic check with an additional bonus
|
62
|
+
# @param bonus [Integer] added bonus, usually from Anne's blung-ing or haste
|
63
|
+
# @param base [Integer] your usual attack bonus. Character-specific default 14.
|
64
|
+
# @see #check
|
65
|
+
def atk_roll(bonus = 0, base = 14)
|
66
|
+
check(base) + bonus
|
67
|
+
end
|
68
|
+
|
69
|
+
# roll for damage
|
70
|
+
# Character-specific to Shalizara
|
71
|
+
# @return [Array<Integer>] magic and normal components of the attack
|
72
|
+
def normal_damage(magic_damage_dice = 2)
|
73
|
+
magic = sum(roll(magic_damage_dice, 6)) + 2
|
74
|
+
dagger = sum(roll(1, 4)) + 2
|
75
|
+
[magic, dagger]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Roll for sneak-attack damage
|
79
|
+
# Character-specific to Shalizara
|
80
|
+
# @see #normal_damage
|
81
|
+
def sneak_damage(magic_damage_dice = 2)
|
82
|
+
sneak = sum(roll(5, 6))
|
83
|
+
reg = normal_damage(magic_damage_dice)
|
84
|
+
reg << sneak
|
85
|
+
reg
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pathfinder-dnd-tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jake Teton-Landis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: pry
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: google_drive
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: oauth2
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Companion tools for the Pathfinder Google Drive character sheet template
|
63
|
+
email: just.1.jake@gmail.com
|
64
|
+
executables:
|
65
|
+
- pathfinder
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- lib/pathfinder_dnd/character_sheet.rb
|
70
|
+
- lib/pathfinder_dnd/tools.rb
|
71
|
+
- lib/pathfinder_dnd/state_manager.rb
|
72
|
+
- lib/pathfinder_dnd/oauth.rb
|
73
|
+
- lib/pathfinder_dnd.rb
|
74
|
+
- bin/pathfinder
|
75
|
+
homepage: http://jake.teton-landis.org/projects/pathfinder-dnd
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.8.24
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: D&D Tools
|
99
|
+
test_files: []
|