#! /usr/bin/perl # ====================================================================== # # ====================================================================== use strict; use warnings; use POSIX qw(ceil); # ====================================================================== # Globals # ====================================================================== my $scriptName = $0; my $absoluteScriptName = $scriptName; $scriptName =~ s/^(.*)[\\\/]//; my $inputFile; my $outputFile; my @simulation; my $template = qq%# Use pound to comment Label: NumberOfRounds: CombatConsts: BaseToHit: MaxToHit: MinToHit: ToHitScale: ToHitStep: DamageScale: Attacker: Stats: AttackValue: CombatSkillMods: SpeedMod: WeaponStats: RateOfFire: MaxDamage: MinDamage: Defender: Stats: DefenseValue: ArmorStats: Effectiveness: IncrementAttribute: Attribute: IncrementValue: MaxValue: End: # You can put multiple of these entries in one file, but each must end with End: %; # ====================================================================== # Combat consts # ====================================================================== use constant DEFAULT_BASE_TO_HIT => 90.0; use constant DEFAULT_MAX_TO_HIT => 95.0; use constant DEFAULT_MIN_TO_HIT => 60.0; use constant DEFAULT_TO_HIT_SCALE => 50.0; use constant DEFAULT_TO_HIT_STEP => 5.0; use constant DEFAULT_DAMAGE_SCALE => 500.0; # ====================================================================== # Subroutines # ====================================================================== sub usage() { die "\nUsage:\n\t$scriptName \n\n". "\t$scriptName --template : will dump out a template file for the input\n"; } sub calculateHit { my $hashRef = $_[0]; my $success = 0; my $damage = 0; my $baseToHit = (exists $hashRef->{"CombatConsts"}->{"BaseToHit"}) ? $hashRef->{"CombatConsts"}->{"BaseToHit"} : DEFAULT_BASE_TO_HIT; my $maxToHit = (exists $hashRef->{"CombatConsts"}->{"MaxToHit"}) ? $hashRef->{"CombatConsts"}->{"MaxToHit"} : DEFAULT_MAX_TO_HIT; my $minToHit = (exists $hashRef->{"CombatConsts"}->{"MinToHit"}) ? $hashRef->{"CombatConsts"}->{"MinToHit"} : DEFAULT_MIN_TO_HIT; my $toHitScale = (exists $hashRef->{"CombatConsts"}->{"ToHitScale"}) ? $hashRef->{"CombatConsts"}->{"ToHitScale"} : DEFAULT_TO_HIT_SCALE; my $toHitStep = (exists $hashRef->{"CombatConsts"}->{"ToHitStep"}) ? $hashRef->{"CombatConsts"}->{"ToHitStep"} : DEFAULT_TO_HIT_STEP; my $damageScale = (exists $hashRef->{"CombatConsts"}->{"DamageScale"}) ? $hashRef->{"CombatConsts"}->{"DamageScale"} : DEFAULT_DAMAGE_SCALE; # ----- START BASE RESOLUTION FORMULA ----- my $attackVal = $hashRef->{"Attacker"}->{"Stats"}->{"AttackValue"} - $hashRef->{"Defender"}->{"Stats"}->{"DefenseValue"}; my $resultAttackVal = $attackVal; $attackVal /= $toHitScale; my $stepDir = 0.0; if ($attackVal > $stepDir) { $stepDir = 1.0; } elsif ($attackVal < $stepDir) { $stepDir = -1.0; } my $toHitChance = $baseToHit; my $maxStep = ceil(($baseToHit - $minToHit)/$toHitStep); for (my $i = 1; $i < $maxStep; $i++) { if (($attackVal * $stepDir) > $i) { $toHitChance += $stepDir * $toHitStep; $attackVal -= $stepDir * $i; } else { $toHitChance += ($attackVal/$i) * $toHitStep; last; } } $toHitChance = $maxToHit if ($toHitChance > $maxToHit); $toHitChance = $minToHit if ($toHitChance < $minToHit); if ((rand(99.0) + 1) < $toHitChance) { $success = 1; my $dist = 0.5 + ($resultAttackVal / $damageScale); $damage = distributedRand($hashRef->{"Attacker"}->{"WeaponStats"}->{"MinDamage"}, $hashRef->{"Attacker"}->{"WeaponStats"}->{"MaxDamage"}, $dist); } my @result = ($success, $damage); return @result; # ----- END BASE RESOLUTION FORMULA ----- } sub distributedRand { my ($min, $max, $dist) = @_; my $inverted = 0; my $_min = $min; my $_max = $max; $dist = -1 if ($dist < -1); $dist = 2 if ($dist > 2); if ($min > $max) { $inverted = 1; $min = $_max; $max = $_min; } my $mid = $min + (($max - $min) * $dist); if ($mid < $min) { $max += ($mid-$min); $mid = $min; } if ($mid > $max) { $min += ($mid-$max); $mid = $max; } my $minRand = (rand(int($mid+0.5) - $min) + $min); my $maxRand = (rand($max - int($mid+0.5)) + int($mid+0.5)); my $randNum = (rand($maxRand - $minRand) + $minRand); $randNum = $_min + ($_max - $randNum) if ($inverted); return $randNum; } sub specialCombatSort { my ($a, $b) = @_; return -1 if ($a eq "Label"); return 1 if ($b eq "Label"); return -1 if ($a eq "NumberOfRounds"); return 1 if ($b eq "NumberOfRounds"); return -1 if ($a eq "Attacker"); return 1 if ($b eq "Attacker"); return -1 if ($a eq "Defender"); return 1 if ($b eq "Defender"); return $a cmp $b; } sub printHash { my ($hashRef, $handle, $tab) = @_; foreach my $key (sort { specialCombatSort($a, $b) } keys %{$hashRef}) { if (ref($$hashRef{$key}) eq "HASH") { print $handle "$tab$key\n"; printHash($$hashRef{$key}, $handle, "$tab\t"); } else { print $handle "$tab$key: $$hashRef{$key}\n"; } } } sub getFromHash { my ($key, $hashRef) = @_; my $subKey; $key .= ":" if ($key !~ /:$/); while ($key) { $key =~ s/^([^:\s]+)://; $subKey = $1; last if ($key eq ""); $$hashRef{$subKey} = {} if (!exists $$hashRef{$subKey}); $hashRef = $$hashRef{$subKey}; } return undef if (!exists $$hashRef{$subKey}); return $$hashRef{$subKey}; } sub putIntoHash { my ($key, $val, $hashRef) = @_; my $subKey; $key .= ":" if ($key !~ /:$/); while ($key) { $key =~ s/^([^:\s]+)://; $subKey = $1; last if ($key eq ""); $$hashRef{$subKey} = {} if (!exists $$hashRef{$subKey}); $hashRef = $$hashRef{$subKey}; } if (!defined $val || $val eq "") { $$hashRef{$subKey} = {}; } else { $$hashRef{$subKey} = $val; } } sub parseInput { my $current = ""; my $line = 0; push @simulation, {}; my @currentLabel; open(INPUTFILE, $inputFile) || die "cannot open $inputFile\n"; while () { ++$line; # skip whitespace and comments next if (/^\s+$/ || /^\s*#/); if (/End:/i) { push @simulation, {}; next; } s/^(\s*)(\S+):\s*(.*)//; my $tabs = $1; my $label = $2; my $value = $3; die "Lines must be led with tabs\n" if ($tabs !~ /^\t*$/); my $tabNum = length $tabs; if ($tabNum == @currentLabel) { push @currentLabel, $label; } elsif ($tabNum < @currentLabel) { while ($tabNum < @currentLabel) { pop @currentLabel; } push @currentLabel, $label; } else { die "Error in input at line $line\n"; } my $key = (join ":", @currentLabel); putIntoHash($key, $value, $simulation[$#simulation]); } close(INPUTFILE); # for extra hash ref at the end pop @simulation; } sub printHeader { my $hashRef = $_[0]; printHash($hashRef, *OUTPUT); print OUTPUT "\n"; # grab the resolution formula and put it in, too my $foundResolution = 0; print OUTPUT "BaseResolutionFormula\n"; open (SCRIPT, $absoluteScriptName) || die "Can't open own script\n"; while (