diff --git a/data/dfndata/creatures/creatures.dfn b/data/dfndata/creatures/creatures.dfn index 77a61f83e..98d677f2b 100644 --- a/data/dfndata/creatures/creatures.dfn +++ b/data/dfndata/creatures/creatures.dfn @@ -2372,13 +2372,13 @@ ANIMAL } [CREATURE 0xa8] -{ Imp - Shadow Fiend +{ Shadow Fiend ICON=0x259f -SOUND_STARTATTACK=0x1a6 -SOUND_IDLE=0x1a7 -SOUND_ATTACK=0x1a8 -SOUND_DEFEND=0x1a9 -SOUND_DIE=0x1aa +SOUND_STARTATTACK=0x37A +SOUND_IDLE=0x37B +SOUND_ATTACK=0x37A +SOUND_DEFEND=0x37F +SOUND_DIE=0x381 ANIM_CASTAREA=0x07,3 ANIM_CASTTARGET=0x07,3 ANIM_ATTACK1=0x04,4 diff --git a/data/dfndata/items/ItemMenu.bulk.dfn b/data/dfndata/items/ItemMenu.bulk.dfn index 7c3e65809..253202779 100644 --- a/data/dfndata/items/ItemMenu.bulk.dfn +++ b/data/dfndata/items/ItemMenu.bulk.dfn @@ -11982,6 +11982,8 @@ ADDITEM=0x0F0C ADDITEM=0x0F0C-b 0x0F0C=greater heal potion ADDITEM=0x0F0C-c +0x0F04=pet bonding potion +ADDITEM=petbondingpotion <=Previous Menu ITEMMENU=8 } diff --git a/data/dfndata/items/magic/potions.dfn b/data/dfndata/items/magic/potions.dfn index 9028e8e03..927a017a8 100644 --- a/data/dfndata/items/magic/potions.dfn +++ b/data/dfndata/items/magic/potions.dfn @@ -393,4 +393,16 @@ script=5041 { get=0x1940 sectionid=0x1940 +} + +[petbondingpotion] +{ +get=base_potion +name=Pet Bonding Potion +id=0x0F04 +colour=2629 +value=80 40 +type=19 +morey=10 +morez=1 } \ No newline at end of file diff --git a/data/dfndata/items/npcmenu.bulk.dfn b/data/dfndata/items/npcmenu.bulk.dfn index 92253c265..80348a184 100644 --- a/data/dfndata/items/npcmenu.bulk.dfn +++ b/data/dfndata/items/npcmenu.bulk.dfn @@ -11,6 +11,10 @@ NPC=gianttoad NPC=scorpion 0x2123=Sewer Rat NPC=sewerrat +0x422F=phoenix +NPC=phoenix +0x2763=bakekitsune +NPC=bakekitsune <=Previous Menu ITEMMENU=5001 } @@ -96,6 +100,10 @@ NPC=whitewolf NPC=squirrel 0x2d98=Ferret NPC=ferret +0x2764=crane +NPC=crane +0x2768=gaman +NPC=gaman <=Previous Menu ITEMMENU=5001 } @@ -145,9 +153,9 @@ Arachnids NPC=dreadspider 0x25c5=Frost Spider NPC=frostspider -0x26c6=Giant Spider +0x20fd=Giant Spider NPC=spider -0x26c4=Giant Black Widow +0x25c4=Giant Black Widow NPC=giantblackwidow 0x25c9=Terathan Drone NPC=teradrone @@ -186,6 +194,8 @@ NPC=moloch NPC=chaosdaemon 0x259f=Shadow Fiend NPC=shadowfiend +0x276d=oni +NPC=oni <=Previous Menu ITEMMENU=5002 } @@ -256,6 +266,8 @@ NPC=shadowironele NPC=valoriteele 0x20d7=Verite Elemental NPC=veriteele +0x20d7=raiju +NPC=raiju <=Previous Menu ITEMMENU=5002 } @@ -343,6 +355,32 @@ NPC=meerwarrior NPC=meereternal 0x0305=a meer captain NPC=meercaptain +0x20de=a lizardman +NPC=lizardman +0x20de=a spear lizardman +NPC=spearlizardman +0x20de=a mace lizardman +NPC=macelizardman +0x20e3=a ratman +NPC=ratman +0x20e3=a mace ratman +NPC=maceratman +0x20e3=a sword ratman +NPC=swordratman +0x20e3=a mage ratman +NPC=ratmanmage +0x20e3=a archer ratman +NPC=ratmanarcher +0x276b=a kappa +NPC=kappa +0x2772=a yomotsu elder +NPC=yomotsuelder +0x2773=a yomotsu priest +NPC=yomotsupriest +0x281b=a yomotsu warrior +NPC=yomotsuwarrior +0x2767=a fan dancer +NPC=fandancer <=Previous Menu ITEMMENU=5002 } @@ -460,7 +498,9 @@ NPC=etherealswampdragon 0x281c=Fire Beetle NPC=firebeetle 0x276a=hiryu -NPC=giantbeetle +NPC=hiryu +0x276a=lesser hiryu +NPC=lesserhiryu 0x2d95=Reptalon NPC=reptalon 0x2d96=Cu Sidhe @@ -502,10 +542,16 @@ NPC=ophapprentice NPC=ophshaman 0x25ac=Ophidian Matriarch NPC=ophmatriarch +0x25ac=an ophidian justicar +NPC=ophjusticar +0x25ac=an ophidian zealot +NPC=ophzealot 0x25c2=Silver Serpent NPC=silverserpent 0x20fc=Snake NPC=snake +0x2771=a yamandon +NPC=yamandon <=Previous Menu ITEMMENU=5002 } @@ -543,6 +589,12 @@ NPC=mummy NPC=rottingcorpse 0x2591=a Ghoul NPC=ghoul +0x25c7=a spectral armor +NPC=spectralarmor +0x276c=lady of the snow +NPC=ladyofthesnow +0x276e=revenant lion +NPC=revenantlion <=Previous Menu ITEMMENU=5002 } @@ -680,6 +732,8 @@ NPC=f_waitress NPC=f_weaponsmith 0x2107=Weaver NPC=f_weaver +0x2107=Veterinarian +NPC=f_veterinarian <=Previous Menu ITEMMENU=5003 } @@ -816,6 +870,8 @@ NPC=m_waiter NPC=m_weaponsmith 0x2106=Weaver NPC=m_weaver +0x2106=Veterinarian +NPC=m_veterinarian <=Previous Menu ITEMMENU=5003 } diff --git a/data/dfndata/npc/femalevendors.dfn b/data/dfndata/npc/femalevendors.dfn index 1f2aefbf2..9607b8906 100644 --- a/data/dfndata/npc/femalevendors.dfn +++ b/data/dfndata/npc/femalevendors.dfn @@ -1080,4 +1080,30 @@ SHOPLIST=BlacksmithShopping SCRIPT=3214 CUSTOMINTTAG=bodType 1 CUSTOMINTTAG=bodSubtype 3 +} + +[f_veterinarian] +{ +GET=basevendor +NAMELIST=2 +TITLE=the veterinarian +ID=0x0191 +EQUIPITEM=listobject15 +HAIRCOLOR=15 +EQUIPITEM=0x0e8a +EQUIPITEM=listobject40 +COLORLIST=5 +EQUIPITEM=listobject41 +COLORLIST=5 +EQUIPITEM=listobject42 +COLORLIST=5 +EQUIPITEM=listobject43 +EQUIPITEM=listobject45 +COLORLIST=5 +ANATOMY=750 980 +FORENSICS=650 880 +HEALING=640 1000 +SPIRITSPEAK=600 830 +SHOPLIST=HealerShopping +SCRIPT=3109 } \ No newline at end of file diff --git a/data/dfndata/npc/malevendors.dfn b/data/dfndata/npc/malevendors.dfn index 749f5db87..0c76186c4 100644 --- a/data/dfndata/npc/malevendors.dfn +++ b/data/dfndata/npc/malevendors.dfn @@ -1128,4 +1128,32 @@ SHOPLIST=BlacksmithShopping SCRIPT=3214 CUSTOMINTTAG=bodType 1 CUSTOMINTTAG=bodSubtype 3 +} + +[m_veterinarian] +{ +GET=basevendor +NAMELIST=1 +TITLE=the veterinarian +ID=0x0190 +EQUIPITEM=listobject13 +HAIRCOLOR=15 +EQUIPITEM=listobject14 +COLORMATCHHAIR +EQUIPITEM=0x0e8a +EQUIPITEM=listobject20 +COLORLIST=5 +EQUIPITEM=listobject21 +COLORLIST=5 +EQUIPITEM=listobject22 +COLORLIST=5 +EQUIPITEM=listobject23 +EQUIPITEM=listobject25 +COLORLIST=5 +ANATOMY=750 980 +FORENSICS=650 880 +HEALING=640 1000 +SPIRITSPEAK=600 830 +SHOPLIST=HealerShopping +SCRIPT=3109 } \ No newline at end of file diff --git a/data/dfndata/npc/vendors.dfn b/data/dfndata/npc/vendors.dfn index 5e558423f..0f8aa03a4 100644 --- a/data/dfndata/npc/vendors.dfn +++ b/data/dfndata/npc/vendors.dfn @@ -188,10 +188,10 @@ GET=m_tinker f_tinker GET=m_vagabond f_vagabond } -//[vet] -//{Note: Not implemented Yet -//GET=m_vet f_vet -//} +[veterinarian] +{ +GET=m_veterinarian f_veterinarian +} [waiter] { diff --git a/data/dfndata/spawn/felucca/spawn_felucca_town_britain.dfn b/data/dfndata/spawn/felucca/spawn_felucca_town_britain.dfn index 3ef56f445..9522b2820 100644 --- a/data/dfndata/spawn/felucca/spawn_felucca_town_britain.dfn +++ b/data/dfndata/spawn/felucca/spawn_felucca_town_britain.dfn @@ -79,7 +79,7 @@ CALL=1 [REGIONSPAWN 5] { NAME=Britannia Animal Care -NPC=animaltrainer +NPC=veterinarian MAXNPCS=1 X1=1508 Y1=1565 diff --git a/data/dfndata/spawn/felucca/spawn_felucca_town_delucia.dfn b/data/dfndata/spawn/felucca/spawn_felucca_town_delucia.dfn index c18ee82d0..4c062258f 100644 --- a/data/dfndata/spawn/felucca/spawn_felucca_town_delucia.dfn +++ b/data/dfndata/spawn/felucca/spawn_felucca_town_delucia.dfn @@ -343,3 +343,18 @@ MINTIME=30 MAXTIME=60 CALL=5 } + +[REGIONSPAWN 1536] +{ +NAME=delucia - stables +NPC=veterinarian +MAXNPCS=1 +X1=5291 +Y1=4004 +X2=5299 +Y2=4011 +WORLD=0 +MINTIME=480 +MAXTIME=600 +CALL=1 +} diff --git a/data/dfndata/spawn/felucca/spawn_felucca_town_minoc.dfn b/data/dfndata/spawn/felucca/spawn_felucca_town_minoc.dfn index 1c89f845c..8a71408ff 100644 --- a/data/dfndata/spawn/felucca/spawn_felucca_town_minoc.dfn +++ b/data/dfndata/spawn/felucca/spawn_felucca_town_minoc.dfn @@ -617,3 +617,18 @@ MINTIME=480 MAXTIME=600 CALL=1 } + +[REGIONSPAWN 544] +{ +NAME=Minoc - Stables +NPC=veterinarian +MAXNPCS=1 +X1=2515 +Y1=375 +X2=2518 +Y2=377 +WORLD=0 +MINTIME=480 +MAXTIME=600 +CALL=1 +} \ No newline at end of file diff --git a/data/dfndata/spawn/felucca/spawn_felucca_town_trinsic.dfn b/data/dfndata/spawn/felucca/spawn_felucca_town_trinsic.dfn index 4a89dcb79..ccc88165b 100644 --- a/data/dfndata/spawn/felucca/spawn_felucca_town_trinsic.dfn +++ b/data/dfndata/spawn/felucca/spawn_felucca_town_trinsic.dfn @@ -757,3 +757,18 @@ MINTIME=60 MAXTIME=90 CALL=1 } + +[REGIONSPAWN 1154] +{ +NAME=Trinsic animal trainer +NPC=veterinarian +MAXNPCS=1 +X1=1821 +Y1=2732 +X2=1823 +Y2=2735 +WORLD=0 +MINTIME=480 +MAXTIME=600 +CALL=1 +} \ No newline at end of file diff --git a/data/dfndata/spawn/trammel/spawn_trammel_town_britain.dfn b/data/dfndata/spawn/trammel/spawn_trammel_town_britain.dfn index f24fafca7..a9fdff256 100644 --- a/data/dfndata/spawn/trammel/spawn_trammel_town_britain.dfn +++ b/data/dfndata/spawn/trammel/spawn_trammel_town_britain.dfn @@ -79,7 +79,7 @@ CALL=1 [REGIONSPAWN 7005] { NAME=Britannia Animal Care -NPC=animaltrainer +NPC=veterinarian MAXNPCS=1 X1=1508 Y1=1565 diff --git a/data/dfndata/spawn/trammel/spawn_trammel_town_delucia.dfn b/data/dfndata/spawn/trammel/spawn_trammel_town_delucia.dfn index 9422ac9cd..2f56898e2 100644 --- a/data/dfndata/spawn/trammel/spawn_trammel_town_delucia.dfn +++ b/data/dfndata/spawn/trammel/spawn_trammel_town_delucia.dfn @@ -343,3 +343,18 @@ MINTIME=30 MAXTIME=60 CALL=5 } + +[REGIONSPAWN 8536] +{ +NAME=delucia - stables +NPC=veterinarian +MAXNPCS=1 +X1=5291 +Y1=4004 +X2=5299 +Y2=4011 +WORLD=1 +MINTIME=480 +MAXTIME=600 +CALL=1 +} diff --git a/data/dfndata/spawn/trammel/spawn_trammel_town_minoc.dfn b/data/dfndata/spawn/trammel/spawn_trammel_town_minoc.dfn index b825b637a..5d5e3d2bf 100644 --- a/data/dfndata/spawn/trammel/spawn_trammel_town_minoc.dfn +++ b/data/dfndata/spawn/trammel/spawn_trammel_town_minoc.dfn @@ -617,3 +617,18 @@ MINTIME=480 MAXTIME=600 CALL=1 } + +[REGIONSPAWN 7544] +{ +NAME=Minoc - Stables +NPC=veterinarian +MAXNPCS=1 +X1=2515 +Y1=375 +X2=2518 +Y2=377 +WORLD=1 +MINTIME=480 +MAXTIME=600 +CALL=1 +} \ No newline at end of file diff --git a/data/dfndata/spawn/trammel/spawn_trammel_town_trinsic.dfn b/data/dfndata/spawn/trammel/spawn_trammel_town_trinsic.dfn index 389279767..c826e17f1 100644 --- a/data/dfndata/spawn/trammel/spawn_trammel_town_trinsic.dfn +++ b/data/dfndata/spawn/trammel/spawn_trammel_town_trinsic.dfn @@ -757,3 +757,18 @@ MINTIME=60 MAXTIME=90 CALL=1 } + +[REGIONSPAWN 8154] +{ +NAME=Trinsic animal trainer +NPC=veterinarian +MAXNPCS=1 +X1=1821 +Y1=2732 +X2=1823 +Y2=2735 +WORLD=1 +MINTIME=480 +MAXTIME=600 +CALL=1 +} \ No newline at end of file diff --git a/data/dictionaries/dictionary.CSY b/data/dictionaries/dictionary.CSY index 1a9c03611..5947374c5 100644 --- a/data/dictionaries/dictionary.CSY +++ b/data/dictionaries/dictionary.CSY @@ -5530,5 +5530,46 @@ 19234=Váš zmatek pominul, nyní můžete vyzbrojit zbraň! 19235=Už nejsi smrtelně zraněný. 19236=Krvácející rány se zahojily, už nekrvácíte! +// [19301-19330] Poutaný a mrtvý mazlíček +19301=Mrtvého mazlíčka nelze vyměnit. +19302=Mrtvý mazlíček nemůže útočit. +19303=Nejste dostatečně zkušení, abyste tohoto mazlíčka vzkřísili. +19304=Vzkřísit lze pouze mazlíčky, kteří jsou poutáni se svým majitelem. +19306=Malíčka jste úspěšně vzkřísili! +19307=Můžete vzkřísit pouze mazlíčky, kteří jsou poutáni se svým majitelem. +19308=Váš mazlíček se s vámi poutal! +19309=Váš mazlíček je mrtvý a na tento příkaz nereaguje. +19310=Přerušíte pouto se svým mazlíčkem. +19311=Jste si jisti, že chcete přerušit pouto se svým mazlíčkem? +19311=Vypuštění ducha mazlíčka ho zničí bez šance na uzdravení. Chcete pokračovat? +19312=Majitel mazlíčka musí být poblíž, aby se mohl pokusit o vzkříšení. +19313=Váš mazlíček si s vámi nemůže vytvořit pouto, dokud se nezvýší vaše schopnost ochočování zvířat. +19314=Váš mazlíček si s vámi nemůže vytvořit pouto, dokud se nezvýší vaše schopnost bushido. +19315=Vzkřísit lze pouze mazlíčky, kteří jsou s majitelem spojeni. +19316=Zvíře musí být ve stejném domě jako vy, aby mohlo být vzkříšeno. +19317=Majitel mazlíčka musí být uvnitř tohoto domu, než budete moci tohoto tvora oživit. +19318=Zaměřte se na mazlíčka, se kterým se chcete spojit. Stiskněte ESC pro zrušení. Tato položka se při úspěšném použití spotřebuje, proto si vybírejte moudře! +19319=Spojení s mazlíčkem zrušeno. +19320=Tento mazlíček je s vámi již spojen. +19321=Toto není váš mazlíček! +19322=Jste si jisti, že chcete svého mazlíčka uvolnit? Přivolaní mazlíčci zmizí trvale! +19323=Na svém cíli nemůžete provádět užitečné činy. +19324=Vyberte mazlíčka pro okamžité spojení. +19325=Neplatný cíl. +19326=Tento tvor musí být před spojením ochočen. +19327=Tento mazlíček je již spojen. +19328=Malíček byl okamžitě spojen! +19329=Připojování domácích mazlíčků je na tomto serveru zakázáno. +19330=Nemáte u sebe dostatek zlata k zaplacení poplatku. +19331=Z vašeho batohu bylo vybráno %i zlata. +19332=Váš mazlíček byl vzkříšen. +19333=[Krotký] +19334=[Svázaný] +19335=svázaný +19336=krotký +19337=nezranitelný +19338=zmrazený +19339=střežený +19340=Duch toho tvora postrádá soudržnost. Zkuste to znovu za pár minut. } EOF diff --git a/data/dictionaries/dictionary.ENG b/data/dictionaries/dictionary.ENG index 943666fd9..354906f35 100644 --- a/data/dictionaries/dictionary.ENG +++ b/data/dictionaries/dictionary.ENG @@ -5534,5 +5534,46 @@ 19234=Your confusion has passed, you may now arm a weapon! 19235=You are no longer mortally wounded. 19236=The bleeding wounds have healed, you are no longer bleeding! +// [19301-19340] Bonded and Dead pet +19301=You may not trade a dead pet. +19302=The dead pet cannot attack. +19303=You are not skilled enough to resurrect that pet. +19304=Only pets bonded with their owner can be resurrected. +19306=You successfully resurrected the pet! +19307=Thou can only resurrect pets that are bonded with their owner. +19308=Your pet has bonded with you! +19309=Your pet is dead and will not respond to that command. +19310=You break the bond with your pet. +19311=Are you sure you want to break the bond with your pet? +19311=Releasing a ghost pet will destroy it, with no chance of recovery. Do you wish to continue? +19312=The pet's owner must be nearby to attempt resurrection. +19313=Your pet cannot form a bond with you until your animal taming ability has risen. +19314=Your pet cannot form a bond with you until your bushido ability has risen. +19315=Only pets bonded with their owner can be resurrected. +19316=The animal must be in the same house as you to be resurrected. +19317=The pet's owner must be inside this house before you can revive this creature. +19318=Target the pet you wish to bond with. Press ESC to cancel. This item is consumed on successful use, so choose wisely! +19319=Pet bonding canceled. +19320=That pet is already bonded to you. +19321=This is not your pet! +19322=Are you sure you want to release your pet? Summoned Pets will vanish permanently! +19323=You can not perform beneficial acts on your target. +19324=Select a pet to instantly bond. +19325=Invalid target. +19326=That creature must be tamed before it can be bonded. +19327=That pet is already bonded. +19328=Pet has been instantly bonded! +19329=Pet bonding is disabled on this server. +19330=You do not have enough gold on you to pay the fee. +19331=%i gold has been withdrawn from your backpack. +19332=Your pet has been resurrected. +19333=[Tame] +19334=[Bonded] +19335=(bonded) +19336=(tame) +19337=(invulnerable) +19338=(frozen) +19339=(guarded) +19340=That creature’s spirit lacks cohesion. Try again in a few minutes. } EOF diff --git a/data/dictionaries/dictionary.FRE b/data/dictionaries/dictionary.FRE index a4cfa47a4..f6634d0e9 100644 --- a/data/dictionaries/dictionary.FRE +++ b/data/dictionaries/dictionary.FRE @@ -2772,7 +2772,7 @@ 2794=pour %i d'or 2795=Vous récupérez quelques pierres précieuses dans les décombres. 2796=* Votre corps convulse sous l'effet du choc électrique * -2797=Vous ne pouvez pas l'utiliser en vol ! +2797=Vous ne pouvez pas l'utiliser en vol ! 2798=Vous avez été cloué au sol. // [2800-2999] House script 2800=Propriétaire de la maison : @@ -3485,11 +3485,11 @@ 5501=La décoration doit être placée à côté d'un mur. 5502=Cet emplacement n'est pas dans votre maison. 5503=La douleur extrême dans vos dents s'atténue. -5504=Vous avez l'impression de pouvoir manger autant que vous le souhaitez ! +5504=Vous avez l'impression de pouvoir manger autant que vous le souhaitez ! 5506=Voulez-vous restituer cette décoration ? 5507=Rapport d'erreur au GM 5508=Vous venez d'être frappé par une boule de neige ! -5509=Vous lancez la boule de neige et atteignez la cible ! +5509=Vous lancez la boule de neige et atteignez la cible ! 5510=Vous ne pouvez lancer une boule de neige que sur quelque chose qui peut en renvoyer une. 5511=La neige n'est pas encore prête à être tassée. Continue d'essayer. 5512=Vous mettez soigneusement la neige en boule... @@ -3497,9 +3497,9 @@ 5700=Il semble vide. 5701=Cette chasse au trésor est déjà terminée. 5702=Le trésor est marqué par l'épingle rouge. Prenez une pelle et allez déterrer ! -5703=Vous avez décodé la carte ! +5703=Vous avez décodé la carte ! 5704=carte au trésor en lambeaux -5705=Vous ne pouvez pas encore décoder cette carte ! +5705=Vous ne pouvez pas encore décoder cette carte ! 5706=Vous êtes déjà en train de creuser un trésor. 5707=Où souhaitez-vous creuser ? 5708=Vous arrêtez de creuser parce que quelque chose se trouve directement au-dessus du coffre au trésor @@ -4546,7 +4546,7 @@ 9403=Utilisations restantes : %i 9404= avec %d charges 9405=Vous creusez pendant un moment mais vous ne parvenez pas à en trouver de qualité suffisante pour le soufflage du verre. -9406=Vous extrayez soigneusement de la pierre exploitable de la veine de minerai ! +9406=Vous extrayez soigneusement de la pierre exploitable de la veine de minerai ! 9407=Vous déterrez soigneusement du sable de qualité suffisante pour le soufflage du verre. 9408=Seul un grand maître mineur peut apprendre de ce livre. 9409=Vous avez déjà appris cette information. @@ -5560,7 +5560,7 @@ 19009=Concentrez-vous et demandez à nouveau. 19010=Au-delà du moindre doute. 19011=Impossible de prévoir. -19012=Mangez au Sanglier Bleu ! +19012=Mangez au Sanglier Bleu ! 19013=Pourquoi me demandes-tu ? 19014=Le sol même s'effondrera devant une chose 19015="Ce n'était pas clair. @@ -5595,13 +5595,13 @@ 19058=La Boule de Cristal se remplit d'une brume grise. Vous n'êtes pas le propriétaire de l'animal que vous essayez d'invoquer. 19059=Vous devez attendre quelques secondes avant de pouvoir invoquer votre animal de compagnie. 19060=La Boule de Cristal se remplit d'une brume verte. Votre animal a été invoqué. -19061=animal lié : +19061=animal lié : 19062=Cette boule de cristal n'est plus liée à un animal de compagnie. -19063=Le nouveau nom de l'animal est : %s +19063=Le nouveau nom de l'animal est : %s 19064=Cela ne semble pas être un animal de compagnie valide. // [19070-19080] Sac d'envoi 19070=Le sac d'envoi doit être dans votre sac à dos. -19071=Erreur : Aucune banque valide trouvée ! Veuillez contacter un directeur général pour obtenir de l'aide. +19071=Erreur : Aucune banque valide trouvée ! Veuillez contacter un directeur général pour obtenir de l'aide. 19072=Vous ne pouvez envoyer que des objets de votre sac à dos vers votre boîte bancaire. 19073=Vous ne pouvez pas envoyer un conteneur via le sac d'envoi. 19074=Votre boîte bancaire est pleine. @@ -5621,72 +5621,111 @@ 19110=Vous devez avoir l'objet dans votre sac à dos ou verrouillé pour pouvoir l'utiliser. 19111=Vous devez d'abord planter une graine dans le bol 19112=Vous taillez la plante. Cette plante ne produira plus de ressources ni de graines, mais ne nécessitera aucun entretien. -19113=Vous ne pouvez pas récolter le pollen d'une plante mutée ! +19113=Vous ne pouvez pas récolter le pollen d'une plante mutée ! 19114=Trop tôt pour récolter le pollen -19115=Vous ne pouvez pas récolter le pollen d'une plante en mauvaise santé ! +19115=Vous ne pouvez pas récolter le pollen d'une plante en mauvaise santé ! 19116=Ciblez la plante avec laquelle vous souhaitez effectuer une pollinisation croisée. -19117=Vous ne pouvez polliniser que d'autres plantes spécialement cultivées ! +19117=Vous ne pouvez polliniser que d'autres plantes spécialement cultivées ! 19118=Cette plante n'est pas en phase de floraison. Vous ne pouvez pas le polliniser ! -19119=Vous ne pouvez pas polliniser une plante en mauvaise santé ! -19120=Vous ne pouvez pas effectuer de pollinisation croisée avec une plante mutée ! -19121=Cette plante a déjà été pollinisée ! +19119=Vous ne pouvez pas polliniser une plante en mauvaise santé ! +19120=Vous ne pouvez pas effectuer de pollinisation croisée avec une plante mutée ! +19121=Cette plante a déjà été pollinisée ! 19122=Vous pollinisez la plante avec son propre pollen. 19123=Vous avez réussi la pollinisation croisée de la plante. -19124=Cette plante n'a pas de graines à récolter ! +19124=Cette plante n'a pas de graines à récolter ! 19125=Vous récoltez les graines de la plante. 19126=Vous ne pouvez pas ajouter d'eau à cette usine. 19127=Vous adoucissez la saleté avec de l'eau. -19128=Vous ne pouvez pas utiliser ça sur une plante ! +19128=Vous ne pouvez pas utiliser ça sur une plante ! 19129=Vous n'avez pas de potions puissantes de ce type dans votre pack. -19130=Vous ne devriez verser des potions que sur une plante ou une graine ! +19130=Vous ne devriez verser des potions que sur une plante ou une graine ! 19131=La plante est déjà imbibée de ce type de potion ! 19132=Vous devez avoir l'objet dans votre sac à dos pour l'utiliser. 19133=Choisissez une parcelle de terre à ramasser. -19134=Vous avez besoin de plus de terre pour remplir un bol de plante ! +19134=Vous avez besoin de plus de terre pour remplir un bol de plante ! 19135=Vous remplissez le bol de terre fraîche. 19136=Vous aurez envie de ramasser de la terre fraîche afin de faire pousser une plante saine ! 19137=Choisissez un bol de terre pour y planter cette graine. -19138=Vous devez utiliser une graine sur un sol préparé ! -19139=Ce bol de terre contient déjà une graine %s ! +19138=Vous devez utiliser une graine sur un sol préparé ! +19139=Ce bol de terre contient déjà une graine %s ! 19140=La saleté doit d'abord être ramollie. 19141=Vous plantez la graine dans le bol de terre. -19142=Vous devez utiliser une graine sur un bol de terre ! +19142=Vous devez utiliser une graine sur un bol de terre ! // [19201-19300] Coups spéciaux 19201=Vous avez besoin de %i compétence d'arme pour effectuer cette attaque 19202=Vous avez besoin de %i mana pour effectuer cette attaque 19203=Votre attaque pénètre leur armure ! -19204=Le coup a pénétré votre armure ! +19204=Le coup a pénétré votre armure ! 19205=Votre cible saigne ! -19206=Vous saignez ! +19206=Vous saignez ! 19207=Vous saignez abondamment 19208=%i saigne abondamment -19209=Vous avez provoqué une commotion cérébrale ! -19210=Vous vous sentez désorienté ! -19211=Vous avez porté un coup fatal ! -19212=Vous subissez des dégâts supplémentaires grâce à l'attaque écrasante ! +19209=Vous avez provoqué une commotion cérébrale ! +19210=Vous vous sentez désorienté ! +19211=Vous avez porté un coup fatal ! +19212=Vous subissez des dégâts supplémentaires grâce à l'attaque écrasante ! 19213=Vous ne pouvez pas désarmer votre adversaire. -19214=Votre cible n'est déjà pas armée ! -19215=Vous désarmez votre arme ! +19214=Votre cible n'est déjà pas armée ! +19215=Vous désarmez votre arme ! 19216=Votre arme a été désarmée ! -19217=Vous ne pouvez pas effectuer cette attaque en étant monté ou en vol ! +19217=Vous ne pouvez pas effectuer cette attaque en étant monté ou en vol ! 19218=Cette attaque ne fonctionne que sur des cibles montées ou volantes 19219=La force de votre attaque les a déloges de leur monture ! 19220=Vous avez été renversé de votre monture par %i ! -19221=Votre arme doit contenir une dose de poison pour effectuer une frappe contagieuse ! +19221=Votre arme doit contenir une dose de poison pour effectuer une frappe contagieuse ! 19222=Votre frappe précise a augmenté le niveau du poison de 1 19223=Le poison semble très efficace ! 19224=Vous avez empoisonné votre cible : %i -19225=%i : vous a empoisonné ! -19226=Vous infligez une blessure mortelle ! -19227=Vous avez été mortellement blessé ! +19225=%i : vous a empoisonné ! +19226=Vous infligez une blessure mortelle ! +19227=Vous avez été mortellement blessé ! 19228=Votre cible résiste à la paralysie. 19229=Vous résistez à la paralysie. -19230=L'attaque vous a temporairement paralysé ! +19230=L'attaque vous a temporairement paralysé ! 19231=Vous n'avez pas la furtivité requise pour effectuer cette attaque 19232=Vous frappez et vous cachez dans l'ombre ! 19233=Vous êtes étourdi par l'attaque et votre attaquant disparaît ! -19234=Votre confusion est passée, vous pouvez maintenant armer une arme ! +19234=Votre confusion est passée, vous pouvez maintenant armer une arme ! 19235=Vous n'êtes plus mortellement blessé. -19236=Les plaies saignantes ont guéri, vous ne saignez plus ! +19236=Les plaies saignantes ont guéri, vous ne saignez plus ! +// [19301-19330] Animal lié et mort +19301=Vous ne pouvez pas échanger un animal mort. +19302=L'animal mort ne peut pas attaquer. +19303=Vous n'êtes pas assez compétent pour ressusciter cet animal. +19304=Seuls les animaux liés à leur propriétaire peuvent être ressuscités. +19306=Vous avez ressuscité l'animal avec succès ! +19307=Vous ne pouvez ressusciter que les animaux liés à leur propriétaire. +19308=Votre animal s'est lié à vous ! +19309=Votre animal est mort et ne répondra pas à cet ordre. +19310=Vous avez rompu le lien avec votre animal. +19311=Êtes-vous sûr de vouloir rompre le lien avec votre animal ? +19311=Libérer un animal fantôme le détruira, sans aucune chance de récupération. Souhaitez-vous continuer ? 19312=Le propriétaire de l'animal doit être à proximité pour tenter la résurrection. +19313=Votre animal ne peut pas se lier à vous tant que votre compétence d'apprivoisement n'est pas activée. +19314=Votre animal ne peut pas se lier à vous tant que votre compétence de bushido n'est pas activée. +19315=Seuls les animaux liés à leur propriétaire peuvent être ressuscités. +19316=L'animal doit se trouver dans la même maison que vous pour être ressuscité. +19317=Le propriétaire de l'animal doit être à l'intérieur de cette maison pour que vous puissiez ressusciter cette créature. +19318=Ciblez l'animal avec lequel vous souhaitez vous lier. Appuyez sur ÉCHAP pour annuler. Cet objet est consommé en cas d'utilisation réussie, alors choisissez judicieusement ! +19319=Lien avec l'animal annulé. +19320=Cet animal est déjà lié à vous. +19321=Ce n'est pas votre animal ! 19322=Êtes-vous sûr de vouloir libérer votre familier ? Les familiers invoqués disparaîtront définitivement ! +19323=Vous ne pouvez pas effectuer d'actions bénéfiques sur votre cible. +19324=Sélectionnez un familier pour créer un lien instantané. +19325=Cible invalide. +19326=Cette créature doit être apprivoisée avant de pouvoir créer un lien. +19327=Ce familier est déjà lié. +19328=Le lien a été instantanément créé ! +19329=Le lien avec les animaux est désactivé sur ce serveur. +19330=Vous n'avez pas assez d'or sur vous pour payer les frais. +19331=%i pièces d'or ont été retirées de votre sac à dos. +19332=Votre familier a été ressuscité. +19333=[Apprivoisé] +19334=[Lié] +19335=lié +19336=apprivoisé +19337=invulnérable +19338=gelé +19339=gardé +19340=L'esprit de cette créature manque de cohésion. Réessayez dans quelques minutes. } EOF diff --git a/data/dictionaries/dictionary.GER b/data/dictionaries/dictionary.GER index bc918bdbe..6b337c5f8 100644 --- a/data/dictionaries/dictionary.GER +++ b/data/dictionaries/dictionary.GER @@ -5532,5 +5532,46 @@ 19234=Ihre Verwirrung ist vorüber, Sie können jetzt eine Waffe bewaffnen! 19235=Sie sind nicht mehr tödlich verwundet. 19236=Die blutenden Wunden sind verheilt, Sie bluten nicht mehr! +// [19301-19330] Verbundenes und totes Haustier +19301=Du kannst ein totes Haustier nicht tauschen. +19302=Das tote Haustier kann nicht angreifen. +19303=Du bist nicht geschickt genug, um dieses Haustier wiederzubeleben. +19304=Nur Haustiere, die mit ihrem Besitzer verbunden sind, können wiederbelebt werden. +19306=Du hast das Haustier erfolgreich wiederbelebt! +19307=Du kannst nur Haustiere wiederbeleben, die mit ihrem Besitzer verbunden sind. +19308=Dein Haustier hat eine Bindung zu dir aufgebaut! +19309=Dein Haustier ist tot und reagiert nicht auf diesen Befehl. +19310=Du löst die Bindung zu deinem Haustier. +19311=Möchtest du die Bindung zu deinem Haustier wirklich lösen? +19311=Das Freilassen eines Geister-Haustiers zerstört es und kann nicht wiederhergestellt werden. Möchten Sie fortfahren? +19312=Der Besitzer des Haustiers muss in der Nähe sein, um die Wiederbelebung zu versuchen. +19313=Ihr Haustier kann keine Bindung zu Ihnen aufbauen, bis Ihre Fähigkeit zur Tierzähmung gestiegen ist. +19314=Ihr Haustier kann keine Bindung zu Ihnen aufbauen, bis Ihre Bushido-Fähigkeit gestiegen ist. +19315=Nur Haustiere, die mit ihrem Besitzer verbunden sind, können wiederbelebt werden. +19316=Das Tier muss sich im selben Haus wie Sie befinden, um wiederbelebt zu werden. +19317=Der Besitzer des Haustiers muss sich in diesem Haus befinden, bevor Sie dieses Tier wiederbeleben können. +19318=Zielen Sie auf das Haustier, mit dem Sie eine Bindung aufbauen möchten. Drücken Sie ESC, um abzubrechen. Dieser Gegenstand wird bei erfolgreicher Verwendung verbraucht, wählen Sie also mit Bedacht! +19319=Tierbindung abgebrochen. +19320=Das Haustier ist bereits mit Ihnen verbunden. +19321=Das ist nicht dein Haustier! +19322=Möchtest du dein Haustier wirklich freilassen? Beschworene Haustiere verschwinden dauerhaft! +19323=Du kannst keine positiven Handlungen an deinem Ziel vornehmen. +19324=Wähle ein Haustier für die sofortige Bindung aus. +19325=Ungültiges Ziel. +19326=Diese Kreatur muss gezähmt werden, bevor sie gebunden werden kann. +19327=Das Haustier ist bereits gebunden. +19328=Das Haustier wurde sofort gebunden! +19329=Die Haustierbindung ist auf diesem Server deaktiviert. +19330=Du hast nicht genug Gold dabei, um die Gebühr zu bezahlen. +19331=%i Gold wurden aus deinem Rucksack entnommen. +19332=Dein Haustier wurde wiederbelebt. +19333=[Zahm] +19334=[Gebunded] +19335=gebunden +19336=zahm +19337=unverwundbar +19338=eingefroren +19339=bewacht +19340=Der Geist dieser Kreatur ist nicht einheitlich. Versuchen Sie es in ein paar Minuten erneut. } EOF diff --git a/data/dictionaries/dictionary.ITA b/data/dictionaries/dictionary.ITA index 2a8507e23..e04b9b67c 100644 --- a/data/dictionaries/dictionary.ITA +++ b/data/dictionaries/dictionary.ITA @@ -5532,5 +5532,45 @@ 19234=La tua confusione è passata, ora puoi armare un'arma! 19235=Non sei più ferito a morte. 19236=Le ferite sanguinanti sono guarite, non stai più sanguinando! +// [19301-19330] Animale domestico legato e morto +19301=Non puoi scambiare un animale domestico morto. +19302=L'animale domestico morto non può attaccare. +19303=Non sei abbastanza abile da resuscitare quell'animale. +19304=Solo gli animali domestici legati al loro proprietario possono essere resuscitati. +19306=Hai resuscitato con successo l'animale! +19307=Puoi resuscitare solo gli animali domestici legati al loro proprietario. +19308=Il tuo animale domestico si è legato a te! +19309=Il tuo animale domestico è morto e non risponderà a quel comando. +19310=Hai interrotto il legame con il tuo animale domestico. +19311=Sei sicuro di voler interrompere il legame con il tuo animale domestico? +19311=Liberare un animale domestico fantasma lo distruggerà, senza possibilità di recupero. Vuoi continuare? +19312=Il proprietario dell'animale domestico deve essere nelle vicinanze per tentare la resurrezione. +19313=Il tuo animale domestico non può creare un legame con te finché la tua capacità di addomesticamento non è aumentata. +19314=Il tuo animale domestico non può creare un legame con te finché la tua capacità di bushido non è aumentata. +19315=Solo gli animali domestici legati al loro proprietario possono essere resuscitati. +19316=L'animale deve trovarsi nella stessa casa in cui si trova per essere resuscitato. +19317=Il proprietario dell'animale domestico deve essere all'interno di questa casa prima di poter rianimare questa creatura. +19318=Scegli l'animale domestico con cui desideri creare un legame. Premi ESC per annullare. Questo oggetto si consuma con l'uso riuscito, quindi scegli con saggezza! +19319=Legame con l'animale domestico annullato. +19320=Quell'animale domestico è già legato a te. 19321=Questo non è il tuo animale domestico! +19322=Sei sicuro di voler liberare il tuo animale domestico? Gli animali domestici evocati spariranno definitivamente! +19323=Non puoi compiere azioni benefiche sul tuo bersaglio. +19324=Seleziona un animale domestico per creare un legame immediato. +19325=Obiettivo non valido. +19326=Quella creatura deve essere addomesticata prima di poter essere creata. +19327=Quell'animale domestico è già creato. +19328=L'animale domestico è stato creato un legame immediato! +19329=Il legame con gli animali domestici è disabilitato su questo server. +19330=Non hai abbastanza oro con te per pagare la tassa. +19331=%i oro sono stati prelevati dal tuo zaino. +19332=Il tuo animale domestico è stato resuscitato. +19333=[Addomesticato] +19334=[Legato] +19335=legato +19336=addomesticato +19337=invulnerabile +19338=congelato +19339=sorvegliato +19340=Lo spirito di quella creatura manca di coesione. Riprova tra qualche minuto. } EOF diff --git a/data/dictionaries/dictionary.POL b/data/dictionaries/dictionary.POL index 0e17a9153..579624535 100644 --- a/data/dictionaries/dictionary.POL +++ b/data/dictionaries/dictionary.POL @@ -5531,5 +5531,45 @@ 19234=Twoje zamieszanie minęło, możesz teraz uzbroić broń! 19235=Nie jesteś już śmiertelnie ranny. 19236=Krwawiące rany się zagoiły, już nie krwawisz! +// [19301-19330] Związany i martwy zwierzak +19301=Nie możesz handlować martwym zwierzakiem. +19302=Martwy zwierzak nie może atakować. +19303=Nie masz wystarczających umiejętności, aby wskrzesić tego zwierzaka. +19304=Można wskrzesić tylko zwierzaki związane ze swoim właścicielem. +19306=Pomyślnie wskrzesiłeś zwierzaka! +19307=Możesz wskrzesić tylko zwierzaki związane ze swoim właścicielem. +19308=Twój zwierzak nawiązał z tobą więź! +19309=Twój zwierzak nie żyje i nie zareaguje na to polecenie. +19310=Zrywasz więź ze swoim zwierzakiem. +19311=Czy na pewno chcesz zerwać więź ze swoim zwierzakiem? +19311=Uwolnienie ducha zwierzaka zniszczy go, bez szansy na odzyskanie. Czy chcesz kontynuować? +19312=Właściciel zwierzaka musi być w pobliżu, aby podjąć próbę wskrzeszenia. +19313=Twój zwierzak nie może nawiązać z tobą więzi, dopóki nie wzrośnie twoja zdolność oswajania zwierząt. +19314=Twój zwierzak nie może nawiązać z tobą więzi, dopóki nie wzrośnie twoja zdolność bushido. +19315=Można wskrzesić tylko zwierzaki związane ze swoim właścicielem. +19316=Zwierzę musi znajdować się w tym samym domu co ty, aby zostać wskrzeszone. +19317=Właściciel zwierzaka musi znajdować się w tym domu, zanim będziesz mógł wskrzesić to stworzenie. +19318=Wybierz zwierzaka, z którym chcesz się związać. Naciśnij ESC, aby anulować. Ten przedmiot zostanie zużyty po pomyślnym użyciu, więc wybieraj mądrze! +19319=Anulowano wiązanie ze zwierzakiem. +19320=Ten zwierzak jest już związany z tobą. +19321=To nie jest twój zwierzak! 19322=Czy na pewno chcesz uwolnić swojego zwierzaka? Przywołane zwierzaki znikną na stałe! +19323=Nie możesz wykonywać korzystnych działań na swoim celu. +19324=Wybierz zwierzaka, aby natychmiast się z nim związać. +19325=Nieprawidłowy cel. +19326=To stworzenie musi zostać oswojone, zanim będzie można je związać. +19327=To zwierzę jest już związane. +19328=Zwierzę zostało natychmiast związane! +19329=Na tym serwerze łączenie zwierząt jest wyłączone. +19330=Nie masz przy sobie wystarczającej ilości złota, aby zapłacić opłatę. +19331=Z twojego plecaka pobrano %i złota. +19332=Twój pupil został wskrzeszony. +19333=[Oswojony] +19334=[Związany] +19335=związany +19336=oswojony +19337=niezniszczalny +19338=zamrożony +19339=strzeżony +19340=Duch tego stworzenia jest pozbawiony spójności. Spróbuj ponownie za kilka minut. } EOF diff --git a/data/dictionaries/dictionary.PTG b/data/dictionaries/dictionary.PTG index cb0b57726..a6e9eccb1 100644 --- a/data/dictionaries/dictionary.PTG +++ b/data/dictionaries/dictionary.PTG @@ -5532,5 +5532,46 @@ 19234=Sua confusão passou, agora você pode armar uma arma! 19235=Você não está mais mortalmente ferido. 19236=As feridas sangrentas sararam, você não está mais sangrando! +// [19301-19330] Animal de estimação Bonded and Dead +19301=Não se pode trocar um animal de estimação morto. +19302=O animal de estimação morto não pode atacar. +19303=Não és habilidoso o suficiente para ressuscitar aquele animal de estimação. +19304=Apenas os animais de estimação ligados ao seu dono podem ser ressuscitados. +19306=Resuscitaste o animal de estimação com sucesso! +19307=Só pode ressuscitar animais de estimação que estejam ligados ao seu dono. +19308=O seu animal de estimação criou um laço consigo! +19309=O seu animal de estimação está morto e não responderá a este comando. +19310=Quebra o vínculo com o seu animal de estimação. +19311=Tem a certeza de que quer quebrar o vínculo com o seu animal de estimação? +19311=Libertar um animal de estimação fantasma irá destruí-lo, sem qualquer hipótese de recuperação. Deseja continuar? +19312=O dono do animal de estimação deve estar por perto para tentar a ressurreição. +19313=O seu animal de estimação não pode criar um vínculo consigo até que a sua capacidade de domesticar animais tenha aumentado. +19314=O seu animal de estimação não pode formar um vínculo consigo até que a sua habilidade bushido tenha aumentado. +19315=Apenas os animais de estimação ligados ao seu dono podem ser ressuscitados. +19316=O animal tem de estar na mesma casa que tu para ser ressuscitado. +19317=O dono do animal de estimação deve estar dentro desta casa antes de poder reviver esta criatura. +19318=Escolha o animal de estimação com o qual pretende criar laços. Prima ESC para cancelar. Este item é consumido com uma utilização bem-sucedida, por isso escolha com sabedoria! +19319=Vínculo de animais de estimação cancelado. +19320=Este animal de estimação já está ligado a si. +19321=Este não é o seu animal de estimação! +19322=Tem a certeza de que pretende libertar o seu animal de estimação? Os animais de estimação invocados desaparecerão permanentemente! +19323=Não pode realizar atos benéficos no seu alvo. +19324=Selecione um animal de estimação para criar um vínculo instantâneo. +19325=Alvo inválido. +19326=Esta criatura deve ser domesticada antes de poder ser ligada. +19327=Este animal de estimação já está vinculado. +19328=O animal de estimação foi ligado instantaneamente! +19329=O vínculo com animais de estimação está desativado neste servidor. +19330=Não tem ouro suficiente para pagar a taxa. +19331=%i ouro foi retirado da sua mochila. +19332=A sua mascote foi ressuscitada. +19333=[Domesticado] +19334=[Ligado] +19335=ligado +19336=domesticado +19337=invulnerável +19338=congelado +19339=protegido +19340=O espírito daquela criatura não tem coesão. Tente novamente dentro de alguns minutos. } EOF diff --git a/data/dictionaries/dictionary.SPA b/data/dictionaries/dictionary.SPA index a2bc7a2b3..4bcdb737c 100644 --- a/data/dictionaries/dictionary.SPA +++ b/data/dictionaries/dictionary.SPA @@ -5532,5 +5532,45 @@ 19234=¡Tu confusión ha pasado, ahora puedes armar un arma! 19235=Ya no estás herido de muerte. 19236=Las heridas sangrantes han sanado, ¡ya no sangras! +// [19301-19330] Mascota vinculada y muerta +19301=No puedes intercambiar una mascota muerta. +19302=La mascota muerta no puede atacar. +19303=No tienes la habilidad suficiente para resucitar a esa mascota. +19304=Solo las mascotas vinculadas con su dueño pueden ser resucitadas. +19306=¡Has resucitado a la mascota con éxito! +19307=Solo puedes resucitar mascotas vinculadas con su dueño. +19308=¡Tu mascota se ha vinculado contigo! +19309=Tu mascota está muerta y no responderá a esa orden. +19310=Rompiste el vínculo con tu mascota. +19311=¿Estás seguro de que quieres romper el vínculo con tu mascota? +19311=Liberar una mascota fantasma la destruirá, sin posibilidad de recuperación. ¿Deseas continuar? +19312=El dueño de la mascota debe estar cerca para intentar la resurrección. +19313=Tu mascota no puede formar un vínculo contigo hasta que tu habilidad de domesticación de animales haya aumentado. +19314=Tu mascota no puede formar un vínculo contigo hasta que tu habilidad de bushido haya aumentado. +19315=Solo las mascotas vinculadas con su dueño pueden resucitar. +19316=El animal debe estar en la misma casa que tú para resucitar. +19317=El dueño de la mascota debe estar dentro de esta casa para que puedas revivir a esta criatura. +19318=Selecciona la mascota con la que deseas vincularte. Pulsa ESC para cancelar. Este objeto se consume al usarlo con éxito, ¡así que elige con cuidado! +19319=Vínculo con la mascota cancelado. +19320=Esa mascota ya está vinculada contigo. 19321=¡Esta no es tu mascota! +19322=¿Seguro que quieres liberar a tu mascota? ¡Las mascotas invocadas desaparecerán permanentemente! +19323=No puedes realizar acciones beneficiosas en tu objetivo. +19324=Selecciona una mascota para vincularla instantáneamente. +19325=Objetivo no válido. +19326=Esa criatura debe ser domesticada antes de vincularla. +19327=Esa mascota ya está vinculada. +19328=¡La mascota ha sido vinculada instantáneamente! +19329=La vinculación de mascotas está deshabilitada en este servidor. +19330=No tienes suficiente oro para pagar la tarifa. +19331=Se ha retirado %i de oro de tu mochila. +19332=Tu mascota ha resucitado. +19333=[Domesticado] +19334=[Unido] +19335=unido +19336=domesticado +19337=invulnerable +19338=congelado +19339=vigilado +19340=El espíritu de esa criatura carece de cohesión. Inténtalo de nuevo en unos minutos. } EOF diff --git a/data/dictionaries/dictionary.ZRO b/data/dictionaries/dictionary.ZRO index a5711b967..43dcf8427 100644 --- a/data/dictionaries/dictionary.ZRO +++ b/data/dictionaries/dictionary.ZRO @@ -5533,5 +5533,45 @@ 19234=Your confusion has passed, you may now arm a weapon! 19235=You are no longer mortally wounded. 19236=The bleeding wounds have healed, you are no longer bleeding! +// [19301-19330] Bonded and Dead pet +19301=You may not trade a dead pet. +19302=The dead pet cannot attack. +19303=You are not skilled enough to resurrect that pet. +19304=Only pets bonded with their owner can be resurrected. +19306=You successfully resurrected the pet! +19307=Thou can only resurrect pets that are bonded with their owner. +19308=Your pet has bonded with you! +19309=Your pet is dead and will not respond to that command. +19310=You break the bond with your pet. +19311=Are you sure you want to break the bond with your pet? +19311=Releasing a ghost pet will destroy it, with no chance of recovery. Do you wish to continue? +19312=The pet's owner must be nearby to attempt resurrection. +19313=Your pet cannot form a bond with you until your animal taming ability has risen. +19314=Your pet cannot form a bond with you until your bushido ability has risen. +19315=Only pets bonded with their owner can be resurrected. +19316=The animal must be in the same house as you to be resurrected. +19317=The pet's owner must be inside this house before you can revive this creature. +19318=Target the pet you wish to bond with. Press ESC to cancel. This item is consumed on successful use, so choose wisely! +19319=Pet bonding canceled. +19320=That pet is already bonded to you. +19321=This is not your pet! +19322=Are you sure you want to release your pet? Summoned Pets will vanish permanently! +19323=You can not perform beneficial acts on your target. +19324=Select a pet to instantly bond. +19325=Invalid target. +19326=That creature must be tamed before it can be bonded. +19327=That pet is already bonded. +19328=Pet has been instantly bonded! +19329=Pet bonding is disabled on this server. +19330=You do not have enough gold on you to pay the fee. +19331=%i gold has been withdrawn from your backpack. +19332=Your pet has been resurrected. +19333=[Tame] +19334=[Bonded] +19335=(bonded) +19336=(tame) +19337=(invulnerable) +19338=(frozen) +19339=(guarded) } EOF diff --git a/data/js/commands/custom/instabond.js b/data/js/commands/custom/instabond.js new file mode 100644 index 000000000..c44622f33 --- /dev/null +++ b/data/js/commands/custom/instabond.js @@ -0,0 +1,49 @@ +//Script to allow GMs to instantly bond a pet. + +function CommandRegistration() +{ + RegisterCommand( "instabond", 10, true ); +} + +function command_INSTABOND( socket, cmdString ) +{ + socket.CustomTarget( 0, GetDictionaryEntry( 19324, socket.language ));//Select a pet to instantly bond. +} + +function onCallback0( socket, ourObj ) +{ + const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); + if( petBondingEnabled > 0 ) + { + if( !ValidateObject(ourObj) || !ourObj.npc ) + { + socket.SysMessage( GetDictionaryEntry( 19325, socket.language ));//Invalid target. + return; + } + + if( !ourObj.tamed ) + { + socket.SysMessage( GetDictionaryEntry( 19326, socket.language ));// That creature must be tamed before it can be bonded. + return; + } + + if( ourObj.GetTag( "isBondedPet" ) == true) + { + socket.SysMessage( GetDictionaryEntry( 19327, socket.language ));// That pet is already bonded. + return; + } + + ourObj.KillJSTimer( 42, 3107 ); + ourObj.SetTag( "bondingStarted", null ); + ourObj.SetTag( "bondingPlayer", null ); + ourObj.SetTag( "isBondedPet", true ); + ourObj.AddScriptTrigger( 3107 );//Add bonding script trigger + ourObj.Refresh(); + + socket.SysMessage( GetDictionaryEntry( 19328, socket.language ));//Pet has been instantly bonded! + } + else + { + socket.SysMessage( GetDictionaryEntry( 19329, socket.language ));//Pet bonding is disabled on this server. + } +} diff --git a/data/js/item/potion.js b/data/js/item/potion.js index 79ba5a411..7a83a4688 100644 --- a/data/js/item/potion.js +++ b/data/js/item/potion.js @@ -278,12 +278,27 @@ function onUseChecked( pUser, iUsed ) pUser.isUsingPotion = true; DoTempEffect( 0, pUser, pUser, 26, 0, 0, 0 ); //Disallow immediately using another potion break; + case 10: // Pet Bonding Potion + switch( iUsed.morez ) + { + case 1: + socket.tempObj = iUsed; + socket.CustomTarget( 1, GetDictionaryEntry( 19318, socket.language )); //"Target the pet you wish to bond with. Press ESC to cancel. This item is consumed on successful use, so choose wisely!" + break; + default: + break; + } + pUser.StaticEffect( 0x376A, 0x09, 0x06 ); + pUser.SoundEffect( 0x01E7, true ); + pUser.isUsingPotion = true; + DoTempEffect( 0, pUser, pUser, 26, 0, 0, 0 ); //Disallow immediately using another potion + break; default: break; } // For potions other than explosion potions, consume potion upon use - if( iUsed.morey != 3 ) + if( iUsed.morey != 3 && iUsed.morey != 10) { pUser.SoundEffect( 0x0030, true ); if( pUser.id > 0x0189 && !pUser.isonhorse ) @@ -371,6 +386,50 @@ function onCallback0( socket, ourObj ) } } +function onCallback1( socket, ourObj ) +{ + var mChar = socket.currentChar; + var iUsed = socket.tempObj; + var cancelCheck = parseInt( socket.GetByte( 11 )); + if( cancelCheck == 255 ) + { + socket.SysMessage( GetDictionaryEntry( 19319, socket.language )); // Pet bonding canceled. + return; + } + + if( ourObj.GetTag( "isBondedPet" )) + { + socket.SysMessage( GetDictionaryEntry( 19320, socket.language )); // That pet is already bonded to you. + } + else if( ourObj.owner != mChar ) + { + socket.SysMessage( GetDictionaryEntry( 19321, socket.language )); // This is not your pet! + } + else + { + ourObj.SetTag( "isBondedPet", true ); + socket.SysMessage( GetDictionaryEntry( 19308, socket.language )); // Your pet has bonded with you! + if( iUsed && iUsed.isItem ) + { + if( iUsed.amount > 1 ) + { + iUsed.amount--; + } + else + { + iUsed.Delete(); + } + } + + // Create empty bottle + var eBottle = CreateDFNItem( socket, mChar, "0x0F0E", 1, "ITEM", true ); + if( eBottle && eBottle.isItem ) + { + eBottle.decay = true; + } + } +} + function onTimer( timerObj, timerID ) { var countdown = 0; diff --git a/data/js/jse_fileassociations.scp b/data/js/jse_fileassociations.scp index 173d576b0..b8a7e3630 100644 --- a/data/js/jse_fileassociations.scp +++ b/data/js/jse_fileassociations.scp @@ -143,6 +143,9 @@ // pets from pet static end 3105=npc/ai/stablemaster.js 3106=npc/pets/mount_restrictions.js +3107=npc/pets/petbonding.js +3108=server/network/0xbf_sendnpcghostmode.js +3109=npc/ai/veterinarian.js // ------------------------ // AI-Stuff @@ -454,6 +457,7 @@ 1063=commands/moongate.js 1064=commands/spawnadmin.js 1065=commands/targeting/xwalk.js +1066=commands/custom/instabond.js } //------------------------------------------- diff --git a/data/js/npc/ai/stablemaster.js b/data/js/npc/ai/stablemaster.js index 3957f9755..b6df3714d 100644 --- a/data/js/npc/ai/stablemaster.js +++ b/data/js/npc/ai/stablemaster.js @@ -443,7 +443,7 @@ function onCallback0( pSock, ourObj ) { pUser.SysMessage( GetDictionaryEntry( 2114, pSock.language )); // You are out of range from the stableMaster. } - else if( !ourObj.tamed || ourObj.isHuman ) + else if( !ourObj.tamed || ourObj.isHuman || ourObj.GetTag( "isPetDead" ) == true ) { stableMaster.TextMessage( GetDictionaryEntry( 2389, pSock.language )); // You can't stable that! } diff --git a/data/js/npc/ai/veterinarian.js b/data/js/npc/ai/veterinarian.js new file mode 100644 index 000000000..6f341c345 --- /dev/null +++ b/data/js/npc/ai/veterinarian.js @@ -0,0 +1,346 @@ +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); +const vetGumpCooldown = 4000; // milliseconds +const scriptID = 3109; + +function inRange( npcVet, player ) +{ + if( !petBondingEnabled ) + return; // Skip if pet bonding is disabled + + if( !ValidateObject( player ) || player.isItem || ( player.isChar && ( player.npc || !player.online || player.dead ))) + return; + + var nearbyPlayersList = []; + var nearbyPlayersListRaw = npcVet.GetTempTag( "vetNearbyPlayersList" ); + if( nearbyPlayersListRaw != 0 && nearbyPlayersListRaw.length > 0 ) + { + nearbyPlayersList = nearbyPlayersListRaw.split( "," ); + } + + var serial = player.serial.toString(); + var found = false; + for( var i = 0; i < nearbyPlayersList.length; ++i ) + { + if( nearbyPlayersList[i] == serial ) + { + found = true; + break; + } + } + + if( !found ) + { + nearbyPlayersList.push( serial ); + } + + npcVet.SetTempTag( "vetNearbyPlayersList", nearbyPlayersList.join( "," )); +} + +function onAISliver( npcVet ) +{ + if( !petBondingEnabled ) + { + return; // Skip if pet bonding is disabled + } + + var now = GetCurrentClock(); + var lastTick = parseInt( npcVet.GetTempTag( "vetLastTick" )); + var cooldown = vetGumpCooldown; // milliseconds + + if(( now - lastTick ) < cooldown ) + return; // Skip if cooldown hasn't passed + + npcVet.SetTempTag( "vetLastTick", now.toString() ); // Update cooldown tracker + + var nearbyPlayersListRaw = npcVet.GetTempTag( "vetNearbyPlayersList" ); + if( nearbyPlayersListRaw == 0 || nearbyPlayersListRaw.length == 0 ) + return; + + // Iterate over nearby players, see if they have any dead pets + var nearbyPlayersList = nearbyPlayersListRaw.split( "," ); + var cleanedList = []; + for( var i = 0; i < nearbyPlayersList.length; ++i ) + { + var serial = parseInt( nearbyPlayersList[i], 10 ); + if( isNaN( serial )) + continue; + + var player = CalcCharFromSer( serial ); + if( !ValidateObject( player )) + continue; + + // Only keep nearby players in memory + if( npcVet.InRange( player, 24 )) + { + cleanedList.push( serial.toString() ); + } + + // Only show gump if player is within range + if( npcVet.InRange( player, 6 ) && player.GetTempTag( "vetGumpOpen" ) != true ) + { + var lastShown = parseInt( player.GetTempTag( "vetGumpLastShown" )); + var cooldown = 10000; + + if(( now - lastShown ) < cooldown ) + continue; + + // Check if player has any dead bonded pets that require resurrection + var deadPetList = GetDeadPetList( player ); + + // Only open gump if there's a dead pet nearby to resurrect + if( deadPetList.length > 0 ) + { + player.SetTempTag( "vetGumpOpen", true ); + player.SetTempTag( "vetGumpLastShown", now.toString() ); + VetResurrectGump( player.socket, deadPetList ); + } + } + else if( !npcVet.InRange( player, 6 )) + { + // Force-close resurrection gump if they're out of range + player.SetTempTag( "vetGumpOpen", null ); + + if( player.socket != null ) + { + let gumpID = scriptID + 0xffff; + player.socket.CloseGump( gumpID, 0 ); + } + } + } + + // Only retain valid, recent, nearby serials + npcVet.SetTempTag( "vetNearbyPlayersList", cleanedList.join( "," )); +} + +// Let players re-trigger the resurrection gump by talking to the vet +function onSpeech( strSaid, pTalking, pTalkingTo ) +{ + if( !pTalking.npc ) + { + // Reset vet/player's gump cooldown if player has dead pets to resurrect + var deadPetList = GetDeadPetList( pTalking ); + if( deadPetList.length > 0 ) + { + pTalkingTo.SetTempTag( "vetLastTick", null ); + pTalking.SetTempTag( "vetGumpLastShown", null ); + } + } + + return 0; +} + +function GetDeadPetList( player ) +{ + var deadPetList = []; + var allPets = player.GetPetList(); + + // Iterate over list of pets + for( var i = 0; i < allPets.length; ++i ) + { + var tempPet = allPets[i]; + if( !ValidateObject( tempPet )) + continue; + + // Only include dead bonded pets that are within short range of the player + if( tempPet.GetTag( "isPetDead" ) && player.InRange( tempPet, 6 )) + { + deadPetList.push( tempPet ); + } + } + + return deadPetList; +} + +function VetResurrectGump( socket, deadPetList ) +{ + var pUser = socket.currentChar; + if( !ValidateObject( pUser )) + return; + + var gump = new Gump; + gump.AddPage( 0 ); + + // Background + gump.AddGump( 0, 0, 0xE10 ); + gump.AddTiledGump(0, 14, 15, 380, 0xE13); + gump.AddTiledGump( 380, 14, 14, 380, 0xE15 ); + gump.AddGump( 0, 381, 0xE16 ); + gump.AddTiledGump(15, 381, 370, 16, 0xE17 ); + gump.AddTiledGump( 15, 0, 370, 16, 0xE11 ); + gump.AddGump( 380, 0, 0xE12 ); + gump.AddGump( 380, 381, 0xE18 ); + gump.AddTiledGump( 15, 15, 365, 370, 0xA40 ); + + // Text + gump.AddXMFHTMLGumpColor( 30, 20, 355, 35, 1113193, false, false, 0xFFFFFF );//Ah, thine pet seems to be in dire condition! I can help thee, but must charge a small fee... + gump.AddXMFHTMLGumpColor( 30, 72, 345, 40, 1113284, false, false, 0x1DB2D );//Please select the pet you wish to resurrect: + gump.AddXMFHTMLGumpColor( 20, 280, 345, 40, 1113286, false, false, 0x1DB2D );//
Your pet will suffer 0.2 points of skill-loss if resurrected in this manner.
+ + // Horizontal lines + gump.AddTiledGump( 95, 62, 200, 1, 0x23C5 ); + gump.AddTiledGump( 15, 325, 365, 1, 0x2393 ); + + // Buttons + gump.AddButton( 110, 343, 0xF7, 0xF8, 1, 0, 1 ); // OK + gump.AddButton( 230, 343, 0xF2, 0xF1, 1, 0, 0 ); // Cancel + + // Frame + gump.AddTiledGump( 15, 14, 365, 1, 0x2393 ); + gump.AddTiledGump( 380, 14, 1, 370, 0x2391 ); + gump.AddTiledGump( 15, 385, 365, 1, 0x2393 ); + gump.AddTiledGump( 15, 14, 1, 370, 0x2391 ); + gump.AddTiledGump( 0, 0, 395, 1, 0x23C5 ); + gump.AddTiledGump( 394, 0, 1, 397, 0x23C3 ); + gump.AddTiledGump( 0, 396, 395, 1, 0x23C5 ); + gump.AddTiledGump( 0, 0, 1, 397, 0x23C3 ); + + // Radio buttons for each pet + for( var i = 0; i < deadPetList.length; ++i ) + { + var deadPet = deadPetList[i]; + var y = 102 + ( i * 35 ); + var resFee = GetResFee( deadPet ); + + gump.AddRadio( 30, y, 0x2600, 0, deadPet.serial ); + gump.AddText( 70, y + 5, 0x47E, deadPet.name + " (" + resFee + " gold)" ); + } + + gump.Send( pUser.socket ); + gump.Free(); +} + +function onGumpPress( pSock, pButton, gumpData ) +{ + var pUser = pSock.currentChar; + if( !ValidateObject( pUser )) + return; + + if( pButton == 0 || gumpData == 0 ) + { + pUser.SetTempTag( "vetGumpOpen", null ); + return; + } + + var selectedSerial = gumpData.getButton( 0 ); + if( selectedSerial == -1 ) + { + // No pet was selected in menu + pUser.SetTempTag( "vetGumpOpen", null ); + return; + } + + var petNpc = CalcCharFromSer( selectedSerial ); + var resFee = GetResFee( petNpc ); + var goldInPack = pUser.ResourceCount( 0x0EED, 0 ); + + if( !ValidateObject( petNpc )) + { + pUser.SetTempTag( "vetGumpOpen", null ); + return; + } + + if( goldInPack < resFee ) + { + pSock.SysMessage( GetDictionaryEntry( 19330, pSock.language )); // You do not have enough gold on you to pay the fee. + pUser.SetTempTag( "vetGumpOpen", null ); + return; + } + + var now = GetCurrentClock(); + var deathTime = parseInt( petNpc.GetTempTag( "bondedPetDeathTime" )) || 0; + var waitTime = 10 * 60 * 1000; // 10 minutes in ms + + if(( now - deathTime ) < waitTime) + { + pSock.SysMessage( GetDictionaryEntry( 19340, pSock.language )); // That creatures spirit lacks cohesion. Try again in a few minutes. + return; + } + + pUser.UseResource( resFee, 0x0EED, 0 ); + PetResurrect( pSock, petNpc ); + var tempMsg = GetDictionaryEntry( 19331, pSock.language ); // %i gold has been withdrawn from your backpack. + tempMsg = ( tempMsg.replace( /%i/gi, resFee.toString() )); + pSock.SysMessage( tempMsg ); + pSock.SysMessage( GetDictionaryEntry( 19332, pSock.language )); // Your pet has been resurrected. + pUser.SetTempTag( "vetGumpOpen", null ); + + // Check if player has any additional dead bonded pets that require resurrection + var deadPetList = GetDeadPetList( pUser ); + if( deadPetList.length > 0 ) + { + pUser.SetTempTag( "vetGumpLastShown", null ); + } +} + +function PetResurrect( socket, deadPet ) +{ + var skillList = [ + "alchemy", "anatomy", "animallore", "itemid", "armslore", "parrying", + "begging", "blacksmithing", "bowcraft", "peacemaking", "camping", + "carpentry", "cartography", "cooking", "detectinghidden", "enticement", + "evaluatingintel", "healing", "fishing", "forensics", "herding", + "hiding", "provocation", "inscription", "lockpicking", "magery", + "magicresistance", "tactics", "snooping", "musicianship", "poisoning", + "archery", "spiritspeak", "stealing", "tailoring", "taming", "tasteid", + "tinkering", "tracking", "veterinary", "swordsmanship", "macefighting", + "fencing", "wrestling", "lumberjacking", "mining", "meditation", + "stealth", "removetrap", "necromancy", "focus", "chivalry", "bushido", + "ninjitsu", "spellweaving", "imbuing", "mysticism", "throwing" + ]; + + var petsAI = deadPet.GetTag( "PetAI" ); + var petsHue = deadPet.GetTag( "PetHue" ); + + // Apply skill loss: lose 0.2 (2 points on UOX3 scale) per skill + for( var i = 0; i < skillList.length; ++i ) + { + var skillName = skillList[i]; + var curSkill = deadPet.baseskills[skillName]; + if( typeof curSkill == "number" && curSkill > 0 ) + { + var newSkill = curSkill - 2; + if( newSkill < 0 ) + { + newSkill = 0; + } + deadPet.baseskills[skillName] = newSkill; + } + } + + TriggerEvent( 3108, "SendNpcGhostMode", socket, 0, deadPet.serial, 0 ); + deadPet.StaticEffect( 0x376A, 10, 16 ); + deadPet.SoundEffect( 0x214, true ); + deadPet.aitype = petsAI; + deadPet.colour = petsHue; + deadPet.target = null; + deadPet.atWar = false; + deadPet.attacker = null; + deadPet.SetTag( "isPetDead", false ); + deadPet.SetTempTag( "deathTime", null ); // Optional cleanup +} + +function GetResFee( deadPet ) +{ + var skillToTame = deadPet.skillToTame; + if( skillToTame == null || skillToTame <= 0 ) + { + skillToTame = 0; + } + else + { + skillToTame = skillToTame / 10; + } + + var resFee = 100 + Math.pow( 1.1041, skillToTame ); + resFee = Math.floor( resFee ); + if( resFee > 30000 ) + { + resFee = 30000; + } + else if( resFee < 100 ) + { + resFee = 100; + } + + return resFee; +} \ No newline at end of file diff --git a/data/js/npc/pets/petbonding.js b/data/js/npc/pets/petbonding.js new file mode 100644 index 000000000..f7532434c --- /dev/null +++ b/data/js/npc/pets/petbonding.js @@ -0,0 +1,371 @@ +// Enables pet bonding through feeding. When true, pets must be fed after taming to start the +// 7-day bonding countdown. Without feeding, bonding will not begin. +const enablePetBondingViaFood = true; + +function onDeathBlow( killedPet, petKiller ) +{ + if( killedPet.tamed && killedPet.GetTag( "isBondedPet" )) + { + var petOwner = killedPet.owner; + var petCorpse = CreateBlankItem( null, killedPet, 1, killedPet.name, 0x2006, 0x0, "ITEM", false ); + petCorpse.amount = killedPet.id; + petCorpse.dir = killedPet.dir; + petCorpse.colour = killedPet.colour; + petCorpse.type = 1; + petCorpse.corpse = true; + petCorpse.carveSection = 26; + petCorpse.decaytime = 300; + petCorpse.decayable = true; + petCorpse.maxItems = 150; + petCorpse.wipable = true; + petCorpse.weightMax = 50000 + + TriggerEvent( 3108, "SendNpcGhostMode", petOwner.socket, 0, killedPet.serial, 1 ); + killedPet.SetTag( "PetAI", killedPet.aitype.toString() ); + killedPet.SetTag( "PetHue", killedPet.colour.toString() ); + killedPet.SetTag( "isPetDead", true ); + killedPet.SetTag( "playTimeAtDeath", petOwner.playTime ); // store current playtime + killedPet.SetTempTag( "bondedPetDeathTime", GetCurrentClock().toString() ); + killedPet.colour = 0x3e6; + killedPet.aitype = 0; + killedPet.health = 1; + killedPet.target = null; + killedPet.atWar = false; + killedPet.attacker = null; + + // Start immediate, repeating timer (30 mins) to monitor playtime + killedPet.StartTimer( 30 * 60 * 1000, 32, 3107 ); + + // Start one-time 7-day fallback timer + killedPet.StartTimer( 7 * 24 * 60 * 60 * 1000, 33, 3107 ); + + if( ValidateObject( petKiller )) + { + petKiller.target = null; + petKiller.attacker = null; + petKiller.atWar = false; + } + + if( petOwner != null ) + { + killedPet.Follow( petOwner ); + } + + return false; + } + return true; +} + +function onCollide( socket, objCollider, objCollideWith ) +{ + if( objCollideWith.GetTag( "isPetDead" ) == true ) + { + return false; + } + return true; +} + +function inRange( pet, objInRange ) +{ + if (objInRange.socket == null) + { + return; + } + if( pet.GetTag( "isPetDead" ) == true ) + { + TriggerEvent( 3108, "SendNpcGhostMode", objInRange.socket, 0, pet.serial, 1 ); + } +} + +function outOfRange( pet, objVanish ) +{ + if( objVanish.socket == null ) + { + return; + } + if( pet.GetTag( "isPetDead" ) == true ) + { + TriggerEvent( 3108, "SendNpcGhostMode", objVanish.socket, 0, pet.serial, 1 ); + } +} + +function onCombatStart( pAttacker, pDefender ) +{ + // If the target is a dead pet, block combat and reset NPC state + if( pDefender.GetTag( "isPetDead" ) == true ) + { + // Handle for player attacker + var pAttackSock = pAttacker.socket; + if( pAttackSock ) + { + pAttackSock.SysMessage( GetDictionaryEntry( 19323, pDropper.socket.language )); // You can not perform beneficial acts on your target. + + } + + pAttacker.target = null; + pAttacker.attacker = null; + pAttacker.atWar = false; + + pDefender.target = null; + pDefender.attacker = null; + pDefender.atWar = false; + + return false; // Block combat + } + + return true; +} + +function onAttack( pAttacker, pDefender, hitStatus, hitLoc, damageDealt ) +{ + // Block attack if defender is a dead pet + if( pDefender.GetTag( "isPetDead" ) == true ) + { + // Notify attacker if player + var pAttackSock = pAttacker.socket; + if( pAttackSock ) + { + pAttackSock.SysMessage( GetDictionaryEntry( 19323, pDropper.socket.language )); // You can not perform beneficial acts on your target. + } + + pAttacker.target = null; + pAttacker.attacker = null; + pAttacker.atWar = false; + + pDefender.target = null; + pDefender.attacker = null; + pDefender.atWar = false; + } +} + +function onSpellTarget( myTarget, pCaster, spellID ) +{ + // We don't care if caster and target is the same - allow it! + if( myTarget == pCaster ) + return 2; + + if( myTarget.GetTag( "isPetDead" ) == true ) + { + var pSock = pCaster.socket; + if( pSock != null ) + { + pSock.SysMessage( GetDictionaryEntry( 19323, pDropper.socket.language )); // You can not perform beneficial acts on your target. + return 2; + } + + pCaster.target = null; + pCaster.attacker = null; + pCaster.atWar = false; + + myTarget.target = null; + myTarget.attacker = null; + myTarget.atWar = false; + } + + // By default, allow Young player to be target of spells from monsters/npcs + return 0; +} + +function onDamage( damaged, pAttacker, damageValue, damageType ) +{ + if (!ValidateObject( damaged )) + return false; + + // If attacker is a pet, use the owner instead + if( ValidateObject( pAttacker ) && pAttacker.npc && ValidateObject( pAttacker.owner ) && !pAttacker.owner.npc ) + { + pAttacker = pAttacker.owner; + } + + // Prevent damaging a bonded pet that is ghosted + if( damaged.GetTag( "isPetDead" )) + { + var atkSock = ValidateObject( pAttacker ) ? pAttacker.socket : null; + if( atkSock ) + { + atkSock.SysMessage( GetDictionaryEntry( 19323, pDropper.socket.language ));// You can not perform beneficial acts on your target. + } + + pAttacker.target = null; + pAttacker.attacker = null; + pAttacker.atWar = false; + + damaged.target = null; + damaged.attacker = null; + damaged.atWar = false; + return false; + } + + return true; +} + +function onCharDoubleClick( pUser, pet ) +{ + if( pet.GetTag( "isPetDead" ) == true ) + { + return false; + } + return true; +} + +function onReleasePet( pUser, pet ) +{ + if( pet.GetTag( "isPetDead" ) == true ) + { + var myGump = new Gump; + myGump.AddPage( 0 ); + myGump.AddBackground( 0, 0, 270, 120, 0x13BE ); + + myGump.AddXMFHTMLGump( 10, 10, 250, 75, 1049669, true, false ); //
Releasing a ghost pet will destroy it, with no chance of recovery. Do you wish to continue?
+ + myGump.AddXMFHTMLGump(55, 90, 75, 20, 1011011, false, false ); // CONTINUE + myGump.AddButton( 20, 90, 0xFA5, 0xFA7, 1, 0, 1 ); + + myGump.AddXMFHTMLGump(170, 90, 75, 20, 1011012, false, false ); // CANCEL + myGump.AddButton( 135, 90, 0xFA5, 0xFA7, 1, 0, 0 ); + myGump.Send( pUser ); + myGump.Free(); + + pUser.SetTempTag( "petSerial", pet.serial ); + return false; + } + return true; +} + +function onDropItemOnNpc( pDropper, pPet, iFood ) +{ + if( pPet.GetTag( "isPetDead" ) == true ) + { + iFood.Refresh() + return 0; + } + + if( enablePetBondingViaFood && !pPet.GetTag( "bondingStarted" )) + { + StartBonding( pDropper, pPet ); + } + iFood.Refresh() + return 1; +} + +function StartBonding( pUser, pPet ) +{ + if( !enablePetBondingViaFood ) + { + return; + } + else + { + var tameSkillRequired = pPet.skillToTame; + var pUserTameSkill = pUser.baseskills.animaltaming; + + if( tameSkillRequired <= 291 || pUserTameSkill >= tameSkillRequired ) + { + if( !pPet.GetTag( "bondingStarted" )) + { + pPet.SetTag( "bondingStarted", true ); + pPet.SetTag( "bondingPlayer", pUser.serial ); + + var bondingDelay = 604800000; // 7 days in milliseconds + pPet.StartTimer( bondingDelay, 42, 3107 ); + } + } + else + { + pUser.socket.SysMessage( GetDictionaryEntry( 19313, pUser.socket.language ));// Your pet cannot form a bond with you until your animal taming ability has risen. + } + } +} + +function onTimer( timerObj, timerID ) +{ + if( timerID == 32 ) + { + if( !timerObj.GetTag( "isPetDead" )) + {// Pet has been resurrected, stop checking. + return; + } + + var petOwner = timerObj.owner; + if( !ValidateObject( petOwner )) + { + timerObj.Delete(); + return; + } + + var playTimeAtDeath = parseInt( timerObj.GetTag( "playTimeAtDeath" )); + var currentPlayTime = petOwner.playTime; + var hoursPlayedSinceDeath = ( currentPlayTime - playTimeAtDeath ) / 60; + + if( hoursPlayedSinceDeath >= 24 ) // 24 hours of playtime since death + { + petOwner.RemoveFollower( timerObj ); + petOwner.controlSlotsUsed = Math.max( 0, petOwner.controlSlotsUsed - timerObj.controlSlots ); + + Console.Log( + "Pet [" + timerObj.name + "] (Serial: " + timerObj.serial.toString() + + ") owned by " + petOwner.name + " (Serial: " + petOwner.serial.toString() + + ") was auto-deleted after 24 hours of active character playtime without resurrection." ); + + timerObj.Delete(); + } + else + { + // Recheck again in 30 minutes + timerObj.StartTimer( 30 * 60 * 1000, 32, 3107 ); + } + } + if( timerID == 33 ) + { + // Fallback: force delete if pet is still dead after 7 real days + if( !timerObj.GetTag( "isPetDead" )) + return; + + var petOwner = timerObj.owner; + if( ValidateObject( petOwner )) + { + petOwner.RemoveFollower( timerObj ); + petOwner.controlSlotsUsed = Math.max( 0, petOwner.controlSlotsUsed - timerObj.controlSlots ); + } + + Console.Log( + "Pet [" + timerObj.name + "] (Serial: " + timerObj.serial.toString() + + ") was force-deleted after 7 days of inactivity." ); + + timerObj.Delete(); + } + if( timerID == 42 ) + { + timerObj.SetTag( "isBondedPet", true ); + timerObj.Refresh(); + + var ownerSerial = timerObj.GetTag( "bondingPlayer" ); + if( ownerSerial ) + { + var owner = CalcCharFromSer( ownerSerial ); + if (ValidateObject( owner )) + { + owner.socket.SysMessage( GetDictionaryEntry( 19308, owner.socket.language )); // Your pet has bonded with you! + } + } + timerObj.SetTag( "bondingStarted", null ); + timerObj.SetTag( "bondingPlayer", null ); + } +} + +function onGumpPress( pSock, pButton, gumpData ) +{ + var pUser = pSock.currentChar; + var petSerial = CalcCharFromSer( pUser.GetTempTag( "petSerial" )); + if( pButton == 1 ) + { + // Remove pet as an active follower + pUser.RemoveFollower( petSerial ); + // Reduce control slots in use for player by amount occupied by pet that was stabled + pUser.controlSlotsUsed = Math.max( 0, pUser.controlSlotsUsed - petSerial.controlSlots ); + Console.Log( pUser.name + " (Serial: " + pUser.serial.toString() + ") released dead pet [" + petSerial.name + "] (Serial: " + petSerial.serial.toString() + ")" ); + petSerial.Delete(); + } +} + +function _restorecontext_() {} \ No newline at end of file diff --git a/data/js/npc/pets/static_horse.js b/data/js/npc/pets/static_horse.js index 1d165e9ef..8e565c1eb 100644 --- a/data/js/npc/pets/static_horse.js +++ b/data/js/npc/pets/static_horse.js @@ -6,6 +6,7 @@ var maxControlSlots = GetServerSetting( "MaxControlSlots" ); // maxFollowers only comes into play if maxControlSlots is set to 0 in UOX.INI var maxFollowers = GetServerSetting( "MaxFollowers" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); // Runs before purchase is validated by server function onBuyFromVendor( pSock, vendor, iBought, numItemsBought ) @@ -82,6 +83,12 @@ function onUseChecked( pUser, iUsed ) // make pet follow owner by default nSpawned.Follow( pUser ); + if( petBondingEnabled > 0 ) + { + nSpawned.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, nSpawned ); + } + // make a sound pUser.SoundEffect( 0x0215, true ); diff --git a/data/js/npc/pets/static_llama.js b/data/js/npc/pets/static_llama.js index 50270bd41..747c72a11 100644 --- a/data/js/npc/pets/static_llama.js +++ b/data/js/npc/pets/static_llama.js @@ -6,6 +6,7 @@ var maxControlSlots = GetServerSetting( "MaxControlSlots" ); // maxFollowers only comes into play if maxControlSlots is set to 0 in UOX.INI var maxFollowers = GetServerSetting( "MaxFollowers" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); // Runs before purchase is validated by server function onBuyFromVendor( pSock, vendor, iBought, numItemsBought ) @@ -65,6 +66,12 @@ function onUseChecked( pUser, iUsed ) // make pet follow owner by default nSpawned.Follow( pUser ); + if( petBondingEnabled > 0 ) + { + nSpawned.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, nSpawned ); + } + // make a sound pUser.SoundEffect( 0x0215, true ); diff --git a/data/js/npc/pets/static_ostard.js b/data/js/npc/pets/static_ostard.js index a5753b9be..d0772e465 100644 --- a/data/js/npc/pets/static_ostard.js +++ b/data/js/npc/pets/static_ostard.js @@ -6,6 +6,7 @@ var maxControlSlots = GetServerSetting( "MaxControlSlots" ); // maxFollowers only comes into play if maxControlSlots is set to 0 in UOX.INI var maxFollowers = GetServerSetting( "MaxFollowers" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); // Runs before purchase is validated by server function onBuyFromVendor( pSock, vendor, iBought, numItemsBought ) @@ -97,6 +98,12 @@ function onUseChecked( pUser, iUsed ) // make pet follow owner by default nSpawned.Follow( pUser ); + + if( petBondingEnabled > 0 ) + { + nSpawned.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, nSpawned ); + } // make a sound pUser.SoundEffect( 0x0215, true ); diff --git a/data/js/npc/pets/static_packhorse.js b/data/js/npc/pets/static_packhorse.js index 1b5ef0498..feb7336aa 100644 --- a/data/js/npc/pets/static_packhorse.js +++ b/data/js/npc/pets/static_packhorse.js @@ -6,6 +6,7 @@ var maxControlSlots = GetServerSetting( "MaxControlSlots" ); // maxFollowers only comes into play if maxControlSlots is set to 0 in UOX.INI var maxFollowers = GetServerSetting( "MaxFollowers" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); // Runs before purchase is validated by server function onBuyFromVendor( pSock, vendor, iBought, numItemsBought ) @@ -64,6 +65,12 @@ function onUseChecked( pUser, iUsed ) // make pet follow owner by default nSpawned.Follow( pUser ); + + if( petBondingEnabled > 0 ) + { + nSpawned.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, nSpawned ); + } // make a sound pUser.SoundEffect( 0x0215, true ); diff --git a/data/js/npc/pets/static_packllama.js b/data/js/npc/pets/static_packllama.js index 36cea89de..754d4777a 100644 --- a/data/js/npc/pets/static_packllama.js +++ b/data/js/npc/pets/static_packllama.js @@ -7,6 +7,8 @@ var maxControlSlots = GetServerSetting( "MaxControlSlots" ); // maxFollowers only comes into play if maxControlSlots is set to 0 in UOX.INI var maxFollowers = GetServerSetting( "MaxFollowers" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); + // Runs before purchase is validated by server function onBuyFromVendor( pSock, vendor, iBought, numItemsBought ) { @@ -64,6 +66,12 @@ function onUseChecked( pUser, iUsed ) // make pet follow owner by default nSpawned.Follow( pUser ); + + if( petBondingEnabled > 0 ) + { + nSpawned.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, nSpawned ); + } // make a sound pUser.SoundEffect( 0x0215, true ); diff --git a/data/js/server/network/0xbf_sendnpcghostmode.js b/data/js/server/network/0xbf_sendnpcghostmode.js new file mode 100644 index 000000000..3d877a60c --- /dev/null +++ b/data/js/server/network/0xbf_sendnpcghostmode.js @@ -0,0 +1,17 @@ +// Sends a packet to toggle ghost mode appearance for an NPC on the client. +// Typically used to make dead pets or NPCs appear as transparent ghosts without affecting game logic. +function SendNpcGhostMode( pSock, unknownFlag, petSerial, ghostFlag ) +{ + var pkt = new Packet(); // Create a new packet + pkt.ReserveSize( 11 ); // Reserve 11 bytes of space + + pkt.WriteByte( 0, 0xBF ); // Packet ID: Extended command + pkt.WriteShort( 1, 11 ); // Total packet length + pkt.WriteShort( 3, 0x19 ); // Subcommand: 0x19 (SendNpcGhostMode) + pkt.WriteByte( 5, unknownFlag ); // Unknown/extra flag (often 0) + pkt.WriteLong( 6, petSerial ); // Pet serial + pkt.WriteByte( 10, ghostFlag ); // Ghost flag (1 = Ghost, 0 = not Ghost) + + pSock.Send( pkt ); // Send packet to client + pkt.Free(); // Free memory +} \ No newline at end of file diff --git a/data/js/skill/healing.js b/data/js/skill/healing.js index d605b3b51..03387f29e 100644 --- a/data/js/skill/healing.js +++ b/data/js/skill/healing.js @@ -185,40 +185,89 @@ function onCallback1( socket, ourObj ) let healTimer = 0; let healTimerID = -1; let anatSkill = mChar.skills.anatomy; + let loreSkill = mChar.skills.animallore; let triggerBonusCure = false; let affectedByMortalStrike = ourObj.GetTempTag( "blockHeal" ); - if( ourObj.dead ) // Resurrection + if( ourObj.dead || ourObj.GetTag( "isPetDead" )) // Resurrection { if( ourObj.region.id == 42 ) // Khaldun, resurrection not allowed { socket.SysMessage( "The veil of death in this area is too strong and resists thy efforts to restore life." ); return; } - else if( healSkill >= 800 && anatSkill >= 800 ) + else if( ourObj.GetTag( "isBondedPet" )) { - // Resurrecting takes between 8 - 10 seconds depending on dexterity - if( coreShardEra >= EraStringToNum( "uor" )) + // Resurrection of bonded pet + // Must have at least 80.0 Vet and 80.0 Lore + if( healSkill >= 800 && loreSkill >= 800 ) { - // UOR introduced dexterity as a modifier for healing timer - healTimer = 10000 - (( mChar.dexterity / 50 ) * 1000 ); - } - else// if( coreShardEra >= EraStringToNum( "t2a" )) - { - // T2A (Publish May 25, 1999), resurrecting "now" takes 10 seconds flat - healTimer = 10000; + let isOwner = ourObj.owner; + let isFriend = false; + let friendList = ourObj.GetFriendList(); + + for( let i = 0; i < friendList.length; i++ ) + { + if( ValidateObject( friendList[i] ) && friendList[i] == mChar ) + { + isFriend = true; + break; + } + } + if( isOwner || isFriend ) + { + // Resurrecting takes between 8 - 10 seconds depending on dexterity + if( coreShardEra >= EraStringToNum( "uor" )) + { + // UOR introduced dexterity as a modifier for healing timer + healTimer = 10000 - (( mChar.dexterity / 50 ) * 1000 ); + } + else// if( coreShardEra >= EraStringToNum( "t2a" )) + { + // T2A (Publish May 25, 1999), resurrecting "now" takes 10 seconds flat + healTimer = 10000; + } + + healTimerID = 0; // Resurrect + } + else + { + socket.SysMessage( GetDictionaryEntry( 19304, socket.language ));// Only pets bonded with their owner can be resurrected.. + } } - /*else + else { - // Did original UO have a different resurrect timer than T2A? - // ??? - }*/ - - healTimerID = 0; // Resurrect + socket.SysMessage( GetDictionaryEntry( 19303, socket.language ));// You are not skilled enough to resurrect that pet. + return; + } } else { - socket.SysMessage( GetDictionaryEntry( 1493, socket.language )); // You are not skilled enough to resurrect. - return; + if( healSkill >= 800 && anatSkill >= 800 ) + { + // Resurrecting takes between 8 - 10 seconds depending on dexterity + if( coreShardEra >= EraStringToNum( "uor" )) + { + // UOR introduced dexterity as a modifier for healing timer + healTimer = 10000 - (( mChar.dexterity / 50 ) * 1000 ); + } + else// if( coreShardEra >= EraStringToNum( "t2a" )) + { + // T2A (Publish May 25, 1999), resurrecting "now" takes 10 seconds flat + healTimer = 10000; + } + /*else + { + // Did original UO have a different resurrect timer than T2A? + // ??? + }*/ + + healTimerID = 0; // Resurrect + } + else + { + socket.SysMessage( GetDictionaryEntry( 1493, socket.language )); // You are not skilled enough to resurrect. + return; + } } } else if( ourObj.poison > 0 || ourObj.GetTempTag( "doBleed" )) // Cure Poison/Bleed @@ -576,7 +625,7 @@ function onTimer( mChar, timerID ) switch ( timerID ) { case 0: // Resurrect - if( !ourObj.dead ) + if( !ourObj.dead && !ourObj.GetTag( "isPetDead" ) ) { socket.SysMessage( GetDictionaryEntry( 9085, socket.language )); // The target is not dead. break; @@ -610,8 +659,25 @@ function onTimer( mChar, timerID ) } else { + if( ourObj.GetTag( "isPetDead" )) + { + var now = GetCurrentClock(); + var deathTime = parseInt( ourObj.GetTempTag( "bondedPetDeathTime" )) || 0; + var waitTime = 10 * 60 * 1000; // 10 minutes in ms + + if(( now - deathTime ) < waitTime) + { + socket.SysMessage( GetDictionaryEntry( 19340, pSock.language )); // That creatures spirit lacks cohesion. Try again in a few minutes. + return; + } - if( coreShardEra >= EraStringToNum( "aos" )) + ResurrectBondedPet( socket, ourObj ); + ourObj.StaticEffect( 0x376A, 10, 16 ); + ourObj.SoundEffect( 0x214, true ); + socket.SysMessage( GetDictionaryEntry( 19306, socket.language )); // You successfully resurrected the pet! + resResult = 1; + } + else if( coreShardEra >= EraStringToNum( "aos" )) { ourObj.frozen = true; ourObj.SetTempTag( "ResurrectingHealer", mChar.serial.toString() ); @@ -903,6 +969,22 @@ function onTimer( mChar, timerID ) } } +function ResurrectBondedPet( socket, deadPet ) +{ + var petsAI = deadPet.GetTag( "PetAI" ); + var petsHue = deadPet.GetTag( "PetHue" ); + + TriggerEvent( 3108, "SendNpcGhostMode", socket, 0, deadPet.serial, 0 ); + deadPet.StaticEffect( 0x376A, 10, 16 ); + deadPet.SoundEffect( 0x214, true ); + deadPet.aitype = petsAI; + deadPet.colour = petsHue; + deadPet.target = null; + deadPet.atWar = false; + deadPet.attacker = null; + deadPet.SetTag( "isPetDead", false ); +} + function onGumpPress( socket, pButton, gumpData ) { var resurrectTarg = socket.currentChar; diff --git a/data/js/skill/taming.js b/data/js/skill/taming.js index 2b4341bb1..b2298a284 100644 --- a/data/js/skill/taming.js +++ b/data/js/skill/taming.js @@ -10,6 +10,7 @@ const maxFollowers = GetServerSetting( "MaxFollowers" ); const maxPetOwners = GetServerSetting( "MaxPetOwners" ); const maxTimesTamed = 5; // The maximum number of times a pet can be tamed (by different players) const checkPetControlDifficulty = GetServerSetting( "CheckPetControlDifficulty" ); +const petBondingEnabled = GetServerSetting( "PetBondingEnabled" ); function onSkill( pUser, objType, skillUsed ) { @@ -179,6 +180,11 @@ function CheckTameSuccess( pUser, toTame ) toTame.aitype = 0; toTame.tamed = true; toTame.loyalty = 25; // start at 25 out of 100 + if( petBondingEnabled ) + { + toTame.AddScriptTrigger( 3107 );//Add bonding script trigger + TriggerEvent( 3107, "StartBonding", pUser, toTame ); + } var hasBeenOwner = toTame.HasBeenOwner( pUser ); if( checkPetControlDifficulty && hasBeenOwner ) diff --git a/data/uox.ini b/data/uox.ini index fbb956dbb..07e115fb0 100644 --- a/data/uox.ini +++ b/data/uox.ini @@ -233,6 +233,7 @@ CHECKPETCONTROLDIFFICULTY=1 PETLOYALTYGAINONSUCCESS=1 PETLOYALTYLOSSONFAILURE=3 PETLOYALTYRATE=900 +PETBONDINGENABLED=0 } [speedup] diff --git a/docs/jsdocs.html b/docs/jsdocs.html index c6148ced8..2bb4f39ee 100644 --- a/docs/jsdocs.html +++ b/docs/jsdocs.html @@ -3301,6 +3301,43 @@

