From 7dff23fe759a2d882b213ff610d0952f62b58cac Mon Sep 17 00:00:00 2001 From: Dragon Slayer <85514184+DragonSlayer62@users.noreply.github.com> Date: Sun, 9 Nov 2025 19:05:15 -0600 Subject: [PATCH 1/2] Aquarium - [ADD] Added All UO Aquarium System. --- data/dfndata/house/house_addons_ml.dfn | 255 +++++ .../items/deeds/houseaddon_deeds_ml.dfn | 63 ++ .../items/houseaddons/house_addons.dfn | 197 ++++ data/dfndata/items/misc/aquarium.dfn | 374 ++++++++ data/dfndata/misc/books.dfn | 119 +++ data/js/item/aquarium.js | 891 ++++++++++++++++++ data/js/item/aquariumdecoration.js | 14 + data/js/item/aquariumfish.js | 72 ++ data/js/item/fishbowl.js | 207 ++++ data/js/jse_fileassociations.scp | 8 + 10 files changed, 2200 insertions(+) create mode 100644 data/dfndata/items/misc/aquarium.dfn create mode 100644 data/js/item/aquarium.js create mode 100644 data/js/item/aquariumdecoration.js create mode 100644 data/js/item/aquariumfish.js create mode 100644 data/js/item/fishbowl.js diff --git a/data/dfndata/house/house_addons_ml.dfn b/data/dfndata/house/house_addons_ml.dfn index 57d6fe89d..32e08ceec 100644 --- a/data/dfndata/house/house_addons_ml.dfn +++ b/data/dfndata/house/house_addons_ml.dfn @@ -346,6 +346,109 @@ HOUSE_ITEM=1058 HOUSE_ITEM=1059 } +// House Addon - Aquarium (South) +[HOUSE 324] +{ +ID=0x14F0 +SPACEX=3 +SPACEY=2 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=aquariumsouthdeed +HOUSE_ITEM=1060 +HOUSE_ITEM=1061 +} + +// House Addon - Aquarium (East) +[HOUSE 325] +{ +ID=0x14F0 +SPACEX=2 +SPACEY=3 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=aquariumeastdeed +HOUSE_ITEM=1062 +HOUSE_ITEM=1063 +} + +// House Addon - Small Elegant Aquarium +[HOUSE 326] +{ +ID=0x14F0 +SPACEX=1 +SPACEY=1 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=smallelegantaquarium +HOUSE_ITEM=1064 +} + +// House Addon - Wall Mounted Elegant Aquarium +[HOUSE 327] +{ +ID=0x14F0 +SPACEX=1 +SPACEY=1 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=wallmountedelegantaquariumsouthdeed +HOUSE_ITEM=1065 +HOUSE_ITEM=1066 +HOUSE_ITEM=1067 +} + +// House Addon - Wall Mounted Elegant Aquarium +[HOUSE 328] +{ +ID=0x14F0 +SPACEX=1 +SPACEY=1 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=wallmountedelegantaquariumeastdeed +HOUSE_ITEM=1068 +HOUSE_ITEM=1069 +HOUSE_ITEM=1070 +} + +// House Addon - Large Elegant Aquarium +[HOUSE 329] +{ +ID=0x14F0 +SPACEX=1 +SPACEY=1 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=largeelegantaquariumsouthdeed +HOUSE_ITEM=1071 +HOUSE_ITEM=1072 +HOUSE_ITEM=1073 +HOUSE_ITEM=1074 +} + +// House Addon - Large Elegant Aquarium +[HOUSE 330] +{ +ID=0x14F0 +SPACEX=1 +SPACEY=1 +CHARX=0 +CHARY=0 +CHARZ=0 +HOUSE_DEED=largeelegantaquariumeastdeed +HOUSE_ITEM=1075 +HOUSE_ITEM=1076 +HOUSE_ITEM=1077 +HOUSE_ITEM=1078 +} + [HOUSE ITEM 1000] { alchemist table part 1 (east) ITEM=0x3077 @@ -825,4 +928,156 @@ ITEM=0x3056 X=0 Y=1 Z=0 +} + +[HOUSE ITEM 1060] +{ +ITEM=0x3060 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1061] +{ +ITEM=0x3061 +X=-1 +Y=0 +Z=0 +} + +[HOUSE ITEM 1062] +{ +ITEM=0x3062 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1063] +{ +ITEM=0x3063 +X=0 +Y=-1 +Z=0 +} + +[HOUSE ITEM 1064] +{ +ITEM=0xA3A6 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1065] +{ +ITEM=0xA3B0 +X=1 +Y=0 +Z=0 +} + +[HOUSE ITEM 1066] +{ +ITEM=0xA3B5 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1067] +{ +ITEM=0xA3BA +X=-1 +Y=0 +Z=0 +} + +[HOUSE ITEM 1068] +{ +ITEM=0xA3BF +X=0 +Y=1 +Z=0 +} + +[HOUSE ITEM 1069] +{ +ITEM=0xA3C4 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1070] +{ +ITEM=0xA3C9 +X=0 +Y=-1 +Z=0 +} + +[HOUSE ITEM 1071] +{ large middle +ITEM=0xA3CE +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1072] +{ large right +ITEM=0xA3D3 +X=0 +Y=-1 +Z=0 +} + +[HOUSE ITEM 1073] +{ large middle +ITEM=0xA3D4 +X=-1 +Y=0 +Z=0 +} + +[HOUSE ITEM 1074] +{ large left +ITEM=0xA3D5 +X=-2 +Y=0 +Z=0 +} + +[HOUSE ITEM 1075] +{ large middle +ITEM=0xA3D6 +X=0 +Y=0 +Z=0 +} + +[HOUSE ITEM 1076] +{ large right +ITEM=0xA3DB +X=-1 +Y=0 +Z=0 +} + +[HOUSE ITEM 1077] +{ large middle +ITEM=0xA3DC +X=0 +Y=-1 +Z=0 +} + +[HOUSE ITEM 1078] +{ large left +ITEM=0xA3DD +X=0 +Y=-2 +Z=0 } \ No newline at end of file diff --git a/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn b/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn index 59ffb2c8e..cb435241f 100644 --- a/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn +++ b/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn @@ -212,4 +212,67 @@ name=Tall Elven Bed (South) id=0x14F0 morex=323 origin=ml +} + +[aquariumsouthdeed] +{ +name=Large Aquarium (South) +id=0x14F0 +morex=324 +value=3000 1500 +restock=15 +} + +[aquariumeastdeed] +{ +name=Large Aquarium (East) +id=0x14F0 +morex=325 +value=3000 1500 +restock=15 +} + +[smallelegantaquariumdeed] +{ +name=Small Elegant Aquarium +id=0x14F0 +morex=326 +value=5000 2500 +restock=15 +} + +[wallmountedelegantaquariumsouthdeed] +{ +name=Wall Mounted Aquarium (South) +id=0x14F0 +morex=327 +value=6000 3000 +restock=15 +} + +[wallmountedelegantaquariumeastdeed] +{ +name=Wall Mounted Aquarium (East) +id=0x14F0 +morex=328 +value=6000 3000 +restock=15 +} + +[largeelegantaquariumsouthdeed] +{ +name=Wall Mounted Aquarium (South) +id=0x14F0 +morex=329 +value=10000 5000 +restock=15 +} + +[largeelegantaquariumeastdeed] +{ +name=Large Aquarium (East) +id=0x14F0 +morex=330 +value=10000 5000 +restock=15 } \ No newline at end of file diff --git a/data/dfndata/items/houseaddons/house_addons.dfn b/data/dfndata/items/houseaddons/house_addons.dfn index d96e7e093..c48fa2a15 100644 --- a/data/dfndata/items/houseaddons/house_addons.dfn +++ b/data/dfndata/items/houseaddons/house_addons.dfn @@ -828,4 +828,201 @@ id=0x1E51 weight=1000 movable=2 decay=0 +} + +[0x3060] +{ south +get=base_item +name=a aquarium +id=0x3060 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0x3061] +{ south +get=base_item +name=a aquarium +id=0x3061 +weight=1000 +movable=2 +decay=0 +} + +[0x3062] +{ east +get=base_item +name=a aquarium +id=0x3062 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0x3063] +{ east +get=base_item +name=a aquarium +id=0x3063 +weight=1000 +movable=2 +decay=0 +} + +[0xA3A6] +{ +get=base_item +name=Small Elegant Aquarium +id=0xA3A6 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0xA3B0] +{// right part south +get=base_item +name=Wall Mounted Aquarium +id=0xA3B0 +weight=1000 +movable=2 +decay=0 +} + +[0xA3B5] +{// middle part south +get=base_item +name=Wall Mounted Aquarium +id=0xA3B5 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0xA3BA] +{// left part south +get=base_item +name=Wall Mounted Aquarium +id=0xA3BA +weight=1000 +movable=2 +decay=0 +} + +[0xA3BF] +{// right part east +get=base_item +name=Wall Mounted Aquarium +id=0xA3BF +weight=1000 +movable=2 +decay=0 +} + +[0xA3C4] +{// middle part east +get=base_item +name=Wall Mounted Aquarium +id=0xA3C4 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0xA3C9] +{// left part east +get=base_item +name=Wall Mounted Aquarium +id=0xA3C9 +weight=1000 +movable=2 +decay=0 +} + +[0xA3CE] +{// middle part south +get=base_item +name=Large Aquarium +id=0xA3CE +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0xA3D3] +{// right part south +get=base_item +name=Large Aquarium +id=0xA3D3 +weight=1000 +movable=2 +decay=0 +} + +[0xA3D4] +{// middle part part south +get=base_item +name=Large Aquarium +id=0xA3D4 +weight=1000 +movable=2 +decay=0 +} + +[0xA3D5] +{// left part part south +get=base_item +name=Large Aquarium +id=0xA3D5 +weight=1000 +movable=2 +decay=0 +} + +[0xA3D6] +{// middle part south +get=base_item +name=Large Aquarium +id=0xA3D6 +weight=1000 +movable=2 +decay=0 +script=5100 +} + +[0xA3DB] +{// right part south +get=base_item +name=Large Aquarium +id=0xA3DB +weight=1000 +movable=2 +decay=0 +} + +[0xA3DC] +{// middle part part south +get=base_item +name=Large Aquarium +id=0xA3DC +weight=1000 +movable=2 +decay=0 +} + +[0xA3DD] +{// left part part south +get=base_item +name=Large Aquarium +id=0xA3DD +weight=1000 +movable=2 +decay=0 } \ No newline at end of file diff --git a/data/dfndata/items/misc/aquarium.dfn b/data/dfndata/items/misc/aquarium.dfn new file mode 100644 index 000000000..337326a88 --- /dev/null +++ b/data/dfndata/items/misc/aquarium.dfn @@ -0,0 +1,374 @@ +[fishbowl] +{ +get=base_item +name=a Fish Bowl +id=0x241C +script=5103 +decay=0 +weight=1 +} + +//Aquarium Fish +[base_aquarium_fish] +{ +get=base_item +decay=1 +weight=1 +custominttag=isAquariumFish 1 +script=5101 +} + +[albinofrog] +{ +get=base_aquarium_fish +name=An Albino Frog +id=0x3B0D +color=0x47E +} + +[minocbluefish] +{ +get=base_aquarium_fish +name=A Minoc Blue Fish +id=0x3AFE +} + +[albinocourtesanfish] +{ +get=base_aquarium_fish +name=Albino Courtesan Fish +id=0x3B04 +} + +[britaincrownfish] +{ +get=base_aquarium_fish +name=Britain Crown Fish +id=0x3AFF +} + +[fandancerfish] +{ +get=base_aquarium_fish +name=Fandancer Fish +id=0x3B02 +} + +[goldenbroadtail] +{ +get=base_aquarium_fish +name=A Golden Broadtail +id=0x3B03 +} + +[jellyfish] +{ +get=base_aquarium_fish +name=Jellyfish +id=0x3B0E +} + +[longclawcrab] +{ +get=base_aquarium_fish +name=A Long Claw Crab +id=0x3AFC +color=0x527 +} + +[speckledcrab] +{ +get=base_aquarium_fish +name=A Speckled Crab +id=0x3AFC +} + +[makotocourtesanfish] +{ +get=base_aquarium_fish +name=A Makoto Courtesan Fish +id=0x3AFD +} + +[killerfrog] +{ +get=base_aquarium_fish +name=A Killer Frog +id=0x3B0D +} + +[nujelmhoneyfish] +{ +get=base_aquarium_fish +name=A Nujelm Honey Fish +id=0x3ACB +} + +[purplefrog] +{ +get=base_aquarium_fish +name=A Purple Frog +id=0x3AE5 +color=0x4fa +} + +[reddartfish] +{ +get=base_aquarium_fish +name=A Red Dart Fish +id=0x3AD1 +} + +[shrimp] +{ +get=base_aquarium_fish +name=A Shrimp +id=0x3ACE +} + +[smallmouthsuckerfin] +{ +get=base_aquarium_fish +name=A Smallmouth Suckerfin +id=0x3ACF +} + +[speckledcrab] +{ +get=base_aquarium_fish +name=A Speckled Crab +id=0x3ACC +} + +[spinedscratcherfish] +{ +get=base_aquarium_fish +name=A Spined Scratcher Fish +id=0x3AD0 +} + +[spottedbuccaneer] +{ +get=base_aquarium_fish +name=A Spotted Buccaneer +id=0x3ACE +} + +[vesperreeftiger] +{ +get=base_aquarium_fish +name=A Vesper Reef Tiger +id=0x3ACA +} + +[yellowfinbluebelly] +{ +get=base_aquarium_fish +name=A Yellow-Fin Bluebelly +id=0x3ACD +} + +//Reward Fish + +[Coral] +{ +get=base_aquarium_fish +name=Coral +id=0x3AF9 +} + +[Coral1] +{ +get=Coral +id=0x3AFA +} + +[Coral2] +{ +get=Coral +id=0x3AFB +} + +[brineshrimp] +{ +get=base_aquarium_fish +name=Brine Shrimp +id=0x3B13 +} + +[fullmoonfish] +{ +get=base_aquarium_fish +name=A Full Moon Fish +id=0x3B15 +} + +[seahorsefish] +{ +get=base_aquarium_fish +name=A Sea Horse +id=0x3B10 +} + +[strippedflakefish] +{ +get=base_aquarium_fish +name=Stripped Flake Fish +id=0x3B0A +} + +[strippedsosarianswill] +{ +get=base_aquarium_fish +name=Stripped Sosarian Swill +id=0x3B0A +} + +[base_aquarium_decoration] +{ +get=base_item +decay=1 +weight=1 +custominttag=isAquariumDecor 1 +script=5102 +} + +[aquariummessage] +{ +get=base_aquarium_decoration +name=Message in a Bottle +id=0x099F +weight=100 +value=8 4 +script=5031 +} + +[captainblackheartsfishingpole] +{ +get=base_aquarium_decoration +name=Captain Blackheart's Fishing Pole +id=0x0DC0 +weight=800 +value=15 7 +restock=20 +decay=1 +good=54 +layer=0x02 +maxuses=75 +usesleft=25 +sectionid=captainblackheartsfishingpole +script=2200// uses left tooltip +} + +[craftysfishinghat] +{ +get=base_aquarium_decoration +name=Crafty's Fishing Hat +id=0x1713 +weight=100 +value=25 12 +layer=0x06 +def=3 +hp=50 +sectionid=craftysfishinghat +more=0x1 0x0 0x0 0x0//Medable armor +} + +[fishbones] +{ +get=base_aquarium_decoration +name=Fish bones +id=0x3B0C +} + +[islandstatue] +{ +get=base_aquarium_decoration +name=An island statue +id=0x3B0F +} + +[shell] +{ +get=base_aquarium_decoration +name=A shell +id=0x3B12 +} + +[shell1] +{ +get=Shell +id=0x3B13 +} + +[toyboat] +{ +get=base_aquarium_decoration +name=A toy boat +id=0x14F4 +} + +[waterloggedboots] +{ +get=base_aquarium_decoration +name=Waterlogged boots +id=0x1711 +weight=400 +value=56 28 +layer=0x03 +def=7 +hp=50 +str=20 +dyeable=1 +decay=1 +good=19 +sectionid=waterloggedboots +more=0x1 0x0 0x0 0x0//Medable armor +} + +[aquariumfood] +{ +get=base_item +name=Aquarium Food +id=0xEFC +decay=1 +weight=1 +custominttag=isAquariumFood 1 +} + +[aquariumflakesphere] +{ +get=base_item +name=An aquarium flake sphere +id=0x973 +decay=1 +weight=1 +custominttag=isAquariumFood 1 +script=5102 +} + +[0xA3E9] +{ +get=base_item +name=Live Rock +id=0xA3E9 +decay=1 +weight=1000 +} + +[liverock] +{ +get=0xA3E9 +} + +[yournewaquarium] +{ +get=base_item +name=Your New Aquarium +id=0x0ff2 +weight=100 +type=11 +decay=1 +value=22 11 +more=0x00000042 +} \ No newline at end of file diff --git a/data/dfndata/misc/books.dfn b/data/dfndata/misc/books.dfn index fe05643d7..c68fb3954 100644 --- a/data/dfndata/misc/books.dfn +++ b/data/dfndata/misc/books.dfn @@ -10493,3 +10493,122 @@ LINE= LINE=We no longer bury the LINE=dead. } + +// ============================ Your New Aquarium +[BOOK 66] +{ +PAGES=9 +TITLE=Your New Aquarium +AUTHOR=Les Bilgewater +PAGE=9100 +PAGE=9101 +PAGE=9102 +PAGE=9103 +PAGE=9104 +PAGE=9105 +PAGE=9106 +PAGE=9107 +PAGE=9108 +} + +[PAGE 9100] +{ +LINE=Welcome to the +LINE=wonderful world +LINE=of aquarium ownership. +LINE=With a little time and +LINE=skill your aquarium can +LINE=become a work of art! +} + +[PAGE 9101] +{ +LINE=Catching Fish: +LINE=You will need to +LINE=aquire a fishing net, +LINE=and a number of fish +LINE=bowls, from your local +LINE=fisherman. +LINE=To use the net, +LINE=select it from your +} + +[PAGE 9102] +{ +LINE=backpack, and cast it +LINE=into shallow water. +LINE=Then we wait. +LINE= The fish will be so +LINE=happy to be in your +LINE=aquarium, they will +LINE=jump right into your +LINE=fish bowl when caught! +} + +[PAGE 9103] +{ +LINE=Just take them home +LINE=and pour them into +LINE=your aquarium, and +LINE=BING! You have a happy +LINE=new addition to your +LINE=collection! +} + +[PAGE 9104] +{ +LINE=Caring for our +LINE=Underwater Allies: +LINE= +LINE= Fish need clean +LINE=water and food to +LINE=survive. +LINE= Our aquarium comes +LINE=with a status monitoring +} + +[PAGE 9105] +{ +LINE=aid. Must be Magic! +LINE= Just look at the +LINE=front of your aquarium +LINE=(mouse over). See the +LINE=helpful and friendly +LINE=guide? It tells how much +LINE=food and water is +LINE=needed to maintain, +} + +[PAGE 9106] +{ +LINE=and improve the quality +LINE=of your tank. +LINE= You dont want to +LINE=overfeed your fish +LINE=very often. In fact, +LINE=you only want to feed +LINE=them every other day. +LINE=But, remember to keep +} + +[PAGE 9107] +{ +LINE=the water strong and +LINE=healthy, and add more +LINE=on a regular basis +LINE=to keep your aquarium +LINE=a pretty, sparkely blue. +LINE= +LINE= Well, I hope you +LINE=get as much enjoyment, +} + +[PAGE 9108] +{ +LINE=collecting a beautiful +LINE=array of our fine, finned, +LINE=friends from the sea, +LINE=as I do! +LINE= +LINE=Happy Fishing! +} \ No newline at end of file diff --git a/data/js/item/aquarium.js b/data/js/item/aquarium.js new file mode 100644 index 000000000..d9fa762f0 --- /dev/null +++ b/data/js/item/aquarium.js @@ -0,0 +1,891 @@ +// @ts-check +let AQUA_EVAL_MS = 86400000; // daily tick + +function initAquarium( aquarium ) +{ + // Skip if already initialized + if(( aquarium.GetTag( "aqua_inited" ) | 0 ) === 1 ) + { + aquarium.StartTimer( AQUA_EVAL_MS, 1, true ); + return; + } + + // Baseline counts + aquarium.SetTag( "aqua_live", 0 ); + aquarium.SetTag( "aqua_dead", 0 ); + aquarium.SetTag( "aqua_decor", 0 ); + aquarium.SetTag( "aqua_reward", 0 ); + aquarium.SetTag( "aqua_vacDays", 0 ); + aquarium.SetTag( "aqua_evalDay", 1 ); // start on state day + + // Food defaults ( Full + thresholds ) + aquarium.SetTag( "aqua_food_state", 3 ); + var fishMaint = RandomNumber( 1, 2 ); // 1..2 + aquarium.SetTag( "aqua_food_maint", fishMaint ); + aquarium.SetTag( "aqua_food_impr", fishMaint + 2 ); + aquarium.SetTag( "aqua_food_added", 0 ); + + // Water defaults ( Strong + thresholds ) + aquarium.SetTag( "aqua_water_state", 4 ); + var waterMaint = RandomNumber( 1, 3 ); // 1..3 + aquarium.SetTag( "aqua_water_maint", waterMaint ); + aquarium.SetTag( "aqua_water_impr", waterMaint + 2 ); + aquarium.SetTag( "aqua_water_added", 0 ); + + // Ordered list tag ( CSV; keep empty string ) + aquarium.SetTag( "aqua_list", "" ); + + // Soft capacity ( fallback if unset/zero ) + var cap = aquarium.GetTag( "aqua_maxItems" ); + if( cap <= 0 ) + aquarium.SetTag( "aqua_maxItems", 30 ); + + // Mark initialized and start timer + aquarium.SetTag( "aqua_inited", 1 ); + aquarium.SetTag( "isAquariumTank", 1 ); + aquarium.StartTimer( AQUA_EVAL_MS, 1, true ); +} + +function totalItems( aquarium ) +{ + var live = aquarium.GetTag( "aqua_live" ); + var dead = aquarium.GetTag( "aqua_dead" ); + var deco = aquarium.GetTag( "aqua_decor" ); + return ( live + dead + deco ); +} + +function maxLiveCreatures( aquarium ) +{ + var fish = aquarium.GetTag( "aqua_food_state" ); + var water = aquarium.GetTag( "aqua_water_state" ); + + var state = (( fish === 4 ) ? 1 : ( 3 - fish )) + ( 4 - water ); + if( state < 0 ) + state = 0; + + var penalty = Math.floor( Math.pow( state, 1.75 )); + var baseCap = aquarium.GetTag( "aqua_maxItems" ); + if( baseCap <= 0 ) + baseCap = 30; + + var cap = baseCap - penalty; + if( cap < 0 ) + cap = 0; + return cap | 0; +} + +function updateFood( aquarium ) +{ + var foodState = aquarium.GetTag( "aqua_food_state" ); + var foodMaint = aquarium.GetTag( "aqua_food_maint" ); + var foodImpr = aquarium.GetTag( "aqua_food_impr" ); + var foodAdd = aquarium.GetTag( "aqua_food_added" ); + + if( foodImpr <= 0 && foodState !== 4 && foodState !== 0 ) + foodImpr = foodMaint + 2; + + if( foodAdd < foodMaint ) + foodState = ( foodState > 0 ) ? ( foodState - 1 ) : 0; + else if( foodAdd >= foodImpr ) + foodState = ( foodState < 4 ) ? ( foodState + 1 ) : 4; + + foodMaint = RandomNumber( 1, Math.max( 1, ( 4 - foodState ))); + foodImpr = ( foodState === 4 ) ? 0 : ( foodMaint + 2 ); + foodAdd = 0; + + aquarium.SetTag( "aqua_food_state", foodState ); + aquarium.SetTag( "aqua_food_maint", foodMaint ); + aquarium.SetTag( "aqua_food_impr", foodImpr ); + aquarium.SetTag( "aqua_food_added", foodAdd ); +} + +function updateWater( aquarium ) +{ + var WaterState = aquarium.GetTag( "aqua_water_state" ); + var WaterMaint = aquarium.GetTag( "aqua_water_maint" ); + var WaterImpr = aquarium.GetTag( "aqua_water_impr" ); + var WaterAdd = aquarium.GetTag( "aqua_water_added" ); + + if( WaterImpr <= 0 && WaterState !== 4 && WaterState !== 0 ) + WaterImpr = WaterMaint + 2; + + if( WaterAdd < WaterMaint ) + WaterState = ( WaterState > 0 ) ? ( WaterState - 1 ) : 0; + else if( WaterAdd >= WaterImpr ) + WaterState = ( WaterState < 4 ) ? ( WaterState + 1 ) : 4; + + WaterMaint = RandomNumber( 1, Math.max( 1, ( 4 - WaterState )) ); + WaterImpr = ( WaterState === 4 ) ? 0 : ( WaterMaint + 2 ); + WaterAdd = 0; + + aquarium.SetTag( "aqua_water_state", WaterState ); + aquarium.SetTag( "aqua_water_maint", WaterMaint ); + aquarium.SetTag( "aqua_water_impr", WaterImpr ); + aquarium.SetTag( "aqua_water_added", WaterAdd ); +} + +function hatchOrCull( aquarium ) +{ + var live = aquarium.GetTag( "aqua_live" ); + var cap = maxLiveCreatures( aquarium ); + + var fish = aquarium.GetTag( "aqua_food_state" ); + var water = aquarium.GetTag( "aqua_water_state" ); + var optimal = ( fish === 3 && water === 4 ); + + if( optimal && live < cap ) + { + var chance = 0.005 * live; + if( Math.random() < chance ) + { + live += 1; + aquarium.SetTag( "aqua_live", live ); + } + } + + cap = maxLiveCreatures( aquarium ); + + if( live > cap ) + { + var kill = live - cap; + + // try to mark 'kill' number of live fish items dead + var actuallyKilled = 0; + for( var k = 0; k < kill; k++ ) + { + if( markRandomFishDead( aquarium )) + actuallyKilled++; + else + break; + } + + if( actuallyKilled > 0 ) + { + live -= actuallyKilled; + var dead = aquarium.GetTag( "aqua_dead" ); + dead += actuallyKilled; + aquarium.SetTag( "aqua_live", live ); + aquarium.SetTag( "aqua_dead", dead ); + } + } + else + { + if( Math.random() < 0.01 && live > 0 ) + { + // convert one live fish item to dead art + var changed = markRandomFishDead( aquarium ); + // only decrement counters if we actually flipped an item + if( changed ) + { + live -= 1; + var dead2 = aquarium.GetTag( "aqua_dead" ); + dead2 += 1; + aquarium.SetTag( "aqua_live", live ); + aquarium.SetTag( "aqua_dead", dead2 ); + } + } + } +} + +// Put near top with other helpers +function stopFishAirTimer( fish ) +{ + fish.KillJSTimer( 1, 7532 ); + fish.SetTag( "fishAirActive", 0 ); +} + +function doEvaluate( aquarium ) +{ + var vac = aquarium.GetTag( "aqua_vacDays" ); + if( vac > 0 ) + { + aquarium.SetTag( "aqua_vacDays", vac - 1 ); + return; + } + + var evalDay = aquarium.GetTag( "aqua_evalDay" ); + if( evalDay === 1 ) + { + updateFood( aquarium ); + updateWater( aquarium ); + + var live = aquarium.GetTag( "aqua_live" ); + if( live > 0 ) + aquarium.SetTag( "aqua_reward", 1 ); + } + else + { + hatchOrCull( aquarium ); + } + + aquarium.SetTag( "aqua_evalDay", ( evalDay === 1 ) ? 0 : 1 ); + aquarium.Refresh(); +} + +function giveReward( pUser, aquarium ) +{ + var rwd = aquarium.GetTag( "aqua_reward" ); + if( rwd !== 1 ) + { + pUser.SysMessage( "No reward available." ); + return; + } + + var live = aquarium.GetTag( "aqua_live" ); + var maxTot = aquarium.GetTag( "aqua_maxItems" ); + if( maxTot <= 0 ) + maxTot = 30; + + var bucket = Math.floor(( live / Math.max( 1, maxTot )) * 5 ); + if( bucket < 0 ) + bucket = 0; + if( bucket > 4 ) + bucket = 4; + + var table = [ "Shell", "Shell1", "AquariumMessage", "CaptainBlackheartsFishingPole", "CraftysFishingHat", "FishBones", "IslandStatue", "ToyBoat", "WaterloggedBoots", "Coral", "Coral1", "Coral2", "brineshrimp", "fullmoonfish", "seahorsefish", "strippedflakefish", "StrippedSosarianSwill"]; + var sid = table[bucket]; + + if( sid ) + { + var r = CreateDFNItem( pUser.socket, pUser, sid, 1, "ITEM", true ); + if( ValidateObject( r )) + { + pUser.SoundEffect( 0x05A3, true); + pUser.SysMessage( "You receive a reward." ); + } + } + aquarium.SetTag( "aqua_reward", 0 ); +} + +function markRandomFishDead( aquarium ) +{ + var items = readAquaList( aquarium ); + if( !items.length ) + return false; + + // filter to live fish (isAquariumFish=1 and not dead) + var candidates = []; + for( var i = 0; i < items.length; i++ ) + { + var aquariumItem = items[i]; + if( !ValidateObject( aquariumItem )) + continue; + + if( !ValidateObject( aquariumItem.container) || aquariumItem.container.serial !== aquarium.serial ) + continue; + + var isFish = aquariumItem.GetTag( "isAquariumFish" ); + var isDead = aquariumItem.GetTag( "dead" ); + if( isFish === 1 && isDead === 0 ) + candidates.push( aquariumItem ); + } + if( !candidates.length ) + return false; + + var pick = candidates[( Math.random() * candidates.length ) | 0]; + + // flip to bones/dead art + tag + pick.SetTag( "dead", 1 ); + pick.color = 0; // optional: clear hue + pick.id = 0x3B0C; // dead art + pick.Refresh(); + + return true; +} + +function BID_REMOVE( number ) +{ + return 1000 + ( number|0 ); +} + +function readAquaList( aquarium ) +{ + var raw = aquarium.GetTag( "aqua_list" ); + if( raw === null || raw === 0 ) + return []; + + if( raw === "" ) + return []; + var parts = raw.split( "," ); + var out = []; + for( var i = 0; i < parts.length; i++ ) + { + var s = parts[i]; + if( s === 0 ) + continue; + + var ser = parseInt( s, 10 ); + if( isNaN( ser )) + continue; + + var aquariumItem = CalcItemFromSer( ser ); + if( ValidateObject( aquariumItem )) + out.push( aquariumItem ); + } + return out; +} + +function writeAquaList( aquarium, items ) +{ + var ss = []; + for( var i = 0; i < items.length; i++ ) + { + if( ValidateObject( items[i] )) + ss.push( String( items[i].serial )); + } + aquarium.SetTag( "aqua_list", ss.join( "," )); +} + +function sanitizeAquaList( aquarium ) +{ + var items = readAquaList( aquarium ); + writeAquaList( aquarium, items ); + return items; +} + +function openAquaGump( pUser, aquarium ) +{ + var items = sanitizeAquaList( aquarium ); + var count = items.length; + + var cur = pUser.GetTempTag( "aqua_page" ) | 0; + if( cur <= 0 ) + cur = 1; + + if( count === 0 ) + cur = 1; + + if( cur > count ) + cur = count; + + var aquariumGump = new Gump; + aquariumGump.AddBackground( 0, 0, 350, 323, 0x0E10 ); + aquariumGump.AddGump( 0, 0, 0x2C96 ); + + // remember binding + current page + pUser.SetTag( "aqua_serial", aquarium.serial ); + pUser.SetTempTag( "aqua_page", cur ); + + if( count === 0 ) + { + aquariumGump.AddText( 20, 195, 0x480, "No items in this aquarium." ); + aquariumGump.Send( pUser.socket ); + aquariumGump.Free(); + return; + } + + var aquariumItem = items[cur - 1]; + aquariumGump.AddPage( 1 ); + + var itemName = ( aquariumItem.name && aquariumItem.name !== "" ) ? aquariumItem.name : ( "Item 0x" + aquariumItem.id.toString( 16 )); + aquariumGump.AddText( 20, 217, 0x480, itemName ); + + var isFish = ( aquariumItem.GetTag( "isAquariumFish" )); + aquariumGump.AddText( 20, 239, 0x480, ( isFish === 1 ) ? "Aquarium creature" : "An aquarium decoration" ); + + if( typeof aquariumGump.AddPictureColor === "function" ) + aquariumGump.AddPictureColor( 150, 80, aquariumItem.id, aquariumItem.color | 0 ); + else + aquariumGump.AddPicture( 150, 80, aquariumItem.id ); + + aquariumGump.AddText( 20, 195, 0x480, ( cur + "/" + count )); + + var edit = ( pUser.isGM === true ); + if( edit ) + { + aquariumGump.AddBackground( 230, 195, 100, 26, 0x13BE ); + // ACTION button ( close=1 ), uniqueID = BID_REMOVE( cur ), page=0 + aquariumGump.AddButton( 235, 200, 0x0845, 1, 0, BID_REMOVE( cur )); + aquariumGump.AddXMFHTMLTok( 260, 198, 60, 26, false, false, 0, 1073838, "", "", "" );// Remove + } + + // Prev ( ACTION: close=1, go handle in onGumpPress ) + if( cur > 1 ) + { + aquariumGump.AddButton( 45, 280, 0x0FAE, 1, 0, 90002 ); + aquariumGump.AddXMFHTMLTok( 80, 283, 100, 18, false, false, 0xFFFFFF, 1044044, "", "", "" );// PREV PAGE + } + + // Next ( ACTION: close=1 ) + if( cur < count ) + { + aquariumGump.AddButton( 195, 280, 0x0FA5, 1, 0, 90001 ); + aquariumGump.AddXMFHTMLTok( 230, 283, 100, 18, false, false, 0xFFFFFF, 1044045, "", "", "" );// NEXT PAGE + } + + aquariumGump.Send( pUser.socket ); + aquariumGump.Free(); +} + +function showEvents( pUser, aquarium ) +{ + var aquariumGump = new Gump; + aquariumGump.AddBackground( 0, 0, 220, 350, 2600 ); + + var y = 30; + aquariumGump.AddText( 55, y, 0x480, "Aquarium Events" ); + y += 20; + + var raw = aquarium.GetTag( "aqua_events" ); + if( !( raw === null || raw === 0 )) + { + if( raw !== 0 ) + { + var parts = raw.split( "," ); + for( var i = 0; i < parts.length; i++ ) + { + if( parts[i] === 0 ) + continue; + + aquariumGump.AddText( 18, y, 0x480, "- " + parts[i] ); y += 16; + } + } + } + else + { + var live = aquarium.GetTag( "aqua_live" ); + var dead = aquarium.GetTag( "aqua_dead" ); + var deco = aquarium.GetTag( "aqua_decor" ); + var fSt = aquarium.GetTag( "aqua_food_state" ); + var wSt = aquarium.GetTag( "aqua_water_state" ); + + aquariumGump.AddText( 18, y, 0x480, "Live: " + live + " Dead: " + dead + " Decor: " + deco ); y += 18; + aquariumGump.AddText( 18, y, 0x480, "Food State: " + fSt + " Water State: " + wSt ); y += 18; + } + + aquariumGump.Send( pUser.socket ); + aquariumGump.Free(); +} + + +function onGumpPress( pSocket, buttonID ) +{ + var pUser = pSocket.currentChar; + if( !ValidateObject( pUser )) + return; + + var serial = Number( pUser.GetTag( "aqua_serial" )); + var aquarium = CalcItemFromSer( serial ); + if( !ValidateObject( aquarium )) + return; + + var items = sanitizeAquaList( aquarium ); + var count = items.length; + var cur = Number( pUser.GetTempTag( "aqua_page" )); + if( cur <= 0 ) + cur = 1; + if( cur > count ) + cur = count; + + // Remove current page’s item + if( buttonID >= ( 1000 +1 ) && buttonID <= ( 1000 + count )) + { + var idx = ( buttonID - 1000 ) - 1; + var aquariumItem = items[idx]; + if( ValidateObject( aquariumItem )) + { + var isFish = aquariumItem.GetTag( "isAquariumFish" ); + var isDead = aquariumItem.GetTag( "dead" ); + if( isFish === 1 ) + { + if( isDead === 1 ) + { + aquarium.SetTag( "aqua_dead", Math.max( 0, ( Number( aquarium.GetTag( "aqua_dead" ))) - 1 )); + } + else + aquarium.SetTag( "aqua_live", Math.max( 0, ( Number( aquarium.GetTag( "aqua_live" ))) - 1 )); + } + else + { + aquarium.SetTag( "aqua_decor", Math.max( 0, ( Number( aquarium.GetTag( "aqua_decor" ))) - 1 )); + } + aquariumItem.Delete(); + } + items = sanitizeAquaList( aquarium ); + var newCount = items.length; + var newPage = Math.min( Math.max( 1, idx + 1 ), Math.max( 1, newCount )); + pUser.SetTempTag( "aqua_page", newPage ); + openAquaGump( pUser, aquarium ); + return; + } + + // Navigation ( action buttons ) + if( buttonID === 90001 ) + { + if( cur < count ) + pUser.SetTempTag( "aqua_page", cur + 1 ); + openAquaGump( pUser, aquarium ); + return; + } + + if( buttonID === 90002 ) + { + if( cur > 1 ) + pUser.SetTempTag( "aqua_page", cur - 1 ); + openAquaGump( pUser, aquarium ); + return; + } + + // Claim, etc., if you use them: + if( buttonID === 8001 ) + { + giveReward( pUser, aquarium ); + openAquaGump( pUser, aquarium ); + return; + } +} + +/** @type { ( tSock: Socket, baseObj: BaseObject ) => boolean } */ +function onContextMenuRequest( socket, targObj ) +{ + var pUser = socket.currentChar; + if( !ValidateObject( pUser )) + return false; + + var list = []; + list.push( { id: 0x1001, text: 6235, flags: 0x0020, hue: 0x03E0 } ); // Examine Aquarium + list.push( { id: 0x1002, text: 6239, flags: 0x0020, hue: 0x03E0 } ); // View Events + list.push( { id: 0x1003, text: 6237, flags: 0x0020, hue: 0x03E0 } ); // Collect Reward + + if( pUser.isGM === true ) + { + list.push( { id: 0x2001, text: 6234, flags: 0x0020, hue: 0x03E0 } ); // GM Open + list.push( { id: 0x2002, text: 6232, flags: 0x0020, hue: 0x03E0 } ); // GM Add Water + list.push( { id: 0x2003, text: 6236, flags: 0x0020, hue: 0x03E0 } ); // GM Fill Food+Water + list.push( { id: 0x2004, text: 6233, flags: 0x0020, hue: 0x03E0 } ); // GM Force Evaluate + } + + var useKR = false; // 2D so hue works + TriggerEvent( 18001, "modifyContextMenu", socket, targObj, list, useKR ); + return false; +} + +/** @type { ( tSock: Socket, baseObj: BaseObject, popupEntry: number ) => boolean } */ +function onContextMenuSelect( socket, targObj, popupEntry ) +{ + var pUser = socket.currentChar; + if( !ValidateObject( pUser )) + return false; + + pUser.SetTag( "aqua_serial", targObj.serial ); + + switch( popupEntry ) + { + case 0x1001: + openAquaGump( pUser, targObj ); + break; + + case 0x1002: + showEvents( pUser, targObj ); + break; + + case 0x1003: + giveReward( pUser, targObj ); + openAquaGump( pUser, targObj ); + break; + + case 0x2001: + openAquaGump( pUser, targObj ); + break; + + case 0x2002: + { + var waterAdd = Number( targObj.GetTag( "aqua_water_added" )); + targObj.SetTag( "aqua_water_added", waterAdd + 1 ); + socket.SysMessage( "Added 1 water unit." ); + break; + } + + case 0x2003: + { + var fishMaint = targObj.GetTag( "aqua_food_maint" ); + var waterMaint = targObj.GetTag( "aqua_water_maint" ); + targObj.SetTag( "aqua_food_added", fishMaint ); + targObj.SetTag( "aqua_water_added", waterMaint ); + socket.SysMessage( "Filled food and water to maintenance." ); + break; + } + + case 0x2004: + doEvaluate( targObj ); + openAquaGump( pUser, targObj ); + break; + } + return false; +} + +/** @type { ( user: Character, iUsing: Item ) => boolean } */ +function onUseChecked( pUser, iUsed ) +{ + if( !ValidateObject( pUser ) || !ValidateObject( iUsed )) + return false; + + pUser.SetTag( "aqua_serial", iUsed.serial ); + openAquaGump( pUser, iUsed ); + return false; +} + +/** @type { ( item: Item, dropper: Character, dest: Item ) => number } */ +function onDropItemOnItem( iDropped, cDropper, iDroppedOn ) +{ + if( !ValidateObject( iDroppedOn )) + return 0; + + // --- Vacation wafer / sphere (adds 7 days per item) --- + var isVac = Number( iDropped.GetTag( "isAquariumVac" )); + if( isVac === 1 ) + { + var addDays = Math.max( 1, ( iDropped.amount ) || 1 ) * 7; // support stacks + var curVac = Number( iDroppedOn.GetTag( "aqua_vacDays" )); + + iDroppedOn.SetTag( "aqua_vacDays", curVac + addDays ); + cDropper.SysMessage( "You add vacation days to the aquarium (" + addDays + " days)."); + cDropper.SoundEffect(37, true); + iDroppedOn.Refresh(); + iDropped.Delete(); + return 2; + } + + // Food + var isFood = iDropped.GetTag( "isAquariumFood" ); + if( isFood === 1 ) + { + var setFood = Number( iDroppedOn.GetTag( "aqua_food_added" )); + iDroppedOn.SetTag( "aqua_food_added", setFood + 1 ); + cDropper.SysMessage( "You add food to the aquarium." ); + iDroppedOn.Refresh(); + + // append to paged list + var li = sanitizeAquaList( iDroppedOn ); // food is not displayed; do not add + return 2; + } + + // Water + var isLiquidContainer = iDropped.GetTag("ContentsType"); // 2==water + var usesLeft = iDropped.usesLeft; + if( isLiquidContainer === "water" ) + { + // This is a water container; treat the drop as a pour action, not a container move + var waterAdd = Number( iDroppedOn.GetTag( "aqua_water_added" )); + iDroppedOn.SetTag( "aqua_water_added", waterAdd + usesLeft ); + // consume one use + iDropped.usesLeft = Math.max( 0, usesLeft - 5 ); + + // flip art when empty if your helper exists (safe no-op if not) + if( iDropped.usesLeft === 0 ) + { + iDropped.SetTag( "ContentsType", 1 ); + iDropped.SetTag( "ContentsName", "nothing" ); + + // caller context isn’t guaranteed to have a socket; just pass null/undefined + TriggerEvent( 2100, "switchPitcherID", null, iDropped ); + } + cDropper.SoundEffect( 37, true ); + cDropper.SysMessage( "You pour water into the aquarium." ); + iDroppedOn.Refresh(); + + return 0; // handled; do NOT move the pitcher into the tank + } + + var total = totalItems( iDroppedOn ); + var maxTotal = Number( iDroppedOn.GetTag( "aqua_maxItems" )); + if( maxTotal <= 0 ) + maxTotal = 30; + + // Fish + var isFish = Number( iDropped.GetTag( "isAquariumFish" )); + var isDead = Number( iDropped.GetTag( "dead" )); + if( isFish === 1 && isDead === 0 ) + { + var live = Number( iDroppedOn.GetTag( "aqua_live" )); + var liveCap = maxLiveCreatures( iDroppedOn ); + + if( total >= maxTotal || live >= liveCap ) + { + cDropper.SysMessage( "The aquarium can not hold the creature." ); + return 0; + } + + stopFishAirTimer( iDropped ); + + iDropped.container = iDroppedOn; + iDroppedOn.SetTag( "aqua_live", live + 1 ); + + // add to paged list + var items = sanitizeAquaList( iDroppedOn ); + items.push( iDropped ); + iDroppedOn.Refresh(); + writeAquaList( iDroppedOn, items ); + + cDropper.SysMessage( "You add the creature to your aquarium." ); + return 2; + } + + // Decoration + var isDecor = Number( iDropped.GetTag( "isAquariumDecor" )); + if( isDecor === 1 ) + { + if( total >= maxTotal ) + { + cDropper.SysMessage( "The aquarium is full." ); + return 0; + } + + iDropped.container = iDroppedOn; + var dec = Number( iDroppedOn.GetTag( "aqua_decor" )); + iDroppedOn.SetTag( "aqua_decor", dec + 1 ); + + // add to paged list + var items2 = sanitizeAquaList( iDroppedOn ); + items2.push( iDropped ); + iDroppedOn.Refresh(); + writeAquaList( iDroppedOn, items2 ); + + cDropper.SysMessage( "You add the decoration to your aquarium." ); + return 2; + } + + return 1; +} + +/** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */ +function onCreateDFN( objMade, objType ) +{ + if( objType == 0 ) + initAquarium( objMade ); +} + +/** @type { ( tObject: BaseObject, timerId: number ) => void } */ +function onTimer( aquarium, timerID ) +{ + if( timerID !== 1 ) + return; + + doEvaluate( aquarium ); + aquarium.StartTimer( AQUA_EVAL_MS, 1, true ); +} + +function Aqua_GetData( tank ) +{ + var data = { + vacDays: ( tank.GetTag("aqua_vacDays")), + events: tank.GetTag("aqua_events") || "", + reward: ( tank.GetTag("aqua_reward")), + live: ( tank.GetTag("aqua_live")), + dead: ( tank.GetTag("aqua_dead")), + decorTag: ( tank.GetTag("aqua_decor")), + + fishState: ( tank.GetTag("aqua_food_state")), + fishAdded: ( tank.GetTag("aqua_food_added")), + fishMaint: ( tank.GetTag("aqua_food_maint")), + fishImprv: ( tank.GetTag("aqua_food_impr")), + + waterState:( tank.GetTag("aqua_water_state")), + waterAdded:( tank.GetTag("aqua_water_added")), + waterMaint:( tank.GetTag("aqua_water_maint")), + waterImprv:( tank.GetTag("aqua_water_impr")) + }; + + // Defensive defaults + if( data.fishImprv <= 0 && data.fishState !== 4 && data.fishState !== 0 ) + data.fishImprv = data.fishMaint + 2; + if( data.waterImprv <= 0 && data.waterState !== 4 && data.waterState !== 0 ) + data.waterImprv = data.waterMaint + 2; + + return data; +} + +function foodStateName( state ) +{ + switch( state ) + { + case 0: return "Dead"; + case 1: return "Starving"; + case 2: return "Hungry"; + case 3: return "Full"; + case 4: return "Overfed"; + default: return "Unknown"; + } +} + +function waterStateName( state ) +{ + switch( state ) + { + case 0: return "Dead"; + case 1: return "Dying"; + case 2: return "Unhealthy"; + case 3: return "Healthy"; + case 4: return "Strong"; + default: return "Unknown"; + } +} + +/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */ +function onTooltip( myObj, pSocket ) +{ + var data = Aqua_GetData( myObj ); + + var maxLive = maxLiveCreatures( myObj ); + var totalKnown = totalItems( myObj ); + var computedDec = totalKnown - ( data.live | 0 ) - ( data.dead | 0 ); + if( computedDec < 0 ) + computedDec = 0; + + // Count events safely + var eventsCount = 0; + if( data.events && data.events !== 0 ) + { + var parts = ( 0 + data.events ).split( "," ); + for( var i = 0; i < parts.length; i++ ) + { + if(( parts[i] | 0 ) !== 0 ) + eventsCount++; + } + } + + // Build lines + var lines = []; + + if( data.vacDays > 0 ) + lines.push( "Vacation days left: " + ( data.vacDays | 0 )); + + if( eventsCount > 0 ) + lines.push( eventsCount + " event( s ) to view!" ); + + if( ( data.reward | 0 ) === 1 ) + lines.push( "A reward is available!" ); + + lines.push( "Live Creatures: " + ( data.live | 0 ) + " / " + maxLive ); + if( ( data.dead | 0 ) > 0 ) + lines.push( "Dead Creatures: " + ( data.dead | 0 )); + + var showDecor = ( computedDec > 0 ) ? computedDec : ( data.decorTag | 0 ); + if( showDecor > 0 ) + lines.push( "Decorations: " + showDecor ); + + lines.push( "Food state: " + foodStateName( data.fishState )); + lines.push( "Water state: " + waterStateName( data.waterState )); + + if( data.fishState === 0 ) + lines.push( "Food Added: " + ( data.fishAdded | 0 ) + " Needed: " + ( data.fishImprv | 0 )); + else if( data.fishState === 4 ) + lines.push( "Food Added: " + ( data.fishAdded | 0 ) + " Needed: " + ( data.fishMaint | 0 )); + else + lines.push( "Food Added: " + ( data.fishAdded | 0 ) + " Feed: " + ( data.fishMaint | 0 ) + " Improve: " + ( data.fishImprv | 0 )); + + if( data.waterState === 0 ) + lines.push( "Water Added: " + ( data.waterAdded | 0 ) + " Needed: " + ( data.waterImprv | 0 )); + else if( data.waterState === 4 ) + lines.push( "Water Added: " + ( data.waterAdded | 0 ) + " Needed: " + ( data.waterMaint | 0 )); + else + lines.push( "Water Added: " + ( data.waterAdded | 0 ) + " Maintain: " + ( data.waterMaint | 0 ) + " Improve: " + ( data.waterImprv | 0 )); + + // Optional: put tooltip after name but before maker mark ( see docs update ) + myObj.SetTempTag( "tooltipSortOrder", 1 ); + + // Use a cliloc container if you like + myObj.SetTempTag( "clilocTooltip", 1042971 ); + var tooltipText = lines.join( "\n" ); + return tooltipText; +} \ No newline at end of file diff --git a/data/js/item/aquariumdecoration.js b/data/js/item/aquariumdecoration.js new file mode 100644 index 000000000..acf164e3c --- /dev/null +++ b/data/js/item/aquariumdecoration.js @@ -0,0 +1,14 @@ +/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */ +function onTooltip( myObj, pSocket ) +{ + if( myObj.GetTag( "isAquariumDecor" ) === 1 ) + { + var tooltipText = "An aquarium decoration"; + return tooltipText; + } + else if( myObj.GetTag( "isAquariumVac" ) === 1 ) + { + var tooltipText = "Vacation days 7"; + return tooltipText; + } +} \ No newline at end of file diff --git a/data/js/item/aquariumfish.js b/data/js/item/aquariumfish.js new file mode 100644 index 000000000..b6de2e032 --- /dev/null +++ b/data/js/item/aquariumfish.js @@ -0,0 +1,72 @@ +var AIR_MS = 300000; // 5 minutes, like RunUO DeathDelay + +function isDead( item ) +{ + return (( item.GetTag( "dead" ) === 1 ) || ( item.id === 0x3B0F )); +} + +function killFish( item ) +{ + if( !ValidateObject( item )) + return; + + if( isDead( item )) + return; + + item.id = 0x3B0C; + item.color = item.color | 0; + item.SetTag( "dead", 1 ); + + item.SetTag( "fishAirActive", null ); +} + +/** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */ +function onCreateDFN( objMade, objType ) +{ + // Only items + if( objType == 0 ) + { + objMade.SetTag( "fishAirActive", 1 ); + objMade.StartTimer( AIR_MS, 1, true ); + } +} + +/** @type { ( tObject: BaseObject, timerId: number ) => void } */ +function onTimer( iOwner, timerID ) +{ + if( timerID !== 1 ) + return; + + // If timer isn't marked active anymore, do nothing + if(( iOwner.GetTag( "fishAirActive" ) == null )) + return; + + // If still gasping in unsafe place, kill + killFish( iOwner ); +} + +/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */ +function onTooltip( myObj, pSocket ) +{ + // Emulate RunUO’s description selection + var live = !isDead( myObj ); + var unusualByID = ( myObj.id > 0x3B0F ); // matches RunUO logic + var hasHue = (( myObj.color | 0 ) !== 0 ); + + var desc; + if( unusualByID ) + desc = live ? "A very unusual aquarium creature" : "A very unusual dead aquarium creature"; + else if( hasHue ) + desc = live ? "An aquarium creature of unusual color" : "A dead aquarium creature of unusual color"; + else + desc = live ? "An aquarium creature" : "A dead aquarium creature"; + + var tooltipText = [desc]; + + // Show “Gasping for air” if the timer is currently active and fish is alive + if( live && ( ( myObj.GetTag( "fishAirActive" ) | 0 ) === 1 )) + tooltipText.push( "Gasping for air" ); + + myObj.SetTempTag( "clilocTooltip", 1042971 ); // ~1_NOTHING~ + return tooltipText.join( "\n" ); +} diff --git a/data/js/item/fishbowl.js b/data/js/item/fishbowl.js new file mode 100644 index 000000000..8fdc01c1b --- /dev/null +++ b/data/js/item/fishbowl.js @@ -0,0 +1,207 @@ +function bowlHasFish( bowl ) +{ + return ( bowl.GetTag( "vf_present" ) === 1); +} + +function clearStoredFish( bowl ) +{ + bowl.SetTag( "vf_present", 0 ); + bowl.SetTag( "vf_id", 0 ); + bowl.SetTag( "vf_hue", 0 ); + bowl.SetTag( "vf_name", "" ); + bowl.SetTag( "vf_dead", 0 ); + bowl.SetTag( "vf_extra", "" ); +} + +function storeFishFromItem( bowl, fish ) +{ + // Copy core look + bowl.SetTag( "vf_id", fish.id ); + bowl.SetTag( "vf_hue", fish.color ); + bowl.SetTag( "vf_name", fish.name ); + + // Small whitelist of extra tags you might care about + var dead = fish.GetTag( "dead" ); + bowl.SetTag( "vf_dead", dead ); + + var extras = []; + extras.push( "isAquariumFish=1" ); // always re-apply + var species = fish.GetTag( "species" ); + if( typeof species !== "undefined" && species !== null && species !== "" ) + extras.push( "species=" + String( species )); + + bowl.SetTag( "vf_extra", extras.join( "," )); + + bowl.SetTag( "vf_present", 1 ); +} + +function recreateFishToPack( pUser, bowl ) +{ + + var id = bowl.GetTag( "vf_id" ); + var hue = bowl.GetTag( "vf_hue" ); + var name = bowl.GetTag( "vf_name" ); + name = ( typeof name === "string" ? name : "" ); + + if( id <= 0 ) + return false; + + // Create a plain item with the same art + var fishItem = CreateBlankItem( pUser.socket, pUser, 1, name, id, hue, "ITEM", true ); + if( !ValidateObject( fishItem )) + return false; + + if( hue > 0 ) + fishItem.color = hue; + if( name !== "" ) + fishItem.name = name; + + fishItem.SetTag( "fishAirActive", 1 ); + fishItem.AddScriptTrigger( 5101 ); + fishItem.StartTimer( 300000, 1, 5101 ); + + fishItem.SetTag( "isAquariumFish", 1 ); + var dead = bowl.GetTag( "vf_dead" ) | 0; + if( dead === 1 ) + fishItem.SetTag( "dead", 1 ); + + var extra = bowl.GetTag( "vf_extra" ); + if( typeof extra === "string" && extra !== "" ) + { + var parts = extra.split( "," ); + for ( var i = 0; i < parts.length; i++ ) + { + var kv = parts[i].split( "=" ); + if( kv.length === 2 ) + { + var k = kv[0], v = kv[1]; + if( k === "isAquariumFish" ) + fishItem.SetTag( k, v | 0 ); + else if( k === "species" ) + fishItem.SetTag( k, String( v )); + } + } + } + + fishItem.PlaceInPack( pUser ); + bowl.Refresh(); + + return true; +} + +/** @type { ( user: Character, iUsing: Item ) => boolean } */ +function onCreateDFN( objMade, objType ) +{ + if( objType === 0 ) + { + objMade.id = 0x241C; + objMade.color = 0x047E; + + objMade.SetTag( "isFishBowl", 1 ); + + if( !bowlHasFish( objMade )) + clearStoredFish( objMade ); + } +} + +/** @type { ( item: Item, dropper: Character, dest: Item ) => number } */ +function onDropItemOnItem( iDropped, cDropper, iDroppedOn ) +{ + if( !ValidateObject( iDroppedOn )) + return 1; + + if( iDropped.id === 0x241C ) + return 1; + + if( iDroppedOn.id !== 0x241C ) + return 1; + + if( bowlHasFish( iDroppedOn )) + { + cDropper.SysMessage( "The fish bowl already contains a creature." ); + return 1; + } + + var isFish = ( iDropped.GetTag( "isAquariumFish" )); + if( isFish !== 1 ) + { + cDropper.SysMessage( "The container can not hold that type of object." ); + return 1; + } + + storeFishFromItem( iDroppedOn, iDropped ); + iDropped.Delete(); + + cDropper.SysMessage( "You place the creature into the fish bowl." ); + if( typeof iDroppedOn.Refresh === "function" ) + iDroppedOn.Refresh( ); + + return 1; +} + +function onContextMenuRequest( socket, targObj ) +{ + if( !ValidateObject( targObj )) + return true; + + var list = []; + if( bowlHasFish( targObj )) + { + list.push( { id: 6242, text: 6242, flags: 0x0020, hue: 0x03E0 } ); + } + var useKR = false; + TriggerEvent( 18001, "modifyContextMenu", socket, targObj, list, useKR ); + return false; +} + +function onContextMenuSelect( socket, targObj, entryID ) +{ + if( !ValidateObject( targObj )) + return false; + + var pUser = socket.currentChar; + if( !ValidateObject( pUser )) + return false; + + switch( entryID | 0 ) + { + case 6242: // Remove creature + { + if( !bowlHasFish( targObj )) + return false; + + if( !recreateFishToPack( pUser, targObj )) + { + socket.SysMessage( "Could not recreate the creature." ); + return false; + } + + clearStoredFish( targObj ); + socket.SysMessage( "The creature has been removed from the fish bowl." ); + targObj.Refresh(); + break; + } + } + return false; +} + +/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */ +function onTooltip( myObj, pSocket ) +{ + var tooltipText = ["A fish bowl"]; + + if( bowlHasFish( myObj )) + { + var name = myObj.GetTag( "vf_name" ); + if( typeof name !== "string" || name === "" ) + { + // fallback generic name + tooltipText .push( "Contains: an aquarium creature" ); + } + else + { + tooltipText .push( "Contains: " + name ); + } + } + return tooltipText.join( "\n" ); +} diff --git a/data/js/jse_fileassociations.scp b/data/js/jse_fileassociations.scp index e1a326fa7..2a2241af5 100644 --- a/data/js/jse_fileassociations.scp +++ b/data/js/jse_fileassociations.scp @@ -317,6 +317,14 @@ 5061=item/puzzlechest.js 5062=item/shrines.js +//------------------------------------------- +// Aquarium Scripts [5100-5105] +//------------------------------------------- +5100=item/aquarium.js +5101=item/aquariumfish.js +5102=item/aquariumdecoration.js +5103=item/fishbowl.js + 19100=item/plant_growing/plantsystem.js 19101=item/plant_growing/plantbowl.js 19102=item/plant_growing/seed.js From 5e01c9ab490922d2545841c84ba9097de22d1189 Mon Sep 17 00:00:00 2001 From: Dragon Slayer <85514184+DragonSlayer62@users.noreply.github.com> Date: Tue, 11 Nov 2025 00:27:16 -0600 Subject: [PATCH 2/2] update --- data/js/item/aquarium.js | 7 ++++--- data/js/item/aquariumdecoration.js | 2 +- data/js/item/aquariumfish.js | 14 +++----------- data/js/item/ballofpetsummoning.js | 2 +- data/js/item/fishbowl.js | 4 +++- data/js/item/key.js | 2 +- data/js/item/recipescroll.js | 2 +- data/js/item/runebook.js | 2 +- data/js/skill/craft/tailoring.js | 3 ++- 9 files changed, 17 insertions(+), 21 deletions(-) diff --git a/data/js/item/aquarium.js b/data/js/item/aquarium.js index d9fa762f0..8bec7374e 100644 --- a/data/js/item/aquarium.js +++ b/data/js/item/aquarium.js @@ -458,6 +458,7 @@ function showEvents( pUser, aquarium ) } +/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */ function onGumpPress( pSocket, buttonID ) { var pUser = pSocket.currentChar; @@ -477,7 +478,7 @@ function onGumpPress( pSocket, buttonID ) if( cur > count ) cur = count; - // Remove current page’s item + // Remove current pages item if( buttonID >= ( 1000 +1 ) && buttonID <= ( 1000 + count )) { var idx = ( buttonID - 1000 ) - 1; @@ -677,7 +678,7 @@ function onDropItemOnItem( iDropped, cDropper, iDroppedOn ) iDropped.SetTag( "ContentsType", 1 ); iDropped.SetTag( "ContentsName", "nothing" ); - // caller context isn’t guaranteed to have a socket; just pass null/undefined + // caller context isnt guaranteed to have a socket; just pass null/undefined TriggerEvent( 2100, "switchPitcherID", null, iDropped ); } cDropper.SoundEffect( 37, true ); @@ -888,4 +889,4 @@ function onTooltip( myObj, pSocket ) myObj.SetTempTag( "clilocTooltip", 1042971 ); var tooltipText = lines.join( "\n" ); return tooltipText; -} \ No newline at end of file +} diff --git a/data/js/item/aquariumdecoration.js b/data/js/item/aquariumdecoration.js index acf164e3c..5f08a48be 100644 --- a/data/js/item/aquariumdecoration.js +++ b/data/js/item/aquariumdecoration.js @@ -11,4 +11,4 @@ function onTooltip( myObj, pSocket ) var tooltipText = "Vacation days 7"; return tooltipText; } -} \ No newline at end of file +} diff --git a/data/js/item/aquariumfish.js b/data/js/item/aquariumfish.js index b6de2e032..f91dff04c 100644 --- a/data/js/item/aquariumfish.js +++ b/data/js/item/aquariumfish.js @@ -1,4 +1,4 @@ -var AIR_MS = 300000; // 5 minutes, like RunUO DeathDelay +var AIR_MS = 300000; // 5 minutes function isDead( item ) { @@ -23,7 +23,6 @@ function killFish( item ) /** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */ function onCreateDFN( objMade, objType ) { - // Only items if( objType == 0 ) { objMade.SetTag( "fishAirActive", 1 ); @@ -34,23 +33,17 @@ function onCreateDFN( objMade, objType ) /** @type { ( tObject: BaseObject, timerId: number ) => void } */ function onTimer( iOwner, timerID ) { - if( timerID !== 1 ) - return; - - // If timer isn't marked active anymore, do nothing if(( iOwner.GetTag( "fishAirActive" ) == null )) return; - // If still gasping in unsafe place, kill killFish( iOwner ); } /** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */ function onTooltip( myObj, pSocket ) { - // Emulate RunUO’s description selection var live = !isDead( myObj ); - var unusualByID = ( myObj.id > 0x3B0F ); // matches RunUO logic + var unusualByID = ( myObj.id > 0x3B0F ); var hasHue = (( myObj.color | 0 ) !== 0 ); var desc; @@ -63,8 +56,7 @@ function onTooltip( myObj, pSocket ) var tooltipText = [desc]; - // Show “Gasping for air” if the timer is currently active and fish is alive - if( live && ( ( myObj.GetTag( "fishAirActive" ) | 0 ) === 1 )) + if( live && ( myObj.GetTag( "fishAirActive" ) === 1 )) tooltipText.push( "Gasping for air" ); myObj.SetTempTag( "clilocTooltip", 1042971 ); // ~1_NOTHING~ diff --git a/data/js/item/ballofpetsummoning.js b/data/js/item/ballofpetsummoning.js index fe3a93d8a..2e12f9b2f 100644 --- a/data/js/item/ballofpetsummoning.js +++ b/data/js/item/ballofpetsummoning.js @@ -313,7 +313,7 @@ function onContextMenuSelect( socket, targObj, popupEntry ) return false; } -/** @type { ( myChar: Character, myItem: Item, mySpeech: string ) => void } */ +/** @type { ( myChar: Character, myItem: Item, mySpeech: string, mySpeechId: number ) => void } */ function onSpeechInput( pUser, targObj, pSpeech, pSpeechID ) { var pSocket = pUser.socket; diff --git a/data/js/item/fishbowl.js b/data/js/item/fishbowl.js index 8fdc01c1b..255aeb100 100644 --- a/data/js/item/fishbowl.js +++ b/data/js/item/fishbowl.js @@ -89,7 +89,7 @@ function recreateFishToPack( pUser, bowl ) return true; } -/** @type { ( user: Character, iUsing: Item ) => boolean } */ +/** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */ function onCreateDFN( objMade, objType ) { if( objType === 0 ) @@ -139,6 +139,7 @@ function onDropItemOnItem( iDropped, cDropper, iDroppedOn ) return 1; } +/** @type { ( tSock: Socket, baseObj: BaseObject ) => boolean } */ function onContextMenuRequest( socket, targObj ) { if( !ValidateObject( targObj )) @@ -154,6 +155,7 @@ function onContextMenuRequest( socket, targObj ) return false; } +/** @type { ( tSock: Socket, baseObj: BaseObject, popupEntry: number ) => boolean } */ function onContextMenuSelect( socket, targObj, entryID ) { if( !ValidateObject( targObj )) diff --git a/data/js/item/key.js b/data/js/item/key.js index a38b2f023..b07218614 100644 --- a/data/js/item/key.js +++ b/data/js/item/key.js @@ -374,7 +374,7 @@ function onCallback3( pSock, myTarget ) pSock.SysMessage( GetDictionaryEntry( 2743, pSock.language )); // You can't make a copy of that. } -/** @type { ( myChar: Character, myItem: Item, mySpeech: string ) => void } */ +/** @type { ( myChar: Character, myItem: Item, mySpeech: string, mySpeechId: number ) => void } */ function onSpeechInput( pUser, pItem, pSpeech, pSpeechID ) { var pSock = pUser.socket; diff --git a/data/js/item/recipescroll.js b/data/js/item/recipescroll.js index ccdc099ef..eb226836e 100644 --- a/data/js/item/recipescroll.js +++ b/data/js/item/recipescroll.js @@ -16,4 +16,4 @@ function onTooltip( recipe, pSocket ) var recipeName = recipe.GetTag( "recipeName" ) var tooltipText = "[" + recipeName.toString() + "]"; // name of the recipe return tooltipText; -} \ No newline at end of file +} diff --git a/data/js/item/runebook.js b/data/js/item/runebook.js index 6298d45dd..b0f56039d 100644 --- a/data/js/item/runebook.js +++ b/data/js/item/runebook.js @@ -898,7 +898,7 @@ function CheckAccessRights( pSocket, pUser, runeBook ) } // Handle renaming of Runebook -/** @type { ( myChar: Character, myItem: Item, mySpeech: string ) => void } */ +/** @type { ( myChar: Character, myItem: Item, mySpeech: string, mySpeechId: number ) => void } */ function onSpeechInput( pUser, runeBook, pSpeech, pSpeechID ) { var pSocket = pUser.socket; diff --git a/data/js/skill/craft/tailoring.js b/data/js/skill/craft/tailoring.js index d890b08fe..66cf7874f 100644 --- a/data/js/skill/craft/tailoring.js +++ b/data/js/skill/craft/tailoring.js @@ -293,6 +293,7 @@ function onTimer( pUser, timerID ) } } +/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */ function onGumpPress( socket, pButton, gumpData ) { var pUser = socket.currentChar; @@ -749,4 +750,4 @@ function eraOK( entry ) if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra )) return false; return true; -} \ No newline at end of file +}