Ga naar hoofdinhoud

Vrienden Systeem

Vrienden toevoegen, beheren, en sociale features.

Overzicht

┌─────────────────────────────────────────────────────────────────┐
│ Vrienden Features │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Vriendschap Interacties │
│ ──────────── ─────────── │
│ • Verzoeken sturen • Bezoeken (teleport) │
│ • Accepteren/weigeren • Whisper chat │
│ • Online status • Giften sturen │
│ • Notificaties • Samen activiteiten │
│ │
│ Beperkingen: │
│ • Max 50 vrienden │
│ • 10 verzoeken per dag │
│ • Cooldown na weigering │
│ │
└─────────────────────────────────────────────────────────────────┘

Friend Manager

# friend_manager.gd
extends Node

signal friend_added(friend_id: int)
signal friend_removed(friend_id: int)
signal friend_request_received(from_id: int, from_name: String)
signal friend_online(friend_id: int)
signal friend_offline(friend_id: int)

const MAX_FRIENDS = 50
const MAX_DAILY_REQUESTS = 10

var friends: Dictionary = {} # friend_id -> FriendData
var pending_requests: Array[int] = [] # incoming requests
var sent_requests: Array[int] = []

func _ready() -> void:
SpacetimeDB.Friend.on_insert.connect(_on_friend_added)
SpacetimeDB.Friend.on_delete.connect(_on_friend_removed)
SpacetimeDB.FriendRequest.on_insert.connect(_on_request_received)
SpacetimeDB.OnlinePlayer.on_insert.connect(_check_friend_online)
SpacetimeDB.OnlinePlayer.on_delete.connect(_check_friend_offline)

_load_friends_from_server()

Friend Requests

Sending Requests

func send_friend_request(target_player_id: int) -> bool:
# Validate
if target_player_id == GameState.player_id:
NotificationManager.toast("Je kunt jezelf niet toevoegen", "warning")
return false

if is_friend(target_player_id):
NotificationManager.toast("Deze speler is al je vriend", "info")
return false

if target_player_id in sent_requests:
NotificationManager.toast("Verzoek al verstuurd", "info")
return false

if friends.size() >= MAX_FRIENDS:
NotificationManager.toast("Je hebt het maximum aantal vrienden bereikt", "warning")
return false

# Daily limit check
if not _check_daily_request_limit():
NotificationManager.toast("Je hebt vandaag te veel verzoeken gestuurd", "warning")
return false

# Send to server
SpacetimeDB.call_reducer("send_friend_request", [target_player_id])

sent_requests.append(target_player_id)

NotificationManager.toast("Vriendschapsverzoek verstuurd!", "success")
return true

func _check_daily_request_limit() -> bool:
var today = Time.get_date_string_from_system()
var requests_today = SaveData.get_value("requests_" + today, 0)
return requests_today < MAX_DAILY_REQUESTS

Receiving Requests

func _on_request_received(row: FriendRequest) -> void:
if row.target_id != GameState.player_id:
return

pending_requests.append(row.sender_id)

# Get sender name
var sender_name = _get_player_name(row.sender_id)

# Notification
NotificationManager.toast(
"Vriendschapsverzoek van %s" % sender_name,
"info"
)

friend_request_received.emit(row.sender_id, sender_name)

func accept_request(sender_id: int) -> void:
if sender_id not in pending_requests:
return

SpacetimeDB.call_reducer("accept_friend_request", [sender_id])

pending_requests.erase(sender_id)

func decline_request(sender_id: int) -> void:
if sender_id not in pending_requests:
return

SpacetimeDB.call_reducer("decline_friend_request", [sender_id])

pending_requests.erase(sender_id)

Friend Data

class_name FriendData extends RefCounted

var player_id: int
var display_name: String
var is_online: bool = false
var current_scene: String = ""
var last_online: int = 0
var friendship_level: int = 1
var friendship_xp: int = 0

