Automatic Skill Level Detection

This section is created for developers and non-developers who think that he/she has a good (and realistic) idea that might contribute to the OpenKore community.

Moderator: Moderators

Message
Author
xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#21 Post by xlr82xs »

OK, since I have them KINDA working in a fashion where they only crash every couple of minutes on my test account, what would people like to see first ?

Smart Skill Level blocks, or support for casting between / around yourself and a mob, or making something like runFromTarget specifiable in a skill block, so you can use monster, target_Race, target_Speed etc modifiers on it ?

Eventually I'll get them all in the public release, but I can't decide on priority.

Also, would anyone have any objections to me renaming this plugin ? It's nolonger really just monsterDB style stuff anymore.

As a little teaser :

Code: Select all

[dist=14.8] Monster Pinguicula (1): *$!@#*
You are casting Fire Wall on location (97, 318) (Delay: 261ms)
Packet Tokenizer: Unknown switch: 099F
[ 83/ 32] Monster Pinguicula (0) attacks you (Dmg: 398) (Delay: 600ms)
You are casting Fire Bolt on Monster Pinguicula (0) (Delay: 750ms)
You failed to cast Fire Bolt
[ 78/ 34] Monster Pinguicula (0) attacks you (Dmg: 426) (Delay: 600ms)
You are casting Fire Wall on location (94, 317) (Delay: 261ms)
Packet Tokenizer: Unknown switch: 099F
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 262) (Delay: 450ms) (HP: 12796/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 182) (Delay: 450ms) (HP: 12614/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 192) (Delay: 450ms) (HP: 12422/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 150) (Delay: 450ms) (HP: 12272/13058)
You are casting Fire Bolt on Monster Pinguicula (0) (Delay: 750ms)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 176) (Delay: 450ms) (HP: 12096/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 178) (Delay: 450ms) (HP: 11918/13058)
[ 78/ 31] You use Fire Bolt (Lv: 4) on Monster Pinguicula (0) (Dmg: 1576) (Delay: 450ms) (HP: 10342/13058)
You are now: Action Delay (Duration: 1.6s)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 176) (Delay: 450ms) (HP: 10166/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 148) (Delay: 450ms) (HP: 10018/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 200) (Delay: 450ms) (HP: 9818/13058)
You are no longer: Action Delay
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 256) (Delay: 450ms) (HP: 9562/13058)
Skill Fire Wall failed: Requirement (error number 10)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 204) (Delay: 450ms) (HP: 9358/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 222) (Delay: 450ms) (HP: 9136/13058)
You are casting Fire Bolt on Monster Pinguicula (0) (Delay: 750ms)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 160) (Delay: 450ms) (HP: 8976/13058)
Unknown #7819 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 138) (Delay: 450ms) (HP: 8838/13058)
[ 72/ 31] You use Fire Bolt (Lv: 4) on Monster Pinguicula (0) (Dmg: 1640) (Delay: 450ms) (HP: 7198/13058)
You are now: Action Delay (Duration: 1.6s)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 244) (Delay: 450ms) (HP: 6954/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 152) (Delay: 450ms) (HP: 6802/13058)
You are no longer: Action Delay
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 158) (Delay: 450ms) (HP: 6644/13058)
You are casting Fire Wall on location (94, 317) (Delay: 261ms)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 176) (Delay: 450ms) (HP: 6468/13058)
Packet Tokenizer: Unknown switch: 099F
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 240) (Delay: 450ms) (HP: 6228/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 232) (Delay: 450ms) (HP: 5996/13058)
You are casting Fire Bolt on Monster Pinguicula (0) (Delay: 750ms)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 192) (Delay: 450ms) (HP: 5804/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 192) (Delay: 450ms) (HP: 5612/13058)
[ 73/ 32] You use Fire Bolt (Lv: 4) on Monster Pinguicula (0) (Dmg: 1552) (Delay: 450ms) (HP: 4060/13058)
You are now: Action Delay (Duration: 1.6s)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 256) (Delay: 450ms) (HP: 3804/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 202) (Delay: 450ms) (HP: 3602/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 176) (Delay: 450ms) (HP: 3426/13058)
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 166) (Delay: 450ms) (HP: 3260/13058)
You are no longer: Action Delay
Unknown #7865 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 178) (Delay: 450ms) (HP: 3082/13058)
Skill Fire Wall failed: Requirement (error number 10)
Unknown #49490 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 228) (Delay: 450ms) (HP: 2854/13058)
Unknown #49490 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 218) (Delay: 450ms) (HP: 2636/13058)
You are casting Fire Bolt on Monster Pinguicula (0) (Delay: 750ms)
Unknown #49490 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 164) (Delay: 450ms) (HP: 2472/13058)
Unknown #49490 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 242) (Delay: 450ms) (HP: 2230/13058)
[ 73/ 31] You use Fire Bolt (Lv: 4) on Monster Pinguicula (0) (Dmg: 2168) (Delay: 450ms) (HP: 62/13058)
You are now: Action Delay (Duration: 1.6s)
Unknown #49490 uses Fire Wall (Lv: 10) on Monster Pinguicula (0) (Dmg: 244) (Delay: 450ms) (HP: -182/13058)
You have gained 3996/2991 (0.00%/0.00%) Exp
Item Appeared: Sharp Leaf (0) x 1 (97, 317)
Item Appeared: Huge Leaf (1) x 1 (98, 316)
Item Appeared: Brown Root (2) x 1 (96, 317)
Target died
I guess I should probably spend some time looking at why AoE spells aren't being reported correctly on iRO since that will make what I'm trying to achieve here work a lot better.

