• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

ConstructableFarm v1.0.1

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.

ConstructableFarm v1.0.1

by Masterninja13579


ConstructableFarm is a farming system in which workers are assigned to crop fields. These workers automatically plant, water, and harvest crops to generate a steady gold income. The farms can be constructed and destroyed like normal structures.

I have had this idea for a while now and haven't been able to find anything like it so far. I am not finished with the project yet as there are several things I plan to optimize. Feedback and recommendations will be much appreciated!

[jass=vjass]scope ConstructableFarm initializer Setup
// By Masterninja13579
// --version 1.0.1--
//
// Constructable Farm is a farming system in which workers are
// assigned to crop fields. These workers automatically plant,
// water, and harvest crops to generate a steady gold income.
// The farms can be constructed and destroyed like normal structures.
// Happy Farming!
//
// Import Instructions:
// 1. Copy the custom units--Farm, Farm (constructable), and
// Farm (pathing)--into your map along with the Harvest ability.
// 2. Copy the Dummy unit into your map. If your map already contains
// a standard dummy unit with the model "Dummy.mdx" then skip to
// step 4.
// 3. Export the Dummy.mdx model file then import it into your map.
// Make sure that the Dummy unit has that file as it's model
// 4. Copy the ConstructableFarm trigger into your map. Inside the
// trigger, go to the Modifiable Variables section and enter all
// the rawcodes for the custom units and the harvest ability.
// 5. Test it out and enjoy! To allow units to construct a farm let
// them construct the Farm (constructable) unit. To allow units to
// harvest the farm just give them the Harvest ability
//
// Possible Future Additions:
// - Crops tilting as units walk through them
// - Floating text to show if a farm is occupied.
// - Ability to have multiple workers per farm
// - Implementation of Bribe's MissileRecycler
// - Multiple crop types with different properties
//
// Known Bugs:
// - (IN PROGRESS) Occasionally the worker will get stuck and will
// stop farming. Re-order him to harvest the farm if it happens
// to you.
//
// Credits:
// - Vexorian, for Dummy.mdx
//
//=======================================================
//================ Modifiable Variables =================
//=======================================================
globals
// Rawcode of the farm that is buildable by workers
// --Farm (constructable)
private constant integer CONSTRUCTABLE_ID = 'h000'

// Rawcode of the actual farm
// --Farm
private constant integer ACTUAL_ID = 'h001'

// Rawcode of the dummy unit to expand the pathing of farms
// --Farm (pathing)
private constant integer PATHING_ID = 'h002'

// Rawcode of the standard dummy unit
// --Dummy
private constant integer DUMMY_ID = 'dumi'

// Rawcode of the Harvest ability and it's order string.
// Note: units that use the harvest ability must not have any
// other abilities with the same order string.
private constant integer SPELL_ID = 'A000'
private constant string SPELL_ORDER = "channel"

// Width and length of the area crops can grow in
// More accurately it's the x distance (width) and y distance
// (length) that a crop can grow from the farm center
private constant real GROWABLE_WIDTH = 170
private constant real GROWABLE_LENGTH = 170

// Radius to NOT plant crops from the center of the farm
private constant real CENTER_RADIUS = 35

// Gold reward for each crop harvested
private constant integer GOLD_PER_CROP = 12

// These variables are used to enlarge the crops as they grow
// XY_SCALE is the width and length, and Z_SCALE is the height
private constant real XY_SCALE_START = 0.3
private constant real XY_SCALE_END = 1.0
private constant real Z_SCALE_START = 2.0
private constant real Z_SCALE_END = 1.0

// The total time a crop takes to finish growing
private constant real GROW_TIME = 180.0

// The time a crop can grow before needing more water
private constant real THIRST_INTERVAL = 80.0

// The time a crop takes to die if not watered
private constant real DECAY_TIME = 60.0

// The time it takes to plant a crop
private constant real PLANT_DURATION = 6.0

// The time it takes to water a crop
private constant real WATER_DURATION = 3.0

// The time between water effects
private constant real WATER_SFX_INTERVAL = 1.0

// The start time for water effects. The first water effect
// appears when the time reaches WATER_SFX_INTERVAL
private constant real WATER_SFX_TIME_OFFSET = 0.6

