Farming Systeem
Tuinieren, planten groeien, en oogsten.
Overzicht
┌─────────────────────────────────────────────────────────────┐
│ Farming Cycle │
├─────────────────────────────────────────────────────────────┤
│ │
│ Planten ──► Water ──► Wachten ──► Oogsten │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Seed Item Water Growth Harvest │
│ Consumed Needed Stages Reward │
│ │
│ Growth Factors: │
│ • Tijd (realtime of game-tijd) │
│ • Water status │
│ • Fertilizer bonus │
│ • Seizoen bonus │
│ │
└─────────────────────────────────────────────────────────────┘
Farm Plot
Scene Structuur
FarmPlot (Node2D)
├── Sprite2D (soil texture)
├── PlantSprite (Sprite2D)
├── WaterIndicator (Sprite2D)
├── GrowthParticles (GPUParticles2D)
├── InteractionArea (Area2D)
│ └── CollisionShape2D
└── ProgressBar (hidden by default)
Farm Plot Script
# farm_plot.gd
extends Node2D
signal planted(seed_id: String)
signal watered()
signal harvested(item_id: String, quantity: int)
@export var plot_index: int = 0
@onready var soil_sprite: Sprite2D = $Sprite2D
@onready var plant_sprite: Sprite2D = $PlantSprite
@onready var water_indicator: Sprite2D = $WaterIndicator
@onready var growth_particles: GPUParticles2D = $GrowthParticles
# Plot state
var seed_id: String = ""
var growth_stage: int = 0
var max_growth_stage: int = 4
var planted_timestamp: int = 0
var last_water_timestamp: int = 0
var is_watered: bool = false
# Textures
const SOIL_TEXTURES = {
"dry": preload("res://assets/farming/soil_dry.png"),
"wet": preload("res://assets/farming/soil_wet.png"),
"tilled": preload("res://assets/farming/soil_tilled.png")
}
func _ready() -> void:
_sync_from_server()
_update_visuals()
func _process(delta: float) -> void:
if seed_id.is_empty():
return
_check_growth()
_check_water_decay()
Planten
Seed Selection
func show_seed_selection() -> void:
var inventory = get_node("/root/InventoryManager")
var seeds = inventory.get_items_by_category("seed")
if seeds.is_empty():
NotificationManager.toast("Je hebt geen zaden!", "warning")
return
var seed_menu = preload("res://scenes/ui/seed_select_menu.tscn").instantiate()
seed_menu.populate(seeds)
seed_menu.seed_selected.connect(_on_seed_selected)
get_tree().root.add_child(seed_menu)
func _on_seed_selected(selected_seed_id: String) -> void:
plant_seed(selected_seed_id)
Plant Logic
func plant_seed(new_seed_id: String) -> void:
if not seed_id.is_empty():
NotificationManager.toast("Er groeit hier al iets!", "warning")
return
var inventory = get_node("/root/InventoryManager")
if not inventory.has_item(new_seed_id):
return
# Remove seed from inventory
inventory.remove_item(new_seed_id, 1)
# Set plot state
seed_id = new_seed_id
growth_stage = 0
planted_timestamp = Time.get_unix_time_from_system()
is_watered = false
# Sync to server
_sync_to_server()
# Update visuals
_update_visuals()
# Effects
_play_plant_effect()
planted.emit(seed_id)
NotificationManager.toast("Zaad geplant!", "success")
func _play_plant_effect() -> void:
AudioManager.play_sound("plant_seed")
growth_particles.emitting = true
Groei Systeem
Growth Stages
const GROWTH_STAGE_SPRITES = {
0: "seedling",
1: "sprout",
2: "growing",
3: "mature",
4: "harvestable"
}
func _check_growth() -> void:
if growth_stage >= max_growth_stage:
return
var crop_data = CropDatabase.get_crop(seed_id)
var growth_time = crop_data.growth_time_per_stage
# Check if enough time passed
var current_time = Time.get_unix_time_from_system()
var time_since_plant = current_time - planted_timestamp
var expected_stage = int(time_since_plant / growth_time)
# Apply modifiers
expected_stage = _apply_growth_modifiers(expected_stage)
# Cap at max
expected_stage = min(expected_stage, max_growth_stage)
# Update if changed
if expected_stage > growth_stage:
_advance_growth(expected_stage)
func _advance_growth(new_stage: int) -> void:
var old_stage = growth_stage
growth_stage = new_stage
_update_visuals()
_sync_to_server()
# Growth effect
if new_stage > old_stage:
growth_particles.emitting = true
AudioManager.play_sound("plant_grow")
Growth Modifiers
func _apply_growth_modifiers(base_stage: int) -> int:
var modified = float(base_stage)
# Water bonus (+50% growth when watered)
if is_watered:
modified *= 1.5
# Fertilizer bonus
var fertilizer = _get_fertilizer_bonus()
modified *= (1.0 + fertilizer)
# Season bonus
var crop_data = CropDatabase.get_crop(seed_id)
if _is_preferred_season(crop_data):
modified *= 1.25
return int(modified)
func _get_fertilizer_bonus() -> float:
# Check if fertilizer applied
var plot_data = FarmManager.get_plot_data(plot_index)
if plot_data.has("fertilizer"):
match plot_data.fertilizer:
"basic": return 0.25
"premium": return 0.5
"organic": return 0.35
return 0.0
func _is_preferred_season(crop_data: Dictionary) -> bool:
var current_season = GameTime.get_current_season()
return current_season in crop_data.preferred_seasons
Water Systeem
Watering
func water_plot() -> void:
if seed_id.is_empty():
NotificationManager.toast("Er is hier niets om water te geven", "info")
return
if is_watered:
NotificationManager.toast("Dit is al bewaterd", "info")
return
# Check for watering can
var inventory = get_node("/root/InventoryManager")
if not inventory.has_equipped("watering_can"):
NotificationManager.toast("Je hebt een gieter nodig!", "warning")
return
is_watered = true
last_water_timestamp = Time.get_unix_time_from_system()
_update_visuals()
_sync_to_server()
_play_water_effect()
watered.emit()
func _play_water_effect() -> void:
AudioManager.play_sound("water_splash")
# Water droplet particles
var water_particles = preload("res://scenes/effects/water_particles.tscn").instantiate()
water_particles.global_position = global_position
get_parent().add_child(water_particles)
Water Decay
const WATER_DECAY_TIME: int = 3600 # 1 hour in seconds
func _check_water_decay() -> void:
if not is_watered:
return
var current_time = Time.get_unix_time_from_system()
var time_since_water = current_time - last_water_timestamp
if time_since_water >= WATER_DECAY_TIME:
is_watered = false
_update_visuals()
_sync_to_server()
Oogsten
Harvest Check
func is_harvestable() -> bool:
return growth_stage >= max_growth_stage
func harvest() -> void:
if not is_harvestable():
NotificationManager.toast("Dit is nog niet klaar om te oogsten", "info")
return
var crop_data = CropDatabase.get_crop(seed_id)
var harvest_item = crop_data.harvest_item
var base_quantity = crop_data.harvest_quantity
# Apply harvest modifiers
var quantity = _calculate_harvest_quantity(base_quantity)
# Add to inventory
var inventory = get_node("/root/InventoryManager")
var added = inventory.add_item(harvest_item, quantity)
if added > 0:
# Clear plot
var harvested_seed = seed_id
_clear_plot()
# Effects
_play_harvest_effect()
harvested.emit(harvest_item, added)
# XP reward
PlayerStats.add_farming_xp(crop_data.xp_reward)
NotificationManager.toast(
"%dx %s geoogst!" % [added, crop_data.display_name],
"success"
)
else:
NotificationManager.toast("Inventory is vol!", "error")
func _clear_plot() -> void:
seed_id = ""
growth_stage = 0
planted_timestamp = 0
is_watered = false
_update_visuals()
_sync_to_server()
Harvest Quality
func _calculate_harvest_quantity(base: int) -> int:
var quantity = base
# Luck bonus
var luck = PlayerStats.get_luck()
if randf() < luck * 0.1:
quantity += 1 # Bonus harvest
# Skill bonus
var farming_level = PlayerStats.get_farming_level()
if farming_level >= 5:
quantity += 1
if farming_level >= 10:
quantity += 1
# Premium seed bonus
if seed_id.ends_with("_premium"):
quantity = int(quantity * 1.5)
return quantity
Visual Updates
func _update_visuals() -> void:
# Soil texture
if is_watered:
soil_sprite.texture = SOIL_TEXTURES.wet
elif seed_id.is_empty():
soil_sprite.texture = SOIL_TEXTURES.tilled
else:
soil_sprite.texture = SOIL_TEXTURES.dry
# Plant sprite
if seed_id.is_empty():
plant_sprite.visible = false
else:
plant_sprite.visible = true
var crop_data = CropDatabase.get_crop(seed_id)
var stage_name = GROWTH_STAGE_SPRITES[growth_stage]
plant_sprite.texture = load(crop_data.sprites[stage_name])
# Water indicator
water_indicator.visible = is_watered
func _play_harvest_effect() -> void:
AudioManager.play_sound("harvest")
# Harvest particles
var particles = preload("res://scenes/effects/harvest_particles.tscn").instantiate()
particles.global_position = global_position
get_parent().add_child(particles)
# Scale pop animation
var tween = create_tween()
tween.tween_property(plant_sprite, "scale", Vector2(1.3, 1.3), 0.1)
tween.tween_property(plant_sprite, "scale", Vector2.ZERO, 0.2)
Server Sync
func _sync_to_server() -> void:
SpacetimeDB.call_reducer("update_farm_plot", [
plot_index,
seed_id,
growth_stage,
planted_timestamp,
is_watered
])
func _sync_from_server() -> void:
var plot_data = FarmManager.get_plot_data(plot_index)
if plot_data.is_empty():
return
seed_id = plot_data.get("seed_id", "")
growth_stage = plot_data.get("growth_stage", 0)
planted_timestamp = plot_data.get("planted_timestamp", 0)
is_watered = plot_data.get("is_watered", false)
Crop Database
# crop_database.gd (autoload)
extends Node
const CROPS = {
"tomato_seed": {
"display_name": "Tomaat",
"growth_time_per_stage": 300, # 5 minutes
"harvest_item": "tomato",
"harvest_quantity": 3,
"xp_reward": 10,
"preferred_seasons": ["summer"],
"sprites": {
"seedling": "res://assets/crops/tomato/seedling.png",
"sprout": "res://assets/crops/tomato/sprout.png",
"growing": "res://assets/crops/tomato/growing.png",
"mature": "res://assets/crops/tomato/mature.png",
"harvestable": "res://assets/crops/tomato/harvestable.png"
}
},
"carrot_seed": {
"display_name": "Wortel",
"growth_time_per_stage": 240,
"harvest_item": "carrot",
"harvest_quantity": 2,
"xp_reward": 8,
"preferred_seasons": ["spring", "fall"],
"sprites": { ... }
}
}
func get_crop(seed_id: String) -> Dictionary:
return CROPS.get(seed_id, {})