franibaflo
Human
Human
Posts: 35
Joined: 06 May 2012, 10:11
Noob?: Yes

Re: Automatic Skill Level Detection

#22 Post by franibaflo »

xlr82xs wrote: what would people like to see first ?

Smart Skill Level blocks, or support for casting between / around yourself and a mob, or making something like runFromTarget specifiable in a skill block, so you can use monster, target_Race, target_Speed etc modifiers on it ?
Smart Skill Level Blocks!!!! Just to remain true to the original thread title: Automatic Skill Level Detection. Also, it will make the transition of changing config blocks smoother because, the way I see it, it's easier to change skill levels and input damage formulas than to organize arguments/conditions to make bot run away from monsters. Also there seems to be more macros and plugins discussing how to run from monsters whereas SMART SKILL LEVEL DETECTION IS UNIQUE - truly one of a kind! :)

Keep up the good work!

User avatar
kLabMouse
Administrator
Administrator
Posts: 1301
Joined: 24 Apr 2008, 12:02

Re: Automatic Skill Level Detection

#23 Post by kLabMouse »

WOW! I RLY Like it.
Tho. I would suggest to wrap it up as a plugin. So it can hook some Internals (GLOB's) and make sure that Skill USE task get's it's proper arguments.

xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#24 Post by xlr82xs »

kLabMouse wrote:WOW! I RLY Like it.
Tho. I would suggest to wrap it up as a plugin. So it can hook some Internals (GLOB's) and make sure that Skill USE task get's it's proper arguments.
I can't really see a way of doing this not as either a plugin or a patch.
And since my SVN access seems to not be present on my SourceForge account anymore, a plugin it is.

Speaking of which, here is the beta version of automatic skill level calculation :

Code: Select all

###########################
# Enhanced Casting plugin for OpenKore by xlr82xs
#
# This software is open source, licensed under the GNU General Public
# License, version 2.
#
# This plugin is based on the work of Damokles and kaliwanagan
#
# 
# 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)
# target_immovable boolean
#
# 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
#
#
# new config block enhancedCasting <skill name> {}
# supports all the enhanced modifiers (except _hpLeft)
# ignores selected level and automatically selects it for you.
#



package enhancedCasting;

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 AI;
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);
use POSIX qw(floor);

Plugins::register('enhancedCasting', 'Extends Skill Selection and Placement', \&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],
	['AI_post', \&choose, 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 $currentTarget;
my %skillUse;
my %delay;

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;

debug ("Enhanced Casting: Finished init.\n",'enhancedCasting',2);
loadMonDB(); # Load MonsterDB into Memory

sub onUnload {
	Plugins::delHooks($hooks);
	%monsterDB = undef;
}

sub choose {
    if (AI::action eq 'enhancedCasting') {
            my $args = AI::args;
            if ($args->{'stage'} eq 'end') {
                    AI::dequeue;
            } elsif ($args->{'stage'} eq 'skillUse') {
                    main::ai_skillUse(
                        $args->{'handle'},
                        $args->{'lvl'},
                        $args->{'maxCastTime'},
                        $args->{'minCastTime'},
                        $args->{'target'}
                    );
                    $args->{'stage'} = 'end';
            } elsif (!$currentTarget) {
                    $args->{'stage'} = 'end';
            }
    }
    if ($currentTarget && AI::action eq "attack") {
            selectSkill();
    }
}

sub selectSkill {
    my $prefix = "enhancedCasting_";
    my $i = 0;
    while (exists $config{$prefix.$i}) {
            if ((main::checkSelfCondition($prefix.$i)) &&
                    main::timeOut($delay{$prefix.$i."_blockDelayBeforeUse"})
            ) {
                    my $skillObj = Skill->new(name => $config{$prefix.$i});
                    unless ($skillObj->getHandle) {
                            my $msg = "Unknown skill name ".$config{$prefix.$i}." in $prefix.$i\n";
                            error $msg;
                            configModify($prefix.$i."_disabled", 1);
                            next;
                    }

                    my %skill;
					my $ID = int($currentTarget->{nameID});
					my $element = $monsterDB{$ID}{element};
					my $element_lvl = $monsterDB{$ID}{elementLevel};
					my $race = $monsterDB{$ID}{race};
					my $size = $monsterDB{$ID}{size};
					
                    $delay{$prefix.$i."_blockDelayBeforeUse"}{'timeout'} = $config{$prefix.$i."_blockDelayBeforeUse"};
                    if (!$delay{$prefix.$i."_blockDelayBeforeUse"}{'set'}) {
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'time'} = time;
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'set'} = 1;
                    }
   
                    $delay{$prefix.$skillObj->getHandle."_skillDelay"}{'timeout'} = $config{$prefix.$i."_skillDelay"};
                    if ($skillUse{$skillObj->getIDN}) { # set the delays only when the skill gets successfully cast
                            $delay{$prefix.$skillObj->getHandle."_skillDelay"}{'time'} = time;
                            $skillUse{$skillObj->getIDN} = 0;
                    }
					
					
					if ($currentTarget->{element} && $currentTarget->{element} ne '') {
						$element = $currentTarget->{element};
						debug("enhancedCasting: Monster $currentTarget->{name} has changed element to $currentTarget->{element}\n", 'enhancedCasting', 3);
					}

					if ($currentTarget->statusActive('BODYSTATE_STONECURSE, BODYSTATE_STONECURSE_ING')) {
						$element = 'Earth';
						$element_lvl = 1;
						debug("enhancedCasting: Monster $currentTarget->{name} is petrified changing element to Earth\n", 'enhancedCasting', 3);
					}

					if ($currentTarget->statusActive('BODYSTATE_FREEZING')) {
						$element = 'Water';
						$element_lvl = 1;
						debug("enhancedCasting: Monster $currentTarget->{name} is frozen changing element to Water\n", 'enhancedCasting', 3);
					}
		
                    if (    main::timeOut($delay{$prefix.$skillObj->getHandle."_skillDelay"}) &&
                            main::timeOut($delay{$prefix.$i."_blockDelayAfterUse"}) &&
                            ((!$config{$prefix.$i."_target"}) || existsInList($config{$prefix.$i."_target"}, $currentTarget->{'name'})) &&
                            ((!$config{$prefix.$i."_notTarget"}) || !existsInList($config{$prefix.$i."_notTarget"}, $currentTarget->{'name'})) &&
							((!$config{$prefix.$i."_Element"}) || (existsInList($config{$prefix.$i."_Element"}, $element) || existsInList($config{$prefix.$i."_Element"}, $element.$element_lvl))) &&
							((!$config{$prefix.$i."_notElement"}) || (!existsInList($config{$prefix.$i."_notElement"}, $element) && !existsInList($config{$prefix.$i."_notElement"}, $element.$element_lvl))) &&
							((!$config{$prefix.$i."_Race"}) || existsInList($config{$prefix.$i."_Race"}, $race)) &&
							((!$config{$prefix.$i."_notRace"}) || !existsInList($config{$prefix.$i."_notRace"}, $race)) &&
							((!$config{$prefix.$i."_Size"}) || existsInList($config{$prefix.$i."_Size"}, $size)) &&
							((!$config{$prefix.$i."_notSize"}) || !existsInList($config{$prefix.$i."_notSize"}, $size)) &&
							((!$config{$prefix.$i."_notImmovable"}) || ($monsterDB{$ID}{mode} & MD_CANMOVE))
                    ) {
							my $monsterID = $currentTarget->{type};
							my $castLevel = 10;
							my $damageNeeded = $monsterDB{$monsterID}{HP} + $currentTarget->{deltaHp};
							
                            $skill{'handle'} = $skillObj->getHandle;
                            $skill{'skillID'} = $skillObj->getIDN;
							my $formula = $config{$prefix.$i.'_damageFormula'};
							my $damageType = $config{$prefix.$i.'_damageType'};
							for (my $i = 1; $i <= $char->{'skills'}->{$skillObj->getHandle}->{'lv'}; $i++) {
								$castLevel = $i;
								my $estimatedDamage = calcSkillDamage($formula, $i, int($currentTarget->{type}), $damageType);
								debug("Checking $skill{'handle'} at level $i need $damageNeeded estimate $estimatedDamage\n", 'enhancedCasting', 1);
								last if ($estimatedDamage >= $damageNeeded);
							}
                            $skill{'lvl'} = $castLevel;
                            $skill{'maxCastTime'} = $config{$prefix.$i."_maxCastTime"};
                            $skill{'minCastTime'} = $config{$prefix.$i."_minCastTime"};
							$skill{'target'} = $currentTarget->{ID};
                            $skill{'stage'} = 'skillUse';
                            $skillUse{$skill{'skillID'}} = 0;
                            AI::queue('enhancedCasting', \%skill);
                            $delay{$prefix.$i."_blockDelayAfterUse"}{'timeout'} = $config{$prefix.$i."_blockDelayAfterUse"};
                            $delay{$prefix.$i."_blockDelayAfterUse"}{'time'} = time;
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'set'} = 0;
							debug("Selected level $skill{'lvl'} for $skill{'handle'} to attack $currentTarget->{'name_given'}\n", 'enhancedCasting', 1);
                            last;
						}
            }
            $i++;
    }
}