// The time it takes to harvest
private constant real HARVEST_DURATION = 4.0

// The starting height of a water effect
private constant real WATER_HEIGHT = 70.0

// The XY speed of a water effect
private constant real WATER_SPEED = 150.0

// The scaling value os a water effect
private constant real WATER_SCALE = 0.3

// The model path of the crop
private constant string CROP_PATH = "Doodads\\LordaeronSummer\\Plants\\Corn\\Corn.mdl"

// The model path of the water effect
private constant string WATER_SFX = "Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl"

// The model path to use when a crop is harvested
private constant string HARVEST_SFX = "UI\\Feedback\\GoldCredit\\GoldCredit.mdl"

// The model path to use when a crop decays
private constant string DECAY_SFX = "Abilities\\Spells\\Orc\\Devour\\DevourEffectArt.mdl"

// The model path to use when the farm is destroyed
private constant string DEATH_SFX = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"

// I recommend that you do not mess with these very much.
// Having 100 farms with 30 crops per farm will slow your system
// down tremendously, and it is more than enough.
private constant integer MAXIMUM_FARMS = 100
private constant integer MAXIMUM_CROPS = 30
endglobals
//=======================================================
//============= End of Modifiable Variables =============
//=======================================================

native UnitAlive takes unit u returns boolean
private keyword Farm
private keyword Water

globals
private constant integer IDLE = 0
private constant integer PLANTING = 1
private constant integer WATERING = 2
private constant integer HARVESTING = 3
private constant real GRID_DISTANCE = 128
private constant real TIMEOUT = 0.03125000

private trigger harvestPoint = CreateTrigger()
private trigger workerOrder = CreateTrigger()
private Farm array farms
private Water array missiles
private integer totalFarms = 0
private integer totalMissiles = 0
private timer t = CreateTimer()
endglobals

//=======================================================
//======================= Structs =======================
//=======================================================
// struct crop
// method destroy takes nothing returns nothing
// method getX takes nothing returns real
// method getY takes nothing returns real
// method isThirsty takes nothing returns boolean
// method quenchThirst takes nothing returns nothing
// method isMature takes nothing returns boolean
// method tick takes nothing returns nothing
// static method create takes real x, real y, Farm parent returns thistype
private struct Crop
private real x
private real y
private unit u
private effect model
private real age
private real thirst
private real health
private Farm parent

method destroy takes nothing returns nothing
call DestroyEffect(.model)
call RemoveUnit(.u)
set .u = null
set .model = null
call .deallocate()
endmethod

private method updateSize takes nothing returns nothing
local real ratio = .age/GROW_TIME
local real xy = ratio*(XY_SCALE_END - XY_SCALE_START) + XY_SCALE_START
local real z = ratio*(Z_SCALE_END - Z_SCALE_START) + Z_SCALE_START
call SetUnitScale(.u, xy, xy, z)
endmethod

method getX takes nothing returns real
return .x
endmethod

method getY takes nothing returns real
return .y
endmethod

method isThirsty takes nothing returns boolean
return .thirst <= 0
endmethod

method quenchThirst takes nothing returns nothing
set .thirst = THIRST_INTERVAL
endmethod

method isMature takes nothing returns boolean
return age >= GROW_TIME and .health >= DECAY_TIME
endmethod

method tick takes nothing returns nothing
if(.thirst <= 0) then
set .health = .health - TIMEOUT
if(.health <= 0) then
call .parent.decayCrop(this)
endif
else
if(.age < GROW_TIME) then
set .age = .age + TIMEOUT
endif
set .thirst = .thirst - TIMEOUT
if(.health < DECAY_TIME) then
set .health = .health + TIMEOUT
endif
call updateSize()
endif
endmethod

static method create takes real x, real y, Farm parent returns thistype
local thistype this = allocate()
set .x = x
set .y = y
set .parent = parent
set .age = 0
set .thirst = THIRST_INTERVAL
set .health = DECAY_TIME
// Create unit
set .u = CreateUnit(Player(15), DUMMY_ID, x, y, GetRandomReal(0, 360))
set .model = AddSpecialEffectTarget(CROP_PATH, .u, "origin")
call SetUnitAnimationByIndex(.u, 90)
return this
endmethod
endstruct

