traveller_rpg 0.0.0.4 → 0.0.1.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 +236 -0
- data/Rakefile +26 -0
- data/VERSION +1 -1
- data/bin/chargen +2 -42
- data/lib/traveller_rpg/career.rb +254 -129
- data/lib/traveller_rpg/career_path.rb +102 -54
- data/lib/traveller_rpg/careers.rb +499 -91
- data/lib/traveller_rpg/character.rb +115 -8
- data/lib/traveller_rpg/homeworld.rb +10 -10
- data/lib/traveller_rpg.rb +8 -9
- metadata +2 -2
data/lib/traveller_rpg/career.rb
CHANGED
@@ -4,23 +4,31 @@ module TravellerRPG
|
|
4
4
|
class Career
|
5
5
|
class Error < RuntimeError; end
|
6
6
|
class UnknownAssignment < Error; end
|
7
|
-
class MusterError < Error; end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
8
|
+
#
|
9
|
+
# Actually useful defaults
|
12
10
|
|
13
11
|
TERM_YEARS = 4
|
12
|
+
ADVANCED_EDUCATION = 8
|
13
|
+
|
14
|
+
#
|
15
|
+
# Examples -- but should not be used as actual defaults
|
14
16
|
|
15
|
-
|
16
|
-
SURVIVAL_CHECK = 6
|
17
|
-
ADVANCEMENT_CHECK = 9
|
17
|
+
QUALIFICATION = [:default, 5]
|
18
18
|
|
19
|
-
|
19
|
+
PERSONAL_SKILLS = Array.new(6) { :default }
|
20
20
|
SERVICE_SKILLS = Array.new(6) { :default }
|
21
21
|
ADVANCED_SKILLS = Array.new(6) { :default }
|
22
|
-
|
23
|
-
RANKS = {}
|
22
|
+
|
23
|
+
RANKS = { 0 => ['Rookie', :default, 0] }
|
24
|
+
SPECIALIST = {
|
25
|
+
default: {
|
26
|
+
skills: Array.new(6) { :default },
|
27
|
+
survival: [:default, 5],
|
28
|
+
advancement: [:default, 5],
|
29
|
+
ranks: RANKS,
|
30
|
+
}
|
31
|
+
}
|
24
32
|
|
25
33
|
EVENTS = {
|
26
34
|
2 => nil,
|
@@ -35,7 +43,6 @@ module TravellerRPG
|
|
35
43
|
11 => nil,
|
36
44
|
12 => nil,
|
37
45
|
}
|
38
|
-
|
39
46
|
MISHAPS = {
|
40
47
|
1 => nil,
|
41
48
|
2 => nil,
|
@@ -45,149 +52,146 @@ module TravellerRPG
|
|
45
52
|
6 => nil,
|
46
53
|
}
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
9 => 2000,
|
57
|
-
10 => 4000,
|
58
|
-
11 => 8000,
|
59
|
-
12 => 16000,
|
55
|
+
MUSTER_OUT = {
|
56
|
+
1 => [0, 'Default'],
|
57
|
+
2 => [0, 'Default'],
|
58
|
+
3 => [5, 'Default'],
|
59
|
+
4 => [5, 'Default'],
|
60
|
+
5 => [10, 'Default'],
|
61
|
+
6 => [10, 'Default'],
|
62
|
+
7 => [5000, 'Default'], # Gambler DM +1
|
60
63
|
}
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
def self.roll_check?(label, dm:, check:, roll: nil)
|
66
|
+
roll ||= TravellerRPG.roll('2d6')
|
67
|
+
puts format("%s check: rolled %i (DM %i) against %i",
|
68
|
+
label, roll, dm, check)
|
69
|
+
(roll + dm) >= check
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.muster_roll(label, dm: 0)
|
73
|
+
roll = TravellerRPG.roll('d6')
|
74
|
+
clamped = (roll + dm).clamp(1, 7)
|
75
|
+
puts "#{label} roll: #{roll} (DM #{dm}) = #{clamped}"
|
76
|
+
clamped
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :term, :active, :rank, :assignment
|
64
80
|
|
65
|
-
def initialize(char,
|
66
|
-
benefits: {})
|
81
|
+
def initialize(char, term: 0, active: false, rank: 0)
|
67
82
|
@char = char
|
68
|
-
@assignment = assignment
|
69
|
-
if @assignment and !self.class::SPECIALIST_SKILLS.key?(@assignment)
|
70
|
-
raise(UnknownAssignment, assignment.inspect)
|
71
|
-
end
|
72
83
|
|
73
84
|
# career tracking
|
74
85
|
@term = term
|
75
86
|
@active = active
|
76
87
|
@rank = rank
|
77
|
-
@benefits = benefits # acquired equipment, ships / shares
|
78
88
|
@term_mandate = nil
|
89
|
+
@title = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def officer?
|
93
|
+
false
|
79
94
|
end
|
80
95
|
|
81
|
-
def assignment
|
82
|
-
@
|
83
|
-
|
96
|
+
def activate(assignment = nil)
|
97
|
+
@active = true
|
98
|
+
s = self.class::SPECIALIST
|
99
|
+
if assignment
|
100
|
+
raise(UnknownAssignment, assignment.inspect) unless s.key?(assignment)
|
101
|
+
@assignment = assignment
|
102
|
+
else
|
103
|
+
@assignment = TravellerRPG.choose("Choose a specialty:", *s.keys)
|
104
|
+
end
|
105
|
+
@title, skill, level = self.rank_benefit
|
106
|
+
@char.train(skill, level) if skill
|
107
|
+
self
|
84
108
|
end
|
85
109
|
|
86
110
|
def active?
|
87
111
|
!!@active
|
88
112
|
end
|
89
113
|
|
90
|
-
def qualify_check?(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
(roll + dm) >= self.class::QUALIFY_CHECK
|
114
|
+
def qualify_check?(dm: 0)
|
115
|
+
stat, check = self.class::QUALIFICATION
|
116
|
+
@char.log "#{self.name} qualification: #{stat} #{check}+"
|
117
|
+
dm += @char.stats_dm(stat)
|
118
|
+
self.class.roll_check?('Qualify', dm: dm, check: check)
|
96
119
|
end
|
97
120
|
|
98
121
|
def survival_check?(dm: 0)
|
122
|
+
stat, check = self.specialty.fetch(:survival)
|
123
|
+
@char.log "#{self.name} #{@assignment} survival: #{stat} #{check}+"
|
124
|
+
dm += @char.stats_dm(stat)
|
125
|
+
self.class.roll_check?('Survival', dm: dm, check: check)
|
126
|
+
end
|
127
|
+
|
128
|
+
def advancement_check?(dm: 0)
|
129
|
+
stat, check = self.specialty.fetch(:advancement)
|
130
|
+
@char.log "#{self.name} #{@assignment} advancement: #{stat} #{check}+"
|
131
|
+
dm += @char.stats_dm(stat)
|
99
132
|
roll = TravellerRPG.roll('2d6')
|
100
|
-
|
101
|
-
|
102
|
-
|
133
|
+
if roll <= @term
|
134
|
+
@term_mandate = :must_exit
|
135
|
+
elsif roll == 12
|
136
|
+
@term_mandate = :must_remain
|
137
|
+
else
|
138
|
+
@term_mandate = nil
|
139
|
+
end
|
140
|
+
self.class.roll_check?('Advancement', dm: dm, check: check, roll: roll)
|
103
141
|
end
|
104
142
|
|
105
|
-
def
|
106
|
-
|
107
|
-
puts format("Advancement check: rolled %i (DM %i) against %i",
|
108
|
-
roll, dm, self.class::ADVANCEMENT_CHECK)
|
109
|
-
(roll + dm) >= self.class::ADVANCEMENT_CHECK
|
143
|
+
def advanced_education?
|
144
|
+
@char.stats[:education] >= self.class::ADVANCED_EDUCATION
|
110
145
|
end
|
111
146
|
|
112
147
|
# any skills obtained start at level 1
|
113
148
|
def training_roll
|
149
|
+
choices = [:personal, :service, :specialist]
|
150
|
+
choices << :advanced if self.advanced_education?
|
151
|
+
choices << :officer if self.officer?
|
152
|
+
choice = TravellerRPG.choose("Choose skills regimen:", *choices)
|
114
153
|
roll = TravellerRPG.roll('d6')
|
115
|
-
@char.log "Training roll
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
else
|
127
|
-
raise "bad stat: #{stat}" unless TravellerRPG::SKILLS.key?(stat)
|
128
|
-
# stat is likely :jack_of_all_trades skill
|
129
|
-
@char.skills[stat] ||= 0
|
130
|
-
@char.skills[stat] += 1
|
131
|
-
@char.log "Trained (stats) skill #{stat} to #{@char.skills[stat]}"
|
132
|
-
end
|
133
|
-
when :service
|
134
|
-
svc = self.class::SERVICE_SKILLS.fetch(roll - 1)
|
135
|
-
@char.skills[svc] ||= 0
|
136
|
-
@char.skills[svc] += 1
|
137
|
-
@char.log "Trained service skill #{svc} to #{@char.skills[svc]}"
|
138
|
-
when :specialist
|
139
|
-
spec =
|
140
|
-
self.class::SPECIALIST_SKILLS.fetch(self.assignment).fetch(roll - 1)
|
141
|
-
@char.skills[spec] ||= 0
|
142
|
-
@char.skills[spec] += 1
|
143
|
-
@char.log "Trained #{@assignment} specialist skill #{spec} " +
|
144
|
-
"to #{@char.skills[spec]}"
|
145
|
-
when :advanced
|
146
|
-
adv = self.class::ADVANCED_SKILLS.fetch(roll - 1)
|
147
|
-
@char.skills[adv] ||= 0
|
148
|
-
@char.skills[adv] += 1
|
149
|
-
@char.log "Trained advanced skill #{adv} to #{@char.skills[adv]}"
|
150
|
-
when :officer
|
151
|
-
off = self.class::OFFICER_SKILLS.fetch(roll - 1)
|
152
|
-
@char.skills[off] ||= 0
|
153
|
-
@char.skills[off] += 1
|
154
|
-
@char.log "Trained officer skill #{off} to #{@char.skills[off]}"
|
155
|
-
end
|
154
|
+
@char.log "Training roll: #{roll}"
|
155
|
+
@char.train \
|
156
|
+
case choice
|
157
|
+
when :personal then self.class::PERSONAL_SKILLS.fetch(roll - 1)
|
158
|
+
when :service then self.class::SERVICE_SKILLS.fetch(roll - 1)
|
159
|
+
when :specialist
|
160
|
+
self.class::SPECIALIST.dig(@assignment, :skills, roll - 1)
|
161
|
+
when :advanced then self.class::ADVANCED_SKILLS.fetch(roll - 1)
|
162
|
+
when :officer then self.class::OFFICER_SKILLS.fetch(roll - 1)
|
163
|
+
end
|
164
|
+
self
|
156
165
|
end
|
157
166
|
|
158
167
|
def event_roll(dm: 0)
|
159
168
|
roll = TravellerRPG.roll('2d6')
|
160
169
|
clamped = (roll + dm).clamp(2, 12)
|
161
|
-
|
162
|
-
@char.log self.class::EVENTS.fetch(clamped)
|
170
|
+
puts "Event roll: #{roll} (DM #{dm}) = #{clamped}"
|
171
|
+
@char.log "Event: #{self.class::EVENTS.fetch(clamped) || roll}"
|
163
172
|
# TODO: actually perform the event stuff
|
164
173
|
end
|
165
174
|
|
166
175
|
def mishap_roll
|
167
176
|
roll = TravellerRPG.roll('d6')
|
168
|
-
|
169
|
-
@char.log self.class::MISHAPS.fetch(roll)
|
177
|
+
puts "Mishap roll: #{roll}"
|
178
|
+
@char.log "Mishap: #{self.class::MISHAPS.fetch(roll) || roll}"
|
170
179
|
# TODO: actually perform the mishap stuff
|
171
180
|
end
|
172
181
|
|
173
|
-
def cash_roll(dm: 0)
|
174
|
-
roll = TravellerRPG.roll('2d6')
|
175
|
-
clamped = (roll + dm).clamp(2, 12)
|
176
|
-
amount = self.class::CASH.fetch(clamped)
|
177
|
-
puts "Cash roll: #{roll} (DM #{dm}) = #{clamped} for #{amount}"
|
178
|
-
amount
|
179
|
-
end
|
180
|
-
|
181
182
|
def advance_rank
|
182
183
|
@rank += 1
|
183
184
|
@char.log "Advanced career to rank #{@rank}"
|
184
185
|
title, skill, level = self.rank_benefit
|
185
186
|
if title
|
186
187
|
@char.log "Awarded rank title: #{title}"
|
188
|
+
@title = title
|
189
|
+
end
|
190
|
+
if skill
|
187
191
|
@char.log "Achieved rank skill: #{skill} #{level}"
|
188
|
-
@char.
|
189
|
-
@char.skills[skill] = level if level > @char.skills[skill]
|
192
|
+
@char.train(skill, level)
|
190
193
|
end
|
194
|
+
self
|
191
195
|
end
|
192
196
|
|
193
197
|
def must_remain?
|
@@ -206,30 +210,28 @@ module TravellerRPG
|
|
206
210
|
self.name, @term, @char.age)
|
207
211
|
self.training_roll
|
208
212
|
|
213
|
+
# TODO: DM?
|
209
214
|
if self.survival_check?
|
210
|
-
@char.log format("%s term %i
|
215
|
+
@char.log format("%s term %i completed successfully.",
|
216
|
+
self.name, @term)
|
211
217
|
@char.age TERM_YEARS
|
212
218
|
|
219
|
+
# TODO: DM?
|
213
220
|
self.commission_roll if self.respond_to?(:commission_roll)
|
214
221
|
|
215
|
-
adv_roll = TravellerRPG.roll('2d6')
|
216
222
|
# TODO: DM?
|
217
|
-
if self.advancement_check?
|
223
|
+
if self.advancement_check?
|
218
224
|
self.advance_rank
|
219
225
|
self.training_roll
|
220
226
|
end
|
221
|
-
if adv_roll <= @term
|
222
|
-
@term_mandate = :must_exit
|
223
|
-
elsif adv_roll == 12
|
224
|
-
@term_mandate = :must_remain
|
225
|
-
else
|
226
|
-
@term_mandate = nil
|
227
|
-
end
|
228
227
|
|
228
|
+
# TODO: DM?
|
229
229
|
self.event_roll
|
230
230
|
else
|
231
|
-
|
232
|
-
@char.
|
231
|
+
years = rand(TERM_YEARS) + 1
|
232
|
+
@char.log format("%s career ended with a mishap after %i years.",
|
233
|
+
self.name, years)
|
234
|
+
@char.age years
|
233
235
|
self.mishap_roll
|
234
236
|
@active = false
|
235
237
|
end
|
@@ -240,30 +242,153 @@ module TravellerRPG
|
|
240
242
|
end
|
241
243
|
|
242
244
|
def muster_out(dm: 0)
|
245
|
+
@char.log "Muster out: #{self.name}"
|
246
|
+
raise(Error, "career has not started") unless @term > 0
|
247
|
+
dm += @char.skill_check?(:gambler, 1) ? 1 : 0
|
248
|
+
|
249
|
+
# one cash and one benefit per term
|
250
|
+
# except if last term suffered mishap, no benefit for that term
|
251
|
+
|
252
|
+
cash_rolls = @term.clamp(0, 3 - @char.cash_rolls)
|
253
|
+
benefit_rolls = @term
|
254
|
+
|
243
255
|
if @active
|
244
|
-
|
256
|
+
@char.log "Career is in good standing -- collect all benefits"
|
245
257
|
@active = false
|
246
|
-
|
247
|
-
@char.log "
|
248
|
-
|
249
|
-
@term.clamp(0, 3).times {
|
250
|
-
cash_benefit += self.cash_roll(dm: dm)
|
251
|
-
}
|
252
|
-
@char.log "Cash benefit: #{cash_benefit}"
|
253
|
-
@char.log "Retirement bonus: #{self.retirement_bonus}"
|
254
|
-
@benefits[:cash] ||= 0
|
255
|
-
@benefits[:cash] += cash_benefit + self.retirement_bonus
|
256
|
-
@benefits
|
258
|
+
else
|
259
|
+
@char.log "Left career early -- lose benefit for last term"
|
260
|
+
benefit_rolls -= 1
|
257
261
|
end
|
262
|
+
|
263
|
+
cash_rolls.times {
|
264
|
+
clamped = self.class.muster_roll('Cash', dm: dm)
|
265
|
+
@char.cash_roll self.class::MUSTER_OUT.fetch(clamped).first
|
266
|
+
}
|
267
|
+
benefit_rolls.times {
|
268
|
+
clamped = self.class.muster_roll('Benefits', dm: dm)
|
269
|
+
@char.benefit self.class::MUSTER_OUT.fetch(clamped).last
|
270
|
+
}
|
271
|
+
@char.log "Retirement bonus: #{self.retirement_bonus}"
|
272
|
+
@char.benefit self.retirement_bonus
|
273
|
+
self
|
258
274
|
end
|
259
275
|
|
260
276
|
def name
|
261
277
|
self.class.name.split('::').last
|
262
278
|
end
|
263
279
|
|
280
|
+
def specialty
|
281
|
+
self.class::SPECIALIST.fetch(@assignment)
|
282
|
+
end
|
283
|
+
|
264
284
|
# possibly nil
|
265
285
|
def rank_benefit
|
266
|
-
self.
|
286
|
+
self.specialty.fetch(:ranks)[@rank]
|
287
|
+
end
|
288
|
+
|
289
|
+
def report(term: true, active: true, rank: true, spec: true)
|
290
|
+
hsh = {}
|
291
|
+
hsh['Term'] = @term if term
|
292
|
+
hsh['Active'] = @active if active
|
293
|
+
hsh['Specialty'] = @assignment if spec
|
294
|
+
if rank
|
295
|
+
hsh['Officer Rank'] = self.rank if self.officer?
|
296
|
+
hsh['Rank'] = @rank
|
297
|
+
hsh['Title'] = @title if @title
|
298
|
+
end
|
299
|
+
report = ["Career: #{self.name}", "==="]
|
300
|
+
hsh.each { |label, val|
|
301
|
+
val = val.to_s.capitalize if val.is_a? Symbol
|
302
|
+
report << format("%s: %s", label.to_s.rjust(10, ' '), val.to_s)
|
303
|
+
}
|
304
|
+
report.join("\n")
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
|
309
|
+
#
|
310
|
+
# MilitaryCareer adds Officer commission and parallel Officer ranks
|
311
|
+
|
312
|
+
class MilitaryCareer < Career
|
313
|
+
|
314
|
+
#
|
315
|
+
# Actually useful defaults
|
316
|
+
|
317
|
+
COMMISSION = [:social_status, 8]
|
318
|
+
|
319
|
+
#
|
320
|
+
# Examples -- but should not be used as actual defaults
|
321
|
+
|
322
|
+
AGE_PENALTY = 40
|
323
|
+
OFFICER_SKILLS = Array.new(6) { :default }
|
324
|
+
OFFICER_RANKS = {}
|
325
|
+
|
326
|
+
def initialize(char, **kwargs)
|
327
|
+
super(char, **kwargs)
|
328
|
+
@officer = false
|
329
|
+
end
|
330
|
+
|
331
|
+
# Implement age penalty
|
332
|
+
def qualify_check?(dm: 0)
|
333
|
+
dm -= 2 if @char.age >= self.class::AGE_PENALTY
|
334
|
+
super(dm: dm)
|
335
|
+
end
|
336
|
+
|
337
|
+
def commission_check?(dm: 0)
|
338
|
+
stat, check = self.class::COMMISSION
|
339
|
+
@char.log "#{self.name} commission: #{stat} #{check}"
|
340
|
+
dm += @char.stats_dm(stat)
|
341
|
+
self.class.roll_check?('Commission', dm: dm, check: check)
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
# Achieve an officer commission
|
346
|
+
|
347
|
+
def commission_roll(dm: 0)
|
348
|
+
return if @officer
|
349
|
+
if TravellerRPG.choose("Apply for commission?", :yes, :no) == :yes
|
350
|
+
if self.commission_check?
|
351
|
+
@char.log "Became an officer!"
|
352
|
+
@officer = 0 # officer rank
|
353
|
+
self.advance_rank # officers start at rank 1
|
354
|
+
else
|
355
|
+
@char.log "Commission was rejected"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Handle parallel officer track, conditional on officer commission
|
362
|
+
|
363
|
+
def officer?
|
364
|
+
!!@officer
|
365
|
+
end
|
366
|
+
|
367
|
+
def enlisted_rank
|
368
|
+
@rank
|
369
|
+
end
|
370
|
+
|
371
|
+
def rank
|
372
|
+
@officer ? @officer : @rank
|
373
|
+
end
|
374
|
+
|
375
|
+
def rank_benefit
|
376
|
+
@officer ? self.class::OFFICER_RANKS[@officer] : super
|
377
|
+
end
|
378
|
+
|
379
|
+
def advance_rank
|
380
|
+
return super unless @officer
|
381
|
+
@officer += 1
|
382
|
+
@char.log "Advanced career to officer rank #{@officer}"
|
383
|
+
title, skill, level = self.rank_benefit
|
384
|
+
if title
|
385
|
+
@char.log "Awarded officer rank title: #{title}"
|
386
|
+
@title = title
|
387
|
+
end
|
388
|
+
if skill
|
389
|
+
@char.log "Achieved officer rank skill: #{skill} #{level}"
|
390
|
+
@char.train(skill, level)
|
391
|
+
end
|
267
392
|
end
|
268
393
|
end
|
269
394
|
end
|
@@ -1,33 +1,67 @@
|
|
1
|
-
require 'traveller_rpg
|
1
|
+
require 'traveller_rpg'
|
2
|
+
require 'traveller_rpg/careers'
|
2
3
|
|
3
4
|
module TravellerRPG
|
5
|
+
autoload :Generator, 'traveller_rpg/generator'
|
6
|
+
|
4
7
|
class CareerPath
|
5
8
|
class Error < RuntimeError; end
|
6
9
|
class Ineligible < Error; end
|
7
10
|
|
8
|
-
|
11
|
+
DRAFT_CAREERS = {
|
12
|
+
1 => ['Navy', nil],
|
13
|
+
2 => ['Army', nil],
|
14
|
+
3 => ['Marines', nil],
|
15
|
+
4 => ['Merchant', :merchant_marine],
|
16
|
+
5 => ['Scout', nil],
|
17
|
+
6 => ['Agent', :law_enforcement],
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.career_class(str)
|
21
|
+
TravellerRPG.const_get(str.split('::').last)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.run(careers, character: nil)
|
25
|
+
character = Generator.character unless character
|
26
|
+
puts "\n", character.report(desc: :long, stuff: false, credits: false)
|
27
|
+
path = self.new(character)
|
28
|
+
loop {
|
29
|
+
careers = path.eligible(careers)
|
30
|
+
break if careers.empty?
|
31
|
+
choice = TravellerRPG.choose("Choose a career:", *careers)
|
32
|
+
career = self.career_class(choice).new(character)
|
33
|
+
path.run(career)
|
34
|
+
puts "\n", path.report, "\n"
|
35
|
+
break if TravellerRPG.choose("Exit career mode?", :yes, :no) == :yes
|
36
|
+
}
|
37
|
+
path
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :char, :careers
|
9
41
|
|
10
42
|
def initialize(character)
|
11
43
|
@char = character
|
12
44
|
@char.log "Initiated new career path"
|
13
45
|
@careers = []
|
14
|
-
@active_career
|
15
46
|
end
|
16
47
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
48
|
+
def run(career)
|
49
|
+
career = self.fallback unless self.apply(career)
|
50
|
+
loop {
|
51
|
+
career.run_term
|
52
|
+
break unless career.active?
|
53
|
+
break if career.must_exit?
|
54
|
+
next if career.must_remain?
|
55
|
+
break if TravellerRPG.choose("Muster out?", :yes, :no) == :yes
|
56
|
+
}
|
57
|
+
career.muster_out
|
58
|
+
@careers << career
|
59
|
+
career
|
26
60
|
end
|
27
61
|
|
28
62
|
def apply(career)
|
29
63
|
raise(Ineligible, career.name) unless self.eligible?(career)
|
30
|
-
if career.qualify_check?(@careers.size)
|
64
|
+
if career.qualify_check?(dm: -1 * @careers.size)
|
31
65
|
@char.log "Qualified for #{career.name}"
|
32
66
|
self.enter(career)
|
33
67
|
else
|
@@ -36,58 +70,72 @@ module TravellerRPG
|
|
36
70
|
end
|
37
71
|
end
|
38
72
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@active_career.active = true
|
47
|
-
self.basic_training
|
48
|
-
self
|
73
|
+
def fallback
|
74
|
+
case TravellerRPG.choose("Fallback career:", :drifter, :draft)
|
75
|
+
when :drifter
|
76
|
+
self.enter TravellerRPG::Drifter.new(@char)
|
77
|
+
when :draft
|
78
|
+
self.draft
|
79
|
+
end
|
49
80
|
end
|
50
81
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
}
|
82
|
+
# return an active career, no qualification, that has completed basic
|
83
|
+
# training
|
84
|
+
def draft
|
85
|
+
roll = TravellerRPG.roll('d6')
|
86
|
+
puts "Draft roll: #{roll}"
|
87
|
+
dc = self.class::DRAFT_CAREERS.fetch(roll)
|
88
|
+
@char.log "Drafted: #{dc.compact.join(', ')}"
|
89
|
+
|
90
|
+
career = CareerPath.career_class(dc.first).new(@char)
|
91
|
+
career.activate(dc.last)
|
92
|
+
self.basic_training(career)
|
93
|
+
career
|
64
94
|
end
|
65
95
|
|
66
|
-
def
|
67
|
-
|
68
|
-
@active_career.run_term
|
69
|
-
unless @active_career.active?
|
70
|
-
@careers << @active_career
|
71
|
-
@active_career = nil
|
72
|
-
end
|
96
|
+
def eligible(careers)
|
97
|
+
careers.select { |c| self.eligible?(c) }
|
73
98
|
end
|
74
99
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
100
|
+
def eligible?(career)
|
101
|
+
case career
|
102
|
+
when Career
|
103
|
+
return false if career.active?
|
104
|
+
cls = career.class
|
105
|
+
when String
|
106
|
+
cls = CareerPath.career_class(career)
|
82
107
|
end
|
108
|
+
return true if cls == TravellerRPG::Drifter
|
109
|
+
!@careers.any? { |c| c.class == cls }
|
110
|
+
end
|
111
|
+
|
112
|
+
def enter(career)
|
113
|
+
raise(Ineligible, career.name) unless self.eligible?(career)
|
114
|
+
raise(Error, "career is already active") if career.active?
|
115
|
+
raise(Error, "career has already started") unless career.term == 0
|
116
|
+
@char.log "Entering new career: #{career.name}"
|
117
|
+
career.activate
|
118
|
+
self.basic_training(career)
|
119
|
+
career
|
83
120
|
end
|
84
121
|
|
85
|
-
def
|
86
|
-
|
122
|
+
def basic_training(career)
|
123
|
+
return unless career.term.zero?
|
124
|
+
skills = career.class::SERVICE_SKILLS - @char.skills.keys
|
125
|
+
return if skills.empty?
|
126
|
+
if @careers.length > 0
|
127
|
+
skills = [TravellerRPG.choose("Choose service skill:", *skills)]
|
128
|
+
end
|
129
|
+
skills.each { |sym|
|
130
|
+
raise "unknown skill: #{sym}" unless TravellerRPG::SKILLS.key?(sym)
|
131
|
+
@char.log "Acquired basic training skill: #{sym} 0"
|
132
|
+
@char.skills[sym] = 0
|
133
|
+
}
|
87
134
|
end
|
88
135
|
|
89
|
-
def
|
90
|
-
@char.
|
136
|
+
def report(char: true, careers: true)
|
137
|
+
[@char.report,
|
138
|
+
@careers.map(&:report).join("\n\n")].join("\n\n")
|
91
139
|
end
|
92
140
|
end
|
93
141
|
end
|