RE:CORD
REC_Farm

Stracture

Installation, configuration, and usage for REC_Farm.
Added
Updated

The following configuration files and manifest are taken from the current source.

config/sh_config.lua

---@type REC_Farm.Shared.Enum
local shEnums = require "@REC_Farm.shared.sh_enum"
local roles = shEnums.roles
local permissions = shEnums.permissions
local actionTypes = shEnums.actionTypes
local toolKinds = shEnums.toolKinds

---@class REC_Farm.Shared.Config
local config = {}

---[[
---     Debug mode
---]]
---@type boolean
config.debugMode = true

---[[
---     Data files (crops, animals, structures, recipes, land, tools, diseases, skills, obstacles, tutorial)
---     Everything a server owner adds lives in config/data/, the core never needs a change
---]]
---@type REC_Farm.Data.Crops
config.crops = require "@REC_Farm.config.data.sh_crops"

---@type REC_Farm.Data.Animals
config.animals = require "@REC_Farm.config.data.sh_animals"

---@type REC_Farm.Data.Structures
config.structures = require "@REC_Farm.config.data.sh_structures"

---@type REC_Farm.Data.Recipes
config.recipes = require "@REC_Farm.config.data.sh_recipes"

---@type REC_Farm.Data.Sites
config.sites = require "@REC_Farm.config.data.sh_sites"

---@type REC_Farm.Data.Tools
config.tools = require "@REC_Farm.config.data.sh_tools"

---@type REC_Farm.Data.Diseases
config.diseases = require "@REC_Farm.config.data.sh_diseases"

---@type REC_Farm.Data.Skills
config.skills = require "@REC_Farm.config.data.sh_skills"

---@type REC_Farm.Data.Obstacles
config.obstacles = require "@REC_Farm.config.data.sh_obstacles"

---@type REC_Farm.Data.Tutorial
config.tutorial = require "@REC_Farm.config.data.sh_tutorial"

---@type REC_Farm.Data.Difficulty
config.difficulty = require "@REC_Farm.config.data.sh_difficulty"

---[[
---     Generic items (seeds, feed and products are declared in the data files)
---]]
config.items = {
    fertilizer = "rec_farm_fertilizer",
    compost = "rec_farm_compost",
    manure = "rec_farm_manure",
    repairKit = "rec_farm_repair_kit",
    landDeed = "rec_farm_land_deed",
}

---[[
---     Quality grades
---     Every harvest, animal product and processed good is a separate item per star:
---     itemFormat turns "rec_farm_tomato" + 3 into "rec_farm_tomato_3star"
---]]
config.quality = {
    ---@type integer
    minStars = 1,
    ---@type integer
    maxStars = 5,
    ---@type string
    itemFormat = "%s_%dstar",
    ---@type string
    itemPattern = "^(.-)_(%d)star$",
}

---[[
---     Farmland grid, one cell per crop, aligned to the site origin
---]]
config.grid = {
    ---@type number
    cellSize = 1.5, -- meters
    ---@type number
    plantOffsetZ = 0.0,
}

---[[
---     Distances (meters)
---]]
config.distance = {
    interact = 3.0,
    target = 2.5,
    purchasePoint = 2.0,
    tillReach = 2.0,
    structureInteract = 3.5,
    animalInteract = 3.0,
    leadRadius = 30.0,
    inspectCamera = 2.2,
}

---[[
---     Client streaming
---     A farm is sent to a client once the player is within farmDistance of the site,
---     entities are only created within renderDistance of the player and never more than maxObjects
---]]
config.streaming = {
    ---@type number
    farmDistance = 250.0,
    ---@type number
    renderDistance = 70.0,
    ---@type number
    animalDistance = 120.0,
    ---@type integer
    maxObjects = 400,
    ---@type integer
    maxWeedObjects = 120,
    ---@type integer
    updateInterval = 1000, -- ms
    ---@type integer
    spawnBudgetPerTick = 12,
}

---[[
---     Activity - when does simulation time advance for a farm
---     A farm only lives while it is "active". Offline time and server downtime are never added.
---
---     members_online : at least one member (owner / manager / worker) is online     (default)
---     owner_online   : the owner is online
---     anyone_nearby  : any player is within streaming distance of the site
---     always         : whenever the server is running
---
---     NOTE: the choice of who keeps a farm active is a game design decision, see the delivery notes
---]]
config.activity = {
    ---@type REC_Farm.Shared.Enum.ActivityModes
    mode = "members_online",
}