func update_from_row(row: Friend) -> void:
player_id = row.friend_id
display_name = row.friend_name
friendship_level = row.friendship_level
friendship_xp = row.friendship_xp
last_online = row.last_online

Online Status

func _check_friend_online(row: OnlinePlayer) -> void:
if row.player_id in friends:
friends[row.player_id].is_online = true
friends[row.player_id].current_scene = row.current_scene

friend_online.emit(row.player_id)

func _check_friend_offline(row: OnlinePlayer) -> void:
if row.player_id in friends:
friends[row.player_id].is_online = false
friends[row.player_id].last_online = Time.get_unix_time_from_system()

friend_offline.emit(row.player_id)

func get_online_friends() -> Array:
var online = []
for friend_id in friends:
if friends[friend_id].is_online:
online.append(friends[friend_id])
return online

Friends List UI

Layout

┌─────────────────────────────────────────────────────────────────┐
│ VRIENDEN [X] │
├─────────────────────────────────────────────────────────────────┤
│ [Online (3)] [Alle (12)] [Verzoeken (2)] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ● Milena @ Treehouse │
│ ────────────────────────────────────────────────────────── │
│ [💬 Chat] [🏠 Bezoek] [🎁 Gift] │
│ │
│ ● Buurman @ World Map │
│ ────────────────────────────────────────────────────────── │
│ [💬 Chat] [🏠 Bezoek] [🎁 Gift] │
│ │
│ ○ Oma Wilma (offline) laatst: 2u geleden │
│ ────────────────────────────────────────────────────────── │
│ │
└─────────────────────────────────────────────────────────────────┘

Script

# friends_panel.gd
extends Control

@onready var friend_list: VBoxContainer = $FriendList
@onready var online_tab: Button = $Tabs/OnlineTab
@onready var all_tab: Button = $Tabs/AllTab
@onready var requests_tab: Button = $Tabs/RequestsTab

const FRIEND_ENTRY = preload("res://scenes/ui/friend_entry.tscn")

var current_view: String = "online"

func _ready() -> void:
FriendManager.friend_added.connect(_refresh_list)
FriendManager.friend_removed.connect(_refresh_list)
FriendManager.friend_online.connect(_refresh_list)
FriendManager.friend_offline.connect(_refresh_list)

_refresh_list()

func _refresh_list(_arg = null) -> void:
for child in friend_list.get_children():
child.queue_free()

match current_view:
"online":
_show_online_friends()
"all":
_show_all_friends()
"requests":
_show_requests()

func _show_online_friends() -> void:
var online = FriendManager.get_online_friends()

for friend in online:
var entry = FRIEND_ENTRY.instantiate()
entry.setup(friend, true)
friend_list.add_child(entry)

func _show_all_friends() -> void:
var all_friends = FriendManager.friends.values()

# Sort: online first, then by name
all_friends.sort_custom(func(a, b):
if a.is_online != b.is_online:
return a.is_online
return a.display_name < b.display_name
)

for friend in all_friends:
var entry = FRIEND_ENTRY.instantiate()
entry.setup(friend, friend.is_online)
friend_list.add_child(entry)

Friend Entry Component

# friend_entry.gd
extends Control

signal visit_pressed(friend_id: int)
signal chat_pressed(friend_id: int)
signal gift_pressed(friend_id: int)

@onready var status_indicator: ColorRect = $StatusIndicator
@onready var name_label: Label = $NameLabel
@onready var location_label: Label = $LocationLabel
@onready var action_buttons: HBoxContainer = $ActionButtons

var friend_data: FriendData

func setup(data: FriendData, is_online: bool) -> void:
friend_data = data

name_label.text = data.display_name

if is_online:
status_indicator.color = Color.GREEN
location_label.text = "@ " + _format_scene_name(data.current_scene)
action_buttons.visible = true
else:
status_indicator.color = Color.GRAY
location_label.text = _format_last_online(data.last_online)
action_buttons.visible = false

func _format_scene_name(scene: String) -> String:
match scene:
"treehouse": return "Treehouse"
"world_map": return "World Map"
"garden": return "Tuin"
_: return scene.capitalize()

