# Jobs

Jobs turn Minecraft actions (mining, killing, fishing, crafting, brewing, enchanting, breeding ...) into income: money, job XP and a dedicated points currency. Players level up, unlock titles, rewards, daily quests and a job shop. All progress persists across restarts.

**Files:** `modules/jobs.yml` (main config) and `modules/jobs/<jobId>.yml` (one file per job, seeded with 10 ready-made jobs on first start; your changes are never overwritten by updates, only missing keys are added).

## Commands

All `/jobs` subcommands need `jobs.use` (default true) plus the listed permission (all default true).

| Command | Syntax | Permission | Description |
|---|---|---|---|
| Menu | `/jobs` or `/jobs menu` | `jobs.menu` | Hub GUI: my jobs, browse, quests, shop |
| Browse | `/jobs browse` | `jobs.browse` | Paginated job list with progress, click = join |
| Info | `/jobs info <job>` | `jobs.info` | All actions, targets, rewards, titles of a job |
| Join | `/jobs join <job>` | `jobs.join` + `jobs.join.<job>` | Checks max-jobs limit and cooldown |
| Leave | `/jobs leave <job>` / `leaveall` | `jobs.leave` | With XP loss, if enabled |
| Stats | `/jobs stats` | `jobs.stats` | Level/XP/earnings per job |
| Top lists | `/jobs top <job>` / `gtop` | `jobs.top` | Top 10 per job or globally |
| Limit | `/jobs limit` | `jobs.limit` | Remaining period quota |
| Points | `/jobs points` | `jobs.points` | Job points balance |
| Quests | `/jobs quests` | `jobs.quests` | Daily quest GUI with progress bars |
| Display | `/jobs toggle [actionbar\|bossbar\|off]` | `jobs.toggle` | Income display mode |
| Boosts | `/jobs boosts` | `jobs.boosts` | Active boosts and total multiplier |
| Shop | `/jobs shop` | `jobs.shop` | Job shop (pays with points) |

> **Tip:** `jobs.join.<jobId>` is registered automatically per job with default true. Block individual jobs via LuckPerms with `jobs.join.<jobId>: false`; the browse menu checks the same permission.

## Admin commands (/jobsa)

| Command | Syntax | Permission | Description |
|---|---|---|---|
| Reload | `/jobsa reload` | `jobs.admin.reload` | Reloads jobs, quests, boosts, shop |
| Status | `/jobsa status` | `jobs.admin.status` | Status of the block history providers (diagnosis) |
| Join/Leave | `/jobsa join\|leave\|leaveall <player> [job]` | `jobs.admin.manage` | Without limit check, without XP loss |
| Fire | `/jobsa fire <player> <job>` | `jobs.admin.manage` | With guaranteed XP loss |
| Level | `/jobsa promote\|demote <player> <job> [levels]` | `jobs.admin.manage` | Level up/down, XP reset to 0 |
| XP | `/jobsa grantxp\|removexp <player> <job> <amount>` | `jobs.admin.manage` | Grant/remove XP |
| Boosts | `/jobsa boost <job\|all> <money\|xp\|all> <factor> [duration]` | `jobs.admin.boost` | Permanent without duration; `boostremove`, `boostlist` |
| Limits | `/jobsa resetlimit <player>` / `resetlimitall` | `jobs.admin.limit` | Reset period counters |
| Points | `/jobsa points <player> <set\|add\|remove> <amount>` | `jobs.admin.points` | Manage points balance |
| Quest | `/jobsa skipquest <player> <job> <index>` | `jobs.admin.quest` | Complete a quest without reward |

## Additional permissions

| Permission | Default | Description |
|---|---|---|
| `jobs.max.<n>` / `jobs.max.unlimited` | `false` | More concurrent jobs (highest wins; unlimited = 50) |
| `jobs.maxlevel.<n>` | `false` | Higher max level when n exceeds the config value |
| `jobs.autojoin.<jobId>` | `false` | Automatic join on server join |
| `jobs.boost.<factor>` | `false` | Permission boost, e.g. `jobs.boost.1.25` (highest wins) |
| `jobs.bypass.xploss` | `false` | No XP loss on leaving |
| `jobs.bypass.placedblock` / `cropcheck` / `spamkill` / `spawner` / `afk` / `limit` / `dynamicpay` | `false` | Bypasses the respective exploit protection |

## The 10 default jobs

| Job | Color | Main actions |
|---|---|---|
| `miner` | gold | `BREAK` (ores, stone, obsidian, amethyst, sculk, ancient debris) |
| `woodcutter` | dark_green | `BREAK` (all wood variants), `STRIP_LOG` |
| `farmer` | yellow | `BREAK` (crops), `BREED`, `SHEAR`, `MILK` |
| `hunter` | red | `KILL` (30+ hostile mobs up to ender dragon/wither) |
| `fisherman` | aqua | `FISH` (fish and treasure) |
| `crafter` | light_purple | `CRAFT` (redstone, ender chest, beacon, shulker box ...) |
| `enchanter` | dark_purple | `ENCHANT` (tools, armor, books, elytra) |
| `brewer` | dark_aqua | `BREW`, `CRAFT` (brewing stand, cauldron, bottles) |
| `weaponsmith` | gray | `CRAFT` (weapons/armor), `REPAIR`, `SMELT` |
| `explorer` | green | `EXPLORE` (distance based), `TAME`, `KILL` (shulker only) |