---[[
---     Simulation tick
---]]
config.sim = {
    ---@type integer
    tickInterval = 10, -- seconds
    ---@type integer
    maxStep = 60, -- seconds, a hitch never advances a farm further than this in one tick
}

---[[
---     Structure placement (edit mode)
---     walk  : the ghost floats in front of the player, rotate / raise with keys
---     gizmo : REC_WorldManager's editor (falls back to walk when it is not running)
---]]
config.placement = {
    ---@type REC_Farm.Shared.Enum.PlacementModes
    mode = "walk",
    ---@type number
    distance = 4.0,
    ---@type number
    minDistance = 1.5,
    ---@type number
    maxDistance = 12.0,
    ---@type number
    rotationStep = 5.0,
    ---@type number
    heightStep = 0.05,
    ---@type number
    maxHeightOffset = 1.5,
    ---@type number
    snapRotation = 0.0, -- 0 = free, 15 = snap to 15 degree steps
    ---@type integer
    ghostAlpha = 160,
    ---@type { r: integer, g: integer, b: integer, }
    validColor = { r = 60, g = 220, b = 100, },
    ---@type { r: integer, g: integer, b: integer, }
    invalidColor = { r = 220, g = 60, b = 60, },
    keys = {
        confirm = 191, -- ENTER
        cancel = 202, -- BACKSPACE
        rotateLeft = 174, -- LEFT
        rotateRight = 175, -- RIGHT
        raise = 172, -- UP
        lower = 173, -- DOWN
        closer = 15, -- SCROLL UP
        further = 14, -- SCROLL DOWN
        placeOnGround = 47, -- G
    },
}

---[[
---     Construction
---     instant : the building is done as soon as it is placed
---     timed   : the building needs its buildTime of active farm time (players are free meanwhile)
---]]
config.construction = {
    ---@type REC_Farm.Shared.Enum.ConstructionModes
    mode = "timed",
    ---@type string
    scaffoldModel = "prop_ld_farm_table01",
}

---[[
---     Weather
---     GTA weather names to the modifiers applied while that weather is active
---]]
config.weather = {
    ---@type boolean
    enabled = true,
    ---@type table<string, REC_Farm.Shared.Config.WeatherModifier>
    modifiers = {
        ["RAIN"] = { moisturePerHour = 25.0, growthMultiplier = 1.0, },
        ["THUNDER"] = { moisturePerHour = 35.0, growthMultiplier = 0.9, },
        ["CLEARING"] = { moisturePerHour = 5.0, growthMultiplier = 1.0, },
        ["OVERCAST"] = { moisturePerHour = 0.0, growthMultiplier = 1.0, },
        ["CLOUDS"] = { moisturePerHour = 0.0, growthMultiplier = 1.0, },
        ["EXTRASUNNY"] = { moisturePerHour = -6.0, growthMultiplier = 1.05, },
        ["SMOG"] = { moisturePerHour = -2.0, growthMultiplier = 0.95, },
        ["FOGGY"] = { moisturePerHour = 2.0, growthMultiplier = 1.0, },
        ["SNOW"] = { moisturePerHour = 0.0, growthMultiplier = 0.4, },
        ["BLIZZARD"] = { moisturePerHour = 0.0, growthMultiplier = 0.2, },
        ["XMAS"] = { moisturePerHour = 0.0, growthMultiplier = 0.4, },
        ["HALLOWEEN"] = { moisturePerHour = 0.0, growthMultiplier = 1.0, },
    },
    ---@type REC_Farm.Shared.Config.WeatherModifier
    default = { moisturePerHour = 0.0, growthMultiplier = 1.0, },
}

