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.