Récolter puis fabriquer en Lua

Jitsuri permet de construire un atelier automatisé sans imposer un parcours unique. Votre script choisit où récupérer les ingrédients, quelles ressources récolter et dans quel atelier fabriquer l'objet.

Les anciens trajets utilisant craftItem(...) restent compatibles. Les nouvelles fonctions sont surtout utiles lorsque la banque et l'atelier sont éloignés, lorsque plusieurs coffres sont utilisés ou lorsque le script doit récolter uniquement ce qui manque.

Lire une recette

local recipe = getCraftRecipe(32521)

if recipe == nil then
    printMessage("Recette inconnue.", "red")
    stopScript()
    return
end

for _, ingredient in ipairs(recipe.ingredients) do
    printMessage(
        "GID " .. ingredient.gid .. " x" .. ingredient.quantity,
        "yellow"
    )
end

La recette indique notamment le métier, la compétence d'atelier et les ingrédients nécessaires.

Savoir ce qu'il manque

local quantity = getCraftableQuantity(32521)
local missing = getMissingCraftIngredients(32521, 10, "inventory")

for _, item in ipairs(missing) do
    printMessage(
        "Il manque " .. item.missing .. " unité(s) du GID " .. item.gid,
        "yellow"
    )
end

Utilisez "inventory" pour le sac du personnage et "storage" pour la banque ou le coffre actuellement ouvert.

Créer un objectif de récolte

Pour demander dix unités supplémentaires d'une ressource :

setGatheringGoal(421, 10, "additional")

Deux modes sont disponibles :

  • total atteint une quantité totale dans l'inventaire ;
  • additional récolte une quantité en plus de celle présente au démarrage de l'objectif.

Jitsuri suit la quantité réelle reçue dans l'inventaire. Si le GID correspond à un récoltable connu, la ressource est ajoutée automatiquement à la sélection du trajet, puis retirée lorsque l'objectif est atteint.

function onGatheringGoalProgress(goal)
    printMessage(
        goal.collectedQuantity .. "/" .. goal.requestedQuantity,
        "yellow"
    )
end

function onGatheringGoalReached(goal)
    printMessage("Objectif de récolte terminé.", "green")
    requestRouteRefresh()
end

Pour préparer automatiquement les objectifs d'une recette :

local result = createGatheringGoalsForCraft(32521, 10)

if not result.success then
    printMessage("Recette inconnue ou quantité invalide.", "red")
    stopScript()
    return
end

for _, item in ipairs(result.nonHarvestable) do
    printMessage(
        "À récupérer dans un stockage : GID " .. item.gid ..
        " x" .. item.missing,
        "yellow"
    )
end

Les ingrédients récoltables deviennent des objectifs. Les autres restent dans nonHarvestable afin que votre script choisisse une banque, un coffre de maison, un coffre de guilde ou une autre source.

result.ready vaut true lorsqu'aucun ingrédient ne manque. result.code précise si les objectifs ont été créés ou si les éléments manquants ne sont pas récoltables.

Utiliser une banque ou un coffre

Une fois sur la bonne carte :

if not openBank() then
    printMessage("Impossible d'ouvrir la banque.", "red")
    stopScript()
    return
end

local result = withdrawCraftIngredients(32521, 10)
closeStorage()

if not result.success then
    printMessage(result.code .. " : " .. result.message, "red")
    stopScript()
end

Fonctions utiles :

Fonction Description
isStorageOpen() Confirme qu'une banque ou un coffre est réellement ouvert.
getStorageItemCount(gid) Quantité du GID dans le stockage courant.
getStorageItems() Liste les piles du stockage courant.
withdrawStorageItem(gid, quantity) Retire une quantité précise.
withdrawCraftIngredients(itemGid, quantity) Retire uniquement ce qui manque pour le craft.
depositStorageItem(gid, quantity) Dépose une quantité ; 0 dépose tout le GID.
closeStorage() Ferme proprement le stockage.

openGuildChest(number), goToAndEnterHouse(...) et goAndOpenChest(...) permettent d'utiliser les autres stockages déjà configurés dans vos trajets.

Fabriquer depuis l'inventaire

Sur la carte de l'atelier :

local opened = openCraftWorkshop(32521)

if not opened.success then
    printMessage(opened.message, "red")
    stopScript()
    return
