traveller_rpg 0.0.1.1 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +417 -167
- data/Rakefile +286 -0
- data/VERSION +1 -1
- data/bin/chargen +17 -3
- data/lib/traveller_rpg.rb +31 -169
- data/lib/traveller_rpg/career.rb +242 -201
- data/lib/traveller_rpg/career_path.rb +92 -85
- data/lib/traveller_rpg/careers.rb +256 -544
- data/lib/traveller_rpg/character.rb +114 -123
- data/lib/traveller_rpg/generator.rb +1 -1
- data/lib/traveller_rpg/homeworld.rb +54 -35
- metadata +2 -2
@@ -1,11 +1,21 @@
|
|
1
1
|
require 'traveller_rpg'
|
2
|
+
require 'traveller_rpg/homeworld'
|
3
|
+
require 'traveller_rpg/skill_set'
|
2
4
|
|
3
5
|
module TravellerRPG
|
4
6
|
class Character
|
5
7
|
Stats = Struct.new(:strength, :dexterity, :endurance,
|
6
8
|
:intelligence, :education, :social_status) do
|
7
|
-
def self.
|
8
|
-
|
9
|
+
def self.sym(str)
|
10
|
+
str.is_a?(Symbol) ? str : str.to_s.downcase.gsub(' ', '_').to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.member?(stat)
|
14
|
+
self.members.include? self.sym stat
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.roll
|
18
|
+
self.new(*Array.new(6) { TravellerRPG.roll '2d6' })
|
9
19
|
end
|
10
20
|
|
11
21
|
def self.empty
|
@@ -16,23 +26,44 @@ module TravellerRPG
|
|
16
26
|
hsh.each { |k,v| self[k] += v if self[k] }
|
17
27
|
self
|
18
28
|
end
|
29
|
+
|
30
|
+
def bump(sym, level = nil)
|
31
|
+
if level
|
32
|
+
self[sym] = level if level > self[sym]
|
33
|
+
else
|
34
|
+
self[sym] += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def report
|
39
|
+
rpt = []
|
40
|
+
w = 'Social Status'.size + 2
|
41
|
+
self.members.each { |sym|
|
42
|
+
rpt << format("%s: %s", sym.to_s.capitalize.rjust(w, ' '), self[sym])
|
43
|
+
}
|
44
|
+
rpt.join("\n")
|
45
|
+
end
|
19
46
|
end
|
20
47
|
|
21
48
|
Description = Struct.new(:name, :gender, :age,
|
22
49
|
:appearance, :plot, :temperament) do
|
23
|
-
def
|
24
|
-
self.new(
|
25
|
-
|
50
|
+
def merge(other)
|
51
|
+
self.class.new(other[:name] || self.name,
|
52
|
+
other[:gender] || self.gender,
|
53
|
+
other[:age] || self.age,
|
54
|
+
other[:appearance] || self.appearance,
|
55
|
+
other[:plot] || self.plot,
|
56
|
+
other[:temperament] || self.temperament)
|
26
57
|
end
|
27
58
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
59
|
+
def report(short: false)
|
60
|
+
rpt = []
|
61
|
+
w = (short ? 'Gender' : 'Temperament').size + 2
|
62
|
+
self.members.each { |sym|
|
63
|
+
next if short and [:appearance, :plot, :temperament].include?(sym)
|
64
|
+
rpt << format("%s: %s", sym.to_s.capitalize.rjust(w, ' '), self[sym])
|
65
|
+
}
|
66
|
+
rpt.join("\n")
|
36
67
|
end
|
37
68
|
end
|
38
69
|
|
@@ -50,89 +81,64 @@ module TravellerRPG
|
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
53
|
-
attr_reader :desc, :stats, :
|
54
|
-
:stuff, :credits, :cash_rolls
|
84
|
+
attr_reader :desc, :stats, :skills, :stuff, :credits, :cash_rolls
|
55
85
|
|
56
|
-
def initialize(desc:, stats:,
|
57
|
-
|
86
|
+
def initialize(desc:, stats:, skills: SkillSet.new, stuff: {},
|
87
|
+
log: [], credits: 0, cash_rolls: 0, homeworld: nil)
|
58
88
|
@desc = desc
|
59
89
|
@stats = stats
|
60
|
-
@homeworld = homeworld
|
61
90
|
@skills = skills
|
62
91
|
@stuff = stuff
|
63
92
|
@log = log
|
64
|
-
@cash_rolls = cash_rolls # max 3 lifetime
|
65
93
|
@credits = credits
|
66
|
-
|
94
|
+
@cash_rolls = cash_rolls # max 3 lifetime
|
95
|
+
self.birth(homeworld) if homeworld
|
67
96
|
end
|
68
97
|
|
69
98
|
def stats_dm(stat_sym)
|
70
99
|
self.class.stats_dm(@stats[stat_sym])
|
71
100
|
end
|
72
101
|
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
skill_count = 3 + self.stats_dm(:education)
|
81
|
-
self.log format("Education %i qualifies for %i skills",
|
82
|
-
@stats.education, skill_count)
|
83
|
-
skill_choices = []
|
84
|
-
|
85
|
-
# choose skill_count skills
|
86
|
-
if @homeworld.skills.size <= skill_count
|
87
|
-
self.log format("Homeworld %s only has %i skills available",
|
88
|
-
@homeworld.name, @homeworld.skills.size)
|
89
|
-
skill_choices = @homeworld.skills
|
90
|
-
else
|
91
|
-
skill_count.times { |i|
|
92
|
-
available = @homeworld.skills - skill_choices
|
93
|
-
skill_choices << TravellerRPG.choose("Choose a skill:", *available)
|
94
|
-
}
|
102
|
+
# accepts an array of strings or a single string
|
103
|
+
# choose from the array; provide a single skill
|
104
|
+
# return nil if no skill was trained
|
105
|
+
def basic_training(skill)
|
106
|
+
unless @skills[skill]
|
107
|
+
self.log "Basic Training: #{skill} 0"
|
108
|
+
@skills.provide skill
|
95
109
|
end
|
96
|
-
skill_choices.each { |sym|
|
97
|
-
if TravellerRPG::SKILLS.key?(sym)
|
98
|
-
self.log "Acquired background skill: #{sym} 0"
|
99
|
-
@skills[sym] ||= 0
|
100
|
-
else
|
101
|
-
raise(KeyError, sym)
|
102
|
-
end
|
103
|
-
}
|
104
110
|
self
|
105
111
|
end
|
106
112
|
|
107
|
-
def
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
+
def benefit(item)
|
114
|
+
case item
|
115
|
+
when Integer
|
116
|
+
if item != 0
|
117
|
+
@credits += item
|
118
|
+
self.log "Career benefit: #{item} credits"
|
119
|
+
end
|
120
|
+
when String
|
121
|
+
matches = item.match %r{\A(\d)x (.+)}
|
122
|
+
if matches
|
123
|
+
# e.g. 2x Ship Share
|
124
|
+
count = matches[1].to_i
|
125
|
+
item = matches[2]
|
113
126
|
else
|
114
|
-
|
127
|
+
count = 1
|
115
128
|
end
|
129
|
+
qty = count > 1 ? "(#{count})" : ''
|
130
|
+
@stuff[item] ||= 0
|
131
|
+
@stuff[item] += count
|
132
|
+
self.log "Career benefit: #{item} #{qty}"
|
133
|
+
when Symbol
|
134
|
+
@stats.bump(item)
|
135
|
+
self.log "Career benefit: #{item} bump to #{@stats[item]}"
|
136
|
+
when Array
|
137
|
+
item.each { |i| self.benefit i }
|
116
138
|
else
|
117
|
-
|
118
|
-
level = target[sym]
|
139
|
+
raise "unexpected benefit: #{item.inspect}"
|
119
140
|
end
|
120
|
-
self
|
121
|
-
end
|
122
|
-
|
123
|
-
def add_stuff(benefits)
|
124
|
-
benefits.each { |sym, val|
|
125
|
-
self.log "Collecting #{sym} #{val}"
|
126
|
-
case @stuff[sym]
|
127
|
-
when Numeric, Array
|
128
|
-
self.log "Adding #{sym} #{val} to #{@stuff[sym]}"
|
129
|
-
@stuff[sym] += val
|
130
|
-
when NilClass
|
131
|
-
@stuff[sym] = val
|
132
|
-
else
|
133
|
-
raise("unexpected benefit: #{sym} #{val} (#{val.class})")
|
134
|
-
end
|
135
|
-
}
|
141
|
+
self
|
136
142
|
end
|
137
143
|
|
138
144
|
def log(msg = nil)
|
@@ -142,6 +148,7 @@ module TravellerRPG
|
|
142
148
|
self
|
143
149
|
end
|
144
150
|
|
151
|
+
# convenience
|
145
152
|
def name
|
146
153
|
@desc.name
|
147
154
|
end
|
@@ -150,62 +157,23 @@ module TravellerRPG
|
|
150
157
|
years ? @desc.age += years : @desc.age
|
151
158
|
end
|
152
159
|
|
153
|
-
def skill_check?(skill, val = 0)
|
154
|
-
@skills[skill] and @skills[skill] >= val
|
155
|
-
end
|
156
|
-
|
157
|
-
def skill_level(sym)
|
158
|
-
@skills[sym] and @skills[sym].clamp(0, 4)
|
159
|
-
end
|
160
|
-
|
161
160
|
def cash_roll(amount)
|
162
161
|
@cash_rolls += 1
|
163
162
|
if @cash_rolls <= 3
|
164
163
|
@credits += amount
|
165
|
-
self.log "
|
164
|
+
self.log "Cash roll ##{@cash_rolls}: #{amount} credits"
|
166
165
|
else
|
167
|
-
self.log "
|
166
|
+
self.log "Cash roll ##{@cash_rolls}: Ignored"
|
168
167
|
end
|
169
168
|
self
|
170
169
|
end
|
171
170
|
|
172
|
-
def benefit(item)
|
173
|
-
if item.is_a?(Integer)
|
174
|
-
if item != 0
|
175
|
-
self.log "Acquired #{item} credits as a career benefit"
|
176
|
-
@credits += item
|
177
|
-
end
|
178
|
-
else
|
179
|
-
self.log "Acquired #{item} as a career benefit"
|
180
|
-
@stuff[item] ||= 0
|
181
|
-
@stuff[item] += 1
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
171
|
def report(desc: :short, stats: true, skills: true, stuff: true,
|
186
172
|
log: false, credits: true)
|
187
173
|
hsh = {}
|
188
|
-
if desc
|
189
|
-
|
190
|
-
|
191
|
-
'Age' => @desc.age }
|
192
|
-
if desc == :long
|
193
|
-
hsh['Description'].merge! 'Appearance' => @desc.appearance,
|
194
|
-
'Temperament' => @desc.temperament,
|
195
|
-
'Plot' => @desc.plot
|
196
|
-
end
|
197
|
-
end
|
198
|
-
if stats
|
199
|
-
hsh['Characteristics'] = {
|
200
|
-
'Strength' => @stats.strength,
|
201
|
-
'Dexterity' => @stats.dexterity,
|
202
|
-
'Endurance' => @stats.endurance,
|
203
|
-
'Intelligence' => @stats.intelligence,
|
204
|
-
'Education' => @stats.education,
|
205
|
-
'Social Status' => @stats.social_status,
|
206
|
-
}
|
207
|
-
end
|
208
|
-
hsh['Skills'] = @skills if skills
|
174
|
+
hsh['Description'] = @desc.report(short: desc == :short) if desc
|
175
|
+
hsh['Characteristics'] = @stats.report if stats
|
176
|
+
hsh['Skills'] = @skills.report if skills
|
209
177
|
hsh['Stuff'] = @stuff if stuff
|
210
178
|
hsh['Log'] = @log if log
|
211
179
|
if credits
|
@@ -217,10 +185,12 @@ module TravellerRPG
|
|
217
185
|
report = []
|
218
186
|
hsh.each { |section, tbl|
|
219
187
|
report << "#{section}\n==="
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
188
|
+
if tbl.empty?
|
189
|
+
report << "(none)"
|
190
|
+
elsif tbl.is_a?(Hash)
|
191
|
+
width = tbl.keys.map(&:size).max + 2
|
192
|
+
tbl.each { |k, v|
|
193
|
+
report << format("%s: %s", k.to_s.rjust(width, ' '), v)
|
224
194
|
}
|
225
195
|
elsif tbl.is_a?(Array)
|
226
196
|
report += tbl
|
@@ -231,5 +201,26 @@ module TravellerRPG
|
|
231
201
|
}
|
232
202
|
report.join("\n")
|
233
203
|
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
|
207
|
+
# gain background skills based on homeworld
|
208
|
+
def birth(homeworld)
|
209
|
+
raise "log should be empty" unless @log.empty?
|
210
|
+
skill_count = 3 + self.stats_dm(:education)
|
211
|
+
self.log format("%s was born on %s (%s)",
|
212
|
+
@desc.name,
|
213
|
+
homeworld.name,
|
214
|
+
homeworld.traits.join(' '))
|
215
|
+
puts self.report(desc: :long, skills: false,
|
216
|
+
stuff: false, credits: false)
|
217
|
+
self.log format("Education %i provides up to %i background skills",
|
218
|
+
@stats.education, skill_count)
|
219
|
+
homeworld.choose_skills(skill_count).each { |skill|
|
220
|
+
self.log "Background skill: #{skill} 0"
|
221
|
+
@skills.provide skill
|
222
|
+
}
|
223
|
+
self
|
224
|
+
end
|
234
225
|
end
|
235
226
|
end
|
@@ -3,49 +3,68 @@ require 'traveller_rpg'
|
|
3
3
|
module TravellerRPG
|
4
4
|
class Homeworld
|
5
5
|
TRAITS = {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
6
|
+
economy: {
|
7
|
+
agricultural: ['Animals'],
|
8
|
+
industrial: ['Profession'],
|
9
|
+
high_tech: ['Astrogation', 'Electronics'],
|
10
|
+
},
|
11
|
+
wealth: {
|
12
|
+
poor: ['Medic', 'Melee'],
|
13
|
+
rich: ['Carouse', 'Gambler', 'Profession'],
|
14
|
+
},
|
15
|
+
population: {
|
16
|
+
low_population: ['Jack Of All Trades', 'Survival'],
|
17
|
+
high_population: ['Art', 'Streetwise'],
|
18
|
+
},
|
19
|
+
environment: {
|
20
|
+
desert: ['Flyer', 'Survival'],
|
21
|
+
fluid_oceans: ['Navigation', 'Seafarer'],
|
22
|
+
garden: ['Animals', 'Art'],
|
23
|
+
ice_capped: ['Science', 'Vacc Suit'],
|
24
|
+
minerals: ['Drive', 'Explosives'],
|
25
|
+
water_world: ['Seafarer', 'Tactics'],
|
26
|
+
},
|
24
27
|
}
|
25
28
|
TRAIT_MIN = 3
|
26
|
-
TRAIT_MAX =
|
29
|
+
TRAIT_MAX = TRAITS.keys.size
|
27
30
|
|
28
31
|
attr_reader :name, :traits, :skills
|
29
32
|
|
30
|
-
def initialize(name, traits =
|
33
|
+
def initialize(name, traits = {})
|
31
34
|
@name = name
|
32
|
-
if traits.size > self.class::TRAIT_MAX
|
33
|
-
warn "lots of world traits: #{traits}"
|
34
|
-
elsif traits.empty?
|
35
|
-
sample_num = rand(TRAIT_MAX - TRAIT_MIN + 1) + TRAIT_MIN
|
36
|
-
traits = self.class::TRAITS.keys.sample(sample_num)
|
37
|
-
end
|
38
|
-
@traits = traits
|
39
35
|
@skills = []
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
@skills
|
46
|
-
|
47
|
-
|
36
|
+
|
37
|
+
if traits.empty?
|
38
|
+
trait_count = rand(TRAIT_MAX - TRAIT_MIN + 1) + TRAIT_MIN
|
39
|
+
@traits = self.class::TRAITS.keys.sample(trait_count).map { |t|
|
40
|
+
k = TRAITS[t].keys.sample
|
41
|
+
@skills += TRAITS[t][k]
|
42
|
+
k
|
43
|
+
}
|
44
|
+
else
|
45
|
+
@traits = []
|
46
|
+
traits.each { |type, trait|
|
47
|
+
@skills += TRAITS.fetch(type).fetch(trait)
|
48
|
+
@traits << trait
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
48
52
|
@skills.uniq!
|
53
|
+
def @skills.choose(count)
|
54
|
+
s = self.dup
|
55
|
+
return s if s.size < count
|
56
|
+
Array.new(count) {
|
57
|
+
s.delete TravellerRPG.choose("Choose background skill:", *s)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def choose_skills(count)
|
63
|
+
s = @skills.dup
|
64
|
+
return s if s.size <= count
|
65
|
+
Array.new(count) {
|
66
|
+
s.delete TravellerRPG.choose("Choose background skill:", *s)
|
67
|
+
}
|
49
68
|
end
|
50
69
|
end
|
51
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: traveller_rpg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1
|
4
|
+
version: 0.1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rick Hull
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: buildar
|