// struct Water
// method destroy takes nothing returns nothing
// method isFinished takes nothing returns boolean
// method tick takes nothing returns nothing
// static method create takes real x, real y, real x2, real y2 returns thistype
private struct Water
private unit u
private effect model
private real x
private real y
private real z
private real dx
private real dy
private real zSlope // The M variable. (y = mx^2 + b)
private real age
private boolean expired

method destroy takes nothing returns nothing
call DestroyEffect(.model)
call RemoveUnit(.u)
set .u = null
set .model = null
call .deallocate()
endmethod

method isFinished takes nothing returns boolean
return expired
endmethod

method tick takes nothing returns nothing
set .age = .age + TIMEOUT
set .x = .x + .dx
set .y = .y + .dy
set .z = zSlope*.age*.age + WATER_HEIGHT
if(.z <= 0) then
set .expired = true
else
call SetUnitX(.u, .x)
call SetUnitY(.u, .y)
call SetUnitFlyHeight(.u, .z, 0)
endif
endmethod

static method create takes real x, real y, real x2, real y2 returns thistype
local thistype this = allocate()
local real angle = Atan2(y2 - y, x2 - x)
local real distance = SquareRoot(Pow(x2 - x, 2) + Pow(y2 - y, 2))
set .x = x
set .y = y
set .z = WATER_HEIGHT
set .dx = Cos(angle)*WATER_SPEED*TIMEOUT
set .dy = Sin(angle)*WATER_SPEED*TIMEOUT
set .zSlope = (-1*WATER_HEIGHT) / Pow(distance/WATER_SPEED, 2)
set .age = 0
set .expired = false
// Create unit
set .u = CreateUnit(Player(15), DUMMY_ID, x, y, angle)
set .model = AddSpecialEffectTarget(WATER_SFX, .u, "origin")
call PauseUnit(.u, true)
call SetUnitFlyHeight(.u, .z, 0)
call SetUnitFacing(.u, Rad2Deg(angle))
call SetUnitScale(.u, WATER_SCALE, WATER_SCALE, WATER_SCALE)
call SetUnitAnimationByIndex(.u, 90)
return this
endmethod
endstruct

// struct FarmWorker
// method destroy takes nothing returns nothing
// method getTaskX takes nothing returns real
// method getTaskY takes nothing returns real
// method getTaskCrop takes nothing returns crop
// method getUnit takes nothing returns unit
// method getState takes nothing returns integer
// method getParentFarm takes nothing returns Farm
// method registerCastStart takes nothing returns nothing
// method registerCastStop takes nothing returns nothing
// method setTask takes integer state, real x, real y, crop target returns nothing
// method setTaskSimple takes integer state, location l returns nothing
// method tick takes nothing returns nothing
// static method isUnitWorker takes unit u returns boolean
// static method create takes unit u, Farm parent returns thistype
private struct FarmWorker
private real taskX
private real taskY
private Crop taskCrop
private unit u
private integer state
private boolean casting
private real time
private Farm parent
private static group workers = CreateGroup()

method destroy takes nothing returns nothing
call GroupRemoveUnit(workers, .u)
if(.casting) then
call .parent.issuePointOrder(.u, "move", GetUnitX(.u), GetUnitY(.u))
endif
set .u = null
call .deallocate()
endmethod

method getTaskX takes nothing returns real
return .taskX
endmethod

method getTaskY takes nothing returns real
return .taskY
endmethod

method getTaskCrop takes nothing returns Crop
return .taskCrop
endmethod

method getUnit takes nothing returns unit
return .u
endmethod

method getState takes nothing returns integer
return .state
endmethod

method getParentFarm takes nothing returns Farm
return .parent
endmethod

method registerCastStart takes nothing returns nothing
set .casting = true
endmethod

method registerCastStop takes nothing returns nothing
set .casting = false
endmethod

method setTask takes integer state, real x, real y, Crop target returns nothing
set .state = state
set .taskX = x
set .taskY = y
set .taskCrop = target
set .casting = false
set .time = 0
call .parent.issuePointOrder(.u, SPELL_ORDER, x, y)
endmethod

