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.
@@ -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