diff --git a/data/dfndata/items/shoplist.dfn b/data/dfndata/items/shoplist.dfn
index 188bebb33..88adb7e76 100644
--- a/data/dfndata/items/shoplist.dfn
+++ b/data/dfndata/items/shoplist.dfn
@@ -407,6 +407,7 @@ RSHOPITEM=0x097e
RSHOPITEM=0x098c
RSHOPITEM=0x103c
RSHOPITEM=0x09f2
+SELLITEM=playervendordeed
SELLITEM=0x099f
SELLITEM=0x099b
SELLITEM=0x09c7
@@ -1550,6 +1551,7 @@ RSHOPITEM=0x1f98
RSHOPITEM=0x09c8
RSHOPITEM=0x09c4
RSHOPITEM=0x098d
+SELLITEM=playervendordeed
SELLITEM=0x097b
SELLITEM=0x09bb
SELLITEM=0x09f2
diff --git a/data/dictionaries/dictionary.CSY b/data/dictionaries/dictionary.CSY
index 01293a24f..3984ccbf3 100644
--- a/data/dictionaries/dictionary.CSY
+++ b/data/dictionaries/dictionary.CSY
@@ -5731,5 +5731,97 @@
30003=Šampion padl!
30004=Byl jsi odměněn Svitkem moci!
30005=Energie svatyně ubývá, protože nejsou přítomni žádní vyzyvatelé...
+// [ 40000 - 40200 ] Hráč Prodejce
+40000=%s si již nemůže dovolit údržbu a byl propuštěn.
+40001=Nelze vytvořit prodejní listinu. Propuštění prodejce bylo zrušeno.
+40002=Vrácené předměty prodejce - banka: %i, pozemek: %i.
+40003=Vrácené předměty prodejce - balíček: %i, banka: %i, pozemek: %i.
+40004=Vráceno %i zlata do vašeho balíčku.
+40005=Vráceno %i zlata do vaší bankovní schránky.
+40006=V lokaci prodejce bylo odloženo %i zlata.
+40007=Listina prodejce byla umístěna do vašeho balíčku.
+40008=Listina prodejce byla umístěna do vaší bankovní schránky.
+40009=Listina prodejce byla odložena do lokace prodejce.
+40010=%s si nemohl dovolit údržbu a byl propuštěn.
+40011=Na prodejcově lokaci bylo odloženo %i prodejních předmětů.
+40012=Tento prodejce nemá žádné zlato k vyzvednutí.
+40013=Vaše bankovní schránka momentálně nemůže přijímat platby od prodejce.
+40014=Nelze vytvořit bankovní šek na prodejní zlato.
+40015=Ve vaší bankovní schránce bylo shromážděno %i zlata jako bankovní šek.
+40016=Nelze vložit prodejní zlato do vaší bankovní schránky.
+40017=Ve vaší bankovní schránce bylo shromážděno %i zlata.
+40018=Neplatná částka vkladu.
+40019=Nemáte batoh.
+40020=Ve vašem batohu není dostatek zlata.
+40021=Bankovní účet prodejce je plný.
+40022=Nelze odebrat zlato z vašeho batohu. Vklad zrušen.
+40023=Na bankovní účet dodavatele vloženo %i zlata.
+40024=Banka dodavatele dosáhla maximálního zůstatku.
+40025=Bankovní účet dodavatele je prázdný.
+40026=Přesunuto %i zlata z bankovního účtu dodavatele do rezervovaného zlata.
+40027=Nelze vytvořit smlouvu s dodavatelem. Zrušení zrušeno.
+40028=Nepracuji pro vás!
+40029=Zobrazit zboží
+40030=Přizpůsobit
+40031=Sbírat zlato
+40032=Vložit zlato
+40033=Vyprázdnit účet
+40034=Zavřít
+40035=Počet dnů, které mohu pracovat:
+40036=Hodin, které mohu pracovat:
+40037=Zlato držené pro vás:
+40038=Zlato držené na mém účtu:
+40039=Údržba dodavatele:
+40040=Můj poplatek za období je:
+40041=Hodiny účtovaného období:
+40042=Celkový poplatek za položku:
+40043=Neomezeně
+40044=Zakázáno
+40045=HLAVNÍ
+40046=PŘIZPŮSOBENÍ DODAVATELEM MENU
+40047=VLASY
+40048=Přejmenovat prodejce
+40049=Přejmenovat obchod
+40050=Zadejte nový název prodejce.
+40051=Zadejte nový název obchodu.
+40052=Zadejte množství zlata, které chcete vložit.
+40053=Toto není platný hráčský prodejce.
+40054=Váš prodejce je nyní pojmenován: %s
+40055=Název obchodu je příliš dlouhý. Maximálně 40 znaků.
+40056=Název vašeho obchodu je nyní: %s
+40057=Musíte zadat platné množství zlata.
+40058=Krátké vousy a knír
+40059=Z tohoto domu nemůžete nakupovat, protože vám byl do tohoto podniku zakázán vstup.
+40068=Dlouhé vousy a Knír
+40069=Kozí bradka
+40071=Odstranit ochlupení
+40072=Barvení vlasů
+40073=VOUSY
+40074=Odstranit vousy
+40075=Barvení vousů
+40076=Změnit na muže
+40077=Změnit na ženu
+40078=Změnit na člověka
+40079=Změnit na elfa
+40080=Změnit oblečení
+40081=ZAVŘÍT
+40082=BARVA VLASŮ
+40083=BARVA VOUSŮ
+40084=Vlasy - %s
+40085=Vousy - %s
+40086=NABÍDKA OBLEČENÍ PRODEJCE
+40087=Barva
+40088=Odstranit
+40089=KATEGORIE BAREV
+40090=HOTOVO
+40091=OBLEČENÍ BARVA
+40100=Speciální fialová
+40101=Speciální červená
+40102=Speciální oranžová
+40103=Speciální žlutá
+40104=Speciální limetková
+40105=Speciální tmavě limetková
+40106=Speciální ledově modrá
+40107=Speciální ledově bílá
}
EOF
diff --git a/data/dictionaries/dictionary.ENG b/data/dictionaries/dictionary.ENG
index 398ae6b41..691459976 100644
--- a/data/dictionaries/dictionary.ENG
+++ b/data/dictionaries/dictionary.ENG
@@ -5731,5 +5731,97 @@
30003=The Champion has fallen!
30004=You have been rewarded with a Scroll of Power!
30005=The shrine's energy wanes with no challengers present...
+// [ 40000 - 40200 ] Player Vendor
+40000=%s can no longer afford upkeep and has been dismissed.
+40001=Unable to create vendor deed. Vendor dismissal aborted.
+40002=Vendor items returned - bank: %i, ground: %i.
+40003=Vendor items returned - pack: %i, bank: %i, ground: %i.
+40004=Returned %i gold to your pack.
+40005=Returned %i gold to your bank box.
+40006=Dropped %i gold at vendor location.
+40007=Placed vendor deed in your pack.
+40008=Placed vendor deed in your bank box.
+40009=Dropped vendor deed at vendor location.
+40010=%s could not afford upkeep and was dismissed.
+40011=Dropped %i vendor item(s) at vendor location.
+40012=This vendor has no held gold to collect.
+40013=Your bank box cannot receive vendor payouts right now.
+40014=Unable to create a bank check for vendor gold.
+40015=Collected %i gold as a bank check in your bank box.
+40016=Unable to deposit vendor gold into your bank box.
+40017=Collected %i gold to your bank box.
+40018=Invalid deposit amount.
+40019=You have no backpack.
+40020=You do not have enough gold in your pack.
+40021=Vendor bank account is full.
+40022=Unable to remove gold from your pack. Deposit cancelled.
+40023=Deposited %i gold into vendor bank account.
+40024=Vendor bank reached its maximum balance.
+40025=Vendor bank account is empty.
+40026=Moved %i gold from vendor bank account to held gold.
+40027=Unable to create vendor deed. Dismiss cancelled.
+40028=I don't work for you!
+40029=See goods
+40030=Customize
+40031=Collect Gold
+40032=Deposit Gold
+40033=Flush Bank
+40034=Dismiss
+40035=Amount of days I can work:
+40036=Hours I can work:
+40037=Gold held for you:
+40038=Gold held in my account:
+40039=Vendor upkeep:
+40040=My charge per period is:
+40041=Charge period hours:
+40042=Item fee total:
+40043=Unlimited
+40044=Disabled
+40045=MAIN
+40046=VENDOR CUSTOMIZATION MENU
+40047=HAIR
+40048=Rename Vendor
+40049=Rename Shop
+40050=Enter a new vendor name.
+40051=Enter a new shop name.
+40052=Enter the amount of gold you wish to deposit.
+40053=That is not a valid player vendor.
+40054=Your vendor is now named: %s
+40055=That shop name is too long. Maximum 40 chars.
+40056=Your shop title is now: %s
+40057=You must enter a valid gold amount.
+40058=Short Beard and Mustache
+40059=You can't shop from this home as you have been banned from this establishment.
+40068=Long Beard and Mustache
+40069=Goatee
+40071=Remove Hair
+40072=Color Hair
+40073=BEARD
+40074=Remove Beard
+40075=Color Beard
+40076=Change to Male
+40077=Change to Female
+40078=Change to Human
+40079=Change to Elf
+40080=Change Clothing
+40081=CLOSE
+40082=HAIR COLOR
+40083=BEARD COLOR
+40084=Hair - %s
+40085=Beard - %s
+40086=VENDOR CLOTHING MENU
+40087=Color
+40088=Remove
+40089=COLOR CATEGORY
+40090=DONE
+40091=CLOTHING COLOR
+40100=Special Purple
+40101=Special Red
+40102=Special Orange
+40103=Special Yellow
+40104=Special Lime
+40105=Special Dark Lime
+40106=Special Ice Blue
+40107=Special Ice White
}
EOF
diff --git a/data/dictionaries/dictionary.FRE b/data/dictionaries/dictionary.FRE
index 63a2382a3..9a5144853 100644
--- a/data/dictionaries/dictionary.FRE
+++ b/data/dictionaries/dictionary.FRE
@@ -5895,5 +5895,94 @@
30003=Le Champion est tombé !
30004=Vous avez reçu un Parchemin de Puissance !
30005=L'énergie du sanctuaire s'affaiblit en l'absence de challengers...
+// [ 40000 - 40200 ] Vendeur du joueur
+40000=%s n'a plus les moyens de s'entretenir et a été renvoyé.
+40001=Impossible de créer l'acte de vendeur. Renvoi du vendeur annulé.
+40002=Objets du vendeur retournés - banque : %i, terrain : %i.
+40003=Objets du vendeur retournés - sac : %i, banque : %i, terrain : %i.
+40004=%i pièces d'or retournées dans votre sac.
+40005=%i pièces d'or retournées dans votre coffre.
+40006=%i pièces d'or déposées à l'emplacement du vendeur.
+40007=Acte de vendeur placé dans votre sac.
+40008=Acte de vendeur placé dans votre coffre.
+40009=Acte de vendeur déposé à l'emplacement du vendeur. 40010 = %s n'avait plus les moyens de payer l'entretien et a été renvoyé.
+40011=%i objets déposés chez le vendeur.
+40012=Ce vendeur n'a plus d'or à encaisser.
+40013=Votre coffre bancaire ne peut pas recevoir les paiements des vendeurs pour le moment.
+40014=Impossible de créer un chèque bancaire pour l'or du vendeur.
+40015=%i pièces d'or encaissées sous forme de chèque bancaire dans votre coffre bancaire.
+40016 = Impossible de déposer l'or du vendeur dans votre coffre bancaire.
+40017=%i pièces d'or encaissées dans votre coffre bancaire.
+40018=Montant du dépôt invalide.
+40019=Vous n'avez pas de sac à dos.
+40020=Vous n'avez pas assez d'or dans votre sac à dos.
+40021=Le compte bancaire du vendeur est plein. 40022 = Impossible de retirer l'or de votre inventaire. Dépôt annulé.
+40023=%i pièces d'or déposées sur le compte bancaire du vendeur.
+40024=Solde maximum atteint sur le compte bancaire du vendeur.
+40025=Compte bancaire du vendeur vide.
+40026=%i pièces d'or transférées du compte bancaire du vendeur vers votre réserve.
+40027=Impossible de créer l'acte de vente. Renvoi annulé.
+40028=Je ne travaille pas pour vous ! 40029=Voir les articles
+40030=Personnaliser
+40031=Collecter de l'or
+40032=Déposer de l'or
+40033=Vider la banque
+40034=Annuler
+40035=Nombre de jours ouvrables :
+40036=Heures ouvrables :
+40037=Or détenu pour vous :
+40038=Or détenu sur mon compte :
+40039=Frais d'entretien du vendeur :
+40040=Tarif par période :
+40041=Heures de facturation :
+40042=Total des frais par article :
+40043=Illimité
+40044=Désactivé
+40045=PRINCIPAL
+40046=PERSONNALISATION DU VENDEUR MENU
+40047=CHEVEUX
+40048=Renommer le vendeur
+40049=Renommer la boutique
+40050=Saisissez un nouveau nom de vendeur.
+40051=Saisissez un nouveau nom de boutique.
+40052=Saisissez le montant d'or que vous souhaitez déposer.
+40053=Ce n'est pas un vendeur de joueur valide.
+40054=Votre vendeur s'appelle désormais : %s
+40055=Ce nom de boutique est trop long. Maximum 40 caractères.
+40056=Le titre de votre boutique est désormais : %s
+40057=Vous devez saisir un montant d'or valide.
+40058=Barbe courte et moustache
+40059=Vous ne pouvez pas faire d'achats dans cette demeure, car vous avez été banni de cet établissement.
+40068=Barbe et Moustache
+40069=Bouquet
+40071=Enlever les cheveux
+40072=Colorer les cheveux
+40073=Barbe
+40074=Enlever la barbe
+40075=Colorer la barbe
+40076=Changer pour un homme
+40077=Changer pour une femme
+40078=Changer pour un humain
+40079=Changer pour un elfe
+40080=Changer de vêtements
+40081=FERMER
+40082=COULEUR DES CHEVEUX
+40083=COULEUR DE LA BARBE
+40084=Cheveux - %s
+40085=Barbe - %s
+40086=MENU VÊTEMENTS DU VENDEUR
+40087=Colorer
+40088=Enlever
+40089=CATÉGORIE DE COULEUR
+40090=TERMINÉ
+40091=VÊTEMENTS COULEUR
+40100=Violet spécial
+40101=Rouge spécial
+40102=Orange spécial
+40103=Jaune spécial
+40104=Vert citron spécial
+40105=Vert citron foncé spécial
+40106=Bleu glacier spécial
+40107=Blanc glacier spécial
}
EOF
diff --git a/data/dictionaries/dictionary.GER b/data/dictionaries/dictionary.GER
index 9014ffff0..64d2d1668 100644
--- a/data/dictionaries/dictionary.GER
+++ b/data/dictionaries/dictionary.GER
@@ -5731,5 +5731,97 @@
30003=Der Champion ist besiegt!
30004=Du wurdest mit einer Schriftrolle der Macht belohnt!
30005=Die Energie des Schreins schwindet, da keine Herausforderer anwesend sind...
+// [ 40000 - 40200 ] Spielerhändler
+40000=%s kann sich den Unterhalt nicht mehr leisten und wurde entlassen.
+40001=Händlerurkunde konnte nicht erstellt werden. Händlerentlassung abgebrochen.
+40002=Händlergegenstände zurückgegeben – Bank: %i, Boden: %i.
+40003=Händlergegenstände zurückgegeben – Rucksack: %i, Bank: %i, Boden: %i.
+40004=%i Gold wurden deinem Rucksack hinzugefügt.
+40005=%i Gold wurden deiner Bank hinzugefügt.
+40006=%i Gold wurden am Händlerstandort abgelegt.
+40007=Händlerurkunde wurde deinem Rucksack hinzugefügt.
+40008=Händlerurkunde wurde deiner Bank hinzugefügt.
+40009=Händlerurkunde wurde am Händlerstandort abgelegt.
+40010=%s konnte sich den Unterhalt nicht leisten und wurde entlassen.
+40011=%i Händlerartikel wurden am Händlerstandort abgelegt.
+40012=Dieser Händler hat kein Gold zum Einsammeln.
+40013=Ihr Bankfach kann derzeit keine Händlerauszahlungen empfangen.
+40014=Es konnte kein Bankscheck für Händlergold erstellt werden.
+40015=%i Gold wurden als Bankscheck in Ihrem Bankfach eingesammelt.
+40016=Es konnte kein Händlergold in Ihr Bankfach eingezahlt werden.
+40017=%i Gold wurden in Ihrem Bankfach eingesammelt.
+40018=Ungültiger Einzahlungsbetrag.
+40019=Sie haben keinen Rucksack.
+40020=Sie haben nicht genügend Gold in Ihrem Rucksack.
+40021=Das Händlerbankkonto ist voll.
+40022=Gold konnte nicht aus Ihrem Paket entnommen werden. Einzahlung abgebrochen.
+40023=%i Gold wurden auf das Händlerbankkonto eingezahlt.
+40024=Das Händlerbankkonto hat seinen maximalen Kontostand erreicht.
+40025=Das Händlerbankkonto ist leer.
+40026=%i Gold wurden vom Händlerbankkonto auf das gehaltene Goldkonto übertragen.
+40027=Händlervertrag konnte nicht erstellt werden. Kündigung abgebrochen.
+40028=Ich arbeite nicht für Sie!
+40029=Waren ansehen
+40030=Anpassen
+40031=Gold sammeln
+40032=Gold einzahlen
+40033=Bank leeren
+40034=Schließen
+40035=Anzahl der Tage, an denen ich arbeiten kann:
+40036=Stunden, die ich arbeiten kann:
+40037=Für Sie reserviertes Gold:
+40038=Gold auf meinem Konto:
+40039=Händlerunterhalt:
+40040=Meine Gebühr pro Periode:
+40041=Stunden der Abrechnungsperiode:
+40042=Gesamtgebühr für Artikel:
+40043=Unbegrenzt
+40044=Deaktiviert
+40045=HAUPT
+40046=HÄNDLERANPASSUNG MENÜ
+40047=HAAR
+40048=Händler umbenennen
+40049=Laden umbenennen
+40050=Geben Sie einen neuen Händlernamen ein.
+40051=Geben Sie einen neuen Ladennamen ein.
+40052=Geben Sie die Menge an Gold ein, die Sie einzahlen möchten.
+40053=Dies ist kein gültiger Spielerhändler.
+40054=Ihr Händler heißt nun: %s
+40055=Dieser Ladenname ist zu lang. Maximal 40 Zeichen.
+40056=Ihr Ladentitel lautet nun: %s
+40057=Sie müssen einen gültigen Goldbetrag eingeben.
+40058=Kurzer Bart und Schnurrbart
+40059=Sie können in diesem Heim keine Einkäufe tätigen, da Sie von dieser Einrichtung ausgeschlossen wurden.
+40068=Langer Bart und Schnurrbart Schnurrbart
+40069=Ziegenbart
+40071=Haare entfernen
+40072=Haare färben
+40073=BART
+40074=Bart entfernen
+40075=Bart färben
+40076=Zu Mann wechseln
+40077=Zu Frau wechseln
+40078=Zu Mensch wechseln
+40079=Zu Elf wechseln
+40080=Kleidung ändern
+40081=SCHLIESSEN
+40082=HAARFARBE
+40083=BARTFARBE
+40084=Haare - %s
+40085=Bart - %s
+40086=KLEIDUNGSMENÜ DES HÄNDLERS
+40087=Farbe
+40088=Entfernen
+40089=FARBKATEGORIE
+40090=FERTIG
+40091=KLEIDUNG FARBE
+40100=Speziallila
+40101=Spezialrot
+40102=Spezialorange
+40103=Spezialgelb
+40104=Speziallimette
+40105=Spezialdunkellimette
+40106=Spezialeisblau
+40107=Spezialeisweiß
}
EOF
diff --git a/data/dictionaries/dictionary.ITA b/data/dictionaries/dictionary.ITA
index 15acb8adf..c6549b19a 100644
--- a/data/dictionaries/dictionary.ITA
+++ b/data/dictionaries/dictionary.ITA
@@ -5730,5 +5730,97 @@
30003=Il Campione è stato sconfitto!
30004=Hai ricevuto in ricompensa una Pergamena del Potere!
30005=L'energia del santuario si affievolisce in assenza di sfidanti...
+// [ 40000 - 40200 ] Venditore
+40000=%s non può più permettersi il mantenimento ed è stato licenziato.
+40001=Impossibile creare l'atto di vendita. Licenziamento del venditore annullato.
+40002=Oggetti del venditore restituiti - banca: %i, terreno: %i.
+40003=Oggetti del venditore restituiti - pacco: %i, banca: %i, terreno: %i.
+40004=Restituito %i oro nel tuo pacco.
+40005=Restituito %i oro nella tua banca.
+40006=Lasciato %i oro nella posizione del venditore.
+40007=Atto di vendita inserito nel tuo pacco.
+40008=Atto di vendita inserito nella tua banca.
+40009=Atto di vendita inserito nella posizione del venditore.
+40010=%s non poteva permettersi il mantenimento ed è stato licenziato.
+40011=Ha lasciato %i oggetti del venditore nella posizione del venditore.
+40012=Questo venditore non ha oro da riscuotere.
+40013=La tua cassetta della posta non può ricevere pagamenti dal venditore in questo momento.
+40014=Impossibile creare un assegno bancario per l'oro del venditore.
+40015=Hai raccolto %i oro come assegno bancario nella tua cassetta della posta.
+40016=Impossibile depositare l'oro del venditore nella tua cassetta della posta.
+40017=Hai raccolto %i oro nella tua cassetta della posta.
+40018=Importo del deposito non valido.
+40019=Non hai uno zaino.
+40020=Non hai abbastanza oro nel tuo zaino.
+40021=Il conto bancario del venditore è pieno.
+40022=Impossibile rimuovere l'oro dal tuo pacchetto. Deposito annullato.
+40023=Depositato %i oro sul conto bancario del venditore.
+40024=Il conto bancario del venditore ha raggiunto il saldo massimo.
+40025=Il conto bancario del venditore è vuoto.
+40026=Spostato %i oro dal conto bancario del venditore all'oro trattenuto.
+40027=Impossibile creare l'atto di vendita del venditore. Ignora annullato.
+40028=Non lavoro per te!
+40029=Vedi beni
+40030=Personalizza
+40031=Raccogli oro
+40032=Deposita oro
+40033=Svuota banca
+40034=Ignora
+40035=Numero di giorni in cui posso lavorare:
+40036=Ore in cui posso lavorare:
+40037=Oro trattenuto per te:
+40038=Oro trattenuto sul mio conto:
+40039=Mantenimento venditore:
+40040=La mia tariffa per periodo è:
+40041=Ore del periodo di addebito:
+40042=Totale commissione articolo:
+40043=Illimitato
+40044=Disabilitato
+40045=PRINCIPALE
+40046=PERSONALIZZAZIONE VENDITORE MENU
+40047=CAPELLI
+40048=Rinomina venditore
+40049=Rinomina negozio
+40050=Inserisci un nuovo nome per il venditore.
+40051=Inserisci un nuovo nome per il negozio.
+40052=Inserisci la quantità d'oro che desideri depositare.
+40053=Non è un venditore gestito da un giocatore valido.
+40054=Il tuo venditore ora si chiama: %s
+40055=Il nome del negozio è troppo lungo. Massimo 40 caratteri.
+40056=Il titolo del tuo negozio ora è: %s
+40057=Devi inserire una quantità d'oro valida.
+40058=Barba e baffi corti
+40059=Non puoi fare acquisti in questa abitazione, poiché sei stato bandito da questa struttura.
+40068=Barba lunga e Baffi
+40069=Pizzetto
+40071=Rimuovi peli
+40072=Colora capelli
+40073=BARBA
+40074=Rimuovi barba
+40075=Colora barba
+40076=Cambia in maschile
+40077=Cambia in femminile
+40078=Cambia in umano
+40079=Cambia in elfo
+40080=Cambia abbigliamento
+40081=CHIUDI
+40082=COLORE CAPELLI
+40083=COLORE BARBA
+40084=Capelli - %s
+40085=Barba - %s
+40086=MENU ABBIGLIAMENTO VENDITORE
+40087=Colore
+40088=Rimuovi
+40089=CATEGORIA COLORE
+40090=FATTO
+40091=ABBIGLIAMENTO COLORE
+40100=Viola speciale
+40101=Rosso speciale
+40102=Arancione speciale
+40103=Giallo speciale
+40104=Lime speciale
+40105=Lime scuro speciale
+40106=Blu ghiaccio speciale
+40107=Bianco ghiaccio speciale
}
EOF
diff --git a/data/dictionaries/dictionary.POL b/data/dictionaries/dictionary.POL
index 74b21e9a8..fa83710be 100644
--- a/data/dictionaries/dictionary.POL
+++ b/data/dictionaries/dictionary.POL
@@ -5731,5 +5731,97 @@
30003=Mistrz został pokonany!
30004=Otrzymałeś Zwoj mocy!
30005=Energia kapliczki słabnie z powodu braku pretendentów...
+// [ 40000 - 40200 ] Gracz Sprzedawca
+40000=%s nie jest już w stanie utrzymać się i został zwolniony.
+40001=Nie można utworzyć aktu własności sprzedawcy. Zwolnienie sprzedawcy zostało przerwane.
+40002=Zwrócono przedmioty sprzedawcy - bank: %i, ziemia: %i.
+40003=Zwrócono przedmioty sprzedawcy - paczka: %i, bank: %i, ziemia: %i.
+40004=Zwrócono %i złota do twojego pakietu.
+40005=Zwrócono %i złota do twojego skarbca.
+40006=Upuszczono %i złota u sprzedawcy.
+40007=Umieszczono akt własności sprzedawcy w twoim pakiecie.
+40008=Umieszczono akt własności sprzedawcy w twoim skarbcu.
+40009=Upuszczono akt własności sprzedawcy u sprzedawcy.
+40010=%s nie mógł sobie pozwolić na utrzymanie i został zwolniony.
+40011=Upuścił %i przedmiotów u sprzedawcy.
+40012=Ten sprzedawca nie ma złota do odebrania.
+40013=Twoja skrytka bankowa nie może obecnie przyjmować wypłat od sprzedawcy.
+40014=Nie można utworzyć czeku bankowego na złoto sprzedawcy.
+40015=Zebrano %i złota jako czek bankowy w Twojej skrytce bankowej.
+40016=Nie można wpłacić złota sprzedawcy do Twojej skrytki bankowej.
+40017=Zebrano %i złota do Twojej skrytki bankowej.
+40018=Nieprawidłowa kwota wpłaty.
+40019=Nie masz plecaka.
+40020=Nie masz wystarczającej ilości złota w plecaku.
+40021=Konto bankowe sprzedawcy jest pełne.
+40022=Nie można usunąć złota z paczki. Depozyt anulowany.
+40023=Wpłacono %i złota na konto bankowe sprzedawcy.
+40024=Saldo banku sprzedawcy osiągnęło maksymalne saldo.
+40025=Konto bankowe sprzedawcy jest puste.
+40026=Przeniesiono %i złota z konta bankowego sprzedawcy do posiadanego złota.
+40027=Nie można utworzyć aktu własności sprzedawcy. Anulowano anulowanie.
+40028=Nie pracuję dla ciebie!
+40029=Zobacz towary
+40030=Dostosuj
+40031=Odbierz złoto
+40032=Wpłać złoto
+40033=Opróżnij bank
+40034=Odrzuć
+40035=Liczba dni, w których mogę pracować:
+40036=Godziny, w których mogę pracować:
+40037=Złoto przechowywane dla Ciebie:
+40038=Złoto przechowywane na moim koncie:
+40039=Utrzymanie przez sprzedawcę:
+40040=Moja opłata za okres wynosi:
+40041=Godziny okresu rozliczeniowego:
+40042=Całkowita opłata za przedmiot:
+40043=Nieograniczony
+40044=Wyłączony
+40045=GŁÓWNY
+40046=DOSTOSOWANIE PRZEZ SPRZEDAWCĘ MENU
+40047=WŁOSY
+40048=Zmień nazwę handlarza
+40049=Zmień nazwę sklepu
+40050=Wpisz nową nazwę handlarza.
+40051=Wpisz nową nazwę sklepu.
+40052=Wpisz ilość złota, którą chcesz wpłacić.
+40053=To nie jest poprawny handlarz gracza.
+40054=Twój handlarz nazywa się teraz: %s
+40055=Ta nazwa sklepu jest zbyt długa. Maksymalnie 40 znaków.
+40056=Tytuł twojego sklepu brzmi teraz: %s
+40057=Musisz wpisać poprawną ilość złota.
+40058=Krótka broda i wąsy
+40059=Nie możesz robić zakupów w tym domu, ponieważ otrzymałeś zakaz wstępu do tego lokalu.
+40068=Długa broda i Wąsy
+40069=Kozia bródka
+40071=Usuń włosy
+40072=Kolor włosów
+40073=BRODA
+40074=Usuń brodę
+40075=Kolor brody
+40076=Zmień na mężczyznę
+40077=Zmień na kobietę
+40078=Zmień na człowieka
+40079=Zmień na elfa
+40080=Zmień ubranie
+40081=ZAMKNIJ
+40082=KOLOR WŁOSÓW
+40083=KOLOR BRODY
+40084=Włosy - %s
+40085=Broda - %s
+40086=MENU ODZIEŻY U DOSTAWCY
+40087=Kolor
+40088=Usuń
+40089=KOLOR KATEGORIA
+40090=GOTOWE
+40091=KOLOR ODZIEŻY
+40100=Specjalny fioletowy
+40101=Specjalny czerwony
+40102=Specjalny pomarańczowy
+40103=Specjalny żółty
+40104=Specjalny limonkowy
+40105=Specjalny ciemnolimonkowy
+40106=Specjalny lodowoniebieski
+40107=Specjalny lodowobiały
}
EOF
diff --git a/data/dictionaries/dictionary.PTG b/data/dictionaries/dictionary.PTG
index 8b9aabf8a..84ede1e46 100644
--- a/data/dictionaries/dictionary.PTG
+++ b/data/dictionaries/dictionary.PTG
@@ -5731,5 +5731,97 @@
30003=O Campeão caiu!
30004=Foste recompensado com um Pergaminho de Poder!
30005=A energia do santuário diminui sem desafiantes presentes...
+// [ 40000 - 40200 ] Vendedor do Jogador
+40000=%s já não pode suportar a manutenção e foi dispensado.
+40001=Não foi possível criar a escritura do vendedor. Dispensa do vendedor abortada.
+40002=Artigos do vendedor devolvidos - banco: %i, chão: %i.
+40003=Artigos do vendedor devolvidos - mochila: %i, banco: %i, chão: %i.
+40004=Devolvido %i de ouro para a sua mochila.
+40005=Devolvido %i de ouro para o seu cofre no banco.
+40006=Depositado %i de ouro no local do vendedor.
+40007=Escritura do vendedor colocada na sua mochila.
+40008=Escritura do vendedor colocada no seu cofre no banco.
+40009=Escritura do vendedor depositada no local do vendedor.
+40010=%s não tinha condições para suportar os custos de manutenção e foi despedido.
+40011=Deixou %i artigo(s) do vendedor no local do vendedor.
+40012=Este vendedor não tem ouro para recolher.
+40013=O seu ATM não pode receber pagamentos de vendedores no momento.
+40014=Não foi possível criar um cheque bancário para o ouro do vendedor.
+40015=Coletou %i de ouro como um cheque bancário no seu ATM.
+40016=Não foi possível depositar o ouro do vendedor no seu ATM.
+40017=Coletou %i de ouro no seu ATM.
+40018=Valor de depósito inválido.
+40019=Não tem mochila.
+40020=Não tem ouro suficiente na sua mochila.
+40021=A conta bancária do vendedor está cheia.
+40022=Não foi possível remover o ouro do seu pacote. Depósito cancelado.
+40023=Depositado %i de ouro na conta bancária do fornecedor.
+40024=O saldo bancário do fornecedor atingiu o máximo permitido.
+40025=A conta bancária do fornecedor está vazia.
+40026=Movido %i de ouro da conta bancária do fornecedor para o ouro retido.
+40027=Não foi possível criar a escritura do fornecedor. Dispensa cancelada.
+40028=Eu não trabalho para ti!
+40029=Ver mercadorias
+40030=Personalizar
+40031=Coletar Ouro
+40032=Depositar Ouro
+40033=Levantar dinheiro
+40034=Dispensar
+40035=Nº de dias que posso trabalhar:
+40036=Horas em que posso trabalhar:
+40037=Ouro reservado para si:
+40038=Ouro mantido na minha conta:
+40039=Manutenção do fornecedor:
+40040=A minha faturação por período é:
+40041=Horas do período de cobrança:
+40042=Total da taxa do artigo:
+40043=Ilimitado
+40044=Desativado
+40045=PRINCIPAL
+40046=PERSONALIZAÇÃO DO FORNECEDOR MENU
+40047=CABELO
+40048=Renomear Vendedor
+40049=Renomear Loja
+40050=Introduza um novo nome para o vendedor.
+40051=Introduza um novo nome para a loja.
+40052=Introduza a quantidade de ouro que pretende depositar.
+40053=Este não é um vendedor de jogadores válido.
+40054=O seu vendedor chama-se agora: %s
+40055=O nome da loja é demasiado longo. Máximo de 40 caracteres.
+40056=O nome da sua loja é agora: %s
+40057=Tem de introduzir uma quantidade de ouro válida.
+40058=Barba e Bigode Curtos
+40059=Não pode comprar nesta casa, pois foi banido deste estabelecimento.
+40068=Barba comprida e Bigode
+40069=Cavahaque
+40071=Remover Cabelo
+40072=Colorir Cabelo
+40073=Barba
+40074=Remover Barba
+40075=Colorir Barba
+40076=Mudar para Masculino
+40077=Mudar para Feminino
+40078=Mudar para Humano
+40079=Mudar para Elfo
+40080=Mudar Roupa
+40081=FECHAR
+40082=COR DO CABELO
+40083=COR DA BARBA
+40084=Cabelo - %s
+40085=Barba - %s
+40086=MENU DE VESTUÁRIO DO FORNECEDOR
+40087=Colorir
+40088=Remover
+40089=COR CATEGORIA
+40090=CONCLUÍDO
+40091=COR DA ROUPA
+40100=Roxo Especial
+40101=Vermelho Especial
+40102=Laranja Especial
+40103=Amarelo Especial
+40104=Verde Limão Especial
+40105=Verde Limão Escuro Especial
+40106=Azul Gelo Especial
+40107=Branco Gelo Especial
}
EOF
diff --git a/data/dictionaries/dictionary.SPA b/data/dictionaries/dictionary.SPA
index 3d415c99b..5aee93790 100644
--- a/data/dictionaries/dictionary.SPA
+++ b/data/dictionaries/dictionary.SPA
@@ -5730,5 +5730,95 @@
30003=¡El Campeón ha caído!
30004=¡Has sido recompensado con un Pergamino de Poder!
30005=La energía del santuario disminuye al no haber desafiantes presentes...
+// [ 40000 - 40200 ] Vendedor Jugador
+40000=%s ya no puede costear el mantenimiento y ha sido despedido.
+40001=No se pudo crear la escritura de vendedor. Se canceló la destitución del vendedor.
+40002=Objetos de vendedor devueltos - banco: %i, suelo: %i.
+40003=Objetos de vendedor devueltos - paquete: %i, banco: %i, suelo: %i.
+40004=Se devolvieron %i de oro a tu paquete.
+40005=Se devolvieron %i de oro a tu caja del banco.
+40006=Se dejó %i de oro en la ubicación del vendedor.
+40007=Se colocó la escritura de vendedor en tu paquete.
+40008=Se colocó la escritura de vendedor en tu caja del banco.
+40009=Se dejó la escritura de vendedor en la ubicación del vendedor.
+40010=%s no pudo costear el mantenimiento y fue despedido.
+40011=Se dejaron %i objetos de vendedor en la ubicación del vendedor.
+40012=Este vendedor no tiene oro disponible para recolectar.
+40013=Tu caja bancaria no puede recibir pagos de vendedor en este momento.
+40014=No se puede crear un cheque bancario para el oro del vendedor.
+40015=Se recaudó %i de oro como cheque bancario en tu caja bancaria.
+40016=No se puede depositar oro de vendedor en tu caja bancaria.
+40017=Se recaudó %i de oro en tu caja bancaria.
+40018=Importe de depósito no válido.
+40019=No tienes mochila.
+40020=No tienes suficiente oro en tu mochila.
+40021=La cuenta bancaria del vendedor está llena. 40022=No se pudo retirar oro de tu paquete. Depósito cancelado.
+40023=Se depositó %i de oro en la cuenta bancaria del vendedor.
+40024=El banco del vendedor alcanzó su saldo máximo.
+40025=La cuenta bancaria del vendedor está vacía.
+40026=Se transfirió %i de oro de la cuenta bancaria del vendedor al oro retenido.
+40027=No se pudo crear la escritura del vendedor. Descartar cancelado.
+40028=¡No trabajo para ti! 40029=Ver artículos
+40030=Personalizar
+40031=Recoger oro
+40032=Depositar oro
+40033=Fluir banco
+40034=Descartar
+40035=Días que puedo trabajar:
+40036=Horas que puedo trabajar:
+40037=Oro guardado para ti:
+40038=Oro guardado en mi cuenta:
+40039=Mantenimiento del vendedor:
+40040=Mi cargo por período es:
+40041=Horas del período de cobro:
+40042=Total del cargo por artículo:
+40043=Ilimitado
+40044=Desactivado
+40045=PRINCIPAL
+40046=PERSONALIZACIÓN DEL VENDEDOR MENÚ
+40047=CABELLO
+40048=Cambiar nombre de vendedor
+40049=Cambiar nombre de tienda
+40050=Introduce un nuevo nombre de vendedor.
+40051=Introduce un nuevo nombre de tienda.
+40052=Introduce la cantidad de oro que deseas depositar.
+40053=Ese vendedor no es válido para el jugador.
+40054=Tu vendedor ahora se llama: %s
+40055=El nombre de la tienda es demasiado largo. Máximo 40 caracteres.
+40056=El nombre de tu tienda ahora es: %s
+40057=Debes introducir una cantidad válida de oro.
+40058=Barba y bigote cortos
+40059=No puedes comprar en esta casa porque has sido expulsado del establecimiento.
+40068=Barba larga y bigote Bigote
+40069=Perilla
+40071=Quitar pelo
+40072=Color de pelo
+40073=BARBA
+40074=Quitar barba
+40075=Color de barba
+40076=Cambiar a masculino
+40077=Cambiar a femenino
+40078=Cambiar a humano
+40079=Cambiar a elfo
+40080=Cambiar ropa
+40081=CERRAR
+40082=COLOR DE PELO
+40083=COLOR DE BARBA
+40084=Cabello - %s
+40085=Barba - %s
+40086=MENÚ DE ROPA DEL VENDEDOR
+40087=Color
+40088=Quitar
+40089=COLOR CATEGORÍA
+40090=HECHO
+40091=COLOR DE LA ROPA
+40100=Morado especial
+40101=Rojo especial
+40102=Naranja especial
+40103=Amarillo especial
+40104=Lima especial
+40105=Lima oscuro especial
+40106=Azul hielo especial
+40107=Blanco hielo especial
}
EOF
diff --git a/data/dictionaries/dictionary.ZRO b/data/dictionaries/dictionary.ZRO
index eadedcad9..755341cbd 100644
--- a/data/dictionaries/dictionary.ZRO
+++ b/data/dictionaries/dictionary.ZRO
@@ -5735,5 +5735,97 @@
30003=The Champion has fallen!
30004=You have been rewarded with a Scroll of Power!
30005=The shrine's energy wanes with no challengers present...
+// [ 40000 - 40200 ] Player Vendor
+40000=%s can no longer afford upkeep and has been dismissed.
+40001=Unable to create vendor deed. Vendor dismissal aborted.
+40002=Vendor items returned - bank: %i, ground: %i.
+40003=Vendor items returned - pack: %i, bank: %i, ground: %i.
+40004=Returned %i gold to your pack.
+40005=Returned %i gold to your bank box.
+40006=Dropped %i gold at vendor location.
+40007=Placed vendor deed in your pack.
+40008=Placed vendor deed in your bank box.
+40009=Dropped vendor deed at vendor location.
+40010=%s could not afford upkeep and was dismissed.
+40011=Dropped %i vendor item(s) at vendor location.
+40012=This vendor has no held gold to collect.
+40013=Your bank box cannot receive vendor payouts right now.
+40014=Unable to create a bank check for vendor gold.
+40015=Collected %i gold as a bank check in your bank box.
+40016=Unable to deposit vendor gold into your bank box.
+40017=Collected %i gold to your bank box.
+40018=Invalid deposit amount.
+40019=You have no backpack.
+40020=You do not have enough gold in your pack.
+40021=Vendor bank account is full.
+40022=Unable to remove gold from your pack. Deposit cancelled.
+40023=Deposited %i gold into vendor bank account.
+40024=Vendor bank reached its maximum balance.
+40025=Vendor bank account is empty.
+40026=Moved %i gold from vendor bank account to held gold.
+40027=Unable to create vendor deed. Dismiss cancelled.
+40028=I don't work for you!
+40029=See goods
+40030=Customize
+40031=Collect Gold
+40032=Deposit Gold
+40033=Flush Bank
+40034=Dismiss
+40035=Amount of days I can work:
+40036=Hours I can work:
+40037=Gold held for you:
+40038=Gold held in my account:
+40039=Vendor upkeep:
+40040=My charge per period is:
+40041=Charge period hours:
+40042=Item fee total:
+40043=Unlimited
+40044=Disabled
+40045=MAIN
+40046=VENDOR CUSTOMIZATION MENU
+40047=HAIR
+40048=Rename Vendor
+40049=Rename Shop
+40050=Enter a new vendor name.
+40051=Enter a new shop name.
+40052=Enter the amount of gold you wish to deposit.
+40053=That is not a valid player vendor.
+40054=Your vendor is now named: %s
+40055=That shop name is too long. Maximum 40 chars.
+40056=Your shop title is now: %s
+40057=You must enter a valid gold amount.
+40058=Short Beard and Mustache
+40059=You can't shop from this home as you have been banned from this establishment.
+40068=Long Beard and Mustache
+40069=Goatee
+40071=Remove Hair
+40072=Color Hair
+40073=BEARD
+40074=Remove Beard
+40075=Color Beard
+40076=Change to Male
+40077=Change to Female
+40078=Change to Human
+40079=Change to Elf
+40080=Change Clothing
+40081=CLOSE
+40082=HAIR COLOR
+40083=BEARD COLOR
+40084=Hair - %s
+40085=Beard - %s
+40086=VENDOR CLOTHING MENU
+40087=Color
+40088=Remove
+40089=COLOR CATEGORY
+40090=DONE
+40091=CLOTHING COLOR
+40100=Special Purple
+40101=Special Red
+40102=Special Orange
+40103=Special Yellow
+40104=Special Lime
+40105=Special Dark Lime
+40106=Special Ice Blue
+40107=Special Ice White
}
EOF
diff --git a/data/js/coreDefinitions.json b/data/js/coreDefinitions.json
index cb3b43f61..36de64c6b 100644
--- a/data/js/coreDefinitions.json
+++ b/data/js/coreDefinitions.json
@@ -4266,6 +4266,12 @@
"cppType": "JSPROP_ENUMANDPERM",
"type": "number"
},
+ {
+ "name": "vendorGoldHeld",
+ "cppName": "CCP_VENDORGOLDHELD",
+ "cppType": "JSPROP_ENUMANDPERM",
+ "type": "number"
+ },
{
"name": "willhunger",
"cppName": "CCP_HUNGERSTATUS",
diff --git a/data/js/definitions.d.ts b/data/js/definitions.d.ts
index 6aa24e82d..e69de29bb 100644
Binary files a/data/js/definitions.d.ts and b/data/js/definitions.d.ts differ
diff --git a/data/js/jse_fileassociations.scp b/data/js/jse_fileassociations.scp
index 8fb66b609..33675fa44 100644
--- a/data/js/jse_fileassociations.scp
+++ b/data/js/jse_fileassociations.scp
@@ -148,6 +148,7 @@
3107=npc/pets/petbonding.js
3108=server/network/0xbf_sendnpcghostmode.js
3109=npc/ai/veterinarian.js
+3110=npc/ai/playervendor.js
// ------------------------
// AI-Stuff
diff --git a/data/js/npc/ai/playervendor.js b/data/js/npc/ai/playervendor.js
new file mode 100644
index 000000000..88fc8da89
--- /dev/null
+++ b/data/js/npc/ai/playervendor.js
@@ -0,0 +1,2063 @@
+///
+// @ts-check
+// Version: 1.1.7
+
+const VendorSettings = {
+ // Max amount of money the vendor can hold in the bank.
+ // Held gold is not included in this limit, but will
+ // be used first to pay upkeep before bank funds are used.
+ get MaxFunds() { return GetServerSetting( "VendorMaxFunds" ); },
+ // If true, returned vendor belongings skip player backpack entirely.
+ // Items, gold and vendor deed will try bank box first, then drop at vendor location.
+ // If false, belongings try backpack first, then bank box, then ground.
+ get OnlyReturnToBank() { return GetServerSetting( "onlyReturnToBank" ); },
+ get ChargesEnabled() { return GetServerSetting( "VendorChargesEnabled" ); },// Master toggle for vendor upkeep charges
+ get BaseCharge() { return GetServerSetting( "VendorBaseCharge" ); },// Flat fee per charge period
+ get ChargeHours() { return GetServerSetting( "VendorChargeHours" ); },// Charge every X real hours
+ get ItemFeesEnabled() { return GetServerSetting( "VendorUseItemFeesEnabled" ); },// Add item-based fee from listed item prices
+ get ItemFeeDivisor() { return GetServerSetting( "VendorItemFeeDivisor" ); },// 3 gold per 500 worth of one item
+ get ItemFeeAmount() { return GetServerSetting( "VendorItemFeeAmount" ); } // Fee added per divisor step
+}
+
+const VendorTimer = {
+ Upkeep: 90
+};
+
+const VendorEquipmentLayer = {
+ OneHand: 0x01,
+ TwoHand: 0x02,
+ Shoes: 0x03,
+ Pants: 0x04,
+ Shirt: 0x05,
+ Helm: 0x06,
+ Gloves: 0x07,
+ Ring: 0x08,
+ Neck: 0x0A,
+ Hair: 0x0B,
+ Waist: 0x0C,
+ InnerTorso: 0x0D,
+ Bracelet: 0x0E,
+ Beard: 0x10,
+ MiddleTorso: 0x11,
+ Earrings: 0x12,
+ Arms: 0x13,
+ Cloak: 0x14,
+ OuterTorso: 0x16,
+ OuterLegs: 0x17
+};
+
+const vendorHairHueCategories = [
+ { name: null, hues: [ 0x044E, 0x044F, 0x0450, 0x0451, 0x0452, 0x0453, 0x0454 ], dictID: 17111 }, // Black
+ { name: null, hues: [ 0x0455, 0x0456, 0x0457, 0x0458, 0x0459, 0x045A, 0x045B, 0x045C ], dictID: 17112 }, // Beige
+ { name: null, hues: [ 0x045D, 0x045E, 0x045F, 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, 0x0468, 0x0469, 0x046A, 0x046B, 0x046C ], dictID: 17113 }, // Golden
+ { name: null, hues: [ 0x046D, 0x046E, 0x046F, 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478, 0x0479, 0x047A, 0x047B, 0x047C ], dictID: 17114 }, // Dark Brown
+ { name: null, hues: [ 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7, 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF, 0x04C0 ], dictID: 17108 }, // Red
+ { name: null, hues: [ 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC ], dictID: 17105 }, // Auburn
+ { name: null, hues: [ 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0657, 0x0658, 0x0659, 0x065A, 0x065B, 0x065C, 0x065D, 0x065E, 0x065F, 0x0660 ], dictID: 17103 }, // Brown
+ { name: null, hues: [ 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9, 0x0961, 0x0962, 0x0963, 0x0964, 0x0965, 0x0966, 0x0967, 0x0968 ], dictID: 17110 }, // Blonde
+ { name: null, hues: [ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ], dictID: 40100 }, // Special Purple
+ { name: null, hues: [ 32, 33, 34, 35, 36, 37 ], dictID: 40101 }, // Special Red
+ { name: null, hues: [ 38, 39, 40, 41, 42, 43, 44, 45, 46 ], dictID: 40102 }, // Special Orange
+ { name: null, hues: [ 54, 55, 56, 57 ], dictID: 40103 }, // Special Yellow
+ { name: null, hues: [ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72 ], dictID: 40104 }, // Special Lime
+ { name: null, hues: [ 81, 82, 83 ], dictID: 40105 }, // Special Dark Lime
+ { name: null, hues: [ 89, 90, 91 ], dictID: 40106 }, // Special Ice Blue
+ { name: null, hues: [ 1153, 1154, 1155 ], dictID: 40107 } // Special Ice White
+];
+
+const femaleElfHairStyles = [
+ { itemID: 0x2FCC, name: null, dictID: 18035 }, // Flower
+ { itemID: 0x2FC0, name: null, dictID: 18028 }, // Long Feather
+ { itemID: 0x2FC1, name: null, dictID: 18029 }, // Short
+ { itemID: 0x2FC2, name: null, dictID: 18030 }, // Mullet
+ { itemID: 0x2FCE, name: null, dictID: 18032 }, // Topknot
+ { itemID: 0x2FCF, name: null, dictID: 18033 }, // Long Braid
+ { itemID: 0x2FD0, name: null, dictID: 18036 }, // Buns
+ { itemID: 0x2FD1, name: null, dictID: 18034 } // Spiked
+];
+
+const maleElfHairStyles = [
+ { itemID: 0x2FBF, name: null, dictID: 18027 }, // Mid Long
+ { itemID: 0x2FC0, name: null, dictID: 18028 }, // Long Feather
+ { itemID: 0x2FC1, name: null, dictID: 18029 }, // Short
+ { itemID: 0x2FC2, name: null, dictID: 18030 }, // Mullet
+ { itemID: 0x2FCE, name: null, dictID: 18032 }, // Topknot
+ { itemID: 0x2FCF, name: null, dictID: 18033 }, // Long Braid
+ { itemID: 0x2FCD, name: null, dictID: 18031 }, // Long
+ { itemID: 0x2FD1, name: null, dictID: 18034 } // Spiked
+];
+
+const humanHairStyles = [
+ { itemID: 0x203B, name: null, dictID: 18029 }, // Short
+ { itemID: 0x203C, name: null, dictID: 18031 }, // Long
+ { itemID: 0x203D, name: null, dictID: 2086 }, // Ponytail
+ { itemID: 0x2044, name: null, dictID: 2087 }, // Mohawk
+ { itemID: 0x2045, name: null, dictID: 2080 }, // Pageboy
+ { itemID: 0x204A, name: null, dictID: 18032 }, // Topknot
+ { itemID: 0x2047, name: null, dictID: 2088 }, // Curly
+ { itemID: 0x2048, name: null, dictID: 2081 }, // Receding
+ { itemID: 0x2049, name: null, dictID: 2082 } // 2-Tails
+];
+
+const humanBeardStyles = [
+ { itemID: 0x2041, name: null, dictID: 18001 }, // Mustache
+ { itemID: 0x203F, name: null, dictID: 18002 }, // Short Beard
+ { itemID: 0x204B, name: null, dictID: 40058 }, // Short Beard and Mustache
+ { itemID: 0x203E, name: null, dictID: 18005 }, // Long Beard
+ { itemID: 0x204C, name: null, dictID: 40068 }, // Long Beard and Mustache
+ { itemID: 0x2040, name: null, dictID: 40069 }, // Goatee
+ { itemID: 0x204D, name: null, dictID: 18000 } // Vandyke
+];
+
+const vendorClothingCategories = [
+ { layer: VendorEquipmentLayer.InnerTorso, name: "Upper Torso", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x1517, name: "Shirt" },
+ { itemID: 0x1EFD, name: "Fancy Shirt" },
+ { itemID: 0x1F01, name: "Plain Dress" },
+ { itemID: 0x1EFF, name: "Fancy Dress" },
+ { itemID: 0x1F03, name: "Robe" }
+ ]},
+
+ { layer: VendorEquipmentLayer.MiddleTorso, name: "Over Chest", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x1F7B, name: "Doublet" },
+ { itemID: 0x1FA1, name: "Tunic" },
+ { itemID: 0x1F9F, name: "Jester Suit" },
+ { itemID: 0x1541, name: "Body Sash" },
+ { itemID: 0x1FFD, name: "Surcoat" },
+ { itemID: 0x153B, name: "Half Apron" },
+ { itemID: 0x153D, name: "Full Apron" }
+ ]},
+
+ { layer: VendorEquipmentLayer.Shoes, name: "Footwear", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x170D, name: "Sandals" },
+ { itemID: 0x1710, name: "Shoes" },
+ { itemID: 0x170B, name: "Boots" },
+ { itemID: 0x1711, name: "Thigh Boots" }
+ ]},
+
+ { layer: VendorEquipmentLayer.Helm, name: "Hats", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x1544, name: "Skull Cap" },
+ { itemID: 0x1540, name: "Bandana" },
+ { itemID: 0x1713, name: "Floppy Hat" },
+ { itemID: 0x1714, name: "Wide Brim Hat" },
+ { itemID: 0x1715, name: "Cap" },
+ { itemID: 0x1716, name: "Tall Straw Hat" },
+ { itemID: 0x1717, name: "Straw Hat" },
+ { itemID: 0x1718, name: "Wizard Hat" },
+ { itemID: 0x1719, name: "Bonnet" },
+ { itemID: 0x171A, name: "Feathered Hat" },
+ { itemID: 0x171B, name: "Tricorne Hat" },
+ { itemID: 0x171C, name: "Jester Hat" }
+ ]},
+
+ { layer: VendorEquipmentLayer.Pants, name: "Lower Torso", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x1539, name: "Long Pants" },
+ { itemID: 0x1537, name: "Kilt" },
+ { itemID: 0x1516, name: "Skirt" }
+ ]},
+
+ { layer: VendorEquipmentLayer.Cloak, name: "Back", canDye: true, compactLayout: false, entries: [
+ { itemID: 0x1515, name: "Cloak" }
+ ]},
+
+ { layer: VendorEquipmentLayer.OneHand, name: "Held Items", canDye: false, compactLayout: true, entries: [
+ { itemID: 0x0DBF, name: "Fishing Pole" },
+ { itemID: 0x0E86, name: "Pickaxe" },
+ { itemID: 0x0E87, name: "Pitchfork" },
+ { itemID: 0x0EC3, name: "Cleaver" },
+ { itemID: 0x0F5C, name: "Mace" },
+ { itemID: 0x0F6B, name: "Torch" },
+ { itemID: 0x102A, name: "Hammer" },
+ { itemID: 0x0F61, name: "Longsword" },
+ { itemID: 0x13F8, name: "Gnarled Staff" },
+ { itemID: 0x0F4F, name: "Crossbow" },
+ { itemID: 0x1407, name: "War Mace" },
+ { itemID: 0x1443, name: "Two-Handed Axe" },
+ { itemID: 0x0F62, name: "Spear" },
+ { itemID: 0x13FF, name: "Katana" },
+ { itemID: 0x0EFA, name: "Spellbook" }
+ ]}
+];
+
+/** @param {VendorHueCategory} entry @param {Socket|null} socket @returns {string} */
+function GetVendorHueCategoryName( entry, socket )
+{
+ if( entry.dictID && socket )
+ return GetDictionaryEntry( entry.dictID, socket.language );
+
+ return entry.name || "";
+}
+
+/** @param {HairOrBeard} entry @param {Socket} socket @returns {string} */
+function GetHairOrBeardName( entry, socket )
+{
+ if( entry.dictID && socket )
+ return GetDictionaryEntry( entry.dictID, socket.language );
+
+ return entry.name || "";
+}
+
+/** @param {Item} itemObj @returns {number} */
+function GetVendorItemPeriodFee( itemObj )
+{
+ if( itemObj.vendorPrice <= 0 || !VendorSettings.ItemFeesEnabled || VendorSettings.ItemFeeDivisor <= 0 || VendorSettings.ItemFeeAmount <= 0 )
+ return 0;
+
+ return Math.floor( itemObj.vendorPrice / VendorSettings.ItemFeeDivisor ) * VendorSettings.ItemFeeAmount;
+}
+
+/** @type { ( vendor: Character ) => number } */
+function GetVendorItemFeeTotal( vendor )
+{
+ if( vendor.aitype != 17 || !ValidateObject( vendor.pack ) )
+ return 0;
+
+ var totalFee = 0;
+ var packItem = vendor.pack.FirstItem();
+ for( ; !vendor.pack.FinishedItems(); packItem = vendor.pack.NextItem() )
+ {
+ if( ValidateObject( packItem ))
+ totalFee += GetVendorItemPeriodFee( packItem );
+ }
+
+ return totalFee;
+}
+
+/** @type { ( vendor: Character ) => number } */
+function GetVendorChargePerPeriod( vendor )
+{
+ if( vendor.aitype != 17 || !VendorSettings.ChargesEnabled )
+ return 0;
+
+ var overrideValue = vendor.GetTag( "VendorChargePerPeriod" );
+ if( overrideValue > 0 )
+ return overrideValue;
+
+ return VendorSettings.BaseCharge + ( VendorSettings.ItemFeesEnabled ? GetVendorItemFeeTotal( vendor ) : 0 );
+}
+
+/** @param {Character} vendor @param {number} amount @returns {number} */
+function DepositVendorBankGold( vendor, amount )
+{
+ if( vendor.aitype != 17 || amount <= 0 )
+ return 0;
+
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ var roomLeft = VendorSettings.MaxFunds - bankGold;
+ if( roomLeft <= 0 )
+ return 0;
+
+ if( amount > roomLeft )
+ amount = roomLeft;
+
+ vendor.SetTag( "VendorBankAccount", bankGold + amount );
+ return amount;
+}
+
+/** @param {Character} vendor @param {number} amount @returns {number} */
+function WithdrawVendorBankGold( vendor, amount )
+{
+ if( vendor.aitype != 17 || amount <= 0 )
+ return 0;
+
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ if( amount > bankGold )
+ amount = bankGold;
+
+ vendor.SetTag( "VendorBankAccount", bankGold - amount );
+ return amount;
+}
+
+/** @param {Character} vendor @param {number} charge @returns {boolean} */
+function PayVendorCharge( vendor, charge )
+{
+ if( vendor.aitype != 17 || charge <= 0 )
+ return false;
+
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ var heldGold = vendor.vendorGoldHeld || 0;
+
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ if(( bankGold + heldGold ) < charge )
+ return false;
+
+ if( bankGold >= charge )
+ {
+ vendor.SetTag( "VendorBankAccount", bankGold - charge );
+ return true;
+ }
+
+ charge -= bankGold;
+ vendor.SetTag( "VendorBankAccount", 0 );
+ vendor.vendorGoldHeld = heldGold - charge;
+ return true;
+}
+
+/** @param {Character} vendor @returns {number} */
+function FlushVendorBankToHeldGold( vendor )
+{
+ if( vendor.aitype != 17 )
+ return 0;
+
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold <= 0 )
+ return 0;
+
+ vendor.SetTag( "VendorBankAccount", 0 );
+ vendor.vendorGoldHeld = ( vendor.vendorGoldHeld || 0 ) + bankGold;
+ return bankGold;
+}
+
+/** @param {Character} vendor @returns {number} */
+function GetVendorPeriodsAffordable( vendor )
+{
+ var perPeriod = GetVendorChargePerPeriod( vendor );
+ if( perPeriod <= 0 )
+ return 0;
+
+ var bankGold = 0;
+ var heldGold = 0;
+
+ if( vendor.aitype == 17 )
+ {
+ bankGold = vendor.GetTag( "VendorBankAccount" );
+ heldGold = vendor.vendorGoldHeld || 0;
+
+ if( bankGold < 0 )
+ bankGold = 0;
+ }
+
+ return Math.floor(( bankGold + heldGold ) / perPeriod );
+}
+
+/** @param {Character} vendor @returns {number} */
+function GetVendorHoursAffordable( vendor )
+{
+ return GetVendorPeriodsAffordable( vendor ) * VendorSettings.ChargeHours;
+}
+
+/** @param {Character} vendor @returns {number} */
+function GetVendorDaysAffordable( vendor )
+{
+ return Math.floor( GetVendorHoursAffordable( vendor ) / 24 );
+}
+
+/** @param {Item} itemObj @param {Character} vendor */
+function DropItemAtVendorLocation( itemObj, vendor )
+{
+ itemObj.container = null;
+ itemObj.x = vendor.x;
+ itemObj.y = vendor.y;
+ itemObj.z = vendor.z;
+ itemObj.worldnumber = vendor.worldnumber;
+ itemObj.instanceID = vendor.instanceID;
+}
+
+/** @param {Item} itemObj @param {Item} container @returns {boolean} */
+function TryMoveItemToContainer( itemObj, container )
+{
+ if( !ValidateObject( container ) )
+ return false;
+
+ if( container.totalItemCount >= container.maxItems )
+ return false;
+
+ if(( container.weight + ( itemObj.weight * itemObj.amount ) ) > container.weightMax )
+ return false;
+
+ itemObj.container = container;
+ return ValidateObject( itemObj.container ) && itemObj.container.serial == container.serial;
+}
+
+/** @param {Item} itemObj @param {Character} pUser @param {Character} vendor @returns {number} */
+function PlaceItemForOwnerOrDrop( itemObj, pUser, vendor )
+{
+ var pack = pUser.pack;
+ var bankBox = pUser.FindItemLayer( 29 );
+
+ if( VendorSettings.OnlyReturnToBank )
+ {
+ if( TryMoveItemToContainer( itemObj, bankBox ) )
+ return 2;
+
+ if( TryMoveItemToContainer( itemObj, pack ) )
+ return 1;
+ }
+ else
+ {
+ if( TryMoveItemToContainer( itemObj, pack ) )
+ return 1;
+
+ if( TryMoveItemToContainer( itemObj, bankBox ) )
+ return 2;
+ }
+
+ DropItemAtVendorLocation( itemObj, vendor );
+ return 3;
+}
+
+/** @param {Character} vendor @param {Character} pUser @returns {{pack:number, bank:number, ground:number}} */
+function ReturnVendorItemsToOwner( vendor, pUser )
+{
+ var results = { pack:0, bank:0, ground:0 };
+
+ if( vendor.aitype != 17 || !ValidateObject( vendor.pack ))
+ return results;
+
+ var itemList = [];
+ var packItem = vendor.pack.FirstItem();
+ for( ; !vendor.pack.FinishedItems(); packItem = vendor.pack.NextItem() )
+ {
+ if( ValidateObject( packItem ) )
+ itemList.push( packItem );
+ }
+
+ for( var i = 0; i < itemList.length; ++i )
+ {
+ var placeResult = PlaceItemForOwnerOrDrop( itemList[i], pUser, vendor );
+ if( placeResult == 1 )
+ results.pack++;
+ else if( placeResult == 2 )
+ results.bank++;
+ else if( placeResult == 3 )
+ results.ground++;
+ }
+
+ return results;
+}
+
+/** @param {Character} vendor @returns {number} */
+function DropVendorItemsAtLocation( vendor )
+{
+ if( vendor.aitype != 17 || !ValidateObject( vendor.pack ) )
+ return 0;
+
+ var itemList = [];
+ var packItem = vendor.pack.FirstItem();
+ for( ; !vendor.pack.FinishedItems(); packItem = vendor.pack.NextItem() )
+ {
+ if( ValidateObject( packItem ))
+ itemList.push( packItem );
+ }
+
+ for( var i = 0; i < itemList.length; ++i )
+ {
+ DropItemAtVendorLocation( itemList[i], vendor );
+ }
+
+ return itemList.length;
+}
+
+/** @param {Character} vendor @returns {Item|null} */
+function CreateVendorDismissDeedAtLocation( vendor )
+{
+ if( vendor.aitype != 17 )
+ return null;
+
+ var deed = CreateBlankItem( null, vendor, 1, "A vendor deed for " + vendor.name + ".", 0x14F0, 0x0, "ITEM", false );
+ if( !ValidateObject( deed ) )
+ return null;
+
+ deed.type = 217;
+ deed.buyvalue = 2000;
+ deed.SetTag( "vendorName", vendor.name );
+ DropItemAtVendorLocation( deed, vendor );
+ return deed;
+}
+
+/** @param {Character} pUser @param {Character} vendor @returns {Item|null} */
+function CreateVendorDismissDeedForOwner( pUser, vendor )
+{
+ if( vendor.aitype != 17 )
+ return null;
+
+ var deedName = "A vendor deed for " + vendor.name + ".";
+ if( pUser.socket )
+ {
+ deedName = GetDictionaryEntry( 580, pUser.socket.language );
+ deedName = deedName.replace( /%s/gi, vendor.name );
+ }
+
+ var deed = CreateBlankItem( null, pUser, 1, deedName, 0x14F0, 0x0, "ITEM", false );
+ if( !ValidateObject( deed ) )
+ return null;
+
+ deed.type = 217;
+ deed.buyvalue = 2000;
+ deed.SetTag( "vendorName", vendor.name );
+ return deed;
+}
+
+/** @param {Character} vendor @returns {number} */
+function DropVendorGoldAtLocation( vendor )
+{
+ if( vendor.aitype != 17 )
+ return 0;
+
+ var heldGold = vendor.vendorGoldHeld || 0;
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ var totalGold = heldGold + bankGold;
+ if( totalGold <= 0 )
+ return 0;
+
+ var goldItem = CreateDFNItem( null, vendor, "0x0EED", totalGold, "ITEM", false );
+ if( !ValidateObject( goldItem ) )
+ return 0;
+
+ vendor.vendorGoldHeld = 0;
+ vendor.SetTag( "VendorBankAccount", 0 );
+ DropItemAtVendorLocation( goldItem, vendor );
+ return totalGold;
+}
+
+/** @param {Character} pUser @param {Character} vendor @returns {{amount:number, location:number}} */
+function ReturnVendorGoldToOwner( pUser, vendor )
+{
+ var result = { amount:0, location:0 };
+
+ if( vendor.aitype != 17 )
+ return result;
+
+ var heldGold = vendor.vendorGoldHeld || 0;
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ var totalGold = heldGold + bankGold;
+ if( totalGold <= 0 )
+ return result;
+
+ var goldItem = CreateDFNItem( null, pUser, "0x0EED", totalGold, "ITEM", false );
+ if( !ValidateObject( goldItem ) )
+ return result;
+
+ var placeResult = PlaceItemForOwnerOrDrop( goldItem, pUser, vendor );
+ if( placeResult == 0 )
+ return result;
+
+ vendor.vendorGoldHeld = 0;
+ vendor.SetTag( "VendorBankAccount", 0 );
+
+ result.amount = totalGold;
+ result.location = placeResult;
+ return result;
+}
+
+/** @param {Character} vendor */
+function FinalizeVendorRemoval( vendor )
+{
+ if( vendor.aitype != 17 )
+ return;
+
+ if( ValidateObject( vendor.multi ) )
+ vendor.multi.RemoveVendor( vendor );
+
+ vendor.Delete();
+}
+
+/** @param {Character} vendor @param {Character|null} notifyChar */
+function RemoveVendorForNoFunds( vendor, notifyChar )
+{
+ if( vendor.aitype != 17 )
+ return;
+
+ var ownerChar = ValidateObject( vendor.owner ) ? vendor.owner : null;
+ if( ValidateObject( ownerChar ) && ownerChar.socket )
+ {
+ var ownerSocket = ownerChar.socket;
+ ownerSocket.SysMessage( GetDictionaryEntry( 40000, ownerSocket.language ), vendor.name );
+
+ var deed = CreateVendorDismissDeedForOwner( ownerChar, vendor );
+ if( !ValidateObject( deed ))
+ {
+ ownerSocket.SysMessage( GetDictionaryEntry( 40001, ownerSocket.language ) );
+ return;
+ }
+
+ var itemResults = ReturnVendorItemsToOwner( vendor, ownerChar );
+ var goldResult = ReturnVendorGoldToOwner( ownerChar, vendor );
+ var deedLocation = PlaceItemForOwnerOrDrop( deed, ownerChar, vendor );
+
+ if(( itemResults.pack + itemResults.bank + itemResults.ground ) > 0 )
+ {
+ if( VendorSettings.OnlyReturnToBank )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40002, ownerSocket.language ), itemResults.bank, itemResults.ground );
+ else
+ ownerSocket.SysMessage( GetDictionaryEntry( 40003, ownerSocket.language ), itemResults.pack, itemResults.bank, itemResults.ground );
+ }
+
+ if( goldResult.amount > 0 )
+ {
+ if( goldResult.location == 1 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40004, ownerSocket.language ), goldResult.amount );
+ else if( goldResult.location == 2 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40005, ownerSocket.language ), goldResult.amount );
+ else if( goldResult.location == 3 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40006, ownerSocket.language ), goldResult.amount );
+ }
+
+ if( deedLocation == 1 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40007, ownerSocket.language ) );
+ else if( deedLocation == 2 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40008, ownerSocket.language ) );
+ else if( deedLocation == 3 )
+ ownerSocket.SysMessage( GetDictionaryEntry( 40009, ownerSocket.language ) );
+
+ FinalizeVendorRemoval( vendor );
+ return;
+ }
+
+ var itemCount = DropVendorItemsAtLocation( vendor );
+ var totalGold = DropVendorGoldAtLocation( vendor );
+ var offlineDeed = CreateVendorDismissDeedAtLocation( vendor );
+
+ if( notifyChar.socket )
+ {
+ var notifySocket = notifyChar.socket;
+ notifySocket.SysMessage( GetDictionaryEntry( 40010, notifySocket.language ), vendor.name );
+
+ if( itemCount > 0 )
+ notifySocket.SysMessage( GetDictionaryEntry( 40011, notifySocket.language ), itemCount );
+
+ if( totalGold > 0 )
+ notifySocket.SysMessage( GetDictionaryEntry( 40006, notifySocket.language ), totalGold );
+
+ if( ValidateObject( offlineDeed ))
+ notifySocket.SysMessage( GetDictionaryEntry( 40009, notifySocket.language ) );
+ }
+
+ FinalizeVendorRemoval( vendor );
+}
+
+/** @param {Character} vendor @param {Character|null} notifyChar @returns {boolean} */
+function CheckVendorUpkeep( vendor, notifyChar )
+{
+ if( vendor.aitype != 17 )
+ return false;
+
+ if( !VendorSettings.ChargesEnabled )
+ return true;
+
+ var chargeInterval = VendorSettings.ChargeHours * 60 * 60;
+ if( chargeInterval <= 0 )
+ return true;
+
+ var currentTime = Math.floor( GetCurrentClock() / 1000 );
+ var lastChargeTime = vendor.GetTag( "VendorLastChargeTime" );
+
+ if( lastChargeTime <= 0 )
+ {
+ vendor.SetTag( "VendorLastChargeTime", currentTime );
+ return true;
+ }
+
+ if( currentTime <= lastChargeTime )
+ return true;
+
+ var periodsOwed = Math.floor(( currentTime - lastChargeTime ) / chargeInterval );
+ if( periodsOwed <= 0 )
+ return true;
+
+ var costPerPeriod = GetVendorChargePerPeriod( vendor );
+ for( var i = 0; i < periodsOwed; ++i )
+ {
+ if( !PayVendorCharge( vendor, costPerPeriod ) )
+ {
+ RemoveVendorForNoFunds( vendor, notifyChar );
+ return false;
+ }
+ }
+
+ vendor.SetTag( "VendorLastChargeTime", lastChargeTime + ( periodsOwed * chargeInterval ) );
+ return true;
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function StoreVendorForGump( pUser, vendor )
+{
+ if( vendor.aitype == 17 )
+ pUser.SetTempTag( "PlayerVendorGumpSerial", vendor.serial );
+}
+
+/** @param {Character} pUser @returns {Character|null} */
+function GetStoredVendorForGump( pUser )
+{
+ var serial = pUser.GetTempTag( "PlayerVendorGumpSerial" );
+ if( !serial )
+ return null;
+
+ var vendor = CalcCharFromSer( serial );
+ if( !ValidateObject( vendor ) || vendor.aitype != 17 )
+ return null;
+
+ return vendor;
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function OpenVendorBackpack( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket || !ValidateObject( vendor.pack ) )
+ return;
+
+ pUser.socket.OpenContainer( vendor.pack );
+}
+
+/** @param {Character} pUser @returns {number} */
+function GetPackGoldAmount( pUser )
+{
+ if( !ValidateObject( pUser.pack ) )
+ return 0;
+
+ var totalGold = 0;
+ var itemObj = pUser.pack.FirstItem();
+ for( ; !pUser.pack.FinishedItems(); itemObj = pUser.pack.NextItem() )
+ {
+ if( ValidateObject( itemObj ) && itemObj.id == 0x0EED )
+ totalGold += itemObj.amount;
+ }
+
+ return totalGold;
+}
+
+/** @param {Character} pUser @param {number} amount @returns {boolean} */
+function RemoveGoldFromPack( pUser, amount )
+{
+ if( amount <= 0 || !ValidateObject( pUser.pack ) )
+ return false;
+
+ if( GetPackGoldAmount( pUser ) < amount )
+ return false;
+
+ var goldStacks = [];
+ var itemObj = pUser.pack.FirstItem();
+ for( ; !pUser.pack.FinishedItems(); itemObj = pUser.pack.NextItem() )
+ {
+ if( ValidateObject( itemObj ) && itemObj.id == 0x0EED )
+ goldStacks.push( itemObj );
+ }
+
+ for( var i = 0; i < goldStacks.length && amount > 0; ++i )
+ {
+ if( goldStacks[i].amount <= amount )
+ {
+ amount -= goldStacks[i].amount;
+ goldStacks[i].Delete();
+ }
+ else
+ {
+ goldStacks[i].amount -= amount;
+ amount = 0;
+ }
+ }
+
+ return amount <= 0;
+}
+
+/** @type { ( pUser: Character ) => Item|null } */
+function GetBankBox( pUser )
+{
+ var bankBox = pUser.FindItemLayer( 29 );
+ return ValidateObject( bankBox ) ? bankBox : null;
+}
+
+/** @param {Character} pUser @returns {boolean} */
+function CanBankReceiveVendorPayout( pUser )
+{
+ var bankBox = GetBankBox( pUser );
+ if( !ValidateObject( bankBox ) )
+ return false;
+
+ if( bankBox.totalItemCount >= bankBox.maxItems )
+ return false;
+
+ if( bankBox.weight > bankBox.weightMax )
+ return false;
+
+ return true;
+}
+
+/** @param {Character} pUser @param {number} checkSize @returns {Item|null} */
+function CreateVendorBankCheck( pUser, checkSize )
+{
+ if( checkSize <= 0 )
+ return null;
+
+ var bankBox = GetBankBox( pUser );
+ if( !ValidateObject( bankBox ))
+ return null;
+
+ var newCheck = CreateDFNItem( pUser.socket, pUser, "bankcheck", 1, "ITEM", false );
+ if( !ValidateObject( newCheck ))
+ return null;
+
+ newCheck.SetTag( "CheckSize", checkSize );
+ newCheck.AddScriptTrigger( 5015 );
+ newCheck.container = bankBox;
+ return newCheck;
+}
+
+/** @param {Character} pTalking @param {Item} bankBox @param {number} depositAmt */
+function DivideDepositedGold( pTalking, bankBox, depositAmt )
+{
+ var remainingGold = depositAmt;
+ var newGoldPile;
+
+ while( remainingGold > 0 )
+ {
+ if( remainingGold >= 65535 )
+ {
+ newGoldPile = CreateDFNItem( pTalking.socket, pTalking, "0x0EED", 65535, "ITEM", false );
+ remainingGold -= 65535;
+ }
+ else
+ {
+ newGoldPile = CreateDFNItem( pTalking.socket, pTalking, "0x0EED", remainingGold, "ITEM", false );
+ remainingGold = 0;
+ }
+
+ if( ValidateObject( newGoldPile ) )
+ newGoldPile.container = bankBox;
+ }
+}
+
+/** @param {Character} pUser @param {number} goldAmount @returns {boolean} */
+function DepositVendorGoldToBankBox( pUser, goldAmount )
+{
+ var bankBox = GetBankBox( pUser );
+ if( !ValidateObject( bankBox ) || goldAmount <= 0 )
+ return false;
+
+ if( bankBox.totalItemCount >= bankBox.maxItems )
+ return false;
+
+ if( bankBox.weight > bankBox.weightMax )
+ return false;
+
+ DivideDepositedGold( pUser, bankBox, goldAmount );
+ return true;
+}
+
+/** @type { ( pUser: Character, vendor: Character ) => void } */
+function CollectVendorGold( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ var socket = pUser.socket;
+ var heldGold = vendor.vendorGoldHeld || 0;
+
+ if( heldGold <= 0 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40012, socket.language ));
+ return;
+ }
+
+ if( !CanBankReceiveVendorPayout( pUser ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 40013, socket.language ));
+ return;
+ }
+
+ if( heldGold > 2000 )
+ {
+ var vendorCheck = CreateVendorBankCheck( pUser, heldGold );
+ if( !ValidateObject( vendorCheck ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 40014, socket.language ));
+ return;
+ }
+
+ vendor.vendorGoldHeld = 0;
+ socket.SysMessage( GetDictionaryEntry( 40015, socket.language ), heldGold );
+ return;
+ }
+
+ if( !DepositVendorGoldToBankBox( pUser, heldGold ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 40016, socket.language ));
+ return;
+ }
+
+ vendor.vendorGoldHeld = 0;
+ socket.SysMessage( GetDictionaryEntry( 40017, socket.language ), heldGold );
+}
+
+/** @param {Character} pUser @param {Character} vendor @param {number} amount */
+function DepositToVendorBank( pUser, vendor, amount )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ var socket = pUser.socket;
+ if( amount <= 0 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40018, socket.language ));
+ return;
+ }
+
+ if( !ValidateObject( pUser.pack ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 40019, socket.language ));
+ return;
+ }
+
+ if( GetPackGoldAmount( pUser ) < amount )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40020, socket.language ));
+ return;
+ }
+
+ var deposited = DepositVendorBankGold( vendor, amount );
+ if( deposited <= 0 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40021, socket.language ));
+ return;
+ }
+
+ if( !RemoveGoldFromPack( pUser, deposited ))
+ {
+ WithdrawVendorBankGold( vendor, deposited );
+ socket.SysMessage( GetDictionaryEntry( 40022, socket.language ));
+ return;
+ }
+
+ socket.SysMessage( GetDictionaryEntry( 40023, socket.language ), deposited );
+
+ if( deposited < amount )
+ socket.SysMessage( GetDictionaryEntry( 40024, socket.language ));
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function FlushBankToHeld( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ var moved = FlushVendorBankToHeldGold( vendor );
+ if( moved <= 0 )
+ {
+ pUser.socket.SysMessage( GetDictionaryEntry( 40025, pUser.socket.language ));
+ return;
+ }
+
+ pUser.socket.SysMessage( GetDictionaryEntry( 40026, pUser.socket.language ), moved );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function DismissVendor( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ var socket = pUser.socket;
+ var deed = CreateVendorDismissDeedForOwner( pUser, vendor );
+ if( !ValidateObject( deed ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 40027, socket.language ));
+ return;
+ }
+
+ var itemResults = ReturnVendorItemsToOwner( vendor, pUser );
+ var goldResult = ReturnVendorGoldToOwner( pUser, vendor );
+ var deedLocation = PlaceItemForOwnerOrDrop( deed, pUser, vendor );
+
+ socket.SysMessage( GetDictionaryEntry( 581, socket.language ), deed.name );
+
+ if(( itemResults.pack + itemResults.bank + itemResults.ground ) > 0 )
+ {
+ if( VendorSettings.OnlyReturnToBank )
+ socket.SysMessage( GetDictionaryEntry( 40002, socket.language ), itemResults.bank, itemResults.ground );
+ else
+ socket.SysMessage( GetDictionaryEntry( 40003, socket.language ), itemResults.pack, itemResults.bank, itemResults.ground );
+ }
+
+ if( goldResult.amount > 0 )
+ {
+ if( goldResult.location == 1 )
+ socket.SysMessage( GetDictionaryEntry( 40004, socket.language ), goldResult.amount );
+ else if( goldResult.location == 2 )
+ socket.SysMessage( GetDictionaryEntry( 40005, socket.language ), goldResult.amount );
+ else if( goldResult.location == 3 )
+ socket.SysMessage( GetDictionaryEntry( 40006, socket.language ), goldResult.amount );
+ }
+
+ if( deedLocation == 1 )
+ socket.SysMessage( GetDictionaryEntry( 40007, socket.language ) );
+ else if( deedLocation == 2 )
+ socket.SysMessage( GetDictionaryEntry( 40008, socket.language ) );
+ else if( deedLocation == 3 )
+ socket.SysMessage( GetDictionaryEntry( 40009, socket.language ) );
+
+ FinalizeVendorRemoval( vendor );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function CycleVendorDirection( pUser, vendor )
+{
+ if( vendor.aitype != 17 )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ vendor.direction = ( vendor.direction + 1 ) % 8;
+}
+
+/** @param {Character} vendor @param {string} speech @returns {string} */
+function NormalizeVendorSpeech( vendor, speech )
+{
+ if( !speech )
+ return "";
+
+ var cleanedSpeech = ( "" + speech ).toLowerCase().replace( /^\s+|\s+$/g, "" );
+ if( vendor.aitype != 17 || !vendor.name )
+ return cleanedSpeech;
+
+ var vendorName = ( "" + vendor.name ).toLowerCase();
+ if( cleanedSpeech.indexOf( vendorName + " " ) == 0 )
+ return cleanedSpeech.substring( vendorName.length + 1 );
+
+ if( cleanedSpeech.indexOf( vendorName + "," ) == 0 )
+ return cleanedSpeech.substring( vendorName.length + 1 ).replace( /^\s+|\s+$/g, "" );
+
+ return cleanedSpeech;
+}
+
+/** @param {Character} pUser @param {Character} vendor @returns {boolean} */
+function VendorAccessBlocked( pUser, vendor )
+{
+ if( ValidateObject( vendor.multi ) && vendor.multi.IsOnBanList( pUser ))
+ {
+ if( pUser.socket )
+ pUser.socket.SysMessage( GetDictionaryEntry( 40059, pUser.socket.language ));
+ return true;
+ }
+
+ if( !CheckVendorUpkeep( vendor, pUser ))
+ return true;
+
+ return false;
+}
+
+/** @param {Character} pUser @param {Character} vendor @param {string} speech @returns {number} */
+function HandleVendorSpeechCommand( pUser, vendor, speech )
+{
+ if( !ValidateObject( pUser ) || !ValidateObject( vendor ) || vendor.aitype != 17 )
+ return 0;
+
+ if( !pUser.InRange( vendor, 3 ))
+ return 0;
+
+ var cmd = NormalizeVendorSpeech( vendor, speech );
+
+ if( cmd == "view" || cmd == "browse" || cmd == "look" )
+ {
+ if( VendorAccessBlocked( pUser, vendor ))
+ return 2;
+
+ vendor.TextMessage( "Take a look at my goods.");
+ OpenVendorBackpack( pUser, vendor );
+ return 2;
+ }
+
+ if( cmd == "buy" || cmd == "purchase" )
+ {
+ if( VendorAccessBlocked( pUser, vendor ))
+ return 2;
+
+ pUser.BuyFrom( vendor );
+ return 2;
+ }
+
+ if( cmd == "gold" || cmd == "collect" || cmd == "get" )
+ {
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ {
+ vendor.TextMessage( GetDictionaryEntry( 40028 ));
+ return 2;
+ }
+
+ if( !CheckVendorUpkeep( vendor, pUser ))
+ return 2;
+
+ CollectVendorGold( pUser, vendor );
+ return 2;
+ }
+
+ if( cmd == "status" || cmd == "info" )
+ {
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ {
+ vendor.TextMessage( GetDictionaryEntry( 40028 ));
+ return 2;
+ }
+
+ if( !CheckVendorUpkeep( vendor, pUser ))
+ return 2;
+
+ if( pUser.socket )
+ {
+ StoreVendorForGump( pUser, vendor );
+ ShowPlayerVendorOwnerGump( pUser.socket, vendor );
+ }
+ return 2;
+ }
+
+ if( cmd == "dismiss" || cmd == "replace" )
+ {
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ {
+ vendor.TextMessage( GetDictionaryEntry( 40028 ));
+ return 2;
+ }
+
+ DismissVendor( pUser, vendor );
+ return 2;
+ }
+
+ if( cmd == "vendor cycle" || cmd == "cycle" )
+ {
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ {
+ vendor.TextMessage( GetDictionaryEntry( 40028 ));
+ return 2;
+ }
+
+ CycleVendorDirection( pUser, vendor );
+ return 2;
+ }
+
+ return 0;
+}
+
+/** @param {Character} vendor @param {number} layer */
+function RemoveVendorLayerItem( vendor, layer )
+{
+ var layerItem = vendor.FindItemLayer( layer );
+ if( ValidateObject( layerItem ))
+ layerItem.Delete();
+}
+
+/** @param {Character} vendor @param {number} layer @param {number} itemID @param {number} hue @param {string} itemName */
+function SetVendorLayerItem( vendor, layer, itemID, hue, itemName )
+{
+ var oldItem = vendor.FindItemLayer( layer );
+ if( ValidateObject( oldItem ))
+ oldItem.Delete();
+
+ if( itemID == 0 )
+ return;
+
+ var newItem = CreateBlankItem( vendor.socket ? vendor.socket : null, vendor, 1, itemName, itemID, hue, "ITEM", false );
+ if( !ValidateObject( newItem ))
+ return;
+
+ newItem.container = vendor;
+ newItem.layer = layer;
+}
+
+/** @param {Character} vendor @param {number} layer @param {number} hue */
+function SetVendorLayerHue( vendor, layer, hue )
+{
+ var layerItem = vendor.FindItemLayer( layer );
+ if( ValidateObject( layerItem ))
+ layerItem.colour = hue;
+}
+
+/** @param {Character} vendor @param {boolean} isElf @param {boolean} isFemale */
+function SetVendorRaceGenderBody( vendor, isElf, isFemale )
+{
+ if( isElf )
+ vendor.id = isFemale ? 0x025E : 0x025D;
+ else
+ vendor.id = isFemale ? 0x0191 : 0x0190;
+
+ if( isFemale )
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Beard );
+
+ vendor.Teleport();
+}
+
+/** @param {Character} pUser @param {number} categoryIndex */
+function StoreClothingDyeCategory( pUser, categoryIndex )
+{
+ pUser.SetTempTag( "PlayerVendorClothingDyeCategory", categoryIndex );
+}
+
+/** @param {Character} pUser @returns {number} */
+function GetStoredClothingDyeCategory( pUser )
+{
+ var categoryIndex = pUser.GetTempTag( "PlayerVendorClothingDyeCategory" );
+ return ( categoryIndex === null || categoryIndex === undefined || categoryIndex === "" ) ? -1 : categoryIndex;
+}
+
+/** @param {Socket} socket @param {Character} vendor */
+function ShowPlayerVendorOwnerGump( socket, vendor )
+{
+ if( socket == null )
+ return;
+
+ if( vendor.aitype != 17 )
+ return;
+
+ var heldGold = vendor.vendorGoldHeld || 0;
+ var bankGold = vendor.GetTag( "VendorBankAccount" );
+ if( bankGold < 0 )
+ bankGold = 0;
+
+ var perPeriod = GetVendorChargePerPeriod( vendor );
+ var days = GetVendorDaysAffordable( vendor );
+ var hours = GetVendorHoursAffordable( vendor );
+ var itemFeeTotal = GetVendorItemFeeTotal( vendor );
+
+ var myGump = new Gump;
+ myGump.AddPage( 0 );
+ myGump.AddBackground( 25, 10, 555, 240, 5054 );
+ myGump.AddCheckerTrans( 32, 20, 537, 220 );
+ myGump.NoResize();
+
+ myGump.AddButton( 390, 25, 4005, 4007, 1, 0, 1 );
+ myGump.AddHTMLGump( 425, 25, 120, 20, false, false, "" + GetDictionaryEntry( 40029, socket.language ) + "" );
+
+ myGump.AddButton( 390, 48, 4005, 4007, 1, 0, 2 );
+ myGump.AddHTMLGump( 425, 48, 120, 20, false, false, "" + GetDictionaryEntry( 40030, socket.language ) + "" );
+
+ myGump.AddButton( 390, 71, 4005, 4007, 1, 0, 7 );
+ myGump.AddHTMLGump( 425, 72, 120, 20, false, false, "" + GetDictionaryEntry( 40048, socket.language ) + "" );
+
+ myGump.AddButton( 390, 94, 4005, 4007, 1, 0, 8 );
+ myGump.AddHTMLGump( 425, 95, 120, 20, false, false, "" + GetDictionaryEntry( 40049, socket.language ) + "" );
+
+ myGump.AddButton( 390, 117, 4005, 4007, 1, 0, 3 );
+ myGump.AddHTMLGump( 425, 118, 120, 20, false, false, "" + GetDictionaryEntry( 40031, socket.language ) + "" );
+
+ myGump.AddButton( 390, 140, 4005, 4007, 1, 0, 4 );
+ myGump.AddHTMLGump( 425, 141, 120, 20, false, false, "" + GetDictionaryEntry( 40032, socket.language ) + "" );
+
+ myGump.AddButton( 390, 163, 4005, 4007, 1, 0, 5 );
+ myGump.AddHTMLGump( 425, 164, 120, 20, false, false, "" + GetDictionaryEntry( 40033, socket.language ) + "" );
+
+ myGump.AddButton( 390, 186, 4005, 4007, 1, 0, 6 );
+ myGump.AddHTMLGump( 425, 187, 120, 20, false, false, "" + GetDictionaryEntry( 40034, socket.language ) + "" );
+
+ myGump.AddButton( 390, 209, 4005, 4007, 1, 0, 0 );
+ myGump.AddHTMLGump( 425, 210, 120, 20, false, false, "" + GetDictionaryEntry( 18762, socket.language ) + "" );
+
+ if( !VendorSettings.ChargesEnabled )
+ {
+ myGump.AddHTMLGump( 40, 25, 260, 20, false, false, "" + GetDictionaryEntry( 40035, socket.language ) + "" );
+ myGump.AddText( 300, 25, 2603, GetDictionaryEntry( 40043, socket.language ) );
+
+ myGump.AddHTMLGump( 40, 48, 260, 20, false, false, "" + GetDictionaryEntry( 40036, socket.language ) + "" );
+ myGump.AddText( 300, 48, 2603, GetDictionaryEntry( 40043, socket.language ) );
+
+ myGump.AddHTMLGump( 40, 72, 260, 20, false, false, "" + GetDictionaryEntry( 40037, socket.language ) + "" );
+ myGump.AddText( 300, 72, 1153, "" + heldGold );
+
+ myGump.AddHTMLGump( 40, 96, 260, 20, false, false, "" + GetDictionaryEntry( 40038, socket.language ) + "" );
+ myGump.AddText( 300, 96, 1153, "" + bankGold );
+
+ myGump.AddHTMLGump( 40, 120, 260, 20, false, false, "" + GetDictionaryEntry( 40039, socket.language ) + "" );
+ myGump.AddText( 300, 120, 2603, GetDictionaryEntry( 40044, socket.language ) );
+ }
+ else
+ {
+ myGump.AddHTMLGump( 40, 25, 260, 20, false, false, "" + GetDictionaryEntry( 40035, socket.language ) + "" );
+ myGump.AddText( 300, 25, 1153, "" + days );
+
+ myGump.AddHTMLGump( 40, 48, 260, 20, false, false, "" + GetDictionaryEntry( 40036, socket.language ) + "" );
+ myGump.AddText( 300, 48, 1153, "" + hours );
+
+ myGump.AddHTMLGump( 40, 72, 260, 20, false, false, "" + GetDictionaryEntry( 40037, socket.language ) + "" );
+ myGump.AddText( 300, 72, 1153, "" + heldGold );
+
+ myGump.AddHTMLGump( 40, 96, 260, 20, false, false, "" + GetDictionaryEntry( 40038, socket.language ) + "" );
+ myGump.AddText( 300, 96, 1153, "" + bankGold );
+
+ myGump.AddHTMLGump( 40, 120, 260, 20, false, false, "" + GetDictionaryEntry( 40040, socket.language ) + "" );
+ myGump.AddText( 300, 120, 1153, "" + perPeriod );
+
+ myGump.AddHTMLGump( 40, 144, 260, 20, false, false, "" + GetDictionaryEntry( 40041, socket.language ) + "" );
+ myGump.AddText( 300, 144, 1153, "" + VendorSettings.ChargeHours );
+
+ myGump.AddHTMLGump( 40, 168, 260, 20, false, false, "" + GetDictionaryEntry( 40042, socket.language ) + "" );
+ myGump.AddText( 300, 168, 1153, "" + itemFeeTotal );
+ }
+
+ myGump.Send( socket );
+ myGump.Free();
+}
+
+/** @param {Socket} socket @param {Gump} myGump @param {number} menuPage @param {number} categoryPageStart @param {string} title */
+function AddHairHueCategoryMenu( socket, myGump, menuPage, categoryPageStart, title )
+{
+ myGump.AddPage( menuPage );
+ myGump.AddHTMLGump( 10, 42, 350, 18, false, false, "
" + title + "" );
+
+ var columns = 2;
+ var startX = 20;
+ var startY = 70;
+ var colWidth = 160;
+ var rowHeight = 20;
+
+ for( var i = 0; i < vendorHairHueCategories.length; ++i )
+ {
+ var row = Math.floor( i / columns );
+ var col = i % columns;
+ var x = startX + ( col * colWidth );
+ var y = startY + ( row * rowHeight );
+
+ myGump.AddButton( x, y, 0x0A9A, 0x0A9A, 0, categoryPageStart + i, 0 );
+ myGump.AddHTMLGump( x + 30, y, 125, 18, false, false, "" + GetVendorHueCategoryName( vendorHairHueCategories[i], socket ) + "" );
+ }
+
+ myGump.AddButton( 270, 340, 0xFA5, 0xFA7, 1, 1, 5007 );
+ myGump.AddHTMLGump( 305, 342, 60, 18, false, false, "" + GetDictionaryEntry( 10267, socket.language ) + "" );
+}
+
+/** @param {Socket} socket @param {Gump} myGump @param {number} pageNumber @param {string} title @param {number[]} hueList @param {number} buttonBase @param {number} backPage */
+function AddHairHueCategoryPage( socket, myGump, pageNumber, title, hueList, buttonBase, backPage )
+{
+ myGump.AddPage( pageNumber );
+ myGump.AddHTMLGump( 10, 42, 350, 18, false, false, "" + title + "" );
+
+ var columns = 3;
+ var startX = 20;
+ var startY = 70;
+ var colWidth = 112;
+ var rowHeight = 18;
+
+ for( var i = 0; i < hueList.length; ++i )
+ {
+ var row = Math.floor( i / columns );
+ var col = i % columns;
+ var x = startX + ( col * colWidth );
+ var y = startY + ( row * rowHeight );
+ var hueHex = hueList[i].toString( 16 ).toUpperCase();
+
+ myGump.AddButton( x, y, 0x0A9A, 0x0A9A, 1, pageNumber, buttonBase + hueList[i] );
+ myGump.AddText( x + 25, y, hueList[i], "0x" + hueHex );
+ }
+
+ myGump.AddButton( 10, 340, 0xFA5, 0xFA7, 0, backPage, 0 );
+ myGump.AddHTMLGump( 45, 342, 80, 18, false, false, "" + GetDictionaryEntry( 10267, socket.language ) + "" );
+
+ myGump.AddButton( 270, 340, 0xFA5, 0xFA7, 1, 1, 5007 );
+ myGump.AddHTMLGump( 305, 342, 60, 18, false, false, "" + GetDictionaryEntry( 40045, socket.language ) + "" );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function ShowVendorCustomizeGump( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ var socket = pUser.socket;
+ var isElf = ( vendor.id == 0x025D || vendor.id == 0x025E );
+ var isFemale = ( vendor.id == 0x0191 || vendor.id == 0x025E );
+ var hairStyles = humanHairStyles;
+
+ if( isElf )
+ hairStyles = isFemale ? femaleElfHairStyles : maleElfHairStyles;
+
+ StoreVendorForGump( pUser, vendor );
+
+ var myGump = new Gump;
+ myGump.AddPage( 0 );
+ myGump.AddBackground( 0, 0, 370, 370, 0x13BE );
+ myGump.AddCheckerTrans( 10, 10, 350, 350 );
+ myGump.AddHTMLGump( 10, 12, 350, 18, false, false, "" + GetDictionaryEntry( 40046, socket.language ) + "" );
+
+ myGump.AddPage( 1 );
+ myGump.AddHTMLGump( 10, 42, 150, 18, false, false, "" + GetDictionaryEntry( 40047, socket.language ) + "" );
+
+ for( var i = 0; i < hairStyles.length; ++i )
+ {
+ myGump.AddButton( 10, 70 + ( i * 20 ), 0xFA5, 0xFA7, 1, 1, 0x1000 + i );
+ myGump.AddHTMLGump( 45, 72 + ( i * 20 ), 110, 18, false, false, "" + GetHairOrBeardName( hairStyles[i], socket ) + "" );
+ }
+
+ myGump.AddButton( 10, 70 + ( hairStyles.length * 20 ), 0xFB1, 0xFB3, 1, 1, 5002 );
+ myGump.AddHTMLGump( 45, 72 + ( hairStyles.length * 20 ), 110, 18, false, false, "" + GetDictionaryEntry( 40071, socket.language ) + "" );
+
+ myGump.AddButton( 10, 70 + (( hairStyles.length + 1 ) * 20 ), 0xFA5, 0xFA7, 0, 2, 0 );
+ myGump.AddHTMLGump( 45, 72 + (( hairStyles.length + 1 ) * 20 ), 110, 18, false, false, "" + GetDictionaryEntry( 40072, socket.language ) + "" );
+
+ if( !isElf && !isFemale )
+ {
+ myGump.AddHTMLGump( 160, 42, 190, 18, false, false, "" + GetDictionaryEntry( 40073, socket.language ) + "" );
+
+ for( i = 0; i < humanBeardStyles.length; ++i )
+ {
+ myGump.AddButton( 160, 70 + ( i * 20 ), 0xFA5, 0xFA7, 1, 1, 0x2000 + i );
+ myGump.AddHTMLGump( 195, 72 + ( i * 20 ), 160, 18, false, false, "" + GetHairOrBeardName( humanBeardStyles[i], socket ) + "" );
+ }
+
+ myGump.AddButton( 160, 70 + ( humanBeardStyles.length * 20 ), 0xFB1, 0xFB3, 1, 1, 5004 );
+ myGump.AddHTMLGump( 195, 72 + ( humanBeardStyles.length * 20 ), 160, 18, false, false, "" + GetDictionaryEntry( 40074, socket.language ) + "" );
+
+ myGump.AddButton( 160, 70 + (( humanBeardStyles.length + 1 ) * 20 ), 0xFA5, 0xFA7, 0, 100, 0 );
+ myGump.AddHTMLGump( 195, 72 + (( humanBeardStyles.length + 1 ) * 20 ), 160, 18, false, false, "" + GetDictionaryEntry( 40075, socket.language ) + "" );
+ }
+
+ myGump.AddButton( 160, 290, 0xFA5, 0xFA7, 1, 1, 5001 );
+ myGump.AddHTMLGump( 195, 292, 160, 18, false, false, isFemale ? "" + GetDictionaryEntry( 40076, socket.language ) + "" : "" + GetDictionaryEntry( 40077, socket.language ) + "" );
+
+ myGump.AddButton( 160, 310, 0xFA5, 0xFA7, 1, 1, 5006 );
+ myGump.AddHTMLGump( 195, 312, 160, 18, false, false, isElf ? "" + GetDictionaryEntry( 40078, socket.language ) + "" : "" + GetDictionaryEntry( 40079, socket.language ) + "" );
+
+ myGump.AddButton( 160, 330, 0xFA5, 0xFA7, 1, 0, 9 );
+ myGump.AddHTMLGump( 195, 332, 160, 18, false, false, "" + GetDictionaryEntry( 40080, socket.language ) + "" );
+
+ myGump.AddButton( 10, 340, 0xFA5, 0xFA7, 1, 1, 5000 );
+ myGump.AddHTMLGump( 45, 342, 305, 18, false, false, "" + GetDictionaryEntry( 40081, socket.language ) + "" );
+
+ AddHairHueCategoryMenu( socket, myGump, 2, 10, GetDictionaryEntry( 40082, socket.language ) );
+ AddHairHueCategoryMenu( socket, myGump, 100, 110, GetDictionaryEntry( 40083, socket.language ) );
+
+ for( i = 0; i < vendorHairHueCategories.length; ++i )
+ {
+ var hairTitle = GetDictionaryEntry( 40084, socket.language ).replace( /%s/gi, GetVendorHueCategoryName( vendorHairHueCategories[i], socket ) );
+ var beardTitle = GetDictionaryEntry( 40085, socket.language ).replace( /%s/gi, GetVendorHueCategoryName( vendorHairHueCategories[i], socket ) );
+
+ AddHairHueCategoryPage( socket, myGump, 10 + i, hairTitle, vendorHairHueCategories[i].hues, 0x3000, 2 );
+ AddHairHueCategoryPage( socket, myGump, 110 + i, beardTitle, vendorHairHueCategories[i].hues, 0x4000, 100 );
+ }
+
+ myGump.Send( socket );
+ myGump.Free();
+}
+
+/** @type { ( pUser: Character, vendor: Character, pButton: number ) => void } */
+function HandleVendorCustomizeButton( pUser, vendor, pButton )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ var isElf = ( vendor.id == 0x025D || vendor.id == 0x025E );
+ var isFemale = ( vendor.id == 0x0191 || vendor.id == 0x025E );
+ var hairStyles = humanHairStyles;
+
+ if( isElf )
+ hairStyles = isFemale ? femaleElfHairStyles : maleElfHairStyles;
+
+ // Return to vendor owner menu
+ if( pButton == 5000 )
+ {
+ ShowPlayerVendorOwnerGump( pUser.socket, vendor );
+ return;
+ }
+
+ // Refresh vendor customize menu
+ if( pButton == 5007 )
+ {
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Toggle vendor gender
+ if( pButton == 5001 )
+ {
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Beard );
+ SetVendorRaceGenderBody( vendor, isElf, !isFemale );
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Toggle vendor race
+ if( pButton == 5006 )
+ {
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Hair );
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Beard );
+ SetVendorRaceGenderBody( vendor, !isElf, isFemale );
+
+ var newHairStyles = humanHairStyles;
+ var newIsElf = ( vendor.id == 0x025D || vendor.id == 0x025E );
+ var newIsFemale = ( vendor.id == 0x0191 || vendor.id == 0x025E );
+
+ if( newIsElf )
+ newHairStyles = newIsFemale ? femaleElfHairStyles : maleElfHairStyles;
+
+ if( newHairStyles.length > 0 )
+ SetVendorLayerItem( vendor, VendorEquipmentLayer.Hair, newHairStyles[0].itemID, 0, "vendor hair" );
+
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Remove vendor hair
+ if( pButton == 5002 )
+ {
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Hair );
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Remove vendor beard
+ if( pButton == 5004 )
+ {
+ RemoveVendorLayerItem( vendor, VendorEquipmentLayer.Beard );
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Set vendor hair style
+ if( pButton >= 0x1000 && pButton < ( 0x1000 + 256 ) )
+ {
+ var hairIndex = pButton - 0x1000;
+ if( hairIndex >= 0 && hairIndex < hairStyles.length )
+ {
+ var oldHairItem = vendor.FindItemLayer( VendorEquipmentLayer.Hair );
+ var oldHairHue = ValidateObject( oldHairItem ) ? oldHairItem.colour : 0;
+ SetVendorLayerItem( vendor, VendorEquipmentLayer.Hair, hairStyles[hairIndex].itemID, oldHairHue, "vendor hair" );
+ }
+
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Set vendor beard style
+ if( pButton >= 0x2000 && pButton < ( 0x2000 + 256 ) )
+ {
+ var beardIndex = pButton - 0x2000;
+ if( !isElf && !isFemale && beardIndex >= 0 && beardIndex < humanBeardStyles.length )
+ {
+ var oldBeardItem = vendor.FindItemLayer( VendorEquipmentLayer.Beard );
+ var oldBeardHue = ValidateObject( oldBeardItem ) ? oldBeardItem.colour : 0;
+ SetVendorLayerItem( vendor, VendorEquipmentLayer.Beard, humanBeardStyles[beardIndex].itemID, oldBeardHue, "vendor beard" );
+ }
+
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Set vendor hair color
+ if( pButton >= 0x3000 && pButton < 0x4000 )
+ {
+ SetVendorLayerHue( vendor, VendorEquipmentLayer.Hair, pButton - 0x3000 );
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Set vendor beard color
+ if( pButton >= 0x4000 && pButton < 0x5000 )
+ {
+ if( !isElf && !isFemale )
+ SetVendorLayerHue( vendor, VendorEquipmentLayer.Beard, pButton - 0x4000 );
+
+ ShowVendorCustomizeGump( pUser, vendor );
+ }
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function ShowVendorClothingGump( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ var socket = pUser.socket;
+ StoreVendorForGump( pUser, vendor );
+
+ var myGump = new Gump;
+ myGump.AddPage( 0 );
+ myGump.AddBackground( 0, 0, 585, 393, 5054 );
+ myGump.AddBackground( 195, 36, 387, 275, 3000 );
+ myGump.AddHTMLGump( 10, 10, 565, 18, false, false, "" + GetDictionaryEntry( 40086, socket.language ) + "" );
+
+ myGump.AddHTMLGump( 60, 355, 150, 18, false, false, GetDictionaryEntry( 18718, socket.language ) );
+ myGump.AddButton( 25, 355, 4005, 4007, 1, 0, 5000 );
+
+ myGump.AddHTMLGump( 320, 355, 150, 18, false, false, GetDictionaryEntry( 10267, socket.language ) );
+ myGump.AddButton( 285, 355, 4005, 4007, 1, 0, 0x8FFF );
+
+ var i;
+ var y = 35;
+ for( i = 0; i < vendorClothingCategories.length; ++i )
+ {
+ myGump.AddHTMLGump( 5, y, 150, 25, false, false, vendorClothingCategories[i].name );
+ myGump.AddButton( 155, y, 4005, 4007, 0, 20 + i, 0 );
+ y += 25;
+ }
+
+ for( i = 0; i < vendorClothingCategories.length; ++i )
+ {
+ var cat = vendorClothingCategories[i];
+ myGump.AddPage( 20 + i );
+
+ for( var entryIndex = 0; entryIndex < cat.entries.length; ++entryIndex )
+ {
+ var entry = cat.entries[entryIndex];
+ var cols = cat.name == "Held Items" ? 4 : 3;
+ var startX = cat.name == "Held Items" ? 205 : 198;
+ var startY = cat.name == "Held Items" ? 42 : 38;
+ var colWidth = cat.name == "Held Items" ? 92 : 129;
+ var rowHeight = cat.name == "Held Items" ? 52 : 67;
+ var textWidth = cat.name == "Held Items" ? 80 : 100;
+ var itemOffsetX = cat.name == "Held Items" ? 18 : 20;
+ var itemOffsetY = cat.name == "Held Items" ? 16 : 25;
+ var buttonOffsetY = cat.name == "Held Items" ? 30 : 45;
+
+ var x = startX + ( entryIndex % cols ) * colWidth;
+ var ey = startY + Math.floor( entryIndex / cols ) * rowHeight;
+
+ myGump.AddHTMLGump( x, ey, textWidth, 18, false, false, entry.name );
+ myGump.AddPicture( x + itemOffsetX, ey + itemOffsetY, entry.itemID );
+ myGump.AddButton( x, ey + buttonOffsetY, 4005, 4007, 1, 20 + i, 0x5000 + ( i * 100 ) + entryIndex );
+ }
+
+ if( cat.canDye )
+ {
+ myGump.AddButton( 300, 315, 4005, 4007, 1, 0, 0x8000 + i );
+ myGump.AddHTMLGump( 335, 317, 100, 18, false, false, GetDictionaryEntry( 40087, socket.language ) );
+ }
+
+ myGump.AddButton( 430, 315, 4005, 4007, 1, 20 + i, 0x7000 + i );
+ myGump.AddHTMLGump( 465, 317, 100, 18, false, false, GetDictionaryEntry( 40088, socket.language ) );
+ }
+
+ myGump.Send( socket );
+ myGump.Free();
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function ShowVendorClothingDyeGump( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ var socket = pUser.socket;
+ var categoryIndex = GetStoredClothingDyeCategory( pUser );
+ var title = GetDictionaryEntry( 40091, socket.language );
+
+ if( categoryIndex >= 0 && categoryIndex < vendorClothingCategories.length )
+ title = vendorClothingCategories[categoryIndex].name + " COLOR";
+
+ StoreVendorForGump( pUser, vendor );
+
+ var myGump = new Gump;
+ myGump.AddPage( 0 );
+ myGump.AddBackground( 0, 0, 370, 370, 0x13BE );
+ myGump.AddCheckerTrans( 10, 10, 350, 350 );
+ myGump.AddHTMLGump( 10, 12, 350, 18, false, false, "" + title + "" );
+
+ myGump.AddPage( 1 );
+ myGump.AddHTMLGump( 10, 42, 350, 18, false, false, "" + GetDictionaryEntry( 40089, socket.language ) + "" );
+
+ var columns = 2;
+ var startX = 20;
+ var startY = 70;
+ var colWidth = 160;
+ var rowHeight = 20;
+
+ for( var i = 0; i < vendorHairHueCategories.length; ++i )
+ {
+ var row = Math.floor( i / columns );
+ var col = i % columns;
+ var x = startX + ( col * colWidth );
+ var y = startY + ( row * rowHeight );
+
+ myGump.AddButton( x, y, 0x0A9A, 0x0A9A, 0, 10 + i, 0 );
+ myGump.AddHTMLGump( x + 30, y, 125, 18, false, false, "" + GetVendorHueCategoryName( vendorHairHueCategories[i], socket ) + "" );
+ }
+
+ myGump.AddButton( 10, 340, 0xFA5, 0xFA7, 1, 0, 0x8FFE );
+ myGump.AddHTMLGump( 45, 342, 120, 18, false, false, "" + GetDictionaryEntry( 10267, socket.language ) + "" );
+
+ for( i = 0; i < vendorHairHueCategories.length; ++i )
+ {
+ myGump.AddPage( 10 + i );
+ myGump.AddHTMLGump( 10, 42, 350, 18, false, false, "" + GetVendorHueCategoryName( vendorHairHueCategories[i], socket ) + "" );
+
+ var hues = vendorHairHueCategories[i].hues;
+ for( var j = 0; j < hues.length; ++j )
+ {
+ var hueRow = Math.floor( j / 3 );
+ var hueCol = j % 3;
+ var hx = 20 + ( hueCol * 112 );
+ var hy = 70 + ( hueRow * 18 );
+ var hueHex = hues[j].toString( 16 ).toUpperCase();
+
+ myGump.AddButton( hx, hy, 0x0A9A, 0x0A9A, 1, 10 + i, 0x9000 + hues[j] );
+ myGump.AddText( hx + 25, hy, hues[j], "0x" + hueHex );
+ }
+
+ myGump.AddButton( 10, 340, 0xFA5, 0xFA7, 0, 1, 0 );
+ myGump.AddHTMLGump( 45, 342, 80, 18, false, false, "" + GetDictionaryEntry( 10267, socket.language ) + "" );
+
+ myGump.AddButton( 270, 340, 0xFA5, 0xFA7, 1, 0, 0x8FFE );
+ myGump.AddHTMLGump( 305, 342, 60, 18, false, false, "" + GetDictionaryEntry( 40090, socket.language ) + "" );
+ }
+
+ myGump.Send( socket );
+ myGump.Free();
+}
+
+/** @param {Character} pUser @param {Character} vendor @param {number} pButton */
+function HandleVendorClothingButton( pUser, vendor, pButton )
+{
+ if( vendor.aitype != 17 )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ // Return to vendor customize menu
+ if( pButton == 0x8FFF )
+ {
+ ShowVendorCustomizeGump( pUser, vendor );
+ return;
+ }
+
+ // Return to vendor clothing menu
+ if( pButton == 0x8FFE )
+ {
+ ShowVendorClothingGump( pUser, vendor );
+ return;
+ }
+
+ // Remove selected clothing layer
+ if( pButton >= 0x7000 && pButton < ( 0x7000 + 100 ) )
+ {
+ var removeCatIndex = pButton - 0x7000;
+ if( removeCatIndex >= 0 && removeCatIndex < vendorClothingCategories.length )
+ {
+ RemoveVendorLayerItem( vendor, vendorClothingCategories[removeCatIndex].layer );
+ ShowVendorClothingGump( pUser, vendor );
+ }
+ return;
+ }
+
+ // Equip selected clothing item
+ if( pButton >= 0x5000 && pButton < ( 0x5000 + 10000 ) )
+ {
+ var entryValue = pButton - 0x5000;
+ var catIndex = Math.floor( entryValue / 100 );
+ var entryIndex = entryValue % 100;
+
+ if( catIndex >= 0 && catIndex < vendorClothingCategories.length )
+ {
+ var cat = vendorClothingCategories[catIndex];
+ if( entryIndex >= 0 && entryIndex < cat.entries.length )
+ {
+ var oldClothingItem = vendor.FindItemLayer( cat.layer );
+ var oldClothingHue = ValidateObject( oldClothingItem ) ? oldClothingItem.colour : 0;
+ SetVendorLayerItem( vendor, cat.layer, cat.entries[entryIndex].itemID, oldClothingHue, "vendor clothing" );
+ ShowVendorClothingGump( pUser, vendor );
+ }
+ }
+ return;
+ }
+
+ // Open clothing dye menu
+ if( pButton >= 0x8000 && pButton < ( 0x8000 + vendorClothingCategories.length ) )
+ {
+ var dyeCatIndex = pButton - 0x8000;
+ if( dyeCatIndex >= 0 && dyeCatIndex < vendorClothingCategories.length )
+ {
+ StoreClothingDyeCategory( pUser, dyeCatIndex );
+ ShowVendorClothingDyeGump( pUser, vendor );
+ }
+ return;
+ }
+
+ // Set selected clothing color
+ if( pButton >= 0x9000 && pButton < ( 0x9000 + 0x1000 ) )
+ {
+ var hueValue = pButton - 0x9000;
+ var storedCatIndex = GetStoredClothingDyeCategory( pUser );
+
+ if( storedCatIndex >= 0 && storedCatIndex < vendorClothingCategories.length )
+ {
+ var dyeCat = vendorClothingCategories[storedCatIndex];
+ if( dyeCat.canDye )
+ SetVendorLayerHue( vendor, dyeCat.layer, hueValue );
+ }
+
+ ShowVendorClothingDyeGump( pUser, vendor );
+ }
+}
+
+/** @param {string} text @returns {string} */
+function TrimString( text )
+{
+ if( text === null || text === undefined )
+ return "";
+
+ return ( "" + text ).replace( /^\s+|\s+$/g, "" );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function BeginRenameVendor( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ StoreVendorForGump( pUser, vendor );
+ pUser.SpeechInput( 20, vendor );
+ pUser.socket.SysMessage( GetDictionaryEntry( 40050, pUser.socket.language ) );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function BeginRenameVendorShop( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ StoreVendorForGump( pUser, vendor );
+ pUser.SpeechInput( 21, vendor );
+ pUser.socket.SysMessage( GetDictionaryEntry( 40051, pUser.socket.language ) );
+}
+
+/** @param {Character} pUser @param {Character} vendor */
+function BeginDepositVendorGold( pUser, vendor )
+{
+ if( vendor.aitype != 17 || !pUser.socket )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ StoreVendorForGump( pUser, vendor );
+ pUser.SpeechInput( 22, vendor );
+ pUser.socket.SysMessage( GetDictionaryEntry( 40052, pUser.socket.language ) );
+}
+
+/** @type { ( pUser: Character, targChar: Character, nonMouseClickEvent: boolean ) => boolean } */
+function onCharDoubleClick( pUser, targChar, nonMouseClickEvent )
+{
+ if( !ValidateObject( pUser ) || !ValidateObject( targChar ) )
+ return false;
+
+ if( nonMouseClickEvent )
+ return true;
+
+ if( targChar.aitype != 17 )
+ return true;
+
+ if( !CheckVendorUpkeep( targChar, pUser ) )
+ return false;
+
+ if( ValidateObject( targChar.owner ) && targChar.owner.serial == pUser.serial )
+ {
+ if( pUser.socket )
+ {
+ StoreVendorForGump( pUser, targChar );
+ ShowPlayerVendorOwnerGump( pUser.socket, targChar );
+ }
+ return false;
+ }
+
+ if( ValidateObject( targChar.multi ) && targChar.multi.IsOnBanList( pUser ) )
+ {
+ if( pUser.socket )
+ pUser.socket.SysMessage( GetDictionaryEntry( 40059, pUser.socket.language ) );
+ return false;
+ }
+
+ OpenVendorBackpack( pUser, targChar );
+ return false;
+}
+
+/** @param {string} speech @param {Character} personTalking @param {BaseObject} talkingTo @returns {number} */
+function onSpeech( speech, personTalking, talkingTo )
+{
+ if( !ValidateObject( personTalking ) || !ValidateObject( talkingTo ) )
+ return 0;
+
+ if( talkingTo.aitype != 17 )
+ return 0;
+
+ return HandleVendorSpeechCommand( personTalking, talkingTo, speech );
+}
+
+/** @param {Socket} pSock @param {number} pButton @param {GumpData} gumpData */
+function onGumpPress( pSock, pButton, gumpData )
+{
+ var pUser = pSock.currentChar;
+ if( !ValidateObject( pUser ) )
+ return;
+
+ var vendor = GetStoredVendorForGump( pUser );
+ if( !ValidateObject( vendor ) || vendor.aitype != 17 )
+ return;
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ return;
+
+ if( !CheckVendorUpkeep( vendor, pUser ) )
+ return;
+
+ // Handle vendor customize buttons
+ if( pButton == 5000 || pButton == 5001 || pButton == 5002 || pButton == 5004 || pButton == 5006 || pButton == 5007 ||
+ ( pButton >= 0x1000 && pButton < ( 0x1000 + 256 ) ) ||
+ ( pButton >= 0x2000 && pButton < ( 0x2000 + 256 ) ) ||
+ ( pButton >= 0x3000 && pButton < 0x4000 ) ||
+ ( pButton >= 0x4000 && pButton < 0x5000 ) )
+ {
+ HandleVendorCustomizeButton( pUser, vendor, pButton );
+ return;
+ }
+
+ // Handle vendor clothing buttons
+ if( pButton == 0x8FFF || pButton == 0x8FFE ||
+ ( pButton >= 0x5000 && pButton < ( 0x5000 + 10000 ) ) ||
+ ( pButton >= 0x7000 && pButton < ( 0x7000 + 100 ) ) ||
+ ( pButton >= 0x8000 && pButton < ( 0x8000 + vendorClothingCategories.length ) ) ||
+ ( pButton >= 0x9000 && pButton < ( 0x9000 + 0x1000 ) ) )
+ {
+ HandleVendorClothingButton( pUser, vendor, pButton );
+ return;
+ }
+
+ switch( pButton )
+ {
+ case 0: break;
+ case 1: OpenVendorBackpack( pUser, vendor ); break;
+ case 2: ShowVendorCustomizeGump( pUser, vendor ); break;
+ case 3:
+ CollectVendorGold( pUser, vendor );
+ ShowPlayerVendorOwnerGump( pSock, vendor );
+ break;
+ case 4: BeginDepositVendorGold( pUser, vendor ); break;
+ case 5:
+ FlushBankToHeld( pUser, vendor );
+ ShowPlayerVendorOwnerGump( pSock, vendor );
+ break;
+ case 6: DismissVendor( pUser, vendor ); break;
+ case 7: BeginRenameVendor( pUser, vendor ); break;
+ case 8: BeginRenameVendorShop( pUser, vendor ); break;
+ case 9: ShowVendorClothingGump( pUser, vendor ); break;
+ }
+}
+
+/** @type { ( pUser: Character, targObj: BaseObject, pSpeech: string, pSpeechID: number ) => void } */
+function onSpeechInput( pUser, targObj, pSpeech, pSpeechID )
+{
+ if( !ValidateObject( pUser ) || !pUser.socket )
+ return;
+
+ var socket = pUser.socket;
+ var vendor = GetStoredVendorForGump( pUser );
+ if( !ValidateObject( vendor ) || vendor.aitype != 17 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40053, socket.language ) );
+ return;
+ }
+
+ if( !ValidateObject( vendor.owner ) || vendor.owner.serial != pUser.serial )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40028, socket.language ) );
+ return;
+ }
+
+ if( !CheckVendorUpkeep( vendor, pUser ) )
+ return;
+
+ var newText = TrimString( pSpeech );
+ if( newText == "" )
+ {
+ socket.SysMessage( GetDictionaryEntry( 9270, socket.language ) );
+ return;
+ }
+
+ switch( pSpeechID )
+ {
+ case 20:
+ if( newText.length > 50 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 9271, socket.language ) );
+ return;
+ }
+
+ vendor.name = newText;
+ vendor.Refresh();
+ socket.SysMessage( GetDictionaryEntry( 40054, socket.language ), vendor.name );
+ ShowPlayerVendorOwnerGump( socket, vendor );
+ break;
+
+ case 21:
+ if( newText.length > 40 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40055, socket.language ) );
+ return;
+ }
+
+ vendor.title = "Shop Name: " + newText;
+ vendor.Refresh();
+ socket.SysMessage( GetDictionaryEntry( 40056, socket.language ), vendor.title );
+ ShowPlayerVendorOwnerGump( socket, vendor );
+ break;
+
+ case 22:
+ if( !/^\d+$/.test( newText ) )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40057, socket.language ) );
+ return;
+ }
+
+ var depositAmount = parseInt( newText, 10 );
+ if( isNaN( depositAmount ) || depositAmount <= 0 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 40018, socket.language ) );
+ return;
+ }
+
+ DepositToVendorBank( pUser, vendor, depositAmount );
+ ShowPlayerVendorOwnerGump( socket, vendor );
+ break;
+ }
+}
+
+/** @type { ( timerObj: BaseObject, timerID: number ) => void } */
+function onTimer( timerObj, timerID )
+{
+ if( timerID != VendorTimer.Upkeep )
+ return;
+
+ if( !ValidateObject( timerObj ) || !timerObj.isChar || timerObj.aitype != 17 )
+ return;
+
+ if( CheckVendorUpkeep( timerObj, null ) )
+ StartVendorUpkeepTimer( timerObj );
+}
+
+
+function StartVendorUpkeepTimer( vendor )
+{
+ if( vendor.aitype != 17 )
+ return;
+
+ vendor.StartTimer( 60000, VendorTimer.Upkeep, true );
+}
\ No newline at end of file
diff --git a/data/js/server/house/playerVendorDeed.js b/data/js/server/house/playerVendorDeed.js
index 47e286204..d52a19537 100644
--- a/data/js/server/house/playerVendorDeed.js
+++ b/data/js/server/house/playerVendorDeed.js
@@ -9,9 +9,9 @@ function onUseChecked( pUser, iUsed )
if( pSocket != null && ValidateObject( iUsed ) && iUsed.isItem )
{
var itemOwner = GetPackOwner( iUsed, 0 );
- if( itemOwner == null || itemOwner.serial != pUser.serial )
+ if( itemOwner == null || itemOwner.serial != pUser.serial || !ValidateObject( iUsed.container) || iUsed.container.layer == 0x1D )
{
- pUser.SysMessage( GetDictionaryEntry( 1763, pSocket.language )); // That item must be in your backpack before it can be used.
+ pSocket.SysMessage( GetDictionaryEntry( 1763, pSocket.language )); // That item must be in your backpack before it can be used.
return false;
}
@@ -20,7 +20,7 @@ function onUseChecked( pUser, iUsed )
if( !ValidateObject( iMulti ) || !iMulti.IsInMulti( pUser ))
{
// Player vendors can only be placed in houses!
- pUser.SysMessage( GetDictionaryEntry( 2857, pSocket.language )); // Player vendors can only be placed in houses!
+ pSocket.SysMessage( GetDictionaryEntry( 2857, pSocket.language )); // Player vendors can only be placed in houses!
return false;
}
@@ -28,7 +28,7 @@ function onUseChecked( pUser, iUsed )
if( !iMulti.IsOwner( pUser ))
{
// Only the house owner can place player vendors in a house!
- pUser.SysMessage( GetDictionaryEntry( 2858, pSocket.language )); // Only the house owner can place player vendors in a house!
+ pSocket.SysMessage( GetDictionaryEntry( 2858, pSocket.language )); // Only the house owner can place player vendors in a house!
return false;
}
@@ -36,7 +36,7 @@ function onUseChecked( pUser, iUsed )
if( iMulti.vendors >= iMulti.maxVendors )
{
// You cannot place any more player vendors in this house!
- pUser.SysMessage( GetDictionaryEntry( 2859, pSocket.language )); // You cannot place any more player vendors in this house!
+ pSocket.SysMessage( GetDictionaryEntry( 2859, pSocket.language )); // You cannot place any more player vendors in this house!
return false;
}
@@ -56,6 +56,14 @@ function onUseChecked( pUser, iUsed )
return false;
}
+ // Make sure there is not already an NPC on the exact tile
+ var foundNPC = AreaCharacterFunction( "CheckForNPCAtVendorSpot", pUser, 0, pSocket );
+ if( foundNPC )
+ {
+ pSocket.SysMessage( "You cannot place a player vendor on top of another NPC." );
+ return false;
+ }
+
// Create player vendor at player's current location
var npcVendor = SpawnNPC( "playervendor", pUser.x, pUser.y, pUser.z, pUser.worldnumber, pUser.instanceID );
if( ValidateObject( npcVendor ))
@@ -72,6 +80,14 @@ function onUseChecked( pUser, iUsed )
// Add the player vendor to the house's list of vendors
iMulti.AddVendor( npcVendor );
+ npcVendor.SetTag( "VendorBankAccount",0 );
+ npcVendor.AddScriptTrigger( 3110 );
+ npcVendor.StartTimer( 60000, 90, 3110 );
+ var vendorName = iUsed.GetTag( "vendorName" );
+ if( vendorName )
+ {
+ npcVendor.name = vendorName;
+ }
// Delete the player vendor deed!
iUsed.Delete();
@@ -85,6 +101,23 @@ function onUseChecked( pUser, iUsed )
return false;
}
+/** @type { ( srcChar: Character, trgChar: Character, pSocket: Socket ) => boolean } */
+function CheckForNPCAtVendorSpot( srcChar, trgChar, pSocket )
+{
+ if( !ValidateObject( srcChar ) || !ValidateObject( trgChar ))
+ return false;
+
+ // Ignore the player placing the vendor
+ if( trgChar.serial == srcChar.serial )
+ return false;
+
+ // Only block NPCs already standing on the exact placement tile
+ if( trgChar.npc && trgChar.x == srcChar.x && trgChar.y == srcChar.y && trgChar.z == srcChar.z )
+ return true;
+
+ return false;
+}
+
function CheckForNearbyDoors( pUser, itemToCheck, pSocket )
{
if( ValidateObject( itemToCheck ))
@@ -107,11 +140,11 @@ function CheckForNearbyDoors( pUser, itemToCheck, pSocket )
// Make sure to check against the distance from the door in it's closed state, rather than it's open state!
var origX = itemToCheck.x - itemToCheck.GetTag( "DOOR_X" );
var origY = itemToCheck.y - itemToCheck.GetTag( "DOOR_Y" );
- if( pUser.x - origX < 2 || origX - pUser.x < 2 || pUser.y - origY < 2 || origY - pUser.y < 2 )
- return true;
+ if( Math.abs( pUser.x - origX ) < 1 && Math.abs( pUser.y - origY ) < 1 )
+ return true;
}
- if( pUser.DistanceTo( itemToCheck ) <= 2 )
+ if( pUser.DistanceTo( itemToCheck ) <= 1 )
return true;
}
}
diff --git a/data/uox.ini b/data/uox.ini
index ec78aaded..5e3e0ca9e 100644
--- a/data/uox.ini
+++ b/data/uox.ini
@@ -243,6 +243,18 @@ PETLOYALTYRATE=900
PETBONDINGENABLED=0
}
+[playervendor]
+{
+VENDORCHARGESENABLED=1
+VENDORBASECHARGE=60
+VENDORCHARGEHOURS=24
+VENDORUSEITEMFEESENABLED=1
+VENDORITEMFEEDIVISOR=500
+VENDORITEMFEEAMOUNT=3
+ONLYRETURNTOBANK=1
+VENDORMAXFUNDS=16960
+}
+
[speedup]
{
CHECKITEMS=1.5
diff --git a/docs/jsdocs.html b/docs/jsdocs.html
index 4b185c73a..455d630d3 100644
--- a/docs/jsdocs.html
+++ b/docs/jsdocs.html
@@ -16716,6 +16716,10 @@ Integer Types
.visible (int, 0 - 3)
Get/Set visibility of character to other players (0 = Visible, 1 = Hidden, 2 = Magically Invisible, 3 = Visible to GMs only)
+
+
.vendorGoldHeld (int)
+ Get/Set amount of gold the vendor is holding after a player buys an item.
+
.wandertype (int, 0 - 3)
Get/Set wandertype for NPC (0 = None, [1 = Follow], 2 = Free, 3 = Box, 4 = Circle, 5 = Frozen, [6 = Flee, 7 = Pathfind]. Types 1, 6 and 7 are set primarily by code)
diff --git a/source/CResponse.cpp b/source/CResponse.cpp
index 96031ceff..118e8616e 100644
--- a/source/CResponse.cpp
+++ b/source/CResponse.cpp
@@ -242,13 +242,6 @@ bool WhichResponse( CSocket *mSock, CChar *mChar, std::string text, CChar *tChar
case TW_BUY: tResp = new CVendorBuyResponse( false, oldstrutil::upper( text )); break;
case TW_VENDORSELL: tResp = new CVendorSellResponse( true, oldstrutil::upper( text )); break;
case TW_SELL: tResp = new CVendorSellResponse( false, oldstrutil::upper( text )); break;
- case TW_VENDORVIEW: tResp = new CVendorViewResponse( true, oldstrutil::upper( text )); break;
- case TW_VIEW: tResp = new CVendorViewResponse( false, oldstrutil::upper( text )); break;
- case TW_VENDORGOLD: tResp = new CVendorGoldResponse( true, oldstrutil::upper( text )); break;
- case TW_COLLECT: [[fallthrough]];
- case TW_GOLD: tResp = new CVendorGoldResponse( false, oldstrutil::upper( text )); break;
- case TW_VENDORSTATUS: tResp = new CVendorStatusResponse( true, oldstrutil::upper( text )); break;
- case TW_STATUS: tResp = new CVendorStatusResponse( false, oldstrutil::upper( text )); break;
case TW_VENDORDISMISS: tResp = new CVendorDismissResponse( true, oldstrutil::upper( text )); break;
case TW_DISMISS: tResp = new CVendorDismissResponse( false, oldstrutil::upper( text )); break;
case TW_HOUSEBAN: tResp = new CHouseMultiResponse( TARGET_HOUSEBAN, 585 ); break;
@@ -1032,144 +1025,6 @@ bool CVendorSellResponse::Handle( CSocket *mSock, CChar *mChar, CChar *vendorNpc
return false;
}
-CVendorViewResponse::CVendorViewResponse( bool vendVal, const std::string &text ) : CBaseVendorResponse( vendVal, text )
-{
-}
-//o------------------------------------------------------------------------------------------------o
-//| Function - CVendorViewResponse::Handle()
-//o------------------------------------------------------------------------------------------------o
-//| Purpose - Handles response to players wanting to view the items a vendor has for sale
-//o------------------------------------------------------------------------------------------------o
-bool CVendorViewResponse::Handle( CSocket *mSock, [[maybe_unused]] CChar *mChar, CChar *vendorNpc )
-{
- if( vendorNpc->GetNpcAiType() == AI_PLAYERVENDOR )
- {
- CItem *pack = nullptr;
- vendorNpc->TextMessage( mSock, 385, TALK, false ); // Take a look at my goods.
- pack = vendorNpc->GetPackItem();
- if( ValidateObject( pack ))
- {
- mSock->OpenPack( pack, true );
- }
- }
- if( !saidVendor )
- return false;
-
- return true;
-}
-
-CVendorGoldResponse::CVendorGoldResponse( bool vendVal, const std::string &text ) : CBaseVendorResponse( vendVal, text )
-{
-}
-//o------------------------------------------------------------------------------------------------o
-//| Function - CVendorGoldResponse::Handle()
-//o------------------------------------------------------------------------------------------------o
-//| Purpose - Handles response to players wanting to withdraw their earnings from their vendor
-//o------------------------------------------------------------------------------------------------o
-bool CVendorGoldResponse::Handle( CSocket *mSock, [[maybe_unused]] CChar *mChar, CChar *vendorNpc )
-{
- if( vendorNpc->GetNpcAiType() == AI_PLAYERVENDOR )
- {
- CChar *mChar = mSock->CurrcharObj();
- if( mChar == vendorNpc->GetOwnerObj() )
- {
- UI32 pay = 0;
- auto give = vendorNpc->GetHoldG();
- auto earned = vendorNpc->GetHoldG();
-
- if( vendorNpc->GetHoldG() <= 0 )
- {
- vendorNpc->TextMessage( mSock, 1326, TALK, false ); // I have no gold waiting for you.
- vendorNpc->SetHoldG( 0 );
- }
- else if( vendorNpc->GetHoldG() > 0 && vendorNpc->GetHoldG() <= MAX_STACK )
- {
- if( vendorNpc->GetHoldG() > 9 )
- {
- pay = static_cast
( vendorNpc->GetHoldG() / 10 );
- give = vendorNpc->GetHoldG() - pay;
- }
- else
- {
- pay = vendorNpc->GetHoldG();
- give = 0;
- }
- vendorNpc->SetHoldG( 0 );
- }
- else
- {
- UI32 t = vendorNpc->GetHoldG() - MAX_STACK; // yank of 65 grand, then do calculations
- vendorNpc->SetHoldG( t );
- pay = 6554;
- give = 58981;
- if( t > 0 )
- {
- vendorNpc->TextMessage( mSock, 1327, TALK, false ); // You still have money left to claim.
- }
- }
- if( give )
- {
- Items->CreateScriptItem( mSock, mChar, "0x0EED", give, OT_ITEM, true );
- }
-
- // Today's purchases total %i gold. I am keeping %i gold for my self. Here is the remaining %i gold. Have a nice day.
- vendorNpc->TextMessage( mSock, 1328, TALK, 0, earned, pay, give );
- }
- else
- {
- vendorNpc->TextMessage( mSock, 1329, TALK, false ); // I don't work for you!
- }
- }
- if( !saidVendor )
- return false;
-
- return true;
-}
-
-CVendorStatusResponse::CVendorStatusResponse( bool vendVal, const std::string &text ) : CBaseVendorResponse( vendVal, text )
-{
-}
-//o------------------------------------------------------------------------------------------------o
-//| Function - CVendorStatusResponse::Handle()
-//o------------------------------------------------------------------------------------------------o
-//| Purpose - Handles response to players wanting to see the status of their vendor
-//o------------------------------------------------------------------------------------------------o
-bool CVendorStatusResponse::Handle( CSocket *mSock, [[maybe_unused]] CChar *mChar, CChar *vendorNpc )
-{
- if( vendorNpc->GetNpcAiType() == AI_PLAYERVENDOR )
- {
- CChar *mChar = mSock->CurrcharObj();
- if( mChar == vendorNpc->GetOwnerObj() )
- {
- if( vendorNpc->GetHoldG() <= 0 )
- {
- vendorNpc->TextMessage( mSock, 1326, TALK, false ); // I have no gold waiting for you.
- }
- else
- {
- UI32 pay = 0;
- if( vendorNpc->GetHoldG() > 9 )
- {
- pay = static_cast( vendorNpc->GetHoldG() / 10 );
- }
- else
- {
- pay = vendorNpc->GetHoldG();
- }
- vendorNpc->TextMessage( mSock, 1782, TALK, 0, vendorNpc->GetHoldG(), pay ); // Today's purchases total %i gold. I am keeping %i gold for my self.
- }
- }
- else
- {
- vendorNpc->TextMessage( mSock, 1329, TALK, false ); // I don't work for you!
- }
- }
- if( !saidVendor )
- return false;
-
- return true;
-}
-
CVendorDismissResponse::CVendorDismissResponse( bool vendVal, const std::string &text ) : CBaseVendorResponse( vendVal, text )
{
}
diff --git a/source/CResponse.h b/source/CResponse.h
index b1434f29b..f36095784 100644
--- a/source/CResponse.h
+++ b/source/CResponse.h
@@ -291,36 +291,6 @@ class CVendorSellResponse : public CBaseVendorResponse
virtual bool Handle( CSocket *mSock, CChar *mChar, CChar *Npc ) override;
};
-class CVendorViewResponse : public CBaseVendorResponse
-{
-public:
- CVendorViewResponse( bool vendVal, const std::string &text );
- virtual ~CVendorViewResponse()
- {
- }
- virtual bool Handle( CSocket *mSock, CChar *mChar, CChar *Npc ) override;
-};
-
-class CVendorGoldResponse : public CBaseVendorResponse
-{
-public:
- CVendorGoldResponse( bool vendVal, const std::string &text );
- virtual ~CVendorGoldResponse()
- {
- }
- virtual bool Handle( CSocket *mSock, CChar *mChar, CChar *Npc ) override;
-};
-
-class CVendorStatusResponse : public CBaseVendorResponse
-{
-public:
- CVendorStatusResponse( bool vendVal, const std::string &text );
- virtual ~CVendorStatusResponse()
- {
- }
- virtual bool Handle( CSocket *mSock, CChar *mChar, CChar *Npc ) override;
-};
-
class CVendorDismissResponse : public CBaseVendorResponse
{
public:
diff --git a/source/SEFunctions.cpp b/source/SEFunctions.cpp
index 84a67872a..562a3a5fb 100644
--- a/source/SEFunctions.cpp
+++ b/source/SEFunctions.cpp
@@ -5775,7 +5775,31 @@ JSBool SE_GetServerSetting( JSContext *cx, uintN argc, jsval *vp )
JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->EventManagerSystem() ));
break;
case 407: // LOGINQUESTENABLED
- JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->LoginQuestEnabled() ) );
+ JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->LoginQuestEnabled() ));
+ break;
+ case 408: // VENDORCHARGESENABLED
+ JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->VendorChargesEnabled() ));
+ break;
+ case 409: // VENDORBASECHARGE
+ JS_SET_RVAL( cx, vp, INT_TO_JSVAL( static_cast( cwmWorldState->ServerData()->VendorBaseCharge() )));
+ break;
+ case 410: // VENDORCHARGEHOURS
+ JS_SET_RVAL( cx, vp, INT_TO_JSVAL( static_cast( cwmWorldState->ServerData()->VendorChargeHours() )));
+ break;
+ case 411: // VENDORUSEITEMFEESENABLED
+ JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->VendorUseItemFeesEnabled() ));
+ break;
+ case 412: // VENDORITEMFEEDIVISOR
+ JS_SET_RVAL( cx, vp, INT_TO_JSVAL( static_cast( cwmWorldState->ServerData()->VendorItemFeeDivisor() )));
+ break;
+ case 413: // VENDORITEMFEEAMOUNT
+ JS_SET_RVAL( cx, vp, INT_TO_JSVAL( static_cast( cwmWorldState->ServerData()->VendorItemFeeAmount() )));
+ break;
+ case 414: // ONLYRETURNTOBANK
+ JS_SET_RVAL( cx, vp, BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->OnlyReturnToBank() ));
+ break;
+ case 415: // VENDORMAXFUNDS
+ JS_SET_RVAL( cx, vp, INT_TO_JSVAL( static_cast( cwmWorldState->ServerData()->VendorMaxFunds() )));
break;
default:
ScriptError( cx, "GetServerSetting: Invalid server setting name provided" );
diff --git a/source/UOXJSPropertyEnums.h b/source/UOXJSPropertyEnums.h
index 7a09cc7ec..b9b27c020 100644
--- a/source/UOXJSPropertyEnums.h
+++ b/source/UOXJSPropertyEnums.h
@@ -365,6 +365,7 @@ enum CC_Properties
CCP_AITYPE,
CCP_SPLIT,
CCP_SPLITCHANCE,
+ CCP_VENDORGOLDHELD,
CCP_TRAINER,
CCP_HIRELING,
CCP_WEIGHT,
diff --git a/source/UOXJSPropertyFuncs.cpp b/source/UOXJSPropertyFuncs.cpp
index eed6afb31..1894ab6d9 100644
--- a/source/UOXJSPropertyFuncs.cpp
+++ b/source/UOXJSPropertyFuncs.cpp
@@ -2176,6 +2176,7 @@ JSBool CCharacterProps_getProperty( JSContext *cx, JSObject *obj, jsid id, jsval
case CCP_AITYPE: *vp = INT_TO_JSVAL( gPriv->GetNpcAiType() ); break;
case CCP_SPLIT: *vp = INT_TO_JSVAL( gPriv->GetSplit() ); break;
case CCP_SPLITCHANCE: *vp = INT_TO_JSVAL( gPriv->GetSplitChance() ); break;
+ case CCP_VENDORGOLDHELD:*vp = INT_TO_JSVAL( gPriv->GetHoldG() ); break;
case CCP_TRAINER: *vp = BOOLEAN_TO_JSVAL( gPriv->CanTrain() ); break;
case CCP_HIRELING: *vp = BOOLEAN_TO_JSVAL( gPriv->CanBeHired() ); break;
case CCP_WEIGHT: *vp = INT_TO_JSVAL( gPriv->GetWeight() ); break;
@@ -2694,6 +2695,7 @@ JSBool CCharacterProps_setProperty( JSContext* cx, JSObject* obj, jsid id, JSBoo
case CCP_AITYPE: gPriv->SetNPCAiType( static_cast( encaps.toInt() )); break;
case CCP_SPLIT: gPriv->SetSplit( static_cast( encaps.toInt() )); break;
case CCP_SPLITCHANCE: gPriv->SetSplitChance( static_cast( encaps.toInt() ));break;
+ case CCP_VENDORGOLDHELD:gPriv->SetHoldG( static_cast( encaps.toInt() )); break;
case CCP_HIRELING: gPriv->SetCanHire( encaps.toBool() ); break;
case CCP_TRAINER: gPriv->SetCanTrain( encaps.toBool() ); break;
case CCP_WEIGHT: gPriv->SetWeight( static_cast( encaps.toInt() )); break;
diff --git a/source/UOXJSPropertySpecs.h b/source/UOXJSPropertySpecs.h
index d274d5df3..d917f3fe8 100644
--- a/source/UOXJSPropertySpecs.h
+++ b/source/UOXJSPropertySpecs.h
@@ -384,6 +384,7 @@ inline JSPropertySpec CCharacterProps[] =
{ "aitype", CCP_AITYPE, JSPROP_ENUMANDPERM, nullptr, nullptr },
{ "split", CCP_SPLIT, JSPROP_ENUMANDPERM, nullptr, nullptr },
{ "splitchance", CCP_SPLITCHANCE, JSPROP_ENUMANDPERM, nullptr, nullptr },
+ { "vendorGoldHeld", CCP_VENDORGOLDHELD, JSPROP_ENUMANDPERM, nullptr, nullptr },
{ "hireling", CCP_HIRELING, JSPROP_ENUMANDPERM, nullptr, nullptr },
{ "trainer", CCP_TRAINER, JSPROP_ENUMANDPERM, nullptr, nullptr },
{ "weight", CCP_WEIGHT, JSPROP_ENUMANDPERM, nullptr, nullptr },
diff --git a/source/cServerData.cpp b/source/cServerData.cpp
index bea28892b..0750efdc5 100644
--- a/source/cServerData.cpp
+++ b/source/cServerData.cpp
@@ -419,6 +419,14 @@ const std::map CServerData::uox3IniCaseValue
{"SPEEDHACKTHROTTLEPENALTY", 405},
{"EVENTMANAGERSYSTEM", 406},
{"LOGINQUESTENABLED"s, 407},
+ {"VENDORCHARGESENABLED", 408},
+ {"VENDORBASECHARGE", 409},
+ {"VENDORCHARGEHOURS", 410},
+ {"VENDORUSEITEMFEESENABLED", 411},
+ {"VENDORITEMFEEDIVISOR", 412},
+ {"VENDORITEMFEEAMOUNT", 413},
+ {"ONLYRETURNTOBANK", 414},
+ {"VENDORMAXFUNDS", 415},
};
constexpr auto MAX_TRACKINGTARGETS = 128;
constexpr auto SKILLTOTALCAP = 7000;
@@ -540,8 +548,11 @@ constexpr auto BIT_HOUSEITEMSDELETEONDECAY = UI32( 112 );
constexpr auto BIT_HOUSEGRANDFATHERED = UI32( 113 );
constexpr auto BIT_SPEEDHACKDETECTION = UI32( 119 );
constexpr auto BIT_EVENTMANAGERSYSTEM = UI32( 120 );
-constexpr auto BIT_QUESTSYSTEMENABLED = UI32( 121 );
-constexpr auto BIT_LOGINQUESTENABLED = UI32( 122 );
+constexpr auto BIT_QUESTSYSTEMENABLED = UI32( 121 );
+constexpr auto BIT_LOGINQUESTENABLED = UI32( 122 );
+constexpr auto BIT_VENDORCHARGESENABLED = UI32( 123 );
+constexpr auto BIT_VENDORUSEITEMFEESENABLED = UI32( 124 );
+constexpr auto BIT_ONLYRETURNTOBANK = UI32( 125 );
// New uox3.ini format lookup
@@ -953,6 +964,16 @@ auto CServerData::ResetDefaults() -> void
AmbientFootsteps( false );
ServerCommandPrefix( '\'' );
+ // Player vendor upkeep settings
+ VendorChargesEnabled( true );
+ VendorBaseCharge( 60 );
+ VendorChargeHours( 24 );
+ VendorUseItemFeesEnabled( true );
+ VendorItemFeeDivisor( 500 );
+ VendorItemFeeAmount( 3 );
+ OnlyReturnToBank( true );
+ VendorMaxFunds( 1000000 );
+
// SPEEDUP
CheckSpawnRegionSpeed( 30 );
CheckItemsSpeed( 1.5 );
@@ -5475,6 +5496,118 @@ auto CServerData::KickOnAssistantSilence( bool nVal ) -> void
boolVals.set( BIT_KICKONASSISTANTSILENCE, nVal );
}
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorChargesEnabled()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets whether player vendors charge upkeep fees
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorChargesEnabled() const -> bool
+{
+ return boolVals.test( BIT_VENDORCHARGESENABLED );
+}
+auto CServerData::VendorChargesEnabled( bool newVal ) -> void
+{
+ boolVals.set( BIT_VENDORCHARGESENABLED, newVal );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorBaseCharge()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets the flat gold fee charged to player vendors each upkeep period
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorBaseCharge() const -> SI16
+{
+ return vendorBaseCharge;
+}
+auto CServerData::VendorBaseCharge( SI16 value ) -> void
+{
+ vendorBaseCharge = value;
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorChargeHours()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets how often player vendors are charged upkeep fees, in real hours
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorChargeHours() const -> SI16
+{
+ return vendorChargeHours;
+}
+auto CServerData::VendorChargeHours( SI16 value ) -> void
+{
+ vendorChargeHours = value;
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorUseItemFeesEnabled()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets whether listed item values add extra player vendor upkeep fees
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorUseItemFeesEnabled() const -> bool
+{
+ return boolVals.test( BIT_VENDORUSEITEMFEESENABLED );
+}
+auto CServerData::VendorUseItemFeesEnabled( bool newVal ) -> void
+{
+ boolVals.set( BIT_VENDORUSEITEMFEESENABLED, newVal );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorItemFeeDivisor()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets the listed item value divisor used for player vendor item fees
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorItemFeeDivisor() const -> SI16
+{
+ return vendorItemFeeDivisor;
+}
+auto CServerData::VendorItemFeeDivisor( SI16 value ) -> void
+{
+ vendorItemFeeDivisor = value;
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorItemFeeAmount()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets the gold fee added per player vendor item fee divisor step
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorItemFeeAmount() const -> SI16
+{
+ return vendorItemFeeAmount;
+}
+auto CServerData::VendorItemFeeAmount( SI16 value ) -> void
+{
+ vendorItemFeeAmount = value;
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::OnlyReturnToBank()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets returned vendor belongings skip player backpack entirely
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::OnlyReturnToBank() const -> bool
+{
+ return boolVals.test( BIT_ONLYRETURNTOBANK );
+}
+auto CServerData::OnlyReturnToBank( bool newVal ) -> void
+{
+ boolVals.set( BIT_ONLYRETURNTOBANK, newVal );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function - CServerData::VendorMaxFunds()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose - Gets/Sets the Max amount of money the vendor can hold in the bank.
+//o------------------------------------------------------------------------------------------------o
+auto CServerData::VendorMaxFunds() const -> UI32
+{
+ return vendorMaxFunds;
+}
+auto CServerData::VendorMaxFunds( UI32 value ) -> void
+{
+ vendorMaxFunds = value;
+}
+
//o------------------------------------------------------------------------------------------------o
//| Function - CServerData::SaveIni()
//| Date - 02/21/2002
@@ -5842,6 +5975,17 @@ auto CServerData::SaveIni( const std::string &filename ) -> bool
ofsOutput << "PETBONDINGENABLED=" << ( PetBondingEnabled() ? 1 : 0 ) << '\n';
ofsOutput << "}" << '\n';
+ ofsOutput << '\n' << "[playervendor]" << '\n' << "{" << '\n';
+ ofsOutput << "VENDORCHARGESENABLED=" << ( VendorChargesEnabled() ? 1 : 0 ) << '\n';
+ ofsOutput << "VENDORBASECHARGE=" << VendorBaseCharge() << '\n';
+ ofsOutput << "VENDORCHARGEHOURS=" << VendorChargeHours() << '\n';
+ ofsOutput << "VENDORUSEITEMFEESENABLED=" << ( VendorUseItemFeesEnabled() ? 1 : 0 ) << '\n';
+ ofsOutput << "VENDORITEMFEEDIVISOR=" << VendorItemFeeDivisor() << '\n';
+ ofsOutput << "VENDORITEMFEEAMOUNT=" << VendorItemFeeAmount() << '\n';
+ ofsOutput << "ONLYRETURNTOBANK=" << ( OnlyReturnToBank() ? 1 : 0 ) << '\n';
+ ofsOutput << "VENDORMAXFUNDS=" << VendorMaxFunds() << '\n';
+ ofsOutput << "}" << '\n';
+
ofsOutput << '\n' << "[speedup]" << '\n' << "{" << '\n';
ofsOutput << "CHECKITEMS=" << CheckItemsSpeed() << '\n';
ofsOutput << "CHECKBOATS=" << CheckBoatSpeed() << '\n';
@@ -7587,6 +7731,31 @@ auto CServerData::HandleLine( const std::string& tag, const std::string& value )
break;
case 407: // LOGINQUESTENABLED
LoginQuestEnabled( ( static_cast( std::stoul( value, nullptr, 0 ) ) >= 1 ? true : false ) );
+ break;
+ case 408: // VENDORCHARGESENABLED
+ VendorChargesEnabled( ( static_cast( std::stoul( value, nullptr, 0 ) ) >= 1 ? true : false ) );
+ break;
+ case 409: // VENDORBASECHARGE
+ VendorBaseCharge( static_cast( std::stoi( value )));
+ break;
+ case 410: // VENDORCHARGEHOURS
+ VendorChargeHours( static_cast( std::stoi( value )));
+ break;
+ case 411: // VENDORUSEITEMFEESENABLED
+ VendorUseItemFeesEnabled( ( static_cast( std::stoul( value, nullptr, 0 ) ) >= 1 ? true : false ) );
+ break;
+ case 412: // VENDORITEMFEEDIVISOR
+ VendorItemFeeDivisor( static_cast( std::stoi( value )));
+ break;
+ case 413: // VENDORITEMFEEAMOUNT
+ VendorItemFeeAmount( static_cast( std::stoi( value )));
+ break;
+ case 414: // ONLYRETURNTOBANK
+ OnlyReturnToBank( ( static_cast( std::stoul( value, nullptr, 0 ) ) >= 1 ? true : false ) );
+ break;
+ case 415: // VENDORMAXFUNDS
+ VendorMaxFunds( static_cast( std::stoul( value, nullptr, 0 )));
+ break;
default:
rValue = false;
break;
diff --git a/source/cServerData.h b/source/cServerData.h
index 9bed4047b..030b28036 100644
--- a/source/cServerData.h
+++ b/source/cServerData.h
@@ -212,7 +212,7 @@ class CServerData
// Once over 62, bitsets are costly. std::vector has a special exception in the c++ specificaiton, to minimize wasted space for bools
// These should be updated
- std::bitset<123> boolVals; // Many values stored this way, rather than using bools.
+ std::bitset<126> boolVals; // Many values stored this way, rather than using bools.
std::bitset<64> spawnRegionsFacets; // Used to determine which facets to enable spawn regions for, set in UOX>INI
std::bitset<64> moongateFacets; // Used to determine which facets to enable moongates for, set in UOX>INI
@@ -337,6 +337,15 @@ class CServerData
UI16 petLoyaltyGainOnSuccess; // The default amount of pet loyalty gained on successful use of a pet command
UI16 petLoyaltyLossOnFailure; // The default amount of pet loyalty lost on a failed attempt to use a pet command
+ bool vendorChargesEnabled; // Master toggle for player vendor upkeep charges
+ SI16 vendorBaseCharge; // Flat fee per vendor upkeep charge period
+ SI16 vendorChargeHours; // Number of real hours between vendor upkeep charges
+ bool vendorUseItemFeesEnabled; // Add item-based upkeep fees from listed item prices
+ SI16 vendorItemFeeDivisor; // Listed item value divisor used for vendor item fees
+ SI16 vendorItemFeeAmount; // Fee added per divisor step
+ bool onlyReturnToBank; // returned vendor belongings skip player backpack entirely
+ UI32 vendorMaxFunds; // Max amount of money the vendor can hold in the bank.
+
// SpeedUp
R64 npcWalkingSpeed; // Speed at which walking NPCs move
R64 npcRunningSpeed; // Speed at which running NPCs move
@@ -1387,6 +1396,30 @@ class CServerData
auto APSDelayMaxCap() const -> UI16;
auto APSDelayMaxCap( UI16 newVal ) -> void;
+ auto VendorChargesEnabled( bool value ) -> void;
+ auto VendorChargesEnabled() const -> bool;
+
+ auto VendorBaseCharge( SI16 value ) -> void;
+ SI16 VendorBaseCharge() const;
+
+ auto VendorChargeHours( SI16 value ) -> void;
+ SI16 VendorChargeHours() const;
+
+ auto VendorUseItemFeesEnabled( bool value ) -> void;
+ auto VendorUseItemFeesEnabled() const -> bool;
+
+ auto VendorItemFeeDivisor( SI16 value ) -> void;
+ SI16 VendorItemFeeDivisor() const;
+
+ auto VendorItemFeeAmount( SI16 value ) -> void;
+ SI16 VendorItemFeeAmount() const;
+
+ auto OnlyReturnToBank( bool value ) -> void;
+ auto OnlyReturnToBank() const -> bool;
+
+ auto VendorMaxFunds( UI32 value ) -> void;
+ UI32 VendorMaxFunds() const;
+
SI16 ServerTimeDay() const;
UI08 ServerTimeHours() const;
UI08 ServerTimeMinutes() const;