end

local result = craftFromInventory(32521, 10)
closeCraftWorkshop()

if result.success then
    printMessage("Fabrication terminée.", "green")
else
    printMessage(result.code .. " : " .. result.message, "red")
end

Pour contrôler chaque étape séparément, utilisez setCraftRecipe, setCraftQuantity, executeCraft et executeCraftStep.

Exemples complets

Les trois exemples suivants utilisent le GID 32521, la banque 84935175 et l'atelier 73533440. Remplacez ces trois valeurs par celles de votre recette et de votre trajet. Les parcours manuels demandent dix fabrications ; la fonction automatique historique fabrique la quantité maximale possible.

1. Laisser Jitsuri tout gérer

Cet exemple utilise la fonction historique tout-en-un. Jitsuri rejoint la banque, calcule la quantité fabricable, retire les ingrédients, rejoint l'atelier et lance la fabrication.

local ITEM_GID = 32521
local BANK_MAP_ID = 84935175
local WORKSHOP_MAP_ID = 73533440
local started = false

local function runAutomaticCraft()
    if started then
        return
    end

    started = true
    local success = craftItem(
        ITEM_GID,
        BANK_MAP_ID,
        WORKSHOP_MAP_ID,
        true,  -- atelier spécial si nécessaire
        true   -- retour en banque à la fin
    )

    if success then
        printMessage("Craft automatique terminé.", "green")
    else
        printMessage("Le craft automatique a échoué.", "red")
    end

    stopScript()
end

function move()
    return {
        {
            map = tostring(getMapId()),
            custom = runAutomaticCraft,
        },
    }
end

Cette forme est la plus courte et reste adaptée aux anciens trajets. Utilisez les fonctions séparées des exemples suivants pour contrôler chaque étape.

2. Prendre tout le nécessaire en banque puis fabriquer

Ici, le Lua pilote lui-même les deux phases. Tous les ingrédients nécessaires aux dix crafts sont pris en banque. Seules les quantités manquantes sont retirées : le reste du coffre n'est pas déplacé et les pods ne sont pas remplis avec des objets inutiles au craft.

local ITEM_GID = 32521
local CRAFT_QUANTITY = 10
local BANK_MAP_ID = 84935175
local WORKSHOP_MAP_ID = 73533440
local phase = "bank"

local function fail(result)
    printMessage(result.code .. " : " .. result.message, "red")
    stopScript()
end

local function takeIngredientsFromBank()
    if not openBank() then
        printMessage("Impossible d'ouvrir la banque.", "red")
        stopScript()
        return
    end

    local result = withdrawCraftIngredients(
        ITEM_GID,
        CRAFT_QUANTITY
    )
    closeStorage()

    if not result.success then
        fail(result)
        return
    end

    phase = "workshop"
    requestRouteRefresh()
end

local function craftAtWorkshop()
    local opened = openCraftWorkshop(ITEM_GID)
    if not opened.success then
        fail(opened)
        return
    end

    local result = craftFromInventory(ITEM_GID, CRAFT_QUANTITY)
    closeCraftWorkshop()

    if not result.success then
        fail(result)
        return
    end

    printMessage(
        result.craftedQuantity .. " fabrication(s) confirmée(s).",
        "green"
    )
    stopScript()
end

function move()
    if phase == "bank" then
        return {
            {
                map = tostring(BANK_MAP_ID),
                custom = takeIngredientsFromBank,
            },
        }
    end

    return {
        {
            map = tostring(WORKSHOP_MAP_ID),
            custom = craftAtWorkshop,
        },
    }
end

Pour un coffre de guilde ou de maison, conservez la même logique et remplacez openBank() par l'ouverture du stockage concerné.

3. Récolter, compléter en banque puis fabriquer

Ce dernier exemple crée automatiquement les objectifs correspondant aux ingrédients récoltables. Une fois ces objectifs terminés, il complète la recette en banque puis rejoint l'atelier.

MAX_PODS = 90
ELEMENTS_TO_GATHER = {}

local ITEM_GID = 32521
local CRAFT_QUANTITY = 10
local BANK_MAP_ID = 84935175
local WORKSHOP_MAP_ID = 73533440
local phase = "prepare"

local function stopWithMessage(message)
    printMessage(message, "red")
    stopScript()
