GridTalk.de
Auto Plant Skript - Druckversion

+- GridTalk.de (https://www.gridtalk.de)
+-- Forum: Werkstatt (https://www.gridtalk.de/forumdisplay.php?fid=4)
+--- Forum: Scripting (https://www.gridtalk.de/forumdisplay.php?fid=23)
+--- Thema: Auto Plant Skript (/showthread.php?tid=5455)

Seiten: 1 2


RE: Auto Plant Skript - Manfred Aabye - 24.07.2026

Bitte bedenkt das sich das Skript noch in der Entwicklung befindet.
Das ganze läuft aber robust mit Debug ausgaben.
Die Objekte und Texturen müssten noch einmal generalüberholt werden.


RE: Auto Plant Skript - Ezry Aldrin - 24.07.2026

Huhu

Ich weiss nicht was ich verbocke, Warscheinlich ein einfacher denk oder HandlungsfehlerWink Sorry vielleicht bin ich grad a bisserl BresigWink.

Habe wie du sagtest, die Materrialien ins Inventar kopiert an angegebener Stelle, Texturen ebenso, dann habe ich den Autoplant Würfel gerezzt, dieser leuchtend GelbeWink er sagt auch Bereit - Klicke für Menu. Nur reagiert der Wuerfel nicht, auch kein MenuWink

Habe dann mal die Materialien und Texturen mit in den Würfel geworfen und resetet, dennoch kein Menu, nur die Meldung
Bereit - Klicke for Menu , Zumindest schonmal keine Fehlermeldung, aber irgendwas mach ich sicher verkehrt.

Des kann doch nich so schwer sein.... Was hast du denn anders gemacht @Mareta, sagst ja funktioniert

lg
Ezry

Nachtrag.: Ich habe einen Fehler gefunden, nachdem ich diese Zeile auskomentiert habe sagt er nichts mehr und zumindest startet erstmal das Script sauber das ich zum Menu komme

132 //osSetTerrainTextures(textures, types);

Dieses Zeile sagt er immer wieder den Error an, denn ich dir vor 2 Tagen schrieb, nachdem ich gesehen habe, der steht ja garnicht in rot wie alle anderen OSset... befehle wurde mir klar da stimmt was nicht, auskomentiert, Start möglich, die nachfolgenden osSetTextures Befehle sind alle i.o. Smile.Jetzt erstnmal Pause


RE: Auto Plant Skript - Manfred Aabye - 25.07.2026

Benutzt du auch einen aktuellen Viewer wie den FS 7.2.5?
Welche OpenSim Version hast du und hast du die Landrechte?


RE: Auto Plant Skript - Manfred Aabye - 26.07.2026

Auto Plant Skript 2.36
Neue Version mit Berechtigungsprüfung und besserer Terrainbearbeitung.
Das neue Paket steht wieder auf Wright Plaza.
Dies macht jetzt einen interessanten tiefen Wald, der nicht so langweilig wirkt.
Die Geschwindigkeit des Pflanzens habe ich jetzt auf 1 Sekunde also doppelt so schnell gestellt.

AutoPlant236.lsl
PHP-Code:
// ============================================================
// AutoPlant236.lsl
// OPENSIM WALDPFLANZER SKRIPT MIT DIALOG-MENÜ
// ============================================================

// ===== GLOBALE VARIABLEN =====
integer gIntX;
integer gIntY;  
integer gIntZ;

integer gIntStartX;
integer gIntStartY;
integer gIntEndX;
integer gIntEndY;

float gFltPlantProbability;
float gFltRandMax;

float FLATTEN_HEIGHT 21.0// Standard-Höhe zum Einebnen
float ROUGHNESS_AMOUNT 0.15;   // 0.0 = keine Änderung, 1.0 = maximale Änderung
float MAX_HEIGHT_CHANGE 2.0;  // Maximale Höhenänderung in Metern
float NOISE_SCALE 10.0;       // Skalierung für Perlin-Noise
float NOISE_AMPLITUDE 3.0;    // Amplitude für Perlin-Noise
float BASE_HEIGHT 20.0;       // Basis-Höhe für die Region

// ===== START/STOP-FUNKTION =====
integer gIntRunning FALSE;
key gKeyToucher;
integer gIntCurrentX;
integer gIntCurrentY;

float Pflanszeit 0.5// Geschwindigkeit der Pflanzenbildung

// ===== PFLANZPARAMETER =====
integer PFLANZABSTAND 6;

// ===== KOMBINIERTE LISTE FÜR ALLE OBJEKTE =====
// Format: [Name, Höhe, Priorität, X-Rotation, Y-Rotation, Z-Rotation, Z-Offset]
// Prioritäten basierend auf deutschen Waldanteilen:
// Kiefer: 22%, Fichte: 21%, Rotbuche: 17%, Eiche: 12%, Birke: 4.7%, Ahorn: 3.0%, Lärche: 2.9%, Esche: 1.8%
list ALLE_OBJEKTE = [
    
// Bäume mit deutschen Waldanteilen
    
"European-Beech-tree-A"10.0170.00.00.00.0,
    
"European-Beech-tree-B"10.0170.00.00.00.0,
    
"European-Oak-tree"12.0120.00.00.00.0,
    
"European-Larch-tree"15.0390.00.00.00.0,
    
"European-Pine-tree"12.0220.00.00.00.0,
    
"European-Spruce-tree"15.0210.00.00.00.0,
    
"European-Birch-tree"10.0590.00.00.00.0,
    
"European-Maple-tree"12.030.00.00.00.0,
    
"European-Ash-tree"13.020.00.00.00.0,
    
    
// Büsche (Priorität 8)
    
"Buesche-1"3.0890.00.00.0, -2.00,
    
"Buesche-2"3.0890.00.00.0, -2.00,
    
"Buesche-3"3.0890.00.00.0, -2.00,
    
"Buesche-4"3.0890.00.00.0, -2.00,
    
    
// Farne (Priorität 6)
    
"Fern-1"1.2690.00.00.0, -0.70,
    
"Fern-2"1.2690.00.00.0, -0.70,
    
"Fern-3"1.2690.00.00.0, -0.70,
    
"Fern-4"1.2690.00.00.0, -0.70,
    
    
// Todholz (Priorität 4)
    
"Todholz-1"1.540.00.00.0, -1.30,
    
"Todholz-2"1.540.00.00.0, -1.30,
    
"Todholz-3"1.540.00.00.0, -1.30,
    
"Todholz-4"1.540.00.00.0, -1.30,
    
    
// Gräser (Priorität 5)
    
"Grass-1"0.550.00.00.0, -0.10,
    
"Grass-2"0.550.00.00.0, -0.10,
    
"Grass-3"0.55180.00.00.00.70,
    
"Grass-4"0.550.00.00.0, -0.10,
    
    
// Steine (Priorität 3)
    
"Stone-1"0.830.00.00.0, -0.40,
    
"Stone-2"0.830.00.00.0, -3.00,
    
"Stone-3"0.830.00.00.0, -0.40,
    
"Stone-4"0.830.00.00.0, -0.40
];

// ===== TERRAIN-TEXTUREN =====
float TextureLow 10.0;
float TextureHigh 30.0;

key Terrain_Dirt "1e78c37b-90a1-40d3-a913-31c965309679";
key Terrain_Grass "a9b4d0d5-5679-4fd5-b9e3-ac4367ea9b95";
key Terrain_Mountain "795204d3-bd28-467c-b526-08b95090841e";
key Terrain_Rock "4b012b05-a357-46af-8e6e-e8a9aa501a8c";

// ===== DIALOG-MENÜ VARIABLEN =====
integer MENU_CHANNEL;
integer listenHandle;
string MENU_TEXT "\n? Wähle eine Aktion:";
list 
MENU_BUTTONS = ["? Aufrauhen""?️ Einebnen","❌ Schließen",  "? Pflanzen""? Terrain"];

// ===== HILFSFUNKTIONEN =====
float getObjectHeight(string objectName)
{
    
integer index llListFindList(ALLE_OBJEKTE, [objectName]);
    if (
index != -1)
        return 
llList2Float(ALLE_OBJEKTEindex 1);
    return 
4.0;
}

float getObjectRotationX(string objectName)
{
    
integer index llListFindList(ALLE_OBJEKTE, [objectName]);
    if (
index != -1)
        return 
llList2Float(ALLE_OBJEKTEindex 3);
    return 
0.0;
}

float getObjectRotationY(string objectName)
{
    
integer index llListFindList(ALLE_OBJEKTE, [objectName]);
    if (
index != -1)
        return 
llList2Float(ALLE_OBJEKTEindex 4);
    return 
0.0;
}

float getObjectRotationZ(string objectName)
{
    
integer index llListFindList(ALLE_OBJEKTE, [objectName]);
    if (
index != -1)
        return 
llList2Float(ALLE_OBJEKTEindex 5);
    return 
0.0;
}

float getObjectZOffset(string objectName)
{
    
integer index llListFindList(ALLE_OBJEKTE, [objectName]);
    if (
index != -1)
        return 
llList2Float(ALLE_OBJEKTEindex 6);
    return 
0.0;
}

rotation getRotation(float gradXfloat gradYfloat gradZ)
{
    return 
llEuler2Rot(<gradX DEG_TO_RADgradY DEG_TO_RADgradZ DEG_TO_RAD>);
}

// ===== BERECHTIGUNGSPRÜFUNG =====
integer checkLandPermissions(key avatar)
{
    
// 1. Prüfen, ob der Nutzer der Besitzer des Landes ist
    
key parcelID osGetParcelID();
    list 
parcelDetails osGetParcelDetails(parcelID, [PARCEL_DETAILS_OWNER]);
    
key parcelOwner llList2Key(parcelDetails0);
    
    if (
parcelOwner == avatar)
    {
        return 
TRUE;
    }
    
    
// 2. Prüfen, ob der Nutzer in der Gruppenliste des Grundstücks ist
    
parcelDetails osGetParcelDetails(parcelID, [PARCEL_DETAILS_GROUP]);
    
key parcelGroup llList2Key(parcelDetails0);
    
    if (
parcelGroup != NULL_KEY)
    {
        if (
llSameGroup(avatar))
        {
            return 
TRUE;
        }
    }
    
    
// 3. Prüfen, ob der Nutzer allgemeine Baurechte hat
    
integer parcelFlags llGetParcelFlags(llGetPos());
    if (
parcelFlags PARCEL_FLAG_ALLOW_CREATE_OBJECTS)
    {
        return 
TRUE;
    }
    
    return 
FALSE;
}

// ===== TERRAIN-FUNKTIONEN =====
setTerrain()
{
    if (!
checkLandPermissions(llGetOwner()))
    {
        
llOwnerSay("❌ Keine Berechtigung für Terrain-Änderungen!");
        return;
    }
    
    list 
textures = [Terrain_DirtTerrain_GrassTerrain_MountainTerrain_Rock];
    
integer types 2;
    
osSetTerrainTextures(texturestypes);
    
    
osSetTerrainTexture(0Terrain_Dirt);
    
osSetTerrainTextureHeight(0TextureLowTextureHigh);
    
osSetTerrainTexture(1Terrain_Grass);
    
osSetTerrainTextureHeight(1TextureLowTextureHigh);
    
osSetTerrainTexture(2Terrain_Mountain);
    
osSetTerrainTextureHeight(2TextureLowTextureHigh);
    
osSetTerrainTexture(3Terrain_Rock);
    
osSetTerrainTextureHeight(3TextureLowTextureHigh);
    
    
llModifyLand(LAND_NOISE0);
    
osTerrainFlush();
    
    
llOwnerSay("✅ Terrain-Texturen wurden gesetzt und aufgeraut!");
}

// ===== FUNKTION ZUM EINEBNEN DER REGION =====
flattenRegion(float targetHeight)
{
    
// Prüfen ob der Aufrufer Berechtigungen hat
    
if (!checkLandPermissions(llGetOwner()))
    {
        
llOwnerSay("❌ Keine Berechtigung zum Einebnen der Region!");
        return;
    }
    
    
llOwnerSay("?️ Region wird auf " + (string)targetHeight " Meter eingeebnet...");
    
    
// Region-Größe ermitteln
    
vector regionSize osGetRegionSize();
    
integer regionX = (integer)regionSize.x;
    
integer regionY = (integer)regionSize.y;
    
    
// In Blöcken arbeiten um Heap-Overflow zu vermeiden
    
integer blockSize 10;
    
integer x;
    
integer y;
    
    for (
0regionX+= blockSize)
    {
        for (
0regionY+= blockSize)
        {
            
integer endX blockSize;
            
integer endY blockSize;
            if (
endX regionXendX regionX;
            if (
endY regionYendY regionY;
            
            
integer ix;
            
integer iy;
            for (
ix xix endXix++)
            {
                for (
iy yiy endYiy++)
                {
                    
osSetTerrainHeight(ixiytargetHeight);
                }
            }
            
llSleep(0.01);
        }
    }
    
    
// Änderungen ans Terrain übergeben
    
osTerrainFlush();
    
    
llOwnerSay("✅ Region wurde auf " + (string)targetHeight " Meter eingeebnet!");
}

// ===== FUNKTION ZUM AUFRAUHEN DER REGION (OPTIMIERT) =====
roughenRegion(float roughnessAmountfloat maxHeightChange)
{
    if (!
checkLandPermissions(llGetOwner()))
    {
        
llOwnerSay("❌ Keine Berechtigung zum Aufrauhen der Region!");
        return;
    }
    
    
llOwnerSay("?️ Region wird aufgeraut (Stärke: " + (string)(roughnessAmount 100) + "%, max. Höhenänderung: " + (string)maxHeightChange "m)...");
    
    
// Region-Größe ermitteln
    
vector regionSize osGetRegionSize();
    
integer regionX = (integer)regionSize.x;
    
integer regionY = (integer)regionSize.y;
    
    
// In Blöcken arbeiten um Heap-Overflow zu vermeiden
    
integer blockSize 10;
    
integer x;
    
integer y;
    
    for (
0regionX+= blockSize)
    {
        for (
0regionY+= blockSize)
        {
            
// Nur innerhalb der Region bleiben
            
integer endX blockSize;
            
integer endY blockSize;
            if (
endX regionXendX regionX;
            if (
endY regionYendY regionY;
            
            
// Block verarbeiten
            
integer ix;
            
integer iy;
            for (
ix xix endXix++)
            {
                for (
iy yiy endYiy++)
                {
                    
float currentHeight osGetTerrainHeight(ixiy);
                    
                    
// Zufällige Höhenänderung
                    
float heightChange = (llFrand(1.0) * 2.0 1.0) * maxHeightChange roughnessAmount;
                    
float newHeight currentHeight heightChange;
                    
                    
// Begrenzen auf sinnvolle Werte
                    
if (newHeight 0.0newHeight 0.0;
                    if (
newHeight 100.0newHeight 100.0;
                    
                    
osSetTerrainHeight(ixiynewHeight);
                }
            }
            
            
// Nach jedem Block kurz pausieren für Heap-Management
            
llSleep(0.01);
        }
    }
    
    
// Änderungen ans Terrain übergeben
    
osTerrainFlush();
    
    
llOwnerSay("✅ Region wurde erfolgreich aufgeraut!");
}

// ===== PFLANZEN-FUNKTIONEN =====
plantOneTree()
{
    if (!
gIntRunning) return;
    
    if (
gIntCurrentX >= gIntEndX || gIntCurrentY >= gIntEndY)
    {
        
gIntRunning FALSE;
        
llOwnerSay("✅ Pflanzvorgang abgeschlossen!");
        
llSetText("Fertig - Klicke für neues Menü", <0,1,0>, 1.0);
        
llSetTimerEvent(0.0);
        return;
    }
    
    
gIntX gIntCurrentX;
    
gIntY gIntCurrentY;
    
    
llSetRegionPos(<gIntXgIntYgIntZ>);
    
llSetText("X: " + (string)gIntX " Y: " + (string)gIntY, <1,1,1>, 1.0);
    
    
// Zufällig entscheiden, ob überhaupt etwas gepflanzt wird
    
float fltDoIRez llFrand(1.0);
    if (
fltDoIRez >= gFltPlantProbability
    {
        
gIntCurrentY += PFLANZABSTAND;
        if (
gIntCurrentY >= gIntEndY)
        {
            
gIntCurrentY gIntStartY + (PFLANZABSTAND 2);
            
gIntCurrentX += PFLANZABSTAND;
        }
        return;
    }
    
    
// ===== GEWICHTETE AUSWAHL =====
    
list verfuegbareObjekte = [];
    
integer listLength llGetListLength(ALLE_OBJEKTE) / 7;
    
integer i;
    
    for (
0listLengthi++)
    {
        
string objName llList2String(ALLE_OBJEKTE7);
        if (
llGetInventoryType(objName) == INVENTORY_OBJECT)
        {
            
float objHeight llList2Float(ALLE_OBJEKTE, (7) + 1);
            
integer objPriority llList2Integer(ALLE_OBJEKTE, (7) + 2);
            
float objRotationX llList2Float(ALLE_OBJEKTE, (7) + 3);
            
float objRotationY llList2Float(ALLE_OBJEKTE, (7) + 4);
            
float objRotationZ llList2Float(ALLE_OBJEKTE, (7) + 5);
            
float objZOffset llList2Float(ALLE_OBJEKTE, (7) + 6);
            
verfuegbareObjekte += [objNameobjHeightobjPriorityobjRotationXobjRotationYobjRotationZobjZOffset];
        }
    }
    
    if (
llGetListLength(verfuegbareObjekte) == 0)
    {
        
llOwnerSay("⚠️ Keine Objekte im Inventar gefunden!");
        
gIntRunning FALSE;
        
llSetTimerEvent(0.0);
        return;
    }
    
    
// Gesamtgewicht berechnen
    
integer availableCount llGetListLength(verfuegbareObjekte) / 7;
    
integer totalWeight 0;
    for (
0availableCounti++)
    {
        
totalWeight += llList2Integer(verfuegbareObjekte, (7) + 2);
    }
    
    
// Zufällige Auswahl basierend auf Gewicht
    
integer randomWeight = (integer)llFrand(totalWeight);
    
integer selectedIndex 0;
    
integer cumulativeWeight 0;
    
    for (
0availableCounti++)
    {
        
integer priority llList2Integer(verfuegbareObjekte, (7) + 2);
        
cumulativeWeight += priority;
        if (
randomWeight cumulativeWeight)
        {
            
selectedIndex i;
            
availableCount;
        }
    }
    
    
// Objekt auswählen
    
string gStrObjectName llList2String(verfuegbareObjekteselectedIndex 7);
    
float objectHeight llList2Float(verfuegbareObjekte, (selectedIndex 7) + 1);
    
float rotationX llList2Float(verfuegbareObjekte, (selectedIndex 7) + 3);
    
float rotationY llList2Float(verfuegbareObjekte, (selectedIndex 7) + 4);
    
float rotationZ llList2Float(verfuegbareObjekte, (selectedIndex 7) + 5);
    
float zOffset llList2Float(verfuegbareObjekte, (selectedIndex 7) + 6);
    
    
// Position setzen
    
vector vecNowPos llGetPos();
    
float fltLandHeight osGetTerrainHeight((integer)vecNowPos.x, (integer)vecNowPos.y);
    
    
integer intNewX = (integer)vecNowPos.x;
    
integer intNewY = (integer)vecNowPos.y;
    
integer intNewZ = (integer)fltLandHeight;
    
    
vecNowPos = <intNewXintNewYintNewZ>;
    
llSetRegionPos(vecNowPos);
    
    
// ===== ROTATION =====
    // Basis-Rotation aus der Liste (X und Y fest, Z wird durch zufälligen Wert ersetzt)
    
rotation baseRotation getRotation(rotationXrotationY0.0);
    
    
// Zufällige Z-Rotation für natürlichen Wald (0-360 Grad)
    
float randomZRotation llFrand(360.0);
    
rotation randomRotation llEuler2Rot(<0.00.0randomZRotation DEG_TO_RAD>);
    
    
// Beide Rotationen kombinieren
    
rotation finalRotation baseRotation randomRotation;
    
    
// Objekt rezzen mit kombinierter Rotation
    
llRezObject(gStrObjectName
                
llGetPos() + <0.00.0objectHeight zOffset>,
                <
0.00.00.0>,
                
finalRotation,
                
0);
    
llModifyLand(LAND_NOISE0);
    
llSleep(0.1);
    
    
// Debug-Ausgabe (optional)
    // llOwnerSay("? " + gStrObjectName + " | X-Rot: " + (string)rotationX + "° | Y-Rot: " + (string)rotationY + "° | Z-Rot: " + (string)(randomZRotation) + "° | Z-Off: " + (string)zOffset);
    
    // Zur nächsten Position
    
gIntCurrentY += PFLANZABSTAND;
    if (
gIntCurrentY >= gIntEndY)
    {
        
gIntCurrentY gIntStartY + (PFLANZABSTAND 2);
        
gIntCurrentX += PFLANZABSTAND;
    }
}

startPlanting(key toucher)
{
    if (!
checkLandPermissions(toucher))
    {
        
llOwnerSay("❌ Du hast nicht die erforderlichen Landrechte zum Pflanzen!");
        
llOwnerSay("Benötigte Berechtigungen (mindestens eine):");
        
llOwnerSay("  • Grundstücksbesitzer");
        
llOwnerSay("  • Mitglied der Grundstücksgruppe");
        
llOwnerSay("  • Baurechte auf dem Grundstück");
        return;
    }
    
    
vector regionSize osGetRegionSize();
    
gIntStartX 0;
    
gIntStartY 0;
    
gIntEndX = (integer)regionSize.x;
    
gIntEndY = (integer)regionSize.y;
    
    
gFltPlantProbability 0.7;
    
gIntZ 0;
    
    
gIntRunning TRUE;
    
gIntCurrentX gIntStartX + (PFLANZABSTAND 2);
    
gIntCurrentY gIntStartY + (PFLANZABSTAND 2);
    
    
// Anzahl der verfügbaren Objekte zählen
    
integer availableCount 0;
    
integer listLength llGetListLength(ALLE_OBJEKTE) / 7;
    
integer i;
    for (
0listLengthi++)
    {
        
string objName llList2String(ALLE_OBJEKTE7);
        if (
llGetInventoryType(objName) == INVENTORY_OBJECT)
            
availableCount++;
    }
    
    
llOwnerSay("? Starte Pflanzvorgang mit " + (string)availableCount " verfügbaren Objekten...");
    
llOwnerSay("? Waldzusammensetzung (deutscher Wald):");
    
llOwnerSay("  • Kiefer: 22%, Fichte: 21%, Rotbuche: 17%");
    
llOwnerSay("  • Eiche: 12%, Birke: 4.7%, Ahorn: 3.0%");
    
llOwnerSay("  • Lärche: 2.9%, Esche: 1.8%");
    
llOwnerSay("? Berechtigung für " llKey2Name(toucher) + " verifiziert.");
    
llSetText("? Pflanze... Klicke zum Stoppen", <1,1,0>, 1.0);

    
    
llSetTimerEvent(Pflanszeit);
}

// ===== HAUPTTEIL DES SKRIPTS =====
default
{
    
state_entry()
    {
        
MENU_CHANNEL = -- (integer)("0x" llGetSubString((string)llGetKey(), -7, -1));
        
llOwnerSay("? Waldpflanzer bereit. Klicke für Menü.");
        
llSetText("Bereit - Klicke für Menü", <0,1,0>, 1.0);
        
llSetTimerEvent(0.0);
    }
    
    
touch_start(integer num_detected)
    {
        if (
gIntRunning)
        {
            
gIntRunning FALSE;
            
llSetTimerEvent(0.0);
            
llListenRemove(listenHandle);
            
llOwnerSay("⏹️ Pflanzvorgang wurde gestoppt!");
            
llSetText("Gestoppt - Klicke für neues Menü", <1,0,0>, 1.0);
            return;
        }
        
        
key toucher llDetectedKey(0);
        
gKeyToucher toucher;
        
        if (!
checkLandPermissions(toucher))
        {
            
string avatarName llKey2Name(toucher);
            
llOwnerSay("❌ " avatarName " hat nicht die erforderlichen Landrechte!");
            
llOwnerSay("Benötigte Berechtigungen (mindestens eine):");
            
llOwnerSay("  • Grundstücksbesitzer");
            
llOwnerSay("  • Mitglied der Grundstücksgruppe");
            
llOwnerSay("  • Baurechte auf dem Grundstück");
            return;
        }
        
        
llListenRemove(listenHandle);
        
listenHandle llListen(MENU_CHANNEL""toucher"");
        
llDialog(toucherMENU_TEXTMENU_BUTTONSMENU_CHANNEL);
        
llSetTimerEvent(60.0);
    }
    
    
listen(integer channelstring namekey idstring message)
    {
        
llSetTimerEvent(0.0);
        
llListenRemove(listenHandle);
        
        if (
message == "? Pflanzen")
        {
            if (!
checkLandPermissions(id))
            {
                
llOwnerSay("❌ Du hast nicht die erforderlichen Landrechte zum Pflanzen!");
                return;
            }
            
llOwnerSay("? Starte Pflanzvorgang...");
            
startPlanting(id);
        }
        else if (
message == "? Terrain")
        {
            if (!
checkLandPermissions(id))
            {
                
llOwnerSay("❌ Du hast nicht die erforderlichen Landrechte für Terrain-Änderungen!");
                return;
            }
            
setTerrain();
            
llDialog(id"✅ Terrain-Texturen wurden aktualisiert.", ["OK"], MENU_CHANNEL);
        }
        else if (
message == "?️ Einebnen")
        {
            if (!
checkLandPermissions(id))
            {
                
llOwnerSay("❌ Du hast nicht die erforderlichen Landrechte zum Einebnen!");
                return;
            }
            
flattenRegion(FLATTEN_HEIGHT);
            
llDialog(id"✅ Region wurde auf " + (string)FLATTEN_HEIGHT " Meter eingeebnet.", ["OK"], MENU_CHANNEL);
        }
        else if (
message == "? Aufrauhen")
        {
            if (!
checkLandPermissions(id))
            {
                
llOwnerSay("❌ Du hast nicht die erforderlichen Landrechte zum Aufrauhen!");
                return;
            }
            
            
// Einfache Variante
            
roughenRegion(ROUGHNESS_AMOUNTMAX_HEIGHT_CHANGE);
            
            
llDialog(id"✅ Region wurde aufgeraut.", ["OK"], MENU_CHANNEL);
        }
        else if (
message == "❌ Schließen")
        {
            
llOwnerSay("Menü geschlossen.");
        }
        else if (
message == "OK")
        {
            
llOwnerSay("✅ OK.");
        }
    }
    
    
timer()
    {
        if (
gIntRunning)
        {
            
plantOneTree();
        }
        else
        {
            
llSetTimerEvent(0.0);
        }
    }