Every job has 6 title tiers (levels 1/10/25/50/75/100) and level-up rewards on the same milestones.

## Action types

| Action | Trigger | Target | Protection/special |
|---|---|---|---|
| `BREAK` | Break a block | Material | Placed-block check, crop age, AFK detection |
| `KILL` | Kill a creature | Creature | Spam-kill protection, spawner reduction, no suicide |
| `FISH` | Fishing | Caught material | Also pays for treasure |
| `CRAFT` | Crafting | Result material | Shift-click counts the real amount |
| `SMELT` | Take from furnace | Material | Counts the taken amount |
| `BREW` | Brew a potion | Potion material | Last brewing stand user = brewer |
| `ENCHANT` | Enchanting | Material | - |
| `REPAIR` | Anvil repair | Material | Both slots must be filled |
| `BREED` / `SHEAR` / `TAME` | Breed/shear/tame | Creature | - |
| `MILK` | Milking | Creature | Cooldown per animal |
| `EXPLORE` | Walking | `ALL` | Pays every `explore-distance` blocks, teleports do not count |
| `STRIP_LOG` | Strip wood | Original material | - |

`ALL` is allowed everywhere as a wildcard. Nexo/Carpentary targets (`nexo:<id>`) take priority over vanilla materials.

## Leveling and scaling

```
xpRequired(level) = base * level + multiplier * level^exponent
effectiveIncome   = baseMoney * (1 + (level - 1) * level-multiplier)
effectiveXp       = baseXp    * (1 + (level - 1) * level-multiplier * 0.5)
```

XP scaling deliberately runs at half rate so high levels do not accelerate their own leveling exponentially. Surplus XP is never lost on level-ups (multi-level-ups possible); at max level no more XP is credited.

**Overridable per job:** each job file may have its own `leveling` section; missing values (or `-1`) use the global formula. GUI, `/jobs stats` and the web interface automatically use the formula of the respective job.

```yaml
# in modules/jobs/woodcutter.yml: this job levels slower, earns more
leveling:
  xp-formula:
    base: 15.0
    multiplier: 6.0
    exponent: 2.2
  income-formula:
    level-multiplier: 0.05
    xp-level-multiplier: 0.02
```

**Max level order:** job's own `max-level` (0 = global) → `global-max-level` → permission `jobs.maxlevel.<n>` (only when higher).

## Payout

Every action is settled for EVERY active job that pays for it (stacking intended): level scaling and boosts apply to money and XP, then active period limits cap them. **Points are always raw:** no level scaling, no boosts, no dynamic pay, no limits. The display is batched (`batch-interval`) as action bar or boss bar.

**Quests:** per job and day, `quests-per-job` quests are generated from the job's real action/target pairs. Reward = base value × amount × `reward-multiplier` (without level scaling), paid out automatically on completion. Daily reset at the configured time.

**Boosts:** three sources (admin via `/jobsa boost`, scheduled by time/weekday, permission `jobs.boost.<n>` where the highest wins). `stacking-mode: multiply` multiplies all, `add` adds the bonus shares; capped at `max-multiplier`. Admin boosts survive reload and restart.

## Exploit protection

Self-placed blocks pay nothing (history check via CoreProtect), immature crops pay nothing, spam kills of the same mob stop paying, spawner mobs pay only the configured fraction, suicide does not count, shift-click crafting counts the real amount, milking has a per-animal cooldown and teleports give no EXPLORE distance. Paid block breaks are optionally logged as `#jobs-<player>` in CoreProtect.

> **Warning:** Without CoreProtect the jobs module disables itself by default (`require-history-provider: true`), visible only as a log warning. To play without a history source, deliberately set the switch to false; then `placed-block-failure` decides (deny = safe, allow = pays anyway). AFK detection only applies to `BREAK`.

## Configuration (modules/jobs.yml)

