Ooops, made a mistake in the logic when I pulled the Mode variable from mob_db.txt (I forgot that it would come across as a string value, not a hex value).
Code: Select all
############################
# MonsterDB plugin for OpenKore by Damokles
#
# This software is open source, licensed under the GNU General Public
# License, version 2.
#
# This plugin extends all functions which use 'checkMonsterCondition'.
# Basically these are AttackSkillSlot, equipAuto, AttackComboSlot, monsterSkill.
#
# Following new checks are possible:
#
# target_Element (list)
# target_notElement (list)
# target_Race (list)
# target_notRace (list)
# target_Size (list)
# target_notSize (list)
# target_hpLeft (range)
#
# In equipAuto you have to leave the target_ part,
# this is due some coding inconsistency in the funtions.pl
#
# You can use monsterEquip if you think that equipAuto is to slow.
# It supports the new equip syntax. It is event-driven and is called
# when a monster: is attacked, changes status, changes element
#
# Note: It will check all monsterEquip blocks but it respects priority.
# If you check in the first block for element fire and in the second
# for race Demi-Human and in both you use different arrows but in the
# Demi-Human block you use a bow, it will take the arrows form the first
# matching block and equip the bow since the fire block didn't specified it.
#
#
# Note: monsterEquip will modify your attackEquip_{slot} so don't be surprised
# about having other attackEquips as you set before.
#
# Be careful with right and leftHand those slots will not be checked for
# two-handed weapons that may conflict.
#
# Example:
# monsterEquip {
# 	target_Element Earth
# 	equip_arrow Fire Arrow
# }
#
# For the element names just scroll a bit down and you'll find it.
# You can check for element Lvls too, eg. target_Element Dark4
#
# $Revision: 5549 $
# $Id: monsterDB.pl 5549 2007-03-21 00:55:47Z h4rry_84 $
############################
package monsterDB;
use 5.010;
use strict;
use Plugins;
use Globals;
use Settings;
use Log qw(message warning error debug);
use Misc qw(bulkConfigModify);
use Translation qw(T TF);
use Utils;
use enum qw(BITMASK:MD_ CANMOVE LOOTER AGGRESSIVE ASSIST CASTSENSOR_IDLE BOSS PLANT CANATTACK DETECTOR CASTSENSOR_CHASE CHANGECHASE ANGRY CHANGETARGET_MELEE CHANGETARGET_CHASE TARGETWEAK RANDOMTARGET);
Plugins::register('monsterDB', 'extends Monster infos', \&onUnload);
my $hooks = Plugins::addHooks(
	['checkMonsterCondition', \&extendedCheck, undef],
	['packet_skilluse', \&onPacketSkillUse, undef],
	['packet/skill_use_no_damage', \&onPacketSkillUseNoDamage, undef],
	['packet_attack', \&onPacketAttack, undef],
	['attack_start', \&onAttackStart, undef],
	['changed_status', \&onStatusChange, undef],
);
my %monsterDB;
my @element_lut = qw(Neutral Water Earth Fire Wind Poison Holy Shadow Ghost Undead);
my @race_lut = qw(Formless Undead Brute Plant Insect Fish Demon Demi-Human Angel Dragon);
my @size_lut = qw(Small Medium Large);
my %skillChangeElement = qw(
	NPC_CHANGEWATER Water
	NPC_CHANGEGROUND Earth
	NPC_CHANGEFIRE Fire
	NPC_CHANGEWIND Wind
	NPC_CHANGEPOISON Poison
	NPC_CHANGEHOLY Holy
	NPC_CHANGEDARKNESS Shadow
	NPC_CHANGETELEKINESIS Ghost
);
my %element_modifiers;
my %raw_modifiers;
$raw_modifiers{lvl1} = "
100     100     100     100     100     100     100     100     25      100
100     25      100     150     50      100     75      100     100     100
100     100     100     50      150     100     75      100     100     100
100     50      150     25      100     100     75      100     100     125
100     175     50      100     25      100     75      100     100     100
100     100     125     125     125     0       75      50      100     -25
100     100     100     100     100     100     0       125     100     150
100     100     100     100     100     50      125     0       100     -25
25      100     100     100     100     100     75      75      125     100
100     100     100     100     100     50      100     0       100     0";
$raw_modifiers{lvl2} = "
100     100     100     100     100     100     100     100     25      100
100     0       100     175     25      100     50      75      100     100
100     100     50      25      175     100     50      75      100     100
100     25      175     0       100     100     50      75      100     150
100     175     25      100     0       100     50      75      100     100
100     75      125     125     125     0       50      25      75      -50
100     100     100     100     100     100     -25     150     100     175
100     100     100     100     100     25      150     -25     100     -50
0       75      75      75      75      75      50      50      150     125
100     75      75      75      75      25      125     0       100     0";
$raw_modifiers{lvl3} = "
100     100     100     100     100     100     100     100     0       100
100     -25     100     200     0       100     25      50      100     125
100     100     0       0       200     100     25      50      100     75
100     0       200     -25     100     100     25      50      100     175
100     200     0       100     -25     100     25      50      100     100
100     50      100     100     100     0       25      0       50      -75
100     100     100     100     100     125     -50     175     100     200
100     100     100     100     100     0       175     -50     100     -75
0       50      50      50      50      50      25      25      175     150
100     50      50      50      50      0       150     0       100     0
";
$raw_modifiers{lvl4} = "
100     100     100     100     100     100     100     100     0       100
100     -50     100     200     0       75      0       25      100     150
100     100     -25     0       200     75      0       25      100     50
100     0       200     -50     100     75      0       25      100     200
100     200     0       100     -50     75      0       25      100     100
100     25      75      75      75      0       0       -25     25      -100
100     75      75      75      75      125     -100    200     100     200
100     75      75      75      75      -25     200     -100    100     -100
0       25      25      25      25      25      0       0       200     175
100     25      25      25      25      -25     175     0       100     0
";
for my $tlevel (1 .. 4) {
		my $x;
        foreach (split /^/ , $raw_modifiers{'lvl'.$tlevel}) {
                next unless m/^\w+/;
                my $base = $element_lut[$x++];
                my @emodifiers = ( split );
                for my $i (0 .. $#element_lut) {
                        $element_modifiers{$element_lut[$i],$tlevel}->{$base} = $emodifiers[$i] / 100;
                }
        }
		delete $raw_modifiers{'lvl'.$tlevel};
}
undef %raw_modifiers;
# can be accessed now as $element_modifiers{"target_element"}{"skill_element"} which returns a multiplier
debug ("MonsterDB: Finished init.\n",'monsterDB',2);
loadMonDB(); # Load MonsterDB into Memory
sub onUnload {
	Plugins::delHooks($hooks);
	%monsterDB = undef;
}
sub loadMonDB {
	%monsterDB = undef;
	debug("MonsterDB: Loading Database\n", 'monsterDB', 2);
	my $file = Settings::getTableFilename('mob_db.txt');
	error("MonsterDB: can't load $file\n", 'monsterDB', 0) unless (-r $file);
	open my $fh, "<", $file;
	my $i = 0;
	while (<$fh>) {
		next unless m/^(\d{4}),/;
		my ($ID, $Sprite_Name, $kROName, $iROName, $LV, $HP, $SP, $EXP, $JEXP, $Range1, $ATK1, $ATK2, $DEF, $MDEF, $STR, $AGI, $VIT, $INT, $DEX, $LUK, $Range2, $Range3, $Scale, $Race, $Element, $Mode, $Speed, $aDelay, $aMotion, $dMotion, $MEXP, $ExpPer, $MVP1id, $MVP1per, $MVP2id, $MVP2per, $MVP3id, $MVP3per, $Drop1id, $Drop1per, $Drop2id, $Drop2per, $Drop3id, $Drop3per, $Drop4id, $Drop4per, $Drop5id, $Drop5per, $Drop6id, $Drop6per, $Drop7id, $Drop7per, $Drop8id, $Drop8per, $Drop9id, $Drop9per, $DropCardid, $DropCardper) = split /,/;
		$monsterDB{$ID}{HP} = $HP;
		$monsterDB{$ID}{mDEF} = $MDEF;
		$monsterDB{$ID}{element} = $element_lut[($Element % 10)];
		$monsterDB{$ID}{elementLevel} = int($Element / 20);
		$monsterDB{$ID}{race} = $race_lut[$Race];
		$monsterDB{$ID}{size} = $size_lut[$Scale];
		$monsterDB{$ID}{mode} = hex($Mode);
		$i++;
	}
	close $fh;
	message TF("%d monsters in database\n", $i), 'monsterDB';		
}
sub extendedCheck {
	my (undef, $args) = @_;
	
	return 0 if !$args->{monster} || $args->{monster}->{nameID} eq '';
	if (!defined $monsterDB{int($args->{monster}->{nameID})}) {
		debug("monsterDB: Monster {$args->{monster}->{name}} not found\n", 'monsterDB', 2);
		return 0;
	} #return if monster is not in DB
	my $ID = int($args->{monster}->{nameID});
	my $element = $monsterDB{$ID}{element};
	my $element_lvl = $monsterDB{$ID}{elementLevel};
	my $race = $monsterDB{$ID}{race};
	my $size = $monsterDB{$ID}{size};
	my $skillBlock;
	($skillBlock = $args->{prefix}) =~ s/_target//;
	if ($args->{monster}->{element} && $args->{monster}->{element} ne '') {
		$element = $args->{monster}->{element};
		debug("monsterDB: Monster $args->{monster}->{name} has changed element to $args->{monster}->{element}\n", 'monsterDB', 3);
	}
	if ($args->{monster}->statusActive('BODYSTATE_STONECURSE, BODYSTATE_STONECURSE_ING')) {
		$element = 'Earth';
		$element_lvl = 1;
		debug("monsterDB: Monster $args->{monster}->{name} is petrified changing element to Earth\n", 'monsterDB', 3);
	}
	if ($args->{monster}->statusActive('BODYSTATE_FREEZING')) {
		$element = 'Water';
		$element_lvl = 1;
		debug("monsterDB: Monster $args->{monster}->{name} is frozen changing element to Water\n", 'monsterDB', 3);
	}
	if ($config{$args->{prefix} . '_Element'}
	&& (!existsInList($config{$args->{prefix} . '_Element'},$element)
		&& !existsInList($config{$args->{prefix} . '_Element'},$element.$element_lvl))) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_notElement'}
	&& (existsInList($config{$args->{prefix} . '_notElement'},$element)
		|| existsInList($config{$args->{prefix} . '_notElement'},$element.$element_lvl))) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_Race'}
	&& !existsInList($config{$args->{prefix} . '_Race'},$race)) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_notRace'}
	&& existsInList($config{$args->{prefix} . '_notRace'},$race)) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_Size'}
	&& !existsInList($config{$args->{prefix} . '_Size'},$size)) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_notSize'}
	&& existsInList($config{$args->{prefix} . '_notSize'},$size)) {
	return $args->{return} = 0;
	}
	if ($config{$args->{prefix} . '_hpLeft'}
	&& !inRange(($monsterDB{$ID}->{HP} + $args->{monster}->{deltaHp}),$config{$args->{prefix} . '_hpLeft'})) {
	return $args->{return} = 0;
	}
	
	if ($config{$args->{prefix} . '_notImmovable'} && (!($monsterDB{$ID}{mode} & MD_CANMOVE))) {
			debug("Will not cast $config{$skillBlock} on an immovable $args->{monster}\n", 'monsterDB', 1);
			return $args->{return} = 0;
	}
			
	my $matkstatus = int((($char->{lv} / 4) + ($char->{int} + $char->{int_bonus}) + (($char->{int} + $char->{int_bonus}) / 2) + (($char->{dex} + $char->{dex_bonus}) / 5) + (($char->{luk} + $char->{luk_bonus}) / 3)) + (($char->{lv} / 4) + ($char->{int} + $char->{int_bonus}) + (($char->{int} + $char->{int_bonus}) / 2) + (($char->{dex} + $char->{dex_bonus}) / 5) + (($char->{luk} + $char->{luk_bonus}) / 3))/abs((($char->{lv} / 4) + ($char->{int} + $char->{int_bonus}) + (($char->{int} + $char->{int_bonus}) / 2) + (($char->{dex} + $char->{dex_bonus}) / 5) + (($char->{luk} + $char->{luk_bonus}) / 3))*2));
	my $matkav = $char->{attack_magic_max} + $matkstatus;
	my $skillLevel = $config{$skillBlock.'_lvl'};
	my $powerMultiplier = 1;
	if ($char->{'statuses'}->{'EFST_MAGICPOWER'}) {
		$powerMultiplier += $char->{'skills'}->{'HW_MAGICPOWER'}->{'lv'} * 0.05;
		debug("I have Magic Power active, which makes my modifier $powerMultiplier\n", 'monsterDB', 1);
	}
	my $elementalMultiplier = 1;
	if ($config{$skillBlock.'_damageType'}) {
		$elementalMultiplier = $element_modifiers{$element,$element_lvl}{$config{$skillBlock.'_damageType'}};
		debug("$config{$skillBlock.'_damageType'} on a $element$element_lvl gives a multiplier of $elementalMultiplier\n", 'monsterDB', 1);
	}
	my $formula = $config{$skillBlock.'_damageFormula'};
	$formula =~ s/mATK/\(\$matkav - \$monsterDB\{\$ID\}\{mDEF\}\)/;
	$formula =~ s/sLVL/\$skillLevel/;
	$formula = int(eval($formula));
	$formula *= $powerMultiplier;
	$formula *= $elementalMultiplier;
	
	if ($config{$skillBlock.'_damageFormula'}
	&& inRange(($monsterDB{$ID}{HP} + $args->{monster}->{deltaHp}),'>= '.$formula)) {
		debug("Rejected $config{$skillBlock} with estimated damage : $formula using skill level $skillLevel\n", 'monsterDB', 1);
		return $args->{return} = 0;
	}
	
	if ($config{$skillBlock.'_damageFormula'}
	&& inRange(($monsterDB{$ID}{HP} + $args->{monster}->{deltaHp}),'< '.$formula)) {
		debug("I think my damage will be around : $formula using skill level $skillLevel\n", 'monsterDB', 1);
		return $args->{return} = 1;
	}
	return 1;
}
sub onPacketSkillUse { monsterHp($monsters{$_[1]->{targetID}}, $_[1]->{disp}) if $_[1]->{disp} }
sub onPacketSkillUseNoDmg {
	my (undef,$args) = @_;
	return 1 unless $monsters{$args->{targetID}} && $monsters{$args->{targetID}}{nameID};
	if (
		$args->{targetID} eq $args->{sourceID} && $args->{targetID} ne $accountID
		&& $skillChangeElement{$args->{skillID}}
	) {
		$monsters{$args->{targetID}}{element} = $skillChangeElement{$args->{skillID}};
		monsterEquip($monsters{$args->{targetID}});
		return 1;
	}
}
sub onPacketAttack { monsterHp($monsters{$_[1]->{targetID}}, $_[1]->{msg}) if $_[1]->{msg} }
sub monsterHp {
	my ($monster, $message) = @_;
	return 1 unless $monster && $monster->{nameID};
	my $ID = int($monster->{nameID});
	return 1 unless my $monsterInfo = $monsterDB{$ID};
	$$message =~ s~(?=\n)~TF(" (HP: %d/%d)", $monsterDB{$ID}{HP} + $monster->{deltaHp}, $monsterDB{$ID}{HP})~se;
}
sub onAttackStart {
	my (undef,$args) = @_;
	monsterEquip($monsters{$args->{ID}});
}
sub onStatusChange {
	my (undef, $args) = @_;
	return unless $args->{changed};
	my $actor = $args->{actor};
	return unless (UNIVERSAL::isa($actor, 'Actor::Monster'));
	my $index = binFind(\@ai_seq, 'attack');
	return unless defined $index;
	return unless $ai_seq_args[$index]->{target} == $actor->{ID};
	monsterEquip($actor);
}
sub monsterEquip {
	my $monster = shift;
	return unless $monster;
	my %equip_list;
	my %args = ('monster' => $monster);
	my $slot;
	for (my $i=0;exists $config{"monsterEquip_$i"};$i++) {
		$args{prefix} = "monsterEquip_${i}_target";
		if (extendedCheck(undef,\%args)) {
			foreach $slot (%equipSlot_lut) {
				if ($config{"monsterEquip_${i}_equip_$slot"}
				&& !$equip_list{"attackEquip_$slot"}
				&& defined Actor::Item::get($config{"monsterEquip_${i}_equip_$slot"})) {
					$equip_list{"attackEquip_$slot"} = $config{"monsterEquip_${i}_equip_$slot"};
					debug "monsterDB: using ".$config{"monsterEquip_${i}_equip_$slot"}."\n",'monsterDB';
				}
			}
		}
	}
	foreach (keys %equip_list) {
		$config{$_} = $equip_list{$_};
	}
	Actor::Item::scanConfigAndEquip('attackEquip');
}
1;
I have also implemented the target_notImmovable config option, set it to 1 and you won't cast on immovable objects. Will implement target_notPlant at some point later.