method setTaskSimple takes integer state, location l returns nothing
set .state = state
set .taskX = GetLocationX(l)
set .taskY = GetLocationY(l)
set .taskCrop = 0
set .casting = false
set .time = 0
call .parent.issuePointOrder(.u, SPELL_ORDER, .taskX, .taskY)
endmethod

method tick takes nothing returns nothing
if not(UnitAlive(.u)) then
call .parent.removeFarmWorker()
elseif(.casting) then
set .time = .time + TIMEOUT
if(.state == PLANTING and .time >= PLANT_DURATION) then
call .parent.taskIsDone(this)
elseif(.state == WATERING) then
if(ModuloReal(.time + WATER_SFX_TIME_OFFSET, WATER_SFX_INTERVAL) < TIMEOUT) then
call .parent.createWater(GetUnitX(.u), GetUnitY(.u), .taskX, .taskY)
endif
if(taskCrop == 0) then
set .state = IDLE
elseif(.time >= WATER_DURATION) then
call .parent.taskIsDone(this)
endif
elseif(.state == HARVESTING and .time >= HARVEST_DURATION) then
call .parent.taskIsDone(this)
endif
elseif(.state == WATERING and taskCrop == 0) then
set .state = IDLE
elseif(.state == IDLE) then
call .parent.taskIsDone(this)
elseif not(GetUnitCurrentOrder(.u) == OrderId(SPELL_ORDER)) then
call .parent.issuePointOrder(.u, SPELL_ORDER, .taskX, .taskY)
endif
endmethod

static method isUnitWorker takes unit u returns boolean
return IsUnitInGroup(u, workers)
endmethod

static method create takes unit u, Farm parent returns thistype
local thistype this = allocate()
set .u = u
set .state = IDLE
set .casting = false
set .time = 0
set .parent = parent
call GroupAddUnit(workers, u)
return this
endmethod
endstruct

// struct Farm
// method destroy takes nothing returns nothing
// method destroyCrop takes crop c returns nothing
// method createCrop takes real x, real y returns nothing
// method createWater takes real x, real y, real x2, real y2 returns nothing
// method harvestCrop takes crop c returns nothing
// method decayCrop takes crop c returns nothing
// method isFull takes nothing returns boolean
// method getUnit takes nothing returns unit
// method removeFarmWorker takes nothing returns nothing
// method addFarmWorker takes unit u returns nothing
// method getFarmWorker takes nothing returns FarmWorker
// method getRandomPlantLoc takes nothing returns location
// method issuePointOrder takes unit u, string order, real x, real y returns nothing
// method assignNewTask takes FarmWorker w returns nothing
// method taskIsDone takes FarmWorker w returns nothing
// method tick takes nothing returns nothing
// static method create takes real x, real y, player p returns thistype
private struct Farm
private unit u
private FarmWorker worker
private Crop array crops[MAXIMUM_CROPS]
private integer totalCrops
private group pathing

method destroy takes nothing returns nothing
local integer i = 0
local unit p

if not(.worker == 0) then
set p = .worker.getUnit()
call .worker.destroy()
call IssuePointOrder(p, "move", GetUnitX(p), GetUnitY(p))
endif
loop
exitwhen i >= .totalCrops
if(GetRandomInt(0, 3) == 0) then
call DestroyEffect(AddSpecialEffect(DEATH_SFX, .crops.getX(), .crops.getY()))
endif
call .crops.destroy()
set i = i + 1
endloop
loop
set p = FirstOfGroup(.pathing)
exitwhen p == null
call GroupRemoveUnit(.pathing, p)
call RemoveUnit(p)
endloop
call DestroyEffect(AddSpecialEffect(DEATH_SFX, GetUnitX(.u), GetUnitY(.u)))
call RemoveUnit(.u)
set .u = null
set p = null
set .pathing = null
call DestroyGroup(.pathing)
call .deallocate()
endmethod

