amar-rpg 2.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/LICENSE +675 -0
- data/README.md +155 -0
- data/amar-tui.rb +8195 -0
- data/cli_enc_output.rb +87 -0
- data/cli_enc_output_new.rb +433 -0
- data/cli_enc_output_new_3tier.rb +198 -0
- data/cli_enc_output_new_compact.rb +238 -0
- data/cli_name_gen.rb +21 -0
- data/cli_npc_output.rb +279 -0
- data/cli_npc_output_new.rb +700 -0
- data/cli_town_output.rb +39 -0
- data/cli_weather_output.rb +36 -0
- data/includes/class_enc.rb +341 -0
- data/includes/class_enc_new.rb +512 -0
- data/includes/class_monster_new.rb +551 -0
- data/includes/class_npc.rb +1378 -0
- data/includes/class_npc_new.rb +1187 -0
- data/includes/class_npc_new.rb.backup +706 -0
- data/includes/class_npc_new_skills.rb +153 -0
- data/includes/class_town.rb +237 -0
- data/includes/d6s.rb +40 -0
- data/includes/equipment_tables.rb +120 -0
- data/includes/functions.rb +67 -0
- data/includes/includes.rb +30 -0
- data/includes/randomizer.rb +15 -0
- data/includes/spell_catalog.rb +441 -0
- data/includes/tables/armour.rb +13 -0
- data/includes/tables/chartype.rb +4412 -0
- data/includes/tables/chartype_new.rb +765 -0
- data/includes/tables/chartype_new_full.rb +2713 -0
- data/includes/tables/enc_specific.rb +168 -0
- data/includes/tables/enc_type.rb +17 -0
- data/includes/tables/encounters.rb +99 -0
- data/includes/tables/magick.rb +169 -0
- data/includes/tables/melee.rb +36 -0
- data/includes/tables/missile.rb +17 -0
- data/includes/tables/monster_stats_new.rb +264 -0
- data/includes/tables/month.rb +18 -0
- data/includes/tables/names.rb +21 -0
- data/includes/tables/personality.rb +12 -0
- data/includes/tables/race_templates.rb +318 -0
- data/includes/tables/religions.rb +266 -0
- data/includes/tables/spells_new.rb +496 -0
- data/includes/tables/tier_system.rb +104 -0
- data/includes/tables/town.rb +71 -0
- data/includes/tables/weather.rb +41 -0
- data/includes/town_relations.rb +127 -0
- data/includes/weather.rb +108 -0
- data/includes/weather2latex.rb +114 -0
- data/lib/rcurses.rb +33 -0
- metadata +157 -0
@@ -0,0 +1,551 @@
|
|
1
|
+
# Monster/Animal NPC class for the new 3-tier system
|
2
|
+
# This creates simplified NPCs for non-humanoid encounters
|
3
|
+
|
4
|
+
class MonsterNew
|
5
|
+
attr_accessor :name, :type, :level, :sex, :description, :weight
|
6
|
+
attr_accessor :SIZE, :BP, :DB, :MD, :ENC
|
7
|
+
attr_accessor :tiers, :armor, :special_abilities, :spells
|
8
|
+
|
9
|
+
def initialize(monster_type, level = 1)
|
10
|
+
@type = monster_type
|
11
|
+
@level = level
|
12
|
+
@sex = ["M", "F"].sample # Even monsters have gender sometimes
|
13
|
+
|
14
|
+
# Load monster stats if not loaded
|
15
|
+
unless defined?($MonsterStats)
|
16
|
+
load File.join($pgmdir, "includes/tables/monster_stats_new.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get stats template
|
20
|
+
stats = get_monster_stats(@type)
|
21
|
+
|
22
|
+
# Set name based on type
|
23
|
+
@name = @type.gsub(/Monster:|Animal:|Small |Large /, "").strip.capitalize
|
24
|
+
@description = stats["description"]
|
25
|
+
@special_abilities = stats["special"]
|
26
|
+
|
27
|
+
# Generate weight and SIZE
|
28
|
+
if stats["weight_range"]
|
29
|
+
# New system: use weight range and calculate SIZE
|
30
|
+
@weight = rand(stats["weight_range"][0]..stats["weight_range"][1])
|
31
|
+
@SIZE = calculate_size_from_weight(@weight)
|
32
|
+
elsif stats["size_range"]
|
33
|
+
# Legacy system: convert old SIZE to approximate weight
|
34
|
+
old_size = rand(stats["size_range"][0]..stats["size_range"][1])
|
35
|
+
# Convert old SIZE to weight (rough approximation)
|
36
|
+
@weight = size_to_weight(old_size)
|
37
|
+
@SIZE = calculate_size_from_weight(@weight)
|
38
|
+
else
|
39
|
+
# Default human-like range centered around 70kg
|
40
|
+
# Using a more realistic distribution
|
41
|
+
base_weight = 60
|
42
|
+
variation = rand(-10..20) # Slightly skewed toward heavier
|
43
|
+
@weight = base_weight + variation + rand(10)
|
44
|
+
@weight = [@weight, 40].max # Minimum weight
|
45
|
+
@SIZE = calculate_size_from_weight(@weight)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initialize tiers with simplified structure for monsters
|
49
|
+
@tiers = {
|
50
|
+
"BODY" => {
|
51
|
+
"level" => stats["base_body"] + (@level / 2),
|
52
|
+
"Strength" => { "level" => stats["base_body"] + rand(0..1) },
|
53
|
+
"Endurance" => { "level" => stats["base_body"] + rand(0..1) },
|
54
|
+
"Melee Combat" => {
|
55
|
+
"level" => stats["base_body"],
|
56
|
+
"skills" => generate_combat_skills(stats["skills"])
|
57
|
+
},
|
58
|
+
"Missile Combat" => {
|
59
|
+
"level" => 0,
|
60
|
+
"skills" => {}
|
61
|
+
},
|
62
|
+
"Athletics" => {
|
63
|
+
"level" => stats["base_body"] + rand(-1..1),
|
64
|
+
"skills" => generate_athletics_skills(stats)
|
65
|
+
}
|
66
|
+
},
|
67
|
+
"MIND" => {
|
68
|
+
"level" => stats["base_mind"] + (@level / 3),
|
69
|
+
"Intelligence" => { "level" => stats["base_mind"] },
|
70
|
+
"Awareness" => {
|
71
|
+
"level" => stats["base_mind"] + rand(0..1),
|
72
|
+
"skills" => generate_awareness_skills(stats)
|
73
|
+
}
|
74
|
+
},
|
75
|
+
"SPIRIT" => {
|
76
|
+
"level" => stats["base_spirit"],
|
77
|
+
"Casting" => { "level" => stats["base_spirit"] > 0 ? rand(1..3) : 0 },
|
78
|
+
"Attunement" => { "level" => stats["base_spirit"] > 0 ? rand(1..2) : 0, "skills" => {} }
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
# Add magical abilities ONLY for truly magical creatures (not animals)
|
83
|
+
if stats["base_spirit"] > 0 && !@type.match?(/animal:/i)
|
84
|
+
# Dragons get Earth magic, other magical creatures get varied
|
85
|
+
if @type =~ /dragon/i
|
86
|
+
@tiers["SPIRIT"]["Attunement"]["skills"]["Earth"] = @level + rand(2..4)
|
87
|
+
elsif @type =~ /drake/i
|
88
|
+
@tiers["SPIRIT"]["Attunement"]["skills"]["Fire"] = @level + rand(1..3)
|
89
|
+
elsif @type =~ /elemental/i
|
90
|
+
element = ["Fire", "Water", "Air", "Earth"].sample
|
91
|
+
@tiers["SPIRIT"]["Attunement"]["skills"][element] = @level + rand(2..4)
|
92
|
+
elsif @type =~ /undead|zombie|skeleton|lich/i
|
93
|
+
@tiers["SPIRIT"]["Attunement"]["skills"]["Death"] = @level + rand(1..3)
|
94
|
+
elsif @type =~ /vampire|werewolf|faerie|wyvern/i
|
95
|
+
# Specific magical monsters
|
96
|
+
domain = ["Fire", "Water", "Air", "Earth", "Life", "Death", "Mind"].sample
|
97
|
+
@tiers["SPIRIT"]["Attunement"]["skills"][domain] = @level + rand(1..2)
|
98
|
+
end
|
99
|
+
# Do NOT give magic to generic creatures or animals
|
100
|
+
end
|
101
|
+
|
102
|
+
# Calculate derived stats
|
103
|
+
calculate_derived_stats
|
104
|
+
|
105
|
+
# Generate spells for magical creatures (not animals)
|
106
|
+
generate_spells if stats["base_spirit"] > 0 && !@type.match?(/animal:/i)
|
107
|
+
|
108
|
+
# Set armor
|
109
|
+
set_armor(stats["armor"])
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def size_to_weight(old_size)
|
115
|
+
# Convert old SIZE system to approximate weight for backward compatibility
|
116
|
+
# Old system: SIZE 8-10 for humans, SIZE 15-20 for dragons
|
117
|
+
case old_size
|
118
|
+
when 0..1 then rand(5..15)
|
119
|
+
when 2 then rand(15..30)
|
120
|
+
when 3 then rand(30..60)
|
121
|
+
when 4 then rand(60..90)
|
122
|
+
when 5 then rand(90..135)
|
123
|
+
when 6 then rand(135..180)
|
124
|
+
when 7 then rand(180..240)
|
125
|
+
when 8 then rand(240..300)
|
126
|
+
when 9 then rand(300..375)
|
127
|
+
when 10 then rand(375..450)
|
128
|
+
when 11 then rand(450..525)
|
129
|
+
when 12 then rand(525..600)
|
130
|
+
when 13 then rand(600..700)
|
131
|
+
when 14 then rand(700..800)
|
132
|
+
when 15 then rand(800..900)
|
133
|
+
when 16 then rand(900..1000)
|
134
|
+
when 17 then rand(1000..1200)
|
135
|
+
when 18 then rand(1200..1400)
|
136
|
+
when 19 then rand(1400..1600)
|
137
|
+
when 20 then rand(1600..1800)
|
138
|
+
else old_size * 90 # Rough approximation for very large creatures
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def calculate_size_from_weight(weight)
|
143
|
+
# SIZE system based on weight with half-sizes - matches NPC class
|
144
|
+
case weight
|
145
|
+
when 0...10 then 0.5
|
146
|
+
when 10...15 then 1
|
147
|
+
when 15...20 then 1.5
|
148
|
+
when 20...35 then 2
|
149
|
+
when 35...50 then 2.5
|
150
|
+
when 50...75 then 3
|
151
|
+
when 75...100 then 3.5
|
152
|
+
when 100...125 then 4
|
153
|
+
when 125...150 then 4.5
|
154
|
+
when 150...188 then 5
|
155
|
+
when 188...225 then 5.5
|
156
|
+
when 225...263 then 6
|
157
|
+
when 263...300 then 6.5
|
158
|
+
when 300...350 then 7
|
159
|
+
when 350...400 then 7.5
|
160
|
+
when 400...450 then 8
|
161
|
+
when 450...500 then 8.5
|
162
|
+
when 500...550 then 9
|
163
|
+
when 550...600 then 9.5
|
164
|
+
when 600...663 then 10
|
165
|
+
when 663...725 then 10.5
|
166
|
+
when 725...788 then 11
|
167
|
+
when 788...850 then 11.5
|
168
|
+
when 850...925 then 12
|
169
|
+
when 925...1000 then 12.5
|
170
|
+
when 1000...1075 then 13
|
171
|
+
when 1075...1150 then 13.5
|
172
|
+
when 1150...1225 then 14
|
173
|
+
when 1225...1300 then 14.5
|
174
|
+
when 1300...1375 then 15
|
175
|
+
when 1375...1450 then 15.5
|
176
|
+
when 1450...1525 then 16
|
177
|
+
when 1525...1600 then 16.5
|
178
|
+
else
|
179
|
+
# For very large creatures, add 0.5 per 100kg
|
180
|
+
16.5 + ((weight - 1600) / 100.0).floor * 0.5
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def generate_combat_skills(skill_list)
|
185
|
+
skills = {}
|
186
|
+
|
187
|
+
skill_list.each do |skill_name|
|
188
|
+
case skill_name.downcase
|
189
|
+
when /bite/
|
190
|
+
skills["Bite"] = @level + rand(2..4) # Natural attacks are effective
|
191
|
+
when /claw/
|
192
|
+
skills["Claw"] = @level + rand(2..4)
|
193
|
+
when /tusk/
|
194
|
+
skills["Tusk"] = @level + rand(1..3)
|
195
|
+
when /tail/
|
196
|
+
skills["Tail"] = @level + rand(1..3)
|
197
|
+
when /sword/
|
198
|
+
skills["Sword"] = @level + rand(1..3)
|
199
|
+
when /spear/
|
200
|
+
skills["Spear"] = @level + rand(1..3)
|
201
|
+
when /dagger/
|
202
|
+
skills["Dagger"] = @level + rand(0..2)
|
203
|
+
when /club/
|
204
|
+
skills["Club"] = @level + rand(0..2)
|
205
|
+
when /unarmed/
|
206
|
+
skills["Unarmed"] = @level + rand(2..4)
|
207
|
+
when /grappl/
|
208
|
+
skills["Grappling"] = @level + rand(1..3)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Ensure at least one combat skill with a reasonable level
|
213
|
+
if skills.empty?
|
214
|
+
skills["Unarmed"] = @level + rand(2..3)
|
215
|
+
end
|
216
|
+
|
217
|
+
skills
|
218
|
+
end
|
219
|
+
|
220
|
+
def generate_athletics_skills(stats)
|
221
|
+
skills = {}
|
222
|
+
|
223
|
+
# Base athletics skills appropriate for all creatures
|
224
|
+
skills["Dodge"] = @level + rand(0..2)
|
225
|
+
|
226
|
+
# Movement skills based on creature type
|
227
|
+
case stats["type"]
|
228
|
+
when "Undead"
|
229
|
+
# Undead don't swim or climb well, but can move quietly
|
230
|
+
skills["Move quietly"] = @level + rand(0..2)
|
231
|
+
skills["Hide"] = @level + rand(-1..1)
|
232
|
+
# Skeletons and zombies are slow
|
233
|
+
if @name.downcase.include?("skeleton") || @name.downcase.include?("zombie")
|
234
|
+
skills["Running"] = rand(0..1) # Very slow
|
235
|
+
else
|
236
|
+
skills["Running"] = @level + rand(-1..1)
|
237
|
+
end
|
238
|
+
when "Animal"
|
239
|
+
# Animals have natural athletics
|
240
|
+
skills["Running"] = @level + rand(0..2)
|
241
|
+
skills["Jumping"] = @level + rand(0..1)
|
242
|
+
# Aquatic animals can swim
|
243
|
+
if @name.downcase.include?("fish") || @name.downcase.include?("shark")
|
244
|
+
skills["Swimming"] = @level + rand(2..4)
|
245
|
+
elsif @name.downcase.include?("bear") || @name.downcase.include?("dog")
|
246
|
+
skills["Swimming"] = rand(1..2)
|
247
|
+
end
|
248
|
+
# Climbing for appropriate animals
|
249
|
+
if @name.downcase.include?("bear") || @name.downcase.include?("monkey")
|
250
|
+
skills["Climbing"] = @level + rand(1..3)
|
251
|
+
end
|
252
|
+
# Stealth for predators
|
253
|
+
if stats["skills"].any? { |s| s.downcase.include?("stealth") || s.downcase.include?("ambush") }
|
254
|
+
skills["Move quietly"] = @level + rand(1..3)
|
255
|
+
skills["Hide"] = @level + rand(0..2)
|
256
|
+
end
|
257
|
+
when "Monster"
|
258
|
+
# Most monsters have standard athletics
|
259
|
+
skills["Running"] = @level + rand(-1..2)
|
260
|
+
# Flying creatures
|
261
|
+
if stats["skills"].any? { |s| s.downcase.include?("flight") || s.downcase.include?("fly") }
|
262
|
+
skills["Flying"] = @level + rand(2..4)
|
263
|
+
end
|
264
|
+
# Stealthy monsters
|
265
|
+
if @name.downcase.include?("goblin") || stats["skills"].include?("Stealth")
|
266
|
+
skills["Move quietly"] = @level + rand(1..3)
|
267
|
+
skills["Hide"] = @level + rand(1..3)
|
268
|
+
end
|
269
|
+
# Large monsters don't hide well
|
270
|
+
if @weight > 500
|
271
|
+
skills["Hide"] = rand(0..1) if skills["Hide"]
|
272
|
+
skills["Move quietly"] = rand(0..1) if skills["Move quietly"]
|
273
|
+
end
|
274
|
+
else
|
275
|
+
# Default fallback
|
276
|
+
skills["Running"] = @level + rand(-1..1)
|
277
|
+
end
|
278
|
+
|
279
|
+
# Remove any skills with 0 or negative values
|
280
|
+
skills.delete_if { |_, v| v <= 0 }
|
281
|
+
|
282
|
+
skills
|
283
|
+
end
|
284
|
+
|
285
|
+
def calculate_derived_stats
|
286
|
+
body = @tiers["BODY"]["level"]
|
287
|
+
mind = @tiers["MIND"]["level"]
|
288
|
+
spirit = @tiers["SPIRIT"]["level"]
|
289
|
+
|
290
|
+
# Body Points: SIZE * 2 + Fortitude / 3 (same as NPC class)
|
291
|
+
fortitude = get_skill_total("BODY", "Endurance", "Fortitude")
|
292
|
+
@BP = (@SIZE * 2 + fortitude / 3.0).round
|
293
|
+
|
294
|
+
# Damage Bonus: (SIZE + Wield weapon) / 3 (same as NPC class)
|
295
|
+
wield_weapon = get_skill_total("BODY", "Strength", "Wield weapon")
|
296
|
+
@DB = ((@SIZE + wield_weapon) / 3.0).round
|
297
|
+
|
298
|
+
# Magic Defense: (Mental fortitude + Attunement/Self) / 3 (same as NPC class)
|
299
|
+
mental_fortitude = get_skill_total("MIND", "Learning", "Mental fortitude")
|
300
|
+
attunement_self = get_skill_total("SPIRIT", "Magic", "Attunement/Self")
|
301
|
+
@MD = ((mental_fortitude + attunement_self) / 3.0).round
|
302
|
+
|
303
|
+
# Encumbrance (monsters don't carry much)
|
304
|
+
@ENC = 0
|
305
|
+
end
|
306
|
+
|
307
|
+
def set_armor(armor_desc)
|
308
|
+
return unless armor_desc
|
309
|
+
|
310
|
+
if armor_desc =~ /Natural \((\d+) AP\)/
|
311
|
+
@armor = {
|
312
|
+
name: "Natural armor",
|
313
|
+
ap: $1.to_i,
|
314
|
+
enc: 0
|
315
|
+
}
|
316
|
+
else
|
317
|
+
@armor = {
|
318
|
+
name: armor_desc,
|
319
|
+
ap: 1,
|
320
|
+
enc: 0
|
321
|
+
}
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def generate_spells
|
326
|
+
# Load spell tables if not loaded
|
327
|
+
unless defined?($SpellDatabase)
|
328
|
+
load File.join($pgmdir, "includes/tables/spells_new.rb")
|
329
|
+
end
|
330
|
+
|
331
|
+
@spells = []
|
332
|
+
|
333
|
+
# Determine primary domain
|
334
|
+
domain = nil
|
335
|
+
highest_skill = 0
|
336
|
+
|
337
|
+
if @tiers["SPIRIT"]["Attunement"]["skills"]
|
338
|
+
@tiers["SPIRIT"]["Attunement"]["skills"].each do |dom, skill|
|
339
|
+
if skill > highest_skill
|
340
|
+
domain = dom
|
341
|
+
highest_skill = skill
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
return unless domain
|
347
|
+
|
348
|
+
# Calculate spell count based on level and domain skill
|
349
|
+
spell_count = (@level + highest_skill) / 2
|
350
|
+
spell_count = [spell_count, 1].max
|
351
|
+
spell_count = [spell_count, 20].min # Cap at 20 spells
|
352
|
+
|
353
|
+
# Dragons get more spells
|
354
|
+
spell_count = (@level * 2) if @type =~ /dragon/i
|
355
|
+
spell_count = [spell_count, 30].min if @type =~ /dragon/i
|
356
|
+
|
357
|
+
# Get spells from the appropriate domain
|
358
|
+
available_spells = []
|
359
|
+
|
360
|
+
# Filter spells by domain from $SpellDatabase
|
361
|
+
$SpellDatabase.each do |spell_name, spell_data|
|
362
|
+
if spell_data["domain"] == domain
|
363
|
+
available_spells << {
|
364
|
+
'name' => spell_name,
|
365
|
+
'duration' => spell_data["duration"],
|
366
|
+
'range' => spell_data["distance"],
|
367
|
+
'area' => spell_data["aoe"]
|
368
|
+
}
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# If no spells found for domain, use some generic ones
|
373
|
+
if available_spells.empty?
|
374
|
+
# Create some basic spells for the domain
|
375
|
+
case domain
|
376
|
+
when "Earth"
|
377
|
+
available_spells = [
|
378
|
+
{'name' => "Stone Skin", 'duration' => "1 hour", 'range' => "Touch", 'area' => "Single"},
|
379
|
+
{'name' => "Earth Spike", 'duration' => "Instant", 'range' => "20m", 'area' => "Single"},
|
380
|
+
{'name' => "Tremor", 'duration' => "Instant", 'range' => "50m", 'area' => "10m radius"},
|
381
|
+
{'name' => "Stone Wall", 'duration' => "Permanent", 'range' => "10m", 'area' => "Wall 5m"},
|
382
|
+
{'name' => "Earth Bind", 'duration' => "1 minute", 'range' => "30m", 'area' => "Single"},
|
383
|
+
{'name' => "Quake", 'duration' => "Instant", 'range' => "100m", 'area' => "20m radius"}
|
384
|
+
]
|
385
|
+
when "Fire"
|
386
|
+
available_spells = [
|
387
|
+
{'name' => "Flame Bolt", 'duration' => "Instant", 'range' => "30m", 'area' => "Single"},
|
388
|
+
{'name' => "Fire Shield", 'duration' => "10 minutes", 'range' => "Self", 'area' => "Self"},
|
389
|
+
{'name' => "Fireball", 'duration' => "Instant", 'range' => "50m", 'area' => "5m radius"}
|
390
|
+
]
|
391
|
+
when "Water"
|
392
|
+
available_spells = [
|
393
|
+
{'name' => "Ice Bolt", 'duration' => "Instant", 'range' => "30m", 'area' => "Single"},
|
394
|
+
{'name' => "Water Walk", 'duration' => "1 hour", 'range' => "Touch", 'area' => "Single"},
|
395
|
+
{'name' => "Freeze", 'duration' => "1 minute", 'range' => "20m", 'area' => "Single"}
|
396
|
+
]
|
397
|
+
when "Air"
|
398
|
+
available_spells = [
|
399
|
+
{'name' => "Lightning", 'duration' => "Instant", 'range' => "50m", 'area' => "Single"},
|
400
|
+
{'name' => "Wind Walk", 'duration' => "10 minutes", 'range' => "Touch", 'area' => "Single"},
|
401
|
+
{'name' => "Storm", 'duration' => "10 minutes", 'range' => "100m", 'area' => "50m radius"}
|
402
|
+
]
|
403
|
+
when "Death"
|
404
|
+
available_spells = [
|
405
|
+
{'name' => "Drain Life", 'duration' => "Instant", 'range' => "Touch", 'area' => "Single"},
|
406
|
+
{'name' => "Animate Dead", 'duration' => "1 hour", 'range' => "10m", 'area' => "Corpse"},
|
407
|
+
{'name' => "Death Touch", 'duration' => "Instant", 'range' => "Touch", 'area' => "Single"}
|
408
|
+
]
|
409
|
+
else
|
410
|
+
available_spells = [
|
411
|
+
{'name' => "Magic Bolt", 'duration' => "Instant", 'range' => "30m", 'area' => "Single"}
|
412
|
+
]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Select random spells
|
417
|
+
selected_spells = available_spells.sample([spell_count, available_spells.length].min)
|
418
|
+
|
419
|
+
selected_spells.each do |spell|
|
420
|
+
@spells << {
|
421
|
+
'name' => spell['name'],
|
422
|
+
'domain' => domain,
|
423
|
+
'duration' => spell['duration'] || "Instant",
|
424
|
+
'range' => spell['range'] || "Touch",
|
425
|
+
'area' => spell['area'] || "Target"
|
426
|
+
}
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
public
|
431
|
+
|
432
|
+
# Compatibility methods for encounter system
|
433
|
+
def get_characteristic(char_name)
|
434
|
+
@tiers[char_name]["level"] || 0
|
435
|
+
end
|
436
|
+
|
437
|
+
def get_attribute(char_name, attr_name)
|
438
|
+
return 0 unless @tiers[char_name] && @tiers[char_name][attr_name]
|
439
|
+
@tiers[char_name][attr_name]["level"] || 0
|
440
|
+
end
|
441
|
+
|
442
|
+
def get_skill(char_name, attr_name, skill_name)
|
443
|
+
return 0 unless @tiers[char_name] && @tiers[char_name][attr_name]
|
444
|
+
return 0 unless @tiers[char_name][attr_name]["skills"]
|
445
|
+
@tiers[char_name][attr_name]["skills"][skill_name] || 0
|
446
|
+
end
|
447
|
+
|
448
|
+
|
449
|
+
def age
|
450
|
+
"Unknown"
|
451
|
+
end
|
452
|
+
|
453
|
+
def height
|
454
|
+
@SIZE * 20 # Rough estimate
|
455
|
+
end
|
456
|
+
|
457
|
+
def weight
|
458
|
+
@SIZE * 10 # Rough estimate
|
459
|
+
end
|
460
|
+
|
461
|
+
def area
|
462
|
+
"Wild"
|
463
|
+
end
|
464
|
+
|
465
|
+
def has_magic?
|
466
|
+
# Check if monster has any casting ability
|
467
|
+
return false unless @tiers["SPIRIT"] && @tiers["SPIRIT"]["Casting"]
|
468
|
+
casting_level = @tiers["SPIRIT"]["Casting"]["level"] || 0
|
469
|
+
casting_level > 0
|
470
|
+
end
|
471
|
+
|
472
|
+
def get_skill_total(char_name, attr_name, skill_name)
|
473
|
+
# Calculate total: Characteristic + Attribute + Skill
|
474
|
+
char_level = get_characteristic(char_name)
|
475
|
+
attr_level = get_attribute(char_name, attr_name)
|
476
|
+
skill_level = get_skill(char_name, attr_name, skill_name)
|
477
|
+
|
478
|
+
char_level + attr_level + skill_level
|
479
|
+
end
|
480
|
+
|
481
|
+
# Additional methods for compatibility with detailed views
|
482
|
+
def profession
|
483
|
+
@type.to_s.gsub(/Monster:|Animal:/, "")
|
484
|
+
end
|
485
|
+
|
486
|
+
def cult
|
487
|
+
nil # Monsters don't have religions
|
488
|
+
end
|
489
|
+
|
490
|
+
def social_status
|
491
|
+
"Wild" # Monsters are outside society
|
492
|
+
end
|
493
|
+
|
494
|
+
def money
|
495
|
+
0 # Monsters don't carry money
|
496
|
+
end
|
497
|
+
|
498
|
+
def equipment
|
499
|
+
[] # Monsters don't have equipment
|
500
|
+
end
|
501
|
+
|
502
|
+
def generate_awareness_skills(stats)
|
503
|
+
skills = {}
|
504
|
+
|
505
|
+
# Base awareness skills
|
506
|
+
skills["Reaction speed"] = @level + rand(0..2)
|
507
|
+
skills["Alertness"] = @level + rand(0..2)
|
508
|
+
|
509
|
+
case stats["type"]
|
510
|
+
when "Undead"
|
511
|
+
# Undead have poor awareness but don't get surprised
|
512
|
+
skills["Alertness"] = rand(1..2)
|
513
|
+
skills["Search"] = rand(0..1)
|
514
|
+
# No tracking or sense ambush for mindless undead
|
515
|
+
unless @name.downcase.include?("vampire") || @name.downcase.include?("lich")
|
516
|
+
skills.delete("Reaction speed")
|
517
|
+
end
|
518
|
+
when "Animal"
|
519
|
+
# Animals have good natural senses
|
520
|
+
skills["Alertness"] = @level + rand(1..3)
|
521
|
+
# Predators track
|
522
|
+
if stats["skills"].any? { |s| s.downcase.include?("track") || s.downcase.include?("hunt") }
|
523
|
+
skills["Tracking"] = @level + rand(2..4)
|
524
|
+
end
|
525
|
+
# Pack animals sense ambush
|
526
|
+
if stats["skills"].include?("Pack tactics")
|
527
|
+
skills["Sense ambush"] = @level + rand(1..3)
|
528
|
+
end
|
529
|
+
when "Monster"
|
530
|
+
# Standard monster awareness
|
531
|
+
skills["Search"] = @level + rand(0..1)
|
532
|
+
# Intelligent monsters have better awareness
|
533
|
+
if stats["base_mind"] >= 3
|
534
|
+
skills["Sense ambush"] = @level + rand(0..2)
|
535
|
+
skills["Search"] = @level + rand(1..2)
|
536
|
+
end
|
537
|
+
# Tracking for appropriate monsters
|
538
|
+
if stats["skills"].include?("Tracking")
|
539
|
+
skills["Tracking"] = @level + rand(1..3)
|
540
|
+
end
|
541
|
+
else
|
542
|
+
# Default fallback
|
543
|
+
skills["Search"] = rand(0..1)
|
544
|
+
end
|
545
|
+
|
546
|
+
# Remove any skills with 0 or negative values
|
547
|
+
skills.delete_if { |_, v| v <= 0 }
|
548
|
+
|
549
|
+
skills
|
550
|
+
end
|
551
|
+
end
|