---[[
---     Progress bars per action (duration in ms, anims and props)
---]]
---@type table<REC_Farm.Shared.Enum.ActionTypes, REC_Farm.Shared.Config.Action>
config.actions = {
    [actionTypes.clear_obstacle] = { duration = 6000, anim = { dict = "amb@world_human_hammering@male@base", clip = "base", }, },
    [actionTypes.till] = { duration = 4000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.plant] = { duration = 3500, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.water] = { duration = 3000, anim = { dict = "weapon@w_sp_jerrycan", clip = "fire", }, prop = { model = "prop_wateringcan", bone = 28422, pos = vector3(0.0, 0.0, 0.0), rot = vector3(0.0, 0.0, 0.0), }, },
    [actionTypes.fertilize] = { duration = 3500, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.weed] = { duration = 4500, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.treat_crop] = { duration = 4000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.harvest] = { duration = 4500, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.clear_dead] = { duration = 4000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.fill_can] = { duration = 3000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.fill_feed] = { duration = 3500, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.fill_water] = { duration = 3500, anim = { dict = "weapon@w_sp_jerrycan", clip = "fire", }, },
    [actionTypes.clean_manure] = { duration = 4000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.pet] = { duration = 2500, anim = { dict = "creatures@rottweiler@tricks@", clip = "petting_franklin", }, },
    [actionTypes.brush] = { duration = 4000, anim = { dict = "mini@repair", clip = "fixing_a_player", }, },
    [actionTypes.treat_animal] = { duration = 5000, anim = { dict = "mp_medic", clip = "cpr_pumpchest", }, },
    [actionTypes.collect] = { duration = 5000, anim = { dict = "amb@world_human_gardener_plant@male@base", clip = "base", }, },
    [actionTypes.ship] = { duration = 2000, },
    [actionTypes.process_start] = { duration = 3000, anim = { dict = "mini@repair", clip = "fixing_a_player", }, },
    [actionTypes.process_collect] = { duration = 2500, anim = { dict = "mini@repair", clip = "fixing_a_player", }, },
    [actionTypes.build] = { duration = 4000, anim = { dict = "amb@world_human_hammering@male@base", clip = "base", }, },
    [actionTypes.upgrade] = { duration = 4000, anim = { dict = "amb@world_human_hammering@male@base", clip = "base", }, },
    [actionTypes.repair_tool] = { duration = 4000, anim = { dict = "mini@repair", clip = "fixing_a_player", }, },
}

---[[
---     Minimum role for every permission (a higher role always includes a lower one)
---]]
---@type table<REC_Farm.Shared.Enum.Permissions, REC_Farm.Shared.Enum.Roles>
config.permissions = {
    [permissions.land_purchase] = roles.owner,
    [permissions.land_expand] = roles.owner,
    [permissions.farm_edit] = roles.manager,
    [permissions.building_purchase] = roles.manager,
    [permissions.building_upgrade] = roles.manager,
    [permissions.building_remove] = roles.owner,
    [permissions.clear_obstacle] = roles.worker,
    [permissions.till] = roles.worker,
    [permissions.plant] = roles.worker,
    [permissions.crop_care] = roles.worker,
    [permissions.harvest] = roles.worker,
    [permissions.clear_dead] = roles.worker,
    [permissions.processing] = roles.worker,
    [permissions.animal_purchase] = roles.manager,
    [permissions.animal_care] = roles.worker,
    [permissions.animal_collect] = roles.worker,
    [permissions.animal_ship] = roles.manager,
    [permissions.animal_rename] = roles.manager,
    [permissions.animal_pasture] = roles.worker,
    [permissions.manage_members] = roles.manager,
    [permissions.farm_settings] = roles.owner,
    [permissions.storage] = roles.worker,
}

---[[
---     Farmland cell visuals (the crop object is the crop model, the soil is a marker)
---]]
config.cellVisual = {
    ---@type boolean
    drawSoilMarker = true,
    ---@type integer
    soilMarkerType = 1,
    ---@type { r: integer, g: integer, b: integer, a: integer, }
    soilMarkerColor = { r = 120, g = 80, b = 40, a = 70, },
    ---@type number
    soilMarkerDrawDistance = 25.0,
    ---@type integer
    refreshInterval = 4000, -- ms
    ---[[
    ---     Small props drawn on a cell as its weed level climbs (1 - 3 of them)
    ---]]
    ---@type string[]
    weedModels = { "prop_fernba", "prop_fernbb", "prop_plant_fern_01a", },
    ---@type number
    weedSpread = 0.55,
    ---[[
    ---     Marker on the tilled cell the player can plant on
    ---]]
    ---@type number
    plantReach = 1.6,
}