method destroyCrop takes Crop c returns nothing
local integer i = 0
loop
exitwhen i >= totalCrops
if(crops == c) then
call c.destroy()
set totalCrops = totalCrops - 1
set crops = crops[totalCrops]
else
set i = i + 1
endif
endloop
endmethod

method createCrop takes real x, real y returns nothing
set crops[totalCrops] = Crop.create(x, y, this)
set totalCrops = totalCrops + 1
endmethod

method createWater takes real x, real y, real x2, real y2 returns nothing
set missiles[totalMissiles] = Water.create(x, y, x2, y2)
set totalMissiles = totalMissiles + 1
endmethod

method harvestCrop takes Crop c returns nothing
local player p = GetOwningPlayer(.u)
call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) + GOLD_PER_CROP)
call DestroyEffect(AddSpecialEffect(HARVEST_SFX, c.getX(), c.getY()))
call destroyCrop(c)
set p = null
endmethod

method decayCrop takes Crop c returns nothing
call DestroyEffect(AddSpecialEffect(DECAY_SFX, c.getX(), c.getY()))
call destroyCrop(c)
endmethod

method isFull takes nothing returns boolean
return not(.worker == 0)
endmethod

method getUnit takes nothing returns unit
return .u
endmethod

method removeFarmWorker takes nothing returns nothing
if not(.worker == 0) then
call .worker.destroy()
set .worker = 0
endif
endmethod

method addFarmWorker takes unit u returns nothing
call .removeFarmWorker()
set .worker = FarmWorker.create(u, this)
endmethod

method getFarmWorker takes nothing returns FarmWorker
return .worker
endmethod

method getRandomPlantLoc takes nothing returns location
local real x = GetUnitX(.u)
local real y = GetUnitY(.u)
local real x2
local real y2
local real distance
local boolean success = false
local integer iterations = 0

loop
set x2 = GetRandomReal(x - GROWABLE_WIDTH, x + GROWABLE_WIDTH)
set y2 = GetRandomReal(y - GROWABLE_LENGTH, y + GROWABLE_LENGTH)
set distance = SquareRoot(Pow(x - x2, 2) + Pow(y - y2, 2))
if(distance > CENTER_RADIUS) then
set success = true
endif
set iterations = iterations + 1
exitwhen success or iterations > 50
endloop
if(success) then
return Location(x2, y2)
endif
return Location(0, 0)
endmethod

method issuePointOrder takes unit u, string order, real x, real y returns nothing
call DisableTrigger(harvestPoint)
call DisableTrigger(workerOrder)
call IssuePointOrder(u, order, x, y)
call EnableTrigger(harvestPoint)
call EnableTrigger(workerOrder)
endmethod

method assignNewTask takes FarmWorker w returns nothing
local integer i = 0

loop
exitwhen i >= totalCrops
if(crops.isThirsty()) then
call w.setTask(WATERING, crops.getX(), crops.getY(), crops)
return
endif
set i = i + 1
endloop
set i = 0
loop
exitwhen i >= totalCrops
if(crops.isMature()) then
call w.setTask(HARVESTING, crops.getX(), crops.getY(), crops)
return
endif
set i = i + 1
endloop
if(totalCrops >= MAXIMUM_CROPS) then
call w.setTask(IDLE, 0, 0, 0)
else
call w.setTaskSimple(PLANTING, .getRandomPlantLoc())
endif
endmethod

method taskIsDone takes FarmWorker w returns nothing
if(w.getState() == PLANTING) then
call createCrop(w.getTaskX(), w.getTaskY())
elseif(w.getState() == WATERING) then
call w.getTaskCrop().quenchThirst()
elseif(w.getState() == HARVESTING) then
call harvestCrop(w.getTaskCrop())
endif
call assignNewTask(w)
endmethod

method tick takes nothing returns nothing
local integer i = 0

if not(.worker == 0) then
call .worker.tick()
endif
loop
exitwhen i >= .totalCrops
call .crops.tick()
set i = i + 1
endloop
endmethod

static method create takes real x, real y, player p returns thistype
local thistype this = allocate()
local integer i = -1
local integer i2