sub loadMonDB {
	%monsterDB = undef;
	debug("Enhanced Casting: Loading Database\n", 'enhancedCasting', 2);
	my $file = Settings::getTableFilename('mob_db.txt');
	error("Enhanced Casting: can't load $file for monster information\n", 'enhancedCasting', 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("Enhanced Casting: Monster {$args->{monster}->{name}} not found\n", 'enhancedCasting', 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("enhancedCasting: Monster $args->{monster}->{name} has changed element to $args->{monster}->{element}\n", 'enhancedCasting', 3);
	}

	if ($args->{monster}->statusActive('BODYSTATE_STONECURSE, BODYSTATE_STONECURSE_ING')) {
		$element = 'Earth';
		$element_lvl = 1;
		debug("enhancedCasting: Monster $args->{monster}->{name} is petrified changing element to Earth\n", 'enhancedCasting', 3);
	}

	if ($args->{monster}->statusActive('BODYSTATE_FREEZING')) {
		$element = 'Water';
		$element_lvl = 1;
		debug("enhancedCasting: Monster $args->{monster}->{name} is frozen changing element to Water\n", 'enhancedCasting', 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", 'enhancedCasting', 1);
			return $args->{return} = 0;
	}
			

	my $skillLevel = $config{$skillBlock.'_lvl'};
	
	my $potentialDamage = calcSkillDamage($config{$skillBlock.'_damageFormula'}, $config{$skillBlock.'_lvl'}, int($args->{monster}->{nameID}), $config{$skillBlock.'_damageType'});
	if ($config{$skillBlock.'_damageFormula'}
	&& inRange(($monsterDB{$ID}{HP} + $args->{monster}->{deltaHp}),'>= '.$potentialDamage)) {
		debug("Rejected $config{$skillBlock} with estimated damage : $potentialDamage using skill level $skillLevel\n", 'enhancedCasting', 1);
		return $args->{return} = 0;
	}

	return 1;
}

sub statusMATK {
	return floor(($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));
}

sub elementalMultiplier {
	my ($targetElement, $attackElement) = @_;
	if (defined $attackElement) {
		return $element_modifiers{$targetElement}->{$attackElement};
	} else {
		return 1;
	}
}

sub powerMultiplier {
	if ($char->{'statuses'}->{'EFST_MAGICPOWER'}) {
		return 1 + ($char->{'skills'}->{'HW_MAGICPOWER'}->{'lv'} * 0.05);
	} else {
		return 1;
	}
}

sub calcSkillDamage {
	my ($formula, $skillLevel, $monsterID, $attackElement) = @_;
	my $matkstatus = statusMATK();
	my $matkav = $char->{attack_magic_max} + $matkstatus;
	my $mDEF_Bypass = 0;
	my $int = $char->{int} + $char->{int_bonus};
	$formula =~ s/mATK/\(\$matkav - \(\$monsterDB\{\$monsterID\}\{mDEF\} - \$mDEF_Bypass\)\)/;
	$formula =~ s/sLVL/\$skillLevel/;
	$formula =~ s/bLVL/\$char->{lv}/;
	$formula =~ s/INT/\$int/;
	$formula = int(eval($formula));
	$formula *= powerMultiplier();
	$formula *= elementalMultiplier($monsterDB{$monsterID}{element}.$monsterDB{$monsterID}{elementLevel}, $attackElement);
	return floor($formula);
}

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) = @_;
	$currentTarget = $monsters{$args->{ID}};
	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",'enhancedCasting';
				}
			}
		}
	}
	foreach (keys %equip_list) {
		$config{$_} = $equip_list{$_};
	}
	Actor::Item::scanConfigAndEquip('attackEquip');
}

