Conversation
alisonrag
approved these changes
May 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bundle of updates 3
Summary
This PR is a large bundle of OpenKore updates focused on routing safety, dynamic portals, teleport restrictions, eventMacro compatibility, attack/party aggression logic, combo handling, NPC shop discovery, new table systems, safer position prediction, and condition/config cleanup.
The main goal is to make OpenKore behave better in modern server environments where routing, NPC warps, teleport items, dynamic portals, party combat, cast-sensor monsters, short combo windows, generated rAthena-based data, and complex eventMacro automation interact with each other.
Videos:
https://www.youtube.com/watch?v=-KlAAMlPkW4&t=26s
https://www.youtube.com/watch?v=hsdkZiY57G4&t=6s
Highlights
searchshopcommand.npc_shops.txt,no_teleport_maps.txt, anditem_hand_type.txttable systems.monsters_table.txtwith explicit monster AI-mode flags.checkSelfCondition.castOnByPartyandcastOnToParty.quit 3andquit 4debug shutdown modes.QuestAllListInactive.config.txtby documenting reusable condition groups instead of repeating every possible condition key in every block.Motivation
This bundle addresses several long-standing practical issues:
Route calculation could choose invalid dynamic portal paths.
Some portals are only available depending on how a character entered an area. The old route solver did not preserve route-branch state, so it could incorrectly route through a dynamic exit that should not be available.
Missing NPC/portal route sources were deleted too destructively.
Temporarily missing or failed portal/NPC route sources could be removed from the route table, risking loss of route graph information.
Teleport and warp-item logic did not fully respect map restrictions.
Some maps do not allow random teleport, return teleport, or teleport skill behavior. The new table system makes those restrictions explicit.
NPC shops were not discoverable by item.
Users often need to know where to buy an item. This PR adds persistent NPC shop indexing and route-based closest-shop lookup.
Attack logic needed better cast/party awareness.
Combat does not always start with damage. Party members may cast on monsters, monsters may cast on party members, and cast-sensor monsters may react to casting. The new counters and config keys make this behavior configurable.
Short combo windows could be missed.
Combo delays start when the packet arrives, not when the AI loop later sees the combo. The new combo state tracks the real expiration time.
calcPosFromPathfindingcould return non-walkable predicted cells.This could break route/attack/follow/aggression logic. The new fallback normalizes bad predicted positions to nearby walkable cells.
eventMacro embedded Perl blocks could break when written across multiple lines.
The parser now preserves newlines, making multiline Perl blocks safe.
Main changes
1. EventMacro updates
1.1 New
QuestAllListInactiveconditionThis PR adds:
This condition is fulfilled only when all quests in the configured list are inactive.
This is useful when a macro should only start if none of several mutually exclusive quests are currently active.
Example:
automacro startEdenBoardWhenNoPreviousQuestIsActive { QuestAllListInactive 1001,1002,1003,1004 call start_eden_board }Meaning:
1001must not be active;1002must not be active;1003must not be active;1004must not be active;This is useful for quest flows where starting one quest locks out another quest range, or where automation must avoid starting a new quest while an older quest is still active.
Internally, the condition:
eventMacro::Condition::Base::Quest;achievement_list;It sets:
When every quest in the list is inactive, it mirrors the first configured quest ID as the fulfilled ID.
1.2 EventMacro Perl sub parser fix
plugins/eventMacro/eventMacro/FileParser.pmnow preserves line boundaries inside embedded Perl sub blocks.Before:
After:
This matters because the previous behavior collapsed multiline Perl into one continuous line, which could break valid Perl constructs.
Example that is now preserved correctly:
This helps eventMacro files that use multiline:
qw(...);2. Default config cleanup
control/config.txtwas cleaned up to reduce duplicated condition lists.Instead of repeating every possible condition key inside every automation block, the config now documents shared condition groups:
Example:
attackSkillSlot { lvl dist 1 maxDist 1 maxCastTime 0 minCastTime 0 previousDamage monsters notMonsters maxAttempts 0 maxUses 0 isSelfSkill 0 isStartSkill 0 equip_topHead equip_midHead equip_lowHead equip_leftHand equip_rightHand equip_robe equip_armor equip_shoes equip_arrow # [checkSelfCondition] # [checkMonsterCondition] }This keeps block-specific options visible while making the shared condition system easier to maintain.
3. New config keys
3.1 XKore listen-port additions
These allow separate map/char listen-port configuration in addition to the existing XKore listen settings.
3.2 Attack/aggression config
When enabled, OpenKore can consider a cast-sensor monster aggressive when the player or party casts on it.
Example:
With this enabled:
attackAuto_partyis enabled, it can be treated as party-aggressive;3.3 Warp-item route config keys
route_warpByItem_chainingAllows route calculations to consider warp items as part of a larger route branch.
route_warpItem_minGainMinimum required route-cost improvement before choosing a warp item.
This prevents wasting warp items for tiny gains.
route_warpItem_routeCostHeuristic_maxCaps the route-cost heuristic added from warp-item destination probing.
route_warpItem_routeCostProbe_maxPerTickLimits expensive route-cost probes per tick.
route_warpItem_routeCostCache_maxLimits cached warp-item route-cost entries.
3.4 Deal and Rodex limits
3.5 WX/map display keys
3.6 Project URL keys
3.7 Logging/debug keys
3.8 Skill/item automation keys
Example:
useSelf_skill Heal { lvl 10 hp < 60% noSmartHeal 1 # [checkSelfCondition] }Example:
partySkill Heal { lvl 10 target target_hp < 70% notPartyOnly 0 noSmartHeal 0 # [checkSelfCondition] # [checkPlayerCondition] }Example:
useSelf_item White Potion { hp < 40% dcOnEmpty 1 # [checkSelfCondition] }3.9
buyAutoandgetAutocondition keysbuyAutonow documents:zeny minDistance maxDistance dcOnEmpty 0 # [checkSelfCondition]Example:
buyAuto Butterfly Wing { npc prontera 150 150 minAmount 5 maxAmount 20 zeny > 10000 maxDistance 500 dcOnEmpty 1 # [checkSelfCondition] }getAutonow documents:dcOnEmpty 0 # [checkSelfCondition]4. New
checkSelfConditionchecksThis PR adds new equipment/hand-state conditions.
These are backed by the new
item_hand_type.txttable.4.1
whenNotEquippedOpposite of
whenEquipped.Example:
equipAuto { rightHand Main Gauche whenNotEquipped Main Gauche }Meaning:
Main Gaucheis not equipped.4.2 Empty-hand checks
Example:
useSelf_skill Throw Stone { whenEquip_Right_Hand_Empty 1 }Example:
useSelf_skill Shield Skill { whenEquip_Left_Hand_Empty 0 }4.3 Hand type checks
Supported aliases include:
Example: only use a bow skill with a bow equipped.
attackSkillSlot Double Strafe { lvl 10 dist 8 maxDist 10 whenEquip_Right_Hand_Type Bow # [checkSelfCondition] # [checkMonsterCondition] }Example: only use a shield skill with a shield equipped.
useSelf_skill Shield Reflect { lvl 10 whenEquip_Left_Hand_Type Shield # [checkSelfCondition] }Example: allow multiple weapon types.
attackSkillSlot Bash { lvl 10 whenEquip_Right_Hand_Type 1hSword,2hSword,Mace # [checkSelfCondition] # [checkMonsterCondition] }5. New
item_hand_type.txttableNew table:
Format:
Example:
Loaded by:
Stored in:
%itemHandType_lutGenerator added:
This lets OpenKore generate hand-type metadata from rAthena item DB sources.
6. Monster table and monster AI mode system
tables/monsters_table.txtwas regenerated and expanded with explicit monster AI flags.The new table format includes:
The parser now validates:
Malformed rows are dropped with explicit errors.
Generator updated:
7. New monster condition checks
New monster condition keys:
Example:
attackSkillSlot Provoke { lvl 10 target_is_aggressive 0 # [checkSelfCondition] # [checkMonsterCondition] }Example:
attackSkillSlot Safety Wall { lvl 10 target_is_aggressive_party 1 # [checkSelfCondition] # [checkMonsterCondition] }These allow config blocks to explicitly require or reject monsters considered aggressive in player or party context.
8. eCast AI-mode conditions
plugins/eCast/eCast.plnow supports monster AI-mode target conditions.New keys:
Each accepts:
Example: only cast on aggressive-AI monsters.
attackSkillSlot Fire Bolt { lvl 10 target_isAIMode_Aggressive 1 target_Element Earth }Example: avoid MVPs.
attackSkillSlot Cold Bolt { lvl 10 target_isAIMode_MVP 0 }Example: avoid monsters that take fixed 1 ranged damage.
attackSkillSlot Double Strafe { lvl 10 target_isAIMode_TakesFixed_1_Damage_Ranged 0 }9.
checkAggressiveplugin updateThe
checkAggressiveplugin now uses generated monster AI flags instead of local hardcoded AI constants.It now checks whether a monster is likely aggressive toward an actor by considering:
New helper logic includes:
This should reduce false positives where a naturally aggressive monster is nearby but is likely targeting someone else.
10. Party cast tracking
New actor counters:
Meaning:
castOnByParty: a party-relevant actor casted on this actor.castOnToParty: this actor casted on a party-relevant actor.These are updated by
countCastOn.This improves logic for combat that starts through skill casts rather than damage or misses.
Affected logic includes:
11. Cast-sensor aggressive logic
New config key:
When enabled, OpenKore can treat cast-sensor monsters as aggressive when the player or party casts on them.
Example:
This is useful for monsters with:
Before this PR, these monsters might not be treated as aggressive unless damage/miss counters were updated.
After this PR, cast-sensor behavior can be included explicitly.
12. Attack logic changes
12.1 Attack combo handling
Combo handling now uses structured
combo_stateinstead of only a rawcombo_packet.Old model:
New model:
The attack loop now uses the remaining combo window instead of waiting the full delay again.
This is important because combo windows are measured from packet arrival time.
Example config:
attackComboSlot Combo Finish { afterSkill Triple Attack waitBeforeUse 0 dist 1 maxDist 1 lvl 10 autoCombo 1 isSelfSkill 0 monsters notMonsters # [checkSelfCondition] # [checkMonsterCondition] }With
autoCombo 1, OpenKore now uses the packet-provided combo window more accurately.12.2 Combo target matching
The combo logic now checks:
last_skill_used;combo_state->{source_skill};combo_state->{target_id};last_skill_target.This makes combo handling more resilient when packets arrive very close together.
12.3 Temporary extra range no longer persists
The attack loop no longer permanently applies:
Reason:
This prevents melee attacks from getting stuck trying to attack from an invalid distance.
12.4
resolve_movetoattack_posfixresolve_movetoattack_posnow uses the actor’s ownmovetoattack_pos.Before:
After:
13. Dead logic updates
processDeadnow logs and handles dead-state transitions more clearly.13.1 Dead queue cleanup
When AI action is
deadbut the character is no longer dead:13.2 Stale
Deadstatus cleanupIf the current AI action is not
dead, the character is not dead, but theDeadstatus is still active, OpenKore now clears stale state:13.3 Death detection log
13.4 Respawn log
This helps debug death/respawn loops and stale dead-state issues.
14. Safe position prediction with
_normalize_calc_pos_to_walkableNew helper:
calcPosFromPathfindingpredicts actor position using:pos;pos_to;Sometimes predicted positions can be non-walkable due to:
Now, if a predicted position is not walkable, OpenKore tries:
then:
If a fallback exists, it returns the closest walkable cell and logs once per unique fallback.
Example log:
This protects attack, follow, route, and aggression code from consuming impossible actor positions.
15. Follow updates
New helpers:
15.1 New plugin hook
Plugins can set:
to suppress default follow behavior.
15.2 Better route reset logic
Follow state now tracks:
Follow route is reset when:
15.3 Direct movement when possible
start_followcan use direct movement instead ofai_routewhen:15.4 Party-follow coordinate validation
New helper:
Invalid coordinates are ignored when they are:
>= 65535;15.5 Party follow avoids respawn teleport
Party follow now passes:
when routing to the master.
This prevents follow behavior from using save-map respawn teleport as part of party follow.
16. NPC shop system
This PR adds a complete NPC shop cache/index system.
New table:
New parser:
New updater:
New runtime cache updater:
New index compiler:
New lookup helper:
New command:
New generator:
16.1
npc_shops.txtformatExample:
16.2 Runtime shop cache updates
When OpenKore receives NPC shop data in
npc_store_info, it now collects item IDs/prices and calls:This:
npc_shops.txt;This lets OpenKore learn shops during normal gameplay.
16.3 Item-to-shop index
When
npc_shops.txtis loaded, OpenKore builds:%itemIDtoShopsExample:
16.4 Closest shop lookup
get_closest_npc_shop_for_item:Task::CalcMapRoute;Returned data includes:
{ shop => $shop, map => $chosen->{map}, x => int($chosen->{x}), y => int($chosen->{y}), destination => "map x y", price => $price, move_cost => $move_cost, route => $task->getRouteString(), }16.5 New
searchshopcommandExamples:
The command:
Example output:
Example ambiguous item name:
Example missing shop:
17. No-teleport map system
New table:
New parser:
New global:
%no_teleport_mapsNew helpers:
17.1 Table format
Example:
Meaning:
noteleport = 1: random teleport and teleport skill are blocked.noreturn = 1: return-to-save-map teleport is blocked.17.2 Affected systems
Random teleport now checks:
Return teleport now checks:
Teleport skill now checks:
Affected systems include:
Task::Teleport::Random;Task::Teleport::Respawn;canUseTeleport;getTeleportItemFromTable;getFlyWing;getButterflyWing.17.3 Example behavior
Example table:
Expected behavior:
On
pay_dun00:On
tur_dun03:18. Dynamic portal and route calculation updates
Task::CalcMapRoutewas heavily refactored.It now better handles:
@gocommands;18.1 Dynamic portal syntax
Route entries and teleport items can now use:
Meaning:
[GroupName]marks a destination as belonging to a dynamic group.
[^GroupName]marks a route step as blocking that group for the current branch.
Example:
moc_para01 30 10 prontera 116 72 [EdenPortalExit] rachel 125 144 moc_para01 31 14 [^EdenPortalExit] 0 0 c c r0Meaning:
EdenPortalExit;EdenPortalExitfor that route branch;18.2 Branch-specific route state
The route key can now include blocked dynamic portal group state.
This allows two branches that reach the same route node to behave differently.
Example:
Branch A cannot use
[EdenPortalExit].Branch B can.
18.3 Dynamic portal group config
Dynamic portal groups can be controlled through config keys.
Example:
This can enable only destinations in that group that match the selected map/position.
Helpers involved:
18.4 Warp items can block portal groups
Teleport items can also include dynamic group markers.
Example concept:
22508 warp moc_para01 171 115 1 0 1200 [^EdenPortalExit]Meaning:
22508warps tomoc_para01 171 115;EdenPortalExitfor that route branch.18.5 Route source suspension instead of deletion
New helpers:
Instead of deleting a failed portal source:
OpenKore now marks it:
and queues it for later restoration.
This preserves:
portals_losgraph edges;18.6 Safer route point checks
New helpers:
These prevent route calculation from consuming incomplete, suspended, or unreachable entries.
18.7 Invalid route coordinates now fail loudly
Task::Route::getRoutenow fails on invalid coordinates instead of treating them as success.It logs diagnostics and prints callers when called with invalid start/destination coordinates.
This helps catch bugs where map-only route data accidentally reaches cell-route logic.
19. avoidObstacles portal route compatibility
plugins/avoidObstacles/avoidObstacles.plnow understands when a planned route intentionally uses a portal.New helpers:
This prevents portal avoidance from dropping or blocking a route whose destination is itself a known portal cell.
Example:
Before:
After:
20. New quit commands
quit 3Behavior:
s;st;skills;exp report;Useful for getting a final debug/status snapshot before shutdown.
quit 4Same as
quit 3, but disablesdcPausefor this shutdown:Useful for immediate controlled shutdown.
21.
talk respdebug improvementThe
talk respsyntax error now includes the invalid argument.Before:
After:
Wrong talk resp sintax ('<arg>').22.
monsterSkilllogic updateprocessMonsterSkillUsenow iterates visible monsters directly:instead of only using:
This allows
monsterSkillto be controlled by conditions more broadly.Example:
monsterSkill Provoke { target lvl 10 maxUses 1 target_isAIMode_Aggressive 1 target_is_aggressive 0 # [checkSelfCondition] # [checkMonsterCondition] }23. Actor status visibility
Actor::setStatusnow prints status changes for:as messages, while other actors remain debug-level output.
This makes important self/slave status changes more visible without spamming normal logs with every actor status update.
24. Table loading changes
functions.plnow loads these optional tables:Table registration examples:
Examples
Cast-sensor aggressive config
This allows the bot to treat cast-sensor monsters as aggressive after the player or party casts on them.
Attack only non-MVP monsters
attackSkillSlot Soul Strike { lvl 10 dist 8 maxDist 9 target_isAIMode_MVP 0 # [checkSelfCondition] # [checkMonsterCondition] }Attack only aggressive-AI monsters
attackSkillSlot Bash { lvl 10 dist 1 maxDist 1 target_isAIMode_Aggressive 1 # [checkSelfCondition] # [checkMonsterCondition] }Avoid monsters with fixed 1 magic damage
attackSkillSlot Fire Bolt { lvl 10 dist 8 maxDist 9 target_isAIMode_TakesFixed_1_Damage_Magic 0 # [checkSelfCondition] # [checkMonsterCondition] }Use bow skill only with bow equipped
attackSkillSlot Double Strafe { lvl 10 dist 8 maxDist 10 whenEquip_Right_Hand_Type Bow # [checkSelfCondition] # [checkMonsterCondition] }Use shield skill only with shield equipped
useSelf_skill Shield Reflect { lvl 10 whenEquip_Left_Hand_Type Shield # [checkSelfCondition] }Use item and disconnect if empty
useSelf_item White Potion { hp < 40% dcOnEmpty 1 # [checkSelfCondition] }Buy only if enough zeny and shop is near
buyAuto Butterfly Wing { npc prontera 150 150 minAmount 5 maxAmount 20 zeny > 10000 maxDistance 500 dcOnEmpty 1 # [checkSelfCondition] }Search known NPC shop
Expected output includes:
Dynamic portal group example
Portal table concept:
moc_para01 30 10 prontera 116 72 [EdenPortalExit] moc_para01 30 10 geffen 120 60 [EdenPortalExit] rachel 125 144 moc_para01 31 14 [^EdenPortalExit] 0 0 c c r0Config:
Meaning:
No-teleport table example
Expected behavior:
tur_dun03: no random teleport, no teleport skill, no return teleport.job_thief1: no random teleport and no teleport skill, but return may still be allowed.Tests added or updated
Updated/added test files:
Dynamic portal tests cover
portalExistsandportalExists2ignoring suspended sources.Hand condition tests cover
Fistbehavior for empty hands;Parser tests cover
Suggested test command:
Manual test plan
Routing and portals
route_reAddMissingPortals.avoidObstaclesis enabled.Warp items
route_warpByItem.route_warpItem_minGain.No-teleport maps
no_teleport_maps.txtwithnoteleport 1.noreturn 1.NPC shop system
npc_shops.txtis updated.searchshop <item ID>.searchshop <item name>.Attack/aggression
attackAuto_considerAggressiveIfCastOnCastSensor 0.attackAuto_considerAggressiveIfCastOnCastSensor 1.target_is_aggressive.target_is_aggressive_party.eCast
target_isAIMode_Aggressive.target_isAIMode_MVP 0.target_isAIMode_Detector.Attack combos
autoCombo 1.Follow
Dead logic
deadAI action is queued.Deadstatus is cleared after respawn.Safe calc position
calcPosFromPathfindingwhen predicted cell is not walkable.New files
Major modified files
Compatibility notes
Most new features are opt-in or table-driven.
Important notes:
attackAuto_considerAggressiveIfCastOnCastSensordefaults to0.[^GroupName].no_teleport_maps.txtis optional.npc_shops.txtis optional.item_hand_type.txtis optional.config.txtnow documents shared condition groups instead of repeating every condition in every block.monsters_table.txtfiles must follow the new strict tab-separated schema.Task::Route::getRoutenow fails on invalid coordinates instead of returning success with an empty solution.Task::Route::getRoutewith missing x/y should be updated to use map-route logic instead.Review focus
Please review carefully:
Task::CalcMapRoutedynamic route state keys.portalExists/portalExists2.Task::Route::getRoutebehavior change for invalid coordinates.no_teleport_maps.txtrestrictions.canUseTeleportbehavior for random vs return teleport.npc_shops.txtupdate/reload behavior.searchshopcommand item-name resolution and route calculation.item_hand_type.txtparser and hand condition behavior.monsters_table.txtstrict parser and generated AI flags.Short changelog
QuestAllListInactiveeventMacro condition.[^GroupName].Task::CalcMapRoute.no_teleport_maps.txtand teleport restriction helpers.npc_shops.txt, NPC shop cache updates, andsearchshop.item_hand_type.txtand hand/equipment condition checks.monsters_table.txtwith AI-mode flags.target_is_aggressiveandtarget_is_aggressive_party.castOnByPartyandcastOnToParty.combo_state.calcPosFromPathfinding.quit 3andquit 4.talk resperror output.