set .u = CreateUnit(p, ACTUAL_ID, x, y, bj_UNIT_FACING)
set .totalCrops = 0
set .pathing = CreateGroup()
// Create pathing units
loop
exitwhen i >= 2
set i2 = -1
loop
exitwhen i2 >= 2
call GroupAddUnit(.pathing, CreateUnit(p, PATHING_ID, x + i*GRID_DISTANCE, y + i2*GRID_DISTANCE, bj_UNIT_FACING))
set i2 = i2 + 1
endloop
set i = i + 1
endloop

return this
endmethod
endstruct

//=======================================================
//====================== Functions ======================
//=======================================================
private function Periodic takes nothing returns nothing
local integer i = 0

loop
exitwhen i >= totalFarms
if(UnitAlive(farms.getUnit())) then
call farms.tick()
set i = i + 1
else
call farms.destroy()
set totalFarms = totalFarms - 1
set farms = farms[totalFarms]
endif
endloop
set i = 0
loop
exitwhen i >= totalMissiles
if(missiles.isFinished()) then
call missiles.destroy()
set totalMissiles = totalMissiles - 1
set missiles = missiles[totalMissiles]
else
call missiles.tick()
set i = i + 1
endif
endloop
if(totalMissiles == 0 and totalFarms == 0) then
call PauseTimer(t)
endif
endfunction

private function GetFarm takes unit u returns Farm
local integer i = 0

loop
exitwhen i >= totalFarms
if(farms.getUnit() == u) then
return farms
endif
set i = i + 1
endloop
return 0
endfunction

private function AddWorkerToFarm takes unit u, Farm target returns boolean
if(target == 0 or target.isFull()) then
return false
endif
call target.addFarmWorker(u)
return true
endfunction

private function AddWorker takes unit u returns boolean
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real distance
local real closest = 99999
local Farm candidate = 0
local player p = GetOwningPlayer(u)
local unit f
local integer i = 0

loop
exitwhen i >= totalFarms
set f = farms.getUnit()
if(GetOwningPlayer(f) == p and not(farms.isFull())) then
set distance = SquareRoot(Pow(x - GetUnitX(f), 2) + Pow(y - GetUnitY(f), 2))
if(distance < closest) then
set closest = distance
set candidate = farms
endif
endif
set i = i + 1
endloop
if not(candidate == 0) then
call AddWorkerToFarm(u, candidate)
return true
endif

set p = null
set u = null
return false
endfunction

private function GetWorker takes unit u returns FarmWorker
local integer i = 0
loop
exitwhen i >= totalFarms
if(farms.getFarmWorker().getUnit() == u) then
return farms.getFarmWorker()
endif
set i = i + 1
endloop
return 0
endfunction

private function RemoveWorker takes unit u returns boolean
local integer i = 0

loop
exitwhen i >= totalFarms
if(farms.getFarmWorker().getUnit() == u) then
call farms.removeFarmWorker()
return true
endif
set i = i + 1
endloop
return false
endfunction

private function CreateFarm takes unit u returns nothing
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local player p = GetOwningPlayer(u)

call RemoveUnit(u)
if(totalFarms < MAXIMUM_FARMS) then
set farms[totalFarms] = Farm.create(x, y, p)
set totalFarms = totalFarms + 1
if(totalFarms == 1) then
call TimerStart(t, TIMEOUT, true, function Periodic)
endif
endif

set p = null
endfunction

private function RegisterTaskStart takes unit u returns nothing
local FarmWorker worker = GetWorker(u)

if not(worker == 0) then
call worker.registerCastStart()
endif
endfunction

private function RegisterTaskStop takes unit u returns nothing
local FarmWorker worker = GetWorker(u)

if not(worker == 0) then
call worker.registerCastStop()
endif
endfunction

private function HandleHarvestUnit takes nothing returns nothing
local unit u = GetOrderedUnit()
local unit t = GetOrderTargetUnit()
local player pu = GetOwningPlayer(u)
local player pt = GetOwningPlayer(t)
local FarmWorker worker = GetWorker(u)

call IssuePointOrder(u, "move", GetUnitX(u), GetUnitY(u))
if not(worker == 0) and pu == pt then
call RemoveWorker(u)
endif
if(GetUnitTypeId(t) == ACTUAL_ID and pu == pt) then
if not(AddWorkerToFarm(u, GetFarm(t))) then
call AddWorker(u)
endif
endif