---[[
---     Inspection camera ("look closer")
---]]
config.inspection = {
    ---@type integer
    transitionTime = 700, -- ms
    ---@type number
    cropFov = 35.0,
    ---@type number
    animalFov = 40.0,
    ---@type number
    cropHeight = 1.1,
    ---@type number
    animalHeight = 1.3,
    ---@type integer
    refreshInterval = 1500, -- ms
    keys = {
        exit = 202, -- BACKSPACE
        actions = 51, -- E
    },
}

---[[
---     Watering can (ox_inventory durability is the water level)
---]]
config.wateringCan = {
    ---@type integer
    capacity = 100,
    ---@type integer
    costPerUse = 10,
    ---@type number
    moisturePerUse = 40.0,
    ---@type integer
    troughUnitsPerCan = 20,
}

---[[
---     Tool durability (ox_inventory durability, 100 is new)
---     usePerAction is subtracted per action, at 0 the tool is broken until repaired
---]]
config.toolDurability = {
    ---@type boolean
    enabled = true,
    ---@type number
    repairKitRestore = 100.0,
}

---[[
---     Management panel command (the panel itself is gated by farm membership on the server)
---]]
config.management = {
    ---@type string
    command = "farm",
    ---@type string
    editCommand = "farmedit",
}

---[[
---     Blips
---]]
config.blips = {
    ---@type boolean
    showPurchasePoints = true,
    purchasePoint = { sprite = 469, color = 2, scale = 0.7, },
    ownFarm = { sprite = 469, color = 25, scale = 0.8, },
}

---[[
---     Farm boundary drawing while editing / placing
---]]
config.boundary = {
    ---@type { r: integer, g: integer, b: integer, a: integer, }
    ownedColor = { r = 60, g = 220, b = 100, a = 120, },
    ---@type { r: integer, g: integer, b: integer, a: integer, }
    availableColor = { r = 220, g = 200, b = 60, a = 120, },
    ---@type number
    height = 1.0,
}

---[[
---     Animal visuals (client)
---     manureModel     placeholder prop for a manure spot, swap for an addon prop
---     nameTagDistance draw the animal's name above its head within this distance (0 = off)
---     ai              controller tick, stuck detection and follow distances
---]]
config.animalVisual = {
    ---@type string
    manureModel = "prop_rock_4_c",
    ---@type number
    nameTagDistance = 8.0,
    ---@type integer
    resolveInterval = 2000, -- ms, how often peds of streamed animals are looked up by net id
    ai = {
        ---@type integer
        tickInterval = 500, -- ms
        ---@type integer
        reportInterval = 5000, -- ms, position / state reports of a controlled ped (server: sv_config animals.reportInterval)
        ---@type number
        arriveDistance = 1.6,
        ---@type number
        followDistance = 2.5,
        ---@type number
        followRetaskDistance = 5.0,
        ---@type number
        wanderSpeed = 1.0,
        ---@type number
        followSpeed = 1.8,
        ---@type integer
        idleTime = { 4000, 12000, }, -- ms, min / max before the next decision
        ---@type integer
        restTime = { 10000, 25000, }, -- ms
        ---@type integer
        stuckTime = 10000, -- ms without moving before the ped is re-tasked
        ---@type number
        stuckDistance = 0.4,
        ---@type integer
        stuckRetries = 2, -- re-tasks before the server is asked to reset the ped
        ---@type number
        escapeRadius = 25.0,
        ---@type number
        homeRadius = 6.0, -- an escaped animal led inside housing radius + this is home
    },
}

---[[
---     Tool item required per action (nil = no tool)
---]]
---@type table<REC_Farm.Shared.Enum.ActionTypes, REC_Farm.Shared.Enum.ToolKinds>
config.actionTools = {
    [actionTypes.till] = toolKinds.hoe,
    [actionTypes.water] = toolKinds.wateringCan,
    [actionTypes.weed] = toolKinds.scythe,
    [actionTypes.harvest] = toolKinds.basket,
    [actionTypes.clear_dead] = toolKinds.scythe,
    [actionTypes.clean_manure] = toolKinds.shovel,
    [actionTypes.brush] = toolKinds.brush,
}

return config

---@class REC_Farm.Shared.Config.Action
---@field duration integer
---@field anim? { dict: string, clip: string, }
---@field prop? { model: string, bone: integer, pos: vector3, rot: vector3, }

---@class REC_Farm.Shared.Config.WeatherModifier
---@field moisturePerHour number
---@field growthMultiplier number
©2026 RE:CORD Development Group. All rights reserved.