traveller_rpg 0.0.1.1 → 0.1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|