set u = null
endfunction

//=======================================================
//=================== Event Handlers ====================
//=======================================================
private function HarvestPointCondition takes nothing returns nothing
local unit u = GetOrderedUnit()

if(GetIssuedOrderId() == OrderId(SPELL_ORDER)) then
call IssuePointOrder(u, "move", GetUnitX(u), GetUnitY(u))
endif

set u = null
endfunction

private function HarvestUnitCondition takes nothing returns nothing
if(GetIssuedOrderId() == OrderId(SPELL_ORDER)) then
call HandleHarvestUnit()
endif
endfunction

private function CastCondition takes nothing returns nothing
if(GetSpellAbilityId() == SPELL_ID and FarmWorker.isUnitWorker(GetSpellAbilityUnit())) then
call RegisterTaskStart(GetSpellAbilityUnit())
endif
endfunction

private function WorkerOrderCondition takes nothing returns nothing
if(FarmWorker.isUnitWorker(GetOrderedUnit()) and not(GetIssuedOrderId() == OrderId(SPELL_ORDER))) then
call RemoveWorker(GetOrderedUnit())
endif
endfunction

private function CancelCondition takes nothing returns nothing
if(GetSpellAbilityId() == SPELL_ID and FarmWorker.isUnitWorker(GetSpellAbilityUnit())) then
call RegisterTaskStop(GetSpellAbilityUnit())
endif
endfunction

private function ConstructCondition takes nothing returns nothing
if(GetUnitTypeId(GetConstructedStructure()) == CONSTRUCTABLE_ID) then
call CreateFarm(GetConstructedStructure())
endif
endfunction

//=======================================================
//=================== Event Triggers ====================
//=======================================================
// 1. on order to cast harvest on a point (detects when player
// tries to cast harvest incorrectly)
// 2. on order to cast harvest on a unit (detects when player
// tries to cast harvest incorrectly, but if target unit is
// a farm it will try to assign the ordered unit to it)
// 3. on cast of harvest ability (detects when a worker begins
// a task, since all other attempts at casting harvest will
// be stopped)
// 4. on order of worker unit (detects when a farm worker is
// ordered to perform a different task by the player)
// 5. on cancel of harvest ability (detects if a worker is
// interrupted while planting, watering, or growing crops)
// 6. on finish of construction
private function Setup takes nothing returns nothing
local trigger harvestUnit = CreateTrigger()
local trigger cast = CreateTrigger()
local trigger cancel = CreateTrigger()
local trigger construct = CreateTrigger()
local integer i = 0
local player p

