#!/usr/bin/ruby -w
require 'find_file'
class Mod
def initialize(&blk)
@files = {}
@save_as = {}
open_files!
instance_eval(&blk)
save!
end
def open(name, file_name, save_dir=nil)
save_as = File.join(*["output/concentrated_vanilla/data", save_dir, file_name].compact)
@files[name] = File.read(File.first_matching(file_name, 'data', 'data/packed', 'data/imperial-campaign'))
@save_as[name] = save_as
end
def modify(name)
@files[name] = yield(@files[name])
end
def save!
@files.each{|name, value|
File.open(@save_as[name], 'w') {|fh|
fh.print value
}
}
end
def open_files!
end
end
class M2TW_Mod < Mod
def open_files!
open 'character', 'descr_character.txt'
open 'engines', 'descr_engines.txt'
open 'settlement', 'descr_settlement_mechanics.xml'
open 'resources', 'descr_sm_resources.txt'
open 'walls', 'descr_walls.txt'
open 'buildings', 'export_descr_buildings.txt'
open 'units', 'export_descr_unit.txt'
open 'strat', 'descr_strat.txt', 'world/maps/campaign/imperial_campaign'
open 'mercenaries', 'descr_mercenaries.txt', 'world/maps/campaign/imperial_campaign'
open 'cultures', 'descr_cultures.txt'
open 'character_traits', 'export_descr_character_traits.txt'
open 'standing', 'descr_faction_standing.txt'
open 'regions', 'descr_regions.txt', 'world/maps/base'
open 'rebels', 'descr_rebel_factions.txt'
open 'sounds', 'descr_sounds_units.txt'
end
def nerf_rams!
#Rams should really suck - 50% attack, 50% health
modify('engines'){|file|
file.sub(/^(type\s+tortoise_ram.*?^attack_stat\s+)([^\r\n]+)/m){
$1 + "5, 2, no, 0, 0, melee, melee_simple, blunt"
}.sub(/^(type\s+tortoise_ram.*?^engine_health\s+)([^\r\n]+)/m){
$1 + "75"
}
}
end
def taxes_influence_on_growth!(influence=2.0)
# Taxes should influence settlement growth rates twice as much
modify('settlement'){|file|
file.gsub(/(<factor name="SPF_TAX_RATE_(?:BONUS|PENALTY)">\s+<pip_modifier value=")1.0(")/){
"#{$1}#{influence}#{$2}"
}
}
end
def resource_value!(value=1.5)
# Resources 50% more valuable
modify('resources'){|file|
file.gsub(/^(trade_value\s+)(\d+)/){ "#{$1}#{(value*$2.to_i).to_i}" }
}
end
def spy_cost!(mult)
# Spies twice the recruitment cost
modify('cultures'){|file|
file.gsub(/^(spy.*?\s+)(\d+)\b/) { "#{$1}#{($2.to_i * mult).to_i}"}
}
# Spies x2 more expensive
modify('character'){|file|
type = nil
file.gsub(/(?:^type\s*(\S+))|(?:^(wage_base\s*)(\d+))/) {
if $1
type = $1
$&
elsif type == 'spy'
"#{$2}#{($3.to_i*mult).to_i}"
else
$&
end
}
}
end
def campaign_movement_speed!(mult)
# Everyone move 75% faster
modify('character'){|file|
file.gsub(/^(starting_action_points\s+)(\d+)/){"#{$1}#{(1.75*$2.to_i).to_i}"}
}
end
def wall_strength!(wall_mult, tower_mult)
# Gates and walls 5x stronger
# towers 0% stronger
modify('walls'){|file|
t = nil
file.gsub(/(.*)/){|line|
if line =~ /^\s+(wall|gateway|tower|gate)/
t = $1
elsif line =~ /\A(\s*full_health\s*)(\d+)(.*)/
if t == 'tower'
line = "#{$1}#{($2.to_i*tower_mult).to_i}#{$3}"
else
line = "#{$1}#{($2.to_i*wall_mult).to_i}#{$3}"
end
end
line
}
}
end
def tower_fire_rate!(normal, flaming)
# 2x faster tower firing rate, flaming reload the same
modify('walls'){|file|
file.gsub(/^(\s+fire_rate\s+\S+\s+)(\d+)(\s+)(\d+)/) {
"#{$1}#{($2.to_i/normal).to_i}#{$3}#{(($4.to_i)/flaming).to_i}"
}
}
end
def wall_control_area!(mult)
# WAS: All towers are manned as long as any unit is inside walls
# NOW: Much higher tower control radius
modify('walls'){|file|
file.gsub(/(control_area_radius\s*)(\d+)/){ "#{$1}#{($2.to_i*mult).to_i}" }
}
end
def construction_time_one_turn!
# All buildings can be constructed in one turn
modify('buildings'){|file|
file.gsub(/^(\s+construction\s+)(\d+)/) { "#{$1}1" }
}
end
def building_cost!(cost_mult, mine_extra_mult)
# All buildings 50% more expensive, mines 100% more expensive extra (for total of 300%)
modify('buildings'){|file|
file.gsub(/^(building hinterland_(?:castle_)?mines.*?^\})/m) {
$1.gsub(/(cost\s+)(\d+)/) {"#{$1}#{(mine_extra_mult*$2.to_i).to_i}"}
}.gsub(/(cost\s+)(\d+)/) {"#{$1}#{(cost_mult*$2.to_i).to_i}"}
}
end
def mine_resource!(mult)
# Mines levels are 2x, for total of 3x more money
modify('buildings'){|file|
file.gsub(/(mine_resource\s+)(\d+)/m) {
"#{$1}#{($2.to_i*mult).to_i}"
}
}
end
def bodyguard_size!(mult)
# Bodyguards nerfed to half size, no more 1hp nerfing, as they were useless late in game
# TODO: Their price in custom battles should be reduced
modify('units'){|file|
file.gsub(/(.*\S.*\n)+/) {|para|
if para =~ /\Atype\s*.*Bodyguard/
para = para.sub(/^(soldier\s+\S+, )(\d+)/){ "#{$1}#{($2.to_i*mult).to_i}" }
end
para
}
}
end
def cavalry_cost!(normal, bodyguard)
# Heavy cavalry 50% more expensive.
# Take bodyguards to 25% down because they're half as numerous and
# they were just insanely expensive without this.
modify('units'){|file|
file.gsub(/(.*\S.*\n)+/) {|para|
if para =~ /^category\s*cavalry/
mult = normal
mult = bodyguard if para =~ /\Atype\s*.*Bodyguard/
para = para.sub(/(stat_cost\s*)([0-9, ]*)/) {
pre, data = $1, $2.split(/,\s*/).map{|x|x.to_i}
# 0=turns(1), 6=custom battle too-many-same-units penalty(4)
[1,2,3,4,5,7].each{|i| data[i] = (data[i]*mult).to_i}
"#{pre}#{data.join(', ')}"
}
end
para
}
}
end
def missile_infantry_ammo!(mult)
# More ammo for missile infantry
modify('units'){|file|
file.gsub(/^(category\s+infantry.*?stat_pri\s+\d+,\s*\d+,\s*\S+,\s*\d+,\s*)(\d+)/m){
"#{$1}#{($2.to_i*mult).to_i}"
}
}
end
def rebel_spawn_rates!(mult)
# Make rebels and pirates spawn 10x less often - doesn't really seem to work
modify('strat'){|file|
file.gsub(/^((?:pirate|brigand)_spawn_value\s+)(\d+)/){"#{$1}#{($2.to_i*mult).to_i}"}
}
end
def king_purse!(mult)
# Double king's purse to help small countries
modify('strat'){|file|
file.gsub(/^(denari_kings_purse\s+)(\d+)/){"#{$1}#{($2.to_i*mult).to_i}"}
}
end
def big_garrisons!
# More free garrisons
# Cities: 0 2 3 4 5 6 -> 0 4 6 8 10 12
# Castles: 0 0 0 0 0 -> 1 2 4 7 12
modify('buildings'){|file|
file.sub(/^(building core_building\s*\{)(.*?)(^\})/m){
prefix, description, suffix = $1,$2,$3
description = description.gsub(/(free_upkeep bonus )(\d+)/) {"#{$1}#{$2.to_i*2}"}
"#{prefix}#{description}#{suffix}"
}.sub(/^(building core_castle_building\s*\{)(.*?)(^\})/m){
prefix, description, suffix = $1,$2,$3
by_level = [0, 1, 2, 4, 7, 12]
description = description.gsub(/^(\s*)(law_bonus bonus )(\d+)(\s*?\n)/) { "#{$1}#{$2}#{$3}#{$4}#{$1}free_upkeep bonus #{by_level[$3.to_i]}#{$4}" }
"#{prefix}#{description}#{suffix}"
}
}
end
def basic_infantry_garrisonned_for_free!
# Basic non-merc infantry should be free upkeep - where basic means morale <= 5 and upkeep <= 155,
# what works quite well
modify('units'){|file|
file.gsub(/(.*\S.*\n)+/){|para|
if para =~ /\Atype/ and
para =~ /^category\s+infantry/ and
para !~ /attributes.*\b(free_upkeep_unit|mercenary_unit|general_unit)\b/ and
para !~ /^ownership\s+(slave|saxons)\s*$/
raise "Parse error: #{para}" unless para =~ /^stat_cost\s+\d+,\s+(\d+),\s+(\d+)/
r, u = $1.to_i, $2.to_i
raise "Parse error: #{para}" unless para =~ /stat_mental\s+(\d+)/
m = $1.to_i
para = para.sub(/(attributes[^\r\n]+)/){"#{$1}, free_upkeep_unit"} if m <= 5 && u <= 155
# c = [m <= 5 && u <= 155].select{|x|x}.size
# c += 10 if u == 0 # Irrelevant anyways
# para =~ /dictionary\s*(\S+)/
# puts "#{c} #{m} #{u} #{r} #{$1}"
end
para
}
}
end
def fix_standing!
modify('standing'){|file|
file.sub(/;Trigger 0102_city_razed_decrease_global.*?; make all other factions hate the rebels/m) {|part|
part.gsub(/^;*/, ';')
}
}
end
def fix_rubber_swords!
# Heavy cavalry 50% more expensive.
# Take bodyguards to 25% down because they're half as numerous and
# they were just insanely expensive without this.
modify('units'){|file|
file.gsub(/(.*\S.*\n)+/) {|para|
if para =~ /^formation.*phalanx/
para.sub!(/^(stat_sec\s+)(.*)/){"#{$1}0, 0, no, 0, 0, no, melee_simple, blunt, none, 25, 1"}
end
para
}
}
end
def change_regions!
modify('regions'){|file|
file.gsub(/^([A-Z].*\n(?:\t.*\n)+)/){|region|
yield(region)
}
}
end
def change_region_resources!
change_regions!{|region|
lines = region.split(/\n/)
lines[5] =~ /\A(\s*)(.*?)(\s*)\Z/
st, en = $1, $3
resources = $2.split(/,\s+/)
resources = [] if resources == ['none']
resources = yield(resources).uniq
resources = ['none'] if resources == []
lines[5] = st + resources.join(', ') + en
lines.join("\n") + "\n"
}
end
def no_rebels!
change_region_resources!{|res|
res + ['no_brigands', 'no_pirates']
}
end
def crusades_everywhere!
change_region_resources!{|res|
res += ['crusade', 'jihad', 'horde_target'] unless res.include? 'america'
res
}
end
def no_siege!
modify('buildings'){|file|
file.sub(/^building siege.*?^\}\r\n/m, '').sub(/^building castle_siege.*?^\}\r\n/m, '')
}
end
def all_mercenaries_available!
modify('mercenaries'){|file|
file.gsub(/^\s+unit.*$/){|line|
line.gsub(/ start_year \d+/, '').gsub(/ end_year \d+/, '').gsub(/ events \{(?: |gunpowder_discovered|mongols_invasion_warn)*\}/, '')
}
}
end
def all_buildings_available!
modify('buildings'){|file|
file.gsub(/ and event_counter (?:first_printing_press|gunpowder_discovered|world_is_round) 1/, '')
}
end
def reduce_captain_obvious!
modify('sounds'){|file|
file.gsub(/(unit_under_attack_delay )(\d+)/){ "#{$1}#{$2.to_i*100}"}
}
end
# def more_rebels!
# modify('rebels'){|file|
# file.gsub(/(chance\s+)3/){ "#{$1}50"}
# }
# end
end