1;

an example config block (note I've probably got the formula wrong, and I haven't coded in support for everything you will need in the formulas yet)

Code: Select all

enhancedCasting Crimson Rock {
	damageFormula mATK * floor((((sLVL * 200) + INT) * (bLVL / 100)) / 10)
	dist 9
	disabled 0
	target_notElement Fire
	whenStatusInactive Action Delay, Crimson RockDelay
	damageType Fire
}

xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#25 Post by xlr82xs »

New version.

I forgot to do the fall through.

It's not elegant, it needs to be re-worked, but it works for now.
If anyone can see a nicer way of doing this, please tell me.

config :

Code: Select all

enhancedCasting Fire Bolt {
	damageFormula mATK * sLVL
	dist 9
	notElement Fire
	damageType Fire
	whenStatusInactive Action Delay
	fallThrough 1
	disabled 0
}

enhancedCasting Crimson Rock {
	damageFormula mATK * floor((((sLVL * 200) + INT) * (bLVL / 100)) / 10)
	dist 11
	disabled 0
	notElement Fire
	whenStatusInactive Action Delay, Crimson RockDelay
	damageType Fire
}

enhancedCasting Fire Bolt {
	damageFormula mATK * sLVL
	dist 9
	notElement Fire
	damageType Fire
	whenStatusInactive Action Delay
	disabled 0
}


