Beiträge von SBIKA

    Habe mal das ganze ueberflogen und die Fehler kommentiert, die mir direkt aufgefallen sind.

    if(dialogid == DIALOG_ATM)
    {
    new str[256]; // 1.
    switch(listitem)
    {
    new str[256]; // 2. - doppelt haelt besser? + kann keine neue Variable in switch erstellen
    case 0:
    {
    new str[256]; // 3. + kann keine neue Variable in switch erstellen
    format(str, 256, "Gib den Betrag ein, den du einzahlen möchtest!");
    ShowPlayerDialog(playerid,DIALOG_ATMEIN,DIALOG_STYLE_INPUT, "Einzahlen", str, "Einzahlen", "Abbrechen");
    }
    case 1:
    {
    format(str, 256, "Gib den Betrag ein, den du ausgezahlt haben möchtest!");
    ShowPlayerDialog(playerid, DIALOG_ATMAUS,DIALOG_STYLE_INPUT, "Auszahlung", str, "Auszahlen", "Abbrechen");
    }
    case 2:
    {
    format(str, 256, "Ihr momentaner Kontostand beträgt: %d $", GetPVarInt(playerid,"Bank"));
    ShowPlayerDialog(playerid,DIALOG_ATMSTAND,DIALOG_STYLE_MSGBOX, "Kontostand", str, "Zurück", "Ende");
    }
    case 3:
    {
    format(str, 256, "Kontostand: %d $\nGib den Spielernamen/ID des Spieler an, dem du etwas überweisen möchtest\ngetrennt durch einem ':' vom Betrag, den du überweisen möchtest.\nBeispiel: PlayerA 10000 überweisen: PlayerA:10000", PlayerInfo[playerid][pAccount]);
    ShowPlayerDialog(playerid,DIALOG_ATMÜBERW, DIALOG_STYLE_INPUT, "Überweisen", str, "Weiter", "Abbrechen");
    }
    }
    }
    else if(dialogid == DIALOG_ATMSTAND) // wenn es bereits == DIALOG_ATM ist kann es nicht auch == DIALOG_ATMSTAND sein
    {
    if(response)
    {
    ShowPlayerDialog(playerid,DIALOG_ATM,DIALOG_STYLE_LIST, "Bank Automat", "Einzahlen\nAuszahlen\nKontostand\nÜberweisung", "Auswählen", "Ende");
    }
    if(!response)
    {
    ShowPlayerDialog(playerid, DIALOG_ATM, DIALOG_STYLE_LIST, "Bank Automat", "Einzahlen\nAuszahlen\nKontostand\nÜberweisung", "Auswählen", "Ende");
    }
    }
    else if(dialogid == DIALOG_ATMEIN) // wenn es bereits == .. ist kann es nicht auch == .. sein
    {
    if(response)
    {
    new str[256];
    new betrag = strval(inputtext);
    if(betrag > GetPlayerMoney(playerid) || betrag > 99999999 || betrag <= 0)
    {
    SendClientMessage(playerid, BLAU, "So viel Geld hast du nicht!");
    format(str, 256, "Gib den Betrag ein, den du einzahlen möchtest!"); // haette man auch als statischen String verwenden koennen, da nichts variable ist an diesem string
    ShowPlayerDialog(playerid,DIALOG_ATMEIN, DIALOG_STYLE_INPUT, "Einzahlen", str, "Einzahlen", "Abbrechen");
    }
    else if(betrag < GetPlayerMoney(playerid) && betrag < GetPlayerMoney(playerid)) // Erleichtertes cheaten, wenn GetPlayerMoney nicht zu einer GetPVarInt funktion umgeschrieben ist
    {
    GivePlayerMoney(playerid, -= betrag; // -= funktioniert nur bei Variablen so
    SetPVarInt(playerid,"Bank", += betrag; // Warum Versuchst du dem Spieler 'betrag' abzuziehen, aber versuchst ihm 'betrag' in der internen Variable drauf zu rechnen?
    GivePlayerMoney(playerid, -betrag); // Doppelt haelt besser?
    format(str, 256, "Alter Kontostand: %d $\nEingezahlt: %d $\nNeuer Kontostand: %d $", GetPVarInt(playerid,"Bank",-betrag), // GetPVarInt( playerid, variableName, value )??
    betrag, GetPVarInt(playerid,"Bank")); // GetPVarInt( playerid, variableName )
    SendClientMessage(playerid, WEISS, str);
    }
    }
    if(!response) // unnoetig, da bereits vorher ueberprueft wurde ob == true, kann mit else rechenzeit einsparen
    {
    ShowPlayerDialog(playerid,DIALOG_ATM, 2, "Bank Automat", "Einzahlen\nAuszahlen\nKontostand\nÜberweisung", "Auswählen", "Ende");
    }
    }
    else if(dialogid == DIALOG_ATMAUS)
    {
    if(response)
    new str[256]; // nur 'str' erstellen, wenn response positiv und danach gleich wieder loeschen? Eine zeile hoeher..
    {
    new betrag = strval(inputtext);
    if(betrag > GetPVarInt(playerid,"Bank" || betrag > 100000000 || betrag <= 0) // GetPVarInt (interne Variable) anstatt die, die vom Client kommt - gut, aber
    // funktionsaufruf zu GetPVarInt() wird nicht geschlossen
    {
    SendClientMessage(playerid, BLAU, "Ungültiger Betrag!");
    format(str, 256, "Kontostand: %d$\nGib den Betrag ein, den du ausgezahlt haben möchtest!", GetPVarInt(playerid,"Bank"));
    ShowPlayerDialog(playerid, DIALOG_ATMAUS, DIALOG_STYLE_INPUT, "Auszahlen", str, "Auszahlen", "Abbrechen");
    }
    else
    {
    SetPVarInt(playerid,"Bank",-= betrag); // -= funktioniert so nicht, nur mit variablen
    GivePlayerMoney(playerid, betrag); // -betrag in der internen Variable und +betrag in der PlayerMoney Variable vom Client?
    format(str, 256, "Alter Kontostand: %d $\nAusgezahlt: %d $\nNeuer Kontostand: %d $", GetPVarInt(playerid,"Bank",+betrag), // GetPVarInt( playerid, variableName, value )??
    betrag, GetPVarInt(playerid,"Bank")); // GetPVarInt( playerid, variableName )
    SendClientMessage(playerid,WEISS, str);
    }
    }
    if(!response) // .. else
    {
    ShowPlayerDialog(playerid,DIALOG_ATM, 2, "Bank Automat", "Einzahlen\nAuszahlen\nKontostand\nÜberweisung", "Auswählen", "Ende");
    }
    }
    else if(dialogid == ATMÜBERW) // ..
    {
    if(response)
    {
    new TransferInfo[2][20], transferid;
    if(transferid == INVALID_PLAYER_ID) // ? transferid wurde noch kein Wert zugewiesen, warum wird diese Variable dann schon verwendet? halb fertiger code?
    {
    SendClientMessage(playerid, COLOR_GREY, " Ungülte Eingabe!");
    format(str, 256, "Kontostand: %d $\nGib den Spielernamen/ID des Spieler an, dem du etwas überweisen möchtest\ngetrennt durch einem ':' vom Betrag, den du überweisen möchtest.\nBeispiel: PlayerA 10000 überweisen: PlayerA:10000", GetPVarInt(playerid,"Bank"));
    ShowPlayerDialog(playerid, ATMÜBERW, 1, "Überweisen", str, "Weiter", "Abbrechen");
    }
    else
    {
    split(inputtext, TransferInfo, ':');
    transferid = ReturnUser(TransferInfo[0]);
    if(strval(TransferInfo[1]) > 100000000 || strval(TransferInfo[1]) <= 0 || strval(TransferInfo[1]) > GetPVarInt(playerid,"Bank"))
    {
    SendClientMessage(playerid, COLOR_GREY, " Ungültiger Betrag!");


    }
    else
    {
    GetPVarInt(transferid,"Bank",+=strval(TransferInfo[1]) // ); fehlt und += funktioniert nur bei Variablen
    GetPVarInt(transferid,"Bank",-=strval(TransferInfo[1]) // das gleiche + warum wird hier +Value und direkt danach -Value gemacht..
    format(str, 256, " Du hast %s %d $ überwiesen!", pName(playerid), strval(TransferInfo[1]));
    // pName(playerid) ist korrekt, wenn pName eine Funktion und kein Array ist, ansonsten pName[playerid]
    SendClientMessage(playerid,BLAU, str);
    format(str, 256, " %s hat dir %d $ auf dein Konto überwiesen!", pName(playerid), strval(TransferInfo[1]));
    SendClientMessage(playerid, BLAU, str); // transferid nicht playerid?
    }
    }
    }
    if(!response) // .. else
    {
    ShowPlayerDialog(playerid,DIALOG_ATM, 2, "Bank", "Einzahlen\nAuszahlen\nÜberweisen\nKontostand\nÜberweisung", "Auswählen", "Ende");
    }
    }


    Zeig mal den Teil in dem du DIALOG_ATM definiert hast. Vielleicht hast du bei den defines DIALOG_ATMÜBERW mit DIALOG_ATM vertauscht, denn _ATM kann nicht gefunden werden aber dafuer _ATMUEBERW zweimal vorhanden.


    Ausserdem ist es hilfreich, wenn du die Zeilen, in denen Error sind kommentierst. Am besten mit der Error-Beschreibung. Ansonsten muss man nach den Error-Zeilen suchen und bei so vielen Zeilen (hast mehr als 100 Zeilen gepostet..) hat wohl kaum einer Lust dazu herum zu raetseln welche Zeile denn nun Zeile 731 sein koennte.

    'number of arguments does not match definition'
    Sagt doch alles aus.


    http://wiki.sa-mp.com/wiki/GetPlayerScore 1 Parameter.
    http://wiki.sa-mp.com/wiki/GetPVarInt 2 Parameter.
    Ansonsten, wenn man nicht weiss, was der Fehler eigentlich bedeutet kann man auch mal nach der Fehlerbeschreibung suchen.


    Was du vermutlich wolltest war ein Vergleich und kein weiterer Funktionsparameter.
    Aber woher sollen die Nutzer auch wissen, wie sie Vergleiche anstellen, wenn es nirgendwo erklaert wird. Die 'tutorials' auf wiki.sa-mp nutzen zwar teilweise Vergleiche, aber kein einziges erklaert tatsaechlich, wie man einen Vergleich anstellt, dabei ist ein Vergleich zu nutzen, doch eines der einfachsten Dinge, die man gleich zu Beginn lernen sollte. Wenn man mal bei den Tutorials 'Pawn for beggars' und 'Scripting Basics' (wiki.sa-mp) nach 'if' sucht findet man zwar Saetze mit 'if', aber nirgends werden if-abfragen erklaert..

    Wenn du die Geschwindigkeit via Distanz ausrechnen moechtest brauchst du auch noch eine Zeitangabe von dem letzten Update von PCarPos und eine Zeitangabe von nun. Das Ergebnis noch mit einer Konstanten multiplizieren um eine ungefaehre Geschwindigkeit in km/h zu erhalten.


    GetPlayerPos(i, x, y, z);
    distance = floatsqroot(floatpower(floatabs(floatsub(x,PCarPos[i][PCarX])),2)+floatpower(floatabs(floatsub(y,PCarPos[i][PCarY])),2)+floatpower(floatabs(floatsub(z,PCarPos[i][PCarZ])),2));
    value = distance / (tickcount() - tickcount_old[i]);
    tickcount_old[i] = tickcount();
    PCarPos[i][PCarY] = y;
    /.../
    value *= 3;


    Wenn du das mit GetVehicleVelocity ausrechen moechtest musst du die einzelnen Ergebnisse von x,y,z zusammen rechnen und mit einer Konstanten multiplizieren. (Werte zwischen 160 und 170 werden dazu von vielen Server benutzt.)

    DAs Problem beim countdown ist, dass nur der jenige der Das Rennen
    gestartet hat unfreezt wird, die anderen Spieler bleiben gefreezt ???


    Das Problem liegt daran, dass du nur den Spieler mit playerId 0 unfreezed:rtimer = SetTimer("RaceCount",1000,1);


    forward RaceCount(playerid);
    public RaceCount(playerid)
    {
    ...
    TogglePlayerControllable(playerid,1);
    ...
    }
    SetTimerEx, damit du die korrekte playerid setzen kannst und eine Schleife, die alle Spieler durchlaeuft, um alle Spieler wieder bewegungsfaehig zu machen.
    Btw. repeating (letzter Wert von SetTimer) kannst du bei dem Timer deaktivieren und in RaceCount den Timer einfach neu setzen, wenn rcount > 0.

    Ja dass ist mir klar, jedoch weis ich net welchen


    Der Code der mit dem Tacho zu tun hat, welcher denn sonst?


    Koennte aber daran liegen, dass du vergisst, dass Arrays 0-indiziert sind.
    new test[2];


    test[0] =
    test[1] =

    Crasht vermutlich bei dem Versuch aus irgendeiner Datei zu lesen. Wenn dein Script auch ohne file-operationen Funktioniert kannst du einfach mal diese mit #define fopen, und dasselbe nochmal fuer die anderen file-operationen wie fread, fclose, deaktivieren.

    Schritt 1 bei Fehlern:
    Fehler Beschreibung durchlesen und Reihe angucken, in der der Fehler aufgetreten ist.
    Schritt 2:
    Fehler beseitigen.



    // Hangar 2
    else if ( IsPlayerInRangeOfPoint(playerid, 7.0, 286.50668334961, 1985.2104492188, 12.539841079712);


    // Hangar 2
    else if ( IsPlayerInRangeOfPoint(playerid, 7.0, 286.50668334961, 1985.2104492188, 12.539841079712) )


    Am ende Fehlt unter umstaenden auch noch eine }. Kannst du mit der richtigen Einrueckung aber herausfinden, ob eine fehlt.

    Man kann mit fopen, fread und fwite auch außerhalb der scripfiles arbeiten ?
    Cool, danke, probiere ich gleich mal aus :thumbup:


    Bin mir nicht sicher, ob das mit den von sa-mp mitgelieferten Funktionen tatsaechlich funktioniert. Eventuell muss man dazu ein externes Plugin benutzen. Im offiziellen sa-mp Forum hatte ich vor ein paar Tagen ein Plugin gesehen, dass auf jeden Fall dazu in der Lage ist, da Plugins externe Programme sind, d.h. ohne sicherheits checks, die von sa-mp code durchgefuehrt werden. :P


    C:\Users\Dark.ShadoW\Server\Ravens\gamemodes\gfr003.pwn(13063) : error 029: invalid expression, assumed zero
    C:\Users\Dark.ShadoW\Server\Ravens\gamemodes\gfr003.pwn(13050) : warning 204: symbol is assigned a value that is never used: "Model"
    C:\Users\Dark.ShadoW\Server\Ravens\gamemodes\gfr003.pwn(16046) : error 033: array must be indexed (variable "TextdrawSpeedo")


    Zeile 13063 ist nicht gepostet. Und die restlichen Errors kommen vermutlich daher, dass du die Variablen TextdrawTank und TextdrawSpeedo als array initialisiert hast.
    new Text:TextDrawSpeedo[2]; = Falsch
    new Text:TextDrawSpeedo; = Richtig
    oder, wenn es ein Array sein soll, dann einen Index bei TextDrawCreate verwenden:
    TextDrawSpeed[0] = TextDrawCreate..

    Du hast die Variable 'mod' zweimal erstellt.


    Bsp:
    new mod;
    if ( 1 == 1 ){ new mod = 3; }


    Ein unbenutztes symbol (ret_memcpy) bedeutet, dass du die Variable zwar erstellst, aber nie nutzt. Daher solltest du die einfach entfernen koennen, ohne, dass Probleme entstehen.


    //Global/Static variable - bei initialization der Hangar = 1/0 setzen
    new HangarOpen[2];
    // KeyStateChange
    if( (newkeys & KEY_CROUCH) && !(oldkeys & KEY_CROUCH) ) // First time touching the Crotch.. button
    {
    // Hangar 1
    if( IsPlayerInRangeOfPoint(playerid, 7.0, 286.53305053711, 1953.8522949219, 12.539841079712) )
    {
    if ( !HangarOpen[0] )
    {
    SendClientMessage(playerid,0x00C0FFFF,"Hangar 1 wird geöffnet....!");
    MoveObject(Hangar11, 286.53305053711, 1953.8522949219, 12.539841079712, 1.5);
    MoveObject(Hangar12, 286.55035400391, 1959.6234130859, 12.539841079712, 1.5);
    HangarOpen[0] = 1;
    PlayerPlaySound(playerid, 1153 ,286.55035400391, 1959.6234130859, 12.539841079712);
    }
    else
    {
    SendClientMessage(playerid,0x00C0FFFF,"Hangar 1 wird geschlossen....!");
    MoveObject(Hangar11, //geschlossen11 coords );
    MoveObject(Hangar12, //geschlossen12 coords );
    HangarOpen[0] = 0;
    PlayerPlaySound(playerid, 1153 ,286.55035400391, 1959.6234130859, 12.539841079712);
    }
    return 1;
    }
    // Hangar 2
    else if ( IsPlayerInRangeOfPoint(playerid, 7.0, // Hangar 2 Coords
    {
    if ( !HangarOpen[1] )
    {
    SendClientMessage(playerid,0x00C0FFFF,"Hangar 2 wird geöffnet....!");
    MoveObject(Hangar21, // offen21 coords
    MoveObject(Hangar22, // offen22 coords
    HangarOpen[1] = 1;
    PlayerPlaySound(playerid, 1153 ,// Hangar 2 Coords
    }
    else
    {
    SendClientMessage(playerid,0x00C0FFFF,"Hangar 2 wird geschlossen....!");
    MoveObject(Hangar21, // geschlossen21 coords );
    MoveObject(Hangar22, // geschlossen22 coords );
    HangarOpen[1] = 0;
    PlayerPlaySound(playerid, 1153 ,// Hangar 2 Coords
    }
    return 1;
    }

    In dem Quelltext den der NPC nutzen wird (npcmodes Ordner und Name der Script Datei die in ConnectNPC verwendet wurde) ueberpruefen, ob ein Spieler in der naeheren Umgebung ist und dann PauseRecordingPlayback verwenden. Wenn der Spieler wieder weiter weg ist ResumeRecordingPlayback.


    'Hier muss die NPC-Datei iwie in den npcmodes/recordes Ordner ?'
    Das recording wird vermutlich direkt dort gespeichert. Ansonsten kannst du die Datei nach /stoprecording mit fopen, fread und fwrite verschieben, da es bei den standard sa-mp includes kein fmove dabei ist.
    'Und noch eine Datei in den npcmodes-Ordner wo dann der NPC-Name drinne steht'
    Die amx Datei des NPCs kann eigentlich immer die gleiche bleiben und sollte bei OnNPCConnect das RecordingFile 'npc' verwenden. (Evtl. auch noch die oben beschriebene Methode mit PauseRecordingPlayback/ResumeRecordingPlayback.)
    Die npc include beschreibt keine Funktion NPCDisconnect, sie wird ebenfalls nicht in der samp include beschrieben. Anscheinend muss man den NPC dann manuell vom gamemode oder einem Filterscript her kicken. Verkompliziert das nur unnoetig..

    Ich verstehe nicht ganz was du meinst. Welche Funktion wird fuer beide aufgerufen?


    Habe einfach mal ein paar optimisierungen vorgenommen, da du z.B. SetPVarInt und SetTimerEx immer wieder in der for-schleife aufgerufen hast. (Unter der Annahme geschrieben, dass du mit 'SpielerDarfBefehl("kontrolle"' ueberpruefst, ob der Spieler ein Cop ist.)

    if(newkeys & KEY_SUBMISSION)
    {
    if(SpielerDarfBefehl("kontrolle",playerid))
    {
    if(GetPVarInt(playerid,"ruft") == 1) return SCM(playerid,rot,"Du hast schon Verstärkung gerufen! 30 Sekunden warten");
    SetPVarInt(playerid,"ruft",1);
    SetTimerEx("NeedCops",60000,0,"d",playerid);
    new s[50];
    format(s,sizeof(s),"Polizist: %s: benötigt Verstärkung!",PlayerName[playerid]);
    for(new i;i<MAX_PLAYERS;i++)
    {
    if(IsPlayerConnected(i) && SpielerDarfBefehl("kontrolle",i))
    {
    SetPlayerMarkerForPlayer(i,playerid,rot);
    SCM(i, COLOR_LIGHTBLUE, s);
    //SendMessageToTeam(playerid,COLOR_LIGHTBLUE,s);
    }
    }
    }
    return 1;
    }

    Alternativ kann man auch SendMessageToTeam vor die for-schleife setzen, sofern SendMessageToTeam eine Nachricht an das team der uebergebenen playerid sendet. Ausser, wenn SendMessageToTeam eine 'for( i < MAX_PLAYERS )'-schleife benutzt, denn dann ist SCM dort wo es jetzt ist nutzvoller, da man sich dann eine for-MAX-Players schleife spart.

    1. Gehen alle Tasten nur im Auto?


    Sollte eigentlich nicht davon abhaengen, ob man im Auto ist oder nicht.



    Falls du mit 'Bei mir funzt das nur beim ersten' meinst, dass der Hangar sich nicht schliesst liegt das daran, dass du zwar ueberpruefst, ob der Spieler in der Naehe des Hangars ist, aber nicht, ob der Hangar bereits offen ist. Du hast zwar danach Code, der das Tor wieder schliessen soll, aber dieser wird wegen dem return 1 im ersten IsPlayerInRangeOfPoint nicht erreicht, sofern der Spieler auf dem Boden ist (nicht hoeher als 19.5). Ich hoffe das war jetzt halbwegs verstaendlich ausgedrueckt.
    Ansonsten, falls du damit meinst, dass der zweite Hangar sich nicht oeffnet liegt es daran, dass dort kein 2. Hangar im Code ist :P


    Btw. ein check, ob bei OldKey ebenfalls KEY_CROUCH gedrueckt wurde wuerde ebenfalls die Wahrscheinlichkeit, dass etwas ungeplantes passiert reduzieren. Ausserdem sind die newkeys/oldkeys variablen bitmasken, d.h. dass, wenn du die variablen mit '(newkeys == KEY_CROUCH)' vergleichst, dass nur KEY_CROUCH, nicht gleichzeitig z.B. KEY_ACTION gedrueckt werden darf. Wenn du moechtest, dass sich das tor auch oeffnet/schliesst, wenn KEY_ACTION und KEY_CROUCH gedrueckt werden, dann solltest '(newkeys & KEY_CROUCH)' verwenden.

    Man kann einen Sound nicht einfach stoppen - zumindest gibt es keine Funktion dafuer in sa-mp. Du koenntest dir nur einen kuerzeren Sound suchen, oder einen lauteren Sound den du dann Abspielst, wenn das Garagentor sich nicht mehr bewegt :P
    Bei dem angegebenen Beispiel wird aber auch nicht der Sound gestoppt, sondern die Plattform und wenn die Plattform dann stoppt wird ein Sound abgespielt.


    Edit:
    Habe gerade mal gegoogelt und das gefunden:
    http://forum.sa-mp.com/showthread.php?t=77566
    Evtl. hilft einer der Methoden/SondIds.

    Das ist also das Script, was ich in verschiedenen Role-Play server gesehen habe.
    Beim spielen auf servern mit dem Script ist mir negativ aufgefallen, dass die untere Bildleiste, zwar sagt, wie man aus dem Auto-Kauf-Menue wieder herauskommt, oder etwas kauft (Enter/Steuerung), aber wenn man die Tasten-Einstellung fuer 'Feuern'/'In auto einsteigen' veraendert hat muss man erst eine halbe stunde lang nach der korrekten Taste suchen.
    Daher als verbesserungs-Vorschlag, dass die Nachricht z.B. in 'Feuern-Taste zum abbrechen' umgewandelt wird. Oder bei GameTexten gab es auch die Moeglichkeit ~KEY_FIRE~ oder derartiges zu verwenden - vllt. gibt es das auch fuer TextDraws, bin mir nicht sicher, auch nicht ob es wirklich 'KEY_FIRE' heisst oder irgendwie anders, kann man aber sicherlich beides im sa-mp wiki nachgucken.


    Ansonsten meiner Meinung nach ein sehr gutes Script. Gute Arbeit.

    Cooles tuning. Wenn du das tatsaechlich umsetzen solltest und den so tief, wie bei dem 2. oder 3. machst kannst du ja dann mal bitte Bilder posten, wenn du erste Erfahrungen mit Bodenwellen gesammelt hast. Oder einfach Bilder, wenn du den zu Schrott gefahren hast (muss ja nicht umbedingt eine Bodenwelle sein).

    Die Warnung bedeutet, dass du die GetDistanceBetweenPoints-Funktion benutzt, bevor sie definiert war und der Code an der Stelle, wo die bislang undefinierte Funktion benutzt wird neu bestimmt wurde.


    PS.: GetPlayerInterior fuer die Interior id.