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,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
|