loop
exitwhen i > 11
set p = Player(i)
call TriggerRegisterPlayerUnitEvent(harvestPoint, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(harvestUnit, p, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(cast, p, EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
call TriggerRegisterPlayerUnitEvent(workerOrder, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(workerOrder, p, EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(workerOrder, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
call TriggerRegisterPlayerUnitEvent(cancel, p, EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(construct, p, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null)
set i = i + 1
endloop

call TriggerAddAction(harvestPoint, function HarvestPointCondition)
call TriggerAddAction(harvestUnit, function HarvestUnitCondition)
call TriggerAddAction(cast, function CastCondition)
call TriggerAddAction(workerOrder, function WorkerOrderCondition)
call TriggerAddAction(cancel, function CancelCondition)
call TriggerAddAction(construct, function ConstructCondition)

set harvestUnit = null
set cast = null
set cancel = null
set construct = null
set p = null
endfunction

endscope[/code]


Changelog

Version 1.0
- Initial release

Version 1.0.1
- Fixed a bug that caused the deallocate method of the FarmWorker to be called twice.


Keywords:
Farm, Crop, Wheat, Corn, Resource, Harvest, System
Contents

ConstructableFarm (Map)

Reviews
14:56, 11th Mar 2016 IcemanBo: Conversation in thread.

Although they are both additional methods of getting resources, that is the extent of their similarities. I struggled to categorize this correctly because it's certainly not a spell, but as a system it performs a much more specific purpose than a true resource system would. Here are a few of the differences:
  • ConstructableFarm presents a renewable gold resource. It doesn't have to be mined or gathered from a limited supply source, but as a result produces gold at a much slower rate. (Though that is configurable)
  • Workers do not have to return the resource to a building to "cash in," though I am not opposed to the idea and may add an option for that in the future.
  • This has a simulation aspect and visual appeal. The workers actually plant crops, water them when they are thirsty, and cut them down when they are mature.

The main concept idea is based off of farms in the game Age of Empires. The fact that farms cannot be built upon but can be walked over is a good example of that.
 
Last edited:
So basicly a worker gets order "harvest" on the farm,
then he starts working on the farm. Meanwhile working
the whole process is taking action and crop is growing and being farmed.

Instead of adding x gold to the user, the user should be able to register code/trigger.
There the user should be able to do what ever he wants with the given input information of the instance.

For sizing the crop scale, only the first paramter can be used. The y/z, can stay the same always.

Getting an error when a farm gets destroyed.

UnitIndexer or hshtable should be used to bind farm with worker.
 
Instead of adding x gold to the user, the user should be able to register code/trigger.
There the user should be able to do what ever he wants with the given input information of the instance.

That is a good idea, I will go ahead and add that feature to the next update. Thanks!

Getting an error when a farm gets destroyed.

Hmm, could you give me more information on what the error was? I will have more time to work on it in the next few days, that may help me narrow my search.

UnitIndexer or hshtable should be used to bind farm with worker.

Do you mean the Unit Indexer by Nestharus? I have yet to implement it into any of my code, but I know it's a powerful resource and that many spells on the Hive use it.

I can see some areas where that could be used, particularly when an event is fired for a worker and we need to locate the farm he is a part of... I will experiment with this. If there are specific ways you would like to see it used, I am open to suggestions.
 
more information on what the error was
I'm simply in debug mode, and I get something like following message when destroying a farm that is in usage:
"Attempt to destroy a 0 struct".
That should mean that something is wrong with the deallocation.

Do you mean the Unit Indexer by Nestharus?
For example. The other one that might be used is the Bribe's indexer.
It was initialy more GUI friendly, but recently he provided a vJASS plug in. link

What would be useful about that is that a O(n) search complexity is not needed anymore,
to find a specific instance. Currently a loop is required, which is not the best solutin in general and for more instances.
 
I guess the main goal is to make a very well looking gold income system.

It may look nice at the moment, but the functionality is very low compared
to the mighty code it uses. The result is basicly giving x gold under certain condition.

If there should be functionality then I still would primary
first focus only on the farm composition and visuals,
and let all the gameplay functionaltiy let define by the user himself.
Like for example giving him access to events of the farm, so he can add
gold/lumber/custom resources, or do anything else if he wants on himself.

What do think of it? I would like to hear your opinion on that.

Some simple code documentation is very welcome.
It can make the reviewing a lot of easier, if we know
what the code block is exactly about which we are currently reading.

The water is paused, but can't the crops be paused as well?

In method destroy in struct Farm the unit group won't be destroyed,
because the variable doesn't point to the object anymore, but was set to null already.
 
Last edited:
I agree with you Icemanbo, the current version is not very configurable.

I have been thinking about what those who download the project might want
to change, and there are certainly things to change: primarily your
suggestion on giving the user access to the events of the farm.

As far as those events go, the upcoming version will fire an event when a
crop is harvested via executing a GUI trigger set by the player. They will be
given the farm and the worker that harvested the crop. This way they could
decide what resource to add and how much to add, they could implement a
veterancy system so that workers return more resources the longer they
work on a farm, or any other ideas they might have.

Is this the kind of customization you were hoping to see?

I have not been thorough with my code documentation, I can definitely add
more comments throughout the code.

Thanks for the help and code fixes!
 
Basicly, yes.

I don't want to force you to add such events, but outsourcing the functionality
might be good for the whole system, so it only deals with the farming visuls.

Because with having this very functionality as hardcoded it seems a bit wasted for me.
Such a big code and structures, and in the end, only adding x amount of gold is possible. It's okay, but unnecessarily limited in my eyes.
 
Top