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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +675 -0
  3. data/README.md +155 -0
  4. data/amar-tui.rb +8195 -0
  5. data/cli_enc_output.rb +87 -0
  6. data/cli_enc_output_new.rb +433 -0
  7. data/cli_enc_output_new_3tier.rb +198 -0
  8. data/cli_enc_output_new_compact.rb +238 -0
  9. data/cli_name_gen.rb +21 -0
  10. data/cli_npc_output.rb +279 -0
  11. data/cli_npc_output_new.rb +700 -0
  12. data/cli_town_output.rb +39 -0
  13. data/cli_weather_output.rb +36 -0
  14. data/includes/class_enc.rb +341 -0
  15. data/includes/class_enc_new.rb +512 -0
  16. data/includes/class_monster_new.rb +551 -0
  17. data/includes/class_npc.rb +1378 -0
  18. data/includes/class_npc_new.rb +1187 -0
  19. data/includes/class_npc_new.rb.backup +706 -0
  20. data/includes/class_npc_new_skills.rb +153 -0
  21. data/includes/class_town.rb +237 -0
  22. data/includes/d6s.rb +40 -0
  23. data/includes/equipment_tables.rb +120 -0
  24. data/includes/functions.rb +67 -0
  25. data/includes/includes.rb +30 -0
  26. data/includes/randomizer.rb +15 -0
  27. data/includes/spell_catalog.rb +441 -0
  28. data/includes/tables/armour.rb +13 -0
  29. data/includes/tables/chartype.rb +4412 -0
  30. data/includes/tables/chartype_new.rb +765 -0
  31. data/includes/tables/chartype_new_full.rb +2713 -0
  32. data/includes/tables/enc_specific.rb +168 -0
  33. data/includes/tables/enc_type.rb +17 -0
  34. data/includes/tables/encounters.rb +99 -0
  35. data/includes/tables/magick.rb +169 -0
  36. data/includes/tables/melee.rb +36 -0
  37. data/includes/tables/missile.rb +17 -0
  38. data/includes/tables/monster_stats_new.rb +264 -0
  39. data/includes/tables/month.rb +18 -0
  40. data/includes/tables/names.rb +21 -0
  41. data/includes/tables/personality.rb +12 -0
  42. data/includes/tables/race_templates.rb +318 -0
  43. data/includes/tables/religions.rb +266 -0
  44. data/includes/tables/spells_new.rb +496 -0
  45. data/includes/tables/tier_system.rb +104 -0
  46. data/includes/tables/town.rb +71 -0
  47. data/includes/tables/weather.rb +41 -0
  48. data/includes/town_relations.rb +127 -0
  49. data/includes/weather.rb +108 -0
  50. data/includes/weather2latex.rb +114 -0
  51. data/lib/rcurses.rb +33 -0
  52. metadata +157 -0