January 9th, 2022

+
  • + onReleasePet +
    +
    +
    +

    Prototype

    +

    function onReleasePet( player, pet )

    +
    +
    +

    Purpose

    +

    Lets script authors override or block the release of a pet by a player. Return false to prevent the release and handle it through script (e.g., open a confirmation gump), or true to allow the default UOX3 behavior.

    +
    +
    +

    When Triggered

    +

    When a player attempts to release a pet using the “release” context menu or other command.

    +
    +
    +

    Notes

    +

    The pet must be valid and under the control of the player. Script can check bonded status, or open a custom UI to confirm release. Be sure to return true or false explicitly; omitting a return will default to false (blocking release).

    +
    + +
    +

    Example of usage
    +

    function onReleasePet( player, pet )
    +{
    +	if( pet.GetTag("isBondedPet") == 1 )
    +	{
    +		player.SysMessage("You cannot release a bonded pet!");
    +		return false;
    +	}
    +	return true;
    +}

    +
    +
    +
    +
  • +
  • onResurrect
    diff --git a/source/CPacketSend.cpp b/source/CPacketSend.cpp index fcc95d6ea..73550bac4 100644 --- a/source/CPacketSend.cpp +++ b/source/CPacketSend.cpp @@ -8203,6 +8203,28 @@ void CPToolTip::CopyCharData( CChar& mChar, size_t &totalStringLen ) } } + // Is character tame/bonded? + if( mChar.IsNpc() && mChar.IsTamed() && !cwmWorldState->creatures[mChar.GetId()].IsHuman() ) + { + TAGMAPOBJECT petBond = mChar.GetTag( "isBondedPet" ); + if( petBond.m_IntValue == 1 ) + { + // Bonded + tempEntry.stringNum = 1042971; // ~1_NOTHING~ + tempEntry.ourText = oldstrutil::format( "%s", Dictionary->GetEntry( 19334, tSock->Language() ).c_str() ); // [Bonded] + tempEntry.sortOrder = 12; + FinalizeData( tempEntry, totalStringLen ); + } + else + { + // Tame + tempEntry.stringNum = 1042971; // ~1_NOTHING~ + tempEntry.ourText = oldstrutil::format( "%s", Dictionary->GetEntry( 19333, tSock->Language() ).c_str() ); // [Tame] + tempEntry.sortOrder = 12; + FinalizeData( tempEntry, totalStringLen ); + } + } + // Is character Guarded? if( mChar.IsGuarded() ) { diff --git a/source/CResponse.cpp b/source/CResponse.cpp index 612cf8d17..96031ceff 100644 --- a/source/CResponse.cpp +++ b/source/CResponse.cpp @@ -783,6 +783,18 @@ bool CPetReleaseResponse::Handle( CSocket *mSock, CChar *mChar, CChar *petNpc ) { if( Npcs->CanControlPet( mChar, petNpc, true, false )) { + std::vector scriptTriggers = petNpc->GetScriptTriggers(); + for( auto scriptTrig : scriptTriggers ) + { + cScript *toExecute = JSMapping->GetScript( scriptTrig ); + if( toExecute ) + { + SI08 retStatus = toExecute->OnReleasePet( mChar, petNpc ); + if( retStatus == 0 ) + return false; + } + } + // Reduce player's control slot usage by the amount of control slots taken up by the pet mChar->SetControlSlotsUsed( std::max( 0, mChar->GetControlSlotsUsed() - petNpc->GetControlSlots() )); @@ -842,6 +854,12 @@ bool CPetAttackResponse::Handle( CSocket *mSock, CChar *mChar, CChar *petNpc ) std::string npcName = GetNpcDictName( petNpc, mSock, NRS_SYSTEM ); if( saidAll || FindString( ourText, oldstrutil::upper( npcName ))) { + TAGMAPOBJECT deadPet = petNpc->GetTag( "isPetDead" ); + if( deadPet.m_IntValue == 1 ) + { + mSock->SysMessage( 19302 );// The dead pet cannot attack. + return true; + } if( Npcs->CanControlPet( mChar, petNpc, false, true )) { Npcs->StopPetGuarding( petNpc ); diff --git a/source/Changelog.txt b/source/Changelog.txt index 88ba928bb..3706774f5 100644 --- a/source/Changelog.txt +++ b/source/Changelog.txt @@ -1,3 +1,14 @@ +09/07/2025 - Xuri + Added [Tame] and [Bonded] to character tooltips of tame/bonded animals and updated Dictionary files + Updated .AddRadio() and .AddCheckbox() Gump Methods to support 32-bit unsigned int for the buttonID + +05/07/2025 - Dragon Slayer + Updated Addmenu with all the missing npcs (Ratmen/lizardman/Se Npcs/Vet) + Fixed PoisonedBy missing on world save tags. + Fixed Spiders Icons in Addmenu. + Added Vet Spawns thru out the worlds. + Fixed Sound on Shadow fiend ( creatures.dfn ) + 04/07/2025 - Xuri Fixed an issue with poison damage in combat where the poison resist of the attacker was checked instead of that of the defender Fixed an issue where the poison details in tooltip of an item would only be visible when equipped, and not visible when kept in backpack @@ -208,6 +219,17 @@ Bump the types for the timer from the uint32_t to uint64_t and encapsulate behind a TVAL type definition. According to the C++ reference, chrono should support at least 45 bits of accuracy for milliseconds. This gives a 1115 year wraparound. + +18/05/2025 - Dragon Slayer + Added support for pet bonding system similar to OSI mechanics + Pets now track bonding status using 'isBondedPet' tag + Bonding begins when player tames a tameable pet that meets taming skill requirements, + Pets become bonded after 7 days (604800000 ms) of loyalty to their owner, + Bonded pets no longer delete on death, but enter ghost state, + Added packet 'SendNpcGhostMode( pSock, unknownFlag, serial, ghostFlag )' to trigger bonded ghost appearance, + New JS event hook: onReleasePet( pTamer, pPet ) + Triggered when a player releases a controlled pet, + Can be used to reset bonded status or perform cleanup. 14/05/2025 - Xuri Fixed an issue where integer division and rounding of values could lead to skill gains not being visible to player diff --git a/source/SEFunctions.cpp b/source/SEFunctions.cpp index b405f7652..7d3d59571 100644 --- a/source/SEFunctions.cpp +++ b/source/SEFunctions.cpp @@ -5382,6 +5382,9 @@ JSBool SE_GetServerSetting( JSContext *cx, [[maybe_unused]] JSObject *obj, uintN case 381: // POISONCORROSIONSYSTEM *rval = BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->PoisonCorrosionSystem() ); break; + case 382: // PETBONDINGENABLED + *rval = BOOLEAN_TO_JSVAL( cwmWorldState->ServerData()->PetBondingEnabled() ); + break; default: ScriptError( cx, "GetServerSetting: Invalid server setting name provided" ); return false; diff --git a/source/UOXJSMethods.cpp b/source/UOXJSMethods.cpp index 62dd071d9..21471efbf 100644 --- a/source/UOXJSMethods.cpp +++ b/source/UOXJSMethods.cpp @@ -570,7 +570,7 @@ JSBool CGump_AddCheckbox( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, UI16 gImage = 0; UI16 gImageChk = 0; SI16 initState = 0; - SI16 relay = 0; + UI32 relay = 0; if( argc == 5 ) { @@ -579,7 +579,7 @@ JSBool CGump_AddCheckbox( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, gImage = static_cast( JSVAL_TO_INT( argv[2] )); gImageChk = gImage + 1; initState = static_cast( JSVAL_TO_INT( argv[3] )); - relay = static_cast( JSVAL_TO_INT( argv[4] )); + relay = static_cast( JSVAL_TO_INT( argv[4] )); } else { @@ -588,7 +588,7 @@ JSBool CGump_AddCheckbox( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, gImage = static_cast( JSVAL_TO_INT( argv[2] )); gImageChk = static_cast( JSVAL_TO_INT( argv[3] )); initState = static_cast( JSVAL_TO_INT( argv[4] )); - relay = static_cast( JSVAL_TO_INT( argv[5] )); + relay = static_cast( JSVAL_TO_INT( argv[5] )); } SEGump_st *gList = static_cast( JS_GetPrivate( cx, obj )); @@ -599,7 +599,7 @@ JSBool CGump_AddCheckbox( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, return JS_FALSE; } - gList->one->push_back( oldstrutil::format( "checkbox %i %i %u %u %i %i", tL, tR, gImage, gImageChk, initState, relay )); + gList->one->push_back( oldstrutil::format( "checkbox %i %i %u %u %i %u", tL, tR, gImage, gImageChk, initState, relay )); return JS_TRUE; } @@ -1373,7 +1373,7 @@ JSBool CGump_AddRadio( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, [[ UI16 gImage = 0; UI16 gImageChk = 0; SI16 initialState = 0; - SI16 relay = 0; + UI32 relay = 0; if( argc == 5 ) { @@ -1382,7 +1382,7 @@ JSBool CGump_AddRadio( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, [[ gImage = static_cast( JSVAL_TO_INT( argv[2] )); gImageChk = gImage + 1; initialState = static_cast( JSVAL_TO_INT( argv[3] )); - relay = static_cast( JSVAL_TO_INT( argv[4] )); + relay = static_cast( JSVAL_TO_INT( argv[4] )); } else { @@ -1391,7 +1391,7 @@ JSBool CGump_AddRadio( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, [[ gImage = static_cast( JSVAL_TO_INT( argv[2] )); gImageChk = static_cast( JSVAL_TO_INT( argv[3] )); initialState = static_cast( JSVAL_TO_INT( argv[4] )); - relay = static_cast( JSVAL_TO_INT( argv[5] )); + relay = static_cast( JSVAL_TO_INT( argv[5] )); } @@ -1402,7 +1402,7 @@ JSBool CGump_AddRadio( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, [[ return JS_FALSE; } - gList->one->push_back( oldstrutil::format( "radio %i %i %u %u %i %i", tL, tR, gImage, gImageChk, initialState, relay )); + gList->one->push_back( oldstrutil::format( "radio %i %i %u %u %i %u", tL, tR, gImage, gImageChk, initialState, relay )); return JS_TRUE; } diff --git a/source/ai.cpp b/source/ai.cpp index 9c7b5e3e8..6b86ef53d 100644 --- a/source/ai.cpp +++ b/source/ai.cpp @@ -49,6 +49,13 @@ bool IsValidAttackTarget( CChar& mChar, CChar *cTarget ) } } + // Dead (bonded) pets are not valid + TAGMAPOBJECT deadPet = cTarget->GetTag( "isPetDead" ); + if( deadPet.m_IntValue == 1 ) + { + return false; + } + // Targets on character's combat ignore list are not valid if( mChar.CheckCombatIgnore( cTarget->GetSerial() )) { diff --git a/source/cBaseObject.cpp b/source/cBaseObject.cpp index 1120e092e..fd7b9744b 100644 --- a/source/cBaseObject.cpp +++ b/source/cBaseObject.cpp @@ -2478,6 +2478,10 @@ bool CBaseObject::HandleLine( std::string &UTag, std::string &data ) { poisoned = oldstrutil::value( data ); } + else if( UTag == "POISONEDBY" ) + { + poisonedBy = oldstrutil::value( data ); + } else { rValue = false; diff --git a/source/cChar.cpp b/source/cChar.cpp index d94a5ddf0..ba901a555 100644 --- a/source/cChar.cpp +++ b/source/cChar.cpp @@ -628,8 +628,9 @@ void CChar::DoHunger( CSocket *mSock ) } else if( IsTamed() && GetTamedHungerRate() > 0 ) { + TAGMAPOBJECT deadPet = GetTag( "isPetDead" ); // Handle hunger for pets - if( !WillHunger() || GetMounted() || GetStabled() ) + if( !WillHunger() || GetMounted() || GetStabled() || deadPet.m_IntValue == 1 ) return; // Pets don't hunger if owner is offline diff --git a/source/cScript.cpp b/source/cScript.cpp index b568bd058..4a11a4c5a 100644 --- a/source/cScript.cpp +++ b/source/cScript.cpp @@ -1668,6 +1668,37 @@ SI08 cScript::OnDecay( CItem *decaying ) return TryParseJSVal( rval ); } +//o------------------------------------------------------------------------------------------------o +//| Function - cScript::OnReleasePet() +//| Purpose - Script trigger for when a player tries to release a pet +//o------------------------------------------------------------------------------------------------o +SI08 cScript::OnReleasePet( CChar *owner, CChar *pet ) +{ + const SI08 RV_NOFUNC = -1; + if( !ValidateObject( owner ) || !ValidateObject( pet )) + return RV_NOFUNC; + + if( !ExistAndVerify( seOnReleasePet, "onReleasePet" )) + return RV_NOFUNC; + + jsval params[2], rval; + JSObject *ownerObj = JSEngine->AcquireObject( IUE_CHAR, owner, runTime ); + JSObject *petObj = JSEngine->AcquireObject( IUE_CHAR, pet, runTime ); + + params[0] = OBJECT_TO_JSVAL( ownerObj ); + params[1] = OBJECT_TO_JSVAL( petObj ); + + JSBool retVal = JS_CallFunctionName( targContext, targObject, "onReleasePet", 2, params, &rval ); + + if( retVal == JS_FALSE ) + { + SetEventExists( seOnReleasePet, false ); + return RV_NOFUNC; + } + + return TryParseJSVal( rval ); +} + //o------------------------------------------------------------------------------------------------o //| Function - cScript::OnLeaving() //o------------------------------------------------------------------------------------------------o diff --git a/source/cScript.h b/source/cScript.h index 59f1b4376..276ac5899 100644 --- a/source/cScript.h +++ b/source/cScript.h @@ -122,7 +122,8 @@ enum ScriptEvent seOnContextMenuSelect, seOnWarModeToggle, seOnSpecialMove, - seOnFacetChange + seOnFacetChange, + seOnReleasePet }; struct SEGump_st @@ -314,6 +315,7 @@ class cScript SI08 OnSoldToVendor( CSocket *targSock, CChar *objVendor, CBaseObject *objItemSold, UI16 numItemsSold ); SI08 OnHouseCommand( CSocket *targSock, CMultiObj *multiObj, UI08 targId ); SI08 OnMakeItem( CSocket *mSock, CChar *objChar, CItem *objItem, UI16 createEntryId ); + SI08 OnReleasePet( CChar *owner, CChar *pet ); // Critical handler type stuff bool IsFiring( void ); diff --git a/source/cServerData.cpp b/source/cServerData.cpp index 494630ea4..96346640a 100644 --- a/source/cServerData.cpp +++ b/source/cServerData.cpp @@ -401,7 +401,8 @@ const std::map CServerData::uox3IniCaseValue {"ELFMAXWEIGHTBONUS"s, 378}, {"GARGOYLEMAXWEIGHTBONUS"s, 379}, {"MAXNPCAGGRORANGE"s, 380}, - {"POISONCORROSIONSYSTEM"s, 381} + {"POISONCORROSIONSYSTEM"s, 381}, + {"PETBONDINGENABLED"s, 382} }; constexpr auto MAX_TRACKINGTARGETS = 128; constexpr auto SKILLTOTALCAP = 7000; @@ -444,7 +445,7 @@ constexpr auto BIT_ITEMDECAYINHOUSES = UI32( 32 ); constexpr auto BIT_PAPERDOLLGUILDBUTTON = UI32( 33 ); constexpr auto BIT_ATTSPEEDFROMSTAMINA = UI32( 34 ); constexpr auto BIT_SHOWDAMAGENUMBERS = UI32( 35 ); -// 37 free! +constexpr auto BIT_PETBONDINGENABLED = UI32( 37 ); constexpr auto BIT_EXTENDEDSTARTINGSTATS = UI32( 38 ); constexpr auto BIT_EXTENDEDSTARTINGSKILLS = UI32( 39 ); constexpr auto BIT_ASSISTANTNEGOTIATION = UI32( 40 ); @@ -882,6 +883,7 @@ auto CServerData::ResetDefaults() -> void MaxControlSlots( 0 ); // Default to 0, which is equal to off MaxFollowers( 5 ); MaxPetOwners( 10 ); + PetBondingEnabled( false ); ToolUseLimit( true ); ToolUseBreak( true ); ItemRepairDurabilityLoss( true ); @@ -1456,6 +1458,20 @@ auto CServerData::BuildSystemTimeValue( cSD_TID timerId ) const -> TIMERVAL return BuildTimeValue( static_cast( SystemTimer( timerId ))); } +//o------------------------------------------------------------------------------------------------o +//| Function - CServerData::PetBondingEnabled() +//o------------------------------------------------------------------------------------------------o +//| Purpose - Gets/Sets status of server accouncements for players connecting/disconnecting +//o------------------------------------------------------------------------------------------------o +auto CServerData::PetBondingEnabled() const -> bool +{ + return boolVals.test( BIT_PETBONDINGENABLED ); +} +auto CServerData::PetBondingEnabled( bool newVal ) -> void +{ + boolVals.set( BIT_PETBONDINGENABLED, newVal ); +} + //o------------------------------------------------------------------------------------------------o //| Function - CServerData::SystemTimer() //o------------------------------------------------------------------------------------------------o @@ -5546,6 +5562,7 @@ auto CServerData::SaveIni( const std::string &filename ) -> bool ofsOutput << "PETLOYALTYGAINONSUCCESS=" << static_cast( GetPetLoyaltyGainOnSuccess() ) << '\n'; ofsOutput << "PETLOYALTYLOSSONFAILURE=" << static_cast( GetPetLoyaltyLossOnFailure() ) << '\n'; ofsOutput << "PETLOYALTYRATE=" << SystemTimer( tSERVER_LOYALTYRATE ) << '\n'; + ofsOutput << "PETBONDINGENABLED=" << ( PetBondingEnabled() ? 1 : 0 ) << '\n'; ofsOutput << "}" << '\n'; ofsOutput << '\n' << "[speedup]" << '\n' << "{" << '\n'; @@ -7227,6 +7244,9 @@ auto CServerData::HandleLine( const std::string& tag, const std::string& value ) case 381: // POISONCORROSIONSYSTEM PoisonCorrosionSystem(( static_cast( std::stoi( value, nullptr, 0 )) == 1 )); break; + case 382: // PETBONDINGENABLED + PetBondingEnabled(( static_cast( std::stoi( value, nullptr, 0 )) == 1 )); + break; default: rValue = false; break; diff --git a/source/cServerData.h b/source/cServerData.h index 0d1009d01..9a8d315a4 100644 --- a/source/cServerData.h +++ b/source/cServerData.h @@ -576,6 +576,8 @@ class CServerData SI16 MaxStealthMovement() const; auto MaxStaminaMovement( SI16 value ) -> void; SI16 MaxStaminaMovement() const; + auto PetBondingEnabled( bool setting ) -> void; + auto PetBondingEnabled() const -> bool; auto SystemTimer( cSD_TID timerId, UI16 value ) -> void; auto SystemTimer( cSD_TID timerId ) const -> UI16; TIMERVAL BuildSystemTimeValue( cSD_TID timerId ) const; diff --git a/source/cSocket.cpp b/source/cSocket.cpp index c406e199e..b68e58cbd 100644 --- a/source/cSocket.cpp +++ b/source/cSocket.cpp @@ -2191,7 +2191,15 @@ void CSocket::ShowCharName( CChar *i, bool showSer ) { if( i->IsTamed() && ValidateObject( i->GetOwnerObj() ) && !cwmWorldState->creatures[i->GetId()].IsHuman() ) { - charName += " (tame) "; + TAGMAPOBJECT petBond = i->GetTag( "isBondedPet" ); + if( petBond.m_IntValue == 1 ) + { + charName += " " + Dictionary->GetEntry( 19335, Language() ); // (bonded) + } + else + { + charName += " " + Dictionary->GetEntry( 19336, Language() ); // (tame) + } } // Show NPC title over their head? @@ -2205,15 +2213,15 @@ void CSocket::ShowCharName( CChar *i, bool showSer ) // Show (invulnerable) tags over the heads of invulnerable characters? if( i->IsInvulnerable() && cwmWorldState->ServerData()->ShowInvulnerableTagOverhead() ) { - charName += " (invulnerable)"; + charName += " " + Dictionary->GetEntry( 19337, Language() ); // (invulnerable) } if( i->IsFrozen() ) { - charName += " (frozen) "; + charName += " " + Dictionary->GetEntry( 19338, Language() ); // (frozen) } if( i->IsGuarded() ) { - charName += " (guarded)"; + charName += " " + Dictionary->GetEntry( 19339, Language() ); // (guarded) } if( i->GetGuildNumber() != -1 && !i->IsIncognito() && !i->IsDisguised() ) { diff --git a/source/npcs.cpp b/source/npcs.cpp index c7def6289..03f7955f1 100644 --- a/source/npcs.cpp +++ b/source/npcs.cpp @@ -2126,6 +2126,15 @@ void CCharStuff::FinalizeTransfer( CChar *petChar, CChar *srcChar, CChar *targCh // Update control slots used for both old and new owners srcChar->SetControlSlotsUsed( std::max(0, srcChar->GetControlSlotsUsed() - petChar->GetControlSlots() )); targChar->SetControlSlotsUsed( std::clamp( targChar->GetControlSlotsUsed() + petChar->GetControlSlots(), 0, 255 )); + + // Remove Pet Bonding if its Bonded + TAGMAPOBJECT petBond = petChar->GetTag( "isBondedPet" ); + if( petBond.m_IntValue == 1 ) + { + petBond.m_IntValue = 0; + petChar->SetTag( "isBondedPet", petBond ); + petChar->RemoveScriptTrigger( 3107 );// petbonding.js + } } //o------------------------------------------------------------------------------------------------o @@ -2177,6 +2186,15 @@ void CCharStuff::ReleasePet( CChar *pet ) default: break; } + + // Remove Bonding if its Bonded + TAGMAPOBJECT petBond = pet->GetTag( "isBondedPet" ); + if( petBond.m_IntValue == 1 ) + { + petBond.m_IntValue = 0; + pet->SetTag( "isBondedPet", petBond ); + pet->RemoveScriptTrigger( 3107 );// petbonding.js + } } //o------------------------------------------------------------------------------------------------o diff --git a/source/targeting.cpp b/source/targeting.cpp index 142492ad0..42e0c2030 100644 --- a/source/targeting.cpp +++ b/source/targeting.cpp @@ -1230,6 +1230,13 @@ void TransferTarget( CSocket *s ) return; } + TAGMAPOBJECT deadPet = petChar->GetTag( "isPetDead" ); + if( deadPet.m_IntValue == 1 ) + { + s->SysMessage( 19301 ); // You may not trade a dead pet. + return; + } + if( cwmWorldState->ServerData()->YoungPlayerSystem() ) { if( !mChar->IsNpc() && mChar->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) && !targChar->GetAccount().wFlags.test( AB_FLAGS_YOUNG )) diff --git a/source/uox3.cpp b/source/uox3.cpp index d1cf8a015..380cb06b9 100644 --- a/source/uox3.cpp +++ b/source/uox3.cpp @@ -1266,6 +1266,13 @@ auto PassiveHealthRegen( CChar &mChar, UI16 maxHP ) -> SI32 if( mChar.GetHP() < maxHP ) { + TAGMAPOBJECT deadPet = mChar.GetTag( "isPetDead" ); + if( deadPet.m_IntValue == 1 ) + { + // If the pet is dead, we don't want to regen health for the owner + return nextHealthRegen; + } + if( !cwmWorldState->ServerData()->HungerSystemEnabled() || ( mChar.GetHunger() > 0 ) || ( !Races->DoesHunger( mChar.GetRace() ) && (( cwmWorldState->ServerData()->SystemTimer( tSERVER_HUNGERRATE ) == 0) || mChar.IsNpc() ))) {