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,512 @@
1
+ # New Encounter class for the 3-tier system
2
+
3
+ class EncNew
4
+ attr_reader :terrain, :terraintype, :enc, :enc_spec, :enc_number, :enc_attitude
5
+ attr_reader :npcs, :encounter
6
+
7
+ def initialize(enc_spec = "", enc_number = 0, terraintype = nil, level_mod = 0)
8
+ # Set defaults
9
+ @terraintype = terraintype || $Terraintype || 8 # Default to day/rural
10
+ @level_mod = level_mod || $Level || 0
11
+ @terrain = @terraintype % 8 # Extract terrain from terraintype
12
+ @day = @terraintype >= 8 ? 1 : 0 # Extract day/night
13
+
14
+ @enc_spec = enc_spec.to_s
15
+ @enc_number = enc_number.to_i
16
+ @encounter = []
17
+ @npcs = []
18
+
19
+ # Load tables if not loaded
20
+ unless defined?($Enc_type)
21
+ load File.join($pgmdir, "includes/tables/enc_type.rb")
22
+ load File.join($pgmdir, "includes/tables/enc_specific.rb")
23
+ load File.join($pgmdir, "includes/tables/encounters.rb")
24
+ end
25
+
26
+ # Generate attitude
27
+ generate_attitude
28
+
29
+ # Generate the encounter
30
+ if @enc_spec.empty?
31
+ generate_random_encounter
32
+ else
33
+ generate_specific_encounter
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def generate_attitude
40
+ # Generate encounter attitude
41
+ case d6
42
+ when 1
43
+ @enc_attitude = "HOSTILE"
44
+ when 2
45
+ @enc_attitude = "ANTAGONISTIC"
46
+ when 3..4
47
+ @enc_attitude = "NEUTRAL"
48
+ when 5
49
+ @enc_attitude = "POSITIVE"
50
+ when 6
51
+ @enc_attitude = "FRIENDLY"
52
+ end
53
+ end
54
+
55
+ def generate_random_encounter
56
+ # Use the encounter tables from old system
57
+ enc_terrain = {}
58
+ $Enc_type.each do |key, value|
59
+ enc_terrain[key] = value[@terraintype]
60
+ end
61
+
62
+ @enc_type = randomizer(enc_terrain)
63
+
64
+ # Handle no encounter
65
+ if @enc_type =~ /NO ENCOUNTER/
66
+ @encounter[0] = {}
67
+ @encounter[0]["string"] = "NO ENCOUNTER"
68
+ return
69
+ end
70
+
71
+ # Get specific encounter from type
72
+ if defined?($Enc_specific) && $Enc_specific[@enc_type]
73
+ enc_terrain2 = {}
74
+ $Enc_specific[@enc_type].each do |key, value|
75
+ enc_terrain2[key] = value[@terraintype]
76
+ end
77
+ @enc_spec = randomizer(enc_terrain2)
78
+ else
79
+ # Fallback to basic type
80
+ @enc_spec = @enc_type
81
+ end
82
+
83
+ # Generate number of encounters
84
+ if @enc_number == 0
85
+ case oD6
86
+ when -Float::INFINITY..3
87
+ @enc_number = 1
88
+ when 4
89
+ @enc_number = dX(3)
90
+ when 5
91
+ @enc_number = d6
92
+ when 6..7
93
+ @enc_number = 2 * d6
94
+ else
95
+ @enc_number = 3 * d6
96
+ end
97
+ @enc_number = 5 if @enc_number > 5 && @enc_type =~ /onster/
98
+ end
99
+
100
+ # For humanoid encounters, sometimes create mixed groups
101
+ if @enc_type =~ /human/i && @enc_number > 3 && rand(100) < 30
102
+ generate_mixed_group
103
+ else
104
+ # Generate NPCs normally
105
+ generate_npcs_from_tables
106
+ end
107
+ end
108
+
109
+ def generate_specific_encounter
110
+ @enc = @enc_spec
111
+ @enc_type = "human" # Default type
112
+
113
+ # Generate number if not specified
114
+ if @enc_number == 0
115
+ @enc_number = rand(6) + 1
116
+ end
117
+
118
+ # Generate NPCs
119
+ generate_npcs_from_tables
120
+ end
121
+
122
+ def generate_mixed_group
123
+ # Create a diverse but logical group
124
+ # Examples: merchant caravan, adventuring party, soldiers with support
125
+
126
+ # Load required classes if needed
127
+ unless defined?(NpcNew)
128
+ load File.join($pgmdir, "includes/class_npc_new.rb")
129
+ end
130
+
131
+ group_type = rand(100)
132
+
133
+ if group_type < 25
134
+ # Merchant caravan
135
+ leader_count = 1 + rand(2)
136
+ guard_count = (@enc_number - leader_count) * 2 / 3
137
+ worker_count = @enc_number - leader_count - guard_count
138
+
139
+ enc_types = []
140
+ leader_count.times { enc_types << "Merchant" }
141
+ guard_count.times { enc_types << ["Guard", "Warrior", "Soldier"].sample }
142
+ worker_count.times { enc_types << ["Worker", "Farmer", "Porter"].sample }
143
+
144
+ @enc_spec = "Mixed merchant caravan"
145
+ elsif group_type < 50
146
+ # Adventuring party
147
+ enc_types = []
148
+ enc_types << ["Warrior", "Soldier", "Gladiator"].sample
149
+ enc_types << ["Priest", "Cleric", "Healer"].sample if @enc_number > 2
150
+ enc_types << ["Wizard (fire)", "Wizard (water)", "Wizard (air)", "Wizard (earth)"].sample if @enc_number > 3
151
+ enc_types << ["Thief", "Ranger", "Scout"].sample if @enc_number > 4
152
+
153
+ # Fill rest with warriors/fighters
154
+ while enc_types.length < @enc_number
155
+ enc_types << ["Warrior", "Ranger", "Soldier", "Guard"].sample
156
+ end
157
+
158
+ @enc_spec = "Adventuring party"
159
+ elsif group_type < 75
160
+ # Military patrol
161
+ officer_count = 1
162
+ soldier_count = @enc_number - 1
163
+
164
+ enc_types = []
165
+ officer_count.times { enc_types << "Army officer" }
166
+ soldier_count.times { enc_types << ["Soldier", "Guard", "Warrior"].sample }
167
+
168
+ @enc_spec = "Military patrol"
169
+ else
170
+ # Bandits/thieves
171
+ leader_count = 1
172
+ thief_count = (@enc_number - leader_count) / 2
173
+ muscle_count = @enc_number - leader_count - thief_count
174
+
175
+ enc_types = []
176
+ leader_count.times { enc_types << ["Assassin", "Highwayman", "Bandit"].sample }
177
+ thief_count.times { enc_types << ["Thief", "Rogue", "Highwayman"].sample }
178
+ muscle_count.times { enc_types << ["Warrior", "Barbarian", "Thug"].sample }
179
+
180
+ @enc_spec = "Bandit group"
181
+ end
182
+
183
+ # Generate the NPCs
184
+ enc_types.shuffle! # Randomize order
185
+
186
+ @enc_number.times do |i|
187
+ @encounter[i] = {}
188
+ @encounter[i]["string"] = enc_types[i] || "Commoner"
189
+
190
+ # Generate level with some variety
191
+ base_level = rand(3) + 1 + @level_mod
192
+ if i == 0 && group_type != 50 # Leader gets bonus (except adventuring party)
193
+ base_level += 1
194
+ end
195
+ @encounter[i]["level"] = [base_level, 1].max
196
+
197
+ # Generate sex
198
+ @encounter[i]["sex"] = rand(2) == 0 ? "F" : "M"
199
+
200
+ # Create NPC
201
+ npc = NpcNew.new(
202
+ "", # Name will be generated
203
+ enc_types[i] || "Commoner",
204
+ @encounter[i]["level"],
205
+ "", # Area
206
+ @encounter[i]["sex"],
207
+ 0, # Age will be generated
208
+ 0, # Height will be generated
209
+ 0, # Weight will be generated
210
+ "" # Description
211
+ )
212
+
213
+ @encounter[i]["npc"] = npc
214
+ @npcs << npc
215
+ end
216
+ end
217
+
218
+ def generate_npcs_from_tables
219
+ # Load required classes if needed
220
+ unless defined?(MonsterNew)
221
+ load File.join($pgmdir, "includes/class_monster_new.rb")
222
+ end
223
+
224
+ unless defined?(NpcNew)
225
+ load File.join($pgmdir, "includes/class_npc_new.rb")
226
+ end
227
+
228
+ # Generate NPCs based on encounter tables
229
+ @enc_number.times do |i|
230
+ @encounter[i] = {}
231
+ @encounter[i]["string"] = @enc_spec
232
+
233
+ # Skip if event
234
+ if @encounter[i]["string"] =~ /Event:/
235
+ break
236
+ end
237
+
238
+ # Check if this is a monster/animal encounter
239
+ if @enc_spec =~ /Monster:|Animal:|animal:/i
240
+ # Generate level for monster
241
+ if defined?($Encounters) && $Encounters[@enc_spec]
242
+ stats = $Encounters[@enc_spec].dup
243
+ base_level = stats[0].is_a?(Array) ? dX(stats[0]) : stats[0]
244
+ else
245
+ base_level = rand(3) + 1
246
+ end
247
+
248
+ @encounter[i]["level"] = base_level + @level_mod
249
+ @encounter[i]["level"] = 1 if @encounter[i]["level"] < 1
250
+
251
+ # Create monster/animal NPC
252
+ npc = MonsterNew.new(@enc_spec, @encounter[i]["level"])
253
+ @encounter[i]["sex"] = npc.sex
254
+ else
255
+ # Handle humanoid encounters
256
+ # Check if this is a race-prefixed encounter (e.g., "Faerie: Sailor")
257
+ lookup_spec = @enc_spec
258
+ if @enc_spec =~ /^(\w+):\s+(.+)/
259
+ race_prefix = $1
260
+ base_type = $2
261
+ lookup_spec = base_type # Look up the base type in encounters table
262
+ end
263
+
264
+ # Get stats from encounter table if exists
265
+ if defined?($Encounters) && $Encounters[lookup_spec]
266
+ stats = $Encounters[lookup_spec].dup
267
+
268
+ # Generate level with modifier
269
+ base_level = stats[0].is_a?(Array) ? dX(stats[0]) : stats[0]
270
+ @encounter[i]["level"] = base_level + @level_mod
271
+ @encounter[i]["level"] += 2 if @enc_spec =~ /Elf|Faerie/
272
+ @encounter[i]["level"] += 1 if @enc_spec =~ /Dwarf/
273
+ @encounter[i]["level"] = 1 if @encounter[i]["level"] < 1
274
+
275
+ # Determine character type from encounter
276
+ char_type = determine_character_type(@enc_spec)
277
+ else
278
+ # Fallback values
279
+ @encounter[i]["level"] = rand(3) + 1 + @level_mod
280
+ @encounter[i]["level"] = 1 if @encounter[i]["level"] < 1
281
+ char_type = determine_character_type(@enc_spec)
282
+ end
283
+
284
+ # Generate sex
285
+ @encounter[i]["sex"] = rand(2) == 0 ? "F" : "M"
286
+ @encounter[i]["sex"] = "M" if @enc_spec =~ /officer/ && rand(6) != 0
287
+ @encounter[i]["sex"] = "F" if @enc_spec =~ /Prostitute/ && rand(10) != 0
288
+ @encounter[i]["sex"] = "F" if @enc_spec =~ /Nanny/ && rand(10) != 0
289
+ @encounter[i]["sex"] = "F" if @enc_spec =~ /wife/
290
+
291
+ # Create NPC with new system
292
+ npc = NpcNew.new(
293
+ "", # Name will be generated
294
+ char_type,
295
+ @encounter[i]["level"],
296
+ "", # Area
297
+ @encounter[i]["sex"],
298
+ 0, # Age will be generated
299
+ 0, # Height will be generated
300
+ 0, # Weight will be generated
301
+ "" # Description
302
+ )
303
+ end
304
+
305
+ @encounter[i]["npc"] = npc
306
+ @npcs << npc
307
+ end
308
+ end
309
+
310
+ def determine_character_type(enc_string)
311
+ # Map encounter strings to character types
312
+ # Check if encounter string already has race prefix
313
+ if enc_string =~ /^(\w+):\s+(.+)/
314
+ race = $1
315
+ profession = $2
316
+
317
+ # Map profession to appropriate type for that race
318
+ base_type = case profession.downcase
319
+ when /warrior/, /soldier/, /fighter/
320
+ "Warrior"
321
+ when /ranger/, /scout/, /hunter/
322
+ "Ranger"
323
+ when /mage/, /wizard/, /sorcerer/
324
+ "Mage"
325
+ when /thief/, /rogue/, /bandit/
326
+ "Thief"
327
+ when /smith/, /blacksmith/, /armour smith/
328
+ "Smith"
329
+ when /guard/, /watchman/
330
+ "Guard"
331
+ when /priest/, /cleric/, /monk/
332
+ "Priest"
333
+ when /merchant/, /trader/
334
+ "Merchant"
335
+ when /noble/, /lord/, /lady/
336
+ "Noble"
337
+ when /sailor/, /seaman/, /mariner/
338
+ "Sailor"
339
+ when /bard/, /minstrel/, /entertainer/
340
+ "Bard"
341
+ when /assassin/
342
+ "Assassin"
343
+ when /sage/, /scholar/
344
+ "Sage"
345
+ when /healer/, /physician/
346
+ "Healer"
347
+ when /commoner/, /farmer/, /peasant/
348
+ "Commoner"
349
+ else
350
+ # Use the profession directly if not matched
351
+ profession.capitalize
352
+ end
353
+
354
+ # Return race-specific type if it exists in templates
355
+ race_type = "#{race}: #{base_type}"
356
+
357
+ # Check if this race-type combo exists in templates
358
+ if defined?($ChartypeNew) && $ChartypeNew.key?(race_type)
359
+ return race_type
360
+ elsif defined?($RaceTemplates) && $RaceTemplates.key?(race_type)
361
+ return race_type
362
+ else
363
+ # Try alternate names
364
+ alt_types = []
365
+ case base_type
366
+ when "Mage"
367
+ alt_types = ["Wizard", "Sorcerer"]
368
+ when "Warrior"
369
+ alt_types = ["Fighter", "Soldier"]
370
+ when "Ranger"
371
+ alt_types = ["Scout", "Hunter"]
372
+ end
373
+
374
+ for alt in alt_types
375
+ alt_race_type = "#{race}: #{alt}"
376
+ if (defined?($ChartypeNew) && $ChartypeNew.key?(alt_race_type)) ||
377
+ (defined?($RaceTemplates) && $RaceTemplates.key?(alt_race_type))
378
+ return alt_race_type
379
+ end
380
+ end
381
+
382
+ # Fallback to generic warrior for that race
383
+ return "#{race}: Warrior"
384
+ end
385
+ end
386
+
387
+ # Original logic for encounters without race prefix
388
+ case enc_string.downcase
389
+ when /elf/
390
+ # Elves can be various types
391
+ ["Elf: Ranger", "Elf: Warrior", "Elf: Mage"].sample
392
+ when /dwarf/
393
+ # Dwarves are typically warriors or smiths
394
+ ["Dwarf: Warrior", "Dwarf: Smith", "Dwarf: Guard"].sample
395
+ when /araxi/, /arax/
396
+ # Araxi are savage predatory warriors
397
+ "Araxi: Warrior"
398
+ when /troll/
399
+ "Troll: Warrior"
400
+ when /ogre/
401
+ "Ogre: Warrior"
402
+ when /lizard/
403
+ "Lizard Man: Warrior"
404
+ when /goblin/
405
+ ["Goblin: Warrior", "Goblin: Thief"].sample
406
+ when /centaur/
407
+ ["Centaur: Warrior", "Centaur: Ranger"].sample
408
+ when /faer/
409
+ "Faerie: Mage"
410
+ # Then check for profession-based encounters
411
+ when /warrior/, /soldier/, /guard/
412
+ "Warrior"
413
+ when /merchant/, /trader/, /caravan/
414
+ "Merchant"
415
+ when /thief/, /bandit/, /robber/
416
+ "Thief"
417
+ when /wizard/, /mage/, /sorcerer/
418
+ ["Wizard (fire)", "Wizard (water)", "Wizard (air)", "Wizard (earth)"].sample
419
+ when /priest/, /cleric/, /monk/
420
+ "Priest"
421
+ when /ranger/, /scout/, /hunter/
422
+ "Ranger"
423
+ when /noble/, /lord/, /lady/
424
+ "Noble"
425
+ when /barbarian/
426
+ "Barbarian"
427
+ when /assassin/
428
+ "Assassin"
429
+ when /gladiator/
430
+ "Gladiator"
431
+ when /smith/, /blacksmith/, /weaponsmith/, /armorer/
432
+ "Smith"
433
+ when /farmer/, /peasant/, /laborer/
434
+ "Farmer"
435
+ when /sailor/, /seaman/, /mariner/
436
+ "Sailor"
437
+ when /bard/, /minstrel/, /storyteller/
438
+ "Bard"
439
+ when /sage/, /scholar/, /learned/, /wise/
440
+ "Sage"
441
+ when /scribe/, /clerk/, /secretary/
442
+ "Scribe"
443
+ when /healer/, /physician/, /apothecary/
444
+ "Healer"
445
+ when /entertainer/, /actor/, /dancer/
446
+ "Entertainer"
447
+ when /artisan/, /craftsman/, /crafter/
448
+ "Artisan"
449
+ when /guard/, /watchman/
450
+ "Guard"
451
+ else
452
+ "Commoner"
453
+ end
454
+ end
455
+
456
+ # Helper methods from old system
457
+ def d6
458
+ rand(6) + 1
459
+ end
460
+
461
+ def oD6
462
+ d6 + rand(3) - 1
463
+ end
464
+
465
+ def dX(x)
466
+ return 0 if x <= 0
467
+ if x.is_a?(Array)
468
+ x = x.first.to_i
469
+ end
470
+ sum = 0
471
+ x.times { sum += d6 }
472
+ sum
473
+ end
474
+
475
+ def randomizer(hash)
476
+ # Weight-based random selection
477
+ total = hash.values.sum
478
+ return "" if total == 0
479
+
480
+ roll = rand(total) + 1
481
+ cumulative = 0
482
+
483
+ hash.each do |key, weight|
484
+ cumulative += weight
485
+ return key if roll <= cumulative
486
+ end
487
+
488
+ hash.keys.first
489
+ end
490
+
491
+ public
492
+
493
+ def summary
494
+ if @encounter.first && @encounter.first["string"] == "NO ENCOUNTER"
495
+ "NO ENCOUNTER"
496
+ else
497
+ "#{@enc_number} #{@enc_spec}"
498
+ end
499
+ end
500
+
501
+ def get_npc(index)
502
+ @npcs[index] if index < @npcs.length
503
+ end
504
+
505
+ def total_threat_level
506
+ @npcs.sum { |npc| npc.level }
507
+ end
508
+
509
+ def is_no_encounter?
510
+ @encounter.first && @encounter.first["string"] == "NO ENCOUNTER"
511
+ end
512
+ end