@@ -0,0 +1,706 @@
1
+ # New NPC class for the 3-tier Amar RPG system
2
+ # Supports Characteristics > Attributes > Skills hierarchy
3
+
4
+ class NpcNew
5
+ attr_reader :type, :level, :name, :area, :sex, :age
6
+ attr_reader :height, :weight, :description
7
+ attr_reader :SIZE, :BP, :DB, :MD, :ENC, :STATUS
8
+ attr_reader :tiers, :marks, :spells, :armor
9
+ attr_writer :name, :area, :sex, :age, :height, :weight, :description
10
+ attr_writer :ENC
11
+
12
+ def initialize(name, type, level, area, sex, age, height, weight, description)
13
+ @name = name.to_s
14
+ @type = type.to_s
15
+ @level = level.to_i
16
+ @area = area.to_s
17
+ @sex = sex.to_s
18
+ @age = age.to_i
19
+ @height = height.to_i
20
+ @weight = weight.to_i
21
+ @description = description.to_s
22
+
23
+ # Initialize the 3-tier system
24
+ @tiers = {
25
+ "BODY" => {},
26
+ "MIND" => {},
27
+ "SPIRIT" => {}
28
+ }
29
+
30
+ # Initialize marks tracking for progression
31
+ @marks = {
32
+ "BODY" => {},
33
+ "MIND" => {},
34
+ "SPIRIT" => {}
35
+ }
36
+
37
+ # Initialize spell ownership system
38
+ @spells = []
39
+
40
+ # Randomize omitted values
41
+ randomize_basics
42
+
43
+ # Generate tier values based on type and level
44
+ generate_tiers
45
+
46
+ # Calculate derived statistics
47
+ calculate_modifiers
48
+
49
+ # Generate equipment
50
+ generate_equipment
51
+
52
+ # Generate spells if applicable
53
+ generate_spells if has_magic?
54
+ end
55
+
56
+ private
57
+
58
+ def randomize_basics
59
+ # Sex
60
+ if @sex == ""
61
+ @sex = rand(2) == 0 ? "F" : "M"
62
+ end
63
+
64
+ # Name
65
+ @name = naming("Human", @sex) if @name == ""
66
+
67
+ # Type
68
+ unless $ChartypeNew.has_key?(@type)
69
+ @type = $ChartypeNew.keys.sample
70
+ end
71
+
72
+ # Level
73
+ @level = rand(5) + 1 if @level == 0
74
+
75
+ # Area
76
+ if @area == ""
77
+ @area = randomizer(
78
+ "Amaronir" => 4,
79
+ "Merisir" => 2,
80
+ "Calaronir" => 2,
81
+ "Feronir" => 2,
82
+ "Aleresir" => 2,
83
+ "Rauinir" => 3,
84
+ "Outskirts" => 1,
85
+ "Other" => 1
86
+ )
87
+ end
88
+
89
+ # Age
90
+ @age = @level * 5 + oD6.abs * 3 + rand(10) if @age == 0
91
+
92
+ # Height
93
+ if @height == 0
94
+ @height = 160 + oD6 * 2 + oD6 + rand(10)
95
+ @height -= 5 if @sex == "F"
96
+ @height -= (3 * (16 - @age)) if @age < 17
97
+ end
98
+
99
+ # Weight
100
+ @weight = @height - 120 + aD6 * 4 + rand(10) if @weight == 0
101
+ end
102
+
103
+ def generate_tiers
104
+ # Get character type template
105
+ template = $ChartypeNew[@type]
106
+
107
+ # Generate characteristics (top tier)
108
+ $TierSystem.each do |char_name, attributes|
109
+ @tiers[char_name] = {}
110
+
111
+ # Set characteristic level based on template and NPC level
112
+ char_base = template["characteristics"][char_name] || 0
113
+ char_level = calculate_tier_level(char_base, @level, 1.0)
114
+
115
+ # Generate attributes (middle tier)
116
+ attributes.each do |attr_name, attr_data|
117
+ @tiers[char_name][attr_name] = {}
118
+
119
+ # Calculate attribute level
120
+ attr_base = template["attributes"]["#{char_name}/#{attr_name}"] || attr_data["base"]
121
+
122
+ # Special handling for Innate - should be very rare
123
+ if attr_name == "Innate"
124
+ # Only special character types get Innate
125
+ special_types = ["Witch (white)", "Witch (black)", "Sorcerer", "Summoner"]
126
+ unless special_types.include?(@type)
127
+ attr_base = 0 # Force to 0 for non-special types
128
+ end
129
+ end
130
+
131
+ attr_level = calculate_tier_level(attr_base, @level, 0.8)
132
+ @tiers[char_name][attr_name]["level"] = attr_level
133
+
134
+ # Generate skills (bottom tier)
135
+ @tiers[char_name][attr_name]["skills"] = {}
136
+ attr_data["skills"].each do |skill_name|
137
+ skill_key = "#{char_name}/#{attr_name}/#{skill_name}"
138
+ skill_base = template["skills"][skill_key] || 0
139
+
140
+ # Zero out Innate skills for non-special types
141
+ if attr_name == "Innate" && attr_base == 0
142
+ skill_level = 0
143
+ # Zero out Casting skills for non-casters
144
+ elsif attr_name == "Casting" && !has_template_magic?(template)
145
+ skill_level = 0
146
+ else
147
+ skill_level = calculate_tier_level(skill_base, @level, 0.6)
148
+ end
149
+
150
+ @tiers[char_name][attr_name]["skills"][skill_name] = skill_level
151
+ end
152
+ end
153
+ end
154
+
155
+ # Add weapon skills from existing tables
156
+ add_weapon_skills(template)
157
+
158
+ # Add additional skills for experienced NPCs (level 4+)
159
+ if @level >= 4
160
+ add_experience_skills()
161
+ end
162
+ end
163
+
164
+ def add_experience_skills
165
+ # Add additional skills for experienced characters
166
+ # These represent skills picked up through life experience
167
+
168
+ # Add some general skills based on level and type
169
+ experience_bonus = @level - 3 # 1 for level 4, 2 for level 5, etc.
170
+
171
+ # Significantly expand skill generation based on level
172
+ # Level 4: 8-12 additional skills
173
+ # Level 5: 12-18 additional skills
174
+ # Level 6+: 18-25 additional skills
175
+ skill_count = case @level
176
+ when 4 then rand(8..12)
177
+ when 5 then rand(12..18)
178
+ when 6 then rand(18..25)
179
+ else rand(20..30)
180
+ end
181
+
182
+ skills_added = 0
183
+
184
+ # Helper method to safely add skills
185
+ def safe_add_skill(char, attr, skill, value)
186
+ if @tiers[char] && @tiers[char][attr] && @tiers[char][attr]["skills"]
187
+ # Initialize skills hash if it's an array
188
+ if @tiers[char][attr]["skills"].is_a?(Array)
189
+ skill_list = @tiers[char][attr]["skills"]
190
+ @tiers[char][attr]["skills"] = {}
191
+ skill_list.each { |s| @tiers[char][attr]["skills"][s] = 0 }
192
+ end
193
+ # Now add the skill if it exists or create it
194
+ if @tiers[char][attr]["skills"].key?(skill) || @tiers[char][attr]["skills"][skill].nil?
195
+ @tiers[char][attr]["skills"][skill] ||= 0
196
+ if @tiers[char][attr]["skills"][skill] == 0
197
+ @tiers[char][attr]["skills"][skill] = value
198
+ return true
199
+ end
200
+ end
201
+ end
202
+ false
203
+ end
204
+
205
+ # AWARENESS SKILLS - everyone needs these
206
+ awareness_skills = ["Spot Hidden", "Listening", "Direction Sense", "Tracking", "Lie Detection"]
207
+ awareness_skills.sample(3 + experience_bonus).each do |skill|
208
+ if @tiers["MIND"]["Awareness"]["skills"].key?(skill) && @tiers["MIND"]["Awareness"]["skills"][skill] == 0
209
+ @tiers["MIND"]["Awareness"]["skills"][skill] = rand(2..4) + experience_bonus
210
+ skills_added += 1
211
+ end
212
+ end
213
+
214
+ # SOCIAL SKILLS - for interaction
215
+ social_skills = ["Rhetoric", "Bargain", "Read People", "Persuasion", "Etiquette", "Leadership"]
216
+ social_skills.sample(3 + experience_bonus).each do |skill|
217
+ if @tiers["MIND"]["Social Knowledge"]["skills"].key?(skill) && @tiers["MIND"]["Social Knowledge"]["skills"][skill] == 0
218
+ @tiers["MIND"]["Social Knowledge"]["skills"][skill] = rand(2..3) + experience_bonus
219
+ skills_added += 1
220
+ end
221
+ end
222
+
223
+ # PRACTICAL SKILLS - everyday knowledge
224
+ practical_skills = ["Direction Sense", "First Aid", "Cooking", "Animal Handling", "Navigation"]
225
+ practical_skills.sample(2 + experience_bonus).each do |skill|
226
+ if @tiers["MIND"]["Practical Knowledge"]["skills"].key?(skill) && @tiers["MIND"]["Practical Knowledge"]["skills"][skill] == 0
227
+ @tiers["MIND"]["Practical Knowledge"]["skills"][skill] = rand(2..3) + experience_bonus
228
+ skills_added += 1
229
+ end
230
+ end
231
+
232
+ # ATHLETICS SKILLS - physical competencies
233
+ athletics_skills = ["Swimming", "Climbing", "Running", "Jumping", "Balance", "Acrobatics"]
234
+ athletics_skills.sample(2 + experience_bonus).each do |skill|
235
+ if @tiers["BODY"]["Athletics"]["skills"].key?(skill) && @tiers["BODY"]["Athletics"]["skills"][skill] == 0
236
+ @tiers["BODY"]["Athletics"]["skills"][skill] = rand(1..3) + experience_bonus
237
+ skills_added += 1
238
+ end
239
+ end
240
+
241
+ # DEXTERITY SKILLS
242
+ dex_skills = ["Sleight of Hand", "Pick Locks", "Disarm Traps", "Juggling", "Crafting"]
243
+ dex_skills.sample(2).each do |skill|
244
+ if @tiers["BODY"]["Dexterity"] && @tiers["BODY"]["Dexterity"]["skills"] &&
245
+ @tiers["BODY"]["Dexterity"]["skills"].key?(skill) && @tiers["BODY"]["Dexterity"]["skills"][skill] == 0
246
+ @tiers["BODY"]["Dexterity"]["skills"][skill] = rand(1..2) + experience_bonus
247
+ skills_added += 1
248
+ end
249
+ end
250
+
251
+ # NATURE KNOWLEDGE - world understanding
252
+ nature_skills = ["History", "Geography", "Legends", "Heraldry", "Languages", "Culture", "Weather", "Nature Lore"]
253
+ nature_skills.sample(3 + experience_bonus).each do |skill|
254
+ if @tiers["MIND"]["Nature Knowledge"]["skills"].key?(skill) && @tiers["MIND"]["Nature Knowledge"]["skills"][skill] == 0
255
+ @tiers["MIND"]["Nature Knowledge"]["skills"][skill] = rand(2..3) + experience_bonus
256
+ skills_added += 1
257
+ end
258
+ end
259
+
260
+ # For magic users, add extensive magic-related skills
261
+ if @tiers["SPIRIT"]["Casting"]["level"] > 0
262
+ magic_skills = ["Spell art I", "Spell art II", "Spell mastery", "Ritual Casting", "Counterspelling"]
263
+ magic_skills.sample(3).each do |skill|
264
+ if @tiers["SPIRIT"]["Casting"]["skills"].key?(skill) && @tiers["SPIRIT"]["Casting"]["skills"][skill] == 0
265
+ @tiers["SPIRIT"]["Casting"]["skills"][skill] = rand(3..5) + experience_bonus
266
+ skills_added += 1
267
+ end
268
+ end
269
+
270
+ # Magical knowledge
271
+ if @tiers["MIND"]["Nature Knowledge"]["skills"]["Magick Rituals"] == 0
272
+ @tiers["MIND"]["Nature Knowledge"]["skills"]["Magick Rituals"] = rand(3..5) + experience_bonus
273
+ skills_added += 1
274
+ end
275
+ if @tiers["MIND"]["Nature Knowledge"]["skills"].key?("Arcane Lore") && @tiers["MIND"]["Nature Knowledge"]["skills"]["Arcane Lore"] == 0
276
+ @tiers["MIND"]["Nature Knowledge"]["skills"]["Arcane Lore"] = rand(2..4) + experience_bonus
277
+ skills_added += 1
278
+ end
279
+
280
+ # Literacy is essential for magic users
281
+ if @tiers["MIND"]["Social Knowledge"]["skills"]["Literacy"] == 0
282
+ @tiers["MIND"]["Social Knowledge"]["skills"]["Literacy"] = rand(3..5) + experience_bonus
283
+ skills_added += 1
284
+ end
285
+ end
286
+
287
+ # For physical combat types, add extensive combat skills
288
+ if ["Warrior", "Guard", "Soldier", "Gladiator", "Body guard", "Ranger", "Hunter", "Barbarian"].any? { |t| @type.include?(t) }
289
+ combat_skills = ["Dodge", "Parry", "Shield Use", "Tactics", "Battle Formation"]
290
+ combat_skills.sample(3).each do |skill|
291
+ if @tiers["BODY"]["Athletics"]["skills"].key?(skill) && @tiers["BODY"]["Athletics"]["skills"][skill] == 0
292
+ @tiers["BODY"]["Athletics"]["skills"][skill] = rand(3..5) + experience_bonus
293
+ skills_added += 1
294
+ end
295
+ end
296
+
297
+ endurance_skills = ["Combat Tenacity", "Pain Resistance", "Stamina", "Recovery"]
298
+ endurance_skills.sample(2).each do |skill|
299
+ if @tiers["BODY"]["Endurance"]["skills"].key?(skill) && @tiers["BODY"]["Endurance"]["skills"][skill] == 0
300
+ @tiers["BODY"]["Endurance"]["skills"][skill] = rand(2..4) + experience_bonus
301
+ skills_added += 1
302
+ end
303
+ end
304
+ end
305
+
306
+ # For scholarly types, add extensive intellectual skills
307
+ if ["Scholar", "Sage", "Scribe", "Wizard", "Mage"].any? { |t| @type.include?(t) }
308
+ intel_skills = ["Logic", "Memory", "Research", "Analysis", "Mathematics", "Philosophy"]
309
+ intel_skills.sample(4).each do |skill|
310
+ if @tiers["MIND"]["Intelligence"]["skills"].key?(skill) && @tiers["MIND"]["Intelligence"]["skills"][skill] == 0
311
+ @tiers["MIND"]["Intelligence"]["skills"][skill] = rand(3..5) + experience_bonus
312
+ skills_added += 1
313
+ end
314
+ end
315
+
316
+ # Extra history and languages
317
+ if @tiers["MIND"]["Nature Knowledge"]["skills"].key?("Ancient History") && @tiers["MIND"]["Nature Knowledge"]["skills"]["Ancient History"] == 0
318
+ @tiers["MIND"]["Nature Knowledge"]["skills"]["Ancient History"] = rand(3..5) + experience_bonus
319
+ skills_added += 1
320
+ end
321
+ if @tiers["MIND"]["Nature Knowledge"]["skills"].key?("Dead Languages") && @tiers["MIND"]["Nature Knowledge"]["skills"]["Dead Languages"] == 0
322
+ @tiers["MIND"]["Nature Knowledge"]["skills"]["Dead Languages"] = rand(2..4) + experience_bonus
323
+ skills_added += 1
324
+ end
325
+ end
326
+
327
+ # For rogueish types, add stealth and subterfuge
328
+ if ["Thief", "Rogue", "Assassin", "Scout", "Spy"].any? { |t| @type.include?(t) }
329
+ # Stealth skills are under Dexterity in our tier system
330
+ stealth_skills = ["Hide", "Sneak", "Move Silently", "Hide in Shadows", "Stealth"]
331
+ stealth_skills.sample(3).each do |skill|
332
+ if @tiers["BODY"]["Dexterity"]["skills"].key?(skill) && @tiers["BODY"]["Dexterity"]["skills"][skill] == 0
333
+ @tiers["BODY"]["Dexterity"]["skills"][skill] = rand(4..6) + experience_bonus
334
+ skills_added += 1
335
+ end
336
+ end
337
+
338
+ thief_skills = ["Pick Pockets", "Forgery", "Disguise", "Escape Artist"]
339
+ thief_skills.sample(2).each do |skill|
340
+ if @tiers["BODY"]["Dexterity"]["skills"].key?(skill) && @tiers["BODY"]["Dexterity"]["skills"][skill] == 0
341
+ @tiers["BODY"]["Dexterity"]["skills"][skill] = rand(3..5) + experience_bonus
342
+ skills_added += 1
343
+ end
344
+ end
345
+ end
346
+
347
+ # For merchants and nobles, add trade and social skills
348
+ if ["Merchant", "Trader", "Noble", "Diplomat"].any? { |t| @type.include?(t) }
349
+ trade_skills = ["Appraisal", "Accounting", "Law", "Economics", "Negotiation"]
350
+ trade_skills.sample(3).each do |skill|
351
+ if @tiers["MIND"]["Intelligence"]["skills"].key?(skill) && @tiers["MIND"]["Intelligence"]["skills"][skill] == 0
352
+ @tiers["MIND"]["Intelligence"]["skills"][skill] = rand(3..5) + experience_bonus
353
+ skills_added += 1
354
+ end
355
+ end
356
+ end
357
+
358
+ # Everyone gets willpower skills at higher levels
359
+ if @level >= 5
360
+ will_skills = ["Mental Fortitude", "Resist Pain", "Concentration", "Meditation"]
361
+ will_skills.sample(2).each do |skill|
362
+ if @tiers["MIND"]["Willpower"]["skills"].key?(skill) && @tiers["MIND"]["Willpower"]["skills"][skill] == 0
363
+ @tiers["MIND"]["Willpower"]["skills"][skill] = rand(2..4) + experience_bonus
364
+ skills_added += 1
365
+ end
366
+ end
367
+ end
368
+
369
+ # Ensure we hit minimum skill counts by adding random skills
370
+ while skills_added < skill_count
371
+ # Pick random characteristic, attribute, and skill
372
+ char_name = ["BODY", "MIND", "SPIRIT"].sample
373
+ if @tiers[char_name]
374
+ attr_name = @tiers[char_name].keys.sample
375
+ if @tiers[char_name][attr_name] && @tiers[char_name][attr_name]["skills"]
376
+ skill_name = @tiers[char_name][attr_name]["skills"].keys.sample
377
+ if @tiers[char_name][attr_name]["skills"][skill_name] == 0
378
+ @tiers[char_name][attr_name]["skills"][skill_name] = rand(1..3) + experience_bonus
379
+ skills_added += 1
380
+ end
381
+ end
382
+ end
383
+ # Prevent infinite loop
384
+ break if skills_added >= 50
385
+ end
386
+ end
387
+
388
+ def calculate_tier_level(base, npc_level, tier_modifier)
389
+ # Natural caps based on training difficulty
390
+ # The harder to train, the lower the natural cap
391
+
392
+ # Determine tier type and apply realistic caps
393
+ # Adjusted to create proper population distribution
394
+ tier_caps = case tier_modifier
395
+ when 1.0 # Characteristic (hardest to train)
396
+ { normal: 2, experienced: 3, master: 4, hero: 5 }
397
+ when 0.8 # Attribute (moderate training)
398
+ { normal: 3, experienced: 5, master: 6, hero: 7 }
399
+ when 0.6 # Skill (easiest to train)
400
+ { normal: 5, experienced: 7, master: 9, hero: 11 }
401
+ else
402
+ { normal: 2, experienced: 3, master: 4, hero: 5 }
403
+ end
404
+
405
+ # Determine NPC experience level - matches population distribution
406
+ experience = case npc_level
407
+ when 1..2 then :normal # Common folk
408
+ when 3..4 then :experienced # Town champions
409
+ when 5..6 then :master # Regional masters
410
+ else :hero # National/legendary
411
+ end
412
+
413
+ max_value = tier_caps[experience]
414
+
415
+ # Calculate level with diminishing returns
416
+ # Characteristics grow very slowly, skills grow faster
417
+ growth_rate = case tier_modifier
418
+ when 1.0 then 0.4 # Very slow for characteristics
419
+ when 0.8 then 0.6 # Moderate for attributes
420
+ when 0.6 then 0.8 # Faster for skills
421
+ else 0.5
422
+ end
423
+
424
+ # Use square root for more realistic progression
425
+ level = (base * Math.sqrt(npc_level + 1) * growth_rate).to_i
426
+
427
+ # Add minimal variation ONLY if base > 0
428
+ if base > 0
429
+ variation = rand(3) - 1 # -1, 0, or 1
430
+ level += variation
431
+ end
432
+
433
+ # Ensure minimum competence for trained individuals
434
+ # Skills should rarely be below 3 for anyone with training
435
+ if tier_modifier == 0.6 # Skills
436
+ min_skill = case npc_level
437
+ when 1..2 then 2
438
+ when 3..4 then 3
439
+ else 4
440
+ end
441
+ level = min_skill if level < min_skill && base > 0
442
+ elsif tier_modifier == 0.8 # Attributes
443
+ min_attr = case npc_level
444
+ when 1..2 then 1
445
+ when 3..4 then 2
446
+ else 3
447
+ end
448
+ level = min_attr if level < min_attr && base > 0
449
+ end
450
+
451
+ # Apply training reality - most people plateau
452
+ # Only exceptional individuals (high level + good base) reach max
453
+ if tier_modifier == 1.0 # Characteristics rarely exceed 3
454
+ level = 3 if level > 3 && rand(100) > 20 # 80% plateau at 3
455
+ elsif tier_modifier == 0.8 # Attributes occasionally reach 6
456
+ level = 5 if level > 5 && rand(100) > 40 # 60% plateau at 5
457
+ end
458
+
459
+ # Ensure within bounds
460
+ level = 0 if level < 0
461
+ level = max_value if level > max_value
462
+
463
+ level
464
+ end
465
+
466
+ def add_weapon_skills(template)
467
+ # Add melee weapon skills
468
+ if template["melee_weapons"]
469
+ @tiers["BODY"]["Melee Combat"]["skills"] ||= {}
470
+ template["melee_weapons"].each do |weapon, skill_level|
471
+ base_level = calculate_tier_level(skill_level, @level, 0.6)
472
+ @tiers["BODY"]["Melee Combat"]["skills"][weapon] = base_level
473
+ end
474
+ end
475
+
476
+ # Add missile weapon skills
477
+ if template["missile_weapons"]
478
+ @tiers["BODY"]["Missile Combat"]["skills"] ||= {}
479
+ template["missile_weapons"].each do |weapon, skill_level|
480
+ base_level = calculate_tier_level(skill_level, @level, 0.6)
481
+ @tiers["BODY"]["Missile Combat"]["skills"][weapon] = base_level
482
+ end
483
+ end
484
+ end
485
+
486
+ def calculate_modifiers
487
+ # Calculate SIZE based on weight
488
+ @SIZE = case @weight
489
+ when 0..10 then 0
490
+ when 11..30 then 1
491
+ when 31..50 then 2
492
+ when 51..70 then 3
493
+ when 71..90 then 4
494
+ when 91..110 then 5
495
+ else 6
496
+ end
497
+
498
+ # Get relevant skill values
499
+ fortitude = @tiers["BODY"]["Endurance"]["skills"]["Fortitude"] || 0
500
+ wield_weapon = @tiers["BODY"]["Strength"]["skills"]["Wield weapon"] || 0
501
+ mental_fortitude = @tiers["MIND"]["Willpower"]["skills"]["Mental Fortitude"] || 0
502
+ attunement_self = @tiers["SPIRIT"]["Attunement"]["skills"]["Self"] || 0
503
+
504
+ # Calculate derived stats using new formulas
505
+ @BP = @SIZE * 2 + fortitude / 3
506
+ @DB = (@SIZE + wield_weapon) / 3
507
+ @MD = (mental_fortitude + attunement_self) / 3
508
+
509
+ # Calculate encumbrance
510
+ carrying = @tiers["BODY"]["Strength"]["skills"]["Carrying"] || 0
511
+ @ENC = @SIZE + carrying
512
+ end
513
+
514
+ def generate_equipment
515
+ # Generate armor based on character type and level
516
+ generate_armor
517
+
518
+ # Weapons are already handled via character templates
519
+ end
520
+
521
+ def generate_armor
522
+ # Determine armor type based on character type
523
+ armor_chance = case @type
524
+ when "Warrior", "Guard" then 80
525
+ when "Soldier", "Noble" then 60
526
+ when "Bandit", "Assassin" then 40
527
+ when "Ranger", "Thief" then 30
528
+ when "Priest" then 20
529
+ else 10
530
+ end
531
+
532
+ if rand(100) < armor_chance
533
+ # Select armor based on level and type
534
+ if @type == "Warrior" || @type == "Guard"
535
+ armor_types = case @level
536
+ when 1..2 then ["Leather", "Padded"]
537
+ when 3..4 then ["Leather", "Chain shirt", "Scale"]
538
+ when 5..6 then ["Chain mail", "Scale", "Plate"]
539
+ else ["Chain mail", "Plate", "Full plate"]
540
+ end
541
+ else
542
+ armor_types = ["Leather", "Padded", "None"]
543
+ end
544
+
545
+ armor_name = armor_types.sample
546
+
547
+ @armor = case armor_name
548
+ when "Leather"
549
+ { name: "Leather", ap: 2, enc: 2 }
550
+ when "Padded"
551
+ { name: "Padded", ap: 1, enc: 1 }
552
+ when "Chain shirt"
553
+ { name: "Chain shirt", ap: 3, enc: 3 }
554
+ when "Scale"
555
+ { name: "Scale armor", ap: 4, enc: 4 }
556
+ when "Chain mail"
557
+ { name: "Chain mail", ap: 4, enc: 5 }
558
+ when "Plate"
559
+ { name: "Plate armor", ap: 5, enc: 6 }
560
+ when "Full plate"
561
+ { name: "Full plate", ap: 6, enc: 7 }
562
+ else
563
+ nil
564
+ end
565
+
566
+ # Add armor encumbrance to total
567
+ @ENC += @armor[:enc] if @armor
568
+ end
569
+ end
570
+
571
+ def has_template_magic?(template)
572
+ # Check if template indicates magical ability
573
+ return false unless template
574
+
575
+ # Check if template has Casting attribute > 0
576
+ casting_attr = template["attributes"]["SPIRIT/Casting"] || 0
577
+ return true if casting_attr > 0
578
+
579
+ # Check for specific magic-using types
580
+ magic_types = ["Mage", "Wizard", "Witch (white)", "Witch (black)", "Sorcerer",
581
+ "Summoner", "Priest", "Sage", "Seer"]
582
+ magic_types.include?(@type)
583
+ end
584
+
585
+ # Moved to public section below
586
+
587
+ def generate_spells
588
+ # Generate spell cards based on character type and level
589
+ casting_level = @tiers["SPIRIT"]["Casting"]["level"] || 0
590
+
591
+ # Only generate spells if character has casting ability
592
+ if casting_level > 0
593
+ # Load spell database if not already loaded
594
+ unless defined?($SpellDatabase)
595
+ load File.join($pgmdir, "includes/tables/spells_new.rb")
596
+ end
597
+
598
+ @spells = generate_spell_cards(@type, @level, casting_level)
599
+ end
600
+ end
601
+
602
+ # Public methods for accessing tier data
603
+ public
604
+
605
+ def has_magic?
606
+ # Check if character has any casting ability
607
+ casting_level = @tiers["SPIRIT"]["Casting"]["level"] || 0
608
+ casting_level > 0
609
+ end
610
+
611
+ def get_characteristic(name)
612
+ # Calculate characteristic as weighted average of its attributes
613
+ # Reflects years of broad training across all aspects
614
+ total = 0
615
+ count = 0
616
+ @tiers[name].each do |attr_name, attr_data|
617
+ if attr_data["level"] && attr_data["level"] > 0
618
+ total += attr_data["level"]
619
+ count += 1
620
+ end
621
+ end
622
+
623
+ return 0 if count == 0
624
+
625
+ # Characteristics are very hard to improve - most people stay at 2-3
626
+ char_level = (total.to_f / count / 1.5).round # Scaled down to reflect training difficulty
627
+
628
+ # Apply realistic caps based on NPC level
629
+ max_char = case @level
630
+ when 1..2 then 2 # Novices rarely exceed 2
631
+ when 3..4 then 3 # Experienced typically max at 3
632
+ when 5..6 then 4 # Veterans might reach 4
633
+ else 5 # Only masters achieve 5
634
+ end
635
+
636
+ char_level = max_char if char_level > max_char
637
+ char_level
638
+ end
639
+
640
+ def get_attribute(char_name, attr_name)
641
+ @tiers[char_name][attr_name]["level"] || 0
642
+ end
643
+
644
+ def get_skill(char_name, attr_name, skill_name)
645
+ @tiers[char_name][attr_name]["skills"][skill_name] || 0
646
+ end
647
+
648
+ def get_skill_total(char_name, attr_name, skill_name)
649
+ # Calculate total: Characteristic + Attribute + Skill
650
+ char_level = get_characteristic(char_name)
651
+ attr_level = get_attribute(char_name, attr_name)
652
+ skill_level = get_skill(char_name, attr_name, skill_name)
653
+
654
+ # Natural progression based on training difficulty:
655
+ # - Getting to 10 is achievable with focused skill training
656
+ # - Getting to 15 requires years of dedicated work
657
+ # - Getting to 18+ is legendary, requiring lifetime mastery
658
+ total = char_level + attr_level + skill_level
659
+
660
+ # Apply soft cap at 18 for game balance (only true masters exceed)
661
+ if total > 18 && @level < 7
662
+ # Small chance for exceptional individuals to exceed 18
663
+ total = 18 unless rand(100) < 5
664
+ end
665
+
666
+ total
667
+ end
668
+
669
+ # Mark system for progression
670
+
671
+ def add_mark(char_name, attr_name, skill_name = nil)
672
+ if skill_name
673
+ # Add mark to skill
674
+ key = "#{attr_name}/#{skill_name}"
675
+ @marks[char_name][key] ||= 0
676
+ @marks[char_name][key] += 1
677
+
678
+ # Check if ready to advance
679
+ check_advancement(char_name, attr_name, skill_name)
680
+ else
681
+ # Add mark to attribute
682
+ @marks[char_name][attr_name] ||= 0
683
+ @marks[char_name][attr_name] += 1
684
+ end
685
+ end
686
+
687
+ def check_advancement(char_name, attr_name, skill_name)
688
+ current_level = get_skill(char_name, attr_name, skill_name)
689
+ required_marks = (current_level + 1) * 5
690
+ key = "#{attr_name}/#{skill_name}"
691
+
692
+ if @marks[char_name][key] >= required_marks
693
+ # Roll for advancement (all but a 1)
694
+ if oD6 > 1
695
+ @tiers[char_name][attr_name]["skills"][skill_name] += 1
696
+ @marks[char_name][key] = 0
697
+
698
+ # Add mark to attribute above
699
+ add_mark(char_name, attr_name)
700
+
701
+ return true
702
+ end
703
+ end
704
+ false
705
+ end
706
+ end