plugin :

Code: Select all

###########################
# Enhanced Casting plugin for OpenKore by xlr82xs
#
# This software is open source, licensed under the GNU General Public
# License, version 2.
#
# This plugin is based on the work of Damokles and kaliwanagan
#
# 
# 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)
# target_immovable boolean
#
# 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
#
#
# new config block enhancedCasting <skill name> {}
# supports all the enhanced modifiers (except _hpLeft)
# ignores selected level and automatically selects it for you.
#



package enhancedCasting;

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 AI;
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);
use POSIX qw(floor);
use Data::Dumper;

Plugins::register('enhancedCasting', 'Extends Skill Selection and Placement', \&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],
	['AI_post', \&choose, 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 $currentTarget;
my %skillUse;
my %delay;

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;

debug ("Enhanced Casting: Finished init.\n",'enhancedCasting',2);
loadMonDB(); # Load MonsterDB into Memory

sub onUnload {
	Plugins::delHooks($hooks);
	%monsterDB = undef;
}

sub choose {
    if (AI::action eq 'enhancedCasting') {
            my $args = AI::args;
            if ($args->{'stage'} eq 'end') {
                    AI::dequeue;
            } elsif ($args->{'stage'} eq 'skillUse') {
                    main::ai_skillUse(
                        $args->{'handle'},
                        $args->{'lvl'},
                        $args->{'maxCastTime'},
                        $args->{'minCastTime'},
                        $args->{'target'}
                    );
                    $args->{'stage'} = 'end';
            } elsif (!$currentTarget) {
                    $args->{'stage'} = 'end';
            }
    }
    if ($currentTarget && AI::action eq "attack") {
            selectSkill();
    }
}

sub selectSkill {
    my $prefix = "enhancedCasting_";
    my $i = 0;
    while (exists $config{$prefix.$i}) {
		my $fellThrough = 0;
            if ((main::checkSelfCondition($prefix.$i)) &&
                    main::timeOut($delay{$prefix.$i."_blockDelayBeforeUse"})
            ) {
                    my $skillObj = Skill->new(name => $config{$prefix.$i});
                    unless ($skillObj->getHandle) {
                            my $msg = "Unknown skill name ".$config{$prefix.$i}." in $prefix.$i\n";
                            error $msg;
                            configModify($prefix.$i."_disabled", 1);
                            next;
                    }
					debug("Trying $config{$prefix.$i}\n", 'enhancedCasting', 1);
                    my %skill;
					my $ID = int($currentTarget->{nameID});
					my $element = $monsterDB{$ID}{element};
					my $element_lvl = $monsterDB{$ID}{elementLevel};
					my $race = $monsterDB{$ID}{race};
					my $size = $monsterDB{$ID}{size};
                    my $charpos = main::calcPosition($char);
                    my $monsterpos = main::calcPosition($currentTarget);
					
					
                    $delay{$prefix.$i."_blockDelayBeforeUse"}{'timeout'} = $config{$prefix.$i."_blockDelayBeforeUse"};
                    if (!$delay{$prefix.$i."_blockDelayBeforeUse"}{'set'}) {
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'time'} = time;
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'set'} = 1;
                    }
   
                    $delay{$prefix.$skillObj->getHandle."_skillDelay"}{'timeout'} = $config{$prefix.$i."_skillDelay"};
                    if ($skillUse{$skillObj->getIDN}) { # set the delays only when the skill gets successfully cast
                            $delay{$prefix.$skillObj->getHandle."_skillDelay"}{'time'} = time;
                            $skillUse{$skillObj->getIDN} = 0;
                    }
					
					
					if ($currentTarget->{element} && $currentTarget->{element} ne '') {
						$element = $currentTarget->{element};
						debug("enhancedCasting: Monster $currentTarget->{name} has changed element to $currentTarget->{element}\n", 'enhancedCasting', 3);
					}

					if ($currentTarget->statusActive('BODYSTATE_STONECURSE, BODYSTATE_STONECURSE_ING')) {
						$element = 'Earth';
						$element_lvl = 1;
						debug("enhancedCasting: Monster $currentTarget->{name} is petrified changing element to Earth\n", 'enhancedCasting', 3);
					}

					if ($currentTarget->statusActive('BODYSTATE_FREEZING')) {
						$element = 'Water';
						$element_lvl = 1;
						debug("enhancedCasting: Monster $currentTarget->{name} is frozen changing element to Water\n", 'enhancedCasting', 3);
					}
							
                    if (    main::timeOut($delay{$prefix.$skillObj->getHandle."_skillDelay"}) &&
                            main::timeOut($delay{$prefix.$i."_blockDelayAfterUse"}) &&
                            ((!$config{$prefix.$i."_target"}) || existsInList($config{$prefix.$i."_target"}, $currentTarget->{'name'})) &&
                            ((!$config{$prefix.$i."_notTarget"}) || !existsInList($config{$prefix.$i."_notTarget"}, $currentTarget->{'name'})) &&
							((!$config{$prefix.$i."_Element"}) || (existsInList($config{$prefix.$i."_Element"}, $element) || existsInList($config{$prefix.$i."_Element"}, $element.$element_lvl))) &&
							((!$config{$prefix.$i."_notElement"}) || (!existsInList($config{$prefix.$i."_notElement"}, $element) && !existsInList($config{$prefix.$i."_notElement"}, $element.$element_lvl))) &&
							((!$config{$prefix.$i."_Race"}) || existsInList($config{$prefix.$i."_Race"}, $race)) &&
							((!$config{$prefix.$i."_notRace"}) || !existsInList($config{$prefix.$i."_notRace"}, $race)) &&
							((!$config{$prefix.$i."_Size"}) || existsInList($config{$prefix.$i."_Size"}, $size)) &&
							((!$config{$prefix.$i."_notSize"}) || !existsInList($config{$prefix.$i."_notSize"}, $size)) &&
							((!$config{$prefix.$i."_notImmovable"}) || ($monsterDB{$ID}{mode} & MD_CANMOVE)) &&
							(round(distance($charpos, $monsterpos)) <= $config{$prefix.$i."_dist"})
                    ) {
							my $monsterID = $currentTarget->{type};
							my $castLevel = 10;
							my $damageNeeded = $monsterDB{$monsterID}{HP} + $currentTarget->{deltaHp};
							my $estimatedDamage;
							
                            $skill{'handle'} = $skillObj->getHandle;
                            $skill{'skillID'} = $skillObj->getIDN;
							my $formula = $config{$prefix.$i.'_damageFormula'};
							my $damageType = $config{$prefix.$i.'_damageType'};
							for (my $x = 1; $x <= $char->{'skills'}->{$skillObj->getHandle}->{'lv'}; $x++) {
								$castLevel = $x;
								$estimatedDamage = calcSkillDamage($formula, $x, int($currentTarget->{type}), $damageType);
								debug("Checking $skill{'handle'} at level $x need $damageNeeded estimate $estimatedDamage\n", 'enhancedCasting', 1);
								last if ($estimatedDamage >= $damageNeeded);
							}
							if (($estimatedDamage < $damageNeeded) && ($config{$prefix.$i."_fallThrough"})) {
								debug("I am allowed to fall through to the next skill because my damage is too low\n", 'enhancedCasting', 1);
								$fellThrough = 1;
							}
							$i++;
							next if $fellThrough;
                            $skill{'lvl'} = $castLevel;
                            $skill{'maxCastTime'} = $config{$prefix.$i."_maxCastTime"};
                            $skill{'minCastTime'} = $config{$prefix.$i."_minCastTime"};
							$skill{'target'} = $currentTarget->{ID};
                            $skill{'stage'} = 'skillUse';
                            $skillUse{$skill{'skillID'}} = 0;
                            AI::queue('enhancedCasting', \%skill);
                            $delay{$prefix.$i."_blockDelayAfterUse"}{'timeout'} = $config{$prefix.$i."_blockDelayAfterUse"};
                            $delay{$prefix.$i."_blockDelayAfterUse"}{'time'} = time;
                            $delay{$prefix.$i."_blockDelayBeforeUse"}{'set'} = 0;
							debug("Selected level $skill{'lvl'} for $skill{'handle'} to attack $currentTarget->{'name_given'}\n", 'enhancedCasting', 1);
                            last;
						}
            }
            $i++;
    }
}