func _format_last_online(timestamp: int) -> String:
var diff = Time.get_unix_time_from_system() - timestamp

if diff < 3600:
return "laatst: %d min geleden" % (diff / 60)
elif diff < 86400:
return "laatst: %d uur geleden" % (diff / 3600)
else:
return "laatst: %d dagen geleden" % (diff / 86400)

func _on_visit_pressed() -> void:
visit_pressed.emit(friend_data.player_id)

func _on_chat_pressed() -> void:
chat_pressed.emit(friend_data.player_id)

func _on_gift_pressed() -> void:
gift_pressed.emit(friend_data.player_id)

Friend Interactions

Visit Friend

func visit_friend(friend_id: int) -> void:
var friend = friends.get(friend_id)
if not friend or not friend.is_online:
NotificationManager.toast("Vriend is niet online", "warning")
return

# Teleport to friend's scene
SceneManager.change_scene(friend.current_scene)

NotificationManager.toast("Op weg naar %s!" % friend.display_name, "info")

Send Gift

func send_gift(friend_id: int, item_id: String, quantity: int) -> bool:
var inventory = get_node("/root/InventoryManager")

if not inventory.has_item(item_id, quantity):
NotificationManager.toast("Je hebt dit item niet", "warning")
return false

# Remove from inventory
inventory.remove_item(item_id, quantity)

# Send via server
SpacetimeDB.call_reducer("send_gift", [
friend_id,
item_id,
quantity
])

# Friendship XP
_add_friendship_xp(friend_id, 10)

NotificationManager.toast("Gift verstuurd!", "success")
return true

func _on_gift_received(row: GiftMessage) -> void:
if row.target_id != GameState.player_id:
return

# Add to inventory
var inventory = get_node("/root/InventoryManager")
inventory.add_item(row.item_id, row.quantity)

var sender_name = _get_player_name(row.sender_id)
var item_name = ItemDatabase.get_item(row.item_id).display_name

NotificationManager.toast(
"%s heeft je %dx %s gestuurd!" % [sender_name, row.quantity, item_name],
"success"
)

Friendship Levels

const FRIENDSHIP_XP_PER_LEVEL = [0, 50, 150, 300, 500, 800, 1200, 1700, 2300, 3000]

func _add_friendship_xp(friend_id: int, amount: int) -> void:
if friend_id not in friends:
return

var friend = friends[friend_id]
friend.friendship_xp += amount

# Check level up
var next_level = friend.friendship_level + 1
if next_level <= 10:
if friend.friendship_xp >= FRIENDSHIP_XP_PER_LEVEL[next_level]:
friend.friendship_level = next_level
_on_friendship_level_up(friend_id, next_level)

# Sync to server
SpacetimeDB.call_reducer("update_friendship_xp", [friend_id, amount])

func _on_friendship_level_up(friend_id: int, new_level: int) -> void:
var friend = friends[friend_id]

NotificationManager.toast(
"Vriendschap met %s is nu level %d!" % [friend.display_name, new_level],
"success"
)

# Unlock rewards at certain levels
match new_level:
3:
NotificationManager.toast("Unlocked: Samen tuinieren!", "info")
5:
NotificationManager.toast("Unlocked: Dagelijkse gifts!", "info")
10:
NotificationManager.toast("Unlocked: Best Friend badge!", "info")

Request Panel

# friend_request_entry.gd
extends Control

@onready var name_label: Label = $NameLabel
@onready var accept_btn: Button = $AcceptButton
@onready var decline_btn: Button = $DeclineButton

var sender_id: int

func setup(request_sender_id: int, sender_name: String) -> void:
sender_id = request_sender_id
name_label.text = sender_name

func _on_accept_pressed() -> void:
FriendManager.accept_request(sender_id)
queue_free()

func _on_decline_pressed() -> void:
FriendManager.decline_request(sender_id)
queue_free()

Volgende