{"id":"aliboily/goal","name":"goal","scope":"aliboily","platform":"roblox","description":"A universal Goal-Oriented Action Planning (GOAP) and HTN system for Roblox","version":"1.5.0","latest":"1.5.0","versions":["1.0.0","1.1.0","1.3.0","1.3.1","1.3.2","1.3.4","1.4.0","1.5.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":false,"dependencies":{},"integrity":"d43d93eed26ffd4bd8cc2930c5382592d7c50544e901cc4a2eb6662bbc64271b","likes":0,"downloads":0,"install":"forest install aliboily/goal","url":"https://forest.dev/p/roblox/aliboily/goal","files":"https://api.forest.dev/ai/package/roblox/aliboily/goal/files","readme":"# Goal\n\n[![CI](https://github.com/aliboIly/goal/actions/workflows/ci.yaml/badge.svg)](https://github.com/aliboIly/goal/actions/workflows/ci.yaml)\n[![Release](https://github.com/aliboIly/goal/actions/workflows/release.yaml/badge.svg)](https://github.com/aliboIly/goal/actions/workflows/release.yaml)\n[![Wally](https://img.shields.io/badge/wally-aliboily%2Fgoal-red)](https://wally.run/package/aliboily/goal)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n\nA universal Goal-Oriented Action Planning (GOAP) system for Roblox with integrated HTN Planning, Utility AI, Perception, Memory, Personality, and Navigation systems.\n\n## Table of Contents\n\n- [What is GOAP?](#what-is-goap)\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Modules](#core-modules)\n  - [State](#state)\n  - [Action](#action)\n  - [Goal](#goal-1)\n  - [ActionSequence](#actionsequence)\n  - [Planner](#planner)\n  - [Logger](#logger)\n- [AI Systems](#ai-systems)\n  - [Consideration (Utility AI)](#consideration-utility-ai)\n  - [Memory](#memory)\n  - [Blackboard](#blackboard)\n  - [Perception](#perception)\n  - [Personality](#personality)\n  - [Navigation](#navigation)\n- [HTN Planning](#htn-planning)\n  - [Tasks](#tasks)\n  - [Methods](#methods)\n  - [HTN Domain](#htn-domain)\n  - [HTN Planner](#htn-planner)\n- [Advanced Features](#advanced-features)\n  - [Cooldown System](#cooldown-system)\n  - [Resource Costs](#resource-costs)\n  - [Interrupt Handling](#interrupt-handling)\n  - [State Persistence](#state-persistence)\n  - [Performance Optimizations](#performance-optimizations)\n  - [Debug Logging](#debug-logging)\n- [Examples](#examples)\n- [API Reference](#api-reference)\n- [Contributing](#contributing)\n- [License](#license)\n\n## What is GOAP?\n\n**Goal-Oriented Action Planning (GOAP)** is an AI architecture that allows NPCs to dynamically determine what actions to take to achieve their goals. Unlike traditional behavior trees or state machines where you explicitly define transitions, GOAP lets the AI figure out the best sequence of actions on its own.\n\nKey concepts:\n- **State**: The current state of the world (key-value pairs)\n- **Goals**: Desired world states the AI wants to achieve\n- **Actions**: Things the AI can do, with preconditions and effects\n- **Planning**: Using A* search to find the optimal action sequence\n\nGOAP was originally developed for the game F.E.A.R. and has since become popular for creating believable, emergent AI behavior in games.\n\n## Features\n\n### Core GOAP\n- **A* Planning Algorithm** - Finds optimal action sequences\n- **Action Sequences/Chains** - Multi-step behaviors like combat combos\n- **Cooldown System** - Time-based or turn-based cooldowns\n- **Resource Costs** - Actions can consume mana, stamina, items, etc.\n- **Interrupt Handling** - Dynamic goal switching for responsive AI\n- **State Serialization** - Save/load NPC states (JSON format)\n- **Performance Optimizations** - Caching and batch evaluation for 100+ NPCs\n- **Profiling Support** - Built-in timing and statistics\n- **Debug Logging** - Configurable logging with multiple severity levels\n\n### HTN Planning\n- **Hierarchical Task Network** - Structured procedural behaviors\n- **Primitive Tasks** - Single actions backed by GOAP Actions\n- **Compound Tasks** - Decompose into subtasks via methods\n- **Method Selection** - Precondition-based method filtering\n- **Backtracking** - Automatic retry with alternate methods on failure\n- **GOAP Integration** - Use Actions from your existing GOAP setup\n\n### Utility AI (Consideration)\n- **Response Curves** - Linear, quadratic, exponential, logistic, bell curves\n- **Curve Parameters** - Slope, exponent, midpoint, steepness customization\n- **Combination Modes** - Multiply, min, max, average, sum for multi-factor decisions\n- **Factory Methods** - Pre-built patterns for health, distance, resources\n\n### Memory System\n- **Working Memory** - Short-term tactical decisions (configurable duration)\n- **Episodic Memory** - Event history with importance weighting\n- **Semantic Memory** - Long-term facts with confidence levels\n- **Pattern Detection** - Learn player behaviors (e.g., \"flanks left 70% of time\")\n\n### Blackboard System\n- **Shared Knowledge** - Squad coordination and communication\n- **Claiming System** - Target claiming to prevent NPC clustering\n- **Expiration** - Auto-cleanup of stale information\n- **Subscriptions** - React to knowledge changes with callbacks\n\n### Perception System\n- **Vision** - FOV, range, peripheral vision, obstruction checks\n- **Hearing** - Sound detection with volume and type awareness\n- **Awareness States** - Unaware → Curious → Suspicious → Alert → Combat\n- **Awareness Decay** - Natural decay over time without stimuli\n\n### Personality System\n- **Traits** - Aggression, courage, caution, patience, loyalty, etc.\n- **Presets** - Berserker, Tactician, Coward, Veteran, Guard, Scout\n- **Mood System** - Temporary modifiers (angry, fearful, confident)\n- **Decision Modifiers** - Traits affect attack priority, retreat thresholds\n\n### Navigation System\n- **A* Pathfinding** - Grid-based tactical pathfinding\n- **Cover Evaluation** - Find and score cover positions\n- **Flanking Routes** - Calculate approaches from sides/rear\n- **High Ground** - Find elevation advantages\n- **Flee Paths** - Escape route calculation from multiple threats\n- **Threat Avoidance** - Automatic avoidance zones\n\n## Installation\n\n### Using Wally\n\nAdd to your `wally.toml`:\n\n```toml\n[dependencies]\ngoal = \"aliboily/goal@1.5.0\"\n```\n\nThen run:\n\n```bash\nwally install\n```\n\n### Using Rojo\n\nIf you're using [Rojo](https://rojo.space/) for development:\n\n#### Option 1: Sync directly to Studio\n\n1. Clone or download this repository\n2. Start the Rojo server:\n   ```bash\n   rojo serve default.project.json\n   ```\n3. Connect the Rojo plugin in Roblox Studio\n4. The package will be available at `ReplicatedStorage.Packages.Goal`\n\n#### Option 2: Build as .rbxm file\n\nBuild a model file that you can import into any place:\n\n```bash\nrojo build default.project.json -o Goal.rbxm\n```\n\nThen drag `Goal.rbxm` into Roblox Studio.\n\n#### Option 3: Include in your own Rojo project\n\nAdd Goal as a submodule or copy the `src/` folder, then reference it in your `default.project.json`:\n\n```json\n{\n  \"name\": \"MyGame\",\n  \"tree\": {\n    \"$className\": \"DataModel\",\n    \"ReplicatedStorage\": {\n      \"$className\": \"ReplicatedStorage\",\n      \"Packages\": {\n        \"$className\": \"Folder\",\n        \"Goal\": {\n          \"$path\": \"path/to/goal/src\"\n        }\n      }\n    }\n  }\n}\n```\n\n### Manual Installation\n\n1. Download the latest release\n2. Place the `src` folder contents in `ReplicatedStorage.Packages.Goal`\n3. Require it in your scripts:\n   ```lua\n   local Goal = require(ReplicatedStorage.Packages.Goal)\n   ```\n\n### Project Structure\n\n```\nsrc/\n├── init.lua          -- Main module entry point\n├── State.lua         -- World state management\n├── Goal.lua          -- Goal definitions\n├── Action.lua        -- Action definitions\n├── ActionSequence.lua\n├── Planner.lua       -- A* GOAP planner\n├── Utilities.lua\n├── Logger.lua\n├── AI/               -- Utility AI systems\n│   ├── init.lua\n│   ├── Consideration.lua\n│   ├── Memory.lua\n│   ├── Blackboard.lua\n│   ├── Perception.lua\n│   └── Personality.lua\n├── Navigation/       -- Pathfinding systems\n│   ├── init.lua\n│   ├── Navigation.lua\n│   ├── NavigationGrid.lua\n│   ├── PathfindingAdapter.lua\n│   └── SpatialGrid.lua\n├── HTN/              -- Hierarchical Task Network\n│   ├── init.lua\n│   ├── Task.lua\n│   ├── Method.lua\n│   ├── HTNDomain.lua\n│   └── HTNPlanner.lua\n└── Actor/            -- Parallel execution (lazy-loaded)\n    ├── init.lua\n    ├── SharedBlackboard.lua\n    ├── ActorPool.lua\n    └── NPCScheduler.lua\n```\n\n## Quick Start\n\n```lua\nlocal Goal = require(path.to.goal)\n\n-- Create a planner\nlocal planner = Goal.Planner.new()\n\n-- Define actions\nlocal gatherWood = Goal.Action.new({\n    name = \"GatherWood\",\n    cost = 2,\n    preconditions = {},\n    effects = { hasWood = true },\n})\n\nlocal makeFire = Goal.Action.new({\n    name = \"MakeFire\",\n    cost = 1,\n    preconditions = { hasWood = true },\n    effects = { hasFire = true },\n})\n\nplanner:registerActions({ gatherWood, makeFire })\n\n-- Define a goal\nlocal warmthGoal = Goal.Goal.new({\n    name = \"GetWarm\",\n    desiredState = { hasFire = true },\n    priority = 1,\n})\n\n-- Create world state\nlocal worldState = Goal.State.new({\n    hasWood = false,\n    hasFire = false,\n})\n\n-- Generate a plan\nlocal plan = planner:plan(worldState, warmthGoal)\n\nif plan.success then\n    print(Goal.formatPlan(plan))\n    -- Output:\n    -- Plan SUCCESS (cost: 3.00, iterations: 3)\n    -- Actions:\n    --   1. GatherWood\n    --   2. MakeFire\nend\n```\n\n## Core Modules\n\n### State\n\nRepresents the world state as key-value pairs.\n\n```lua\nlocal state = Goal.State.new({\n    health = 100,\n    hasWeapon = true,\n})\n\nstate:get(\"health\") -- 100\nstate:set(\"health\", 80)\nstate:has(\"hasWeapon\") -- true\nstate:satisfies(otherState) -- boolean\nstate:isDirty() -- true if modified\nstate:markClean() -- mark as unmodified\n```\n\n### Action\n\nRepresents an action with preconditions, effects, cooldowns, and resource costs.\n\n```lua\nlocal action = Goal.Action.new({\n    name = \"Attack\",\n    cost = 1,\n    preconditions = { hasWeapon = true, enemyInRange = true },\n    effects = { enemyDead = true },\n\n    -- Cooldown (optional)\n    cooldownTime = 3,\n    cooldownMode = \"seconds\", -- or \"turns\"\n\n    -- Resource costs (optional)\n    resourceCosts = {\n        { resource = \"stamina\", amount = 20 },\n        { resource = \"mana\", amount = 10 },\n    },\n\n    -- Interrupt handling (optional)\n    interruptible = true,\n    onInterrupt = function(agent, context)\n        print(\"Attack interrupted!\")\n    end,\n\n    -- Grouping (optional)\n    group = \"combat\",\n    tags = { \"offensive\", \"melee\" },\n\n    -- Dynamic cost (optional)\n    costFn = function(worldState, agent)\n        return worldState:get(\"enemyHealth\") / 10\n    end,\n\n    -- Runtime validation (optional)\n    validateFn = function(worldState, agent)\n        return agent.stamina > 10\n    end,\n\n    -- Execution logic (optional)\n    executeFn = function(agent, context)\n        agent:playAnimation(\"attack\")\n        return true\n    end,\n})\n```\n\n### Goal\n\nRepresents a goal with desired state, priority, and interrupt handling.\n\n```lua\nlocal goal = Goal.Goal.new({\n    name = \"DefeatEnemy\",\n    desiredState = { enemyDead = true },\n    priority = 10,\n\n    -- Dynamic priority (optional)\n    priorityFn = function(worldState)\n        if worldState:get(\"lowHealth\") then\n            return 100 -- Max priority when low health\n        end\n        return 10\n    end,\n\n    -- Interrupt handling (optional)\n    interruptible = true,\n    onInterrupt = function(worldState)\n        print(\"Goal interrupted!\")\n    end,\n\n    -- Grouping (optional)\n    group = \"combat\",\n    tags = { \"offensive\" },\n\n    -- Priority threshold (optional)\n    minPriority = 5, -- Ignore if priority falls below this\n})\n```\n\n### ActionSequence\n\nChains multiple actions for complex multi-step behaviors.\n\n```lua\nlocal comboSequence = Goal.ActionSequence.new({\n    name = \"ComboAttack\",\n    actions = { lightAttack, lightAttack, heavyAttack, finisher },\n    failureStrategy = \"abort\", -- \"skip\", \"retry\", or \"abort\"\n    maxRetries = 3,\n    interruptible = true,\n\n    onStepComplete = function(stepIndex, action, success)\n        print(string.format(\"Step %d: %s\", stepIndex, success and \"OK\" or \"FAILED\"))\n    end,\n\n    onSequenceComplete = function(success, completedSteps)\n        print(string.format(\"Combo %s after %d steps\", success and \"complete\" or \"failed\", completedSteps))\n    end,\n\n    onInterrupt = function(stepIndex, action)\n        print(\"Combo interrupted at step \" .. stepIndex)\n    end,\n})\n\n-- Execute the sequence\nlocal success, completedSteps = comboSequence:executeAll(agent, context)\n\n-- Or execute step by step\nwhile comboSequence:isExecuting() do\n    local stepSuccess, status = comboSequence:executeStep(agent, context)\nend\n```\n\n### Planner\n\nThe A* planner with performance optimizations.\n\n```lua\nlocal planner = Goal.Planner.new({\n    maxIterations = 1000,\n    maxPlanLength = 20,\n    heuristicWeight = 1.0,\n\n    -- Performance options\n    performanceMode = true,\n    priorityThreshold = 5, -- Ignore goals below this priority\n    maxEvaluationsPerTick = 10, -- Limit evaluations per update\n    cacheTTL = 0.5, -- Priority cache time-to-live\n    enableProfiling = true,\n})\n\n-- Register actions\nplanner:registerActions({ action1, action2, action3 })\n\n-- Get actions by group or tag\nlocal combatActions = planner:getActionsByGroup(\"combat\")\nlocal offensiveActions = planner:getActionsByTag(\"offensive\")\n\n-- Plan for a single goal\nlocal plan = planner:plan(worldState, goal, agent, context)\n\n-- Plan for the best available goal\nlocal plan, selectedGoal = planner:planBestGoal(worldState, goals, agent, context)\n\n-- Batch evaluate multiple NPCs efficiently\nlocal results = planner:batchEvaluate({\n    { agent = npc1, state = state1, goals = goals1 },\n    { agent = npc2, state = state2, goals = goals2 },\n}, context)\n\n-- Check for interrupts\nlocal interruptGoal = planner:findInterruptingGoal(currentGoal, allGoals, worldState)\n\n-- Get profiling data\nlocal profilingData = planner:getProfilingData()\nprint(Goal.formatProfiling(profilingData))\n```\n\n### Logger\n\nDebug logging system with configurable severity levels.\n\n```lua\nlocal Logger = Goal.Logger\n\n-- Set global log level\nLogger.setLevel(Logger.Level.DEBUG)\n\n-- Available levels: NONE, ERROR, WARN, INFO, DEBUG, TRACE\nLogger.info(\"Game\", \"Starting AI system\")\nLogger.debug(\"Planner\", \"Planning for goal: %s\", goal:getName())\nLogger.error(\"Action\", \"Failed to execute: %s\", action:getName())\n\n-- Category-specific levels\nLogger.setCategoryLevel(\"Planner\", Logger.Level.TRACE)\nLogger.setCategoryLevel(\"Action\", Logger.Level.WARN)\n\n-- Scoped logger (no need to specify category each time)\nlocal log = Logger.scoped(\"Combat\")\nlog.debug(\"Attacking enemy: %s\", enemy.name)\nlog.info(\"Combat complete\")\n\n-- Check before expensive operations\nif Logger.isEnabled(Logger.Level.TRACE, \"Planner\") then\n    Logger.trace(\"Planner\", \"Full state: %s\", formatState(state))\nend\n```\n\n## AI Systems\n\n### Consideration (Utility AI)\n\nResponse curves for nuanced decision-making. Transform raw values into utility scores.\n\n```lua\nlocal healthUrgency = Goal.Consideration.new({\n    name = \"HealthUrgency\",\n    curve = \"inverse_quadratic\",  -- Low health = high urgency\n    curveParams = { exponent = 2.5 },\n    inputFn = function(state, agent)\n        return state:get(\"health\") / agent.maxHealth\n    end,\n})\n\n-- Evaluate the consideration\nlocal urgency = healthUrgency:evaluate(worldState, agent)\n\n-- Combine multiple considerations\nlocal attackUtility = Goal.Consideration.combine(\n    { healthConsideration, ammoConsideration, distanceConsideration },\n    \"multiply\",  -- or \"min\", \"max\", \"average\"\n    worldState,\n    agent\n)\n```\n\n**Available Curves:** `linear`, `quadratic`, `inverse`, `inverse_quadratic`, `exponential`, `logistic`, `step`, `smoothstep`, `bell`, `custom`\n\n### Memory\n\nShort-term and long-term memory for intelligent agents.\n\n```lua\nlocal memory = Goal.Memory.new({\n    workingMemoryDuration = 15,  -- 15 seconds short-term\n    maxEpisodicMemories = 200,\n})\n\n-- Record events\nmemory:recordEvent(\"player_attack\", { direction = \"left\", damage = 25 }, 0.8)\n\n-- Check recent events\nif memory:hasRecentEvent(\"player_attack\", 5) then\n    -- Player attacked in last 5 seconds\nend\n\n-- Store long-term facts\nmemory:recordFact(\"player_prefers_flanking\", true, 0.7)\n\n-- Get learned patterns\nlocal patterns = memory:getPatterns(\"attack\")\n-- Returns: { pattern = \"left_flank\", confidence = 0.7, occurrences = 5 }\n```\n\n### Blackboard\n\nShared knowledge system for squad coordination.\n\n```lua\nlocal squadBoard = Goal.Blackboard.new({\n    name = \"Alpha Squad\",\n    defaultExpiration = 60,\n})\n\n-- Post target information\nsquadBoard:post(\"primary_target\", {\n    id = \"player_1\",\n    position = Vecto","readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/aliboily/roblox/goal/1.5.0/readme"}