sub loadMonDB {
	%monsterDB = undef;
	debug("Enhanced Casting: Loading Database\n", 'enhancedCasting', 2);
	my $file = Settings::getTableFilename('mob_db.txt');
	error("Enhanced Casting: can't load $file for monster information\n", 'enhancedCasting', 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("Enhanced Casting: Monster {$args->{monster}->{name}} not found\n", 'enhancedCasting', 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("enhancedCasting: Monster $args->{monster}->{name} has changed element to $args->{monster}->{element}\n", 'enhancedCasting', 3);
	}

	if ($args->{monster}->statusActive('BODYSTATE_STONECURSE, BODYSTATE_STONECURSE_ING')) {
		$element = 'Earth';
		$element_lvl = 1;
		debug("enhancedCasting: Monster $args->{monster}->{name} is petrified changing element to Earth\n", 'enhancedCasting', 3);
	}

	if ($args->{monster}->statusActive('BODYSTATE_FREEZING')) {
		$element = 'Water';
		$element_lvl = 1;
		debug("enhancedCasting: Monster $args->{monster}->{name} is frozen changing element to Water\n", 'enhancedCasting', 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", 'enhancedCasting', 1);
			return $args->{return} = 0;
	}
			

	my $skillLevel = $config{$skillBlock.'_lvl'};
	
	my $potentialDamage = calcSkillDamage($config{$skillBlock.'_damageFormula'}, $config{$skillBlock.'_lvl'}, int($args->{monster}->{nameID}), $config{$skillBlock.'_damageType'});
	if ($config{$skillBlock.'_damageFormula'}
	&& inRange(($monsterDB{$ID}{HP} + $args->{monster}->{deltaHp}),'>= '.$potentialDamage)) {
		debug("Rejected $config{$skillBlock} with estimated damage : $potentialDamage using skill level $skillLevel\n", 'enhancedCasting', 1);
		return $args->{return} = 0;
	}

	return 1;
}

sub statusMATK {
	return floor(($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));
}

sub elementalMultiplier {
	my ($targetElement, $attackElement) = @_;
	if (defined $attackElement) {
		return $element_modifiers{$targetElement}->{$attackElement};
	} else {
		return 1;
	}
}

sub powerMultiplier {
	if ($char->{'statuses'}->{'EFST_MAGICPOWER'}) {
		return 1 + ($char->{'skills'}->{'HW_MAGICPOWER'}->{'lv'} * 0.05);
	} else {
		return 1;
	}
}

sub calcSkillDamage {
	my ($formula, $skillLevel, $monsterID, $attackElement) = @_;
	my $matkstatus = statusMATK();
	my $matkav = $char->{attack_magic_max} + $matkstatus;
	my $mDEF_Bypass = 0;
	my $int = $char->{int} + $char->{int_bonus};
	$formula =~ s/mATK/\(\$matkav - \(\$monsterDB\{\$monsterID\}\{mDEF\} - \$mDEF_Bypass\)\)/;
	$formula =~ s/sLVL/\$skillLevel/;
	$formula =~ s/bLVL/\$char->{lv}/;
	$formula =~ s/INT/\$int/;
	$formula = int(eval($formula));
	$formula *= powerMultiplier();
	$formula *= elementalMultiplier($monsterDB{$monsterID}{element}.$monsterDB{$monsterID}{elementLevel}, $attackElement);
	return floor($formula);
}

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) = @_;
	$currentTarget = $monsters{$args->{ID}};
	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",'enhancedCasting';
				}
			}
		}
	}
	foreach (keys %equip_list) {
		$config{$_} = $equip_list{$_};
	}
	Actor::Item::scanConfigAndEquip('attackEquip');
}

