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
data/Rakefile
CHANGED
@@ -42,3 +42,289 @@ task :chargen do
|
|
42
42
|
exit 1
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
task :career_stats do
|
47
|
+
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))
|
48
|
+
require 'traveller_rpg/careers'
|
49
|
+
|
50
|
+
by_stat = {
|
51
|
+
strength: {},
|
52
|
+
dexterity: {},
|
53
|
+
endurance: {},
|
54
|
+
intelligence: {},
|
55
|
+
education: {},
|
56
|
+
social_status: {},
|
57
|
+
}
|
58
|
+
|
59
|
+
# for each career, show relevant stats
|
60
|
+
ObjectSpace.each_object(Class) { |klass|
|
61
|
+
next unless klass < TravellerRPG::Career
|
62
|
+
next if klass == TravellerRPG::MilitaryCareer
|
63
|
+
|
64
|
+
qual = klass.const_get('QUALIFICATION')
|
65
|
+
qual = qual[:choose] if qual and qual[:choose]
|
66
|
+
|
67
|
+
if qual
|
68
|
+
qual.each { |stat, check|
|
69
|
+
by_stat[stat][:qualification] ||= {}
|
70
|
+
by_stat[stat][:qualification][name] = check
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "#{name} #{qual}"
|
75
|
+
spec = klass.const_get('SPECIALIST')
|
76
|
+
spec.each { |asg, cfg|
|
77
|
+
puts "\t#{asg} survival: #{cfg[:survival]}"
|
78
|
+
puts "\t#{asg} advancement: #{cfg[:advancement]}"
|
79
|
+
|
80
|
+
cfg[:survival].each { |stat, check|
|
81
|
+
by_stat[stat][:survival] ||= {}
|
82
|
+
by_stat[stat][:survival][name] ||= {}
|
83
|
+
by_stat[stat][:survival][name][asg] = check
|
84
|
+
}
|
85
|
+
cfg[:advancement].each { |stat, check|
|
86
|
+
by_stat[stat][:advancement] ||= {}
|
87
|
+
by_stat[stat][:advancement][name] ||= {}
|
88
|
+
by_stat[stat][:advancement][name][asg] = check
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
scores = {}
|
94
|
+
|
95
|
+
def score(num, check_type)
|
96
|
+
c = case check_type
|
97
|
+
when :qualification then 1
|
98
|
+
when :survival then 0.9
|
99
|
+
when :advancement then 0.8
|
100
|
+
end
|
101
|
+
c * (10 - num)
|
102
|
+
end
|
103
|
+
|
104
|
+
by_stat.each { |stat, hsh|
|
105
|
+
scores[stat] = {}
|
106
|
+
[:qualification, :survival, :advancement].each { |check_type|
|
107
|
+
next unless hsh.key?(check_type)
|
108
|
+
hsh[check_type].each { |kls, cfg|
|
109
|
+
scores[stat][kls] ||= {}
|
110
|
+
scores[stat][kls][:total] ||= 0
|
111
|
+
case check_type
|
112
|
+
when :qualification
|
113
|
+
scores[stat][kls][:total] += score(cfg, check_type)
|
114
|
+
when :advancement, :survival
|
115
|
+
cfg.each { |asg, check|
|
116
|
+
scores[stat][kls][:total] += score(check, check_type)
|
117
|
+
scores[stat][kls][asg] ||= 0
|
118
|
+
scores[stat][kls][asg] += score(check, check_type)
|
119
|
+
}
|
120
|
+
end
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
scores.each { |stat, hsh|
|
126
|
+
puts
|
127
|
+
puts stat
|
128
|
+
puts "==="
|
129
|
+
hsh.sort_by { |kls, score_hsh|
|
130
|
+
score_hsh[:total]
|
131
|
+
}.reverse.each { |kls, score_hsh|
|
132
|
+
puts format("%s %0.1f", kls, score_hsh[:total])
|
133
|
+
score_hsh.sort_by { |k, v| v }.reverse.each { |k, v|
|
134
|
+
next if k == :total
|
135
|
+
puts format("\t%s %0.1f", k, v)
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
task :choose_report do
|
142
|
+
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))
|
143
|
+
require 'traveller_rpg/careers'
|
144
|
+
|
145
|
+
report = {
|
146
|
+
skills: {},
|
147
|
+
stat_checks: {},
|
148
|
+
ranks: {},
|
149
|
+
benefits: {},
|
150
|
+
}
|
151
|
+
|
152
|
+
# for each career, show relevant stats
|
153
|
+
ObjectSpace.each_object(Class) { |klass|
|
154
|
+
next unless klass < TravellerRPG::Career
|
155
|
+
next if klass == TravellerRPG::MilitaryCareer
|
156
|
+
# next if klass == TravellerRPG::Drifter
|
157
|
+
|
158
|
+
name = klass.name.split('::').last
|
159
|
+
qual = klass.const_get('QUALIFICATION')
|
160
|
+
if qual and qual[:choose]
|
161
|
+
report[:stat_checks][name] ||= []
|
162
|
+
report[:stat_checks][name] << :qualification
|
163
|
+
end
|
164
|
+
%w{PERSONAL_SKILLS SERVICE_SKILLS ADVANCED_SKILLS}.each { |sk|
|
165
|
+
skills = klass.const_get(sk) rescue nil
|
166
|
+
next unless skills
|
167
|
+
skills.each { |item|
|
168
|
+
if item.is_a?(Hash) and item[:choose]
|
169
|
+
report[:skills][name] ||= []
|
170
|
+
report[:skills][name] << sk
|
171
|
+
end
|
172
|
+
}
|
173
|
+
}
|
174
|
+
spec = klass.const_get('SPECIALIST')
|
175
|
+
if spec
|
176
|
+
spec.each { |asg, cfg|
|
177
|
+
# ranks, skills, stat_checks
|
178
|
+
ranks = cfg.fetch(:ranks)
|
179
|
+
ranks.each { |rank, hsh|
|
180
|
+
if hsh[:choose] or
|
181
|
+
(hsh[:skill].is_a?(Hash) and hsh[:skill][:choose]) or
|
182
|
+
(hsh[:stat].is_a?(Hash) and hsh[:stat][:choose])
|
183
|
+
report[:ranks][name] ||= []
|
184
|
+
report[:ranks][name] << "[#{asg}]"
|
185
|
+
else
|
186
|
+
# p hsh
|
187
|
+
end
|
188
|
+
}
|
189
|
+
skills = cfg.fetch(:skills)
|
190
|
+
skills.each { |item|
|
191
|
+
if item.is_a?(Hash) and item[:choose]
|
192
|
+
report[:skills][name] ||= []
|
193
|
+
report[:skills][name] << "[#{asg}]"
|
194
|
+
end
|
195
|
+
}
|
196
|
+
[:survival, :advancement].each { |sc|
|
197
|
+
stat_check = cfg.fetch(sc)
|
198
|
+
if stat_check[:choose]
|
199
|
+
report[:stat_checks][name] ||= []
|
200
|
+
report[:stat_checks][name] << sc
|
201
|
+
end
|
202
|
+
}
|
203
|
+
}
|
204
|
+
end
|
205
|
+
|
206
|
+
ben = klass.const_get('BENEFITS')
|
207
|
+
if ben
|
208
|
+
ben.each { |roll, hsh|
|
209
|
+
if hsh.is_a?(Hash) and hsh[:choose]
|
210
|
+
report[:benefits][name] ||= []
|
211
|
+
report[:benefits][name] << roll
|
212
|
+
end
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
if klass < TravellerRPG::MilitaryCareer
|
217
|
+
oranks = klass.const_get('OFFICER_RANKS')
|
218
|
+
oranks.each { |rank, hsh|
|
219
|
+
if hsh[:choose] or
|
220
|
+
(hsh[:skill].is_a?(Hash) and hsh[:skill][:choose]) or
|
221
|
+
(hsh[:stat].is_a?(Hash) and hsh[:stat][:choose])
|
222
|
+
report[:ranks][name] ||= []
|
223
|
+
report[:ranks][name] << 'Officer'
|
224
|
+
else
|
225
|
+
# p hsh
|
226
|
+
end
|
227
|
+
}
|
228
|
+
end
|
229
|
+
}
|
230
|
+
|
231
|
+
report.each { |item, hsh|
|
232
|
+
puts
|
233
|
+
puts item
|
234
|
+
puts "==="
|
235
|
+
|
236
|
+
hsh.each { |name, stuff|
|
237
|
+
puts "#{name}: #{stuff.join(' ')}"
|
238
|
+
}
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
task :military_compare do
|
243
|
+
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))
|
244
|
+
require 'traveller_rpg/careers'
|
245
|
+
require 'traveller_rpg/generator'
|
246
|
+
|
247
|
+
include TravellerRPG
|
248
|
+
|
249
|
+
def const_get(class_name, const_name)
|
250
|
+
TravellerRPG.const_get(class_name).const_get(const_name)
|
251
|
+
end
|
252
|
+
|
253
|
+
char = Generator.character
|
254
|
+
scalars = %w{ADVANCED_EDUCATION AGE_PENALTY}
|
255
|
+
hashes = %w{QUALIFICATION}
|
256
|
+
arrays = %w{PERSONAL_SKILLS SERVICE_SKILLS ADVANCED_SKILLS OFFICER_SKILLS
|
257
|
+
CREDITS}
|
258
|
+
complex = %w{BENEFITS OFFICER_RANKS SPECIALIST}
|
259
|
+
%w{Army Marines Navy}.each { |cname|
|
260
|
+
cnamex = cname + 'x'
|
261
|
+
(scalars + arrays + hashes).each { |const_name|
|
262
|
+
if const_get(cname, const_name) != const_get(cnamex, const_name)
|
263
|
+
warn "#{cname}::#{const_name}"
|
264
|
+
end
|
265
|
+
}
|
266
|
+
complex.each { |const_name|
|
267
|
+
if const_get(cname, const_name) != const_get(cnamex, const_name)
|
268
|
+
warn "#{cname}::#{const_name}"
|
269
|
+
old = const_get(cnamex, const_name)
|
270
|
+
new = const_get(cname, const_name)
|
271
|
+
case old
|
272
|
+
when Hash
|
273
|
+
warn "mismatch: #{old.class} #{new.class}" unless new.is_a?(Hash)
|
274
|
+
if old.size != new.size
|
275
|
+
warn "size mismatch: #{old.size} #{new.size}"
|
276
|
+
end
|
277
|
+
old.each { |k, v|
|
278
|
+
if old[k] != new[k]
|
279
|
+
warn "mismatched values for #{k}"
|
280
|
+
if v.is_a?(Hash)
|
281
|
+
v.each { |kk, vv|
|
282
|
+
if vv != new[k][kk]
|
283
|
+
warn "mismatched values for #{kk}"
|
284
|
+
end
|
285
|
+
if vv.is_a?(Hash)
|
286
|
+
vv.each { |kkk, vvv|
|
287
|
+
if vvv != new[k][kk][kkk]
|
288
|
+
warn "mismatched values for #{kkk}"
|
289
|
+
end
|
290
|
+
}
|
291
|
+
else
|
292
|
+
warn "#{kk}: #{vv.class} #{new[k][kk].class}"
|
293
|
+
end
|
294
|
+
}
|
295
|
+
else
|
296
|
+
warn "#{v.class} #{new[k].class}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
}
|
300
|
+
when Array
|
301
|
+
warn "ARRAY!"
|
302
|
+
else
|
303
|
+
warn old.class.name
|
304
|
+
end
|
305
|
+
end
|
306
|
+
}
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
task :naval_officer do
|
311
|
+
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))
|
312
|
+
require 'traveller_rpg/careers'
|
313
|
+
require 'traveller_rpg/generator'
|
314
|
+
|
315
|
+
include TravellerRPG
|
316
|
+
|
317
|
+
char = Generator.character
|
318
|
+
char.stats.education = 12
|
319
|
+
char.stats.social_status = 12
|
320
|
+
|
321
|
+
career = Navy.new(char)
|
322
|
+
career.activate
|
323
|
+
career.advancement_roll while !career.officer?
|
324
|
+
puts char.report
|
325
|
+
4.times { career.run_term if career.active? }
|
326
|
+
puts char.report
|
327
|
+
career.muster_out
|
328
|
+
puts char.report
|
329
|
+
puts career.report
|
330
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.1
|
1
|
+
0.1.0.1
|
data/bin/chargen
CHANGED
@@ -1,7 +1,21 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'traveller_rpg/generator'
|
3
4
|
require 'traveller_rpg/career_path'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
include TravellerRPG
|
7
|
+
|
8
|
+
careers = %w{Agent Army Citizen Entertainer Marines Merchant
|
9
|
+
Navy Noble Rogue Scholar Scout}
|
10
|
+
begin
|
11
|
+
path = CareerPath.run(careers, character: Generator.character)
|
12
|
+
rescue => e
|
13
|
+
puts [e.class, e.message].join(': ')
|
14
|
+
begin
|
15
|
+
require 'pry'
|
16
|
+
binding.pry
|
17
|
+
rescue LoadError
|
18
|
+
# ok
|
19
|
+
end
|
20
|
+
raise
|
21
|
+
end
|
data/lib/traveller_rpg.rb
CHANGED
@@ -9,20 +9,27 @@ module TravellerRPG
|
|
9
9
|
\z # end str
|
10
10
|
}x
|
11
11
|
|
12
|
-
def self.roll(
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def self.roll(spec = 'd6', label: nil, dm: 0)
|
13
|
+
matches = spec.match(ROLL_RGX) or raise("bad roll spec: #{spec}")
|
14
|
+
roll = self.roll_int(dice: matches[1].empty? ? 1 : matches[1].to_i,
|
15
|
+
faces: matches[2].to_i)
|
16
|
+
puts "#{label} roll (#{spec}): #{roll} (DM #{dm})" if label
|
17
|
+
roll + dm
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
# this is the fastest, but won't print individual die values
|
21
|
+
def self.roll_int(dice: 1, faces: 6)
|
22
|
+
val = 0
|
23
|
+
dice.times { val += rand(faces) + 1 }
|
24
|
+
val
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.roll_ary(dice: 1, faces: 6)
|
28
|
+
Array.new(dice) { rand(faces) + 1 }.sum
|
22
29
|
end
|
23
30
|
|
24
31
|
def self.choose(msg, *args)
|
25
|
-
puts msg + ' (' + args.join('
|
32
|
+
puts msg + ' (' + args.join(' | ') + ')'
|
26
33
|
raise "no choices" if args.empty?
|
27
34
|
return self.player_choose(msg, *args) if PLAYER_CHOOSE
|
28
35
|
choice = args.sample
|
@@ -38,176 +45,31 @@ module TravellerRPG
|
|
38
45
|
chosen = choice
|
39
46
|
elsif args.include?(choice.to_sym)
|
40
47
|
chosen = choice.to_sym
|
41
|
-
|
48
|
+
elsif choice.empty?
|
42
49
|
puts "Try again.\n"
|
50
|
+
else
|
51
|
+
matches = args.grep %r{\A#{choice}}i
|
52
|
+
case matches.size
|
53
|
+
when 0
|
54
|
+
# ok
|
55
|
+
puts "Try again.\n"
|
56
|
+
when 1
|
57
|
+
chosen = matches.first
|
58
|
+
else
|
59
|
+
puts "Ambiguous: #{matches.join(' | ')}"
|
60
|
+
puts "Try again.\n"
|
61
|
+
end
|
43
62
|
end
|
44
63
|
end
|
64
|
+
puts "Accepted: #{chosen}"
|
45
65
|
chosen
|
46
66
|
end
|
47
67
|
|
48
68
|
def self.player_prompt(msg = nil)
|
49
69
|
print msg + ' ' if msg
|
50
70
|
print '> '
|
51
|
-
$stdin.gets
|
71
|
+
$stdin.gets.chomp
|
52
72
|
end
|
53
|
-
|
54
|
-
# per http://www.traveller-srd.com/core-rules/skills/
|
55
|
-
SKILLS = {
|
56
|
-
admin: nil,
|
57
|
-
advocate: nil,
|
58
|
-
|
59
|
-
animals_group: nil,
|
60
|
-
animals_riding: nil,
|
61
|
-
animals_veterinary: nil,
|
62
|
-
animals_training: nil,
|
63
|
-
animals_farming: nil,
|
64
|
-
|
65
|
-
athletics_group: nil,
|
66
|
-
athletics_coordination: nil,
|
67
|
-
athletics_endurance: nil,
|
68
|
-
athletics_strength: nil,
|
69
|
-
athletics_flying: nil,
|
70
|
-
|
71
|
-
art_group: nil,
|
72
|
-
art_acting: nil,
|
73
|
-
art_dance: nil,
|
74
|
-
art_holography: nil,
|
75
|
-
art_instrument: nil,
|
76
|
-
art_sculpting: nil,
|
77
|
-
art_writing: nil,
|
78
|
-
|
79
|
-
astrogation: nil,
|
80
|
-
battle_dress: nil,
|
81
|
-
broker: nil,
|
82
|
-
carouse: nil,
|
83
|
-
comms: nil,
|
84
|
-
computers: nil,
|
85
|
-
deception: nil,
|
86
|
-
diplomat: nil,
|
87
|
-
|
88
|
-
drive_group: nil,
|
89
|
-
drive_hovercraft: nil,
|
90
|
-
drive_mole: nil,
|
91
|
-
drive_tracked: nil,
|
92
|
-
drive_walker: nil,
|
93
|
-
drive_wheeled: nil,
|
94
|
-
|
95
|
-
engineer_group: nil,
|
96
|
-
engineer_manoeuvre: nil,
|
97
|
-
engineer_jump_drive: nil,
|
98
|
-
engineer_electronics: nil,
|
99
|
-
engineer_life_support: nil,
|
100
|
-
engineer_power: nil,
|
101
|
-
|
102
|
-
explosives: nil,
|
103
|
-
|
104
|
-
flyer_group: nil,
|
105
|
-
flyer_airship: nil,
|
106
|
-
flyer_grav: nil,
|
107
|
-
flyer_rotor: nil,
|
108
|
-
flyer_wing: nil,
|
109
|
-
|
110
|
-
gambler: nil,
|
111
|
-
|
112
|
-
gunner_group: nil,
|
113
|
-
gunner_turrets: nil,
|
114
|
-
gunner_ortillery: nil,
|
115
|
-
gunner_screens: nil,
|
116
|
-
gunner_capital_weapons: nil,
|
117
|
-
|
118
|
-
gun_combat_group: nil,
|
119
|
-
gun_combat_slug_rifle: nil,
|
120
|
-
gun_combat_slug_pistol: nil,
|
121
|
-
gun_combat_shotgun: nil,
|
122
|
-
gun_combat_energy_rifle: nil,
|
123
|
-
gun_combat_energy_pistol: nil,
|
124
|
-
|
125
|
-
heavy_weapons_group: nil,
|
126
|
-
heavy_weapons_launchers: nil,
|
127
|
-
heavy_weapons_man_portable_artillery: nil,
|
128
|
-
heavy_weapons_field_artillery: nil,
|
129
|
-
|
130
|
-
investigate: nil,
|
131
|
-
jack_of_all_trades: nil,
|
132
|
-
|
133
|
-
language_group: nil,
|
134
|
-
language_anglic: nil,
|
135
|
-
|
136
|
-
leadership: nil,
|
137
|
-
|
138
|
-
life_sciences_group: nil,
|
139
|
-
life_sciences_biology: nil,
|
140
|
-
life_sciences_cybernetics: nil,
|
141
|
-
life_sciences_genetics: nil,
|
142
|
-
life_sciences_psionicology: nil,
|
143
|
-
|
144
|
-
mechanic: nil,
|
145
|
-
medic: nil,
|
146
|
-
|
147
|
-
melee_group: nil,
|
148
|
-
melee_unarmed_combat: nil,
|
149
|
-
melee_blade: nil,
|
150
|
-
melee_bludgeon: nil,
|
151
|
-
melee_natural_weapons: nil,
|
152
|
-
|
153
|
-
navigation: nil,
|
154
|
-
persuade: nil,
|
155
|
-
|
156
|
-
pilot_group: nil,
|
157
|
-
pilot_small_craft: nil,
|
158
|
-
pilot_spacecraft: nil,
|
159
|
-
pilot_capital_ships: nil,
|
160
|
-
|
161
|
-
physical_sciences_group: nil,
|
162
|
-
physical_sciences_physics: nil,
|
163
|
-
physical_sciences_chemistry: nil,
|
164
|
-
physical_sciences_electronics: nil,
|
165
|
-
|
166
|
-
recon: nil,
|
167
|
-
remote_operations: nil,
|
168
|
-
|
169
|
-
seafarer_group: nil,
|
170
|
-
seafarer_sail: nil,
|
171
|
-
seafarer_submarine: nil,
|
172
|
-
seafarer_ocean_ships: nil,
|
173
|
-
seafarer_motorboats: nil,
|
174
|
-
|
175
|
-
sensors: nil,
|
176
|
-
|
177
|
-
social_sciences_group: nil,
|
178
|
-
social_sciences_archeology: nil,
|
179
|
-
social_sciences_economics: nil,
|
180
|
-
social_sciences_history: nil,
|
181
|
-
social_sciences_linguistics: nil,
|
182
|
-
social_sciences_philosophy: nil,
|
183
|
-
social_sciences_psychology: nil,
|
184
|
-
social_sciences_sophontology: nil,
|
185
|
-
|
186
|
-
space_sciences_group: nil,
|
187
|
-
space_sciences_planetology: nil,
|
188
|
-
space_sciences_robotics: nil,
|
189
|
-
space_sciences_xenology: nil,
|
190
|
-
|
191
|
-
stealth: nil,
|
192
|
-
steward: nil,
|
193
|
-
streetwise: nil,
|
194
|
-
survival: nil,
|
195
|
-
|
196
|
-
tactics_group: nil,
|
197
|
-
tactics_military: nil,
|
198
|
-
tactics_ground: nil,
|
199
|
-
tactics_naval: nil,
|
200
|
-
|
201
|
-
trade_group: nil,
|
202
|
-
trade_biologicals: nil,
|
203
|
-
trade_civil_engineering: nil,
|
204
|
-
trade_space_construction: nil,
|
205
|
-
trade_hydroponics: nil,
|
206
|
-
trade_polymers: nil,
|
207
|
-
|
208
|
-
vacc_suit: nil,
|
209
|
-
zero_g: nil,
|
210
|
-
}
|
211
73
|
end
|
212
74
|
|
213
75
|
# compatibility stuff
|