traveller_rpg 0.0.0.4 → 0.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.
@@ -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
- def self.advanced_skills?(stats)
10
- stats.education >= 8
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
- QUALIFY_CHECK = 5
16
- SURVIVAL_CHECK = 6
17
- ADVANCEMENT_CHECK = 9
17
+ QUALIFICATION = [:default, 5]
18
18
 
19
- STATS = Array.new(6) { :default }
19
+ PERSONAL_SKILLS = Array.new(6) { :default }
20
20
  SERVICE_SKILLS = Array.new(6) { :default }
21
21
  ADVANCED_SKILLS = Array.new(6) { :default }
22
- SPECIALIST_SKILLS = { default: Array.new(6) { :default } }
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
- CASH = {
49
- 2 => -500,
50
- 3 => -100,
51
- 4 => 200,
52
- 5 => 400,
53
- 6 => 600,
54
- 7 => 800,
55
- 8 => 1000,
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
- attr_reader :stats, :skills, :benefits
63
- attr_accessor :term, :active, :rank
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, assignment: nil, term: 0, active: false, rank: 0,
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
- @assignment ||= TravellerRPG.choose("Choose a specialty:",
83
- *self.class::SPECIALIST_SKILLS.keys)
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?(career_count, dm: 0)
91
- dm += -1 * career_count
92
- roll = TravellerRPG.roll('2d6')
93
- puts format("Qualify check: rolled %i (DM %i) against %i",
94
- roll, dm, self.class::QUALIFY_CHECK)
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
- puts format("Survival check: rolled %i (DM %i) against %i",
101
- roll, dm, self.class::SURVIVAL_CHECK)
102
- (roll + dm) >= self.class::SURVIVAL_CHECK
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 advancement_check?(roll: nil, dm: 0)
106
- roll ||= TravellerRPG.roll('2d6')
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 (d6): #{roll}"
116
- choices = [:stats, :service, :specialist]
117
- choices << :advanced if self.class.advanced_skills?(@char.stats)
118
- choices << :officer if self.respond_to?(:officer) and self.officer
119
- choice = TravellerRPG.choose("Choose training regimen:", *choices)
120
- case choice
121
- when :stats
122
- stat = self.class::STATS.fetch(roll - 1)
123
- if @char.stats.respond_to?(stat)
124
- @char.stats.boost(stat => 1)
125
- @char.log "Trained #{stat} to #{@char.stats.send(stat)}"
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
- @char.log "Event roll (2d6): #{roll} + DM #{dm} = #{clamped}"
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
- @char.log "Mishap roll (d6): #{roll}"
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.skills[skill] ||= 0
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 was successful", self.name, @term)
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?(roll: adv_roll)
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
- @char.log "#{self.name} career ended with a mishap!"
232
- @char.age rand(TERM_YEARS) + 1
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
- raise(MusterError, "career has not started") unless @term > 0
256
+ @char.log "Career is in good standing -- collect all benefits"
245
257
  @active = false
246
- cash_benefit = 0
247
- @char.log "Muster out: #{self.name}"
248
- dm += @char.skill_check?(:gambler, 1) ? 1 : 0
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.class::RANKS[@rank]
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/career'
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
- attr_reader :char, :careers, :active_career
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 eligible?(career)
18
- case career
19
- when Career
20
- return false if career.active?
21
- cls = career.class
22
- when String
23
- cls = TravellerRPG.career_class(career)
24
- end
25
- !@careers.any? { |c| c.class == cls }
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 enter(career)
40
- raise(Ineligible, career.name) unless self.eligible?(career)
41
- raise(Error, "career is already active") if career.active?
42
- raise(Error, "career has already started") unless career.term == 0
43
- self.muster_out
44
- @char.log "Entering new career: #{career.name}"
45
- @active_career = career
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
- def basic_training
52
- return unless @active_career.term.zero?
53
- if @careers.length.zero?
54
- @active_career.class::SERVICE_SKILLS
55
- else
56
- [TravellerRPG.choose("Service skill",
57
- *@active_career.class::SERVICE_SKILLS)]
58
- end.each { |sym|
59
- unless char.skills.key?(sym)
60
- @char.log "Acquired basic training skill: #{sym} 0"
61
- @char.skills[sym] = 0
62
- end
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 run_term
67
- raise(Error, "no active career") unless @active_career
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 muster_out
76
- if @active_career
77
- raise(Error, "career is inactive") unless @active_career.active?
78
- raise(Error, "must remain") if @active_career.must_remain?
79
- @char.add_stuff(@active_career.muster_out)
80
- @careers << @active_career
81
- @active_career = nil
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 draft_term
86
- @char.log "Drafted! (fake)"
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 drifter_term
90
- @char.log "Became a drifter (fake)"
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