traveller_rpg 0.0.0.4
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 +7 -0
- data/README.md +6 -0
- data/Rakefile +18 -0
- data/VERSION +1 -0
- data/bin/chargen +47 -0
- data/lib/traveller_rpg/career.rb +269 -0
- data/lib/traveller_rpg/career_path.rb +93 -0
- data/lib/traveller_rpg/careers.rb +159 -0
- data/lib/traveller_rpg/character.rb +128 -0
- data/lib/traveller_rpg/data.rb +20 -0
- data/lib/traveller_rpg/generator.rb +63 -0
- data/lib/traveller_rpg/homeworld.rb +51 -0
- data/lib/traveller_rpg.rb +222 -0
- data/traveller_rpg.gemspec +29 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5641a18846aa83312b6821006edc36d5add290b17263c4258ad76413b8891993
|
4
|
+
data.tar.gz: 2041763e41ee95d19f4917ff3d996f53820e544caa63655d6e8a5d7cbf361ad9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ebedfc2b15be64c0a617b376640397a42fccce3d1df8948da6f2c340aa5dbc60ca4cbe28ff3c20bd1e52df9b10867c97d83412e300f49bc05b1b3299c111cda
|
7
|
+
data.tar.gz: f21f01d409cd0f3169126e744527f35ee88d9f545974cd57d548082270b94f0e4b7350d0e152ab46eeb999741dcee110adf4a34aaae3c4ddc27ba48a1e2a795e
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new :test do |t|
|
4
|
+
t.pattern = "test/*.rb"
|
5
|
+
t.warning = true
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'buildar'
|
10
|
+
|
11
|
+
Buildar.new do |b|
|
12
|
+
b.gemspec_file = 'traveller_rpg.gemspec'
|
13
|
+
b.version_file = 'VERSION'
|
14
|
+
b.use_git = true
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
warn "buildar tasks unavailable"
|
18
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0.4
|
data/bin/chargen
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require 'traveller_rpg'
|
5
|
+
require 'traveller_rpg/careers'
|
6
|
+
require 'traveller_rpg/generator'
|
7
|
+
require 'traveller_rpg/career_path'
|
8
|
+
|
9
|
+
char = TravellerRPG::Generator.character
|
10
|
+
path = TravellerRPG::CareerPath.new(char)
|
11
|
+
|
12
|
+
careers = %w{Scout}
|
13
|
+
|
14
|
+
loop {
|
15
|
+
careers = careers.select { |c| path.eligible? c }
|
16
|
+
if careers.empty?
|
17
|
+
puts "No eligible careers available!"
|
18
|
+
break
|
19
|
+
end
|
20
|
+
career = TravellerRPG.choose("Choose a career", *careers)
|
21
|
+
career = TravellerRPG.career_class(career).new(char)
|
22
|
+
|
23
|
+
# skip draft / drifter for now
|
24
|
+
accepted = false
|
25
|
+
accepted = path.apply(career) while !accepted
|
26
|
+
|
27
|
+
#
|
28
|
+
loop {
|
29
|
+
path.run_term
|
30
|
+
break unless path.active_career
|
31
|
+
break if career.must_exit?
|
32
|
+
next if career.must_remain?
|
33
|
+
break if TravellerRPG.choose("Muster out?", :yes, :no) == :yes
|
34
|
+
}
|
35
|
+
path.muster_out if path.active_career
|
36
|
+
|
37
|
+
puts char.desc.pretty_inspect
|
38
|
+
puts char.stats.pretty_inspect
|
39
|
+
puts char.skills.pretty_inspect
|
40
|
+
puts char.stuff.pretty_inspect
|
41
|
+
|
42
|
+
break if TravellerRPG.choose("Exit career mode?", :yes, :no) == :yes
|
43
|
+
}
|
44
|
+
|
45
|
+
binding.pry if ENV['PRY']
|
46
|
+
|
47
|
+
puts "DONE"
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'traveller_rpg'
|
2
|
+
|
3
|
+
module TravellerRPG
|
4
|
+
class Career
|
5
|
+
class Error < RuntimeError; end
|
6
|
+
class UnknownAssignment < Error; end
|
7
|
+
class MusterError < Error; end
|
8
|
+
|
9
|
+
def self.advanced_skills?(stats)
|
10
|
+
stats.education >= 8
|
11
|
+
end
|
12
|
+
|
13
|
+
TERM_YEARS = 4
|
14
|
+
|
15
|
+
QUALIFY_CHECK = 5
|
16
|
+
SURVIVAL_CHECK = 6
|
17
|
+
ADVANCEMENT_CHECK = 9
|
18
|
+
|
19
|
+
STATS = Array.new(6) { :default }
|
20
|
+
SERVICE_SKILLS = Array.new(6) { :default }
|
21
|
+
ADVANCED_SKILLS = Array.new(6) { :default }
|
22
|
+
SPECIALIST_SKILLS = { default: Array.new(6) { :default } }
|
23
|
+
RANKS = {}
|
24
|
+
|
25
|
+
EVENTS = {
|
26
|
+
2 => nil,
|
27
|
+
3 => nil,
|
28
|
+
4 => nil,
|
29
|
+
5 => nil,
|
30
|
+
6 => nil,
|
31
|
+
7 => nil,
|
32
|
+
8 => nil,
|
33
|
+
9 => nil,
|
34
|
+
10 => nil,
|
35
|
+
11 => nil,
|
36
|
+
12 => nil,
|
37
|
+
}
|
38
|
+
|
39
|
+
MISHAPS = {
|
40
|
+
1 => nil,
|
41
|
+
2 => nil,
|
42
|
+
3 => nil,
|
43
|
+
4 => nil,
|
44
|
+
5 => nil,
|
45
|
+
6 => nil,
|
46
|
+
}
|
47
|
+
|
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,
|
60
|
+
}
|
61
|
+
|
62
|
+
attr_reader :stats, :skills, :benefits
|
63
|
+
attr_accessor :term, :active, :rank
|
64
|
+
|
65
|
+
def initialize(char, assignment: nil, term: 0, active: false, rank: 0,
|
66
|
+
benefits: {})
|
67
|
+
@char = char
|
68
|
+
@assignment = assignment
|
69
|
+
if @assignment and !self.class::SPECIALIST_SKILLS.key?(@assignment)
|
70
|
+
raise(UnknownAssignment, assignment.inspect)
|
71
|
+
end
|
72
|
+
|
73
|
+
# career tracking
|
74
|
+
@term = term
|
75
|
+
@active = active
|
76
|
+
@rank = rank
|
77
|
+
@benefits = benefits # acquired equipment, ships / shares
|
78
|
+
@term_mandate = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def assignment
|
82
|
+
@assignment ||= TravellerRPG.choose("Choose a specialty:",
|
83
|
+
*self.class::SPECIALIST_SKILLS.keys)
|
84
|
+
end
|
85
|
+
|
86
|
+
def active?
|
87
|
+
!!@active
|
88
|
+
end
|
89
|
+
|
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
|
96
|
+
end
|
97
|
+
|
98
|
+
def survival_check?(dm: 0)
|
99
|
+
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
|
103
|
+
end
|
104
|
+
|
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
|
110
|
+
end
|
111
|
+
|
112
|
+
# any skills obtained start at level 1
|
113
|
+
def training_roll
|
114
|
+
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
|
156
|
+
end
|
157
|
+
|
158
|
+
def event_roll(dm: 0)
|
159
|
+
roll = TravellerRPG.roll('2d6')
|
160
|
+
clamped = (roll + dm).clamp(2, 12)
|
161
|
+
@char.log "Event roll (2d6): #{roll} + DM #{dm} = #{clamped}"
|
162
|
+
@char.log self.class::EVENTS.fetch(clamped)
|
163
|
+
# TODO: actually perform the event stuff
|
164
|
+
end
|
165
|
+
|
166
|
+
def mishap_roll
|
167
|
+
roll = TravellerRPG.roll('d6')
|
168
|
+
@char.log "Mishap roll (d6): #{roll}"
|
169
|
+
@char.log self.class::MISHAPS.fetch(roll)
|
170
|
+
# TODO: actually perform the mishap stuff
|
171
|
+
end
|
172
|
+
|
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
|
+
def advance_rank
|
182
|
+
@rank += 1
|
183
|
+
@char.log "Advanced career to rank #{@rank}"
|
184
|
+
title, skill, level = self.rank_benefit
|
185
|
+
if title
|
186
|
+
@char.log "Awarded rank title: #{title}"
|
187
|
+
@char.log "Achieved rank skill: #{skill} #{level}"
|
188
|
+
@char.skills[skill] ||= 0
|
189
|
+
@char.skills[skill] = level if level > @char.skills[skill]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def must_remain?
|
194
|
+
@term_mandate == :must_remain
|
195
|
+
end
|
196
|
+
|
197
|
+
def must_exit?
|
198
|
+
@term_mandate == :must_exit
|
199
|
+
end
|
200
|
+
|
201
|
+
def run_term
|
202
|
+
raise(Error, "career is inactive") unless @active
|
203
|
+
raise(Error, "must exit") if self.must_exit?
|
204
|
+
@term += 1
|
205
|
+
@char.log format("%s term %i started, age %i",
|
206
|
+
self.name, @term, @char.age)
|
207
|
+
self.training_roll
|
208
|
+
|
209
|
+
if self.survival_check?
|
210
|
+
@char.log format("%s term %i was successful", self.name, @term)
|
211
|
+
@char.age TERM_YEARS
|
212
|
+
|
213
|
+
self.commission_roll if self.respond_to?(:commission_roll)
|
214
|
+
|
215
|
+
adv_roll = TravellerRPG.roll('2d6')
|
216
|
+
# TODO: DM?
|
217
|
+
if self.advancement_check?(roll: adv_roll)
|
218
|
+
self.advance_rank
|
219
|
+
self.training_roll
|
220
|
+
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
|
+
|
229
|
+
self.event_roll
|
230
|
+
else
|
231
|
+
@char.log "#{self.name} career ended with a mishap!"
|
232
|
+
@char.age rand(TERM_YEARS) + 1
|
233
|
+
self.mishap_roll
|
234
|
+
@active = false
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def retirement_bonus
|
239
|
+
@term >= 5 ? @term * 2000 : 0
|
240
|
+
end
|
241
|
+
|
242
|
+
def muster_out(dm: 0)
|
243
|
+
if @active
|
244
|
+
raise(MusterError, "career has not started") unless @term > 0
|
245
|
+
@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
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def name
|
261
|
+
self.class.name.split('::').last
|
262
|
+
end
|
263
|
+
|
264
|
+
# possibly nil
|
265
|
+
def rank_benefit
|
266
|
+
self.class::RANKS[@rank]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'traveller_rpg/career'
|
2
|
+
|
3
|
+
module TravellerRPG
|
4
|
+
class CareerPath
|
5
|
+
class Error < RuntimeError; end
|
6
|
+
class Ineligible < Error; end
|
7
|
+
|
8
|
+
attr_reader :char, :careers, :active_career
|
9
|
+
|
10
|
+
def initialize(character)
|
11
|
+
@char = character
|
12
|
+
@char.log "Initiated new career path"
|
13
|
+
@careers = []
|
14
|
+
@active_career
|
15
|
+
end
|
16
|
+
|
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 }
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply(career)
|
29
|
+
raise(Ineligible, career.name) unless self.eligible?(career)
|
30
|
+
if career.qualify_check?(@careers.size)
|
31
|
+
@char.log "Qualified for #{career.name}"
|
32
|
+
self.enter(career)
|
33
|
+
else
|
34
|
+
@char.log "Did not qualify for #{career.name}"
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
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
|
49
|
+
end
|
50
|
+
|
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
|
+
}
|
64
|
+
end
|
65
|
+
|
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
|
73
|
+
end
|
74
|
+
|
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
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def draft_term
|
86
|
+
@char.log "Drafted! (fake)"
|
87
|
+
end
|
88
|
+
|
89
|
+
def drifter_term
|
90
|
+
@char.log "Became a drifter (fake)"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'traveller_rpg'
|
2
|
+
require 'traveller_rpg/career'
|
3
|
+
|
4
|
+
module TravellerRPG
|
5
|
+
class MilitaryCareer < Career
|
6
|
+
COMMISSION_CHECK = 9
|
7
|
+
OFFICER_SKILLS = Array.new(6) { :default }
|
8
|
+
OFFICER_RANKS = {}
|
9
|
+
|
10
|
+
attr_reader :officer, :officer_rank
|
11
|
+
|
12
|
+
def initialize(char, **kwargs)
|
13
|
+
super(char, **kwargs)
|
14
|
+
@officer = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def rank
|
18
|
+
@officer ? @officer_rank : @rank
|
19
|
+
end
|
20
|
+
|
21
|
+
def rank_benefit
|
22
|
+
@officer ? self.class::OFFICER_RANKS[@officer_rank] : super
|
23
|
+
end
|
24
|
+
|
25
|
+
def advance_rank
|
26
|
+
return super unless @officer
|
27
|
+
@officer_rank += 1
|
28
|
+
@char.log "Advanced career to officer rank #{@officer_rank}"
|
29
|
+
title, skill, level = self.rank_benefit
|
30
|
+
if title
|
31
|
+
@char.log "Awarded officer rank title: #{title}"
|
32
|
+
@char.log "Achieved officer rank skill: #{skill} #{level}"
|
33
|
+
@char.skills[skill] ||= 0
|
34
|
+
@char.skills[skill] = level if level > @char.skills[skill]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def commission_check?(dm = 0)
|
39
|
+
roll = TravellerRPG.roll('2d6')
|
40
|
+
puts format("Commission check: roll %i (DM %i) against %i",
|
41
|
+
roll, dm, self.class::COMMISSION_CHECK)
|
42
|
+
(roll + dm) >= self.class::COMMISSION_CHECK
|
43
|
+
end
|
44
|
+
|
45
|
+
def commission_roll(dm: 0)
|
46
|
+
return if @officer
|
47
|
+
if TravellerRPG.choose("Apply for commission?", :yes, :no) == :yes
|
48
|
+
if self.commission_check?
|
49
|
+
@char.log "Became an officer!"
|
50
|
+
@officer = true
|
51
|
+
@officer_rank = 1
|
52
|
+
else
|
53
|
+
@char.log "Commission was rejected"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def officer?
|
59
|
+
!!@officer
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Navy < MilitaryCareer
|
64
|
+
end
|
65
|
+
|
66
|
+
class Army < MilitaryCareer
|
67
|
+
end
|
68
|
+
|
69
|
+
class Marines < MilitaryCareer
|
70
|
+
end
|
71
|
+
|
72
|
+
class MerchantMarine < MilitaryCareer
|
73
|
+
end
|
74
|
+
|
75
|
+
class Agent < MilitaryCareer
|
76
|
+
end
|
77
|
+
|
78
|
+
class Scout < MilitaryCareer
|
79
|
+
# note, 6th "stat" is really a skill - jack of all trades
|
80
|
+
STATS = [:strength, :dexterity, :endurance,
|
81
|
+
:intelligence, :education, :jack_of_all_trades]
|
82
|
+
SERVICE_SKILLS = [:pilot_small_craft, :survival, :mechanic,
|
83
|
+
:astrogation, :comms, :gun_combat_group]
|
84
|
+
ADVANCED_SKILLS = [:medic, :navigation, :engineer,
|
85
|
+
:computers, :space_science, :jack_of_all_trades]
|
86
|
+
|
87
|
+
# made up by Rick
|
88
|
+
OFFICER_SKILLS = [:deception, :language_group, :investigate,
|
89
|
+
:remote_operations, :tactics_military, :leadership]
|
90
|
+
SPECIALIST_SKILLS = {
|
91
|
+
courier: [:comms, :sensors, :pilot_spacecraft,
|
92
|
+
:vacc_suit, :zero_g, :astrogation],
|
93
|
+
survey: [:sensors, :persuade, :pilot_small_craft,
|
94
|
+
:navigation, :diplomat, :streetwise],
|
95
|
+
exploration: [:sensors, :pilot_spacecraft, :pilot_small_craft,
|
96
|
+
:life_science_any, :stealth, :recon],
|
97
|
+
}
|
98
|
+
|
99
|
+
# key: roll; values: title, skill, skill_value
|
100
|
+
RANKS = {
|
101
|
+
1 => [:scout, :vacc_suit, 1],
|
102
|
+
3 => [:senior_scout, :pilot, 1],
|
103
|
+
}
|
104
|
+
|
105
|
+
EVENTS = {
|
106
|
+
2 => 'Disaster! Roll on the mishap table but you are not ejected ' +
|
107
|
+
'from career.',
|
108
|
+
3 => 'Ambush! Choose Pilot 8+ to escape or Persuade 10+ to bargain. ' +
|
109
|
+
'Gain an Enemy either way',
|
110
|
+
4 => 'Survey an alien world. Choose Animals, Survival, Recon, or ' +
|
111
|
+
'Life Sciences 1',
|
112
|
+
5 => 'You perform an exemplary service. Gain a benefit roll with +1 DM',
|
113
|
+
6 => 'You spend several years exploring the star system; ' +
|
114
|
+
'Choose Atrogation, Navigation, Pilot (small craft) or Mechanic 1',
|
115
|
+
7 => 'Life event. Roll on the Life Events table',
|
116
|
+
8 => 'Gathered intelligence on an alien race. Roll Sensors 8+ or ' +
|
117
|
+
'Deception 8+. Gain an ally in the Imperium and +2 DM to your ' +
|
118
|
+
'next Advancement roll on success. Roll on the mishap table on ' +
|
119
|
+
'failure, but you are not ejected from career.',
|
120
|
+
9 => 'You rescue disaster survivors. Roll either Medic 8+ or ' +
|
121
|
+
'Engineer 8+. Gain a Contact and +2 DM on next Advancement roll ' +
|
122
|
+
'or else gain an Enemy',
|
123
|
+
10 => 'You spend a great deal of time on the fringes of known space. ' +
|
124
|
+
'Roll Survival 8+ or Pilot 8+. Gain a Contact in an alien race ' +
|
125
|
+
'and one level in any skill, or else roll on the Mishap table.',
|
126
|
+
11 => 'You serve as a courier for an important message for the ' +
|
127
|
+
'Imperium. Gain one level of diplomat or take +4 DM to your ' +
|
128
|
+
'next Advancement roll.',
|
129
|
+
12 => 'You make an important discovery for the Imperium. Gain a ' +
|
130
|
+
'career rank.',
|
131
|
+
}
|
132
|
+
|
133
|
+
MISHAPS = {
|
134
|
+
1 => 'Severely injured in action. Roll twice on the Injury table ' +
|
135
|
+
'or take a level 2 Injury.',
|
136
|
+
2 => 'Suffer psychological damage. Reduce Intelligence or Social ' +
|
137
|
+
'Standing by 1',
|
138
|
+
3 => 'Your ship is damaged, and you have to hitch a ride back to ' +
|
139
|
+
'your nearest scout base. Gain 1d6 Contacts and 1d3 Enemies.',
|
140
|
+
4 => 'You inadvertently cause a conflict between the Imperium and ' +
|
141
|
+
'a minor world or race. Gain a Rival and Diplomat 1.',
|
142
|
+
5 => 'You have no idea what happened to you. Your ship was found ' +
|
143
|
+
'drifting on the fringes of friendly space',
|
144
|
+
6 => 'Injured. Roll on the Injury table.',
|
145
|
+
}
|
146
|
+
|
147
|
+
# not defined at http://www.traveller-srd.com/core-rules/careers/ :(
|
148
|
+
BENEFITS = {}
|
149
|
+
|
150
|
+
def qualify_check?(career_count)
|
151
|
+
@char.log format("Qualify DM is based on Intelligence %i",
|
152
|
+
@char.stats.intelligence)
|
153
|
+
super(career_count, dm: @char.class.stats_dm(@char.stats.intelligence))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Drifter < Career
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'traveller_rpg'
|
2
|
+
|
3
|
+
module TravellerRPG
|
4
|
+
class Character
|
5
|
+
Stats = Struct.new(:strength, :dexterity, :endurance,
|
6
|
+
:intelligence, :education, :social_status) do
|
7
|
+
def self.roll(spec = '2d6')
|
8
|
+
self.new(*Array.new(6) { TravellerRPG.roll spec })
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.empty
|
12
|
+
self.new(*Array.new(6) { 0 })
|
13
|
+
end
|
14
|
+
|
15
|
+
def boost(hsh)
|
16
|
+
hsh.each { |k,v| self[k] += v if self[k] }
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Description = Struct.new(:name, :gender, :age,
|
22
|
+
:appearance, :plot, :temperament) do
|
23
|
+
def self.new_with_hash(hsh)
|
24
|
+
self.new(hsh[:name], hsh[:gender], hsh[:age],
|
25
|
+
hsh[:appearance], hsh[:plot], hsh[:temperament])
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge(other)
|
29
|
+
other = self.class.new_with_hash(other) if other.is_a?(Hash)
|
30
|
+
self.class.new(other.name || self.name,
|
31
|
+
other.gender || self.gender,
|
32
|
+
other.age || self.age,
|
33
|
+
other.appearance || self.appearance,
|
34
|
+
other.plot || self.plot,
|
35
|
+
other.temperament || self.temperament)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.stats_dm(stat)
|
40
|
+
case stat
|
41
|
+
when 0 then -3
|
42
|
+
when 1..2 then -2
|
43
|
+
when 3..5 then -1
|
44
|
+
when 6..8 then 0
|
45
|
+
when 9..11 then 1
|
46
|
+
when 12..14 then 2
|
47
|
+
when 15..20 then 3
|
48
|
+
else
|
49
|
+
raise "unexpected stat: #{stat} (#{stat.class})"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :desc, :stats, :homeworld, :skills, :stuff
|
54
|
+
|
55
|
+
def initialize(desc:, stats:, homeworld:,
|
56
|
+
skills: {}, stuff: {}, log: [])
|
57
|
+
@desc = desc
|
58
|
+
@stats = stats
|
59
|
+
@homeworld = homeworld
|
60
|
+
@skills = skills
|
61
|
+
@stuff = stuff
|
62
|
+
@log = log
|
63
|
+
self.birth
|
64
|
+
end
|
65
|
+
|
66
|
+
# gain background skills based on homeworld
|
67
|
+
def birth
|
68
|
+
return nil unless @log.empty?
|
69
|
+
self.log format("%s was born on %s (%s)",
|
70
|
+
@desc.name,
|
71
|
+
@homeworld.name,
|
72
|
+
@homeworld.traits.join(' '))
|
73
|
+
skill_count = 3 + self.class.stats_dm(@stats.education)
|
74
|
+
self.log format("Education %i qualifies for %i skills",
|
75
|
+
@stats.education, skill_count)
|
76
|
+
skill_choices = []
|
77
|
+
|
78
|
+
# choose skill_count skills
|
79
|
+
if @homeworld.skills.size <= skill_count
|
80
|
+
self.log format("Homeworld %s only has %i skills available",
|
81
|
+
@homeworld.name, @homeworld.skills.size)
|
82
|
+
skill_choices = @homeworld.skills
|
83
|
+
else
|
84
|
+
skill_count.times { |i|
|
85
|
+
available = @homeworld.skills - skill_choices
|
86
|
+
skill_choices << TravellerRPG.choose("Choose a skill:", *available)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
skill_choices.each { |sym|
|
90
|
+
self.log "Acquired background skill: #{sym} 0"
|
91
|
+
@skills[sym] ||= 0
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_stuff(benefits)
|
96
|
+
benefits.each { |sym, val|
|
97
|
+
self.log "Collecting #{sym} #{val}"
|
98
|
+
case @stuff[sym]
|
99
|
+
when Numeric, Array
|
100
|
+
self.log "Adding #{sym} #{val} to #{@stuff[sym]}"
|
101
|
+
@stuff[sym] += val
|
102
|
+
when NilClass
|
103
|
+
@stuff[sym] = val
|
104
|
+
else
|
105
|
+
raise("unexpected benefit: #{sym} #{val} (#{val.class})")
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def log(msg = nil)
|
111
|
+
return @log unless msg
|
112
|
+
puts msg
|
113
|
+
@log << msg
|
114
|
+
end
|
115
|
+
|
116
|
+
def name
|
117
|
+
@desc.name
|
118
|
+
end
|
119
|
+
|
120
|
+
def age(years = nil)
|
121
|
+
years ? @desc.age += years : @desc.age
|
122
|
+
end
|
123
|
+
|
124
|
+
def skill_check?(skill, val = 0)
|
125
|
+
@skills[skill] and @skills[skill] >= val
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module TravellerRPG
|
2
|
+
module Data
|
3
|
+
PATH = File.expand_path(File.join(__dir__, '..', '..', 'data'))
|
4
|
+
raise "can't find #{PATH}" unless File.directory?(PATH)
|
5
|
+
|
6
|
+
def self.files
|
7
|
+
Dir[File.join(PATH, '*')]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.sample(filename)
|
11
|
+
path = File.join(PATH, filename)
|
12
|
+
raise "#{path} does not exist" unless File.exist?(path)
|
13
|
+
if filename.match %r{\.txt\z}
|
14
|
+
File.readlines(path).sample.chomp
|
15
|
+
else
|
16
|
+
raise "can't handle #{filename}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'traveller_rpg'
|
2
|
+
require 'traveller_rpg/data'
|
3
|
+
require 'traveller_rpg/character'
|
4
|
+
require 'traveller_rpg/homeworld'
|
5
|
+
|
6
|
+
module TravellerRPG
|
7
|
+
module Generator
|
8
|
+
def self.character(descr = {}, homeworld: nil)
|
9
|
+
homeworld ||= self.homeworld
|
10
|
+
Character.new(desc: self.desc.merge(descr),
|
11
|
+
stats: Character::Stats.roll,
|
12
|
+
homeworld: homeworld)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.homeworld(name = nil)
|
16
|
+
name ||= Data.sample('homeworlds.txt')
|
17
|
+
Homeworld.new(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.name(gender)
|
21
|
+
case gender.to_s.downcase
|
22
|
+
when 'm', 'male'
|
23
|
+
Data.sample('male_names.txt')
|
24
|
+
when 'f', 'female'
|
25
|
+
Data.sample('female_names.txt')
|
26
|
+
else
|
27
|
+
raise "unknown gender: #{gender}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.gender
|
32
|
+
TravellerRPG.roll(dice: 1) > 3 ? 'M' : 'F'
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.hair(tone: nil, body: nil, color: nil, length: nil)
|
36
|
+
tone ||= Data.sample('hair_tone.txt')
|
37
|
+
body ||= Data.sample('hair_body.txt')
|
38
|
+
color ||= Data.sample('hair_colors.txt')
|
39
|
+
length ||= Data.sample('hair_length.txt')
|
40
|
+
[tone, body, color, length].join(' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.appearance(hair: nil, skin: nil)
|
44
|
+
hair ||= self.hair
|
45
|
+
skin ||= Data.sample('skin_tones.txt')
|
46
|
+
"#{hair} hair with #{skin} skin"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.plot
|
50
|
+
Data.sample('plots.txt')
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.temperament
|
54
|
+
Data.sample('temperaments.txt')
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.desc
|
58
|
+
gender = self.gender
|
59
|
+
Character::Description.new(self.name(gender), gender, 18,
|
60
|
+
self.appearance, self.plot, self.temperament)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'traveller_rpg'
|
2
|
+
|
3
|
+
module TravellerRPG
|
4
|
+
class Homeworld
|
5
|
+
TRAITS = {
|
6
|
+
agricultural: :animals,
|
7
|
+
asteroid: :zero_g,
|
8
|
+
desert: :survival,
|
9
|
+
fluid_oceans: :seafarer,
|
10
|
+
garden: :animals,
|
11
|
+
high_technology: :computers,
|
12
|
+
high_population: :streetwise,
|
13
|
+
ice_capped: :vacc_suit,
|
14
|
+
industrial: :trade,
|
15
|
+
low_technology: :survival,
|
16
|
+
poor: :animals,
|
17
|
+
rich: :carouse,
|
18
|
+
water_world: :seafarer,
|
19
|
+
vacuum: :vacc_suit,
|
20
|
+
education: [:admin, :advocate, :art, :carouse, :comms,
|
21
|
+
:computer, :drive, :engineer, :language, :medic,
|
22
|
+
:physical_science, :life_science, :social_science,
|
23
|
+
:space_science, :trade],
|
24
|
+
}
|
25
|
+
TRAIT_MIN = 3
|
26
|
+
TRAIT_MAX = 6
|
27
|
+
|
28
|
+
attr_reader :name, :traits, :skills
|
29
|
+
|
30
|
+
def initialize(name, traits = [])
|
31
|
+
@name = name
|
32
|
+
if traits.size > self.class::TRAIT_MAX
|
33
|
+
warn "lots of world traits: #{traits}"
|
34
|
+
elsif traits.empty?
|
35
|
+
sample_num = rand(TRAIT_MAX - TRAIT_MIN + 1) + TRAIT_MIN
|
36
|
+
traits = self.class::TRAITS.keys.sample(sample_num)
|
37
|
+
end
|
38
|
+
@traits = traits
|
39
|
+
@skills = []
|
40
|
+
@traits.each { |trait|
|
41
|
+
skill = self.class::TRAITS.fetch(trait)
|
42
|
+
if skill.is_a?(Array)
|
43
|
+
skill.each { |sk| @skills << sk }
|
44
|
+
else
|
45
|
+
@skills << skill
|
46
|
+
end
|
47
|
+
}
|
48
|
+
@skills.uniq!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module TravellerRPG
|
2
|
+
PLAYER_CHOICE = ENV['PLAYER_CHOICE']
|
3
|
+
|
4
|
+
ROLL_RGX = %r{
|
5
|
+
\A # starts with
|
6
|
+
(\d*) # 0 or more digits; dice count
|
7
|
+
[dD] # character d
|
8
|
+
(\d+) # 1 or more digits; face count
|
9
|
+
\z # end str
|
10
|
+
}x
|
11
|
+
|
12
|
+
def self.roll(str = nil, faces: 6, dice: 2, count: 1)
|
13
|
+
return self.roll_str(str) if str
|
14
|
+
rolln = -> (faces, dice) { Array.new(dice) { rand(faces) + 1 } }
|
15
|
+
(Array.new(count) { rolln.(faces, dice).sum }.sum.to_f / count).round
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.roll_str(str)
|
19
|
+
matches = str.match(ROLL_RGX) or raise("bad roll: #{str}")
|
20
|
+
dice, faces = matches[1], matches[2]
|
21
|
+
self.roll(dice: dice.empty? ? 1 : dice.to_i, faces: faces.to_i)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.choose(msg, *args)
|
25
|
+
return self.player_choose(msg, *args) if PLAYER_CHOICE
|
26
|
+
puts msg + ' (' + args.join(' ') + ')'
|
27
|
+
choice = args.sample
|
28
|
+
puts "> #{choice}"
|
29
|
+
choice
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.player_choose(msg, *args)
|
33
|
+
chosen = false
|
34
|
+
while !chosen
|
35
|
+
puts msg + ' (' + args.join(' ') + ')'
|
36
|
+
choice = self.prompt.to_s.downcase.to_sym
|
37
|
+
if args.include?(choice)
|
38
|
+
chosen = choice
|
39
|
+
else
|
40
|
+
puts "Try again.\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
chosen
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.player_prompt(msg = nil)
|
47
|
+
print msg + ' ' if msg
|
48
|
+
print '> '
|
49
|
+
$stdin.gets(chomp: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.career_class(str)
|
53
|
+
Object.const_get("TravellerRPG::#{str.split('::').last}")
|
54
|
+
end
|
55
|
+
|
56
|
+
# per http://www.traveller-srd.com/core-rules/skills/
|
57
|
+
SKILLS = {
|
58
|
+
admin: nil,
|
59
|
+
advocate: nil,
|
60
|
+
|
61
|
+
animals_group: nil,
|
62
|
+
animals_riding: nil,
|
63
|
+
animals_veterinary: nil,
|
64
|
+
animals_training: nil,
|
65
|
+
animals_farming: nil,
|
66
|
+
|
67
|
+
athletics_group: nil,
|
68
|
+
athletics_coordination: nil,
|
69
|
+
athletics_endurance: nil,
|
70
|
+
athletics_strength: nil,
|
71
|
+
athletics_flying: nil,
|
72
|
+
|
73
|
+
art_group: nil,
|
74
|
+
art_acting: nil,
|
75
|
+
art_dance: nil,
|
76
|
+
art_holography: nil,
|
77
|
+
art_instrument: nil,
|
78
|
+
art_sculpting: nil,
|
79
|
+
art_writing: nil,
|
80
|
+
|
81
|
+
astrogation: nil,
|
82
|
+
battle_dress: nil,
|
83
|
+
broker: nil,
|
84
|
+
carouse: nil,
|
85
|
+
comms: nil,
|
86
|
+
computers: nil,
|
87
|
+
deception: nil,
|
88
|
+
diplomat: nil,
|
89
|
+
|
90
|
+
drive_group: nil,
|
91
|
+
drive_hovercraft: nil,
|
92
|
+
drive_mole: nil,
|
93
|
+
drive_tracked: nil,
|
94
|
+
drive_walker: nil,
|
95
|
+
drive_wheeled: nil,
|
96
|
+
|
97
|
+
engineer_group: nil,
|
98
|
+
engineer_manoeuvre: nil,
|
99
|
+
engineer_jump_drive: nil,
|
100
|
+
engineer_electronics: nil,
|
101
|
+
engineer_life_support: nil,
|
102
|
+
engineer_power: nil,
|
103
|
+
|
104
|
+
explosives: nil,
|
105
|
+
|
106
|
+
flyer_group: nil,
|
107
|
+
flyer_airship: nil,
|
108
|
+
flyer_grav: nil,
|
109
|
+
flyer_rotor: nil,
|
110
|
+
flyer_wing: nil,
|
111
|
+
|
112
|
+
gambler: nil,
|
113
|
+
|
114
|
+
gunner_group: nil,
|
115
|
+
gunner_turrets: nil,
|
116
|
+
gunner_ortillery: nil,
|
117
|
+
gunner_screens: nil,
|
118
|
+
gunner_capital_weapons: nil,
|
119
|
+
|
120
|
+
gun_combat_group: nil,
|
121
|
+
gun_combat_slug_rifle: nil,
|
122
|
+
gun_combat_slug_pistol: nil,
|
123
|
+
gun_combat_shotgun: nil,
|
124
|
+
gun_combat_energy_rifle: nil,
|
125
|
+
gun_combat_energy_pistol: nil,
|
126
|
+
|
127
|
+
heavy_weapons_group: nil,
|
128
|
+
heavy_weapons_launchers: nil,
|
129
|
+
heavy_weapons_man_portable_artillery: nil,
|
130
|
+
heavy_weapons_field_artillery: nil,
|
131
|
+
|
132
|
+
investigate: nil,
|
133
|
+
jack_of_all_trades: nil,
|
134
|
+
|
135
|
+
language_group: nil,
|
136
|
+
language_anglic: nil,
|
137
|
+
|
138
|
+
leadership: nil,
|
139
|
+
|
140
|
+
life_sciences_group: nil,
|
141
|
+
life_sciences_biology: nil,
|
142
|
+
life_sciences_cybernetics: nil,
|
143
|
+
life_sciences_genetics: nil,
|
144
|
+
life_sciences_psionicology: nil,
|
145
|
+
|
146
|
+
mechanic: nil,
|
147
|
+
medic: nil,
|
148
|
+
|
149
|
+
melee_group: nil,
|
150
|
+
melee_unarmed_combat: nil,
|
151
|
+
melee_blade: nil,
|
152
|
+
melee_bludgeon: nil,
|
153
|
+
melee_natural_weapons: nil,
|
154
|
+
|
155
|
+
navigation: nil,
|
156
|
+
persuade: nil,
|
157
|
+
|
158
|
+
pilot_group: nil,
|
159
|
+
pilot_small_craft: nil,
|
160
|
+
pilot_spacecraft: nil,
|
161
|
+
pilot_capital_ships: nil,
|
162
|
+
|
163
|
+
physical_sciences_group: nil,
|
164
|
+
physical_sciences_physics: nil,
|
165
|
+
physical_sciences_chemistry: nil,
|
166
|
+
physical_sciences_electronics: nil,
|
167
|
+
|
168
|
+
recon: nil,
|
169
|
+
remote_operations: nil,
|
170
|
+
|
171
|
+
seafarer_group: nil,
|
172
|
+
seafarer_sail: nil,
|
173
|
+
seafarer_submarine: nil,
|
174
|
+
seafarer_ocean_ships: nil,
|
175
|
+
seafarer_motorboats: nil,
|
176
|
+
|
177
|
+
sensors: nil,
|
178
|
+
|
179
|
+
social_sciences_group: nil,
|
180
|
+
social_sciences_archeology: nil,
|
181
|
+
social_sciences_economics: nil,
|
182
|
+
social_sciences_history: nil,
|
183
|
+
social_sciences_linguistics: nil,
|
184
|
+
social_sciences_philosophy: nil,
|
185
|
+
social_sciences_psychology: nil,
|
186
|
+
social_sciences_sophontology: nil,
|
187
|
+
|
188
|
+
space_sciences_group: nil,
|
189
|
+
space_sciences_planetology: nil,
|
190
|
+
space_sciences_robotics: nil,
|
191
|
+
space_sciences_xenology: nil,
|
192
|
+
|
193
|
+
stealth: nil,
|
194
|
+
steward: nil,
|
195
|
+
streetwise: nil,
|
196
|
+
survival: nil,
|
197
|
+
|
198
|
+
tactics_group: nil,
|
199
|
+
tactics_military: nil,
|
200
|
+
tactics_ground: nil,
|
201
|
+
|
202
|
+
trade_group: nil,
|
203
|
+
trade_biologicals: nil,
|
204
|
+
trade_civil_engineering: nil,
|
205
|
+
trade_space_construction: nil,
|
206
|
+
trade_hydroponics: nil,
|
207
|
+
trade_polymers: nil,
|
208
|
+
|
209
|
+
vacc_suit: nil,
|
210
|
+
zero_g: nil,
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
# compatibility stuff
|
215
|
+
|
216
|
+
unless Comparable.method_defined?(:clamp)
|
217
|
+
module Comparable
|
218
|
+
def clamp(low, high)
|
219
|
+
[[self, low].max, high].min
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'traveller_rpg'
|
3
|
+
s.summary = 'Automating portions of Traveller RPG'
|
4
|
+
s.description = 'Mostly based on Traveller SRD'
|
5
|
+
s.authors = ["Rick Hull"]
|
6
|
+
s.homepage = 'https://github.com/rickhull/traveller_rpg'
|
7
|
+
s.license = 'LGPL-3.0'
|
8
|
+
s.files = [
|
9
|
+
'traveller_rpg.gemspec',
|
10
|
+
'VERSION',
|
11
|
+
'Rakefile',
|
12
|
+
'README.md',
|
13
|
+
'lib/traveller_rpg.rb',
|
14
|
+
'lib/traveller_rpg/career_path.rb',
|
15
|
+
'lib/traveller_rpg/career.rb',
|
16
|
+
'lib/traveller_rpg/careers.rb',
|
17
|
+
'lib/traveller_rpg/character.rb',
|
18
|
+
'lib/traveller_rpg/data.rb',
|
19
|
+
'lib/traveller_rpg/generator.rb',
|
20
|
+
'lib/traveller_rpg/homeworld.rb',
|
21
|
+
'bin/chargen',
|
22
|
+
]
|
23
|
+
s.executables = ['chargen']
|
24
|
+
s.add_development_dependency 'buildar', '~> 3'
|
25
|
+
s.add_development_dependency 'minitest', '~> 5'
|
26
|
+
s.required_ruby_version = '~> 2'
|
27
|
+
|
28
|
+
s.version = File.read(File.join(__dir__, 'VERSION')).chomp
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: traveller_rpg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rick Hull
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: buildar
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
description: Mostly based on Traveller SRD
|
42
|
+
email:
|
43
|
+
executables:
|
44
|
+
- chargen
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- bin/chargen
|
52
|
+
- lib/traveller_rpg.rb
|
53
|
+
- lib/traveller_rpg/career.rb
|
54
|
+
- lib/traveller_rpg/career_path.rb
|
55
|
+
- lib/traveller_rpg/careers.rb
|
56
|
+
- lib/traveller_rpg/character.rb
|
57
|
+
- lib/traveller_rpg/data.rb
|
58
|
+
- lib/traveller_rpg/generator.rb
|
59
|
+
- lib/traveller_rpg/homeworld.rb
|
60
|
+
- traveller_rpg.gemspec
|
61
|
+
homepage: https://github.com/rickhull/traveller_rpg
|
62
|
+
licenses:
|
63
|
+
- LGPL-3.0
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '2'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.6.14
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Automating portions of Traveller RPG
|
85
|
+
test_files: []
|