Ga naar hoofdinhoud

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, {})

Volgende