coop_al 1.0.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 +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +3 -0
- data/README.md +89 -0
- data/Rakefile +10 -0
- data/coop.gemspec +29 -0
- data/examples/filler.rb +9 -0
- data/examples/test.rb +15 -0
- data/exe/coop +131 -0
- data/lib/coop_al/adventure.rb +52 -0
- data/lib/coop_al/adventure_generator.rb +35 -0
- data/lib/coop_al/bestiary.rb +51 -0
- data/lib/coop_al/bestiary_generator.rb +3 -0
- data/lib/coop_al/bestiary_populator.rb +14 -0
- data/lib/coop_al/chapter.rb +51 -0
- data/lib/coop_al/chapter_generator.rb +32 -0
- data/lib/coop_al/encounter.rb +148 -0
- data/lib/coop_al/encounter_generator.rb +67 -0
- data/lib/coop_al/exception.rb +7 -0
- data/lib/coop_al/item.rb +30 -0
- data/lib/coop_al/library.rb +54 -0
- data/lib/coop_al/library_generator.rb +4 -0
- data/lib/coop_al/loot.rb +49 -0
- data/lib/coop_al/loot_generator.rb +42 -0
- data/lib/coop_al/monster.rb +19 -0
- data/lib/coop_al/monster_definition.rb +18 -0
- data/lib/coop_al/path.rb +65 -0
- data/lib/coop_al/path_follower.rb +34 -0
- data/lib/coop_al/random_encounter.rb +30 -0
- data/lib/coop_al/random_encounter_generator.rb +37 -0
- data/lib/coop_al/session.rb +55 -0
- data/lib/coop_al/session_date_generator.rb +43 -0
- data/lib/coop_al/session_encounter.rb +76 -0
- data/lib/coop_al/session_log.rb +51 -0
- data/lib/coop_al/state.rb +85 -0
- data/lib/coop_al/state_reporter.rb +72 -0
- data/lib/coop_al/trace.rb +29 -0
- data/lib/coop_al/treasure.rb +21 -0
- data/lib/coop_al/treasure_tables.rb +28 -0
- data/lib/coop_al/value.rb +167 -0
- data/lib/coop_al/version.rb +3 -0
- data/lib/coop_al/xp.rb +86 -0
- data/lib/coop_al.rb +34 -0
- data/res/srd.rb +400 -0
- metadata +188 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Encounter
|
4
|
+
#
|
5
|
+
class Encounter
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name, parent)
|
9
|
+
@name = name
|
10
|
+
@parent = parent
|
11
|
+
@monsters = []
|
12
|
+
@xp = 0
|
13
|
+
@loot = Loot.empty
|
14
|
+
@sub_encounters = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def adventure_name
|
18
|
+
@parent.adventure_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def full_name
|
22
|
+
if @parent.nil?
|
23
|
+
@name
|
24
|
+
else
|
25
|
+
@parent.full_name + ' - ' + @name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_sub_encounter(encounter)
|
30
|
+
@sub_encounters << encounter
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_monster(monster)
|
34
|
+
@monsters << monster
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_treasure(treasure)
|
38
|
+
@loot.add_treasure(treasure)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_item(item)
|
42
|
+
@loot.add_item(item)
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_xp(amount)
|
46
|
+
@xp += amount
|
47
|
+
end
|
48
|
+
|
49
|
+
def run(state, log)
|
50
|
+
run_sub_encounters(state, log)
|
51
|
+
trace unless empty?
|
52
|
+
log.record_encounter(adventure_name, full_name, monster_names, total_xp, total_loot)
|
53
|
+
state.add_xp(total_xp)
|
54
|
+
state.add_loot(total_loot)
|
55
|
+
end
|
56
|
+
|
57
|
+
def empty?
|
58
|
+
@monsters.empty? && @xp.zero? && @loot.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def trace
|
64
|
+
Trace.instance.info("Encounter: #{full_name}")
|
65
|
+
Trace.instance.info("Fighting: #{all_monster_names}") unless @monsters.empty?
|
66
|
+
trace_xp
|
67
|
+
trace_loot
|
68
|
+
end
|
69
|
+
|
70
|
+
def trace_xp
|
71
|
+
Trace.instance.info("Monster XP: #{monster_xp}") unless @monsters.empty?
|
72
|
+
Trace.instance.info("Encounter XP: #{encounter_xp}") unless @xp.zero?
|
73
|
+
end
|
74
|
+
|
75
|
+
def trace_loot
|
76
|
+
trace_treasure
|
77
|
+
trace_items
|
78
|
+
end
|
79
|
+
|
80
|
+
def trace_treasure
|
81
|
+
Trace.instance.info("Encounter Treasure: #{encounter_treasure}") unless encounter_loot.treasures.empty?
|
82
|
+
Trace.instance.info("Monster Treasure: #{monster_treasure}") unless monster_loot.treasures.empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
def trace_items
|
86
|
+
Trace.instance.info("Encounter Items: #{encounter_items}") unless encounter_loot.items.empty?
|
87
|
+
Trace.instance.info("Monster Items: #{monster_items}") unless monster_loot.items.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
def all_monster_names
|
91
|
+
@monsters.map(&:id).join(', ')
|
92
|
+
end
|
93
|
+
|
94
|
+
def total_xp
|
95
|
+
encounter_xp + monster_xp
|
96
|
+
end
|
97
|
+
|
98
|
+
def encounter_xp
|
99
|
+
@xp
|
100
|
+
end
|
101
|
+
|
102
|
+
def monster_xp
|
103
|
+
@monsters.inject(0) do |total, monster|
|
104
|
+
total + monster.xp
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def total_loot
|
109
|
+
encounter_loot + monster_loot
|
110
|
+
end
|
111
|
+
|
112
|
+
def encounter_loot
|
113
|
+
@loot
|
114
|
+
end
|
115
|
+
|
116
|
+
def encounter_treasure
|
117
|
+
encounter_loot.treasures.map(&:to_s).join(', ')
|
118
|
+
end
|
119
|
+
|
120
|
+
def encounter_items
|
121
|
+
encounter_loot.items.map(&:to_s).join(', ')
|
122
|
+
end
|
123
|
+
|
124
|
+
def monster_loot
|
125
|
+
@monsters.inject(Loot.empty) do |loot, monster|
|
126
|
+
loot + monster.loot
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def monster_items
|
131
|
+
monster_loot.items.map(&:to_s).join(', ')
|
132
|
+
end
|
133
|
+
|
134
|
+
def monster_treasure
|
135
|
+
monster_loot.treasures.map(&:to_s).join(', ')
|
136
|
+
end
|
137
|
+
|
138
|
+
def monster_names
|
139
|
+
@monsters.map(&:to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
def run_sub_encounters(state, log)
|
143
|
+
@sub_encounters.each do |encounter|
|
144
|
+
encounter.run(state, log)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# EncounterGenerator
|
4
|
+
#
|
5
|
+
class EncounterGenerator
|
6
|
+
def initialize(name, parent, bestiary)
|
7
|
+
@bestiary = bestiary
|
8
|
+
@encounter = Encounter.new(name, parent)
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_encounter(&blk)
|
12
|
+
instance_eval(&blk)
|
13
|
+
@encounter
|
14
|
+
end
|
15
|
+
|
16
|
+
def monsters(count, id, treasure = :default)
|
17
|
+
expand_count(count).times do
|
18
|
+
@encounter.add_monster(@bestiary.create(id, treasure, @encounter))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def monster(id, treasure = :default)
|
23
|
+
monsters(1, id, treasure)
|
24
|
+
end
|
25
|
+
|
26
|
+
def treasure(value, description = nil)
|
27
|
+
@encounter.add_treasure(Treasure.new(value, description))
|
28
|
+
end
|
29
|
+
|
30
|
+
def items(count, description)
|
31
|
+
@encounter.add_item(Item.new(expand_count(count), description, @encounter))
|
32
|
+
end
|
33
|
+
|
34
|
+
def item(description)
|
35
|
+
items(1, description)
|
36
|
+
end
|
37
|
+
|
38
|
+
def encounter(name, &blk)
|
39
|
+
generator = EncounterGenerator.new(name, @encounter, @bestiary)
|
40
|
+
@encounter.add_sub_encounter(generator.generate_encounter(&blk))
|
41
|
+
end
|
42
|
+
|
43
|
+
def random(name, &blk)
|
44
|
+
generator = RandomEncounterGenerator.new(name, @encounter, @bestiary)
|
45
|
+
@encounter.add_sub_encounter(generator.generate_encounter(&blk))
|
46
|
+
end
|
47
|
+
|
48
|
+
def xp(amount)
|
49
|
+
@encounter.add_xp(amount)
|
50
|
+
end
|
51
|
+
|
52
|
+
def npc(cr)
|
53
|
+
xp(XpRewardTable.new[cr])
|
54
|
+
end
|
55
|
+
|
56
|
+
def npcs(count, cr)
|
57
|
+
xp(count * XpRewardTable.new[cr])
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def expand_count(count)
|
63
|
+
return roll_dice(count) if count.is_a?(String)
|
64
|
+
count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/coop_al/item.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Item
|
4
|
+
#
|
5
|
+
class Item
|
6
|
+
attr_reader :description
|
7
|
+
|
8
|
+
def initialize(count, description, encounter)
|
9
|
+
@count = count
|
10
|
+
@description = description
|
11
|
+
@encounter = encounter
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
if @count == 1
|
16
|
+
@description
|
17
|
+
else
|
18
|
+
"#{@description} (#{@count})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def description_with_origin
|
23
|
+
"#{self} (from #{origin})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def origin
|
27
|
+
@encounter.full_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module CoopAl
|
4
|
+
##
|
5
|
+
# Library
|
6
|
+
#
|
7
|
+
class Library
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@adventures = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
@adventures.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_adventure(adventure)
|
19
|
+
raise Exception, 'Duplicate adventure' if @adventures.key?(adventure.name)
|
20
|
+
@adventures[adventure.name] = adventure
|
21
|
+
end
|
22
|
+
|
23
|
+
def adventure?(name)
|
24
|
+
@adventures.key?(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def adventure(name)
|
28
|
+
@adventures[name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def path?(path)
|
32
|
+
raise Exception, "Cannot resolve relative path (#{path})" if path.relative?
|
33
|
+
return false unless @adventures.key?(path.adventure)
|
34
|
+
@adventures[path.adventure].chapter?(path.chapter)
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve(path)
|
38
|
+
raise Exception, "Adventure (#{path.adventure}) not found" unless @adventures.key?(path.adventure)
|
39
|
+
@adventures[path.adventure].chapter(path.chapter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def all_entries
|
43
|
+
@adventures.values.inject([]) { |a, e| a + e.all_entries }
|
44
|
+
end
|
45
|
+
|
46
|
+
def available_paths_from(path)
|
47
|
+
return all_entries if path.root?
|
48
|
+
current_chapter = resolve(path)
|
49
|
+
paths = current_chapter.links
|
50
|
+
paths << Path.root if current_chapter.links_to_downtime?
|
51
|
+
paths
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/coop_al/loot.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Loot
|
4
|
+
#
|
5
|
+
class Loot
|
6
|
+
attr_reader :treasures, :items
|
7
|
+
|
8
|
+
def initialize(treasures, items)
|
9
|
+
@treasures = treasures
|
10
|
+
@items = items
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
return false unless @treasures.empty?
|
15
|
+
return false unless @items.empty?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_treasure(treasure)
|
20
|
+
@treasures << treasure
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_item(item)
|
24
|
+
@items << item
|
25
|
+
end
|
26
|
+
|
27
|
+
def +(other)
|
28
|
+
@treasures += other.treasures
|
29
|
+
@items += other.items
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def treasure_value
|
34
|
+
@treasures.inject(Value.new) { |a, e| a + e.value }
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.empty
|
38
|
+
Loot.new([], [])
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_treasure(treasure)
|
42
|
+
Loot.new([treasure], [])
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.from_item(item)
|
46
|
+
Loot.new([], [item])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# LootGenerator
|
4
|
+
#
|
5
|
+
class LootGenerator
|
6
|
+
def initialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate(cr, treasure)
|
10
|
+
return Loot.empty if treasure == :no_treasure
|
11
|
+
return generate_individual(cr) if treasure == :individual
|
12
|
+
generate_hoard(cr)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def generate_individual(cr)
|
18
|
+
table_name = individual_treasure_table_by_cr(cr)
|
19
|
+
treasure = Treasure.new(roll_on(table_name))
|
20
|
+
Loot.from_treasure(treasure)
|
21
|
+
end
|
22
|
+
|
23
|
+
def individual_treasure_table_by_cr(cr)
|
24
|
+
value = cr_value(cr)
|
25
|
+
return :individual_treasure_cr_0_4 if value <= 4
|
26
|
+
return :individual_treasure_cr_5_10 if value <= 10
|
27
|
+
return :individual_treasure_cr_11_16 if value <= 16
|
28
|
+
:individual_treasure_cr_17_
|
29
|
+
end
|
30
|
+
|
31
|
+
def cr_value(cr)
|
32
|
+
return 0.125 if cr == :cr1_8
|
33
|
+
return 0.25 if cr == :cr1_4
|
34
|
+
return 0.5 if cr == :cr1_2
|
35
|
+
cr[2..-1].to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_hoard(_cr)
|
39
|
+
raise 'Hoard loot not implemented'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Monster
|
4
|
+
#
|
5
|
+
class Monster
|
6
|
+
attr_reader :id, :xp, :loot, :encounter
|
7
|
+
|
8
|
+
def initialize(id, xp, loot, encounter)
|
9
|
+
@id = id
|
10
|
+
@xp = xp
|
11
|
+
@loot = loot
|
12
|
+
@encounter = encounter
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@id.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# MonsterDefinition
|
4
|
+
#
|
5
|
+
class MonsterDefinition
|
6
|
+
attr_reader :id, :cr, :treasure
|
7
|
+
|
8
|
+
def initialize(id, cr, treasure)
|
9
|
+
@id = id
|
10
|
+
@cr = cr
|
11
|
+
@treasure = treasure
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
@id == other.id && @cr == other.cr && @treasure == other.treasure
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/coop_al/path.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Path
|
4
|
+
#
|
5
|
+
class Path
|
6
|
+
attr_reader :adventure, :chapter
|
7
|
+
|
8
|
+
def initialize(adventure, chapter)
|
9
|
+
@adventure = adventure
|
10
|
+
@chapter = chapter
|
11
|
+
end
|
12
|
+
|
13
|
+
def root?
|
14
|
+
@adventure.nil? && @chapter.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def relative?
|
18
|
+
@adventure.nil? && !@chapter.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def absolute?
|
22
|
+
!relative?
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.absolute(adventure, chapter)
|
26
|
+
Path.new(adventure, chapter)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.relative(chapter)
|
30
|
+
Path.new(nil, chapter)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.root
|
34
|
+
Path.new(nil, nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse(path)
|
38
|
+
tokens = path.split('/').map(&:to_sym)
|
39
|
+
raise "Invalid path #{path}" if tokens.count > 2
|
40
|
+
return absolute(tokens[0], tokens[1]) if tokens.count == 2
|
41
|
+
return root if tokens[0] == :downtime
|
42
|
+
relative(tokens[0])
|
43
|
+
end
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
to_s == other.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def +(other)
|
50
|
+
local_path = other.is_a?(Path) ? other : Path.parse(other)
|
51
|
+
return local_path if local_path.absolute?
|
52
|
+
raise 'Cannot add two relative paths' if relative?
|
53
|
+
Path.absolute(@adventure, other.chapter)
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
adventure_s + @chapter.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def adventure_s
|
61
|
+
return @adventure.to_s + '/' if absolute?
|
62
|
+
''
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# PathFollower
|
4
|
+
#
|
5
|
+
class PathFollower
|
6
|
+
attr_reader :state
|
7
|
+
|
8
|
+
def initialize(library, starting_state)
|
9
|
+
@library = library
|
10
|
+
@state = starting_state
|
11
|
+
end
|
12
|
+
|
13
|
+
def follow(paths, log)
|
14
|
+
paths.each do |path|
|
15
|
+
follow_path(path, log)
|
16
|
+
end
|
17
|
+
@state
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def follow_path(path, log)
|
23
|
+
if path.root?
|
24
|
+
log.record_downtime(@library.resolve(@state.current_path).adventure_name)
|
25
|
+
@state.apply_path(path)
|
26
|
+
else
|
27
|
+
raise Exception, "#{path} not a valid next path" unless @library.path?(@state.current_path + path)
|
28
|
+
raise Exception, "Cannot repeat path (#{path})" if @state.history_includes?(@state.current_path + path)
|
29
|
+
@state.apply_path(path)
|
30
|
+
@library.resolve(@state.current_path).follow(@state, log)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# RandomEncounter
|
4
|
+
#
|
5
|
+
class RandomEncounter
|
6
|
+
def initialize(name, parent)
|
7
|
+
@name = name
|
8
|
+
@parent = parent
|
9
|
+
@entries = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_entry(entry)
|
13
|
+
@entries << entry
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_entry(roll, entry)
|
17
|
+
@entries[roll - 1] = entry
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_range(range, entry)
|
21
|
+
range.each do |i|
|
22
|
+
set_entry(i, entry)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(state, log)
|
27
|
+
@entries[roll_dice("d#{@entries.count}") - 1].run(state, log)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# RandomEncounterGenerator
|
4
|
+
#
|
5
|
+
class RandomEncounterGenerator
|
6
|
+
def initialize(name, parent, bestiary)
|
7
|
+
@name = name
|
8
|
+
@parent = parent
|
9
|
+
@bestiary = bestiary
|
10
|
+
@encounter = RandomEncounter.new(name, parent)
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_encounter(&blk)
|
14
|
+
instance_eval(&blk)
|
15
|
+
@encounter
|
16
|
+
end
|
17
|
+
|
18
|
+
def fixed(*args, &blk)
|
19
|
+
generator = EncounterGenerator.new(@name, @parent, @bestiary)
|
20
|
+
entry = generator.generate_encounter(&blk)
|
21
|
+
if args.empty?
|
22
|
+
@encounter.add_entry(entry)
|
23
|
+
else
|
24
|
+
roll = args.shift
|
25
|
+
if roll.is_a?(Integer)
|
26
|
+
@encounter.set_entry(roll, entry)
|
27
|
+
elsif roll.is_a?(Range)
|
28
|
+
@encounter.set_range(roll, entry)
|
29
|
+
else
|
30
|
+
raise "Inappropriate roll descriptor (#{roll})"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
alias f fixed
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CoopAl
|
2
|
+
##
|
3
|
+
# Session
|
4
|
+
#
|
5
|
+
class Session
|
6
|
+
def initialize(number, date_generator, adventure_name, dm_name, starting_xp, starting_treasure, encounter_count)
|
7
|
+
@number = number
|
8
|
+
@date_generator = date_generator
|
9
|
+
@adventure_name = adventure_name
|
10
|
+
@dm_name = dm_name
|
11
|
+
@starting_xp = starting_xp
|
12
|
+
@starting_treasure = starting_treasure
|
13
|
+
@encounter_count = encounter_count
|
14
|
+
@encounters = []
|
15
|
+
|
16
|
+
@date_generator.add_session
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_encounter(encounter)
|
20
|
+
@encounters << encounter
|
21
|
+
end
|
22
|
+
|
23
|
+
def done?
|
24
|
+
@encounters.count { |e| e.counts? } == @encounter_count
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump(s)
|
28
|
+
s.puts "Adventure: #{@adventure_name}"
|
29
|
+
s.puts "Session ##{@number}: #{@date_generator.session(@number)}"
|
30
|
+
s.puts "DM: #{@dm_name}"
|
31
|
+
s.puts "Starting XP: #{@starting_xp} (level #{level(@starting_xp)})"
|
32
|
+
s.puts "XP Earned: #{xp_earned}"
|
33
|
+
s.puts "XP Total: #{@starting_xp + xp_earned} (level #{level(@starting_xp + xp_earned)})"
|
34
|
+
s.puts "Starting Treasure: #{@starting_treasure}"
|
35
|
+
s.puts "Treasure +/-: #{treasure_earned}"
|
36
|
+
s.puts "Treasure Total: #{@starting_treasure + treasure_earned}"
|
37
|
+
@encounters.each { |e| e.dump(s) if e.counts? }
|
38
|
+
s.puts
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def xp_earned
|
44
|
+
@encounters.inject(0) { |a, e| a + e.xp }
|
45
|
+
end
|
46
|
+
|
47
|
+
def treasure_earned
|
48
|
+
@encounters.inject(Value.new) { |a, e| a + e.treasure }
|
49
|
+
end
|
50
|
+
|
51
|
+
def level(xp)
|
52
|
+
XpRequirementTable.new.level_from_xp(xp)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|