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