end

local function prepareGatheringGoals()
    local result = createGatheringGoalsForCraft(
        ITEM_GID,
        CRAFT_QUANTITY
    )

    if not result.success then
        stopWithMessage("Recette inconnue ou quantité invalide.")
        return
    end

    for _, item in ipairs(result.nonHarvestable) do
        printMessage(
            "Banque requise : GID " .. item.gid ..
            " x" .. item.missing,
            "yellow"
        )
    end

    if #result.goals > 0 then
        phase = "gather"
    else
        phase = "bank"
    end
end

function onGatheringGoalProgress(goal)
    printMessage(
        "Récolte GID " .. goal.gid .. " : " ..
        goal.collectedQuantity .. "/" .. goal.requestedQuantity,
        "yellow"
    )
end

function onGatheringGoalReached(goal)
    printMessage("Objectif GID " .. goal.gid .. " terminé.", "green")

    if areAllGatheringGoalsReached() then
        phase = "bank"
        requestRouteRefresh()
    end
end

local function completeRecipeFromBank()
    if not openBank() then
        stopWithMessage("Impossible d'ouvrir la banque.")
        return
    end

    local result = withdrawCraftIngredients(
        ITEM_GID,
        CRAFT_QUANTITY
    )
    closeStorage()

    if not result.success then
        stopWithMessage(result.code .. " : " .. result.message)
        return
    end

    phase = "workshop"
    requestRouteRefresh()
end

local function craftResult()
    local opened = openCraftWorkshop(ITEM_GID)
    if not opened.success then
        stopWithMessage(opened.code .. " : " .. opened.message)
        return
    end

    local result = craftFromInventory(ITEM_GID, CRAFT_QUANTITY)
    closeCraftWorkshop()

    if result.success then
        printMessage("Récolte et fabrication terminées.", "green")
    else
        printMessage(result.code .. " : " .. result.message, "red")
    end
    stopScript()
end

function move()
    if phase == "prepare" then
        prepareGatheringGoals()
    end

    if phase == "gather" then
        return {
            -- Remplacez ces cartes par un trajet contenant les ressources
            -- nécessaires à votre recette.
            { map = "5,7", path = "bottom", gather = true },
            { map = "5,8", path = "left", gather = true },
            { map = "4,8", path = "5,7", gather = true },
        }
    end

    if phase == "bank" then
        return {
            {
                map = tostring(BANK_MAP_ID),
                custom = completeRecipeFromBank,
            },
        }
    end

    return {
        {
            map = tostring(WORKSHOP_MAP_ID),
            custom = craftResult,
        },
    }
end

Écouter l'inventaire

Les callbacks sont facultatifs :

function onInventoryItemChanged(event)
    printMessage(
        "GID " .. event.gid .. " : " .. event.quantity ..
        " (" .. event.delta .. ")",
        "yellow"
    )
end

function onItemGathered(event)
    printMessage(
        "Récolté : GID " .. event.gid .. " x" .. event.quantity,
        "green"
    )
end

onInventoryItemChanged couvre aussi les achats, retraits, dépôts, crafts et autres modifications d'inventaire. onItemGathered est réservé à la récolte.

Résultats et erreurs

Les actions retournent une table avec success, code, message, les quantités demandées/fabricables/fabriquées et la liste des ingrédients manquants.

Les codes les plus courants sont :

  • RECIPE_NOT_FOUND : recette inconnue ;
  • STORAGE_NOT_OPEN : banque ou coffre non ouvert ;
  • INGREDIENT_MISSING : ingrédients insuffisants ;
  • WRONG_WORKSHOP : mauvais atelier sur la carte ;
  • WORKSHOP_NOT_OPEN : atelier non ouvert ;
  • SERVER_REJECTED : action non confirmée ;
  • CRAFT_BUSY : une autre opération est encore en cours.

Ne lancez pas plusieurs transferts ou crafts en parallèle. Attendez le résultat de l'action courante avant de poursuivre.

Rétrocompatibilité

Cette fonction historique reste disponible :

craftItem(itemGid, bankMapId, workshopMapId, specialCraft, returnBank)

Elle convient aux anciens trajets tout-en-un. Pour un nouveau parcours, les fonctions séparées offrent davantage de contrôle et des erreurs plus faciles à diagnostiquer.