```yaml
# Enables or disables the jobs module.
enabled: true

jobs:
  # Concurrent jobs per player. Highest jobs.max.<n> permission wins,
  # jobs.max.unlimited = 50.
  max-jobs: 3
  # Players with jobs.autojoin.<jobId> join automatically on server join.
  autojoin-enabled: false
  # Level cap for jobs without their own max-level.
  global-max-level: 100
  command-cooldowns:
    # Anti spam for /jobs join and leave, seconds.
    join: 3
    leave: 3
  chat-title:
    # Job title before the chat name (after the team prefix).
    # highest = job with the highest level, first = alphabetically first.
    enabled: false
    mode: highest
    format: "<job_color>[<job_title>] "
  actions:
    # Blocks per EXPLORE payout.
    explore-distance: 100.0
    # Seconds between paid milk actions per animal.
    milk-cooldown: 5
  xp-loss:
    # XP loss on voluntary leave. /jobsa fire always applies it,
    # jobs.bypass.xploss prevents it.
    enabled: true
    percentage: 0.3
  leveling:
    xp-formula:
      # Required XP = base * level + multiplier * level^exponent.
      base: 10.0
      multiplier: 4.0
      exponent: 2.0
    income-formula:
      # +3% income per level; XP scales at half rate.
      level-multiplier: 0.03
    level-up:
      chat-enabled: true
      title-enabled: true
      sound-enabled: true
      sound: "entity.player.levelup"
      sound-volume: 1.0
      sound-pitch: 1.0
      title-fade-in: 10
      title-stay: 40
      title-fade-out: 20
    broadcast:
      # Announce level-ups server-wide from min-level on.
      enabled: false
      min-level: 10
  limits:
    # Rolling window with money/XP caps per player.
    enabled: false
    max-earnings: 10000.0
    max-xp: 50000.0
    period-duration: 3600
  income-display:
    # Ticks between batched displays / boss bar duration.
    batch-interval: 40
    bossbar-duration: 60
  boosts:
    # multiply = boosts multiply, add = bonus shares add up.
    stacking-mode: multiply
    max-multiplier: 10.0
    scheduled:
      weekday-dawn:
        job: farmer
        category: all
        multiplier: 1.25
        start: "05:00"
        end: "08:00"
        days: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
  quests:
    enabled: true
    quests-per-job: 4
    min-amount: 12
    max-amount: 160
    reward-multiplier:
      money: 2.2
      xp: 2.1
      points: 1.7
    # Daily reset (clears all quest data and regenerates).
    reset-hour: 0
    reset-minute: 0
    creative-style:
      # Flavor names with the <target> placeholder, per job or default.
      enabled: true
      templates:
        default:
          - "Guild contract: <target>"
          - "Rush order: <target>"
        miner:
          - "Crystal vein: <target>"
  dynamic-pay:
    # Supply/demand adjustment based on action frequency.
    enabled: false
    sensitivity: 0.5
    min-multiplier: 0.5
    max-multiplier: 1.5
  integrations:
    coreprotect:
      # Logs paid breaks as #jobs-<player>.
      jobs-tracking: true
      # Block history window for the placed-block check.
      lookup-time-seconds: 2592000
    nexo:
      jobs-enabled: true
    carpentary:
      jobs-enabled: true
    betterhud:
      job-info:
        enabled: true
        popup-name: ""
        popup-duration: 60
  shop:
    # Items cost job points; required-job/-level gate the purchase.
    enabled: true
    title: "<gold>Job Shop"
    items:
      miner_lantern_kit:
        material: TORCH
        amount: 64
        display-name: "<gold>Miner Lantern Kit"
        lore:
          - "<gray>Cheap light for long tunnels."
        cost: 35.0
        required-job: miner
        required-level: 3
  exploit-protection:
    placed-block-check: true
    # true = without a history source (CoreProtect) the module
    # disables itself.
    require-history-provider: true
    # auto, coreprotect, debug or disabled.
    placed-block-provider: auto
    # Without a definite answer: deny = do not pay (safe), allow = pay.
    placed-block-failure: deny
    crop-age-check: true
    spam-kill:
      enabled: true
      window: 30
      max-kills: 5
    spawner-mobs:
      enabled: true
      money-multiplier: 0.0
      xp-multiplier: 0.0
    afk-detection:
      # More than action-threshold actions within radius blocks during
      # window seconds: payout is reduced by multiplier.
      enabled: false
      radius: 5
      action-threshold: 50
      window: 120
      multiplier: 0.0
```

## Custom jobs (modules/jobs/<jobId>.yml)

```yaml
# name is required, otherwise the file is discarded. Invalid targets or
# negative values are skipped and logged. After changes: /jobsa reload.
# Limits: 500 actions, 100 titles, 200 reward tiers.
name: "Miner"
color: "gold"
short-name: "MIN"
icon: "IRON_PICKAXE"
description: "Mine ores and stones for profit"
# 0 = use global-max-level.
max-level: 0

actions:
  # BREAK/CRAFT/SMELT/STRIP_LOG/FISH/ENCHANT/REPAIR expect materials,
  # KILL/BREED/SHEAR/TAME/MILK expect creatures, ALL works everywhere.
  BREAK:
    DIAMOND_ORE:
      money: 4.0
      xp: 8.0
      points: 0.4
    ANCIENT_DEBRIS:
      money: 10.0
      xp: 20.0
      points: 1.0

titles:
  "1": "Apprentice Miner"
  "25": "Expert Miner"
  "100": "Mythic Deep Delver"

level-up-rewards:
  # Console commands with {player}, {job}, {jobname}. Commands with
  # disallowed characters are discarded and logged.
  "25":
    - "give {player} diamond 3"
  "100":
    - "give {player} netherite_upgrade_smithing_template 1"
```
