traveller_rpg 0.0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|