1;

Edit : Forgot I had to take dist into account because I'm actually taking the place of attackSkillSlot, if you notice that your charactor just goes into a loop not moving close enough to a mob to actually cast on it, I have edited this post to fix that.
Sorry.

At some point, maybe tonight I'll finish off the mATK calculation stuff, which will make the skill selection better.

xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#26 Post by xlr82xs »

Initial commit : https://svn.code.sf.net/p/openkore/code ... ing/trunk/

I'll be keeping trunk as stable obviously, with development sitting in https://svn.code.sf.net/p/openkore/code ... ches/beta/ for a while before getting merged into trunk.

Would it be useful (note, I do not have time to do all of the maintenance this will require) to move the damage formula for skills out of the config block into another table file ? Obviously this would be maintained for each server/patch which I will not have time to do, but it would give people a starting point.

We could settle on a standardised way of representing the formula, which would make keeping the calculation code up to date simpler.

The reason I am actually considering this now rather than sticking to "make the user do it" is that there are server differences between how equipment works, and thus how the equipment and weapon mATK needs to be calculated, and rather than having yet another formula the user needs to figure out, I will probably need to do a table file for it.


Also, in the beta version (trunk doesn't have fallthrough implemented) can someone else give me some feedback on having non enhancedCasting skill blocks ?
I've got the feeling that I might need to make some changes to force the testing of enhancedCasting before autoAttackSkill so you don't get fall-throughs.
Last edited by xlr82xs on 25 Sep 2013, 20:06, edited 1 time in total.

User avatar
kLabMouse
Administrator
Administrator
Posts: 1301
Joined: 24 Apr 2008, 12:02

Re: Automatic Skill Level Detection

#27 Post by kLabMouse »

xlr82xs wrote:Initial commit : https://svn.code.sf.net/p/openkore/code ... ing/trunk/

I'll be keeping trunk as stable obviously, with development sitting in https://svn.code.sf.net/p/openkore/code ... ches/beta/ for a while before getting merged into trunk.

Would it be useful (note, I do not have time to do all of the maintenance this will require) to move the damage formula for skills out of the config block into another table file ? Obviously this would be maintained for each server/patch which I will not have time to do, but it would give people a starting point.

We could settle on a standardised way of representing the formula, which would make keeping the calculation code up to date simpler.

The reason I am actually considering this now rather than sticking to "make the user do it" is that there are server differences between how equipment works, and thus how the equipment and weapon mATK needs to be calculated, and rather than having yet another formula the user needs to figure out, I will probably need to do a table file for it.
From my point of view. Having Min/Max DMG formula build in could be nice.
Also. I suggest to check out how SmartHeal function is made. That way the lvl setting may not be needed is proper hooks are made.

xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#28 Post by xlr82xs »

kLabMouse wrote: Also. I suggest to check out how SmartHeal function is made. That way the lvl setting may not be needed is proper hooks are made.
Ah, that is already done and committed using the "enhancedCasting" config block. It is mentioned in the notes at the top of the plugin, I just haven't finished documentation for it yet.

if you use an attackSkillSlot you can specify the spell level and it will be used (and then just chain multiple attackSkillSlot blocks in your config to have it accept/reject based on damageFormula)

alternatively, you can used an "enhancedCasting" block like :
enhancedCasting Crimson Rock {
damageFormula mATK * floor((((sLVL * 200) + INT) * (bLVL / 100)) / 10)
dist 11
disabled 0
notElement Fire
whenStatusInactive Action Delay, Crimson RockDelay
damageType Fire
}
Which will have it choose the best level of Crimson Rock (best defined as, lowest level that should one shot kill, or highest level known if you can't one shot kill) any mob that isn't of element Fire.

I haven't actually put the whenStatusInactive code into that logic yet, but it should be there soon and will hopefully stop the ai queue getting stuck if you kill a mob "after" the skill use hit's the queue.

xlr82xs
Developers
Developers
Posts: 51
Joined: 04 Sep 2013, 19:54
Noob?: No

Re: Automatic Skill Level Detection

#29 Post by xlr82xs »

https://sourceforge.net/p/openkore/code/8696/ has moved the old "beta" code to trunk.

https://sourceforge.net/p/openkore/code/8697/ has implemented some pretty untested code to "beta" that will allow things like castBetween for casting quagmire between you and your target, vertical firewalling, etc etc.
some rudiments of a replacement for runFromTarget aswell, using "stepBack" in skill blocks instead. (This means you can turn off runFromTarget, and specify a minimum distance for specific mobs (based on all the enhancedCasting checks, like "isImmovable") in a config block.

Documentation will be forthcoming at some point today, don't expect the beta code to be massively stable at the moment though.

Code: Select all

enhancedCasting Fire Wall {
	skillDist vertical
	coords_whenNotGround
	skillDelay 2
	stepBack 3
	target_notElement Fire
	target_notImmovable 1
	disabled 0
}

franibaflo
Human
Human
Posts: 35
Joined: 06 May 2012, 10:11
Noob?: Yes

Re: Automatic Skill Level Detection

#30 Post by franibaflo »

Neat updates! However, can I humbly request for you to provide a formal readme or documentation for the blocks? Because what's written in the header of the plugin itself is the old instructions for the monsterDB plugin. Say for example remove or update these lines:

Code: Select all

# 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)
# target_immovable boolean
and include these:

Code: Select all

enhancedCasting Fire Wall {
   skillDist vertical                    # What does this do aside from creating vertical firewalls? What other values are possible? Horizontal? 
   coords_whenNotGround         # What's this for also?
   skillDelay 2
   stepBack 3
   target_notElement Fire
   target_notImmovable 1
   disabled 0
}
I hope you'll have time to do so because the way I see it, a plugin is only as useful as the readme it comes with. For example: how I never really got monsterDB to work since before you mentioned it I couldn't find a thread of a clue where I should have placed the monsterDB.txt and so on...

Also another questions... can this plugin be used to attack other players for instance in pvp and woe. Methods that I know of include the following: using console command kill, using macro along with kill command, and using attackskills inside partySkill blocks in conjunction with sub-blocks notPartyOnly and notGuild. The functionality is already there through other means but it's very messy so I hope you can make time to also include this in your plugin.

Last suggestion... let's say I have the following scenarios:

1) I'm a mage running around killing monsters with spells; however, aside from these moving monsters I also opt to kill plants (red, yellow, green, blue white, shining) and mushrooms (red and black). Of course it would be a waste to kill them using spells when I could opt to use my bare hands or weapon.

2) I'm an acolyte in pay_dun00 heal bombing my way through zombies and skeletons; however, there are other aggressive monsters like familiars which can't be killed with heal and you need to use you weapon. I know i can just set attackAuto_useWeapon to 2 to kill such monsters but doing so lowers the effectiveness of my acolyte since it will then also use its weapon to attack the zombies and skeletons instead of pure heal bombing them.

I know switching attackAuto_useWeapon ca be done through macros, but I just want to ask for the heck of it if there's a possibility of you including it in this plugin.

Post Reply