diff --git a/data/dfndata/create/glassblowing.dfn b/data/dfndata/create/glassblowing.dfn
index 18c05abd7..b91971368 100644
--- a/data/dfndata/create/glassblowing.dfn
+++ b/data/dfndata/create/glassblowing.dfn
@@ -2,7 +2,7 @@
{
NAME=Empty Bottle
ID=0x0f0e
-RESOURCE=SAND 1
+RESOURCE=SAND 1 0x96d
SKILL=0 1000 1150
ADDITEM=emptybottle
MINRANK=1
@@ -14,7 +14,7 @@ SOUND=0x0242
{
NAME=flask (small)
ID=0x182e
-RESOURCE=SAND 2
+RESOURCE=SAND 2 0x96d
SKILL=0 1000 1150
ADDITEM=0x182e
MINRANK=1
@@ -26,7 +26,7 @@ SOUND=0x0242
{
NAME=flask (medium)
ID=0x182a
-RESOURCE=SAND 3
+RESOURCE=SAND 3 0x96d
SKILL=0 1000 1150
ADDITEM=0x182a
MINRANK=1
@@ -38,7 +38,7 @@ SOUND=0x0242
{
NAME=flask (curved)
ID=0x1832
-RESOURCE=SAND 3
+RESOURCE=SAND 3 0x96d
SKILL=0 1000 1150
ADDITEM=0x1832
MINRANK=1
@@ -50,7 +50,7 @@ SOUND=0x0242
{
NAME=flask (large #1)
ID=0x1838
-RESOURCE=SAND 4
+RESOURCE=SAND 4 0x96d
SKILL=0 1000 1150
ADDITEM=0x1838
MINRANK=1
@@ -62,7 +62,7 @@ SOUND=0x0242
{
NAME=flask (large #2)
ID=0x183b
-RESOURCE=SAND 5
+RESOURCE=SAND 5 0x96d
SKILL=0 1000 1150
ADDITEM=0x183b
MINRANK=1
@@ -74,7 +74,7 @@ SOUND=0x0242
{
NAME=flask (bubbling blue)
ID=0x1844
-RESOURCE=SAND 5
+RESOURCE=SAND 5 0x96d
SKILL=0 1000 1150
ADDITEM=bubblingblueflask
MINRANK=1
@@ -86,7 +86,7 @@ SOUND=0x0242
{
NAME=flask (bubbling purple)
ID=0x1841
-RESOURCE=SAND 5
+RESOURCE=SAND 5 0x96d
SKILL=0 1000 1150
ADDITEM=bubblingpurpleflask
MINRANK=1
@@ -98,7 +98,7 @@ SOUND=0x0242
{
NAME=flask (bubbling red)
ID=0x183e
-RESOURCE=SAND 7
+RESOURCE=SAND 7 0x96d
SKILL=0 1000 1150
ADDITEM=bubblingredflask
MINRANK=1
@@ -110,7 +110,7 @@ SOUND=0x0242
{
NAME=empty vials
ID=0x185b
-RESOURCE=SAND 8
+RESOURCE=SAND 8 0x96d
SKILL=0 1000 1150
ADDITEM=emptyvials
MINRANK=1
@@ -122,7 +122,7 @@ SOUND=0x0242
{
NAME=full vials
ID=0x185d
-RESOURCE=SAND 8
+RESOURCE=SAND 8 0x96d
SKILL=0 1000 1150
ADDITEM=fullvials
MINRANK=1
@@ -134,10 +134,106 @@ SOUND=0x0242
{
NAME=spinning hourglass
ID=0x1810
-RESOURCE=SAND 10
+RESOURCE=SAND 10 0x96d
SKILL=0 1000 1150
ADDITEM=0x1810
MINRANK=1
MAXRANK=10
SOUND=0x0242
+}
+
+[ITEM 3012]
+{
+NAME=hollow prism
+ID=0x2f5d
+RESOURCE=SAND 8 0x96d
+SKILL=0 1000 1500
+ADDITEM=hollowprism
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3013]
+{
+NAME=gargoyle floor mirror
+ID=0x403A
+RESOURCE=SAND 20 0x96d
+SKILL=0 750 1250
+ADDITEM=gargoylefloormirror
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3014]
+{
+NAME=gargoyle wall mirror
+ID=0x4044
+RESOURCE=SAND 10 0x96d
+SKILL=0 700 1200
+ADDITEM=gargoylewallmirror
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3015]
+{
+NAME=empty venom vial
+ID=0x0E24
+RESOURCE=SAND 1 0x96d
+SKILL=0 520 1000
+ADDITEM=emptyvenomvial
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3016]
+{
+NAME=empty oil flask
+ID=0x1C18
+RESOURCE=SAND 5 0x96d
+SKILL=0 600 1100
+ADDITEM=emptyoilflask
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3017]
+{
+NAME=workable glass
+ID=0x4B80
+RESOURCE=SAND 10 0x96d
+SKILL=0 550 1050
+ADDITEM=workableglass
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3018]
+{
+NAME=glass sword
+ID=0x090c
+RESOURCE=SAND 14 0x96d
+SKILL=0 550 1050
+ADDITEM=glasssword
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
+}
+
+[ITEM 3019]
+{
+NAME=glass staff
+ID=0x0905
+RESOURCE=SAND 10 0x96d
+SKILL=0 530 1030
+ADDITEM=glassstaff
+MINRANK=1
+MAXRANK=10
+SOUND=0x0242
}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry.dfn b/data/dfndata/create/masonry.dfn
new file mode 100644
index 000000000..99973ac63
--- /dev/null
+++ b/data/dfndata/create/masonry.dfn
@@ -0,0 +1,747 @@
+[ITEM 3500]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3501]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3502]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3503]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3504]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3505]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3506]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3507]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3508]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 3509]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4
+SKILL=11 550 105
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3510]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3511]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3512]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3513]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3514]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 3515]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3516]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3517]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3518]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3519]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3520]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 3521]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3522]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3523]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3524]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3525]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3526]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 3527]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3528]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3529]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3530]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3531]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3532]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3533]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3534]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3535]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3536]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 3537]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 3538]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3539]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3540]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3541]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3542]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3543]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3544]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3545]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3546]
+{
+NAME=Stone Door S In
+ID=0x0326
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=StoneDoor_S_In
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3547]
+{
+NAME=Stone Door E Out
+ID=0x032C
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=StoneDoor_E_Out
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3548]
+{
+NAME=Stone Door S Out
+ID=0x032a
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=StoneDoor_S_Out
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3549]
+{
+NAME=Stone Door E In
+ID=0x0330
+RESOURCE=STONE 10
+SKILL=11 600 1100
+ADDITEM=StoneDoor_E_In
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 3550]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3551]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3552]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3553]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3554]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3555]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 3556]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3557]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3558]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_agapite.dfn b/data/dfndata/create/masonry_agapite.dfn
new file mode 100644
index 000000000..fd07b68b6
--- /dev/null
+++ b/data/dfndata/create/masonry_agapite.dfn
@@ -0,0 +1,704 @@
+[ITEM 4100]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x979
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4101]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x979
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4102]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x979
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4103]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x979
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4104]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x979
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4105]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x979
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4106]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x979
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4107]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x979
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4108]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x979
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 4109]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x979
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4110]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x979
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4111]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x979
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4112]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x979
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4113]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x979
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4114]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x979
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 4115]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x979
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4116]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x979
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4117]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x979
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4118]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x979
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4119]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x979
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4120]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x979
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 4121]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x979
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4122]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x979
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4123]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x979
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4124]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x979
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4125]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x979
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4126]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x979
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 4127]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x979
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4128]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x979
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4129]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x979
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4130]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x979
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4131]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x979
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4132]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x979
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4133]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x979
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4134]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x979
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4135]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x979
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4136]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x979
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 4137]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x979
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 4138]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4139]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4140]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4141]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4142]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4143]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4144]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4145]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x979
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 4146]
+//[ITEM 4147]
+//[ITEM 4148]
+//[ITEM 4149]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 4150]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4151]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4152]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4153]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4154]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4155]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 4156]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4157]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4158]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x979
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_bronze.dfn b/data/dfndata/create/masonry_bronze.dfn
new file mode 100644
index 000000000..5fb705bd3
--- /dev/null
+++ b/data/dfndata/create/masonry_bronze.dfn
@@ -0,0 +1,704 @@
+[ITEM 3900]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x6d6
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3901]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x6d6
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3902]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x6d6
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3903]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x6d6
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3904]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x6d6
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3905]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x6d6
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3906]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x6d6
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3907]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3908]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 3909]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x6d6
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3910]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x6d6
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3911]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x6d6
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3912]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x6d6
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3913]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x6d6
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3914]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x6d6
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 3915]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3916]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3917]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3918]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x6d6
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3919]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x6d6
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3920]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x6d6
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 3921]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x6d6
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3922]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x6d6
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3923]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x6d6
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3924]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x6d6
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3925]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x6d6
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3926]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x6d6
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 3927]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x6d6
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3928]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x6d6
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3929]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x6d6
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3930]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x6d6
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3931]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x6d6
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3932]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x6d6
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3933]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x6d6
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3934]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x6d6
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3935]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x6d6
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3936]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x6d6
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 3937]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x6d6
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 3938]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3939]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3940]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3941]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3942]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3943]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3944]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3945]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x6d6
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 3946]
+//[ITEM 3947]
+//[ITEM 3948]
+//[ITEM 3949]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 3950]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3951]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3952]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3953]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3954]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3955]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 3956]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3957]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3958]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x6d6
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_copper.dfn b/data/dfndata/create/masonry_copper.dfn
new file mode 100644
index 000000000..017e58ce9
--- /dev/null
+++ b/data/dfndata/create/masonry_copper.dfn
@@ -0,0 +1,704 @@
+[ITEM 3800]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x7dd
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3801]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x7dd
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3802]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x7dd
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3803]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x7dd
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3804]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x7dd
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3805]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x7dd
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3806]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x7dd
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3807]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3808]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 3809]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x7dd
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3810]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x7dd
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3811]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x7dd
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3812]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x7dd
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3813]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x7dd
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3814]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x7dd
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 3815]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3816]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3817]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3818]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x7dd
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3819]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x7dd
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3820]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x7dd
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 3821]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x7dd
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3822]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x7dd
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3823]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x7dd
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3824]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x7dd
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3825]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x7dd
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3826]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x7dd
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 3827]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x7dd
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3828]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x7dd
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3829]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x7dd
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3830]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x7dd
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3831]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x7dd
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3832]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x7dd
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3833]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x7dd
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3834]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x7dd
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3835]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x7dd
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3836]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x7dd
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 3837]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x7dd
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 3838]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3839]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3840]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3841]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3842]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3843]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3844]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3845]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x7dd
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 3846]
+//[ITEM 3847]
+//[ITEM 3848]
+//[ITEM 3849]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 3850]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3851]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3852]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3853]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3854]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3855]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 3856]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3857]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3858]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x7dd
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_dullcopper.dfn b/data/dfndata/create/masonry_dullcopper.dfn
new file mode 100644
index 000000000..223b6d47b
--- /dev/null
+++ b/data/dfndata/create/masonry_dullcopper.dfn
@@ -0,0 +1,704 @@
+[ITEM 3600]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x973
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3601]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x973
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3602]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x973
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3603]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x973
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3604]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x973
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3605]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x973
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3606]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x973
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3607]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x973
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3608]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x973
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 3609]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x973
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3610]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x973
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3611]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x973
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3612]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x973
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3613]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x973
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3614]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x973
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 3615]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x973
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3616]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x973
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3617]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x973
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3618]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x973
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3619]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x973
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3620]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x973
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 3621]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x973
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3622]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x973
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3623]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x973
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3624]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x973
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3625]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x973
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3626]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x973
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 3627]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x973
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3628]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x973
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3629]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x973
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3630]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x973
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3631]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x973
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3632]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x973
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3633]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x973
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3634]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x973
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3635]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x973
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3636]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x973
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 3637]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x973
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 3638]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3639]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3640]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3641]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3642]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3643]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3644]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3645]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x973
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 3646]
+//[ITEM 3647]
+//[ITEM 3648]
+//[ITEM 3649]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 3650]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3651]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3652]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3653]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3654]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3655]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 3656]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3657]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3658]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x973
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_gold.dfn b/data/dfndata/create/masonry_gold.dfn
new file mode 100644
index 000000000..22daec47a
--- /dev/null
+++ b/data/dfndata/create/masonry_gold.dfn
@@ -0,0 +1,704 @@
+[ITEM 4000]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x8a5
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4001]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x8a5
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4002]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x8a5
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4003]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x8a5
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4004]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x8a5
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4005]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x8a5
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4006]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x8a5
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4007]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4008]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 4009]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x8a5
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4010]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x8a5
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4011]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x8a5
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4012]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x8a5
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4013]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x8a5
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4014]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x8a5
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 4015]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4016]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4017]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4018]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x8a5
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4019]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x8a5
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4020]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x8a5
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 4021]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x8a5
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4022]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x8a5
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4023]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x8a5
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4024]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x8a5
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4025]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x8a5
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4026]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x8a5
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 4027]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x8a5
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4028]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x8a5
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4029]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x8a5
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4030]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x8a5
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4031]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x8a5
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4032]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x8a5
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4033]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x8a5
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4034]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x8a5
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4035]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x8a5
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4036]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x8a5
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 4037]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x8a5
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 4038]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4039]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4040]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4041]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4042]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4043]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4044]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4045]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x8a5
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 4046]
+//[ITEM 4047]
+//[ITEM 4048]
+//[ITEM 4049]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 4050]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4051]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4052]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4053]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4054]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4055]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 4056]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4057]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4058]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x8a5
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_shadowiron.dfn b/data/dfndata/create/masonry_shadowiron.dfn
new file mode 100644
index 000000000..40ce6105c
--- /dev/null
+++ b/data/dfndata/create/masonry_shadowiron.dfn
@@ -0,0 +1,704 @@
+[ITEM 3700]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x966
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3701]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x966
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3702]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x966
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3703]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x966
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3704]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x966
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3705]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x966
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3706]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x966
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3707]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x966
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3708]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x966
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 3709]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x966
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3710]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x966
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3711]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x966
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3712]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x966
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3713]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x966
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3714]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x966
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 3715]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x966
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3716]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x966
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3717]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x966
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3718]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x966
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3719]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x966
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3720]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x966
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 3721]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x966
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3722]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x966
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3723]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x966
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3724]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x966
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3725]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x966
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3726]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x966
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 3727]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x966
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3728]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x966
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3729]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x966
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3730]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x966
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3731]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x966
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3732]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x966
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3733]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x966
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3734]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x966
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3735]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x966
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3736]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x966
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 3737]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x966
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 3738]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3739]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3740]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3741]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3742]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3743]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3744]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3745]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x966
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 3746]
+//[ITEM 3747]
+//[ITEM 3748]
+//[ITEM 3749]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 3750]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3751]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3752]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3753]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3754]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3755]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 3756]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3757]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 3758]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x966
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_valorite.dfn b/data/dfndata/create/masonry_valorite.dfn
new file mode 100644
index 000000000..911b991b8
--- /dev/null
+++ b/data/dfndata/create/masonry_valorite.dfn
@@ -0,0 +1,704 @@
+[ITEM 4300]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x8ab
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4301]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x8ab
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4302]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x8ab
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4303]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x8ab
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4304]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x8ab
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4305]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x8ab
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4306]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x8ab
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4307]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4308]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 4309]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x8ab
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4310]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x8ab
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4311]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x8ab
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4312]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x8ab
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4313]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x8ab
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4314]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x8ab
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 4315]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4316]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4317]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4318]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x8ab
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4319]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x8ab
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4320]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x8ab
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 4321]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x8ab
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4322]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x8ab
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4323]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x8ab
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4324]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x8ab
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4325]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x8ab
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4326]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x8ab
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 4327]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x8ab
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4328]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x8ab
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4329]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x8ab
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4330]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x8ab
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4331]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x8ab
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4332]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x8ab
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4333]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x8ab
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4334]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x8ab
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4335]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x8ab
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4336]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x8ab
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 4337]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x8ab
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 4338]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4339]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4340]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4341]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4342]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4343]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4344]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4345]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x8ab
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 4346]
+//[ITEM 4347]
+//[ITEM 4348]
+//[ITEM 4349]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 4350]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4351]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4352]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4353]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4354]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4355]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 4356]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4357]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4358]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x8ab
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/masonry_verite.dfn b/data/dfndata/create/masonry_verite.dfn
new file mode 100644
index 000000000..e3b5b51f9
--- /dev/null
+++ b/data/dfndata/create/masonry_verite.dfn
@@ -0,0 +1,704 @@
+[ITEM 4200]
+{
+NAME=Vase
+ID=0x0b46
+RESOURCE=STONE 1 0x89f
+SKILL=11 520 1020
+ADDITEM=0x0b46
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4201]
+{
+NAME=Large Vase
+ID=0x0b45
+RESOURCE=STONE 3 0x89f
+SKILL=11 520 1020
+ADDITEM=0x0b45
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4202]
+{
+NAME=small Urn
+ID=0x241c
+RESOURCE=STONE 3 0x89f
+SKILL=11 820 1030
+ADDITEM=0x241c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4203]
+{
+NAME=Tower sculpture
+ID=0x241a
+RESOURCE=STONE 3 0x89f
+SKILL=11 820 1030
+ADDITEM=0x241a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4204]
+{
+NAME=gargoyle painting
+ID=0x403d
+RESOURCE=STONE 3 0x89f
+SKILL=11 820 1330
+ADDITEM=0x403d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4205]
+{
+NAME=gargoyle sculpture
+ID=0x403f
+RESOURCE=STONE 3 0x89f
+SKILL=11 820 1320
+ADDITEM=0x403f
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4206]
+{
+NAME=gargoyle vase
+ID=0x4042
+RESOURCE=STONE 3 0x89f
+SKILL=11 800 1260
+ADDITEM=0x4042
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4207]
+{
+NAME=Tall 18th anniversary Vase
+ID=0x9bc7
+RESOURCE=STONE 6 0x89f
+SKILL=11 600 1100
+ADDITEM=0x9bc7
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4208]
+{
+NAME=short 18th anniversary Vase
+ID=0x9bca
+RESOURCE=STONE 6 0x89f
+SKILL=11 600 1100
+ADDITEM=0x9bca
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// furniture /////
+//////////////////////////////////////
+
+[ITEM 4209]
+{
+NAME=stone chair
+ID=0x1218
+RESOURCE=STONE 4 0x89f
+SKILL=11 550 1050
+ADDITEM=0x1218
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4210]
+{
+NAME=stone table (east)
+ID=0x14f0
+RESOURCE=STONE 6 0x89f
+SKILL=11 650 1150
+ADDITEM=mediumstonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4211]
+{
+NAME=stone table (south)
+ID=0x14f0
+RESOURCE=STONE 6 0x89f
+SKILL=11 650 1150
+ADDITEM=mediumstonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4212]
+{
+NAME=large stone table (east)
+ID=0x14f0
+RESOURCE=STONE 9 0x89f
+SKILL=11 750 1250
+ADDITEM=largestonetableeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4213]
+{
+NAME=large stone table (south)
+ID=0x14f0
+RESOURCE=STONE 9 0x89f
+SKILL=11 750 1250
+ADDITEM=largestonetablesouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4214]
+{
+NAME=ritual table
+ID=0x14f0
+RESOURCE=STONE 8 0x89f
+SKILL=11 940 1030
+ADDITEM=ritualtabledeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// statues /////
+//////////////////////////////////////
+
+[ITEM 4215]
+{
+NAME=small statue (south)
+ID=0x139a
+RESOURCE=STONE 3 0x89f
+SKILL=11 600 1100
+ADDITEM=0x139a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4216]
+{
+NAME=small statue (north)
+ID=0x139b
+RESOURCE=STONE 3 0x89f
+SKILL=11 600 1100
+ADDITEM=0x139b
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4217]
+{
+NAME=small statue (east)
+ID=0x139c
+RESOURCE=STONE 3 0x89f
+SKILL=11 600 1100
+ADDITEM=0x139c
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4218]
+{
+NAME=pegasus statuette
+ID=0x139d
+RESOURCE=STONE 4 0x89f
+SKILL=11 700 1200
+ADDITEM=0x139d
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4219]
+{
+NAME=gargoyle statue
+ID=0x494e
+RESOURCE=STONE 20 0x89f
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4220]
+{
+NAME=gryphon statue
+ID=0x494e
+RESOURCE=STONE 15 0x89f
+SKILL=11 540 1040
+ADDITEM=0x494e
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// Misc addons /////
+//////////////////////////////////////
+
+[ITEM 4221]
+{
+NAME=stone anvil (east)
+ID=0x14f0
+RESOURCE=STONE 20 0x89f
+SKILL=11 780 1280
+ADDITEM=stoneanvileastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4222]
+{
+NAME=stone anvil (south)
+ID=0x14f0
+RESOURCE=STONE 20 0x89f
+SKILL=11 780 1280
+ADDITEM=stoneanvilsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4223]
+{
+NAME=large gargish bed (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x89f
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedeastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4224]
+{
+NAME=large gargish bed (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x89f
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=largegargoylebedsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4225]
+{
+NAME=gargish cot (east)
+ID=0x14f0
+RESOURCE=STONE 3 0x89f
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteasteastdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4226]
+{
+NAME=gargish cot (south)
+ID=0x14f0
+RESOURCE=STONE 3 0x89f
+RESOURCE=CLOTH 100
+SKILL=11 760 1260
+SKILL=34 700 750
+ADDITEM=gargishcoteastsouthdeed
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone armor /////
+//////////////////////////////////////
+
+[ITEM 4227]
+{
+NAME=gargish stone arms
+ID=0x0283
+RESOURCE=STONE 8 0x89f
+SKILL=11 560 1060
+ADDITEM=femalegargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4228]
+{
+NAME=gargish stone chest
+ID=0x0285
+RESOURCE=STONE 12 0x89f
+SKILL=11 560 1060
+ADDITEM=femalegargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4229]
+{
+NAME=gargish stone leggings
+ID=0x0289
+RESOURCE=STONE 10 0x89f
+SKILL=11 560 1060
+ADDITEM=femalegargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4230]
+{
+NAME=gargish stone kilt
+ID=0x0287
+RESOURCE=STONE 6 0x89f
+SKILL=11 480 980
+ADDITEM=femalegargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4231]
+{
+NAME=gargish stone arms
+ID=0x0284
+RESOURCE=STONE 8 0x89f
+SKILL=11 560 1060
+ADDITEM=gargishstonearms
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4232]
+{
+NAME=gargish stone chest
+ID=0x0286
+RESOURCE=STONE 12 0x89f
+SKILL=11 560 1060
+ADDITEM=gargishstonechest
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4233]
+{
+NAME=gargish stone leggings
+ID=0x028a
+RESOURCE=STONE 10 0x89f
+SKILL=11 560 1060
+ADDITEM=gargishstoneleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4234]
+{
+NAME=gargish stone kilt
+ID=0x0288
+RESOURCE=STONE 6 0x89f
+SKILL=11 480 980
+ADDITEM=gargishstonekilt
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4235]
+{
+NAME=large stone shield
+ID=0x4205
+RESOURCE=STONE 16 0x89f
+SKILL=11 550 1060
+ADDITEM=largestoneshield
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4236]
+{
+NAME=gargish stone amulet
+ID=0x4D0a
+RESOURCE=STONE 3 0x89f
+SKILL=11 600 1100
+ADDITEM=0x4D0a
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone weapons /////
+//////////////////////////////////////
+
+[ITEM 4237]
+{
+NAME=stone war sword
+ID=0x4D0a
+RESOURCE=STONE 18 0x89f
+SKILL=11 550 1050
+ADDITEM=stonewarsword
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone walls /////
+//////////////////////////////////////
+
+[ITEM 4238]
+{
+NAME=rough windowless
+ID=0x01D0
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughwindowless1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4239]
+{
+NAME=rough window
+ID=0x01D3
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughwindow1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4240]
+{
+NAME=rough arch
+ID=0x01D5
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=rougharch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4241]
+{
+NAME=rough pillar
+ID=0x01Da
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughpillar
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4242]
+{
+NAME=rough rounded arch
+ID=0x01DB
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughroundedarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4243]
+{
+NAME=rough small arch
+ID=0x01E0
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughsmallarch1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4244]
+{
+NAME=rough angled pillar
+ID=0x01E6
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=roughangledpillar1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4245]
+{
+NAME=short rough
+ID=0x01E8
+RESOURCE=STONE 10 0x89f
+SKILL=11 600 1100
+ADDITEM=shortrough1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//[ITEM 4246]
+//[ITEM 4247]
+//[ITEM 4248]
+//[ITEM 4249]
+
+//////////////////////////////////////
+//// stone stairs /////
+//////////////////////////////////////
+
+[ITEM 4250]
+{
+NAME=short rough
+ID=0x0788
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughblock
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4251]
+{
+NAME=rough steps
+ID=0x0789
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4252]
+{
+NAME=rough corner steps
+ID=0x078d
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughcornersteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4253]
+{
+NAME=rough rounded corner step
+ID=0x0791
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughroundedcornerseps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4254]
+{
+NAME=rough inset steps
+ID=0x0795
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughinsetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4255]
+{
+NAME=rough rounded inset steps
+ID=0x0799
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=roughroundedisetsteps1
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+//////////////////////////////////////
+//// stone floors /////
+//////////////////////////////////////
+
+[ITEM 4256]
+{
+NAME=light paver
+ID=0x0519
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=lightpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4257]
+{
+NAME=medium paver
+ID=0x0519
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=mediumpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
+
+[ITEM 4258]
+{
+NAME=dark paver
+ID=0x0519
+RESOURCE=STONE 5 0x89f
+SKILL=11 600 1100
+ADDITEM=darkpaver
+MINRANK=1
+MAXRANK=10
+SOUND=0x23D
+}
\ No newline at end of file
diff --git a/data/dfndata/create/resources.dfn b/data/dfndata/create/resources.dfn
index fff348454..0befb09ae 100644
--- a/data/dfndata/create/resources.dfn
+++ b/data/dfndata/create/resources.dfn
@@ -50,6 +50,11 @@ ID=0x1081
ID=0x1082
}
+[RESOURCE SCALE]
+{
+ID=0x26b4
+}
+
[RESOURCE BONE]
{
ID=0x0f7e
@@ -163,4 +168,9 @@ ID=0x423A
{
ID=0x14eb
ID=0x0E34
+}
+
+[RESOURCE STONE]
+{
+ID=0x1779
}
\ No newline at end of file
diff --git a/data/dfndata/create/smithing.dfn b/data/dfndata/create/smithing.dfn
index 21ddee92a..d7f0f3635 100644
--- a/data/dfndata/create/smithing.dfn
+++ b/data/dfndata/create/smithing.dfn
@@ -979,6 +979,65 @@ MAXRANK=10
SOUND=0x002A
}
+[ITEM 367]
+{
+NAME=dragon sleeves
+ID=0x2657
+RESOURCE=SCALE 24
+SKILL=7 763 1120
+ADDITEM=dragonsleeves
+MINRANK=1
+MAXRANK=10
+SOUND=0x002A
+}
+
+[ITEM 368]
+{
+NAME=dragon breast plate
+ID=0x2657
+RESOURCE=SCALE 36
+SKILL=7 850 1120
+ADDITEM=dragonbreastplate
+MINRANK=1
+MAXRANK=10
+SOUND=0x002A
+}
+
+[ITEM 369]
+{
+NAME=dragon gloves
+ID=0x2657
+RESOURCE=SCALE 16
+SKILL=7 689 1120
+ADDITEM=dragongloves
+MINRANK=1
+MAXRANK=10
+SOUND=0x002A
+}
+
+[ITEM 370]
+{
+NAME=dragon helmet
+ID=0x2657
+RESOURCE=SCALE 20
+SKILL=7 722 1120
+ADDITEM=dragonhelm
+MINRANK=1
+MAXRANK=10
+SOUND=0x002A
+}
+
+[ITEM 371]
+{
+NAME=dragon leggings
+ID=0x2657
+RESOURCE=SCALE 28
+SKILL=7 788 1120
+ADDITEM=dragonleggings
+MINRANK=1
+MAXRANK=10
+SOUND=0x002A
+}
// end of blacksmithing section
//*****************************************
\ No newline at end of file
diff --git a/data/dfndata/house/house.dfn b/data/dfndata/house/house.dfn
index 8ef288aae..2fcbf6ccf 100644
--- a/data/dfndata/house/house.dfn
+++ b/data/dfndata/house/house.dfn
@@ -1639,6 +1639,64 @@ HOUSE_ITEM=629
HOUSE_ITEM=630
}
+// House Addon - stone table (east)
+[HOUSE 210]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=mediumstonetableeastdeed
+HOUSE_ITEM=631
+HOUSE_ITEM=632
+}
+
+// House Addon - stone table (south)
+[HOUSE 211]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=mediumstonetablesouthdeed
+HOUSE_ITEM=633
+HOUSE_ITEM=634
+}
+
+// House Addon - large stone table (east)
+[HOUSE 212]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=largestonetableeastdeed
+HOUSE_ITEM=635
+HOUSE_ITEM=636
+HOUSE_ITEM=637
+}
+
+// House Addon - large stone table (south)
+[HOUSE 213]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=largestonetablesouthdeed
+HOUSE_ITEM=638
+HOUSE_ITEM=639
+HOUSE_ITEM=640
+}
+
[HOUSE ITEM 1]
{ Wooden Door (Small house)
ITEM=0x06A5
@@ -3891,6 +3949,86 @@ Y=2
Z=0
}
+[HOUSE ITEM 631]
+{
+ITEM=0x1202
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 632]
+{
+ITEM=0x1201
+X=0
+Y=1
+Z=0
+}
+
+[HOUSE ITEM 633]
+{
+ITEM=0x1205
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 634]
+{
+ITEM=0x1204
+X=1
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 635]
+{
+ITEM=0x1202
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 636]
+{
+ITEM=0x1203
+X=0
+Y=1
+Z=0
+}
+
+[HOUSE ITEM 637]
+{
+ITEM=0x1201
+X=0
+Y=2
+Z=0
+}
+
+[HOUSE ITEM 638]
+{
+ITEM=0x1205
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 639]
+{
+ITEM=0x1206
+X=1
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 640]
+{
+ITEM=0x1204
+X=2
+Y=0
+Z=0
+}
+
// Dull Copper Colored Anvil 1/1
[HOUSE ITEM 700]
{
diff --git a/data/dfndata/house/house_addons_ml.dfn b/data/dfndata/house/house_addons_ml.dfn
index 57d6fe89d..3fc3f45e4 100644
--- a/data/dfndata/house/house_addons_ml.dfn
+++ b/data/dfndata/house/house_addons_ml.dfn
@@ -346,6 +346,32 @@ HOUSE_ITEM=1058
HOUSE_ITEM=1059
}
+// House Addon - stone anvil (east)
+[HOUSE 324]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=stoneanvileastdeed
+HOUSE_ITEM=1060
+}
+
+// House Addon - stone anvil (south)
+[HOUSE 325]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=stoneanvilsouthdeed
+HOUSE_ITEM=1061
+}
+
[HOUSE ITEM 1000]
{ alchemist table part 1 (east)
ITEM=0x3077
@@ -825,4 +851,20 @@ ITEM=0x3056
X=0
Y=1
Z=0
+}
+
+[HOUSE ITEM 1060]
+{ stone anvil east
+ITEM=0x2dd6
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 1061]
+{ stone anvil south
+ITEM=0x2dd5
+X=0
+Y=0
+Z=0
}
\ No newline at end of file
diff --git a/data/dfndata/house/house_addons_sa.dfn b/data/dfndata/house/house_addons_sa.dfn
new file mode 100644
index 000000000..1c31010bf
--- /dev/null
+++ b/data/dfndata/house/house_addons_sa.dfn
@@ -0,0 +1,293 @@
+// House Addon - ritual table
+[HOUSE 2000]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=ritualtabledeed
+HOUSE_ITEM=2000
+HOUSE_ITEM=2001
+HOUSE_ITEM=2002
+HOUSE_ITEM=2003
+}
+
+// House Addon - large gargish bed (east)
+[HOUSE 2001]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=largegargoylebedeastdeed
+HOUSE_ITEM=2006
+HOUSE_ITEM=2007
+HOUSE_ITEM=2008
+HOUSE_ITEM=2009
+HOUSE_ITEM=2010
+HOUSE_ITEM=2011
+HOUSE_ITEM=2012
+HOUSE_ITEM=2013
+HOUSE_ITEM=2014
+}
+
+// House Addon - large gargish bed (south)
+[HOUSE 2002]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=largegargoylebedsouthdeed
+HOUSE_ITEM=2015
+HOUSE_ITEM=2016
+HOUSE_ITEM=2017
+HOUSE_ITEM=2018
+HOUSE_ITEM=2019
+HOUSE_ITEM=2020
+HOUSE_ITEM=2021
+HOUSE_ITEM=2022
+HOUSE_ITEM=2023
+}
+
+// House Addon - gargish cot (east)
+[HOUSE 2003]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=gargishcoteasteastdeed
+HOUSE_ITEM=2024
+HOUSE_ITEM=2025
+}
+
+// House Addon - gargish cot (south)
+[HOUSE 2004]
+{
+ID=0x14F0
+SPACEX=1
+SPACEY=1
+CHARX=0
+CHARY=0
+CHARZ=0
+HOUSE_DEED=gargishcoteastsouthdeed
+HOUSE_ITEM=2026
+HOUSE_ITEM=2027
+}
+
+[HOUSE ITEM 2000]
+{ // ritual table
+ITEM=0x4985
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 2001]
+{ // ritual table
+ITEM=0x4984
+X=0
+Y=1
+Z=0
+}
+
+[HOUSE ITEM 2002]
+{ // ritual table
+ITEM=0x4983
+X=1
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 2003]
+{ // ritual table
+ITEM=0x4982
+X=1
+Y=1
+Z=0
+}
+
+[HOUSE ITEM 2006]
+{ E Left Side Gargoyle Bed
+ITEM=0x4019
+X=0
+Y=0
+Z=0
+}
+
+[HOUSE ITEM 2007]
+{ E Left Side Gargoyle Bed
+ITEM=0x401c
+X=0
+Y=1
+Z=0
+}
+
+[HOUSE ITEM 2008]
+{ E Left Side Gargoyle Bed
+ITEM=0x401f
+X=0
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2009]
+{ E Middle Gargoyle Bed
+ITEM=0x401a
+X=1
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2010]
+{ E Middle Gargoyle Bed
+ITEM=0x401d
+X=1
+Y=1
+Z=0
+}
+
+HOUSE ITEM 2011]
+{ E Middle Gargoyle Bed
+ITEM=0x4020
+X=1
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2012]
+{ E Right Side Gargoyle Bed
+ITEM=0x401b
+X=2
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2013]
+{ E Right Side Gargoyle Bed
+ITEM=0x401e
+X=2
+Y=1
+Z=0
+}
+
+HOUSE ITEM 2014]
+{ E Right Side Gargoyle Bed
+ITEM=0x4021
+X=2
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2015]
+{ S Left Side Gargoyle Bed
+ITEM=0x4010
+X=0
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2016]
+{ S Left Side Gargoyle Bed
+ITEM=0x4013
+X=0
+Y=1
+Z=0
+}
+
+HOUSE ITEM 2017]
+{ S Left Side Gargoyle Bed
+ITEM=0x4016
+X=0
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2018]
+{ S Middle Gargoyle Bed
+ITEM=0x4011
+X=1
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2019]
+{ S Middle Gargoyle Bed
+ITEM=0x4014
+X=1
+Y=1
+Z=0
+}
+
+HOUSE ITEM 2020]
+{ S Middle Gargoyle Bed
+ITEM=0x4017
+X=1
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2021]
+{ S left Side Gargoyle Bed
+ITEM=0x4012
+X=2
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2022]
+{ S left Side Gargoyle Bed
+ITEM=0x4015
+X=2
+Y=1
+Z=0
+}
+
+HOUSE ITEM 2023]
+{ S left Side Gargoyle Bed
+ITEM=0x4018
+X=2
+Y=2
+Z=0
+}
+
+HOUSE ITEM 2024]
+{ E Gargoyle Cot
+ITEM=0x400e
+X=0
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2025]
+{ E Gargoyle Cot
+ITEM=0x400f
+X=1
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2026]
+{ S Gargoyle Cot
+ITEM=0x400d
+X=0
+Y=0
+Z=0
+}
+
+HOUSE ITEM 2027]
+{ S Gargoyle Cot
+ITEM=0x400c
+X=0
+Y=-1
+Z=0
+}
\ No newline at end of file
diff --git a/data/dfndata/items/ItemMenu.bulk.dfn b/data/dfndata/items/ItemMenu.bulk.dfn
index de4f48a88..0b1652ee1 100644
--- a/data/dfndata/items/ItemMenu.bulk.dfn
+++ b/data/dfndata/items/ItemMenu.bulk.dfn
@@ -13544,6 +13544,10 @@ ADDITEM=0x10E5
ADDITEM=0x10E6
0x10E7=scorp
ADDITEM=0x10E7
+0x12B3=mallet and chisel
+ADDITEM=malletandchisel
+0x0FBE=Making Valuables With Stonecrafting
+ADDITEM=masonrybook
<=Previous Menu
ITEMMENU=127
}
diff --git a/data/dfndata/items/building/decs/misc_sa.dfn b/data/dfndata/items/building/decs/misc_sa.dfn
new file mode 100644
index 000000000..474a7d88a
--- /dev/null
+++ b/data/dfndata/items/building/decs/misc_sa.dfn
@@ -0,0 +1,101 @@
+[0x403d]
+{
+get=base_item
+name=gargoyle painting
+id=0x403d
+weight=1000
+movable=1
+}
+
+[0x403e]
+{
+get=0x403d
+id=0x403e
+}
+
+[0x403f]
+{
+get=base_item
+name=gargoyle sculpture
+id=0x403f
+weight=100
+movable=1
+}
+
+[0x4040]
+{
+get=0x403f
+id=0x4040
+}
+
+[0x4042]
+{
+get=base_item
+name=gargoyle vase
+id=0x4042
+weight=1000
+movable=1
+}
+
+[0x403e]
+{
+get=0x4042
+id=0x403e
+}
+
+[0x4985]
+{
+get=base_item
+name=ritual table
+id=0x4985
+weight=1000
+movable=2
+}
+
+[0x4984]
+{
+get=0x4985
+id=0x4984
+}
+
+[0x4983]
+{
+get=0x4985
+id=0x4983
+}
+
+[0x4982]
+{
+get=0x4985
+id=0x4982
+}
+
+[0x494e]
+{
+get=base_item
+name=gargoyle statue
+id=0x494e
+weight=100
+movable=2
+}
+
+[0x494d]
+{
+get=0x494e
+id=0x494d
+}
+
+[0x493e]
+{
+get=base_item
+name=gryphon statue
+id=0x493e
+weight=100
+movable=2
+}
+
+[0x493b]
+{
+get=0x493e
+id=0x493b
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/decs/misc_se.dfn b/data/dfndata/items/building/decs/misc_se.dfn
new file mode 100644
index 000000000..ea016e08d
--- /dev/null
+++ b/data/dfndata/items/building/decs/misc_se.dfn
@@ -0,0 +1,17 @@
+[0x241C]
+{
+get=base_item
+name=Small Urn
+id=0x241C
+weight=100
+movable=1
+}
+
+[0x241A]
+{
+get=base_item
+name=Tower Sculpture
+id=0x241A
+weight=100
+movable=1
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/decs/misc_tol.dfn b/data/dfndata/items/building/decs/misc_tol.dfn
new file mode 100644
index 000000000..c286e275e
--- /dev/null
+++ b/data/dfndata/items/building/decs/misc_tol.dfn
@@ -0,0 +1,17 @@
+[0x9bc7]
+{
+get=base_item
+name=Tall 18th Anniversary Vase
+id=0x9bc7
+weight=1000
+movable=1
+}
+
+[0x9bca]
+{
+get=base_item
+name=Short 18th Anniversary Vase
+id=0x9bca
+weight=100
+movable=1
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/decs/stoneart.dfn b/data/dfndata/items/building/decs/stoneart.dfn
index 290036814..efbb39478 100644
--- a/data/dfndata/items/building/decs/stoneart.dfn
+++ b/data/dfndata/items/building/decs/stoneart.dfn
@@ -228,9 +228,9 @@ decay=1
[0x139d]
{ S pegasus
get=base_item
-name=statue
+name=pegasus statuette
id=0x139d
-weight=1000
+weight=100
movable=1
decay=1
}
diff --git a/data/dfndata/items/building/floors/craftable_tiles.dfn b/data/dfndata/items/building/floors/craftable_tiles.dfn
new file mode 100644
index 000000000..cf9b816f4
--- /dev/null
+++ b/data/dfndata/items/building/floors/craftable_tiles.dfn
@@ -0,0 +1,26 @@
+[base_craftable_tile]
+{
+get=base_item
+script=5080
+weight=500
+movable=1
+decay=1
+}
+
+[lightpaver]
+{
+get=base_craftable_tile
+id=0x0519
+}
+
+[mediumpaver]
+{
+get=base_craftable_tile
+id=0x051d
+}
+
+[darkpaver]
+{
+get=base_craftable_tile
+id=0x0521
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/furniture/beds_sa.dfn b/data/dfndata/items/building/furniture/beds_sa.dfn
new file mode 100644
index 000000000..4ea646876
--- /dev/null
+++ b/data/dfndata/items/building/furniture/beds_sa.dfn
@@ -0,0 +1,116 @@
+[base_bed_sa]
+{
+get=base_item
+name=bed
+weight=10000
+movable=2
+origin=sa
+}
+
+[0x4019]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x4019
+}
+
+[0x401c]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401c
+}
+
+[0x401f]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401f
+}
+
+[0x401a]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401a
+}
+
+[0x401d]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401d
+}
+
+[0x4020]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x4020
+}
+
+[0x401b]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401b
+}
+
+[0x401e]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x401e
+}
+
+[0x4021]
+{ E gargoyle bed
+get=base_bed_sa
+id=0x4021
+}
+
+[0x4010]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4010
+}
+
+[0x4013]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4013
+}
+
+[0x4016]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4016
+}
+
+[0x4011]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4011
+}
+
+[0x4014]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4014
+}
+
+[0x4017]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4017
+}
+
+[0x4012]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4012
+}
+
+[0x4015]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4015
+}
+
+[0x4018]
+{ S gargoyle bed
+get=base_bed_sa
+id=0x4018
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/furniture/chairs.dfn b/data/dfndata/items/building/furniture/chairs.dfn
index 21b636a28..9348c29ad 100644
--- a/data/dfndata/items/building/furniture/chairs.dfn
+++ b/data/dfndata/items/building/furniture/chairs.dfn
@@ -607,7 +607,7 @@ movable=2
get=base_item
name=stone chair
id=0x1218
-weight=4000
+weight=1000
value=30 15
restock=10
movable=1
@@ -620,7 +620,7 @@ good=1
get=base_item
name=stone chair
id=0x1219
-weight=4000
+weight=1000
value=30 15
restock=10
movable=1
@@ -633,7 +633,7 @@ good=1
get=base_item
name=stone chair
id=0x121a
-weight=4000
+weight=1000
value=30 15
restock=10
movable=1
@@ -646,7 +646,7 @@ good=1
get=base_item
name=stone chair
id=0x121b
-weight=4000
+weight=1000
value=30 15
restock=10
movable=1
diff --git a/data/dfndata/items/building/furniture/chairs_sa.dfn b/data/dfndata/items/building/furniture/chairs_sa.dfn
new file mode 100644
index 000000000..ce740bb14
--- /dev/null
+++ b/data/dfndata/items/building/furniture/chairs_sa.dfn
@@ -0,0 +1,31 @@
+[base_chair_sa]
+{
+get=base_item
+weight=10000
+movable=2
+origin=sa
+}
+
+[0x400e]
+{ E Gargoyle Cot
+get=base_chair_sa
+id=0x400e
+}
+
+[0x400f]
+{ E Gargoyle Cot
+get=base_chair_sa
+id=0x400f
+}
+
+[0x400d]
+{ S Gargoyle Cot
+get=base_chair_sa
+id=0x400d
+}
+
+[0x400c]
+{ S Gargoyle Cot
+get=base_chair_sa
+id=0x400c
+}
\ No newline at end of file
diff --git a/data/dfndata/items/building/walls/craftablewalls.dfn b/data/dfndata/items/building/walls/craftablewalls.dfn
new file mode 100644
index 000000000..948988ad2
--- /dev/null
+++ b/data/dfndata/items/building/walls/craftablewalls.dfn
@@ -0,0 +1,342 @@
+[base_craftable_wall]
+{
+get=base_item
+script=5080
+weight=1000
+movable=1
+decay=1
+}
+
+[roughwindowless1]
+{
+get=base_craftable_wall
+id=0x01cf
+}
+
+[roughwindowless2]
+{
+get=base_craftable_wall
+id=0x01d0
+}
+
+[roughwindowless3]
+{
+get=base_craftable_wall
+id=0x01d1
+}
+
+[roughwindowless4]
+{
+get=base_craftable_wall
+id=0x01d2
+}
+
+[roughwindow1]
+{
+get=base_craftable_wall
+id=0x01d3
+}
+
+[roughwindow2]
+{
+get=base_craftable_wall
+id=0x01d4
+}
+
+[rougharch1]
+{
+get=base_craftable_wall
+id=0x01d5
+}
+
+[rougharch2]
+{
+get=base_craftable_wall
+id=0x01d6
+}
+
+[rougharch3]
+{
+get=base_craftable_wall
+id=0x01d7
+}
+
+[rougharch4]
+{
+get=base_craftable_wall
+id=0x01d8
+}
+
+[rougharch5]
+{
+get=base_craftable_wall
+id=0x01d9
+}
+
+[roughPillar]
+{
+get=base_craftable_wall
+id=0x01da
+}
+
+[roughroundedarch1]
+{
+get=base_craftable_wall
+id=0x01db
+}
+
+[roughroundedarch2]
+{
+get=base_craftable_wall
+id=0x01dc
+}
+
+[roughroundedarch3]
+{
+get=base_craftable_wall
+id=0x01dd
+}
+
+[roughroundedarch4]
+{
+get=base_craftable_wall
+id=0x01dE
+}
+
+[roughroundedarch5]
+{
+get=base_craftable_wall
+id=0x01df
+}
+
+[roughsmallarch1]
+{
+get=base_craftable_wall
+id=0x01E0
+}
+
+[roughsmallarch2]
+{
+get=base_craftable_wall
+id=0x01E1
+}
+
+[roughsmallarch3]
+{
+get=base_craftable_wall
+id=0x01E2
+}
+
+[roughsmallarch4]
+{
+get=base_craftable_wall
+id=0x01E3
+}
+
+[roughsmallarch5]
+{
+get=base_craftable_wall
+id=0x01E4
+}
+
+[roughsmallarch6]
+{
+get=base_craftable_wall
+id=0x01E5
+}
+
+[roughangledPillar1]
+{
+get=base_craftable_wall
+id=0x01E6
+}
+
+[roughangledPillar2]
+{
+get=base_craftable_wall
+id=0x01E7
+}
+
+[shortrough1]
+{
+get=base_craftable_wall
+id=0x01E8
+}
+
+[shortrough2]
+{
+get=base_craftable_wall
+id=0x01E9
+}
+
+[shortrough3]
+{
+get=base_craftable_wall
+id=0x01Ea
+}
+
+[shortrough4]
+{
+get=base_craftable_wall
+id=0x01Eb
+}
+
+[roughblock]
+{
+get=base_craftable_wall
+id=0x0788
+}
+
+[roughsteps1]
+{
+get=base_craftable_wall
+id=0x0789
+}
+
+[roughsteps2]
+{
+get=base_craftable_wall
+id=0x078a
+}
+
+[roughsteps3]
+{
+get=base_craftable_wall
+id=0x078b
+}
+
+[roughsteps4]
+{
+get=base_craftable_wall
+id=0x078c
+}
+
+[roughcornersteps1]
+{
+get=base_craftable_wall
+id=0x078d
+}
+
+[roughcornersteps2]
+{
+get=base_craftable_wall
+id=0x078E
+}
+
+[roughcornersteps3]
+{
+get=base_craftable_wall
+id=0x078f
+}
+
+[roughcornersteps4]
+{
+get=base_craftable_wall
+id=0x0790
+}
+
+[roughroundedcornerseps1]
+{
+get=base_craftable_wall
+id=0x0791
+}
+
+[roughroundedcornerseps2]
+{
+get=base_craftable_wall
+id=0x0792
+}
+
+[roughroundedcornerseps3]
+{
+get=base_craftable_wall
+id=0x0793
+}
+
+[roughroundedcornerseps4]
+{
+get=base_craftable_wall
+id=0x0794
+}
+
+[roughinsetsteps1]
+{
+get=base_craftable_wall
+id=0x0795
+}
+
+[roughinsetsteps2]
+{
+get=base_craftable_wall
+id=0x0796
+}
+
+[roughinsetsteps3]
+{
+get=base_craftable_wall
+id=0x0797
+}
+
+[roughinsetsteps4]
+{
+get=base_craftable_wall
+id=0x0798
+}
+
+[roughroundedisetsteps1]
+{
+get=base_craftable_wall
+id=0x0799
+}
+
+[roughroundedisetsteps2]
+{
+get=base_craftable_wall
+id=0x079a
+}
+
+[roughroundedisetsteps3]
+{
+get=base_craftable_wall
+id=0x079b
+}
+
+[roughroundedisetsteps4]
+{
+get=base_craftable_wall
+id=0x079c
+}
+
+[base_craftable_door]
+{
+get=base_door
+script=5081
+weight=1000
+movable=1
+decay=1
+type=12
+}
+
+[StoneDoor_S_In]
+{
+get=base_craftable_door
+id=0x0326
+}
+
+[StoneDoor_E_Out]
+{
+get=base_craftable_door
+id=0x032C
+}
+
+[StoneDoor_S_Out]
+{
+get=base_craftable_door
+id=0x032a
+}
+
+[StoneDoor_E_In]
+{
+get=base_craftable_door
+id=0x0330
+}
\ No newline at end of file
diff --git a/data/dfndata/items/deeds/houseaddon_deeds.dfn b/data/dfndata/items/deeds/houseaddon_deeds.dfn
index 9bd5fecbd..524e91099 100644
--- a/data/dfndata/items/deeds/houseaddon_deeds.dfn
+++ b/data/dfndata/items/deeds/houseaddon_deeds.dfn
@@ -486,3 +486,39 @@ origin=uor
{
get=dc_anvil_deed si_anvil_deed c_anvil_deed b_anvil_deed g_anvil_deed a_anvil_deed ve_anvil_deed va_anvil_deed
}
+
+[mediumstonetableeastdeed]
+{
+name=stone table (east)
+id=0x14f0
+morex=210
+value=1000 500
+restock=15
+}
+
+[mediumstonetablesouthdeed]
+{
+name=stone table (south)
+id=0x14f0
+morex=211
+value=1000 500
+restock=15
+}
+
+[largestonetableeastdeed]
+{
+name=large stone table (east)
+id=0x14f0
+morex=212
+value=1000 500
+restock=15
+}
+
+[largestonetablesouthdeed]
+{
+name=large stone table (south)
+id=0x14f0
+morex=213
+value=1000 500
+restock=15
+}
\ 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..e5a86e7b5 100644
--- a/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn
+++ b/data/dfndata/items/deeds/houseaddon_deeds_ml.dfn
@@ -212,4 +212,26 @@ name=Tall Elven Bed (South)
id=0x14F0
morex=323
origin=ml
+}
+
+[stoneanvileastdeed]
+{
+get=base_item
+name=stone anvil (east)
+id=0x14f0
+morex=324
+value=1000 500
+restock=15
+origin=ml
+}
+
+[stoneanvilsouthdeed]
+{
+get=base_item
+name=stone anvil (south)
+id=0x14f0
+morex=325
+value=1000 500
+restock=15
+origin=ml
}
\ No newline at end of file
diff --git a/data/dfndata/items/deeds/houseaddon_deeds_sa.dfn b/data/dfndata/items/deeds/houseaddon_deeds_sa.dfn
new file mode 100644
index 000000000..9a5c93d3e
--- /dev/null
+++ b/data/dfndata/items/deeds/houseaddon_deeds_sa.dfn
@@ -0,0 +1,54 @@
+[ritualtabledeed]
+{
+get=base_item
+name=ritual table
+id=0x14f0
+morex=2000
+value=1000 500
+restock=15
+origin=sa
+}
+
+[largegargoylebedeastdeed]
+{
+get=base_item
+name=large gargish bed (east)
+id=0x14f0
+morex=2001
+value=1000 500
+restock=15
+origin=sa
+}
+
+[largegargoylebedsouthdeed]
+{
+get=base_item
+name=large gargish bed (south)
+id=0x14f0
+morex=2002
+value=1000 500
+restock=15
+origin=sa
+}
+
+[gargishcoteasteastdeed]
+{
+get=base_item
+name=gargish cot (east)
+id=0x14f0
+morex=2003
+value=1000 500
+restock=15
+origin=sa
+}
+
+[gargishcoteastsouthdeed]
+{
+get=base_item
+name=gargish cot (south)
+id=0x14f0
+morex=2004
+value=1000 500
+restock=15
+origin=sa
+}
\ No newline at end of file
diff --git a/data/dfndata/items/itemlists/itemlists.dfn b/data/dfndata/items/itemlists/itemlists.dfn
index 1c72df0fd..135c58465 100644
--- a/data/dfndata/items/itemlists/itemlists.dfn
+++ b/data/dfndata/items/itemlists/itemlists.dfn
@@ -1226,7 +1226,7 @@ itemlist=randomgranite
[ITEMLIST randomgranite]
{
-irongranite
+granite
dullcoppergranite
shadowirongranite
coppergranite
diff --git a/data/dfndata/items/misc/crafting_recipes.dfn b/data/dfndata/items/misc/crafting_recipes.dfn
index 157851d7b..26a64fa7e 100644
--- a/data/dfndata/items/misc/crafting_recipes.dfn
+++ b/data/dfndata/items/misc/crafting_recipes.dfn
@@ -16,4 +16,36 @@ origin=ml
get=base_recipe_scroll
custominttag=recipeID 18
customstringtag=recipeName skullcap
+}
+
+[tall_18th_anniversary_vase_recipe]
+{
+get=base_recipe_scroll
+custominttag=recipeID 3500
+customstringtag=recipeName Tall 18th Anniversary Vase
+origin=sa
+}
+
+[short_18th_anniversary_vase_recipe]
+{
+get=base_recipe_scroll
+custominttag=recipeID 3501
+customstringtag=recipeName Short 18th Anniversary Vase
+origin=sa
+}
+
+[stone_anvil_east_recipe]
+{
+get=base_recipe_scroll
+custominttag=recipeID 3520
+customstringtag=recipeName Stone Anvil (East)
+origin=sa
+}
+
+[stone_anvil_south_recipe]
+{
+get=base_recipe_scroll
+custominttag=recipeID 3521
+customstringtag=recipeName Stone Anvil (South)
+origin=sa
}
\ No newline at end of file
diff --git a/data/dfndata/items/misc/jewelry_sa.dfn b/data/dfndata/items/misc/jewelry_sa.dfn
new file mode 100644
index 000000000..f8f338a49
--- /dev/null
+++ b/data/dfndata/items/misc/jewelry_sa.dfn
@@ -0,0 +1,32 @@
+[base_jewelry_sa]
+{
+get=base_item
+weight=100
+value=26 13
+restock=10
+decay=1
+good=37
+}
+
+[0x4D0a]
+{
+get=base_jewelry_sa
+name=gargish stone amulet
+id=0x4D0a
+colour=2500
+layer=0x0a
+str=40
+}
+
+[0x4210]
+{
+get=base_jewelry_sa
+name=gargish amulet
+id=0x4210
+layer=0x0a
+str=40
+hp=30
+maxhp=40
+def=1
+elementresist=2 2 3 2
+}
\ No newline at end of file
diff --git a/data/dfndata/items/skills/resources/mining.dfn b/data/dfndata/items/skills/resources/mining.dfn
index f70678ef1..35a1100f8 100644
--- a/data/dfndata/items/skills/resources/mining.dfn
+++ b/data/dfndata/items/skills/resources/mining.dfn
@@ -346,7 +346,7 @@ decay=1
good=46
}
-[irongranite]
+[granite]
{
get=base_granite
}
diff --git a/data/dfndata/items/skills/tools/alchemy.dfn b/data/dfndata/items/skills/tools/alchemy.dfn
index d46e1a729..3828fbd4c 100644
--- a/data/dfndata/items/skills/tools/alchemy.dfn
+++ b/data/dfndata/items/skills/tools/alchemy.dfn
@@ -789,4 +789,105 @@ decay=1
value=10637 5318
origin=lbr
script=5054
+}
+
+[0x2f5d]
+{
+get=base_item
+name=hollow prism
+id=0x2f5d
+weight=100
+restock=20
+movable=1
+decay=1
+good=51
+}
+
+[hollowprism]
+{
+get=0x2f5d
+}
+
+[0x403a]
+{
+get=base_item
+name=Gargoyle Floor Mirror
+id=0x403a
+weight=1000
+restock=20
+movable=1
+decay=1
+good=51
+}
+
+[0x4046]
+{
+get=0x403a
+id=0x4046
+}
+
+[gargoylefloormirror]
+{
+get=0x403a 0x4046
+}
+
+[0x4044]
+{
+get=base_item
+name=Gargoyle Wall Mirror
+id=0x4044
+weight=1000
+restock=20
+movable=1
+decay=1
+good=51
+}
+
+[0x4045]
+{
+get=0x4044
+id=0x4045
+}
+
+[gargoylewallmirror]
+{
+get=0x4044 0x4045
+}
+
+[emptyvenomvial]
+{
+get=base_item
+name=Empty Venom Vial
+id=0x0E24
+weight=100
+restock=20
+movable=1
+decay=1
+good=51
+}
+
+[emptyoilflask]
+{
+get=base_item
+name=empty oil flask
+id=0x1C18
+weight=100
+restock=20
+pilable=1
+movable=1
+decay=1
+good=51
+}
+
+[workableglass]
+{
+get=base_item
+name=workable glass
+id=0x4B80
+weight=100
+restock=20
+pilable=1
+movable=1
+decay=1
+good=51
}
\ No newline at end of file
diff --git a/data/dfndata/items/skills/tools/blacksmithy.dfn b/data/dfndata/items/skills/tools/blacksmithy.dfn
index debd46232..f99512e04 100644
--- a/data/dfndata/items/skills/tools/blacksmithy.dfn
+++ b/data/dfndata/items/skills/tools/blacksmithy.dfn
@@ -1,5 +1,4 @@
//Blacksmithy Tools
-
[0x0fbb]
{
get=base_item
@@ -51,3 +50,18 @@ id=0x0fb0
get=0x0faf 0x0fb0
}
+[0x2dd6]
+{// stone anvil east
+get=0x0faf
+name=elven anvil
+id=0x2dd6
+origin=ml
+}
+
+[0x2dd5]
+{// stone anvil south
+get=0x0faf
+name=elven anvil
+id=0x2dd5
+origin=ml
+}
\ No newline at end of file
diff --git a/data/dfndata/items/skills/tools/carpenty.dfn b/data/dfndata/items/skills/tools/carpenty.dfn
index 64a7319b6..a12e9e45f 100644
--- a/data/dfndata/items/skills/tools/carpenty.dfn
+++ b/data/dfndata/items/skills/tools/carpenty.dfn
@@ -300,4 +300,34 @@ usesleft=25
script=2200// uses left tooltip
}
+[malletandchisel]
+{
+get=base_item
+name=mallet and chisel
+id=0x12B3
+colour=0x3B9
+weight=100
+value=9 4
+restock=20
+decay=1
+good=53
+maxuses=75
+usesleft=25
+script=2200// uses left tooltip
+script=4031// craft tool
+}
+[masonrybook]
+{
+get=base_item
+name=Making Valuables With Stonecrafting
+id=0xFBE
+weight=500
+movable=1
+pileable=1
+decay=1
+value=10637 5318
+good=53
+origin=lbr
+script=5055
+}
diff --git a/data/dictionaries/dictionary.CSY b/data/dictionaries/dictionary.CSY
index 01293a24f..9fba048c9 100644
--- a/data/dictionaries/dictionary.CSY
+++ b/data/dictionaries/dictionary.CSY
@@ -5112,6 +5112,85 @@
13609=prázdné lahviÄky
13610=plné lahviÄky
13611=toÄÃcà se pÅ™esýpacà hodiny
+// 14001 - 14500 Zednické řemeslné dovednosti
+14001=Zednické menu
+14002=Dekorace
+14003=Nábytek
+14004=Sochy
+14005=Různé doplňky
+14006=Kamenné brnÄ›nÃ
+14007=Kamenné zbraně
+14008=Kamenné zdi
+14009=Kamenné schody
+14010=Kamenné podlahy
+14011=Žula
+14012=Malá měděná žula
+14013=Žula Shadow Iron
+14014=Měděná žula
+14015=Bronzová žula
+14016=Zlatá žula
+14017=Agapitová žula
+14018=Verite žula
+14019=Valoritová žula
+14050=váza
+14051=velká váza
+14052=malá urna
+14053=věžová socha
+14054=obraz chrliÄe
+14055=socha chrliÄe
+14056=váza chrliÄ
+14057=vysoká váza k 18. výroÄÃ
+14058=nÃzká váza k 18. výroÄÃ
+14059=kamenná židle
+14060=kamenný stůl (východ)
+14061=kamenný stůl (jih)
+14062=velký kamenný stůl (východ)
+14063=velký kamenný stůl (jih)
+14064=rituálnà stůl
+14065=malá socha (jih)
+14066=malá socha (sever)
+14067=malá socha (východ)
+14068=soška pegase
+14069=socha chrliÄe
+14070=socha gryfona
+14071=kamenná kovadlina (východ)
+14072=kamenná kovadlina (jih)
+14073=velká ÄernobÃlá postel (východ)
+14074=velká ÄernobÃlá postel (jih)
+14075=ÄernobÃlá postýlka (východ)
+14076=ÄernobÃlá postýlka (jih)
+14077=ÄernobÃlé kamenné paže
+14078=ÄernobÃlá kamenná truhla
+14079=ÄernobÃlé kamenné legÃny
+14080=ÄernobÃlý kamenný kilt
+14081=ÄernobÃlé kamenné paže
+14082=ÄernobÃlá kamenná truhla
+14083=ÄernobÃlé kamenné legÃny
+14084=ÄernobÃlý kamenný kilt
+14085=velký kamenný Å¡tÃt
+14086=ÄernobÃlý kamenný amulet
+14087=kamenný váleÄný meÄ
+14088=Drůstkový Bez oken
+14089=Drůstkový Okno
+14090=Drůstkový Oblouk
+14091=Hrubý sloupek
+14092=Hrubý zaoblený oblouk
+14093=Hrubý malý oblouk
+14094=Hrubý šikmý sloupek
+14095=Krátký hrubý
+14096=Kamenné dveře (J dovnitř)
+14097=Kamenné dveře (V ven)
+14098=Levé kovové dveře (J dovnitř)
+14099=Pravé kovové dveře (J dovnitř)
+14100=krátký hrubý
+14101=hrubé schody
+14102=hrubé rohové schody
+14103=hrubě zaoblený rohový schod
+14104=hrubé vložené schody
+14105=hrubě zaoblené vložené schody
+14106=světlá dlažba
+14107=střednà dlažba
+14108=tmavá dlažba
// 15000 Dovednosti
15000=Alchymie
15001=Anatomie
diff --git a/data/dictionaries/dictionary.ENG b/data/dictionaries/dictionary.ENG
index 398ae6b41..b328a4713 100644
--- a/data/dictionaries/dictionary.ENG
+++ b/data/dictionaries/dictionary.ENG
@@ -3524,6 +3524,9 @@
6278=You have to use your taste identification skill to know more about this potion.
6279=Tip: Skill check success guaranteed due to elevated command level!
6280=Spellcast delay ignored due to elevated command level!
+6297=You havent learned masonry.
+6298=Only a Grandmaster Carpenter can learn from this book.
+6299= You have learned to make items from stone. You will need miners to gather stones for you to make these items.
6300=You havent learned glassblowing.
6301=Only a Grandmaster Alchemist can learn from this book.
6302=You have already learned this information.
@@ -4705,6 +4708,12 @@
10296=You have no knowledge on how to unravel that.
10297=You unravel the item and place 5 resources in your pack.
20298=You aren't skilled enough to unravel this.
+20299=RED SCALES
+20300=YELLOW SCALES
+20301=BLACK SCALES
+20302=GREEN SCALES
+20303=WHITE SCALES
+20304=BLUE SCALES
// [10600-10900] Carpentry Crafting Skill
10600=CARPENTRY MENU
10601=Other
@@ -5111,6 +5120,85 @@
13609=empty vials
13610=full vials
13611=spinning hourglass
+// 14001 - 14500 Masonary Crafting Skill
+14001=Masonry Menu
+14002=Decorations
+14003=Furniture
+14004=Statues
+14005=Misc Addons
+14006=Stone Armor
+14007=Stone Weapons
+14008=Stone Walls
+14009=Stone Stairs
+14010=Stone Floors
+14011=Granite
+14012=Dull Copper Granite
+14013=Shadow Iron Granite
+14014=Copper Granite
+14015=Bronze Granite
+14016=Gold Granite
+14017=Agapite Granite
+14018=Verite Granite
+14019=Valorite Granite
+14050=vase
+14051=large vase
+14052=small urn
+14053=Tower Sculpture
+14054=gargoyle painting
+14055=gargoyle sculpture
+14056=gargoyle vase
+14057=Tall 18th Anniversary Vase
+14058=Short 18th Anniversary Vase
+14059=stone chair
+14060=stone table (east)
+14061=stone table (south)
+14062=large stone table (east)
+14063=large stone table (south)
+14064=ritual table
+14065=small statue (south)
+14066=small statue (north)
+14067=small statue (east)
+14068=pegasus statuette
+14069=gargoyle statue
+14070=gryphon statue
+14071=stone anvil (east)
+14072=stone anvil (south)
+14073=large gargish bed (east)
+14074=large gargish bed (south)
+14075=gargish cot (east)
+14076=gargish cot (south)
+14077=gargish stone arms
+14078=gargish stone chest
+14079=gargish stone leggings
+14080=gargish stone kilt
+14081=gargish stone arms
+14082=gargish stone chest
+14083=gargish stone leggings
+14084=gargish stone kilt
+14085=large stone shield
+14086=gargish stone amulet
+14087=stone war sword
+14088=Rough Windowless
+14089=Rough Window
+14090=Rough Arch
+14091=Rough Pillar
+14092=Rough Rounded Arch
+14093=Rough Small Arch
+14094=Rough Angled Pillar
+14095=Short Rough
+14096=Stone Door (S In)
+14097=Stone Door (E Out)
+14098=Left Metal Door (S In)
+14099=Right Metal Door (S In)
+14100=short rough
+14101=rough steps
+14102=rough corner steps
+14103=rough rounded corner step
+14104=rough inset steps
+14105=rough rounded inset steps
+14106=light paver
+14107=medium paver
+14108=dark paver
// 15000 Skills
15000=Alchemy
15001=Anatomy
diff --git a/data/dictionaries/dictionary.FRE b/data/dictionaries/dictionary.FRE
index 63a2382a3..e373bca53 100644
--- a/data/dictionaries/dictionary.FRE
+++ b/data/dictionaries/dictionary.FRE
@@ -5276,6 +5276,85 @@
13609=flacons vides
13610=flacons pleins
13611=sablier tournant
+// 14001 - 14500 Compétence en maçonnerie
+14001=Menu Maçonnerie
+14002=Décorations
+14003=Mobilier
+14004=Statues
+14005=Accessoires divers
+14006=Armure de pierre
+14007=Armes de pierre
+14008=Murs de pierre
+14009=Escaliers de pierre
+14010=Sols en pierre
+14011=Granite
+14012=Granite de cuivre mat
+14013=Granite de fer de l'ombre
+14014=Granite de cuivre
+14015=Granite de bronze
+14016=Granite d'or
+14017=Granite d'agapite
+14018=Granite de vératie
+14019=Granite de valorite
+14050=Vase
+14051=Grand Vase
+14052=Petite urne
+14053=Sculpture de tour
+14054=Peinture de gargouille
+14055=Sculpture de gargouille
+14056=Vase à gargouille
+14057=Grand vase du 18e anniversaire
+14058=Petit vase du 18e anniversaire
+14059=Chaise en pierre
+14060=Table en pierre (est)
+14061=Table en pierre (sud)
+14062=Grande table en pierre (est)
+14063=Grande table en pierre (sud)
+14064=Table rituelle
+14065=Petite statue (sud)
+14066=Petite statue (nord)
+14067=Petite statue (est)
+14068=Statuette de Pégase
+14069=Statue de gargouille
+14070=Statue de griffon
+14071=Enclume en pierre (est)
+14072=Pierre Enclume (sud)
+14073=Grand lit gargish (est)
+14074=Grand lit gargish (sud)
+14075=Bébé gargish (est)
+14076=Bébé gargish (sud)
+14077=Armes en pierre gargish
+14078=Coffre en pierre gargish
+14079=Jambières en pierre gargish
+14080=Kilt en pierre gargish
+14081=Armes en pierre gargish
+14082=Coffre en pierre gargish
+14083=Jambières en pierre gargish
+14084=Kilt en pierre gargish
+14085=Grand bouclier en pierre
+14086=Amulette en pierre gargish
+14087=Épée de guerre en pierre
+14088=Rugueux sans fenêtre
+14089=Rugueux avec fenêtre
+14090=Arche rugueuse
+14091=Rugueux Pilier
+14092 = Arche arrondie brute
+14093 = Petite arche brute
+14094 = Pilier angulaire brut
+14095 = Courte marche brute
+14096 = Porte en pierre (S vers l'intérieur)
+14097 = Porte en pierre (E vers l'extérieur)
+14098 = Porte métallique gauche (S vers l'intérieur)
+14099 = Porte métallique droite (S vers l'intérieur)
+14100 = Courte marche brute
+14101 = Marches brutes
+14102 = Marches d'angle brutes
+14103 = Marche d'angle grossièrement arrondie
+14104 = Marches encastrées brutes
+14105 = Marches encastrées grossièrement arrondies
+14106 = Pavé clair
+14107 = Pavé moyen
+14108 = Pavé foncé
// 15000 compétences
15000=Alchimie
15001=Anatomie
diff --git a/data/dictionaries/dictionary.GER b/data/dictionaries/dictionary.GER
index 9014ffff0..1d1f546c1 100644
--- a/data/dictionaries/dictionary.GER
+++ b/data/dictionaries/dictionary.GER
@@ -5112,6 +5112,85 @@
13609=leere Fläschchen
13610=volle Fläschchen
13611=drehende Sanduhr
+// 14001 - 14500 Maurerhandwerksfertigkeit
+14001=Mauerwerksmenü
+14002=Dekorationen
+14003=Möbel
+14004=Statuen
+14005=Sonstige Erweiterungen
+14006=Steinrüstung
+14007=Steinwaffen
+14008=Steinmauern
+14009=Steintreppen
+14010=Steinböden
+14011=Granit
+14012=Mattkupfergranit
+14013=Schatteneisengranit
+14014=Kupfergranit
+14015=Bronzegranit
+14016=Goldgranit
+14017=Agapitgranit
+14018=Veritgranit
+14019=Valorit Granit
+14050=Vase
+14051=Große Vase
+14052=Kleine Urne
+14053=Turmskulptur
+14054=Gemälde mit Wasserspeier
+14055=Wasserspeier-Skulptur
+14056=Wasserspeier-Vase
+14057=Hohe Vase zum 18. Jahrestag
+14058=Kleine Vase zum 18. Jahrestag
+14059=Steinstuhl
+14060=Steintisch (Osten)
+14061=Steintisch (Süden)
+14062=Großer Steintisch (Osten)
+14063=Großer Steintisch (Süden)
+14064=Ritualtisch
+14065=Kleine Statue (Süden)
+14066=Kleine Statue (Norden)
+14067=Kleine Statue (Osten)
+14068=Pegasus-Statuette
+14069=Wasserspeier Statue
+14070=Greifenstatue
+14071=Steinamboss (Osten)
+14072=Steinamboss (Süden)
+14073=Großes, grelles Bett (Osten)
+14074=Großes, grelles Bett (Süden)
+14075=Grelles Feldbett (Osten)
+14076=Grelles Feldbett (Süden)
+14077=Grelle Steinarme
+14078=Grelle Steintruhe
+14079=Grelle Steinbeinlinge
+14080=Grelle Steinkilt
+14081=Grelle Steinarme
+14082=Grelle Steintruhe
+14083=Grelle Steinbeinlinge
+14084=Grelle Steinkilt
+14085=Großer Steinschild
+14086=Grelles Steinamulett
+14087=Steinkrieger Schwert
+14088=Rohbau ohne Fenster
+14089=Rohbau mit Fenster
+14090=Rohbau mit Bogen
+14091=Rohbau mit Pfeiler
+14092=Rohbau mit Rundbogen
+14093=Rohbau mit kleinem Bogen
+14094=Rohbau mit abgewinkeltem Pfeiler
+14095=Kurzbau
+14096=Steintür (Südseite)
+14097=Steintür (Ostseite)
+14098=Linke Metalltür (Südseite)
+14099=Rechte Metalltür (Südseite)
+14100=Kurzbau
+14101=Rohbau mit Stufen
+14102=Rohbau mit Eckstufen
+14103=Rohbau mit abgerundeten Eckstufen
+14104=Rohbau mit Einsatzstufen
+14105=Rohbau mit abgerundeten Einsatzstufen
+14106=Heller Pflasterstein
+14107=Mittelgroßer Pflasterstein
+14108=Dunkler Pflasterstein
// 15000 Fertigkeiten
15000=Alchemie
15001=Anatomie
diff --git a/data/dictionaries/dictionary.ITA b/data/dictionaries/dictionary.ITA
index 15acb8adf..c290aa268 100644
--- a/data/dictionaries/dictionary.ITA
+++ b/data/dictionaries/dictionary.ITA
@@ -5112,6 +5112,85 @@
13609=fialette vuote
13610=flaconi pieni
13611=clessidra rotante
+// 14001 - 14500 Abilità di lavorazione della muratura
+14001=Menu muratura
+14002=Decorazioni
+14003=Mobili
+14004=Statue
+14005=Accessori vari
+14006=Armature di pietra
+14007=Armi di pietra
+14008=Muri di pietra
+14009=Scale di pietra
+14010=Pavimenti di pietra
+14011=Granito
+14012=Granito rame opaco
+14013=Granito ferro ombra
+14014=Granito rame
+14015=Granito bronzo
+14016=Granito oro
+14017=Granito agapite
+14018=Granito Verite
+14019=Valorite Granito
+14050=vaso
+14051=vaso grande
+14052=urna piccola
+14053=scultura a torre
+14054=dipinto a gargoyle
+14055=scultura a gargoyle
+14056=vaso a gargoyle
+14057=vaso alto per il 18° anniversario
+14058=vaso basso per il 18° anniversario
+14059=sedia in pietra
+14060=tavolo in pietra (est)
+14061=tavolo in pietra (sud)
+14062=tavolo in pietra grande (est)
+14063=tavolo in pietra grande (sud)
+14064=tavolo rituale
+14065=statua piccola (sud)
+14066=statua piccola (nord)
+14067=statua piccola (est)
+14068=statuetta di Pegaso
+14069=gargoyle statua
+14070=statua di grifone
+14071=incudine di pietra (est)
+14072=incudine di pietra (sud)
+14073=grande letto di pietra (est)
+14074=grande letto di pietra (sud)
+14075=branda di pietra (est)
+14076=branda di pietra (sud)
+14077=braccia di pietra
+14078=baule di pietra
+14079=gambali di pietra
+14080=kilt di pietra
+14081=braccia di pietra
+14082=baule di pietra
+14083=gambali di pietra
+14084=kilt di pietra
+14085=grande scudo di pietra
+14086=amuleto di pietra
+14087=spada da guerra di pietra
+14088=Grezzo Senza finestre
+14089=Finestra grezza
+14090=Arco grezzo
+14091=Pilastro grezzo
+14092=Arco arrotondato grezzo
+14093=Arco piccolo grezzo
+14094=Pilastro angolare grezzo
+14095=Corto grezzo
+14096=Porta in pietra (Ingresso S)
+14097=Porta in pietra (Uscita E)
+14098=Porta metallica sinistra (Ingresso S)
+14099=Porta metallica destra (Ingresso S)
+14100=Corto grezzo
+14101=Gradini grezzi
+14102=Gradini angolari grezzi
+14103=Gradino angolare arrotondato grezzo
+14104=Gradini con inserto grezzo
+14105=Gradini con inserto arrotondato grezzo
+14106=Pavimentazione chiara
+14107=Pavimentazione media
+14108=Pavimentazione scura
// 15000 AbilitÃ
15000=Alchimia
15001=Anatomia
diff --git a/data/dictionaries/dictionary.POL b/data/dictionaries/dictionary.POL
index 74b21e9a8..15de62301 100644
--- a/data/dictionaries/dictionary.POL
+++ b/data/dictionaries/dictionary.POL
@@ -5112,6 +5112,85 @@
13609=puste fiolki
13610=pełne fiolki
13611=wirujÄ…ca klepsydra
+// 14001 - 14500 Umiejętność rzemiosła murarskiego
+14001=Menu murarskie
+14002=Dekoracje
+14003=Meble
+14004=PosÄ…gi
+14005=Różne dodatki
+14006=Kamienna zbroja
+14007=Kamienna broń
+14008=Kamienne ściany
+14009=Kamienne schody
+14010=Kamienne podłogi
+14011=Granit
+14012=Matowy granit miedziany
+14013=Granit z cienistego żelaza
+14014=Granit miedziany
+14015=BrÄ…zowy granit
+14016=Złoty granit
+14017=Granit agapitowy
+14018=Granit verite
+14019=Wartość Granity
+14050=wazon
+14051=duży wazon
+14052=mała urna
+14053=rzeźba wieży
+14054=obraz gargulca
+14055=rzeźba gargulca
+14056=wazon gargulca
+14057=wysoki wazon na 18. rocznicÄ™
+14058=niski wazon na 18. rocznicÄ™
+14059=kamienne krzesło
+14060=kamienny stół (wschód)
+14061=kamienny stół (południe)
+14062=duży kamienny stół (wschód)
+14063=duży kamienny stół (południe)
+14064=stół rytualny
+14065=mała statua (południe)
+14066=mała statua (północ)
+14067=mała statua (wschód)
+14068=pegaz Statuetki
+14069=posągi gargulców
+14070=posÄ…g gryfa
+14071=kamienne kowadło (wschód)
+14072=kamienne kowadło (południe)
+14073=duże łoże gargish (wschód)
+14074=duże łoże gargish (południe)
+14075=łoże gargish (wschód)
+14076=łoże gargish (południe)
+14077=kamienne ramiona gargish
+14078=skrzynia gargish
+14079=kamienne nagolenniki gargish
+14080=kamienny kilt gargish
+14081=kamienne ramiona gargish
+14082=kamienna skrzynia gargish
+14083=kamienne nagolenniki gargish
+14084=kamienny kilt gargish
+14085=duża kamienna tarcza
+14086=kamień gargish Amulet
+14087=Kamienny miecz wojenny
+14088=Surowy bez okien
+14089=Surowy okno
+14090=Surowy łuk
+14091=Surowy filar
+14092=Surowy łuk zaokrąglony
+14093=Surowy mały łuk
+14094=Surowy filar kÄ…towy
+14095=Krótki surowy
+14096=Kamienne drzwi (południowe wejście)
+14097=Kamienne drzwi (wschodnie wyjście)
+14098=Lewe metalowe drzwi (południowe wejście)
+14099=Prawe metalowe drzwi (południowe wejście)
+14100=Krótki surowy
+14101=Surowe stopnie
+14102=Surowe stopnie narożne
+14103=Surowy stopień z zaokrąglonym narożnikiem
+14104=Surowe stopnie z wstawkami
+14105=Surowe stopnie z zaokrÄ…glonymi wstawkami
+14106=Jasny strach
+14107=średnia kostka brukowa
+14108=ciemny strach
// 15000 Umiejętności
15000=Alchemia
15001=Anatomia
diff --git a/data/dictionaries/dictionary.PTG b/data/dictionaries/dictionary.PTG
index 8b9aabf8a..41c66bb07 100644
--- a/data/dictionaries/dictionary.PTG
+++ b/data/dictionaries/dictionary.PTG
@@ -5112,6 +5112,85 @@
13609=frascos vazios
13610=frascos cheios
13611=ampulheta girando
+// 14001 - 14500 Habilidade em Alvenaria
+14001=Menu de Alvenaria
+14002=Decorações
+14003=Móveis
+14004=Estátuas
+14005=Acessórios Diversos
+14006=Armadura de Pedra
+14007=Armas de Pedra
+14008=Paredes de Pedra
+14009=Escadas de Pedra
+14010=Pisos de Pedra
+14011=Granito
+14012=Granito Cobre Mate
+14013=Granito Ferro Sombrio
+14014=Granito Cobre
+14015=Granito Bronze
+14016=Granito Ouro
+14017=Granito Agapita
+14018=Granito Verita
+14019=Valor Granitos
+14050=vaso
+14051=vaso grande
+14052=urna pequena
+14053=Escultura de Torre
+14054=pintura de gárgula
+14055=escultura de gárgula
+14056=vaso de gárgula
+14057=Vaso Alto do 18º Aniversário
+14058=Vaso Baixo do 18º Aniversário
+14059=cadeira de pedra
+14060=mesa de pedra (nascente)
+14061=mesa em pedra (sul)
+14062=mesa de pedra grande (nascente)
+14063=mesa de pedra grande (sul)
+14064=mesa ritual
+14065=estátua pequena (sul)
+14066=estátua pequena (norte)
+14067=estátua pequena (leste)
+14068=pégaso estatuetas
+14069=estátuas de gárgula
+14070=estátua de grifo
+14071=bigorna de pedra (leste)
+14072=bigorna de pedra (sul)
+14073=cama grande de gárgula (nascente)
+14074=cama grande de gárgulas (sul)
+14075=berço de gárgula (nascente)
+14076=berço de gárgula (sul)
+14077=braços de pedra de gárgula
+14078=baú em pedra de gárgula
+14079=calças de pedra de gárgula
+14080=saia de pedra de gárgula
+14081=braços de pedra de gárgula
+14082=baú em pedra de gárgula
+14083=calças de pedra de gárgula
+14084=saia de pedra de gárgula
+14085=escudo de pedra grande
+14086=gárgula Amuleto de pedra
+14087=Espada de guerra de pedra
+14088=Sem janela rústica
+14089=Janela rústica
+14090=Arco rústico
+14091=Pilar rústico
+14092=Arco arredondado rústico
+14093=Arco pequeno rústico
+14094=Pilar angular rústico
+14095=Rouco curto
+14096=Porta em pedra (entrada sul)
+14097=Porta de pedra (saÃda nascente)
+14098=Porta metálica esquerda (entrada sul)
+14099=Porta metálica direita (entrada sul)
+14100=Rouco curto
+14101=Degraus rústicos
+14102=Degraus de canto rústicos
+14103=Degrau de canto arredondado rústico
+14104=Degraus embutidos rústicos
+14105=Degraus embutidos arredondados rústicos
+14106=Luz medo
+14107=pavimento médio
+14108=medo escuro
// 15000 Competências
15000=Alchemia
15001=Anatomia
diff --git a/data/dictionaries/dictionary.SPA b/data/dictionaries/dictionary.SPA
index 3d415c99b..523c8f960 100644
--- a/data/dictionaries/dictionary.SPA
+++ b/data/dictionaries/dictionary.SPA
@@ -5112,6 +5112,85 @@
13609=frascos vacÃos
13610=frascos llenos
13611=reloj de arena giratorio
+// 14001 - 14500 Habilidad de ArtesanÃa en AlbañilerÃa
+14001=Menú de AlbañilerÃa
+14002=Decoraciones
+14003=Mobiliario
+14004=Estatuas
+14005=Complementos Varios
+14006=Armadura de Piedra
+14007=Armas de Piedra
+14008=Muros de Piedra
+14009=Escaleras de Piedra
+14010=Suelos de Piedra
+14011=Granito
+14012=Granito de Cobre Mate
+14013=Granito de Hierro SombrÃo
+14014=Granito de Cobre
+14015=Granito de Bronce
+14016=Granito de Oro
+14017=Granito de Agapita
+14018=Granito de Verita
+14019=Valor Granitos
+14050=jarrón
+14051=jarrón grande
+14052=urna pequeña
+14053=escultura de torre
+14054=pintura de gárgola
+14055=escultura de gárgola
+14056=jarrón de gárgola
+14057=jarrón alto del 18.º aniversario
+14058=jarrón bajo del 18.º aniversario
+14059=silla de piedra
+14060=mesa de piedra (este)
+14061=mesa de piedra (sur)
+14062=mesa de piedra grande (este)
+14063=mesa de piedra grande (sur)
+14064=mesa ritual
+14065=estatua pequeña (sur)
+14066=estatua pequeña (norte)
+14067=estatua pequeña (este)
+14068=estatuillas de pegaso
+14069=gárgola Estatuas
+14070=Estatua de grifo
+14071=Yunque de piedra (este)
+14072=Yunque de piedra (sur)
+14073=Cama grande de gárgola (este)
+14074=Cama grande de gárgola (sur)
+14075=Cuna de gárgola (este)
+14076=Cuna de gárgola (sur)
+14077=Brazos de piedra de gárgola
+14078=Cofre de piedra de gárgola
+14079=Polainas de piedra de gárgola
+14080=Falda de piedra de gárgola
+14081=Brazos de piedra de gárgola
+14082=Cofre de piedra de gárgola
+14083=Polainas de piedra de gárgola
+14084=Falda de piedra de gárgola
+14085=Escudo grande de piedra
+14086=Amuleto de piedra de gárgola
+14087=Espada de guerra de piedra
+14088=Búsqueda Sin ventanas
+14089=Ventana rústica
+14090=Arco rústico
+14091=Pilar rústico
+14092=Arco redondeado rústico
+14093=Arco pequeño rústico
+14094=Pilar angular rústico
+14095=Rústico corto
+14096=Puerta de piedra (Sin entrada)
+14097=Puerta de piedra (E sin salida)
+14098=Puerta metálica izquierda (Sin entrada)
+14099=Puerta metálica derecha (Sin entrada)
+14100=Rústico corto
+14101=Escalones rústicos
+14102=Escalones de esquina rústicos
+14103=Escalón de esquina redondeado rústico
+14104=Escalones empotrados rústicos
+14105=Escalones empotrados redondeados rústicos
+14106=Piso claro
+14107=AdoquÃn mediano
+14108=Piso oscuro
// 15000 Habilidades
15000=Alquimia
15001=AnatomÃa
diff --git a/data/dictionaries/dictionary.ZRO b/data/dictionaries/dictionary.ZRO
index eadedcad9..ba73354b1 100644
--- a/data/dictionaries/dictionary.ZRO
+++ b/data/dictionaries/dictionary.ZRO
@@ -4705,6 +4705,12 @@
10296=You have no knowledge on how to unravel that.
10297=You unravel the item and place 5 resources in your pack.
20298=You aren't skilled enough to unravel this.
+20299=RED SCALES
+20300=YELLOW SCALES
+20301=BLACK SCALES
+20302=GREEN SCALES
+20303=WHITE SCALES
+20304=BLUE SCALES
// [10600-10900] Carpentry Crafting Skill
10600=CARPENTRY MENU
10601=Other
@@ -5111,6 +5117,85 @@
13609=empty vials
13610=full vials
13611=spinning hourglass
+// 14001 - 14500 Masonary Crafting Skill
+14001=Masonry Menu
+14002=Decorations
+14003=Furniture
+14004=Statues
+14005=Misc Addons
+14006=Stone Armor
+14007=Stone Weapons
+14008=Stone Walls
+14009=Stone Stairs
+14010=Stone Floors
+14011=Granite
+14012=Dull Copper Granite
+14013=Shadow Iron Granite
+14014=Copper Granite
+14015=Bronze Granite
+14016=Gold Granite
+14017=Agapite Granite
+14018=Verite Granite
+14019=Valorite Granite
+14050=vase
+14051=large vase
+14052=small urn
+14053=Tower Sculpture
+14054=gargoyle painting
+14055=gargoyle sculpture
+14056=gargoyle vase
+14057=Tall 18th Anniversary Vase
+14058=Short 18th Anniversary Vase
+14059=stone chair
+14060=stone table (east)
+14061=stone table (south)
+14062=large stone table (east)
+14063=large stone table (south)
+14064=ritual table
+14065=small statue (south)
+14066=small statue (north)
+14067=small statue (east)
+14068=pegasus statuette
+14069=gargoyle statue
+14070=gryphon statue
+14071=stone anvil (east)
+14072=stone anvil (south)
+14073=large gargish bed (east)
+14074=large gargish bed (south)
+14075=gargish cot (east)
+14076=gargish cot (south)
+14077=gargish stone arms
+14078=gargish stone chest
+14079=gargish stone leggings
+14080=gargish stone kilt
+14081=gargish stone arms
+14082=gargish stone chest
+14083=gargish stone leggings
+14084=gargish stone kilt
+14085=large stone shield
+14086=gargish stone amulet
+14087=stone war sword
+14088=Rough Windowless
+14089=Rough Window
+14090=Rough Arch
+14091=Rough Pillar
+14092=Rough Rounded Arch
+14093=Rough Small Arch
+14094=Rough Angled Pillar
+14095=Short Rough
+14096=Stone Door (S In)
+14097=Stone Door (E Out)
+14098=Left Metal Door (S In)
+14099=Right Metal Door (S In)
+14100=short rough
+14101=rough steps
+14102=rough corner steps
+14103=rough rounded corner step
+14104=rough inset steps
+14105=rough rounded inset steps
+14106=light paver
+14107=medium paver
+14108=dark paver
// 15000 Skills
15000=Alchemy
15001=Anatomy
diff --git a/data/js/item/buildingcraftables.js b/data/js/item/buildingcraftables.js
new file mode 100644
index 000000000..53e2cd690
--- /dev/null
+++ b/data/js/item/buildingcraftables.js
@@ -0,0 +1,181 @@
+///
+// @ts-check
+/** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */
+function onCreateDFN( iCreated, dfnSection )
+{
+ setLockedState( iCreated, false );
+ iCreated.SetTag( "BuildingCraftable", 1 );
+}
+
+/** @type { ( user: Character, iUsing: Item ) => boolean } */
+function onUseChecked( pUser, iUsed )
+{
+ var pSocket = pUser.socket;
+
+ if( iUsed.container !== null )
+ {
+ if( iUsed.container === pUser.pack )
+ {
+ if (pSocket)
+ pSocket.SysMessage( "Place the wall in your house, then double-click it to lock it down." );
+ }
+ else
+ {
+ if( pSocket )
+ pSocket.SysMessage( "You must first place this wall in the world." );
+ }
+ return false;
+ }
+
+ if( !isInOwnHouse( pUser ))
+ {
+ if( pSocket )
+ {
+ pSocket.SysMessage( GetDictionaryEntry( 2067, pSocket.language )); // You must be in your house to do this.
+ }
+ return false;
+ }
+
+ var iMulti = iUsed.multi;
+ if( !ValidateObject( iMulti ) || iMulti !== pUser.multi )
+ {
+ if( pSocket )
+ pSocket.SysMessage( "This crafted wall must be inside your house to lock it down." );
+ return false;
+ }
+
+ var locked = iUsed.GetTag("CraftWallLocked");
+
+ if( locked == 1 )
+ {
+ setLockedState( iUsed, false );
+ if( pSocket )
+ pSocket.SysMessage("You unlock the crafted wall. It can now be moved and will decay normally.");
+ }
+ else
+ {
+ setLockedState( iUsed, true );
+ if( pSocket )
+ pSocket.SysMessage("You lock the crafted wall in place. It will no longer decay.");
+ }
+
+ return false;
+}
+
+/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */
+function onTooltip( myObj, pSocket )
+{
+ var locked = myObj.GetTag( "CraftWallLocked" );
+
+ if( locked == 1 )
+ {
+ // Line 1: House Only
+ // Line 2: unlock hint
+ return "House Only You must double click this to unlock it.";
+ }
+ else
+ {
+ // Line 1: House Only
+ // Line 2: lock hint
+ return "House Only You must double click this to lock it down.";
+ }
+}
+
+function onDropItemOnNpc( pDropper, pDroppedOn, iDropped )
+{
+ var targPack = pDroppedOn.pack;
+ if( ValidateObject( targPack ) && pDroppedOn.sectionID == "packhorse" )
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+function onDropItemOnItem( iDropped, cDropper, iDroppedOn )
+{
+ if( !ValidateObject( iDropped ) || !ValidateObject( cDropper ) || !ValidateObject( iDroppedOn ))
+ return 0;
+
+ var isBuilding = iDropped.GetTag( "BuildingCraftable" ) === 1;
+ if( !isBuilding )
+ return 0
+
+ var owner = iDroppedOn.container;
+ if( ValidateObject( owner ) && owner.isChar && owner.sectionID == "packhorse" )
+ {
+ var sock = cDropper.socket;
+ if( sock )
+ sock.SysMessage( "You cannot place building pieces on a pack animal." );
+
+ return 0;
+ }
+
+ return 1;
+}
+
+function onDrop( iDropped, pDropper )
+{
+ if( !ValidateObject( iDropped ) || !ValidateObject( pDropper ))
+ return 0;
+
+ var isBuilding = iDropped.GetTag( "BuildingCraftable" ) === 1;
+ if( !isBuilding )
+ return 0;
+
+ var socket = pDropper.socket;
+ if( !isInOwnHouse( pDropper ))
+ {
+ if( socket != null)
+ socket.SysMessage( "The building piece crumbles when dropped on the ground." );
+
+ iDropped.Delete();
+ return 2;
+ }
+
+ return 1;
+}
+
+
+function isInOwnHouse( pUser )
+{
+ if( !ValidateObject( pUser ))
+ return false;
+
+ var iMulti = pUser.multi;
+ if( !ValidateObject( iMulti ))
+ return false;
+
+ // Owner / co-owner / whatever IsOnOwnerList covers
+ if( iMulti.IsOnOwnerList( pUser ))
+ return true;
+
+ // Optional: co-owned houses on same account
+ if( GetServerSetting( "COOWNHOUSESONSAMEACCOUNT" ))
+ {
+ if( ValidateObject( iMulti.owner ) && iMulti.owner.accountNum === pUser.accountNum )
+ return true;
+ }
+
+ return false;
+}
+
+function setLockedState( item, locked )
+{
+ if( !ValidateObject( item ))
+ return;
+
+ if( locked )
+ {
+ item.SetTag( "CraftWallLocked", 1 );
+ item.movable = 2; // locked down / secure
+ item.decayable = false; // no decay
+ }
+ else
+ {
+ item.SetTag( "CraftWallLocked", 0 );
+ item.movable = 1; // movable
+ item.decayable = true; // normal decay
+ }
+ item.Refresh();
+}
\ No newline at end of file
diff --git a/data/js/item/buildingcraftables_doors.js b/data/js/item/buildingcraftables_doors.js
new file mode 100644
index 000000000..c55a77da4
--- /dev/null
+++ b/data/js/item/buildingcraftables_doors.js
@@ -0,0 +1,91 @@
+///
+// @ts-check
+/** @type { ( thingCreated: BaseObject, thingType: 0 | 1 ) => void } */
+function onCreateDFN( iCreated, dfnSection )
+{
+ iCreated.SetTag( "BuildingCraftableDoor", 1 );
+}
+
+/** @type { ( myObj: BaseObject, pSocket: Socket ) => string } */
+function onTooltip( myObj, pSocket )
+{
+ return "House Only";
+}
+
+function onDropItemOnNpc( pDropper, pDroppedOn, iDropped )
+{
+ var targPack = pDroppedOn.pack;
+ if( ValidateObject( targPack ) && pDroppedOn.sectionID == "packhorse" )
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+function onDropItemOnItem( iDropped, cDropper, iDroppedOn )
+{
+ if( !ValidateObject( iDropped ) || !ValidateObject( cDropper ) || !ValidateObject( iDroppedOn ))
+ return 0;
+
+ var isBuilding = iDropped.GetTag( "BuildingCraftableDoor" ) === 1;
+ if( !isBuilding )
+ return 0
+
+ var owner = iDroppedOn.container;
+ if( ValidateObject( owner ) && owner.isChar && owner.sectionID == "packhorse" )
+ {
+ var sock = cDropper.socket;
+ if( sock )
+ sock.SysMessage( "You cannot place building pieces on a pack animal." );
+
+ return 0;
+ }
+
+ return 1;
+}
+
+function onDrop( iDropped, pDropper )
+{
+ if( !ValidateObject( iDropped ) || !ValidateObject( pDropper ))
+ return 0;
+
+ var isBuilding = iDropped.GetTag( "BuildingCraftableDoor" ) === 1;
+ if( !isBuilding )
+ return 0;
+
+ var socket = pDropper.socket;
+ if( !isInOwnHouse( pDropper ))
+ {
+ if( socket != null)
+ socket.SysMessage( "The building piece crumbles when dropped on the ground." );
+
+ iDropped.Delete();
+ return 2;
+ }
+
+ return 1;
+}
+
+function isInOwnHouse( pUser )
+{
+ if( !ValidateObject( pUser ))
+ return false;
+
+ var iMulti = pUser.multi;
+ if( !ValidateObject( iMulti ))
+ return false;
+
+ // Owner / co-owner / whatever IsOnOwnerList covers
+ if( iMulti.IsOnOwnerList( pUser ))
+ return true;
+
+ // Optional: co-owned houses on same account
+ if( GetServerSetting( "COOWNHOUSESONSAMEACCOUNT" ))
+ {
+ if( ValidateObject( iMulti.owner ) && iMulti.owner.accountNum === pUser.accountNum )
+ return true;
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/data/js/item/glassblowingbook.js b/data/js/item/glassblowingbook.js
index acf70e220..57afa8322 100644
--- a/data/js/item/glassblowingbook.js
+++ b/data/js/item/glassblowingbook.js
@@ -14,20 +14,20 @@ function onUseChecked( pUser, iUsed )
}
else if( pUser.skills[0] < 1000 )
{
- socket.SysMessage( GetDictionaryEntry( 6301, socket.Language ) ); // Only a Grandmaster Alchemist can learn from this book.
+ socket.SysMessage( GetDictionaryEntry( 6301, socket.language )); // Only a Grandmaster Alchemist can learn from this book.
}
else if( pUser.GetTag( "GlassBlowing" ) == 1 )
{
- socket.SysMessage( GetDictionaryEntry( 6302, socket.Language ) ); // You have already learned this information.
+ socket.SysMessage( GetDictionaryEntry( 6302, socket.language )); // You have already learned this information.
}
else if( iUsed.movable == 2 || iUsed.movable == 3 )
{
- socket.SysMessage( GetDictionaryEntry( 774, socket.Language )); //That is locked down and you cannot use it
+ socket.SysMessage( GetDictionaryEntry( 774, socket.language )); //That is locked down and you cannot use it
}
else
{
pUser.SetTag( "GlassBlowing", 1 );
- socket.SysMessage( GetDictionaryEntry( 6303, socket.Language )); // You have learned to mine for stones. Target mountains when mining to find stones.
+ socket.SysMessage( GetDictionaryEntry( 6303, socket.language )); // You have learned to make items from glass. You will need to find miners to mine fine
iUsed.Delete();
}
}
diff --git a/data/js/item/interiordecorator.js b/data/js/item/interiordecorator.js
index a3161e947..a2dae4819 100644
--- a/data/js/item/interiordecorator.js
+++ b/data/js/item/interiordecorator.js
@@ -30,6 +30,7 @@ function onUseChecked( pUser, iUsed )
function interiorDecoratorGump( pUser )
{
+ var socket = pUser.socket;
var interiorGump = new Gump;
interiorGump.AddPage( 0 );
interiorGump.AddBackground( 0, 0, 200, 200, 2600 );
@@ -39,7 +40,7 @@ function interiorDecoratorGump( pUser )
interiorGump.AddXMFHTMLGump( 90, 100, 70, 40, 1018324, false, false );//up
interiorGump.AddButton( 50, 145, 0x868, 1, 0, 3 );
interiorGump.AddXMFHTMLGump( 90, 150, 70, 40, 1018325, false, false );//down
- interiorGump.Send( pUser );
+ interiorGump.Send( socket );
interiorGump.Free();
return false;
}
@@ -84,10 +85,18 @@ function onGumpPress( socket, pButton, gumpData )
/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
function onCallback1( socket, ourObj )
{
+ if( !ValidateObject( ourObj ))
+ {
+ return;
+ }
+
+ var isBuilding = ourObj.GetTag( "BuildingCraftable" ) === 1;
+ var isWallLocked = ourObj.GetTag( "CraftWallLocked" ) === 1;
var pUser = socket.currentChar;
- if( !ValidateObject( ourObj ) || !ourObj.isItem || ourObj.movable != 3 )
+ if( !ourObj.isItem || ( isBuilding && !isWallLocked ) || ( !isBuilding && ourObj.movable != 3 ))
{
socket.SysMessage( GetDictionaryEntry( 2072, socket.language )); // You can only use the interior decorator on locked down items.
+ socket.CustomTarget( 1 );
return;
}
@@ -126,10 +135,18 @@ function onCallback1( socket, ourObj )
/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
function onCallback2( socket, ourObj )
{
+ if( !ValidateObject( ourObj ))
+ {
+ return;
+ }
+
+ var isBuilding = ourObj.GetTag( "BuildingCraftable" ) === 1;
+ var isWallLocked = ourObj.GetTag( "CraftWallLocked" ) === 1;
var pUser = socket.currentChar;
- if( !ValidateObject( ourObj ) || !ourObj.isItem || ourObj.movable != 3 )
+ if( !ourObj.isItem || ( isBuilding && !isWallLocked ) || ( !isBuilding && ourObj.movable != 3 ))
{
socket.SysMessage( GetDictionaryEntry( 2072, socket.language )); // You can only use the interior decorator on locked down items.
+ socket.CustomTarget( 2 );
return;
}
@@ -168,10 +185,18 @@ function onCallback2( socket, ourObj )
/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
function onCallback3( socket, ourObj )
{
+ if( !ValidateObject( ourObj ))
+ {
+ return;
+ }
+
+ var isBuilding = ourObj.GetTag( "BuildingCraftable" ) === 1;
+ var isWallLocked = ourObj.GetTag( "CraftWallLocked" ) === 1;
var pUser = socket.currentChar;
- if( !ValidateObject( ourObj ) || !ourObj.isItem || ourObj.movable != 3 )
+ if( !ourObj.isItem || ( isBuilding && !isWallLocked ) || ( !isBuilding && ourObj.movable != 3 ))
{
socket.SysMessage( GetDictionaryEntry( 2072, socket.language )); // You can only use the interior decorator on locked down items.
+ socket.CustomTarget( 3 );
return;
}
diff --git a/data/js/item/masonrybook.js b/data/js/item/masonrybook.js
new file mode 100644
index 000000000..f4b664f3f
--- /dev/null
+++ b/data/js/item/masonrybook.js
@@ -0,0 +1,35 @@
+///
+// @ts-check
+/** @type { ( user: Character, iUsing: Item ) => boolean } */
+function onUseChecked( pUser, iUsed )
+{
+ var socket = pUser.socket;
+ var itemOwner = GetPackOwner( iUsed, 0 );
+
+ if( socket && iUsed && iUsed.isItem )
+ {
+ if( itemOwner == null || itemOwner.serial != pUser.serial )
+ {
+ socket.SysMessage( GetDictionaryEntry( 1763, socket.language )); // That item must be in your backpack before it can be used.
+ }
+ else if( pUser.skills[0] < 1000 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6298, socket.language )); // Only a Grandmaster Carpenter can learn from this book.
+ }
+ else if( pUser.GetTag( "StoneCrafting" ) == 1 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6302, socket.language )); // You have already learned this information.
+ }
+ else if( iUsed.movable == 2 || iUsed.movable == 3 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 774, socket.language )); //That is locked down and you cannot use it
+ }
+ else
+ {
+ pUser.SetTag( "StoneCrafting", 1 );
+ socket.SysMessage( GetDictionaryEntry( 6299, socket.language )); // You have learned to make items from stone. You will need miners to gather stones for you to make these items.
+ iUsed.Delete();
+ }
+ }
+ return false;
+}
diff --git a/data/js/item/sandminingbook.js b/data/js/item/sandminingbook.js
index 89f05f69a..7fa887671 100644
--- a/data/js/item/sandminingbook.js
+++ b/data/js/item/sandminingbook.js
@@ -22,7 +22,7 @@ function onUseChecked( pUser, iUsed )
}
else if( iUsed.movable == 2 || iUsed.movable == 3 )
{
- pSocket.SysMessage( GetDictionaryEntry( 774, pSocket.Language )); //That is locked down and you cannot use it
+ pSocket.SysMessage( GetDictionaryEntry( 774, pSocket.language )); //That is locked down and you cannot use it
}
else
{
diff --git a/data/js/item/stoneminingbook.js b/data/js/item/stoneminingbook.js
index 60cbec328..1b9408b6d 100644
--- a/data/js/item/stoneminingbook.js
+++ b/data/js/item/stoneminingbook.js
@@ -22,12 +22,12 @@ function onUseChecked( pUser, iUsed )
}
else if( iUsed.movable == 2 || iUsed.movable == 3 )
{
- pSocket.SysMessage( GetDictionaryEntry( 774, pSocket.Language )); //That is locked down and you cannot use it
+ pSocket.SysMessage( GetDictionaryEntry( 774, pSocket.language )); //That is locked down and you cannot use it
}
else
{
pUser.SetTag( "GatheringStone", 1 );
- pSocket.SysMessage( GetDictionaryEntry( 9411, pSocket.Language )); // You have learned to mine for stones. Target mountains when mining to find stones.
+ pSocket.SysMessage( GetDictionaryEntry( 9411, pSocket.language )); // You have learned to mine for stones. Target mountains when mining to find stones.
iUsed.Delete();
}
}
diff --git a/data/js/jsdata/crafting/alchemy.json b/data/js/jsdata/crafting/alchemy.json
new file mode 100644
index 000000000..800b02cc5
--- /dev/null
+++ b/data/js/jsdata/crafting/alchemy.json
@@ -0,0 +1,22 @@
+[
+ { "makeID": 298, "label": "Lesser Heal", "dictID": 10910, "page": 1, "timerID": 1, "harvest": [10022], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 299, "label": "Heal", "dictID": 10911, "page": 1, "timerID": 1, "harvest": [10022], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 300, "label": "Greater Heal", "dictID": 10912, "page": 1, "timerID": 1, "harvest": [10022], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 305, "label": "Refresh", "dictID": 10908, "page": 1, "timerID": 1, "harvest": [10025], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 306, "label": "Total Refresh", "dictID": 10909, "page": 1, "timerID": 1, "harvest": [10025], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 292, "label": "Lesser Cure", "dictID": 10913, "page": 1, "timerID": 1, "harvest": [10020], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 293, "label": "Cure", "dictID": 10914, "page": 1, "timerID": 1, "harvest": [10020], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 294, "label": "Greater Cure", "dictID": 10915, "page": 1, "timerID": 1, "harvest": [10020], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 290, "label": "Agility", "dictID": 10916, "page": 2, "timerID": 2, "harvest": [10019], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 291, "label": "Greater Agility", "dictID": 10917, "page": 2, "timerID": 2, "harvest": [10019], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 295, "label": "Strength", "dictID": 10918, "page": 2, "timerID": 2, "harvest": [10021], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 296, "label": "Greater Strength", "dictID": 10919, "page": 2, "timerID": 2, "harvest": [10021], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 297, "label": "Night Sight", "dictID": 10920, "page": 2, "timerID": 2, "harvest": [10021], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 301, "label": "Lesser Poison", "dictID": 10921, "page": 3, "timerID": 3, "harvest": [10024], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 302, "label": "Poison", "dictID": 10922, "page": 3, "timerID": 3, "harvest": [10024], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 303, "label": "Greater Poison", "dictID": 10923, "page": 3, "timerID": 3, "harvest": [10024], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 304, "label": "Deadly Poison", "dictID": 10924, "page": 3, "timerID": 3, "harvest": [10024], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 307, "label": "Explosion", "dictID": 10925, "page": 4, "timerID": 4, "harvest": [10026], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 308, "label": "Greater Explosion", "dictID": 10926, "page": 4, "timerID": 4, "harvest": [10026], "craftComplete": { "type": "autoIdentifyPotion" } },
+ { "makeID": 309, "label": "Conflagration", "dictID": 10927, "page": 4, "timerID": 4, "harvest": [10023], "craftComplete": { "type": "autoIdentifyPotion" } }
+]
diff --git a/data/js/jsdata/crafting/artcraft.json b/data/js/jsdata/crafting/artcraft.json
new file mode 100644
index 000000000..7de867713
--- /dev/null
+++ b/data/js/jsdata/crafting/artcraft.json
@@ -0,0 +1,18 @@
+[
+ {
+ "makeID": 6000,
+ "label": "Charcoal Sketch",
+ "customName": "charcoal sketch",
+ "page": 1,
+ "timerID": 1,
+ "harvest": [ 10016 ]
+ },
+ {
+ "makeID": 6001,
+ "label": "Canvas Painting",
+ "customName": "canvas painting",
+ "page": 2,
+ "timerID": 2,
+ "harvest": [ 10016, 10015 ]
+ }
+]
diff --git a/data/js/jsdata/crafting/blacksmithing.json b/data/js/jsdata/crafting/blacksmithing.json
new file mode 100644
index 000000000..859f5dadd
--- /dev/null
+++ b/data/js/jsdata/crafting/blacksmithing.json
@@ -0,0 +1,56 @@
+[
+ { "buttonID": 100, "label": "Ringmail Gloves", "dictID": 10217, "page": 1, "timerID": 1, "oreMake": [7, 506, 606, 706, 806, 906, 1206, 1006, 1106], "harvest": [10015] },
+ { "buttonID": 101, "label": "Ringmail Leggings", "dictID": 10218, "page": 1, "timerID": 1, "oreMake": [9, 508, 608, 708, 808, 908, 1208, 1008, 1108], "harvest": [10015] },
+ { "buttonID": 102, "label": "Ringmail Sleeves", "dictID": 10219, "page": 1, "timerID": 1, "oreMake": [8, 507, 607, 707, 807, 907, 1207, 1007, 1107], "harvest": [10015] },
+ { "buttonID": 103, "label": "Ringmail Tunic", "dictID": 10220, "page": 1, "timerID": 1, "oreMake": [10, 509, 609, 709, 809, 909, 1209, 1009, 1109], "harvest": [10015] },
+ { "buttonID": 104, "label": "Chainmail Coif", "dictID": 10221, "page": 1, "timerID": 1, "oreMake": [11, 510, 610, 710, 810, 910, 1210, 1010, 1110], "harvest": [10015] },
+ { "buttonID": 105, "label": "Chainmail Leggings", "dictID": 10222, "page": 1, "timerID": 1, "oreMake": [12, 511, 611, 711, 811, 911, 1211, 1011, 1111], "harvest": [10015] },
+ { "buttonID": 106, "label": "Chainmail Tunic", "dictID": 10223, "page": 1, "timerID": 1, "oreMake": [13, 512, 612, 7012, 812, 912, 1212, 1012, 1112], "harvest": [10015] },
+ { "buttonID": 107, "label": "Platemail Arms", "dictID": 10224, "page": 1, "timerID": 1, "oreMake": [16, 515, 615, 715, 815, 915, 1215, 1015, 1115], "harvest": [10015] },
+ { "buttonID": 108, "label": "Platemail Gloves", "dictID": 10225, "page": 1, "timerID": 1, "oreMake": [15, 514, 614, 714, 814, 914, 1214, 1014, 1114], "harvest": [10015] },
+ { "buttonID": 109, "label": "Platemail Gorget", "dictID": 10226, "page": 1, "timerID": 1, "oreMake": [14, 513, 613, 713, 813, 913, 1213, 1013, 1113], "harvest": [10015] },
+ { "buttonID": 110, "label": "Platemail Legs", "dictID": 10227, "page": 1, "timerID": 1, "oreMake": [17, 516, 616, 716, 816, 916, 1216, 1016, 1116], "harvest": [10015] },
+ { "buttonID": 111, "label": "Platemail Tunic", "dictID": 10228, "page": 1, "timerID": 1, "oreMake": [18, 517, 617, 717, 817, 917, 1217, 1017, 1117], "harvest": [10015] },
+ { "buttonID": 112, "label": "Female Plate", "dictID": 10229, "page": 1, "timerID": 1, "oreMake": [19, 518, 618, 718, 818, 918, 1218, 1018, 1118], "harvest": [10015] },
+ { "buttonID": 113, "label": "Dragon Sleeves", "customName": "Dragon Sleeves", "page": 1, "timerID": 1, "oreMake": [367, 0, 0, 0, 0, 0, 0, 0, 0], "harvest": [10015], "useScales": true },
+ { "buttonID": 114, "label": "Dragon Breast Plate", "customName": "Dragon Breast Plate", "page": 1, "timerID": 1, "oreMake": [368, 0, 0, 0, 0, 0, 0, 0, 0], "harvest": [10015], "useScales": true },
+ { "buttonID": 115, "label": "Dragon Gloves", "customName": "Dragon Gloves", "page": 1, "timerID": 1, "oreMake": [369, 0, 0, 0, 0, 0, 0, 0, 0], "harvest": [10015], "useScales": true },
+ { "buttonID": 116, "label": "Dragon Helmet", "customName": "Dragon Helmet", "page": 1, "timerID": 1, "oreMake": [370, 0, 0, 0, 0, 0, 0, 0, 0], "harvest": [10015], "useScales": true },
+ { "buttonID": 117, "label": "Dragon Leggings", "customName": "Dragon leggings", "page": 1, "timerID": 1, "oreMake": [371, 0, 0, 0, 0, 0, 0, 0, 0], "harvest": [10015], "useScales": true },
+ { "buttonID": 200, "label": "Bascinet", "dictID": 10230, "page": 2, "timerID": 2, "oreMake": [46, 520, 620, 720, 820, 920, 1220, 1020, 1120], "harvest": [10015] },
+ { "buttonID": 201, "label": "Close Helmet", "dictID": 10231, "page": 2, "timerID": 2, "oreMake": [48, 522, 622, 722, 822, 922, 1222, 1022, 1122], "harvest": [10015] },
+ { "buttonID": 202, "label": "Helmet", "dictID": 10232, "page": 2, "timerID": 2, "oreMake": [45, 519, 619, 719, 819, 919, 1219, 1019, 1119], "harvest": [10015] },
+ { "buttonID": 203, "label": "Norse Helm", "dictID": 10233, "page": 2, "timerID": 2, "oreMake": [47, 521, 621, 721, 821, 921, 1221, 1021, 1121], "harvest": [10015] },
+ { "buttonID": 204, "label": "Plate Helm", "dictID": 10234, "page": 2, "timerID": 2, "oreMake": [49, 523, 623, 723, 823, 923, 1223, 1023, 1123], "harvest": [10015] },
+ { "buttonID": 300, "label": "Buckler", "dictID": 10235, "page": 3, "timerID": 3, "oreMake": [1, 500, 600, 700, 800, 900, 1200, 1000, 1100], "harvest": [10015] },
+ { "buttonID": 301, "label": "Bronze Shield", "dictID": 10236, "page": 3, "timerID": 3, "oreMake": [2, 501, 601, 701, 801, 901, 1201, 1001, 1101], "harvest": [10015] },
+ { "buttonID": 302, "label": "Heater Shield", "dictID": 10237, "page": 3, "timerID": 3, "oreMake": [6, 505, 605, 705, 805, 905, 1205, 1005, 1105], "harvest": [10015] },
+ { "buttonID": 303, "label": "Metal Shield", "dictID": 10238, "page": 3, "timerID": 3, "oreMake": [3, 502, 602, 702, 802, 902, 1202, 1002, 1102], "harvest": [10015] },
+ { "buttonID": 304, "label": "Metal Kite Shield", "dictID": 10239, "page": 3, "timerID": 3, "oreMake": [5, 504, 604, 704, 804, 904, 1204, 1004, 1104], "harvest": [10015] },
+ { "buttonID": 305, "label": "Wooden Kite Shield", "dictID": 10293, "page": 3, "timerID": 3, "oreMake": [4, 503, 603, 703, 803, 903, 1203, 1003, 1103], "harvest": [10015] },
+ { "buttonID": 400, "label": "Broadsword", "dictID": 10240, "page": 4, "timerID": 4, "oreMake": [25, 25, 25, 25, 25, 25, 25, 25, 25], "harvest": [10015] },
+ { "buttonID": 401, "label": "Cutlass", "dictID": 10241, "page": 4, "timerID": 4, "oreMake": [21, 21, 21, 21, 21, 21, 21, 21, 21], "harvest": [10015] },
+ { "buttonID": 402, "label": "Dagger", "dictID": 10242, "page": 4, "timerID": 4, "oreMake": [20, 20, 20, 20, 20, 20, 20, 20, 20], "harvest": [10015] },
+ { "buttonID": 403, "label": "Katana", "dictID": 10243, "page": 4, "timerID": 4, "oreMake": [22, 22, 22, 22, 22, 22, 22, 22, 22], "harvest": [10015] },
+ { "buttonID": 404, "label": "Kryss", "dictID": 10244, "page": 4, "timerID": 4, "oreMake": [23, 23, 23, 23, 23, 23, 23, 23, 23], "harvest": [10015] },
+ { "buttonID": 405, "label": "Longsword", "dictID": 10245, "page": 4, "timerID": 4, "oreMake": [26, 26, 26, 26, 26, 26, 26, 26, 26], "harvest": [10015] },
+ { "buttonID": 406, "label": "Scimitar", "dictID": 10246, "page": 4, "timerID": 4, "oreMake": [24, 24, 24, 24, 24, 24, 24, 24, 24], "harvest": [10015] },
+ { "buttonID": 407, "label": "Viking Sword", "dictID": 10247, "page": 4, "timerID": 4, "oreMake": [27, 27, 27, 27, 27, 27, 27, 27, 27], "harvest": [10015] },
+ { "buttonID": 500, "label": "Axe", "dictID": 10248, "page": 5, "timerID": 5, "oreMake": [29, 29, 29, 29, 29, 29, 29, 29, 29], "harvest": [10015] },
+ { "buttonID": 501, "label": "Battle Axe", "dictID": 10249, "page": 5, "timerID": 5, "oreMake": [28, 28, 28, 28, 28, 28, 28, 28, 28], "harvest": [10015] },
+ { "buttonID": 502, "label": "Double Axe", "dictID": 10250, "page": 5, "timerID": 5, "oreMake": [32, 32, 32, 32, 32, 32, 32, 32, 32], "harvest": [10015] },
+ { "buttonID": 503, "label": "Executioner's Axe", "dictID": 10251, "page": 5, "timerID": 5, "oreMake": [30, 30, 30, 30, 30, 30, 30, 30, 30], "harvest": [10015] },
+ { "buttonID": 504, "label": "Large Battle Axe", "dictID": 10252, "page": 5, "timerID": 5, "oreMake": [33, 33, 33, 33, 33, 33, 33, 33, 33], "harvest": [10015] },
+ { "buttonID": 505, "label": "Two Handed Axe", "dictID": 10253, "page": 5, "timerID": 5, "oreMake": [31, 31, 31, 31, 31, 31, 31, 31, 31], "harvest": [10015] },
+ { "buttonID": 506, "label": "War Axe", "dictID": 10254, "page": 5, "timerID": 5, "oreMake": [34, 34, 34, 34, 34, 34, 34, 34, 34], "harvest": [10015] },
+ { "buttonID": 600, "label": "Bardiche", "dictID": 10255, "page": 6, "timerID": 6, "oreMake": [38, 38, 38, 38, 38, 38, 38, 38, 38], "harvest": [10015] },
+ { "buttonID": 601, "label": "Halberd", "dictID": 10256, "page": 6, "timerID": 6, "oreMake": [39, 39, 39, 39, 39, 39, 39, 39, 39], "harvest": [10015] },
+ { "buttonID": 602, "label": "Short Spear", "dictID": 10257, "page": 6, "timerID": 6, "oreMake": [35, 35, 35, 35, 35, 35, 35, 35, 35], "harvest": [10015] },
+ { "buttonID": 603, "label": "Spear", "dictID": 10258, "page": 6, "timerID": 6, "oreMake": [36, 36, 36, 36, 36, 36, 36, 36, 36], "harvest": [10015] },
+ { "buttonID": 604, "label": "War Fork", "dictID": 10259, "page": 6, "timerID": 6, "oreMake": [37, 37, 37, 37, 37, 37, 37, 37, 37], "harvest": [10015] },
+ { "buttonID": 700, "label": "Hammer Pick", "dictID": 10260, "page": 7, "timerID": 7, "oreMake": [44, 44, 44, 44, 44, 44, 44, 44, 44], "harvest": [10015] },
+ { "buttonID": 701, "label": "Mace", "dictID": 10261, "page": 7, "timerID": 7, "oreMake": [40, 40, 40, 40, 40, 40, 40, 40, 40], "harvest": [10015] },
+ { "buttonID": 702, "label": "Maul", "dictID": 10262, "page": 7, "timerID": 7, "oreMake": [41, 41, 41, 41, 41, 41, 41, 41, 41], "harvest": [10015] },
+ { "buttonID": 703, "label": "War Hammer", "dictID": 10263, "page": 7, "timerID": 7, "oreMake": [42, 42, 42, 42, 42, 42, 42, 42, 42], "harvest": [10015] },
+ { "buttonID": 704, "label": "War Mace", "dictID": 10264, "page": 7, "timerID": 7, "oreMake": [43, 43, 43, 43, 43, 43, 43, 43, 43], "harvest": [10015] }
+]
diff --git a/data/js/jsdata/crafting/carpentry.json b/data/js/jsdata/crafting/carpentry.json
new file mode 100644
index 000000000..10a2dc0a4
--- /dev/null
+++ b/data/js/jsdata/crafting/carpentry.json
@@ -0,0 +1,79 @@
+[
+ { "buttonID": 100, "label": "Barrel Staves", "dictID": 10611, "page": 1, "timerID": 1, "makeID": 73, "harvest": [10014] },
+ { "buttonID": 101, "label": "Barrel Lid", "dictID": 10612, "page": 1, "timerID": 1, "makeID": 74, "harvest": [10014] },
+ { "buttonID": 102, "label": "Short Music Stand", "dictID": 10613, "page": 1, "timerID": 1, "makeID": 89, "harvest": [10014] },
+ { "buttonID": 103, "label": "Tall Music Stand", "dictID": 10614, "page": 1, "timerID": 1, "makeID": 90, "harvest": [10014] },
+ { "buttonID": 104, "label": "Easel South", "dictID": 10615, "page": 1, "timerID": 1, "makeID": 91, "harvest": [10014] },
+ { "buttonID": 105, "label": "Easel East", "dictID": 10616, "page": 1, "timerID": 1, "makeID": 92, "harvest": [10014] },
+ { "buttonID": 106, "label": "Bamboo Flute", "dictID": 10617, "page": 1, "timerID": 1, "makeID": 76, "harvest": [10014] },
+ { "buttonID": 107, "label": "Log", "dictID": 10618, "page": 1, "timerID": 1, "makeID": 77, "harvest": [10014] },
+ { "buttonID": 108, "label": "Board", "dictID": 10619, "page": 1, "timerID": 1, "makeID": 78, "harvest": [10014] },
+ { "buttonID": 109, "label": "Blank Scroll", "dictID": 10620, "page": 1, "timerID": 1, "makeID": 79, "harvest": [10014, 10016] },
+ { "buttonID": 110, "label": "Fishing Pole", "dictID": 10688, "page": 1, "timerID": 1, "makeID": 72, "harvest": [10014] },
+ { "buttonID": 200, "label": "Stool", "dictID": 10621, "page": 2, "timerID": 2, "makeID": 50, "harvest": [10014] },
+ { "buttonID": 201, "label": "Foot Stool", "dictID": 10622, "page": 2, "timerID": 2, "makeID": 51, "harvest": [10014] },
+ { "buttonID": 202, "label": "Straw Chair", "dictID": 10623, "page": 2, "timerID": 2, "makeID": 52, "harvest": [10014] },
+ { "buttonID": 203, "label": "Wooden Chair", "dictID": 10624, "page": 2, "timerID": 2, "makeID": 53, "harvest": [10014] },
+ { "buttonID": 204, "label": "Vesper-Style Chair", "dictID": 10625, "page": 2, "timerID": 2, "makeID": 57, "harvest": [10014] },
+ { "buttonID": 205, "label": "Trinsic-Style Chair", "dictID": 10626, "page": 2, "timerID": 2, "makeID": 58, "harvest": [10014] },
+ { "buttonID": 206, "label": "Wooden Bench", "dictID": 10627, "page": 2, "timerID": 2, "makeID": 54, "harvest": [10014] },
+ { "buttonID": 207, "label": "Wooden Throne", "dictID": 10628, "page": 2, "timerID": 2, "makeID": 55, "harvest": [10014] },
+ { "buttonID": 208, "label": "Magincia-Style Throne", "dictID": 10629, "page": 2, "timerID": 2, "makeID": 56, "harvest": [10014] },
+ { "buttonID": 209, "label": "Small Table", "dictID": 10630, "page": 2, "timerID": 2, "makeID": 59, "harvest": [10014] },
+ { "buttonID": 210, "label": "Writing Table", "dictID": 10631, "page": 2, "timerID": 2, "makeID": 60, "harvest": [10014] },
+ { "buttonID": 211, "label": "Large Table", "dictID": 10632, "page": 2, "timerID": 2, "makeID": 61, "harvest": [10014] },
+ { "buttonID": 212, "label": "Yew-Wood Table", "dictID": 10633, "page": 2, "timerID": 2, "makeID": 62, "harvest": [10014] },
+ { "buttonID": 300, "label": "Wooden Box", "dictID": 10634, "page": 3, "timerID": 3, "makeID": 63, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 301, "label": "Small Crate", "dictID": 10635, "page": 3, "timerID": 3, "makeID": 64, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 302, "label": "Medium Crate", "dictID": 10636, "page": 3, "timerID": 3, "makeID": 65, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 303, "label": "Large Crate", "dictID": 10637, "page": 3, "timerID": 3, "makeID": 67, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 304, "label": "Wooden Chest", "dictID": 10638, "page": 3, "timerID": 3, "makeID": 68, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 305, "label": "Wooden Shelf", "dictID": 10639, "page": 3, "timerID": 3, "makeID": 69, "harvest": [10014] },
+ { "buttonID": 306, "label": "Armoire", "dictID": 10640, "page": 3, "timerID": 3, "makeID": 70, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 307, "label": "Armoire", "dictID": 10641, "page": 3, "timerID": 3, "makeID": 71, "harvest": [10014], "craftComplete": { "type": "lockableContainer" } },
+ { "buttonID": 308, "label": "Open Keg", "dictID": 10642, "page": 3, "timerID": 3, "makeID": 66, "harvest": [10611, 10612, 11860] },
+ { "buttonID": 400, "label": "Shepherd's Crook", "dictID": 10643, "page": 4, "timerID": 4, "makeID": 80, "harvest": [10014] },
+ { "buttonID": 401, "label": "Quarter Staff", "dictID": 10644, "page": 4, "timerID": 4, "makeID": 81, "harvest": [10014] },
+ { "buttonID": 402, "label": "Gnarled Staff", "dictID": 10645, "page": 4, "timerID": 4, "makeID": 82, "harvest": [10014] },
+ { "buttonID": 403, "label": "Wooden Shield", "dictID": 10646, "page": 4, "timerID": 4, "makeID": 123, "harvest": [10014] },
+ { "buttonID": 404, "label": "Club", "dictID": 10647, "page": 4, "timerID": 4, "makeID": 124, "harvest": [10014] },
+ { "buttonID": 500, "label": "Wooden Shield", "dictID": 10648, "page": 5, "timerID": 5, "makeID": 75, "harvest": [10014] },
+ { "buttonID": 600, "label": "Lap Harp", "dictID": 10649, "page": 6, "timerID": 6, "makeID": 83, "harvest": [10014, 10016] },
+ { "buttonID": 601, "label": "Standing Harp", "dictID": 10650, "page": 6, "timerID": 6, "makeID": 84, "harvest": [10014, 10016] },
+ { "buttonID": 602, "label": "Drum", "dictID": 10651, "page": 6, "timerID": 6, "makeID": 85, "harvest": [10014, 10016] },
+ { "buttonID": 603, "label": "Lute", "dictID": 10652, "page": 6, "timerID": 6, "makeID": 86, "harvest": [10014, 10016] },
+ { "buttonID": 604, "label": "Tambourine", "dictID": 10653, "page": 6, "timerID": 6, "makeID": 87, "harvest": [10014, 10016] },
+ { "buttonID": 605, "label": "Tambourine", "dictID": 10654, "page": 6, "timerID": 6, "makeID": 88, "harvest": [10014, 10016] },
+ { "buttonID": 700, "label": "Small Bed South", "dictID": 10655, "page": 7, "timerID": 7, "makeID": 93, "harvest": [10014, 10016] },
+ { "buttonID": 701, "label": "Small Bed East", "dictID": 10656, "page": 7, "timerID": 7, "makeID": 93, "harvest": [10014, 10016] },
+ { "buttonID": 702, "label": "Large Bed South", "dictID": 10657, "page": 7, "timerID": 7, "makeID": 94, "harvest": [10014, 10016] },
+ { "buttonID": 703, "label": "Large Bed East", "dictID": 10658, "page": 7, "timerID": 7, "makeID": 93, "harvest": [10014, 10016] },
+ { "buttonID": 704, "label": "Training Dummy South", "dictID": 10659, "page": 7, "timerID": 7, "makeID": 96, "harvest": [10014] },
+ { "buttonID": 705, "label": "Training Dummy East", "dictID": 10660, "page": 7, "timerID": 7, "makeID": 95, "harvest": [10014] },
+ { "buttonID": 706, "label": "Pickpocket Dip South", "dictID": 10661, "page": 7, "timerID": 7, "makeID": 97, "harvest": [10014] },
+ { "buttonID": 707, "label": "Pickpocket Dip East", "dictID": 10662, "page": 7, "timerID": 7, "makeID": 98, "harvest": [10014] },
+ { "buttonID": 708, "label": "Bulletin Board", "dictID": 10663, "page": 7, "timerID": 7, "makeID": 99, "harvest": [10014] },
+ { "buttonID": 709, "label": "Pentagram", "dictID": 10664, "page": 7, "timerID": 7, "makeID": 100, "harvest": [10014, 10015] },
+ { "buttonID": 710, "label": "Abbatoir", "dictID": 10665, "page": 7, "timerID": 7, "makeID": 101, "harvest": [10014, 10015] },
+ { "buttonID": 800, "label": "Dressform Front", "dictID": 10666, "page": 8, "timerID": 8, "makeID": 115, "harvest": [10014, 10016] },
+ { "buttonID": 801, "label": "Dressform Side", "dictID": 10667, "page": 8, "timerID": 8, "makeID": 116, "harvest": [10014, 10016] },
+ { "buttonID": 802, "label": "Spinning Wheel East", "dictID": 10668, "page": 8, "timerID": 8, "makeID": 107, "harvest": [10014, 10016] },
+ { "buttonID": 803, "label": "Spinning Wheel South", "dictID": 10669, "page": 8, "timerID": 8, "makeID": 108, "harvest": [10014, 10016] },
+ { "buttonID": 804, "label": "Loom East", "dictID": 10670, "page": 8, "timerID": 8, "makeID": 109, "harvest": [10014, 10016] },
+ { "buttonID": 805, "label": "Loom South", "dictID": 10671, "page": 8, "timerID": 8, "makeID": 110, "harvest": [10014, 10016] },
+ { "buttonID": 806, "label": "Stone Oven East", "dictID": 10672, "page": 8, "timerID": 8, "makeID": 117, "harvest": [10014, 10015] },
+ { "buttonID": 807, "label": "Stone Oven South", "dictID": 10673, "page": 8, "timerID": 8, "makeID": 118, "harvest": [10014, 10015] },
+ { "buttonID": 808, "label": "Flour Mill East", "dictID": 10674, "page": 8, "timerID": 8, "makeID": 119, "harvest": [10014, 10015] },
+ { "buttonID": 809, "label": "Flour Mill South", "dictID": 10675, "page": 8, "timerID": 8, "makeID": 120, "harvest": [10014, 10015] },
+ { "buttonID": 810, "label": "Water Trough East", "dictID": 10676, "page": 8, "timerID": 8, "makeID": 121, "harvest": [10014] },
+ { "buttonID": 811, "label": "Water Trough South", "dictID": 10677, "page": 8, "timerID": 8, "makeID": 122, "harvest": [10014] },
+ { "buttonID": 900, "label": "Small Forge", "dictID": 10678, "page": 9, "timerID": 9, "makeID": 102, "harvest": [10014, 10015] },
+ { "buttonID": 901, "label": "Large Forge East", "dictID": 10679, "page": 9, "timerID": 9, "makeID": 103, "harvest": [10014, 10015] },
+ { "buttonID": 902, "label": "Large Forge South", "dictID": 10680, "page": 9, "timerID": 9, "makeID": 104, "harvest": [10014, 10015] },
+ { "buttonID": 903, "label": "Anvil East", "dictID": 10681, "page": 9, "timerID": 9, "makeID": 105, "harvest": [10014, 10015] },
+ { "buttonID": 904, "label": "Anvil South", "dictID": 10682, "page": 9, "timerID": 9, "makeID": 106, "harvest": [10014, 10015] },
+ { "buttonID": 1000, "label": "Training Dummy East", "dictID": 10683, "page": 10, "timerID": 10, "makeID": 111, "harvest": [10014, 10016] },
+ { "buttonID": 1001, "label": "Training Dummy South", "dictID": 10684, "page": 10, "timerID": 10, "makeID": 112, "harvest": [10014, 10016] },
+ { "buttonID": 1002, "label": "Pickpocket Dip East", "dictID": 10685, "page": 10, "timerID": 10, "makeID": 113, "harvest": [10014, 10016] },
+ { "buttonID": 1003, "label": "Pickpocket Dip South", "dictID": 10686, "page": 10, "timerID": 10, "makeID": 114, "harvest": [10014, 10016] }
+]
diff --git a/data/js/jsdata/crafting/cartography.json b/data/js/jsdata/crafting/cartography.json
new file mode 100644
index 000000000..942206628
--- /dev/null
+++ b/data/js/jsdata/crafting/cartography.json
@@ -0,0 +1,6 @@
+[
+ { "makeID": 2000, "label": "Local Map", "dictID": 13100, "page": 1, "timerID": 1, "harvest": [13004] },
+ { "makeID": 2001, "label": "City Map", "dictID": 13101, "page": 1, "timerID": 1, "harvest": [13004] },
+ { "makeID": 2002, "label": "Sea Chart", "dictID": 13102, "page": 1, "timerID": 1, "harvest": [13004] },
+ { "makeID": 2003, "label": "World Map", "dictID": 13103, "page": 1, "timerID": 1, "harvest": [13004] }
+]
diff --git a/data/js/jsdata/crafting/cooking.json b/data/js/jsdata/crafting/cooking.json
new file mode 100644
index 000000000..7ea76fd90
--- /dev/null
+++ b/data/js/jsdata/crafting/cooking.json
@@ -0,0 +1,33 @@
+[
+ { "makeID": 1500, "label": "Sack of Flour", "dictID": 11606, "page": 1, "timerID": 1, "harvest": [11636] },
+ { "makeID": 1501, "label": "Dough", "dictID": 11607, "page": 1, "timerID": 1, "harvest": [11637, 11638] },
+ { "makeID": 1502, "label": "Sweet Dough", "dictID": 11608, "page": 1, "timerID": 1, "harvest": [11607, 11639] },
+ { "makeID": 1503, "label": "Cake Mix", "dictID": 11609, "page": 1, "timerID": 1, "harvest": [11637, 11608] },
+ { "makeID": 1504, "label": "Cookie Mix", "dictID": 11610, "page": 1, "timerID": 1, "harvest": [11639, 11608] },
+ { "makeID": 1550, "label": "Unbaked Quiche", "dictID": 11611, "page": 2, "timerID": 2, "harvest": [11607, 11640] },
+ { "makeID": 1551, "label": "Unbaked Meat Pie", "dictID": 11612, "page": 2, "timerID": 2, "harvest": [11607, 11641] },
+ { "makeID": 1552, "label": "Uncooked Sausage Pizza", "dictID": 11613, "page": 2, "timerID": 2, "harvest": [11607, 11642] },
+ { "makeID": 1553, "label": "Uncooked Cheese Pizza", "dictID": 11614, "page": 2, "timerID": 2, "harvest": [11607, 11643] },
+ { "makeID": 1554, "label": "Unbaked Fruit Pie", "dictID": 11615, "page": 2, "timerID": 2, "harvest": [11607, 11644] },
+ { "makeID": 1555, "label": "Unbaked Peach Cobbler", "dictID": 11616, "page": 2, "timerID": 2, "harvest": [11607, 11645] },
+ { "makeID": 1556, "label": "Unbaked Apple Pie", "dictID": 11617, "page": 2, "timerID": 2, "harvest": [11607, 11646] },
+ { "makeID": 1557, "label": "Unbaked Pumpkin Pie", "dictID": 11618, "page": 2, "timerID": 2, "harvest": [11607, 11647] },
+ { "makeID": 1600, "label": "Bread Loaf", "dictID": 11619, "page": 3, "timerID": 3, "harvest": [11607] },
+ { "makeID": 1601, "label": "Pan of Cookies", "dictID": 11620, "page": 3, "timerID": 3, "harvest": [11610] },
+ { "makeID": 1602, "label": "Cake", "dictID": 11621, "page": 3, "timerID": 3, "harvest": [11609] },
+ { "makeID": 1603, "label": "Muffins", "dictID": 11657, "page": 3, "timerID": 3, "harvest": [11608] },
+ { "makeID": 1604, "label": "Baked Quiche", "dictID": 11622, "page": 3, "timerID": 3, "harvest": [11611] },
+ { "makeID": 1605, "label": "Baked Meat Pie", "dictID": 11623, "page": 3, "timerID": 3, "harvest": [11612] },
+ { "makeID": 1606, "label": "Sausage Pizza", "dictID": 11624, "page": 3, "timerID": 3, "harvest": [11613] },
+ { "makeID": 1607, "label": "Cheese Pizza", "dictID": 11625, "page": 3, "timerID": 3, "harvest": [11614] },
+ { "makeID": 1608, "label": "Baked Fruit Pie", "dictID": 11626, "page": 3, "timerID": 3, "harvest": [11615] },
+ { "makeID": 1609, "label": "Baked Peach Cobbler", "dictID": 11627, "page": 3, "timerID": 3, "harvest": [11616] },
+ { "makeID": 1610, "label": "Baked Apple Pie", "dictID": 11628, "page": 3, "timerID": 3, "harvest": [11617] },
+ { "makeID": 1611, "label": "Baked Pumpkin Pie", "dictID": 11629, "page": 3, "timerID": 3, "harvest": [11618] },
+ { "makeID": 1650, "label": "Cooked Bird", "dictID": 11630, "page": 4, "timerID": 4, "harvest": [11648] },
+ { "makeID": 1651, "label": "Chicken Leg", "dictID": 11631, "page": 4, "timerID": 4, "harvest": [11649] },
+ { "makeID": 1652, "label": "Fish Steak", "dictID": 11632, "page": 4, "timerID": 4, "harvest": [11650] },
+ { "makeID": 1653, "label": "Fried Eggs", "dictID": 11633, "page": 4, "timerID": 4, "harvest": [11651] },
+ { "makeID": 1654, "label": "Leg of Lamb", "dictID": 11634, "page": 4, "timerID": 4, "harvest": [11652] },
+ { "makeID": 1655, "label": "Cut of Ribs", "dictID": 11635, "page": 4, "timerID": 4, "harvest": [11653] }
+]
diff --git a/data/js/jsdata/crafting/crafttools.json b/data/js/jsdata/crafting/crafttools.json
new file mode 100644
index 000000000..1b15fa2b1
--- /dev/null
+++ b/data/js/jsdata/crafting/crafttools.json
@@ -0,0 +1,103 @@
+[
+ {
+ "craft": "carpentry",
+ "craftIndex": 1,
+ "scriptID": 4025,
+ "legacyScriptID": 4006,
+ "maxPage": 10,
+ "toolIDs": [ 4134, 4135, 4136, 4137, 4140, 4141, 4142, 4143, 4144, 4145, 4146, 4147, 4324, 4325, 4326 ]
+ },
+ {
+ "craft": "alchemy",
+ "craftIndex": 2,
+ "scriptID": 4028,
+ "legacyScriptID": 4007,
+ "maxPage": 4,
+ "toolIDs": [ 3739 ]
+ },
+ {
+ "craft": "fletching",
+ "craftIndex": 3,
+ "scriptID": 4029,
+ "legacyScriptID": 4005,
+ "maxPage": 3,
+ "toolIDs": [ 4130, 7121, 7124 ]
+ },
+ {
+ "craft": "tailoring",
+ "craftIndex": 4,
+ "scriptID": 4030,
+ "legacyScriptID": 4004,
+ "maxPage": 8,
+ "toolIDs": [ 3997 ]
+ },
+ {
+ "craft": "blacksmithing",
+ "craftIndex": 5,
+ "scriptID": 4023,
+ "legacyReturnTrue": true,
+ "maxPage": 7,
+ "specialPages": [
+ {
+ "page": 8,
+ "function": "Page8"
+ }
+ ],
+ "toolIDs": [ 4027, 4028, 5091, 5092 ]
+ },
+ {
+ "craft": "cooking",
+ "craftIndex": 6,
+ "scriptID": 4034,
+ "legacyScriptID": 104,
+ "maxPage": 4,
+ "toolIDs": [ 4163, 2431, 2530, 4158 ]
+ },
+ {
+ "craft": "tinkering",
+ "craftIndex": 7,
+ "scriptID": 4032,
+ "legacyScriptID": 4003,
+ "maxPage": 9,
+ "sectionIDs": [ "tinkerstools" ],
+ "toolIDs": [ 7864, 7865, 7866, 7867, 7868 ]
+ },
+ {
+ "craft": "cartography",
+ "craftIndex": 8,
+ "scriptID": 4035,
+ "maxPage": 1,
+ "sectionIDs": [ "mapmakerspen" ]
+ },
+ {
+ "craft": "glassblowing",
+ "craftIndex": 9,
+ "scriptID": 4036,
+ "maxPage": 1,
+ "sectionIDs": [ "blowpipe" ],
+ "requiredTag": "GlassBlowing",
+ "requiredTagMessage": 6300
+ },
+ {
+ "craft": "masonry",
+ "craftIndex": 10,
+ "scriptID": 4037,
+ "maxPage": 9,
+ "sectionIDs": [ "malletandchisel" ],
+ "requiredTag": "StoneCrafting",
+ "requiredTagMessage": 6297,
+ "specialPages": [
+ {
+ "page": 20,
+ "function": "Page20"
+ }
+ ]
+ },
+ {
+ "craft": "artcraft",
+ "craftIndex": 100,
+ "scriptID": 4040,
+ "maxPage": 2,
+ "sectionIDs": [ "paintbrush" ]
+ }
+]
\ No newline at end of file
diff --git a/data/js/jsdata/crafting/customcrafts.json b/data/js/jsdata/crafting/customcrafts.json
new file mode 100644
index 000000000..7b1a6ed15
--- /dev/null
+++ b/data/js/jsdata/crafting/customcrafts.json
@@ -0,0 +1,44 @@
+[
+ {
+ "craft": "artcraft",
+ "mapFile": "artcraft",
+ "skillID": 0,
+ "maxPage": 2,
+ "menuName": "Art Craft",
+ "showLastTen": true,
+
+ "allowRepair": true,
+ "repairText": "Restore Artwork",
+ "repairTargetText": "Select the artwork you want to restore.",
+ "repairSuccessText": "You carefully restore the artwork.",
+ "repairFailedText": "You fail to restore the artwork.",
+ "repairFullText": "That artwork does not need restoration.",
+ "repairWrongMaterialText": "That is not artwork you can restore.",
+ "repairSkillName": "itemid",
+ "repairMinSkill": 250,
+ "repairMaterialType": "cloth",
+ "repairSound": 42,
+
+ "allowRecycle": true,
+ "recycleText": "Recover Materials",
+ "recycleTargetText": "Select the artwork you want to recover materials from.",
+ "recycleSuccessText": "You recover usable materials from the artwork.",
+ "recycleWrongMaterialText": "You cannot recover materials from that.",
+ "recycleMaterialType": "cloth",
+ "recycleResourceID": "0x0EED",
+ "recycleResourceHue": 0,
+ "recycleResourceAmount": 1,
+ "recycleResourceName": "reclaimed art supplies",
+
+ "categories": [
+ {
+ "page": 1,
+ "label": "Drawings"
+ },
+ {
+ "page": 2,
+ "label": "Paintings"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/data/js/jsdata/crafting/fletching.json b/data/js/jsdata/crafting/fletching.json
new file mode 100644
index 000000000..4dbab290b
--- /dev/null
+++ b/data/js/jsdata/crafting/fletching.json
@@ -0,0 +1,18 @@
+[
+ { "makeID": 190, "label": "Kindling", "dictID": 11205, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 194, "label": "Shaft", "dictID": 11206, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 195, "label": "Five Shafts", "dictID": 11207, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 196, "label": "Twenty Shafts", "dictID": 11208, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 197, "label": "Fifty Shafts", "dictID": 11209, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 198, "label": "Arrow", "dictID": 11210, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 199, "label": "Five Arrows", "dictID": 11211, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 200, "label": "Twenty Arrows", "dictID": 11212, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 201, "label": "Fifty Arrows", "dictID": 11213, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 202, "label": "Bolt", "dictID": 11214, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 203, "label": "Five Bolts", "dictID": 11215, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 204, "label": "Twenty Bolts", "dictID": 11216, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 205, "label": "Fifty Bolts", "dictID": 11217, "page": 2, "timerID": 2, "harvest": [10029, 10028] },
+ { "makeID": 191, "label": "Bow", "dictID": 11218, "page": 3, "timerID": 3, "harvest": [10014] },
+ { "makeID": 192, "label": "Crossbow", "dictID": 11219, "page": 3, "timerID": 3, "harvest": [10014] },
+ { "makeID": 193, "label": "Heavy Crossbow", "dictID": 11220, "page": 3, "timerID": 3, "harvest": [10014] }
+]
diff --git a/data/js/jsdata/crafting/glassblowing.json b/data/js/jsdata/crafting/glassblowing.json
new file mode 100644
index 000000000..8cbc00648
--- /dev/null
+++ b/data/js/jsdata/crafting/glassblowing.json
@@ -0,0 +1,22 @@
+[
+ { "makeID": 3000, "label": "empty bottle", "dictID": 13600, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3001, "label": "flask (small)", "dictID": 13601, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3002, "label": "flask (medium)", "dictID": 13602, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3003, "label": "flask (curved)", "dictID": 13603, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3004, "label": "flask (large #1)", "dictID": 13604, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3005, "label": "flask (large #2)", "dictID": 13605, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3006, "label": "flask (bubbling blue)", "dictID": 13606, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3007, "label": "flask (bubbling purple)", "dictID": 13607, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3008, "label": "flask (bubbling red)", "dictID": 13608, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3009, "label": "empty vials", "dictID": 13609, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3010, "label": "full vials", "dictID": 13610, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3011, "label": "spinning hourglass", "dictID": 13611, "page": 1, "timerID": 1, "harvest": [13504] },
+ { "makeID": 3012, "label": "hollow prism", "customName": "hollow prism", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "ml" },
+ { "makeID": 3013, "label": "gargoyle floor mirror", "customName": "gargoyle floor mirror", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3014, "label": "gargoyle wall mirror", "customName": "gargoyle wall mirror", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3015, "label": "empty venom vial", "customName": "empty venom vial", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3016, "label": "empty oil flask", "customName": "empty oil flask", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3017, "label": "workable glass", "customName": "workable glass", "page": 1, "timerID": 1, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3018, "label": "glass sword", "customName": "glass sword", "page": 2, "timerID": 2, "harvest": [13504], "minEra": "sa" },
+ { "makeID": 3019, "label": "glass staff", "customName": "glass staff", "page": 2, "timerID": 2, "harvest": [13504], "minEra": "sa" }
+]
diff --git a/data/js/jsdata/crafting/masonry.json b/data/js/jsdata/crafting/masonry.json
new file mode 100644
index 000000000..f38ef8237
--- /dev/null
+++ b/data/js/jsdata/crafting/masonry.json
@@ -0,0 +1,61 @@
+[
+ { "buttonID": 100, "label": "Small Vase", "dictID": 14050, "page": 1, "timerID": 1, "graniteMake": [3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300], "harvest": [14011] },
+ { "buttonID": 101, "label": "Large Vase", "dictID": 14051, "page": 1, "timerID": 1, "graniteMake": [3501, 3601, 3701, 3801, 3901, 4001, 4101, 4201, 4301], "harvest": [14011] },
+ { "buttonID": 102, "label": "Small Urn", "dictID": 14052, "page": 1, "timerID": 1, "graniteMake": [3502, 3602, 3702, 3802, 3902, 4002, 4102, 4202, 4302], "harvest": [14011], "minEra": "se" },
+ { "buttonID": 103, "label": "Tower Sculpture", "dictID": 14053, "page": 1, "timerID": 1, "graniteMake": [3503, 3603, 3703, 3803, 3903, 4003, 4103, 4203, 4303], "harvest": [14011], "minEra": "se" },
+ { "buttonID": 104, "label": "Gargoyle Painting", "dictID": 14054, "page": 1, "timerID": 1, "graniteMake": [3504, 3604, 3704, 3804, 3904, 4004, 4104, 4204, 4304], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 105, "label": "Gargoyle Sculpture", "dictID": 14055, "page": 1, "timerID": 1, "graniteMake": [3505, 3605, 3705, 3805, 3905, 4005, 4105, 4205, 4305], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 106, "label": "Gargoyle Vase", "dictID": 14056, "page": 1, "timerID": 1, "graniteMake": [3506, 3606, 3706, 3806, 3906, 4006, 4106, 4206, 4306], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 107, "label": "Tall 18th Anniversary Vase", "dictID": 14057, "page": 1, "timerID": 1, "graniteMake": [3507, 3607, 3707, 3807, 3907, 4007, 4107, 4207, 4307], "harvest": [14011], "recipeID": 3500, "minEra": "tol" },
+ { "buttonID": 108, "label": "Short 18th Anniversary Vase", "dictID": 14058, "page": 1, "timerID": 1, "graniteMake": [3508, 3608, 3708, 3808, 3908, 4008, 4108, 4208, 4308], "harvest": [14011], "recipeID": 3501, "minEra": "tol" },
+ { "buttonID": 200, "label": "Stone Chair", "dictID": 14059, "page": 2, "timerID": 2, "graniteMake": [3509, 3609, 3709, 3809, 3909, 4009, 4109, 4209, 4309], "harvest": [14011] },
+ { "buttonID": 201, "label": "Medium Stone Table", "dictID": 14060, "page": 2, "timerID": 2, "graniteMake": [3510, 3610, 3710, 3810, 3910, 4010, 4110, 4210, 4310], "harvest": [14011] },
+ { "buttonID": 202, "label": "Large Stone Table", "dictID": 14061, "page": 2, "timerID": 2, "graniteMake": [3511, 3611, 3711, 3811, 3911, 4011, 4111, 4211, 4311], "harvest": [14011] },
+ { "buttonID": 203, "label": "Stone Throne", "dictID": 14062, "page": 2, "timerID": 2, "graniteMake": [3512, 3612, 3712, 3812, 3912, 4012, 4112, 4212, 4312], "harvest": [14011] },
+ { "buttonID": 204, "label": "Stone Bench", "dictID": 14063, "page": 2, "timerID": 2, "graniteMake": [3513, 3613, 3713, 3813, 3913, 4013, 4113, 4213, 4313], "harvest": [14011] },
+ { "buttonID": 205, "label": "Ritual Table", "dictID": 14064, "page": 2, "timerID": 2, "graniteMake": [3514, 3614, 3714, 3814, 3914, 4014, 4114, 4214, 4314], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 300, "label": "Statue", "dictID": 14065, "page": 3, "timerID": 3, "graniteMake": [3515, 3615, 3715, 3815, 3915, 4015, 4115, 4215, 4315], "harvest": [14011] },
+ { "buttonID": 301, "label": "Statue", "dictID": 14066, "page": 3, "timerID": 3, "graniteMake": [3516, 3616, 3716, 3816, 3916, 4016, 4116, 4216, 4316], "harvest": [14011] },
+ { "buttonID": 302, "label": "Statue", "dictID": 14067, "page": 3, "timerID": 3, "graniteMake": [3517, 3617, 3717, 3817, 3917, 4017, 4117, 4217, 4317], "harvest": [14011] },
+ { "buttonID": 303, "label": "Statue", "dictID": 14068, "page": 3, "timerID": 3, "graniteMake": [3518, 3618, 3718, 3818, 3918, 4018, 4118, 4218, 4318], "harvest": [14011] },
+ { "buttonID": 304, "label": "Gargoyle Statue", "dictID": 14069, "page": 3, "timerID": 3, "graniteMake": [3519, 3619, 3719, 3819, 3919, 4019, 4119, 4219, 4319], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 305, "label": "Gryphon Statue", "dictID": 14070, "page": 3, "timerID": 3, "graniteMake": [3520, 3620, 3720, 3820, 3920, 4020, 4120, 4220, 4320], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 400, "label": "Stone Anvil East", "dictID": 14071, "page": 4, "timerID": 4, "graniteMake": [3521, 3621, 3721, 3821, 3921, 4021, 4121, 4221, 4321], "harvest": [14011], "recipeID": 3520, "minEra": "ml" },
+ { "buttonID": 401, "label": "Stone Anvil South", "dictID": 14072, "page": 4, "timerID": 4, "graniteMake": [3522, 3622, 3722, 3822, 3922, 4022, 4122, 4222, 4322], "harvest": [14011], "recipeID": 3521, "minEra": "ml" },
+ { "buttonID": 402, "label": "Large Gargish Bed East", "dictID": 14073, "page": 4, "timerID": 4, "graniteMake": [3523, 3623, 3723, 3823, 3923, 4023, 4123, 4223, 4323], "harvest": [14011, 10016], "minEra": "sa" },
+ { "buttonID": 403, "label": "Large Gargish Bed South", "dictID": 14074, "page": 4, "timerID": 4, "graniteMake": [3524, 3624, 3724, 3824, 3924, 4024, 4124, 4224, 4324], "harvest": [14011, 10016], "minEra": "sa" },
+ { "buttonID": 404, "label": "Gargish Cot East", "dictID": 14075, "page": 4, "timerID": 4, "graniteMake": [3525, 3625, 3725, 3825, 3925, 4025, 4125, 4225, 4325], "harvest": [14011, 10016], "minEra": "sa" },
+ { "buttonID": 405, "label": "Gargish Cot South", "dictID": 14076, "page": 4, "timerID": 4, "graniteMake": [3526, 3626, 3726, 3826, 3926, 4026, 4126, 4226, 4326], "harvest": [14011, 10016], "minEra": "sa" },
+ { "buttonID": 500, "label": "Gargish Stone Arms", "dictID": 14077, "page": 5, "timerID": 5, "graniteMake": [3527, 3627, 3727, 3827, 3927, 4027, 4127, 4227, 4327], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 501, "label": "Gargish Stone Chest", "dictID": 14078, "page": 5, "timerID": 5, "graniteMake": [3528, 3628, 3728, 3828, 3928, 4028, 4128, 4228, 4328], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 502, "label": "Gargish Stone Leggings", "dictID": 14079, "page": 5, "timerID": 5, "graniteMake": [3529, 3629, 3729, 3829, 3929, 4029, 4129, 4229, 4329], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 503, "label": "Gargish Stone Kilt", "dictID": 14080, "page": 5, "timerID": 5, "graniteMake": [3530, 3630, 3730, 3830, 3930, 4030, 4130, 4230, 4330], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 504, "label": "Gargish Stone Arms", "dictID": 14081, "page": 5, "timerID": 5, "graniteMake": [3531, 3631, 3731, 3831, 3931, 4031, 4131, 4231, 4331], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 505, "label": "Gargish Stone Chest", "dictID": 14082, "page": 5, "timerID": 5, "graniteMake": [3532, 3632, 3732, 3832, 3932, 4032, 4132, 4232, 4332], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 506, "label": "Gargish Stone Leggings", "dictID": 14083, "page": 5, "timerID": 5, "graniteMake": [3533, 3633, 3733, 3833, 3933, 4033, 4133, 4233, 4333], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 507, "label": "Gargish Stone Kilt", "dictID": 14084, "page": 5, "timerID": 5, "graniteMake": [3534, 3634, 3734, 3834, 3934, 4034, 4134, 4234, 4334], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 508, "label": "Large Stone Shield", "dictID": 14085, "page": 5, "timerID": 5, "graniteMake": [3535, 3635, 3735, 3835, 3935, 4035, 4135, 4235, 4335], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 509, "label": "Gargish Stone Amulet", "dictID": 14086, "page": 5, "timerID": 5, "graniteMake": [3536, 3636, 3736, 3836, 3936, 4036, 4136, 4236, 4336], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 600, "label": "Stone War Sword", "dictID": 14087, "page": 6, "timerID": 6, "graniteMake": [3537, 3637, 3737, 3837, 3937, 4037, 4137, 4237, 4337], "harvest": [14011], "minEra": "sa" },
+ { "buttonID": 700, "label": "Rough Windowless", "dictID": 14088, "page": 7, "timerID": 7, "graniteMake": [3538, 3638, 3738, 3838, 3938, 4038, 4138, 4238, 4338], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 701, "label": "Rough Window", "dictID": 14089, "page": 7, "timerID": 7, "graniteMake": [3539, 3639, 3739, 3839, 3939, 4039, 4139, 4239, 4339], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 702, "label": "Rough Arch", "dictID": 14090, "page": 7, "timerID": 7, "graniteMake": [3540, 3640, 3740, 3840, 3940, 4040, 4140, 4240, 4340], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 703, "label": "Rough Pillar", "dictID": 14091, "page": 7, "timerID": 7, "graniteMake": [3541, 3641, 3741, 3841, 3941, 4041, 4141, 4241, 4341], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 704, "label": "Rough Rounded Arch", "dictID": 14092, "page": 7, "timerID": 7, "graniteMake": [3542, 3642, 3742, 3842, 3942, 4042, 4142, 4242, 4342], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 705, "label": "Rough Small Arch", "dictID": 14093, "page": 7, "timerID": 7, "graniteMake": [3543, 3643, 3743, 3843, 3943, 4043, 4143, 4243, 4343], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 706, "label": "Rough Angled Pillar", "dictID": 14094, "page": 7, "timerID": 7, "graniteMake": [3544, 3644, 3744, 3844, 3944, 4044, 4144, 4244, 4344], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 707, "label": "Short Rough", "dictID": 14095, "page": 7, "timerID": 7, "graniteMake": [3545, 3645, 3745, 3845, 3945, 4045, 4145, 4245, 4345], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 708, "label": "Stone Door S In", "dictID": 14096, "page": 7, "timerID": 7, "graniteMake": [3546, 3646, 3746, 3846, 3946, 4046, 4146, 4246, 4346], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 709, "label": "Stone Door E Out", "dictID": 14097, "page": 7, "timerID": 7, "graniteMake": [3547, 3647, 3747, 3847, 3947, 4047, 4147, 4247, 4347], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 710, "label": "Left Metal Door S In", "dictID": 14098, "page": 7, "timerID": 7, "graniteMake": [3548, 3648, 3748, 3848, 3948, 4048, 4148, 4248, 4348], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 711, "label": "Right Metal Door S In", "dictID": 14099, "page": 7, "timerID": 7, "graniteMake": [3549, 3649, 3749, 3849, 3949, 4049, 4149, 4249, 4349], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 800, "label": "Short Rough", "dictID": 14100, "page": 8, "timerID": 8, "graniteMake": [3550, 3650, 3750, 3850, 3950, 4050, 4150, 4250, 4350], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 801, "label": "Rough Steps", "dictID": 14101, "page": 8, "timerID": 8, "graniteMake": [3551, 3651, 3751, 3851, 3951, 4051, 4151, 4251, 4351], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 802, "label": "Rough Corner Steps", "dictID": 14102, "page": 8, "timerID": 8, "graniteMake": [3552, 3652, 3752, 3852, 3952, 4052, 4152, 4252, 4352], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 803, "label": "Rough Rounded Corner Step", "dictID": 14103, "page": 8, "timerID": 8, "graniteMake": [3553, 3653, 3753, 3853, 3953, 4053, 4153, 4253, 4353], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 804, "label": "Rough Inset Steps", "dictID": 14104, "page": 8, "timerID": 8, "graniteMake": [3554, 3654, 3754, 3854, 3954, 4054, 4154, 4254, 4354], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 805, "label": "Rough Rounded Inset Steps", "dictID": 14105, "page": 8, "timerID": 8, "graniteMake": [3555, 3655, 3755, 3855, 3955, 4055, 4155, 4255, 4355], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 900, "label": "Light Paver", "dictID": 14106, "page": 9, "timerID": 9, "graniteMake": [3556, 3656, 3756, 3856, 3956, 4056, 4156, 4256, 4356], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 901, "label": "Medium Paver", "dictID": 14107, "page": 9, "timerID": 9, "graniteMake": [3557, 3657, 3757, 3857, 3957, 4057, 4157, 4257, 4357], "harvest": [14011], "minEra": "tol" },
+ { "buttonID": 902, "label": "Dark Paver", "dictID": 14108, "page": 9, "timerID": 9, "graniteMake": [3558, 3658, 3758, 3858, 3958, 4058, 4158, 4258, 4358], "harvest": [14011], "minEra": "tol" }
+]
diff --git a/data/js/jsdata/crafting/resourcemap.json b/data/js/jsdata/crafting/resourcemap.json
new file mode 100644
index 000000000..37ba5bb56
--- /dev/null
+++ b/data/js/jsdata/crafting/resourcemap.json
@@ -0,0 +1,44 @@
+[
+ { "resourceSet": "wood", "label": "Wood", "dictID": 10687, "items": [
+ { "label": "Logs", "itemID": 7136, "hue": 0 },
+ { "label": "Boards", "itemID": 7127, "hue": 0 }
+ ] },
+ { "resourceSet": "tailoring", "label": "Tailoring Materials", "dictID": 11402, "items": [
+ { "label": "Leather", "itemID": 4199, "hue": 0 },
+ { "label": "Leather", "itemID": 4200, "hue": 0 },
+ { "label": "Leather", "itemID": 4225, "hue": 0 },
+ { "label": "Leather", "itemID": 4226, "hue": 0 },
+ { "label": "Hides", "itemID": 4216, "hue": 0 },
+ { "label": "Hides", "itemID": 4217, "hue": 0 }
+ ] },
+ { "resourceSet": "ore", "label": "Ore", "tempTag": "ORE", "defaultIndex": 0, "items": [
+ { "index": 0, "label": "Iron", "dictID": 10291, "itemID": 7154, "hue": 0, "minSkill": 0 },
+ { "index": 1, "label": "Dull Copper", "dictID": 10203, "itemID": 7154, "hue": 2419, "minSkill": 650 },
+ { "index": 2, "label": "Shadow Iron", "dictID": 10204, "itemID": 7154, "hue": 2406, "minSkill": 700 },
+ { "index": 3, "label": "Copper", "dictID": 10205, "itemID": 7154, "hue": 2013, "minSkill": 750 },
+ { "index": 4, "label": "Bronze", "dictID": 10206, "itemID": 7154, "hue": 1750, "minSkill": 800 },
+ { "index": 5, "label": "Gold", "dictID": 10207, "itemID": 7154, "hue": 2213, "minSkill": 850 },
+ { "index": 6, "label": "Agapite", "dictID": 10208, "itemID": 7154, "hue": 2425, "minSkill": 900 },
+ { "index": 7, "label": "Verite", "dictID": 10209, "itemID": 7154, "hue": 2207, "minSkill": 950 },
+ { "index": 8, "label": "Valorite", "dictID": 10210, "itemID": 7154, "hue": 2219, "minSkill": 990 }
+ ] },
+ { "resourceSet": "granite", "label": "Granite", "tempTag": "Granite", "defaultIndex": 0, "items": [
+ { "index": 0, "label": "Granite", "dictID": 14011, "itemID": 6009, "hue": 0 },
+ { "index": 1, "label": "Dull Copper Granite", "dictID": 14012, "itemID": 6009, "hue": 2419 },
+ { "index": 2, "label": "Shadow Iron Granite", "dictID": 14013, "itemID": 6009, "hue": 2406 },
+ { "index": 3, "label": "Copper Granite", "dictID": 14014, "itemID": 6009, "hue": 2013 },
+ { "index": 4, "label": "Bronze Granite", "dictID": 14015, "itemID": 6009, "hue": 1750 },
+ { "index": 5, "label": "Gold Granite", "dictID": 14016, "itemID": 6009, "hue": 2213 },
+ { "index": 6, "label": "Agapite Granite", "dictID": 14017, "itemID": 6009, "hue": 2425 },
+ { "index": 7, "label": "Verite Granite", "dictID": 14018, "itemID": 6009, "hue": 2207 },
+ { "index": 8, "label": "Valorite Granite", "dictID": 14019, "itemID": 6009, "hue": 2219 }
+ ] },
+ { "resourceSet": "dragonScales", "label": "Dragon Scales", "tempTag": "Scale", "defaultIndex": 0, "items": [
+ { "index": 0, "label": "Red Scales", "dictID": 20299, "itemID": 9908, "hue": 1635, "minSkill": 0 },
+ { "index": 1, "label": "Yellow Scales", "dictID": 20300, "itemID": 9908, "hue": 2125, "minSkill": 0 },
+ { "index": 2, "label": "Black Scales", "dictID": 20301, "itemID": 9908, "hue": 1109, "minSkill": 0 },
+ { "index": 3, "label": "Green Scales", "dictID": 20302, "itemID": 9908, "hue": 2129, "minSkill": 0 },
+ { "index": 4, "label": "White Scales", "dictID": 20303, "itemID": 9908, "hue": 706, "minSkill": 0 },
+ { "index": 5, "label": "Blue Scales", "dictID": 20304, "itemID": 9908, "hue": 6, "minSkill": 0 }
+ ] }
+]
diff --git a/data/js/jsdata/crafting/tailoring.json b/data/js/jsdata/crafting/tailoring.json
new file mode 100644
index 000000000..17e34d9b6
--- /dev/null
+++ b/data/js/jsdata/crafting/tailoring.json
@@ -0,0 +1,58 @@
+[
+ { "makeID": 130, "label": "Skullcap", "dictID": 11415, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 131, "label": "Bandana", "dictID": 11416, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 132, "label": "Floppy Hat", "dictID": 11417, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 134, "label": "Wide-Brim Hat", "dictID": 11418, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 133, "label": "Cap", "dictID": 11419, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 136, "label": "Tall Straw Hat", "dictID": 11420, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 137, "label": "Straw Hat", "dictID": 11421, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 138, "label": "Wizard Hat", "dictID": 11422, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 139, "label": "Bonnet", "dictID": 11423, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 140, "label": "Feathered Hat", "dictID": 11424, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 141, "label": "Tricorne Hat", "dictID": 11425, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 135, "label": "Jester Hat", "dictID": 11470, "page": 1, "timerID": 1, "harvest": [10016] },
+ { "makeID": 142, "label": "Doublet", "dictID": 11426, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 143, "label": "Shirt", "dictID": 11427, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 144, "label": "Fancy Shirt", "dictID": 11428, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 145, "label": "Tunic", "dictID": 11429, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 146, "label": "Surcoat", "dictID": 11430, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 147, "label": "Plain Dress", "dictID": 11431, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 148, "label": "Fancy Dress", "dictID": 11432, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 149, "label": "Cloak", "dictID": 11433, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 150, "label": "Robe", "dictID": 11434, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 151, "label": "Jester Suit", "dictID": 11435, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 180, "label": "Fur Cape", "dictID": 11436, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 152, "label": "Short Pants", "dictID": 11437, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 153, "label": "Long Pants", "dictID": 11438, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 154, "label": "Kilt", "dictID": 11439, "page": 2, "timerID": 2, "harvest": [10016] },
+ { "makeID": 155, "label": "Skirt", "dictID": 11440, "page": 3, "timerID": 3, "harvest": [10016] },
+ { "makeID": 156, "label": "Body Sash", "dictID": 11441, "page": 3, "timerID": 3, "harvest": [10016] },
+ { "makeID": 157, "label": "Half Apron", "dictID": 11442, "page": 3, "timerID": 3, "harvest": [10016] },
+ { "makeID": 158, "label": "Full Apron", "dictID": 11443, "page": 3, "timerID": 3, "harvest": [10016] },
+ { "makeID": 159, "label": "Sandals", "dictID": 11444, "page": 4, "timerID": 4, "harvest": [10007] },
+ { "makeID": 160, "label": "Shoes", "dictID": 11445, "page": 4, "timerID": 4, "harvest": [10007] },
+ { "makeID": 161, "label": "Boots", "dictID": 11446, "page": 4, "timerID": 4, "harvest": [10007] },
+ { "makeID": 162, "label": "Thigh Boots", "dictID": 11447, "page": 4, "timerID": 4, "harvest": [10007] },
+ { "makeID": 163, "label": "Leather Gorget", "dictID": 11448, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 164, "label": "Leather Cap", "dictID": 11449, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 165, "label": "Leather Gloves", "dictID": 11450, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 166, "label": "Leather Sleeves", "dictID": 11451, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 167, "label": "Leather Leggings", "dictID": 11452, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 168, "label": "Leather Tunic", "dictID": 11453, "page": 5, "timerID": 5, "harvest": [10007] },
+ { "makeID": 169, "label": "Studded Gorget", "dictID": 11454, "page": 6, "timerID": 6, "harvest": [10007] },
+ { "makeID": 170, "label": "Studded Gloves", "dictID": 11455, "page": 6, "timerID": 6, "harvest": [10007] },
+ { "makeID": 171, "label": "Studded Sleeves", "dictID": 11456, "page": 6, "timerID": 6, "harvest": [10007] },
+ { "makeID": 172, "label": "Studded Leggings", "dictID": 11457, "page": 6, "timerID": 6, "harvest": [10007] },
+ { "makeID": 173, "label": "Studded Tunic", "dictID": 11458, "page": 6, "timerID": 6, "harvest": [10007] },
+ { "makeID": 174, "label": "Leather Shorts", "dictID": 11459, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 175, "label": "Leather Skirt", "dictID": 11460, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 176, "label": "Leather Bustier", "dictID": 11461, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 177, "label": "Studded Bustier", "dictID": 11462, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 178, "label": "Female Leather Armor", "dictID": 11463, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 179, "label": "Studded Armor", "dictID": 11464, "page": 7, "timerID": 7, "harvest": [10007] },
+ { "makeID": 181, "label": "Bone Helm", "dictID": 11465, "page": 8, "timerID": 8, "harvest": [10007, 10008] },
+ { "makeID": 182, "label": "Bone Gloves", "dictID": 11466, "page": 8, "timerID": 8, "harvest": [10007, 10008] },
+ { "makeID": 183, "label": "Bone Arms", "dictID": 11467, "page": 8, "timerID": 8, "harvest": [10007, 10008] },
+ { "makeID": 184, "label": "Bone Leggings", "dictID": 11468, "page": 8, "timerID": 8, "harvest": [10007, 10008] },
+ { "makeID": 185, "label": "Bone Armor", "dictID": 11469, "page": 8, "timerID": 8, "harvest": [10007, 10008] }
+]
diff --git a/data/js/jsdata/crafting/tinkering.json b/data/js/jsdata/crafting/tinkering.json
new file mode 100644
index 000000000..0c1187f52
--- /dev/null
+++ b/data/js/jsdata/crafting/tinkering.json
@@ -0,0 +1,78 @@
+[
+ { "makeID": 274, "label": "Axle", "dictID": 11801, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 273, "label": "Clock Frame", "dictID": 11802, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 270, "label": "Jointing Plane", "dictID": 11803, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 271, "label": "Moulding Plane", "dictID": 11804, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 272, "label": "Smoothing Plane", "dictID": 11805, "page": 1, "timerID": 1, "harvest": [10014] },
+ { "makeID": 218, "label": "Dovetail Saw", "dictID": 11820, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 215, "label": "Draw Knife", "dictID": 11821, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 252, "label": "Froe", "dictID": 11822, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 255, "label": "Hammer", "dictID": 11823, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 214, "label": "Hatchet", "dictID": 11824, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 258, "label": "Inshave", "dictID": 11825, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 260, "label": "Lockpick", "dictID": 11826, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 211, "label": "Mortar and Pestle", "dictID": 11827, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 259, "label": "Pick Axe", "dictID": 11828, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 217, "label": "Saw", "dictID": 11829, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 210, "label": "Scissors", "dictID": 11830, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 212, "label": "Scorp", "dictID": 11831, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 216, "label": "Sewing Kit", "dictID": 11832, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 254, "label": "Shovel", "dictID": 11833, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 257, "label": "Sledge Hammer", "dictID": 11834, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 256, "label": "Smith's Hammer", "dictID": 11835, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 253, "label": "Tongs", "dictID": 11836, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 213, "label": "Tool Kit (Tinker's tools)", "dictID": 11837, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 284, "label": "Fletcher's Tools", "dictID": 11838, "page": 2, "timerID": 2, "harvest": [10015] },
+ { "makeID": 224, "label": "Barrel Hoops", "dictID": 11860, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 221, "label": "Barrel Tap", "dictID": 11861, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 220, "label": "Clock parts", "dictID": 11862, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 219, "label": "Gears", "dictID": 11863, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 225, "label": "Hinge", "dictID": 11864, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 223, "label": "Sextant parts", "dictID": 11865, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 222, "label": "Springs", "dictID": 11866, "page": 3, "timerID": 3, "harvest": [10015] },
+ { "makeID": 226, "label": "Butcher Knife", "dictID": 11880, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 232, "label": "Cleaver", "dictID": 11881, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 230, "label": "Fork", "dictID": 11882, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 231, "label": "Fork", "dictID": 11883, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 235, "label": "Goblet", "dictID": 11884, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 233, "label": "Knife", "dictID": 11885, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 234, "label": "Knife", "dictID": 11886, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 236, "label": "Pewter Mug", "dictID": 11887, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 229, "label": "Plate", "dictID": 11888, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 237, "label": "Skinning Knife", "dictID": 11889, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 227, "label": "Spoon", "dictID": 11890, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 228, "label": "Spoon", "dictID": 11891, "page": 4, "timerID": 4, "harvest": [10015] },
+ { "makeID": 243, "label": "Bracelet", "dictID": 11900, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12005], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 241, "label": "Earrings", "dictID": 11901, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12005], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 239, "label": "Necklage (Golden beads)", "dictID": 11902, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12005], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 240, "label": "Necklace (Silver beads)", "dictID": 11903, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12005], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 242, "label": "Necklace (Round)", "dictID": 11904, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12005], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 238, "label": "Weddingband (newbiefied)", "dictID": 11905, "page": 5, "timerID": 5, "requiresGemTarget": true, "harvest": [10015, 12006], "craftComplete": { "type": "gemJewelry" } },
+ { "makeID": 245, "label": "Candelabra", "dictID": 11920, "page": 6, "timerID": 6, "harvest": [10015, 12000] },
+ { "makeID": 248, "label": "Globe", "dictID": 11921, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 251, "label": "Heating stand", "dictID": 11922, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 247, "label": "Iron Key", "dictID": 11923, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 244, "label": "Keyring", "dictID": 11924, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 250, "label": "Lantern", "dictID": 11925, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 246, "label": "Scales", "dictID": 11926, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 249, "label": "Spy glass", "dictID": 11927, "page": 6, "timerID": 6, "harvest": [10015] },
+ { "makeID": 275, "label": "Axle and Gears", "dictID": 11940, "page": 7, "timerID": 7, "harvest": [11801, 11863] },
+ { "makeID": 276, "label": "Clock", "dictID": 11941, "page": 7, "timerID": 7, "harvest": [11802, 11862] },
+ { "makeID": 277, "label": "Clock", "dictID": 11942, "page": 7, "timerID": 7, "harvest": [11802, 11862] },
+ { "makeID": 278, "label": "Clock Parts", "dictID": 11943, "page": 7, "timerID": 7, "harvest": [11801, 11863] },
+ { "makeID": 279, "label": "Locked Box", "dictID": 11944, "page": 7, "timerID": 7, "harvest": [10634] },
+ { "makeID": 280, "label": "Locked Chest", "dictID": 11945, "page": 7, "timerID": 7, "harvest": [10638] },
+ { "makeID": 281, "label": "Potion Keg", "dictID": 11946, "page": 7, "timerID": 7, "harvest": [10642, 11861, 10612, 10928] },
+ { "makeID": 282, "label": "Sextant", "dictID": 11947, "page": 7, "timerID": 7, "harvest": [11948] },
+ { "makeID": 283, "label": "Sextant Parts", "dictID": 11948, "page": 7, "timerID": 7, "harvest": [11801, 11863] },
+ { "makeID": 310, "label": "Standing Candelabra", "dictID": 11961, "page": 8, "timerID": 8, "harvest": [10015, 12000] },
+ { "makeID": 315, "label": "Regular Candle", "dictID": 11962, "page": 8, "timerID": 8, "harvest": [10015, 12000] },
+ { "makeID": 312, "label": "Round Candle", "dictID": 11963, "page": 8, "timerID": 8, "harvest": [12000] },
+ { "makeID": 316, "label": "Skull with Candle", "dictID": 11964, "page": 8, "timerID": 8, "harvest": [12000, 12004] },
+ { "makeID": 314, "label": "Small Candle", "dictID": 11965, "page": 8, "timerID": 8, "harvest": [12000] },
+ { "makeID": 311, "label": "Tall Candle", "dictID": 11966, "page": 8, "timerID": 8, "harvest": [10015, 12000] },
+ { "makeID": 313, "label": "Thick Candle", "dictID": 11967, "page": 8, "timerID": 8, "harvest": [12000] },
+ { "makeID": 261, "label": "Dart Trap", "dictID": 11980, "page": 9, "timerID": 9, "harvest": [10015, 12001] },
+ { "makeID": 263, "label": "Explosion Trap", "dictID": 11981, "page": 9, "timerID": 9, "harvest": [10015, 12003] },
+ { "makeID": 262, "label": "Poison Trap", "dictID": 11982, "page": 9, "timerID": 9, "harvest": [10015, 12002] }
+]
diff --git a/data/js/jse_fileassociations.scp b/data/js/jse_fileassociations.scp
index 8fb66b609..6a4116b84 100644
--- a/data/js/jse_fileassociations.scp
+++ b/data/js/jse_fileassociations.scp
@@ -243,6 +243,9 @@
4034=skill/craft/cooking.js
4035=skill/craft/cartography.js
4036=skill/craft/glassblowing.js
+4037=skill/craft/masonry.js
+4038=skill/craft/craftmap_registry.js
+4040=skill/craft/customcraft.js
4050=skill/mining.js
4055=skill/snooping.js
@@ -316,7 +319,7 @@
5052=item/sandminingbook.js
5053=item/stoneminingbook.js
5054=item/glassblowingbook.js
-//reserved for granitebook
+5055=item/masonrybook.js
5056=item/crystalball.js
5057=item/ballofpetsummoning.js
5058=item/bagofsending.js
@@ -329,6 +332,9 @@
5065=item/jacobspickaxe.js
5070=item/dawns_music_box.js
+5080=item/buildingcraftables.js
+5081=item/buildingcraftables_doors.js
+
5300=item/etherealstatuettes.js
5301=npc/pets/dismountetherealmount.js
5302=item/retouchingtool.js
diff --git a/data/js/server/house/houseCommands.js b/data/js/server/house/houseCommands.js
index 1fb81fab9..e3ccf1a58 100644
--- a/data/js/server/house/houseCommands.js
+++ b/data/js/server/house/houseCommands.js
@@ -1125,15 +1125,17 @@ function onCallback8( pSocket, myTarget )
var StrangeByte = pSocket.GetWord( 1 );
if( StrangeByte === 0 && ValidateObject( myTarget ) && myTarget.isItem )
{
+ var isDoor = ( myTarget.GetTag( "BuildingCraftableDoor" ) === 1 );
+
if( myTarget.type == 87 )
{
pSocket.SysMessage( GetDictionaryEntry( 1888, pSocket.language )); // You cannot lock down trash barrels.
}
- else if( myTarget.movable == 3 )
+ else if( !isDoor && ( myTarget.movable == 3 || myTarget.GetTag( "BuildingCraftable" ) == 1 ))
{
pSocket.SysMessage( GetDictionaryEntry( 1889, pSocket.language )); // That is already locked down!
}
- else if( myTarget.type == 12 || myTarget.type == 13 || myTarget.type == 203 || myTarget.id == 0x0BD2 ||
+ else if( !isDoor && myTarget.type == 12 || myTarget.type == 13 || myTarget.type == 203 || myTarget.id == 0x0BD2 ||
myTarget.movable == 2 || myTarget.movable == 3 || myTarget.id == 0x3996 || myTarget.id == 0x398C ||
myTarget.id == 0x3915 || myTarget.id == 0x3920 || myTarget.id == 0x3979 || myTarget.id == 0x3967 ||
myTarget.id == 0x3956 || myTarget.id == 0x3946 )
@@ -1173,7 +1175,7 @@ function onCallback8( pSocket, myTarget )
// Check if item blocks movement and is too close to a door
// 6 = TF_BLOCKING
- if( CheckTileFlag( myTarget.id, 6 ))
+ if( !isDoor && CheckTileFlag( myTarget.id, 6 ))
{
// Check for nearby doors
var foundDoor = AreaItemFunction( "CheckForNearbyDoors", myTarget, 3, pSocket );
diff --git a/data/js/server/misc/furniture_smartturn.js b/data/js/server/misc/furniture_smartturn.js
index 7bcf3e35e..1fd25f1f5 100644
--- a/data/js/server/misc/furniture_smartturn.js
+++ b/data/js/server/misc/furniture_smartturn.js
@@ -59,7 +59,7 @@ function LookForSittingPlayers( srcItem, trgChar, pSock )
function onDrop( iDropped, pDropper )
{
SmartTurn( iDropped );
- return true;
+ return 1;
}
// Turn supported furniture clockwise, based on previous direction
@@ -81,110 +81,302 @@ function SmartTurn( iTurn )
// our partner is listed! this is an implicit subtraction operation
iTurn.id--;
}// Special Turn for glass made items
- else if (iTurn.id == 0x1810 )//spinning hourglass
+ else if( iTurn.id == 0x1810 )//spinning hourglass
{
iTurn.id = 0x1811;
}
- else if (iTurn.id == 0x1811 || iTurn.id == 0x1812 || iTurn.id == 0x1813 || iTurn.id == 0x1814 || iTurn.id == 0x1815 || iTurn.id == 0x1816 || iTurn.id == 0x1817 || iTurn.id == 0x1818 || iTurn.id == 0x1819 || iTurn.id == 0x181A || iTurn.id == 0x181B || iTurn.id == 0x181C)//spinning hourglass animations
+ else if( iTurn.id == 0x1811 || iTurn.id == 0x1812 || iTurn.id == 0x1813 || iTurn.id == 0x1814 || iTurn.id == 0x1815 || iTurn.id == 0x1816 || iTurn.id == 0x1817 || iTurn.id == 0x1818 || iTurn.id == 0x1819 || iTurn.id == 0x181A || iTurn.id == 0x181B || iTurn.id == 0x181C )//spinning hourglass animations
{
iTurn.id = 0x1810;
}
- else if (iTurn.id == 0x185d)// Full Vials
+ else if( iTurn.id == 0x185d )// Full Vials
{
iTurn.id = 0x185e;
}
- else if (iTurn.id == 0x185e)// Full Vials
+ else if( iTurn.id == 0x185e )// Full Vials
{
iTurn.id = 0x185d;
}
- else if (iTurn.id == 0x185b)// Empty Vials
+ else if( iTurn.id == 0x185b )// Empty Vials
{
iTurn.id = 0x185c;
}
- else if (iTurn.id == 0x185c)// Full Vials
+ else if( iTurn.id == 0x185c )// Full Vials
{
iTurn.id = 0x185b;
}
- else if (iTurn.id == 0x182a)// Medium Flask
+ else if( iTurn.id == 0x182a )// Medium Flask
{
iTurn.id = 0x182b;
}
- else if (iTurn.id == 0x182b)// Medium Flask
+ else if( iTurn.id == 0x182b )// Medium Flask
{
iTurn.id = 0x182c;
}
- else if (iTurn.id == 0x182c)// Medium Flask
+ else if( iTurn.id == 0x182c )// Medium Flask
{
iTurn.id = 0x182d;
}
- else if (iTurn.id == 0x182d)// Medium Flask
+ else if( iTurn.id == 0x182d )// Medium Flask
{
iTurn.id = 0x182a;
}
- else if (iTurn.id == 0x182e)// Small Flask
+ else if( iTurn.id == 0x182e )// Small Flask
{
iTurn.id = 0x182f;
}
- else if (iTurn.id == 0x182f)// Small Flask
+ else if( iTurn.id == 0x182f )// Small Flask
{
iTurn.id = 0x1830;
}
- else if (iTurn.id == 0x1830)// Small Flask
+ else if( iTurn.id == 0x1830 )// Small Flask
{
iTurn.id = 0x1831;
}
- else if (iTurn.id == 0x1831)// Small Flask
+ else if( iTurn.id == 0x1831 )// Small Flask
{
iTurn.id = 0x182e;
}
- else if (iTurn.id == 0x1832)// Curved Flask
+ else if( iTurn.id == 0x1832 )// Curved Flask
{
iTurn.id = 0x1833;
}
- else if (iTurn.id == 0x1833)// Curved Flask
+ else if( iTurn.id == 0x1833 )// Curved Flask
{
iTurn.id = 0x1834;
}
- else if (iTurn.id == 0x1834)// Curved Flask
+ else if( iTurn.id == 0x1834 )// Curved Flask
{
iTurn.id = 0x1835;
}
- else if (iTurn.id == 0x1835)// Curved Flask
+ else if( iTurn.id == 0x1835 )// Curved Flask
{
iTurn.id = 0x1836;
}
- else if (iTurn.id == 0x1836)// Curved Flask
+ else if( iTurn.id == 0x1836 )// Curved Flask
{
iTurn.id = 0x1837;
}
- else if (iTurn.id == 0x1837)// Curved Flask
+ else if( iTurn.id == 0x1837 )// Curved Flask
{
iTurn.id = 0x1832;
}
- else if (iTurn.id == 0x1838)// Large Flask
+ else if( iTurn.id == 0x1838 )// Large Flask
{
iTurn.id = 0x1839;
}
- else if (iTurn.id == 0x1839)// Large Flask
+ else if( iTurn.id == 0x1839 )// Large Flask
{
iTurn.id = 0x183a;
}
- else if (iTurn.id == 0x1838)// Large Flask
+ else if( iTurn.id == 0x1838 )// Large Flask
{
iTurn.id = 0x183b;
}
- else if (iTurn.id == 0x183b)// Large Flask 2
+ else if( iTurn.id == 0x183b )// Large Flask 2
{
iTurn.id = 0x183c;
}
- else if (iTurn.id == 0x183c)// Large Flask 2
+ else if( iTurn.id == 0x183c )// Large Flask 2
{
iTurn.id = 0x183d;
}
- else if (iTurn.id == 0x183d)// Large Flask 2
+ else if( iTurn.id == 0x183d )// Large Flask 2
{
iTurn.id = 0x183b;
}
+ else if( iTurn.id == 0x01CF ) // RoughWindowless
+ {
+ iTurn.id = 0x01D0;
+ }
+ else if( iTurn.id == 0x01D0 )
+ {
+ iTurn.id = 0x01D1;
+ }
+ else if( iTurn.id == 0x01D1 )
+ {
+ iTurn.id = 0x01D0;
+ }
+ else if( iTurn.id == 0x01D2 )
+ {
+ iTurn.id = 0x01CF;
+ }
+ else if( iTurn.id == 0x01D3 ) // RoughWindow
+ {
+ iTurn.id = 0x01D4;
+ }
+ else if( iTurn.id == 0x01D4 )
+ {
+ iTurn.id = 0x01D3;
+ }
+ else if( iTurn.id == 0x01D5 ) // RoughArch
+ {
+ iTurn.id = 0x01D6;
+ }
+ else if( iTurn.id == 0x01D6 )
+ {
+ iTurn.id = 0x01D7;
+ }
+ else if( iTurn.id == 0x01D7 )
+ {
+ iTurn.id = 0x01D8;
+ }
+ else if( iTurn.id == 0x01D8 )
+ {
+ iTurn.id = 0x01D9;
+ }
+ else if( iTurn.id == 0x01D9 )
+ {
+ iTurn.id = 0x01D5;
+ }
+ else if( iTurn.id == 0x01DB ) // RoughRoundedArch
+ {
+ iTurn.id = 0x01DC;
+ }
+ else if( iTurn.id == 0x01DC )
+ {
+ iTurn.id = 0x01DD;
+ }
+ else if( iTurn.id == 0x01DD )
+ {
+ iTurn.id = 0x01DE;
+ }
+ else if( iTurn.id == 0x01DE )
+ {
+ iTurn.id = 0x01DF;
+ }
+ else if( iTurn.id == 0x01DF )
+ {
+ iTurn.id = 0x01DB;
+ }
+ else if( iTurn.id == 0x01E0 ) // RoughSmallArch
+ {
+ iTurn.id = 0x01E1;
+ }
+ else if( iTurn.id == 0x01E1 )
+ {
+ iTurn.id = 0x01E2;
+ }
+ else if( iTurn.id == 0x01E2 )
+ {
+ iTurn.id = 0x01E3;
+ }
+ else if( iTurn.id == 0x01E3 )
+ {
+ iTurn.id = 0x01E4;
+ }
+ else if( iTurn.id == 0x01E4 )
+ {
+ iTurn.id = 0x01E5;
+ }
+ else if( iTurn.id == 0x01E5 )
+ {
+ iTurn.id = 0x01E0;
+ }
+ else if( iTurn.id == 0x01E6 ) // RoughAngledPillar
+ {
+ iTurn.id = 0x01E7;
+ }
+ else if( iTurn.id == 0x01E7 )
+ {
+ iTurn.id = 0x01E6;
+ }
+ else if( iTurn.id == 0x01E8 ) // ShortRough
+ {
+ iTurn.id = 0x01E9;
+ }
+ else if( iTurn.id == 0x01E9 )
+ {
+ iTurn.id = 0x01EA;
+ }
+ else if( iTurn.id == 0x01EA )
+ {
+ iTurn.id = 0x01E9;
+ }
+ else if( iTurn.id == 0x01EB )
+ {
+ iTurn.id = 0x01E8;
+ }
+ else if( iTurn.id == 0x0789 ) // Rough steps
+ {
+ iTurn.id = 0x078A;
+ }
+ else if( iTurn.id == 0x078A )
+ {
+ iTurn.id = 0x078B;
+ }
+ else if( iTurn.id == 0x078B )
+ {
+ iTurn.id = 0x078C;
+ }
+ else if( iTurn.id == 0x078C )
+ {
+ iTurn.id = 0x0789;
+ }
+ else if( iTurn.id == 0x078D ) // Rough corner steps
+ {
+ iTurn.id = 0x078E;
+ }
+ else if( iTurn.id == 0x078E )
+ {
+ iTurn.id = 0x078F;
+ }
+ else if( iTurn.id == 0x078F )
+ {
+ iTurn.id = 0x0790;
+ }
+ else if( iTurn.id == 0x0790 )
+ {
+ iTurn.id = 0x078D;
+ }
+ else if( iTurn.id == 0x0791 ) // Rough rounded corner steps
+ {
+ iTurn.id = 0x0792;
+ }
+ else if( iTurn.id == 0x0792 )
+ {
+ iTurn.id = 0x0793;
+ }
+ else if( iTurn.id == 0x0793 )
+ {
+ iTurn.id = 0x0794;
+ }
+ else if( iTurn.id == 0x0794 )
+ {
+ iTurn.id = 0x0791;
+ }
+ else if( iTurn.id == 0x0795 ) // Rough inset steps
+ {
+ iTurn.id = 0x0796;
+ }
+ else if( iTurn.id == 0x0796 )
+ {
+ iTurn.id = 0x0797;
+ }
+ else if( iTurn.id == 0x0797 )
+ {
+ iTurn.id = 0x0798;
+ }
+ else if( iTurn.id == 0x0798 )
+ {
+ iTurn.id = 0x0795;
+ }
+ else if( iTurn.id == 0x0799 ) // Rough rounded inset steps
+ {
+ iTurn.id = 0x079A;
+ }
+ else if( iTurn.id == 0x079A )
+ {
+ iTurn.id = 0x079B;
+ }
+ else if( iTurn.id == 0x079B )
+ {
+ iTurn.id = 0x079C;
+ }
+ else if( iTurn.id == 0x079C )
+ {
+ iTurn.id = 0x0799;
+ }
}
var divorcedPairs = [ // they are paired up, but more than one index apart and out of order, [hex ID, add or subtract distance]
diff --git a/data/js/skill/craft/alchemy.js b/data/js/skill/craft/alchemy.js
index 5a954c492..4e6fc1259 100644
--- a/data/js/skill/craft/alchemy.js
+++ b/data/js/skill/craft/alchemy.js
@@ -1,241 +1,536 @@
///
// @ts-check
-const textHue = 0x480; // Color of the text.
-const scriptID = 4028; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
+const textHue = 0x480; // Color of the text.
+const alchemyID = 4028; // Script ID for this alchemy gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Generic item details gump
+const craftGumpID = 4027; // Shared crafting menu frame
+const itemsPerPage = 10; // Items per subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const alchemySkillID = 0; // Skill ID: Alchemy
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
+const craftMapRegistryID = 4038;
+var AlchemyMap = {};
-const myPage = [
- // Page 1 - Healing and Curative
- [10908, 10909, 10910, 10911, 10912, 10913, 10914, 10915],
+function LoadAlchemyMap()
+{
+ AlchemyMap = {};
+
+ var alchemyEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "alchemy" );
+
+ if( !alchemyEntries || !IsAlchemyArrayValue( alchemyEntries ) )
+ {
+ Console.Warning( "Alchemy: Unable to load alchemy craft map data." );
+ return false;
+ }
- // Page 2 - Enhancement
- [10916, 10917, 10918, 10919, 10920],
+ for( var i = 0; i < alchemyEntries.length; i++ )
+ {
+ var entry = alchemyEntries[i];
- // Page 3 - Toxic
- [10921, 10922, 10923, 10924],
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
- // Page 4 - Explosives
- [10925, 10926, 10927]
-];
+ if( entry.skill === undefined )
+ entry.skill = alchemySkillID;
+ AlchemyMap[entry.makeID] = entry;
+ }
+
+ Console.Print( "Alchemy: Loaded " + alchemyEntries.length + " craft map entries.\n" );
+ return true;
+}
+
+function IsAlchemyArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+// o--------------------------------------------------------------------------o
+// | PageX() - build a page of alchemy items |
+// o--------------------------------------------------------------------------o
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
function PageX( socket, pUser, pageNum )
{
- // Pages 1 - 4
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ if( !AlchemyMap || Object.keys( AlchemyMap ).length == 0 )
+ {
+ if( !LoadAlchemyMap() )
+ {
+ socket.SysMessage( "Alchemy craft map failed to load." );
+ return;
+ }
+ }
+
+ var pageItems;
+
+ // Special Last Ten page
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenAlchemy" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
+ {
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val ); // makeID itself
+ }
+ }
+ else
{
- var index = i % 10;
- if( index == 0 )
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in AlchemyMap )
+ {
+ if( !AlchemyMap.hasOwnProperty( key ))
+ continue;
+
+ var makeID = parseInt( key );
+ var data = AlchemyMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
{
- if( i > 0 )
+ var ea = AlchemyMap[a];
+ var eb = AlchemyMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering (no recipes yet, but keep hook)
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = AlchemyMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )))
+ pageItems.push( id );
+ }
+
+ // Fallback: if no items on this page and it's not page 1, go to page 1
+ if( pageItems.length == 0 && pageNum != 1 )
+ {
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var key2 in AlchemyMap )
+ {
+ if( !AlchemyMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = AlchemyMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
+ }
+
+ makeIDs.sort( function( a, b )
{
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ var ea2 = AlchemyMap[a];
+ var eb2 = AlchemyMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = AlchemyMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )))
+ pageItems.push( id2 );
}
+ }
+ }
- myGump.AddPage(( i / 10 ) + 1 );
+ // Subpage handling
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
- if( i > 0 )
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var alchGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", alchGump, socket );
+ alchGump.AddPage( 1 );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // use makeID directly as buttonID
+
+ var data5 = AlchemyMap[makeID];
+
+ if( !data5 )
+ {
+ entryText = "[Missing MakeID: " + makeID + "]";
+ }
+ else
+ {
+ if( data5.customName )
{
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
+ {
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), textHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ));
+ // Craft button uses makeID
+ alchGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ alchGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, ( 2000 + ( 100 * pageNum )) + i );
+ // Detail button: 20000 + makeID (same pattern as glassblowing)
+ alchGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
}
- myGump.Send( socket );
- myGump.Free();
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ alchGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ alchGump.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
+ }
+
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ alchGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ alchGump.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
+ }
+
+ alchGump.Send( socket );
+ alchGump.Free();
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+// o--------------------------------------------------------------------------o
+// | onTimer - reopen last page after crafting |
+// o--------------------------------------------------------------------------o
+/** @type { ( pUser: Character, timerID: number ) => void } */
function onTimer( pUser, timerID )
{
if( !ValidateObject( pUser ))
return;
- var socket = pUser.socket;
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
- switch( timerID )
+ if( timerID >= 1 && timerID <= 8 )
{
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- TriggerEvent( scriptID, "PageX", socket, pUser, timerID );
- break;
- default:
- break;
+ PageX( pSocket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
}
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+// o--------------------------------------------------------------------------o
+// | onGumpPress - navigation, Make Last, craft & details |
+// o--------------------------------------------------------------------------o
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
{
- var pUser = pSock.currentChar;
+ if( socket == null )
+ return;
- // Don't continue if character is invalid, or worse... dead!
+ var pUser = socket.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
- // Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ var tool = socket.tempObj;
+ if( !ValidateObject( tool ) || !pUser.InRange( tool, 3 ))
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
return;
}
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var timerID = 0;
+ if( tool.movable == 3 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // That is locked down and cannot be used.
+ return;
+ }
- if(( pButton >= 100 && pButton <= 402 ) || pButton == 5000 )
+ var packOwner = GetPackOwner( tool, 0 );
+ if( ValidateObject( packOwner ))
{
- if( pButton == 5000 )
- {
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
- }
- else
+ if( packOwner.serial != pUser.serial )
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That is not in your pack.
+ return;
}
}
+ else
+ {
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // You must have that item in your pack to use it.
+ return;
+ }
- switch( pButton )
+ var gumpID = alchemyID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
{
- case 0:
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;// abort and do nothing
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- pSock.CloseGump( gumpID, 0 );
- TriggerEvent( scriptID, "PageX", pSock, pUser, pButton );
- break;
- // Make Items
- case 100: // Refresh
- makeID = 305; timerID = 1; break;
- case 101: // Greater Refresh
- makeID = 306; timerID = 1; break;
- case 102: // Lesser Heal
- makeID = 298; timerID = 1; break;
- case 103: // Heal
- makeID = 299; timerID = 1; break;
- case 104: // Greater Heal
- makeID = 300; timerID = 1; break;
- case 105: // Lesser Cure
- makeID = 292; timerID = 1; break;
- case 106: // Cure
- makeID = 293; timerID = 1; break;
- case 107: // Greater Cure
- makeID = 294; timerID = 1; break;
- case 200: // Agility
- makeID = 290; timerID = 2; break;
- case 201: // Greater Agility
- makeID = 291; timerID = 2; break;
- case 202: // Night Sight
- makeID = 309; timerID = 2; break;
- case 203: // Strength
- makeID = 307; timerID = 2; break;
- case 204: // Greater Strength
- makeID = 308; timerID = 2; break;
- case 300: // Lesser Poison
- makeID = 301; timerID = 3; break;
- case 301: // Poison
- makeID = 302; timerID = 3; break;
- case 302: // Greater Poison
- makeID = 303; timerID = 3; break;
- case 303: // Deadly Poison
- makeID = 304; timerID = 3; break;
- case 400: // Lesser Explosion
- makeID = 295; timerID = 4; break;
- case 401: // Explosion
- makeID = 296; timerID = 4; break;
- case 402: // Greater Explosion
- makeID = 297; timerID = 4; break;
- // Show Item Details
- case 2100: // Item Details - Refresh
- itemDetailsID = 305; break;
- case 2101: // Item Details - Greater Refreshment
- itemDetailsID = 306; break;
- case 2102: // Item Details - Lesser Heal
- itemDetailsID = 298; break;
- case 2103: // Item Details - Heal
- itemDetailsID = 299; break;
- case 2104: // Item Details - Greater Heal
- itemDetailsID = 300; break;
- case 2105: // Item Details - Lesser Cure
- itemDetailsID = 292; break;
- case 2106: // Item Details - Cure
- itemDetailsID = 293; break;
- case 2107: // Item Details - Greater Cure
- itemDetailsID = 294; break;
- case 2200: // Item Details - Agility
- itemDetailsID = 290; break;
- case 2201: // Item Details - Greater Agility
- itemDetailsID = 291; break;
- case 2202: // Item Details - Night Sight
- itemDetailsID = 309; break;
- case 2203: // Item Details - Strength
- itemDetailsID = 307; break;
- case 2204: // Item Details - Greater Strength
- itemDetailsID = 308; break;
- case 2300: // Item Details - Lesser Poison
- itemDetailsID = 301; break;
- case 2301: // Item Details - Poison
- itemDetailsID = 302; break;
- case 2302: // Item Details - Greater Poison
- itemDetailsID = 303; break;
- case 2303: // Item Details - Deadly Poison
- itemDetailsID = 304; break;
- case 2400: // Item Details - Lesser Explosion
- itemDetailsID = 295; break;
- case 2401: // Item Details - Explosion
- itemDetailsID = 296; break;
- case 2402: // Item Details - Greater Explosion
- itemDetailsID = 297; break;
- default:
- break;
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
+ }
+
+ if( pButton >= 9001 && pButton < 10000 )
+ {
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs (Alchemy has 4 categories, but using <=8 is harmless)
+ if( pButton >= 1 && pButton <= 8 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
+ return;
}
- if( makeID != 0 )
+ // Last Ten
+ if( pButton == 11000 )
{
- MakeItem( pSock, pUser, makeID );
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Alchemy", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ var makeID = 0;
+ var timerID = 0;
+
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Alchemy" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ // Craft buttons use makeID directly
+ if( AlchemyMap[pButton] != undefined )
+ {
+ makeID = pButton;
+ var data = AlchemyMap[makeID];
+ timerID = data.timerID || 1;
+
+ if( !eraOK( data ))
+ {
+ socket.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ pUser.SetTempTag( "MakeLast_Alchemy", makeID );
+
+ MakeItem( socket, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
if( GetServerSetting( "ToolUseLimit" ))
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ tool.usesLeft -= 1;
+ if( tool.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ tool.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // Your tool wears out.
}
- }
- pUser.StartTimer( gumpDelay, timerID, true );
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, alchemyID );
+ return;
+ }
+
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
+ {
+ var detailMakeID = pButton - 20000;
+ var entry = AlchemyMap[detailMakeID];
+
+ if( entry )
+ {
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || alchemySkillID );
+
+ // Clear old harvest tags to avoid cross-contamination
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // If you later add entry.harvest = [dictID1, dictID2,...], you can push them here
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // OPTIONAL custom names – these override the dictionary string
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
}
- else if( itemDetailsID != 0 )
+}
+
+// o--------------------------------------------------------------------------o
+// | Last Ten handling |
+// o--------------------------------------------------------------------------o
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenAlchemy" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
}
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenAlchemy", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/blacksmithing.js b/data/js/skill/craft/blacksmithing.js
index 8ffa0eba4..55d97271d 100644
--- a/data/js/skill/craft/blacksmithing.js
+++ b/data/js/skill/craft/blacksmithing.js
@@ -1,291 +1,276 @@
///
// @ts-check
-const textHue = 0x480; // Color of the text.
-const blacksmithID = 4023; // Use this to tell the gump what script to close.
-const bronzeID = 4015;
-const copperID = 4016;
-const agapiteID = 4017;
-const dullcopperID = 4018;
-const goldID = 4019;
-const shadowironID = 4020;
-const valoriteID = 4021;
-const veriteID = 4022;
-const gumpDelay = 2000; // Timer for the gump to reappear after crafting.
-const ingotDelay = 200; // Timer for the gump to reappear after selecting a ingot.
-const repairDelay = 200; // Timer for the gump to reappear after repairing an item
+const textHue = 0x480; // Color of the text.
+const blacksmithID = 4023; // Script ID used to identify and close this gump
+const gumpDelay = 2000; // Timer for the gump to reappear after crafting.
+const ingotDelay = 200; // Timer for the gump to reappear after selecting an ingot.
+const repairDelay = 200; // Timer for the gump to reappear after repairing an item
const craftGumpID = 4027;
const itemDetailsScriptID = 4026;
- // If enabled, players can craft coloured variants of weapons, though unless the craftItems array
- // is updated with specific create entries for the coloured weapon variants, they'll just be
- // regular weapons with ore colour applied
+const itemsPerPage = 10; // Number of craftable items shown per gump subpage
+const displayUnlearnedRecipes = true; // Show recipes player has not learned (if we add any later)
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+
+// If enabled, players can craft coloured variants of weapons, though unless the craftItems array
+// is updated with specific create entries for the coloured weapon variants, they will just be
+// regular weapons with ore colour applied
const allowColouredWeapons = GetServerSetting( "CraftColouredWeapons" );
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the armor or weapon to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
-
-const myPage = [
- // Page 1 - Metal Armors
- [10217, 10218, 10219, 10220, 10221, 10222, 10223, 10224, 10225, 10226, 10227, 10228, 10229],
- // Page 2 - Helmets
- [10230, 10231, 10232, 10233, 10234],
- // Page 3 - Shields
- [10235, 10236, 10237, 10238, 10239, 10293],
- // Page 4 - Bladed
- [10240, 10241, 10242, 10243, 10244, 10245, 10246, 10247],
- // Page 5 - Axes
- [10248, 10249, 10250, 10251, 10252, 10253, 10254],
- // Page 6 - PoleArms
- [10255, 10256, 10257, 10258, 10259],
- // Page 7 - Bashing
- [10260, 10261, 10262, 10263, 10264]
-];
-
-const craftItems = [
- // Iron
- [
- // Metal Armors
- [ 7, 9, 8, 10, 11, 12, 13, 16, 15, 14, 17, 18, 19 ],
- // Helmets
- [ 46, 48, 45, 47, 49 ],
- // Shields
- [ 1, 2, 6, 3, 5, 4 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Dull Copper
- [
- // Metal Armors
- [ 506, 508, 507, 509, 510, 511, 512, 515, 514, 513, 516, 517, 518 ],
- // Helmets
- [ 520, 522, 519, 521, 523 ],
- // Shields
- [ 500, 501, 505, 502, 504, 503 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Shadow Iron
- [
- // Metal Armors
- [ 606, 608, 607, 609, 610, 611, 612, 615, 614, 613, 616, 617, 618 ],
- // Helmets
- [ 620, 622, 619, 621, 623 ],
- // Shields
- [ 600, 601, 605, 602, 604, 603 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Copper
- [
- // Metal Armors
- [ 706, 708, 707, 709, 710, 711, 7012, 715, 714, 713, 716, 717, 718 ],
- // Helmets
- [ 720, 722, 719, 721, 723 ],
- // Shields
- [ 700, 701, 705, 702, 704, 703 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Bronze
- [
- // Metal Armors
- [ 806, 808, 807, 809, 810, 811, 812, 815, 814, 813, 816, 817, 818 ],
- // Helmets
- [ 820, 822, 819, 821, 823 ],
- // Shields
- [ 800, 801, 805, 802, 804, 803 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Gold
- [
- // Metal Armors
- [ 906, 908, 907, 909, 910, 911, 912, 915, 914, 913, 916, 917, 918 ],
- // Helmets
- [ 920, 922, 919, 921, 923 ],
- // Shields
- [ 900, 901, 905, 902, 904, 903 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Agapite
- [
- // Metal Armors
- [ 1206, 1208, 1207, 1209, 1210, 1211, 1212, 1215, 1214, 1213, 1216, 1217, 1218 ],
- // Helmets
- [ 1220, 1222, 1219, 1221, 1223 ],
- // Shields
- [ 1200, 1201, 1205, 1202, 1204, 1203 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Verite
- [
- // Metal Armors
- [ 1006, 1008, 1007, 1009, 1010, 1011, 1012, 1015, 1014, 1013, 1016, 1017, 1018 ],
- // Helmets
- [ 1020, 1022, 1019, 1021, 1023 ],
- // Shields
- [ 1000, 1001, 1005, 1002, 1004, 1003 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ],
-
- // Valorite
- [
- // Metal Armors
- [ 1106, 1108, 1107, 1109, 1110, 1111, 1112, 1115, 1114, 1113, 1116, 1117, 1118 ],
- // Helmets
- [ 1120, 1122, 1119, 1121, 1123 ],
- // Shields
- [ 1100, 1101, 1105, 1102, 1104, 1103 ],
- // Bladed
- [ 25, 21, 20, 22, 23, 26, 24, 27 ],
- // Axes
- [ 29, 28, 32, 30, 33, 31, 34 ],
- // Polearms
- [ 38, 39, 35, 36, 37 ],
- // Bashing
- [ 44, 40, 41, 42, 43 ]
- ]
-];
+const craftMapRegistryID = 4038;
+var BlacksmithMap = {};
+
+function LoadBlacksmithMap()
+{
+ BlacksmithMap = {};
+
+ var blacksmithEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "blacksmithing" );
+
+ if( !blacksmithEntries || !IsBlacksmithArrayValue( blacksmithEntries ) )
+ {
+ Console.Warning( "Blacksmithing: Unable to load blacksmithing craft map data." );
+ return false;
+ }
+
+ for( var i = 0; i < blacksmithEntries.length; i++ )
+ {
+ var entry = blacksmithEntries[i];
+
+ if( !entry || typeof entry.buttonID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = 7;
+
+ if( !entry.harvest )
+ entry.harvest = [10015];
+
+ BlacksmithMap[entry.buttonID] = entry;
+ }
+
+ Console.Print( "Blacksmithing: Loaded " + blacksmithEntries.length + " craft map entries.\n" );
+ return true;
+}
+
+function IsBlacksmithArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+function GetBlacksmithResourceList( resourceSet )
+{
+ return TriggerEvent( 4038, "GetCraftResourceList", resourceSet );
+}
function PageX( socket, pUser, pageNum )
{
- // Pages 1 - 7
- var myGump = new Gump;
+ if( !ValidateObject( pUser ))
+ return;
+
+ if( !BlacksmithMap || Object.keys( BlacksmithMap ).length == 0 )
+ {
+ if( !LoadBlacksmithMap() )
+ {
+ socket.SysMessage( "Blacksmithing craft map failed to load." );
+ return;
+ }
+ }
+
+ // Pages 1 - 7: normal crafting pages
+ // Page 999: optional "Last Ten Blacksmith" (if you decide to use it later)
+
+ var subPage = pUser.GetTempTag( "subPage" ) || 1;
+ var pageItems = [];
+
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenBlacksmith" ) || "";
+ var split = lastTenRaw.split( "," );
+ for( var i = 0; i < split.length; i++ )
+ {
+ var val = parseInt( split[i] );
+ if( !isNaN( val ) && BlacksmithMap[val] )
+ pageItems.push( val ); // here val is the buttonID itself
+ }
+ }
+ else
+ {
+ // Build list of buttonIDs for this page from BlacksmithMap
+ for( var buttonID in BlacksmithMap )
+ {
+ var data = BlacksmithMap[buttonID];
+ if( data.page == pageNum && eraOK( data ))
+ {
+ // If we later add recipes and want to hide unknown ones:
+ var needsRecipe = data.recipeID;
+ var showAll = displayUnlearnedRecipes;
+ if( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe ))
+ {
+ pageItems.push( parseInt( buttonID ) );
+ }
+ }
+ }
+
+ // Sort by buttonID to keep consistent ordering
+ pageItems.sort( function( a, b ){ return a - b; } );
+ }
+
+ if( pageItems.length == 0 )
+ {
+ var emptyGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", emptyGump, socket );
+ emptyGump.AddPage( 1 );
+ emptyGump.AddText( 220, 60, textHue, "No items available on this page." );
+ emptyGump.Send( socket );
+ emptyGump.Free();
+ return;
+ }
+
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- var resourceHue = pUser.GetTempTag( "resourceHue" );
+ pUser.SetTempTag( "subPage", subPage );
- for( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
{
- // Don't show weapon entries if player has coloured ingots selected
- if( !allowColouredWeapons && resourceHue > 0 && pageNum > 3 )
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var resourceHue = pUser.GetTempTag( "resourceHue" ) | 0;
+ var blacksmithMenu = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", blacksmithMenu, socket );
+ blacksmithMenu.AddPage( 1 );
+
+ var drawIndex = 0;
+
+ for( var i = startIndex; i < endIndex; i++ )
+ {
+ var buttonID = pageItems[i];
+ var data = BlacksmithMap[buttonID];
+
+ // Do not show weapons when colored ingots selected and colored weapons are disabled
+ if( !allowColouredWeapons && resourceHue > 0 && data.page > 3 )
continue;
+ var entryText = "";
+ if( data.customName )
+ {
+ entryText = data.customName;
+ }
+ else if( data.dictID )
+ {
+ entryText = GetDictionaryEntry( data.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + buttonID + "]";
+ }
+
+ // Same layout as tailoring: button, text, details button
+ blacksmithMenu.AddButton( 220, 60 + ( drawIndex * 20 ), 4005, 4007, 1, 0, buttonID );
+ blacksmithMenu.AddText( 255, 60 + ( drawIndex * 20 ), textHue, entryText );
+ blacksmithMenu.AddButton( 480, 60 + ( drawIndex * 20 ), 4011, 4012, 1, 0, 2000 + buttonID );
+
+ drawIndex++;
+ }
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ blacksmithMenu.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ blacksmithMenu.AddHTMLGump( 255, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" );
+ }
+
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ blacksmithMenu.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ blacksmithMenu.AddHTMLGump( 405, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" );
+ }
+
+ blacksmithMenu.Send( socket );
+ blacksmithMenu.Free();
+}
+
+function Page8( socket, pUser )
+{
+ var oreResourceList = TriggerEvent( craftMapRegistryID, "GetCraftResourceList", "ore" );
+ var oreItems = oreResourceList && oreResourceList.items ? oreResourceList.items : [];
+
+ var myGump = new Gump();
+ pUser.SetTempTag( "page", 8 );
+
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
+
+ for( var i = 0; i < oreItems.length; i++ )
+ {
+ var oreInfo = oreItems[i];
var index = i % 10;
+
if( index == 0 )
{
if( i > 0 )
{
myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ myGump.AddHTMLGump( 405, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10100, socket.language ) + "" );
}
- myGump.AddPage(( i / 10) + 1 );
+ myGump.AddPage(( i / 10 ) + 1 );
if( i > 0 )
{
myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ myGump.AddHTMLGump( 255, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10101, socket.language ) + "" );
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), textHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ));
+ var count = pUser.ResourceCount( oreInfo.itemID, oreInfo.hue || 0 );
+ var label = GetDictionaryEntry( oreInfo.dictID, socket.language );
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, ( 2000 + ( 100 * pageNum )) + i );
+ myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, 1000 + i );
+ myGump.AddText( 255, 60 + ( index * 20 ), textHue, label + " (" + count.toString() + ")" );
}
+
myGump.Send( socket );
myGump.Free();
}
-function Page8( socket, pUser )
+function Page30( socket, pUser )
{
- //Ingot Choices
- var myGump = new Gump;
- pUser.SetTempTag( "page", 8 );
+ var scaleResourceList = TriggerEvent( craftMapRegistryID, "GetCraftResourceList", "dragonScales" );
+ var scaleItems = scaleResourceList && scaleResourceList.items ? scaleResourceList.items : [];
+
+ var myGump = new Gump();
+ pUser.SetTempTag( "page", 30 );
+
TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- var iron = pUser.ResourceCount( 0x1BF2 );
- var bronze = pUser.ResourceCount( 0x1BF2, 0x06d6 );
- var copper = pUser.ResourceCount( 0x1BF2, 0x07dd );
- var agapite = pUser.ResourceCount( 0x1BF2, 0x0979 );
- var dullcopper = pUser.ResourceCount( 0x1BF2, 0x0973 );
- var gold = pUser.ResourceCount( 0x1BF2, 0x08a5 );
- var shadowiron = pUser.ResourceCount( 0x1BF2, 0x0966 );
- var valorite = pUser.ResourceCount( 0x1BF2, 0x08ab );
- var verite = pUser.ResourceCount( 0x1BF2, 0x089f );
- var myPage8 = [
- GetDictionaryEntry( 10291, socket.language ) + " (" + iron.toString() + ")",
- GetDictionaryEntry( 10203, socket.language ) + " (" + dullcopper.toString() + ")",
- GetDictionaryEntry( 10204, socket.language ) + " (" + shadowiron.toString() + ")",
- GetDictionaryEntry( 10205, socket.language ) + " (" + copper.toString() + ")",
- GetDictionaryEntry( 10206, socket.language ) + " (" + bronze.toString() + ")",
- GetDictionaryEntry( 10207, socket.language ) + " (" + gold.toString() + ")",
- GetDictionaryEntry( 10208, socket.language ) + " (" + agapite.toString() + ")",
- GetDictionaryEntry( 10209, socket.language ) + " (" + verite.toString() + ")",
- GetDictionaryEntry( 10210, socket.language ) + " (" + valorite.toString() + ")"
- ];
-
- for( var i = 0; i < myPage8.length; i++ )
+
+ for( var i = 0; i < scaleItems.length; i++ )
{
+ var scaleInfo = scaleItems[i];
var index = i % 10;
+
if( index == 0 )
{
if( i > 0 )
{
myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ myGump.AddHTMLGump( 405, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10100, socket.language ) + "" );
}
myGump.AddPage(( i / 10 ) + 1 );
@@ -293,13 +278,17 @@ function Page8( socket, pUser )
if( i > 0 )
{
myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ myGump.AddHTMLGump( 255, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10101, socket.language ) + "" );
}
}
- myGump.AddButton( 220, 60 + ( index * 20), 4005, 4007, 1, 0, 1000 + i );
- myGump.AddText( 255, 60 + ( index * 20), textHue, myPage8[i] );
+ var count = pUser.ResourceCount( scaleInfo.itemID, scaleInfo.hue || 0 );
+ var label = GetDictionaryEntry( scaleInfo.dictID, socket.language );
+
+ myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, 1100 + i );
+ myGump.AddText( 255, 60 + ( index * 20 ), textHue, label + " (" + count.toString() + ")" );
}
+
myGump.Send( socket );
myGump.Free();
}
@@ -681,22 +670,21 @@ function onTimer( pUser, timerID )
var socket = pUser.socket;
- switch( timerID )
+ if( timerID >= 1 && timerID <= 7 )
{
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- PageX( socket, pUser, timerID );
- break;
- case 8: // Page 8
- Page8( socket, pUser );
- break;
- default:
- break;
+ PageX( socket, pUser, timerID ); // Pages 1 - 7
+ }
+ else if( timerID == 8 )
+ {
+ Page8( socket, pUser ); // Ingot selection
+ }
+ else if( timerID == 30 )
+ {
+ Page30( socket, pUser ); // Scale selection
+ }
+ else if( timerID == 999 )
+ {
+ PageX( socket, pUser, 999 ); // Last Ten Blacksmith (if used)
}
}
@@ -704,12 +692,11 @@ function onTimer( pUser, timerID )
function onGumpPress( pSock, pButton, gumpData )
{
var pUser = pSock.currentChar;
+ var usedMakeLast = false;
- // Don't continue if character is invalid, or worse... dead!
if( !ValidateObject( pUser ) || pUser.dead )
return;
- // Don't continue if player no longer has access to the crafting tool
var bItem = pSock.tempObj;
if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
{
@@ -724,9 +711,9 @@ function onGumpPress( pSock, pButton, gumpData )
}
var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ if( ValidateObject( iPackOwner ))
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ if( iPackOwner.serial != pUser.serial )
{
pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
return;
@@ -739,422 +726,403 @@ function onGumpPress( pSock, pButton, gumpData )
}
var gumpID = blacksmithID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var oreID = pUser.GetTempTag( "ORE" );
- var resourceHue = pUser.GetTempTag( "resourceHue" );
- var newOreID = -1;
- var newResourceHue = -1;
- var timerID = 0;
-
- // Check for nearby anvil
- var anvil = 0;
-
- // If button pressed is one of the crafting buttons (or "make last"), check that anvil was found
- if(( pButton >= 100 && pButton <= 704 ) || pButton == 5000 )
+
+ // Close / Exit
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "prevActionResult", null );
+ pUser.SetTempTag( "MAKELAST", null );
+ pSock.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Repair Item
+ if( pButton == 49 )
+ {
+ RepairTarget( pSock );
+ return;
+ }
+
+ // Select Materials (ingots)
+ if( pButton == 50 )
{
- anvil = AreaItemFunction( "FindNearbyAnvils", pUser, 2, pSock );
- if( anvil > 0 )
+ pSock.CloseGump( gumpID, 0 );
+ Page8( pSock, pUser );
+ return;
+ }
+
+ // Select Materials (scales)
+ if( pButton == 51 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ Page30( pSock, pUser );
+ return;
+ }
+
+ // Smelt Item
+ if( pButton == 52 )
+ {
+ SmeltTarget( pSock );
+ return;
+ }
+
+ // Page buttons (1..7)
+ if( pButton >= 1 && pButton <= 7 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ pSock.CloseGump( gumpID, 0 );
+ PageX( pSock, pUser, pButton );
+ return;
+ }
+
+ // Handle ore selection buttons
+ if( pButton >= 1000 && pButton < 1100 )
+ {
+ var oreIndex = pButton - 1000;
+
+ var oreResourceList = TriggerEvent( craftMapRegistryID, "GetCraftResourceList", "ore" );
+ var oreItems = oreResourceList && oreResourceList.items ? oreResourceList.items : [];
+
+ var oreInfo = oreItems[oreIndex];
+
+ if( !oreInfo)
+ return;
+
+ if( pUser.skills.mining < ( oreInfo.minSkill || 0 ))
{
- if( pButton == 5000 )
- {
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
- }
- else
- {
- // Update Make Last entry
- pUser.SetTempTag( "MAKELAST", pButton );
- }
+ pSock.CloseGump( gumpID, 0 );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ pUser.StartTimer( ingotDelay, 8, true );
+ return;
}
- else
+
+ pUser.SetTempTag( "ORE", oreIndex );
+ pUser.SetTempTag( "resourceHue", oreInfo.hue || 0 );
+ pUser.SetTempTag( "MAKELAST", null );
+ pUser.SetTempTag( "prevActionResult", null );
+
+ pSock.CloseGump( gumpID, 0);
+ pUser.StartTimer( ingotDelay, 8, true );
+ return;
+ }
+
+ // Handle scale selection buttons (1100–11XX) BEFORE the main switch
+ if( pButton >= 1100 && pButton < 1200 )
+ {
+ var scaleIndex = pButton - 1100;
+
+ var scaleResourceList = TriggerEvent( craftMapRegistryID, "GetCraftResourceList", "dragonScales" );
+ var scaleItems = scaleResourceList && scaleResourceList.items ? scaleResourceList.items : [];
+
+ var scaleInfo = scaleItems[scaleIndex];
+
+ if( !scaleInfo )
+ return;
+
+ if( pUser.skills.blacksmithing < ( scaleInfo.minSkill || 0 ))
{
- // No anvil found nearby!
- pUser.SetTempTag( "prevActionResult", "NOANVIL" );
+ pSock.CloseGump( gumpID, 0 );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ pUser.StartTimer( ingotDelay, 30, true );
+ return;
}
+
+ pUser.SetTempTag( "Scale", scaleIndex );
+ pUser.SetTempTag( "resourceHue", scaleInfo.hue || 0 );
+ pUser.SetTempTag( "MAKELAST", null );
+ pUser.SetTempTag( "prevActionResult", null );
+
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 30, true );
+ return;
}
- switch( pButton )
+ // Last Ten page (if you wire it into the gump)
+ if( pButton == 11000 )
{
- case 0:
- pUser.SetTempTag( "prevActionResult", null );
- pUser.SetTempTag( "MAKELAST", null );
- pSock.CloseGump( gumpID, 0 );
- break;// abort and do nothing
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- case 49: // Repair Item
- RepairTarget( pSock );
- break;
- case 50: // Select Materials
- pSock.CloseGump( gumpID, 0 );
- Page8( pSock, pUser );
- break;
- case 52: // Smelt Item
- SmeltTarget( pSock );
- break;
- // [100-199]
- case 100: // Ringmail Gloves
- makeID = craftItems[oreID][0][0]; timerID = 1; break;
- case 101: // Ringmail Legs
- makeID = craftItems[oreID][0][1]; timerID = 1; break;
- case 102: // Ringmail Arms
- makeID = craftItems[oreID][0][2]; timerID = 1; break;
- case 103: // Ringmail Chest
- makeID = craftItems[oreID][0][3]; timerID = 1; break;
- case 104: // Chain Coif
- makeID = craftItems[oreID][0][4]; timerID = 1; break;
- case 105: // Chain Legs
- makeID = craftItems[oreID][0][5]; timerID = 1; break;
- case 106: // Chain Chest
- makeID = craftItems[oreID][0][6]; timerID = 1; break;
- case 107: // Plate Arms
- makeID = craftItems[oreID][0][7]; timerID = 1; break;
- case 108: // Plate Gloves
- makeID = craftItems[oreID][0][8]; timerID = 1; break;
- case 109: // Plate Gorget
- makeID = craftItems[oreID][0][9]; timerID = 1; break;
- case 110: // Plate Legs
- makeID = craftItems[oreID][0][10]; timerID = 1; break;
- case 111: // Plate Chest
- makeID = craftItems[oreID][0][11]; timerID = 1; break;
- case 112: // Female Plate Chest
- makeID = craftItems[oreID][0][12]; timerID = 1; break;
- // [200-299]
- case 200: // Bascinet
- makeID = craftItems[oreID][1][0]; timerID = 2; break;
- case 201: // Close Helm
- makeID = craftItems[oreID][1][1]; timerID = 2; break;
- case 202: // Helmet
- makeID = craftItems[oreID][1][2]; timerID = 2; break;
- case 203: // Norse Helm
- makeID = craftItems[oreID][1][3]; timerID = 2; break;
- case 204: // Plate Helm
- makeID = craftItems[oreID][1][4]; timerID = 2; break;
- // [300-399]
- case 300: // Buckler
- makeID = craftItems[oreID][2][0]; timerID = 3; break;
- case 301: // Bronze Shield
- makeID = craftItems[oreID][2][1]; timerID = 3; break;
- case 302: // Heater Shield
- makeID = craftItems[oreID][2][2]; timerID = 3; break;
- case 303: // Metal Shield
- makeID = craftItems[oreID][2][3]; timerID = 3; break;
- case 304: // Metal Kite Shield
- makeID = craftItems[oreID][2][4]; timerID = 3; break;
- case 305: // Tear Kite Shield
- makeID = craftItems[oreID][2][5]; timerID = 3; break;
- // [400-499]
- case 400: // Broadsword
- makeID = craftItems[oreID][3][0]; timerID = 4; break;
- case 401: // Cutlass
- makeID = craftItems[oreID][3][1]; timerID = 4; break;
- case 402: // Dagger
- makeID = craftItems[oreID][3][2]; timerID = 4; break;
- case 403: // Katana
- makeID = craftItems[oreID][3][3]; timerID = 4; break;
- case 404: // Kryss
- makeID = craftItems[oreID][3][4]; timerID = 4; break;
- case 405: // Longsword
- makeID = craftItems[oreID][3][5]; timerID = 4; break;
- case 406: // Scimitar
- makeID = craftItems[oreID][3][6]; timerID = 4; break;
- case 407: // Viking Sword
- makeID = craftItems[oreID][3][7]; timerID = 4; break;
- // [500-599]
- case 500: // Axe
- makeID = craftItems[oreID][4][0]; timerID = 5; break;
- case 501: // Battle Axe
- makeID = craftItems[oreID][4][1]; timerID = 5; break;
- case 502: // Double Axe
- makeID = craftItems[oreID][4][2]; timerID = 5; break;
- case 503: // Executioners Axe
- makeID = craftItems[oreID][4][3]; timerID = 5; break;
- case 504: // Large Battle Axe
- makeID = craftItems[oreID][4][4]; timerID = 5; break;
- case 505: // Two Handed Axe
- makeID = craftItems[oreID][4][5]; timerID = 5; break;
- case 506: // War Axe
- makeID = craftItems[oreID][4][6]; timerID = 5; break;
- // [600-699]
- case 600: // Bardiche
- makeID = craftItems[oreID][5][0]; timerID = 6; break;
- case 601: // Halberd
- makeID = craftItems[oreID][5][1]; timerID = 6; break;
- case 602: // Short Spear
- makeID = craftItems[oreID][5][2]; timerID = 6; break;
- case 603: // Spear
- makeID = craftItems[oreID][5][3]; timerID = 6; break;
- case 604: // War Fork
- makeID = craftItems[oreID][5][4]; timerID = 6; break;
- // [700-799]
- case 700: // Hammer Pick
- makeID = craftItems[oreID][6][0]; timerID = 7; break;
- case 701: // Mace
- makeID = craftItems[oreID][6][1]; timerID = 7; break;
- case 702: // Maul
- makeID = craftItems[oreID][6][2]; timerID = 7; break;
- case 703: // War Mace
- makeID = craftItems[oreID][6][3]; timerID = 7; break;
- case 704: // War Hammer
- makeID = craftItems[oreID][6][4]; timerID = 7; break;
- // Select Ore Type
- case 1000: // Iron
- newOreID = (( newOreID == -1 ) ? 0 : newOreID );
- newResourceHue = 0; // Update manually if color changes in skills.dfn!
- case 1001: // Dull Copper
- if( pButton == 1001 && pUser.skills[7] < 650 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x0973 : newResourceHue ); // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 1 : newOreID );
- case 1002: // Shadow Iron
- if( pButton == 1002 && pUser.skills[7] < 700 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x0966 : newResourceHue ); // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 2 : newOreID );
- case 1003: // Copper
- if( pButton == 1003 && pUser.skills[7] < 750 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x07dd : newResourceHue ); // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 3 : newOreID );
- case 1004: // Bronze
- if( pButton == 1004 && pUser.skills[7] < 800 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x06d6 : newResourceHue ); // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 4 : newOreID );
- case 1005: // Gold
- if( pButton == 1005 && pUser.skills[7] < 850 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x08a5 : newResourceHue ); // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 5 : newOreID );
- case 1006: // Agapite
- if( pButton == 1006 && pUser.skills[7] < 900 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x0979 : newResourceHue );; // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 6 : newOreID );
- case 1007: // Verite
- if( pButton == 1007 && pUser.skills[7] < 950 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x089f : newResourceHue );; // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 7 : newOreID );
- case 1008: // Valorite
- if( pButton == 1008 && pUser.skills[7] < 990 )
- {
- pSock.CloseGump( gumpID, 0 );
- pUser.StartTimer( ingotDelay, 8, true);
- pUser.SetTempTag( "prevActionResult", "FAILED" );
- break;
- }
- newResourceHue = (( newResourceHue == - 1 ) ? 0x08ab : newResourceHue );; // Update manually if color changes in skills.dfn!
- newOreID = (( newOreID == -1 ) ? 8 : newOreID );
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( pSock, pUser, 999 );
+ return;
+ }
- // Run common code for this group of buttons
- pSock.CloseGump( gumpID, 0 );
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "prevActionResult", null );
- pUser.SetTempTag( "ORE", newOreID );
- pUser.SetTempTag( "resourceHue", newResourceHue );
- Page8( pSock, pUser );
- break;
- // Show Item Details
- case 2100: // Ringmail Gloves
- itemDetailsID = 7; break;
- case 2101: // Ringmail Legs
- itemDetailsID = 9; break;
- case 2102: // Ringmail Arms
- itemDetailsID = 8; break;
- case 2103: // Ringmail Chest
- itemDetailsID = 10; break;
- case 2104: // Chain Coif
- itemDetailsID = 11; break;
- case 2105: // Chain Legs
- itemDetailsID = 12; break;
- case 2106: // Chain Chest
- itemDetailsID = 13; break;
- case 2107: // Plate Arms
- itemDetailsID = 16; break;
- case 2108: // Plate Gloves
- itemDetailsID = 15; break;
- case 2109: // Plate Gorget
- itemDetailsID = 14; break;
- case 2110: // Plate Legs
- itemDetailsID = 17; break;
- case 2111: // Plate Chest
- itemDetailsID = 18; break;
- case 2112: // Female Plate Chest
- itemDetailsID = 19; break;
- // [200-299]
- case 2200: // Bascinet
- itemDetailsID = 46; break;
- case 2201: // Close Helm
- itemDetailsID = 48; break;
- case 2202: //Helmet
- itemDetailsID = 45; break;
- case 2203: // Norse Helm
- itemDetailsID = 47; break;
- case 2204: // Plate Helm
- itemDetailsID = 49; break;
- // [300-399]
- case 2300: // Buckler
- itemDetailsID = 1; break;
- case 2301: // Bronze Shield
- itemDetailsID = 2; break;
- case 2302: // Heater Shield
- itemDetailsID = 6; break;
- case 2303: // Metal Shield
- itemDetailsID = 3; break;
- case 2304: // MetalKiteShield
- itemDetailsID = 5; break;
- case 2305: // tear kite shield
- itemDetailsID = 4; break;
- // [400-499]
- case 2400: // Broadsword
- itemDetailsID = 25; break;
- case 2401: // Cutlass
- itemDetailsID = 21; break;
- case 2402: // Dagger
- itemDetailsID = 20; break;
- case 2403: // Katana
- itemDetailsID = 22; break;
- case 2404: // Kryss
- itemDetailsID = 23; break;
- case 2405: // Longsword
- itemDetailsID = 26; break;
- case 2406: // Scimitar
- itemDetailsID = 24; break;
- case 2407: // Viking Sword
- itemDetailsID = 27; break;
- // [500-599]
- case 2500: // Axe
- itemDetailsID = 29; break;
- case 2501: // Battle Axe
- itemDetailsID = 28; break;
- case 2502: // Double Axe
- itemDetailsID = 32; break;
- case 2503: // Executioners Axe
- itemDetailsID = 30; break;
- case 2504: // Large Battle Axe
- itemDetailsID = 33; break;
- case 2505: // Two Handed Axe
- itemDetailsID = 31; break;
- case 2506: // War Axe
- itemDetailsID = 34; break;
- // [600-699]
- case 2600: // Bardiche
- itemDetailsID = 38; break;
- case 2601: // Halberd
- itemDetailsID = 39; break;
- case 2602: // Short Spear
- itemDetailsID = 35; break;
- case 2603: // Spear
- itemDetailsID = 36; break;
- case 2604: // WarFork
- itemDetailsID = 37; break;
- // [700-799]
- case 2700: // Hammer Pick
- itemDetailsID = 44; break;
- case 2701: // Mace
- itemDetailsID = 40; break;
- case 2702: // Maul
- itemDetailsID = 41; break;
- case 2703: // War Mace
- itemDetailsID = 42; break;
- case 2704: // War Hammer
- itemDetailsID = 43; break;
- default:
- break;
+ // Subpage navigation (8000 = prev, 9000 = next)
+ if( pButton >= 8000 && pButton < 9000 )
+ {
+ var prevSub = pButton - 8000;
+ var curPage = pUser.GetTempTag( "page" ) || 1;
+ pUser.SetTempTag( "subPage", prevSub );
+ PageX( pSock, pUser, curPage );
+ return;
+ }
+
+ if( pButton >= 9000 && pButton < 10000 )
+ {
+ var nextSub = pButton - 9000;
+ var curPage2 = pUser.GetTempTag( "page" ) || 1;
+ pUser.SetTempTag( "subPage", nextSub );
+ PageX( pSock, pUser, curPage2 );
+ return;
}
- if( makeID != 0 )
+ // Handle "Make Last"
+ if(( pButton >= 100 && pButton <= 799 ) || pButton == 5000 )
{
- if( anvil > 0 )
+ if( pButton == 5000 )
{
- // crafting_complete.js for applying special bonuses for exceptional equipment/from runic hammers
- pUser.AddScriptTrigger( 4033 );
- MakeItem( pSock, pUser, makeID, resourceHue );
- var toolUseLimit = GetServerSetting( "ToolUseLimit" );
- var toolUseBreak = GetServerSetting( "ToolUseBreak" );
-
- // Check if player had runic hammer equipped while crafting
- var runicHammer = pUser.FindItemLayer( 0x01 ); // Right Hand
- if( ValidateObject( runicHammer ) && runicHammer.GetTag( "runicHammer" ) && runicHammer.usesLeft > 0 )
- {
- // Store some temp tags on player to get info on runic hammer used in crafting_complete.js
- pUser.SetTempTag( "usedRunicHammer", true );
- pUser.SetTempTag( "runicHammerType", runicHammer.color );
+ pButton = pUser.GetTempTag( "MAKELAST" );
+ usedMakeLast = true;
+ }
+ else
+ {
+ pUser.SetTempTag( "MAKELAST", pButton );
+ }
+ }
- // Wear and tear for equipped runic hammer, even if another tool was used to craft
- if( toolUseLimit && runicHammer != bItem )
+ // Item detail buttons (2000 + buttonID)
+ if( pButton >= 2000 && pButton < 3000 )
+ {
+ var detailButtonID = pButton - 2000;
+ var entry = BlacksmithMap[detailButtonID];
+ if( entry )
+ {
+ // For details we just pass the iron version (ore index 0) to 4026
+ var ironMakeID = entry.oreMake[0] || 0;
+ if( ironMakeID > 0 )
+ {
+ // Masonry uses Carpentry skill
+ pUser.SetTempTag( "Skill", entry.skill | 0 );
+
+ // Clear old harvests
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // If this entry uses dragon scales, override resource label
+ if( entry.useScales )
{
- runicHammer.usesLeft -= 1;
- if( runicHammer.usesLeft == 0 && toolUseBreak )
+ var scaleResourceList = TriggerEvent( craftMapRegistryID, "GetCraftResourceList", "dragonScales" );
+ var scaleItems = scaleResourceList && scaleResourceList.items ? scaleResourceList.items : [];
+
+ var sIndex = pUser.GetTempTag( "Scale" );
+
+ if( sIndex < 0 || sIndex >= scaleItems.length )
+ sIndex = 0;
+
+ var sInfo = scaleItems[sIndex];
+
+ if( sInfo )
{
- runicHammer.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ var sLabel = GetDictionaryEntry( sInfo.dictID, pSock.language );
+ pUser.SetTempTag( "HarvestName", sLabel );
}
}
+ else
+ {
+ // Normal ingot-based items: use the harvest setup as before
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // OPTIONAL custom names – these override the dictionary string
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ pUser.SetTempTag( "ITEMDETAILS", ironMakeID );
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
}
+ }
+ return;
+ }
+
+ // If this is a craft button in our map:
+ if( BlacksmithMap[pButton] != undefined )
+ {
+ var entry2 = BlacksmithMap[pButton];
+ var oreID;
+ if( entry2.useScales )
+ {
+ // For dragon armor, index into oreMake[] by selected scale index
+ oreID = pUser.GetTempTag( "Scale" );
+ }
+ else
+ {
+ oreID = pUser.GetTempTag( "ORE" );
+ }
+ var resourceHue = pUser.GetTempTag( "resourceHue" );
+
+ // Ensure oreID within range
+ if( oreID < 0 || oreID >= entry2.oreMake.length )
+ oreID = 0;
+
+ // Era / recipe gating
+ if( !eraOK( entry2 ))
+ {
+ pSock.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( entry2.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, entry2.recipeID ))
+ {
+ pSock.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ // Check for nearby anvil
+ var anvil = AreaItemFunction( "FindNearbyAnvils", pUser, 2, pSock );
+ if( anvil == 0 )
+ {
+ pUser.SetTempTag( "prevActionResult", "NOANVIL" );
+ pUser.StartTimer( gumpDelay, entry2.timerID, true );
+ return;
+ }
+
+ // No colored weapons if disabled and using non-iron ingots
+ if( !allowColouredWeapons && resourceHue > 0 && entry2.page > 3 )
+ {
+ pSock.SysMessage( "You cannot use colored ingots for weapons on this shard." );
+ return;
+ }
+
+ var makeID = entry2.oreMake[oreID];
+ if( !makeID || makeID == 0 )
+ {
+ // Fallback to iron version if for some reason we did not get a specific ore entry
+ makeID = entry2.oreMake[0];
+ }
+
+ if( !makeID || makeID == 0 )
+ {
+ pSock.SysMessage( "That item is not properly configured." );
+ return;
+ }
+
+ // Runic hammer bonus logic (unchanged from your original)
+ pUser.AddScriptTrigger( 4033 );
+
+ MakeItem( pSock, pUser, makeID, resourceHue );
+
+ // Tool wear
+ var toolUseLimit = GetServerSetting( "ToolUseLimit" );
+ var toolUseBreak = GetServerSetting( "ToolUseBreak" );
+
+ var runicHammer = pUser.FindItemLayer( 0x01 ); // Right Hand
+ if( ValidateObject( runicHammer ) && runicHammer.GetTag( "runicHammer" ) && runicHammer.usesLeft > 0 )
+ {
+ pUser.SetTempTag( "usedRunicHammer", true );
+ pUser.SetTempTag( "runicHammerType", runicHammer.color );
- if( toolUseLimit )
+ if( toolUseLimit && runicHammer != bItem )
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && toolUseBreak )
+ runicHammer.usesLeft -= 1;
+ if( runicHammer.usesLeft == 0 && toolUseBreak )
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ runicHammer.Delete();
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language ));
}
}
}
- pUser.StartTimer( gumpDelay, timerID, true );
+
+ if( toolUseLimit )
+ {
+ bItem.usesLeft -= 1;
+ if( bItem.usesLeft == 0 && toolUseBreak )
+ {
+ bItem.Delete();
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language ));
+ }
+ }
+
+ // Track in last ten list for blacksmith
+ AddToLastTenBlacksmith( pUser, pButton );
+
+ // Reopen page after delay
+ pUser.StartTimer( gumpDelay, entry2.timerID, true );
+ return;
}
- else if( itemDetailsID != 0 )
+
+ // Any other buttons fall through and do nothing
+}
+
+function AddToLastTenBlacksmith( pUser, buttonID )
+{
+ var raw = pUser.GetTempTag( "LastTenBlacksmith" ) || "";
+ var list = raw.split( "," );
+
+ // Remove if already in list
+ for( var i = 0; i < list.length; i++ )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ if( parseInt( list[i] ) == buttonID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
+ }
+
+ var newList = [buttonID];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenBlacksmith", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
}
+ return false;
}
+
+function eraOK( entry )
+{
+ // Optional per-entry gates. Strings like "lbr","aos","ml","sa","hs","tol".
+ // If not present, the entry is valid for all eras.
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/carpentry.js b/data/js/skill/craft/carpentry.js
index abf9fb725..5bd8822b6 100644
--- a/data/js/skill/craft/carpentry.js
+++ b/data/js/skill/craft/carpentry.js
@@ -1,85 +1,199 @@
///
// @ts-check
-const textHue = 0x480; // Color of the text.
-const scriptID = 4025; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reappear after crafting.
-const repairDelay = 200; // Timer for the gump to reappear after repairing an item
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
+const textHue = 0x480; // Color of the text.
+const carpentryID = 4025; // Use this to tell the gump what script to close.
+const gumpDelay = 2000; // Timer for the gump to reappear after crafting.
+const repairDelay = 200; // Timer for the gump to reappear after repairing an item.
+const itemDetailsScriptID = 4026; // Generic item detail gump
+const craftGumpID = 4027; // Crafting frame gump
+const itemsPerPage = 10; // Number of craftable items shown per gump subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ) );
+const carpentrySkillID = 11; // Carpentry skill ID
+const harvestDict = 10014; // Dictionary entry for "wood" (boards/logs)
+
+const craftMapRegistryID = 4038;
+var CarpentryMap = {};
+
+function LoadCarpentryMap()
+{
+ CarpentryMap = {};
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
+ var carpentryEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "carpentry" );
-const myPage = [
+ if( !carpentryEntries || !IsCarpentryArrayValue( carpentryEntries ) )
+ {
+ Console.Warning( "Carpentry: Unable to load carpentry craft map data." );
+ return false;
+ }
- // Page 1 - Other
- [10611, 10612, 10613, 10614, 10615, 10616, 10617, 10618, 10619, 10620, 10688],
+ for( var i = 0; i < carpentryEntries.length; i++ )
+ {
+ var entry = carpentryEntries[i];
- // Page 2 - Furniture
- [10621, 10622, 10623, 10624, 10625, 10626, 10627, 10628, 10629, 10630, 10631, 10632, 10633],
+ if( !entry || typeof entry.buttonID == "undefined" )
+ continue;
- // Page 3 - Containers
- [10634, 10635, 10636, 10637, 10638, 10639, 10640, 10641, 10642],
+ if( entry.skill === undefined )
+ entry.skill = carpentrySkillID;
- // Page 4 - Weapons
- [10643, 10644, 10645, 10646, 10647],
+ if( !entry.harvest )
+ entry.harvest = [ harvestDict ];
- // Page 5 - Armor
- [10648],
+ CarpentryMap[entry.buttonID] = entry;
+ }
- // Page 6 - Instruments
- [10649, 10650, 10651, 10652, 10653, 10654],
+ Console.Print( "Carpentry: Loaded " + carpentryEntries.length + " craft map entries.\n" );
+ return true;
+}
- // Page 7 - Misc. Add-Ons
- [10655, 10656, 10657, 10658, 10659, 10660, 10661, 10662, 10663, 10664, 10665],
+function IsCarpentryArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
- // Page 8 - Tailoring and Cooking
- [10666, 10667, 10668, 10669, 10670, 10671, 10672, 10673, 10674, 10675, 10676, 10677],
+function PageX( socket, pUser, pageNum )
+{
+ if( !socket || !ValidateObject( pUser ))
+ return;
- // Page 9 - Anvil and Forges
- [10678, 10679, 10680, 10681, 10682],
+ if( !CarpentryMap || Object.keys( CarpentryMap ).length == 0 )
+ {
+ if( !LoadCarpentryMap() )
+ {
+ socket.SysMessage( "Carpentry craft map failed to load." );
+ return;
+ }
+ }
- // Page 10 - Training
- [10683, 10684, 10685, 10686]
-];
+ var pageItems = [];
-function PageX( socket, pUser, pageNum )
-{
- // Pages 1 - 10
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ if( pageNum == 999 )
{
- var index = i % 10;
- if( index == 0 )
+ var lastRaw = pUser.GetTempTag( "LastTenCarpentry" ) || "";
+ var split = lastRaw.split( "," );
+ for( var i = 0; i < split.length; i++ )
{
- if( i > 0 )
- {
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
- }
+ var val = parseInt( split[i] );
+ if( !isNaN( val ) && CarpentryMap[val] )
+ pageItems.push( val );
+ }
+ }
+ else
+ {
+ for( var buttonIDStr in CarpentryMap )
+ {
+ if( !CarpentryMap.hasOwnProperty( buttonIDStr ))
+ continue;
- myGump.AddPage(( i / 10) + 1 );
+ var buttonID = parseInt( buttonIDStr );
+ var data = CarpentryMap[buttonID];
- if( i > 0 )
+ if( data.page == pageNum && eraOK( data ))
{
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ var needsRecipe = data.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe ))
+ {
+ pageItems.push( buttonID );
+ }
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), textHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ));
+ pageItems.sort(function(a, b){ return a - b; });
+ }
+
+ if( pageItems.length == 0 )
+ {
+ var emptyGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", emptyGump, socket );
+ emptyGump.AddPage( 1 );
+ emptyGump.AddText( 220, 60, textHue, "No items available on this page." );
+ emptyGump.Send( socket );
+ emptyGump.Free();
+ return;
+ }
+
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, ( 2000 + ( 100 * pageNum )) + i );
+ if( subPage < 1 )
+ subPage = 1;
+
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
}
- myGump.Send( socket );
- myGump.Free();
+
+ var carpGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", carpGump, socket );
+ carpGump.AddPage( 1 );
+
+ var drawIndex = 0;
+
+ for( var i = startIndex; i < endIndex; i++ )
+ {
+ var buttonID = pageItems[i];
+ var data = CarpentryMap[buttonID];
+
+ var entryText = "";
+ if( data.customName )
+ {
+ entryText = data.customName;
+ }
+ else if( data.dictID )
+ {
+ entryText = GetDictionaryEntry( data.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + buttonID + "]";
+ }
+
+ carpGump.AddButton( 220, 60 + ( drawIndex * 20 ), 4005, 4007, 1, 0, buttonID );
+ carpGump.AddText( 255, 60 + ( drawIndex * 20 ), textHue, entryText );
+
+ carpGump.AddButton( 480, 60 + ( drawIndex * 20 ), 4011, 4012, 1, 0, 2000 + buttonID );
+
+ drawIndex++;
+ }
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ carpGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ carpGump.AddHTMLGump( 255, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
+ }
+
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ carpGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ carpGump.AddHTMLGump( 405, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
+ }
+
+ carpGump.Send( socket );
+ carpGump.Free();
}
function RepairTarget( pSock )
@@ -97,8 +211,8 @@ function onCallback2( pSock, ourObj )
if( !ValidateObject( mChar ) || mChar.dead )
return;
- var bItem = pSock.tempObj;
- var gumpID = scriptID + 0xffff;
+ var bItem = pSock.tempObj;
+ var gumpID = carpentryID + 0xffff;
pSock.tempObj = null;
if( ValidateObject( mChar ) && mChar.isChar && ValidateObject( bItem ) && bItem.isItem )
@@ -116,102 +230,64 @@ function onCallback2( pSock, ourObj )
}
var itemDurabilityLossEnabled = GetServerSetting( "ItemRepairDurabilityLoss" );
- var repairID = ourObj.id;
var ownerObj = GetPackOwner( ourObj, 0 );
+
if( ownerObj && mChar.serial == ownerObj.serial )
{
- var maxHitpoints = ourObj.maxhp;
+ var maxHitpoints = ourObj.maxhp;
var currentHitpoints = ourObj.health;
+
if( currentHitpoints < maxHitpoints )
{
- // Get base repair difficulty based on amount of HP missing and max hitpoints
- var deltaHP = maxHitpoints - currentHitpoints;
+ var deltaHP = maxHitpoints - currentHitpoints;
var repairDifficulty = (( deltaHP / maxHitpoints ) * 1000 );
- var minDifficulty = repairDifficulty - 250;
- var skillBonus = 0;
- var repairSkill = mChar.skills.carpentry;
+ var minDifficulty = repairDifficulty - 250;
+ var skillBonus = 0;
+ var repairSkill = mChar.skills.carpentry;
+
if( minDifficulty < 0 )
{
- // If minDifficulty is negative, add the negative value as a bonus to player's skill
- skillBonus = minDifficulty * -1;
+ skillBonus = minDifficulty * -1;
minDifficulty = 0;
}
else if( minDifficulty > repairSkill )
{
- // Player skill below minimum repair difficulty, Too difficult to make the attempt!
pSock.tempObj = bItem;
pSock.CloseGump( gumpID, 0 );
mChar.SetTempTag( "prevActionResult", "CANTREPAIR" );
mChar.StartTimer( repairDelay, 1, true );
return;
}
+
var maxDifficulty = Math.min( repairDifficulty + 250, mChar.skillCaps.carpentry );
- // Allow repair if random number between min and base difficulty is under player's skill
if( RandomNumber( minDifficulty, 1000 ) < ( Math.max( repairSkill + skillBonus, 999 )))
{
- // Give player a chance every now and then to gain skill from repairing
if( RandomNumber( 1, 5 ) == 1 )
- {
- // Run a skill-check, which might trigger a skill-gain if player passes
- mChar.CheckSkill( 11, minDifficulty, maxDifficulty ); // Skill 11 = carpentry
- }
+ mChar.CheckSkill( 11, minDifficulty, maxDifficulty ); // carpentry
- // Reduce object's max durability by 1
if( itemDurabilityLossEnabled )
- {
ourObj.maxhp -= 1;
- }
- // Repair item here
ourObj.health = ourObj.maxhp;
pSock.SoundEffect( 0x023D, true );
- // Reopen gump after a short delay
pSock.CloseGump( gumpID, 0 );
mChar.SetTempTag( "prevActionResult", "REPAIRSUCCESS" );
mChar.StartTimer( repairDelay, 1, true );
-
- // GM skill (100.0 skillpoints)
- // Item with 51 HP max
- // item with 2 hp left - 99.65% chance to repair
- // item with 25 hp left - 99.86% chance to repair
- // item with 40 hp left - 99.9% chance to repair
-
- // Expert Smith (71.5 skill points)
- // Item with 51 HP max
- // item with 2 hp left - 1.45% chance to repair
- // item with 25 hp left - 61.49% chance to repair
- // item with 40 hp left - 74.9% chance to repair
- // item with 48 hp left - 90.6% chance to repair
-
- // Apprentice Smith (51.5 skill points)
- // Item with 51 HP max
- // item with 2 hp left - 0% chance to repair
- // item with 25 hp left - 34.5% chance to repair
- // item with 40 hp left - 54.9% chance to repair
- // item with 48 hp left - 70.6% chance to repair
}
else
{
- // Failed to repair item - decrease item health!
- if( repairSkill >= 1000 ) // GM Smith
- {
+ if( repairSkill >= 1000 )
ourObj.health -= 1;
- }
- else if( repairSkill >= 715 ) // Expert Smith
- {
+ else if( repairSkill >= 715 )
ourObj.health -= 2;
- }
- else // Below Expert Smith
- {
+ else
ourObj.health -= 3;
- }
if( ourObj.health <= 0 )
{
- // Item has been destroyed!
- pSock.SysMessage( GetDictionaryEntry( 311, pSock.language ).replace(/%s/gi, ourObj.name )); // Your %s has been destroyed.
+ pSock.SysMessage( GetDictionaryEntry( 311, pSock.language ).replace(/%s/gi, ourObj.name )); // destroyed
ourObj.Delete();
}
@@ -246,436 +322,260 @@ function onTimer( pUser, timerID )
return;
var socket = pUser.socket;
+ if( socket == null )
+ return;
- switch ( timerID )
+ if( timerID >= 1 && timerID <= 10 )
{
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- case 8: // Page 8
- case 9: // Page 9
- case 10: // Page 10
- PageX( socket, pUser, timerID );
- break;
+ PageX( socket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( socket, pUser, 999 );
}
}
/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
function onGumpPress( pSock, pButton, gumpData )
{
- var pUser = pSock.currentChar;
+ if( pSock == null )
+ return;
- // Don't continue if character is invalid, or worse... dead!
+ var pUser = pSock.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
- // Don't continue if player no longer has access to the crafting tool
var bItem = pSock.tempObj;
if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // too far
return;
}
if( bItem.movable == 3 )
{
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
+ pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // locked down
return;
}
var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ if( ValidateObject( iPackOwner ))
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ if( iPackOwner.serial != pUser.serial )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
+ pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // someone else's pack
return;
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // must be in pack
return;
}
-
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var timerID = 0;
-
- // If button pressed is one of the crafting buttons (or "make last")
- if(( pButton >= 100 && pButton <= 1003 ) || pButton == 5000 )
+
+ var gumpID = carpentryID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
{
- if( pButton == 5000 )
- {
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
- }
- else
- {
- // Update Make Last entry
- pUser.SetTempTag( "MAKELAST", pButton );
- }
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( pSock, pUser, pageNum );
+ return;
}
- switch( pButton )
+ if( pButton >= 9001 && pButton < 10000 )
{
- case 0:
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;// abort and do nothing
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- case 8: // Page 8
- case 9: // Page 9
- case 10: // Page 10
- pSock.CloseGump( gumpID, 0 );
- TriggerEvent( scriptID, "PageX", pSock, pUser, pButton );
- break;
- case 51:
- RepairTarget( pSock );
- break;
- case 100: // Barrel Staves
- makeID = 73; timerID = 1; break;
- case 101: // Barrel Lid
- makeID = 74; timerID = 1; break;
- case 102: // short music stand ( left )
- makeID = 89; timerID = 1; break;
- case 103: // short music stand ( right )
- makeID = 90; timerID = 1; break;
- case 104: // tall music stand ( left )
- makeID = 91; timerID = 1; break;
- case 105: // tall music stand ( left )
- makeID = 92; timerID = 1; break;
- case 106: // Easel ( S )
- makeID = 76; timerID = 1; break;
- case 107: // Easel ( E )
- makeID = 77; timerID = 1; break;
- case 108: // Easel ( N )
- makeID = 78; timerID = 1; break;
- case 109: // Fishing Pole
- makeID = 79; timerID = 1; break;
- case 110: // Boards
- makeID = 72; timerID = 1; break;
- case 200: // Barstool
- makeID = 50; timerID = 2; break;
- case 201: // Stool
- makeID = 51; timerID = 2; break;
- case 202: // Straw Chair
- makeID = 52; timerID = 2; break;
- case 203: // Wooden Chair
- makeID = 53; timerID = 2; break;
- case 204: // Vesper Style
- makeID = 57; timerID = 2; break;
- case 205: // Trinsic Style
- makeID = 58; timerID = 2; break;
- case 206: // Bench
- makeID = 54; timerID = 2; break;
- case 207: // Wooden Throne
- makeID = 55; timerID = 2; break;
- case 208: // Magincian Throne
- makeID = 56; timerID = 2; break;
- case 209: // Small Table
- makeID = 59; timerID = 2; break;
- case 210: // Writing Table
- makeID = 60; timerID = 2; break;
- case 211: // Large Table
- makeID = 61; timerID = 2; break;
- case 212: // Yew-wood Table
- makeID = 62; timerID = 2; break;
- case 300: // Small Chest
- makeID = 63; timerID = 3; break;
- case 301: // Small Crate
- makeID = 64; timerID = 3; break;
- case 302: // Medium Crate
- makeID = 65; timerID = 3; break;
- case 303: // Large Crate
- makeID = 67; timerID = 3; break;
- case 304: // Chest
- makeID = 68; timerID = 3; break;
- case 305: // Bookshelf
- makeID = 69; timerID = 3; break;
- case 306: // Armoire
- makeID = 70; timerID = 3; break;
- case 307: // Armoire
- makeID = 71; timerID = 3; break;
- case 308: // Open Keg
- makeID = 66; timerID = 3; break;
- case 400: // Shepard's Crook
- makeID = 80; timerID = 4; break;
- case 401: // Quarter Staff
- makeID = 81; timerID = 4; break;
- case 402: // Gnarled Staff
- makeID = 82; timerID = 4; break;
- case 403: // Club
- makeID = 123; timerID = 4; break;
- case 404: // Black Staff
- makeID = 124; timerID = 4; break;
- case 500: // Wooden Shield
- makeID = 75; timerID = 5; break;
- case 600: // Lap Harp
- makeID = 83; timerID = 6; break;
- case 601: // Standing Harp
- makeID = 84; timerID = 6; break;
- case 602: // Drum
- makeID = 85; timerID = 6; break;
- case 603: // Lute
- makeID = 86; timerID = 6; break;
- case 604: // Tambourine
- makeID = 87; timerID = 6; break;
- case 605: // Tambourine ( tassel )
- makeID = 88; timerID = 6; break;
- case 700: // bulletin board ( east )
- makeID = 93; timerID = 7; break;
- case 701: // bulletin board ( south )
- makeID = 93; timerID = 7; break;
- case 702: // Sm Bed ( E )
- makeID = 94; timerID = 7; break;
- case 703: // Sm Bed ( S )
- makeID = 93; timerID = 7; break;
- case 704: // Lg Bed ( E )
- makeID = 96; timerID = 7; break;
- case 705: // Lg Bed ( S )
- makeID = 95; timerID = 7; break;
- case 706: // Dart Board ( S )
- makeID = 97; timerID = 7; break;
- case 707: // Dart Board ( E )
- makeID = 98; timerID = 7; break;
- case 708: // Ballot Box
- makeID = 99; timerID = 7; break;
- case 709: // Pentagram
- makeID = 100; timerID = 7; break;
- case 710: // Abbatoir
- makeID = 101; timerID = 7; break;
- case 800: // Dressform
- makeID = 115; timerID = 8; break;
- case 801: // Dressform
- makeID = 116; timerID = 8; break;
- case 802: // Spin Wheel ( E )
- makeID = 107; timerID = 8; break;
- case 803: // Spin Wheel ( S )
- makeID = 108; timerID = 8; break;
- case 804: // Loom ( E )
- makeID = 109; timerID = 8; break;
- case 805: // Loom ( S )
- makeID = 110; timerID = 8; break;
- case 806: // Stone Oven ( E )
- makeID = 117; timerID = 8; break;
- case 807: // Stone Oven ( S )
- makeID = 118; timerID = 8; break;
- case 808: // Flour Mill ( E )
- makeID = 119; timerID = 8; break;
- case 809: // Flour Mill ( S )
- makeID = 120; timerID = 8; break;
- case 810: // Water Trough( E )
- makeID = 121; timerID = 8; break;
- case 811: // Water Trough( S )
- makeID = 122; timerID = 8; break;
- case 900: // Small Forge
- makeID = 102; timerID = 9; break;
- case 901: // Lg Forge ( E )
- makeID = 103; timerID = 9; break;
- case 902: // Lg Forge ( S )
- makeID = 104; timerID = 9; break;
- case 903: // Anvil ( E )
- makeID = 105; timerID = 9; break;
- case 904: // Anvil ( S )
- makeID = 106; timerID = 9; break;
- case 1000: // Dummy ( E )
- makeID = 111; timerID = 10; break;
- case 1001: // Dummy ( S )
- makeID = 112; timerID = 10; break;
- case 1002: // Pickpocket ( E )
- makeID = 113; timerID = 10; break;
- case 1003: // Pickpocket ( S )
- makeID = 114; timerID = 10; break;
- case 2100: // Barrel Staves
- itemDetailsID = 73; break;
- case 2101: // Barrel Lid
- itemDetailsID = 74; break;
- case 2102: // short music stand ( left )
- itemDetailsID = 89; break;
- case 2103: // short music stand ( right )
- itemDetailsID = 90; break;
- case 2104: // tall music stand ( left )
- itemDetailsID = 91; break;
- case 2105: // tall music stand ( left )
- itemDetailsID = 92; break;
- case 2106: // Easel ( S )
- itemDetailsID = 76; break;
- case 2107: // Easel ( E )
- itemDetailsID = 77; break;
- case 2108: // Easel ( N )
- itemDetailsID = 78; break;
- case 2109: // Fishing Pole
- itemDetailsID = 79; break;
- case 2110: // Boards
- itemDetailsID = 72; break;
- case 2200: // Barstool
- itemDetailsID = 50; break;
- case 2201: // Stool
- itemDetailsID = 51; break;
- case 2202: // Straw Chair
- itemDetailsID = 52; break;
- case 2203: // Wooden Chair
- itemDetailsID = 53; break;
- case 2204: // Vesper Style
- itemDetailsID = 57; break;
- case 2205: // Trinsic Style
- itemDetailsID = 58; break;
- case 2206: // Bench
- itemDetailsID = 54; break;
- case 2207: // Wooden Throne
- itemDetailsID = 55; break;
- case 2208: // Magincian Throne
- itemDetailsID = 56; break;
- case 2209:
- itemDetailsID = 59; break;
- case 2210: // Writing Table
- itemDetailsID = 60; break;
- case 2211: // Large Table
- itemDetailsID = 61; break;
- case 2212: // Yew-wood Table
- itemDetailsID = 62; break;
- case 2300: // Small Chest
- itemDetailsID = 63; break;
- case 2301: // Small Crate
- itemDetailsID = 64; break;
- case 2302: // Medium Crate
- itemDetailsID = 65; break;
- case 2303: // Large Crate
- itemDetailsID = 67; break;
- case 2304: // Chest
- itemDetailsID = 68; break;
- case 2305: // Bookshelf
- itemDetailsID = 69; break;
- case 2306: // Armoire
- itemDetailsID = 70; break;
- case 2307: // Armoire
- itemDetailsID = 71; break;
- case 2308: // Open Keg
- itemDetailsID = 66; break;
- case 2400: // Shepard's Crook
- itemDetailsID = 80; break;
- case 2401: // Quarter Staff
- itemDetailsID = 81; break;
- case 2402: // Gnarled Staff
- itemDetailsID = 82; break;
- case 2403: // Club
- itemDetailsID = 123; break;
- case 2404: // Black Staff
- itemDetailsID = 124; break;
- case 2500: // Wooden Shield
- itemDetailsID = 75; break;
- case 2600: // Lap Harp
- itemDetailsID = 83; break;
- case 2601: // Standing Harp
- itemDetailsID = 84; break;
- case 2602: // Drum
- itemDetailsID = 85; break;
- case 2603: // Lute
- itemDetailsID = 86; break;
- case 2604: // Tambourine
- itemDetailsID = 87; break;
- case 2605: // Tambourine ( tassel )
- itemDetailsID = 88; break;
- case 2700: // bulletin board ( east )
- itemDetailsID = 93; break;
- case 2701: // bulletin board ( south )
- itemDetailsID = 93; break;
- case 2702: // Sm Bed ( E )
- itemDetailsID = 94; break;
- case 2703: // Sm Bed ( S )
- itemDetailsID = 93; break;
- case 2704: // Lg Bed ( E )
- itemDetailsID = 96; break;
- case 2705: // Lg Bed ( S )
- itemDetailsID = 95; break;
- case 2706: // Dart Board ( S )
- itemDetailsID = 97; break;
- case 2707: // Dart Board ( E )
- itemDetailsID = 98; break;
- case 2708: // Ballot Box
- itemDetailsID = 99; break;
- case 2709: // Pentagram
- itemDetailsID = 100; break;
- case 2710: // Abbatoir
- itemDetailsID = 101; break;
- case 2800: // Dressform
- itemDetailsID = 115; break;
- case 2801: // Dressform
- itemDetailsID = 116; break;
- case 2802: // Spin Wheel ( E )
- itemDetailsID = 107; break;
- case 2803: // Spin Wheel ( S )
- itemDetailsID = 108; break;
- case 2804: // Loom ( E )
- itemDetailsID = 109; break;
- case 2805: // Loom ( S )
- itemDetailsID = 110; break;
- case 2806: // Stone Oven ( E )
- itemDetailsID = 117; break;
- case 2807: // Stone Oven ( S )
- itemDetailsID = 118; break;
- case 2808: // Flour Mill ( E )
- itemDetailsID = 119; break;
- case 2809: // Flour Mill ( S )
- itemDetailsID = 120; break;
- case 2810: // Water Trough( E )
- itemDetailsID = 121; break;
- case 2811: // Water Trough( S )
- itemDetailsID = 122; break;
- case 2900: // Small Forge
- itemDetailsID = 102; break;
- case 2901: // Lg Forge ( E )
- itemDetailsID = 103; break;
- case 2902: // Lg Forge ( S )
- itemDetailsID = 104; break;
- case 2903: // Anvil ( E )
- itemDetailsID = 105; break;
- case 2904: // Anvil ( S )
- itemDetailsID = 106; break;
- case 3000: // Dummy ( E )
- itemDetailsID = 111; break;
- case 3001: // Dummy ( S )
- itemDetailsID = 112; break;
- case 3002: // Pickpocket ( E )
- itemDetailsID = 113; break;
- case 3003: // Pickpocket ( S )
- itemDetailsID = 114; break;
- default:
- break;
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( pSock, pUser, pageNum2 );
+ return;
}
- if( makeID != 0 )
+ // Page tabs 1..10
+ if( pButton >= 1 && pButton <= 10 )
{
- pUser.AddScriptTrigger( 4033 ); // crafting_complete.js for applying locks to crafted containers
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( pSock, pUser, pButton );
+ return;
+ }
+
+ // Last Ten
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( pSock, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Carpentry", null );
+ pUser.SetTempTag( "CRAFT", null );
+ pSock.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Carpentry" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ if( CarpentryMap[pButton] !== undefined )
+ {
+ var entry = CarpentryMap[pButton];
+ var makeID = entry.makeID;
+ var timerID = entry.timerID || entry.page || 1;
+
+ if( !eraOK( entry ))
+ {
+ pSock.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( entry.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, entry.recipeID ))
+ {
+ pSock.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ pUser.SetTempTag( "MakeLast_Carpentry", pButton );
+
+ // Let crafting_complete.js hook things like locks on containers
+ pUser.AddScriptTrigger( 4033 );
+
MakeItem( pSock, pUser, makeID );
+ AddToLastTen( pUser, pButton );
+
if( GetServerSetting( "ToolUseLimit" ))
{
bItem.usesLeft -= 1;
if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // tool worn out
}
- }
- pUser.StartTimer( gumpDelay, timerID, true );
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, carpentryID );
+ return;
}
- else if( itemDetailsID != 0 )
+
+ // Item detail buttons: 2000 + buttonID
+ if( pButton >= 2000 && pButton < 4000 )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ var detailButtonID = pButton - 2000;
+ var entry = CarpentryMap[detailButtonID];
+
+ if( entry )
+ {
+ pUser.SetTempTag( "ITEMDETAILS", entry.makeID );
+
+ pUser.SetTempTag( "Skill", entry.skill || carpentrySkillID );
+
+ // Clear old harvests
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // OPTIONAL custom names – these override the dictionary string
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
}
}
+
+function AddToLastTen( pUser, buttonID )
+{
+ var raw = pUser.GetTempTag( "LastTenCarpentry" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == buttonID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
+ }
+
+ var newList = [ buttonID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenCarpentry", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/cartography.js b/data/js/skill/craft/cartography.js
index 38f8bc563..3f0bbeebf 100644
--- a/data/js/skill/craft/cartography.js
+++ b/data/js/skill/craft/cartography.js
@@ -1,193 +1,549 @@
///
// @ts-check
-const LabelHue = 0x480; // Color of the text.
-const LabelColor = 0x7FFF; // Second Color of text.
-const scriptID = 4035; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
-
-const myPage = [
- // Page 1 - Maps
- [ 13100, 13101, 13102, 13103 ]
-];
+const textHue = 0x480; // Color of the text.
+const cartographyID = 4035; // Script ID for this cartography gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Generic item details gump
+const craftGumpID = 4027; // Shared crafting menu frame
+const itemsPerPage = 10; // Items per subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const cartographySkillID = 12; // Skill index for "cartography" in ItemDetailGump
+const craftMapRegistryID = 4038;
+var CartographyMap = {};
+
+function LoadCartographyMap()
+{
+ CartographyMap = {};
+
+ var cartographyEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "cartography" );
+
+ if( !cartographyEntries || !IsCartographyArrayValue( cartographyEntries ) )
+ {
+ Console.Warning( "Cartography: Unable to load cartography craft map data." );
+ return false;
+ }
+
+ for( var i = 0; i < cartographyEntries.length; i++ )
+ {
+ var entry = cartographyEntries[i];
+
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = cartographySkillID;
+
+ CartographyMap[entry.makeID] = entry;
+ }
+
+ Console.Print( "Cartography: Loaded " + cartographyEntries.length + " craft map entries.\n" );
+ return true;
+}
+
+function IsCartographyArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
function PageX( socket, pUser, pageNum )
{
- // Pages 1
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for ( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ if( !CartographyMap || Object.keys( CartographyMap ).length == 0 )
{
- var index = i % 10;
- if ( index == 0 )
+ if( !LoadCartographyMap() )
{
- if ( i > 0 )
+ socket.SysMessage( "Cartography craft map failed to load." );
+ return;
+ }
+ }
+
+ var pageItems;
+
+ // Special Last Ten page (if you ever want it for Cartography)
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenCartography" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
+ {
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val ); // makeID itself
+ }
+ }
+ else
+ {
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in CartographyMap )
+ {
+ if( !CartographyMap.hasOwnProperty( key ))
+ continue;
+
+ var makeID = parseInt( key );
+ var data = CartographyMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
+ {
+ var ea = CartographyMap[a];
+ var eb = CartographyMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering (hooks for future use)
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = CartographyMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )) )
+ pageItems.push( id );
+ }
+
+ // Fallback: if no items on this page and it's not page 1, go to page 1
+ if( pageItems.length == 0 && pageNum != 1 )
+ {
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var key2 in CartographyMap )
{
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ if( !CartographyMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = CartographyMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
}
- myGump.AddPage( ( i / 10 ) + 1 );
+ makeIDs.sort( function( a, b )
+ {
+ var ea2 = CartographyMap[a];
+ var eb2 = CartographyMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = CartographyMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )) )
+ pageItems.push( id2 );
+ }
+ }
+ }
+
+ // Subpage handling (future-proof; only 1 subpage needed right now)
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var cartGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", cartGump, socket );
+ cartGump.AddPage( 1 );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // use makeID directly as buttonID
+
+ var data5 = CartographyMap[makeID];
- if ( i > 0 )
+ if( !data5 )
+ {
+ entryText = "[Missing MakeID: " + makeID + "]";
+ }
+ else
+ {
+ if( data5.customName )
+ {
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
{
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), LabelHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ) );
+ // Craft button uses makeID
+ cartGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ cartGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+
+ // Detail button: 20000 + makeID
+ cartGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
+ }
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ cartGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ cartGump.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
+ }
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + ( 100 * pageNum ) + i );
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ cartGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ cartGump.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
}
- myGump.Send( socket );
- myGump.Free();
+
+ cartGump.Send( socket );
+ cartGump.Free();
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+/** @type { ( pUser: Character, timerID: number ) => void } */
function onTimer( pUser, timerID )
{
if( !ValidateObject( pUser ))
return;
- var socket = pUser.socket;
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
- switch ( timerID )
+ if( timerID >= 1 && timerID <= 8 )
{
- case 1: // Page 1 - Maps
- PageX( socket, pUser, timerID );
- break;
+ PageX( pSocket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
}
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
{
- var pUser = pSock.currentChar;
+ if( socket == null )
+ return;
- // Don't continue if character is invalid, or worse... dead!
+ var pUser = socket.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
- // Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ var tool = socket.tempObj;
+ if( !ValidateObject( tool ) || !pUser.InRange( tool, 3 ))
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
return;
}
- if( bItem.movable == 3 )
+ if( tool.movable == 3 )
{
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
return;
}
- var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ var packOwner = GetPackOwner( tool, 0 );
+ if( ValidateObject( packOwner ))
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ if( packOwner.serial != pUser.serial )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That is in someone else's pack.
return;
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // Must be in your pack to use it.
+ return;
+ }
+
+ var gumpID = cartographyID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
+ {
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
+ }
+
+ if( pButton >= 9001 && pButton < 10000 )
+ {
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs (Cartography only uses page 1 for now, but up to 8 is harmless)
+ if( pButton >= 1 && pButton <= 8 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
return;
}
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
+ // Last Ten
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Cartography", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ var makeID = 0;
var timerID = 0;
- if(( pButton >= 100 && pButton <= 305 ) || pButton == 5000 )
+ // Make Last
+ if( pButton == 5000 )
{
- if( pButton == 5000 )
+ var last = pUser.GetTempTag( "MakeLast_Cartography" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ // Craft buttons use makeID directly
+ if( CartographyMap[pButton] != undefined )
+ {
+ makeID = pButton;
+ var data = CartographyMap[makeID];
+ timerID = data.timerID || 1;
+
+ if( !eraOK( data ))
{
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
+ socket.SysMessage( "That item is not available in this era." );
+ return;
}
- else
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
}
- }
- switch ( pButton )
- {
- case 0: // Abort and do nothing
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;
- case 1: // Page 1 - Maps
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- // Make Items
- case 100: // Local Map
- makeID = 2000; timerID = 1; break;
- case 101: // City Map
- makeID = 2001; timerID = 1; break;
- case 102: // Sea Chart
- makeID = 2002; timerID = 1; break;
- case 103: // World map
- mapID = 0;
+ // Save Make Last
+ pUser.SetTempTag( "MakeLast_Cartography", makeID );
+
+ // Special case: World Map uses different create entries per world
+ var makeToCraft = makeID;
+ if( makeID == 2003 ) // world map row
+ {
switch( pUser.worldnumber )
{
- case 0: mapID = 2003; break;//fel
- case 1: mapID = 2003; break;//tram
- case 2: mapID = 2004; break;//ilsh
- case 3: mapID = 2005; break;//malas
- case 4: mapID = 2006; break;//Tokuno
- case 5: mapID = 2007; break;//TerMur
+ case 0: // Felucca
+ case 1: // Trammel
+ makeToCraft = 2003; break;
+ case 2: // Ilshenar
+ makeToCraft = 2004; break;
+ case 3: // Malas
+ makeToCraft = 2005; break;
+ case 4: // Tokuno
+ makeToCraft = 2006; break;
+ case 5: // TerMur
+ makeToCraft = 2007; break;
}
- makeID = mapID; timerID = 1; break;
- // Show Item Details
- case 2100: // Local Map
- itemDetailsID = 2000; break;
- case 2101: // City Map
- itemDetailsID = 2001; break;
- case 2102: // Sea Chart
- itemDetailsID = 2002; break;
- case 2103: // World map
- itemDetailsID = 2003; break;
- default:
- break;
+ }
+
+ // Let crafting_complete.js handle map-specific setup
+ pUser.AddScriptTrigger( 4033 );
+
+ MakeItem( socket, pUser, makeToCraft );
+ AddToLastTen( pUser, makeID );
+
+ if( GetServerSetting( "ToolUseLimit" ))
+ {
+ tool.usesLeft -= 1;
+ if( tool.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ {
+ tool.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
+ }
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, cartographyID );
+ return;
}
- if( makeID != 0 )
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
{
- pUser.AddScriptTrigger(4033); // crafting_complete.js for applying map settings
- MakeItem( pSock, pUser, makeID );
- if( GetServerSetting( "ToolUseLimit" ))
+ var detailMakeID = pButton - 20000;
+ var entry = CartographyMap[detailMakeID];
+
+ if( entry )
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || cartographySkillID );
+
+ // Clear old harvest tags
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old custom harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // If you later add entry.harvest = [ ... ], push them into tags
+ if( entry.harvest && entry.harvest.length > 0 )
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
}
- }
- pUser.StartTimer( gumpDelay, timerID, true );
+
+ // OPTIONAL custom names – override / supplement dictionary labels
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
}
- else if( itemDetailsID != 0 )
+}
+
+// Last Ten handling
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenCartography" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
}
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenCartography", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/cooking.js b/data/js/skill/craft/cooking.js
index 393b4306a..71cba8692 100644
--- a/data/js/skill/craft/cooking.js
+++ b/data/js/skill/craft/cooking.js
@@ -1,73 +1,30 @@
///
// @ts-check
-const LabelHue = 0x480; // Color of the text.
-const LabelColor = 0x7FFF; // Second Color of text.
-const scriptID = 4034; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
-const manualMillTarget = true; // If true, player must manually target mills when grinding wheat
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
-
-const myPage = [
- // Page 1 - Ingredients
- [ 11606, 11607, 11608, 11609, 11610 ],
-
- // Page 2 - Preparations
- [ 11611, 11612, 11613, 11614, 11615, 11616, 11617, 11618 ],
-
- // Page 3 - Baking
- [11619, 11620, 11621, 11657, 11622, 11623, 11624, 11625, 11626, 11627, 11628, 11629 ],
-
- // Page 4 - Barbecue
- [ 11630, 11631, 11632, 11633, 11634, 11635 ]
-
+const textHue = 0x480; // Color of the text.
+const cookingID = 4034; // Script ID for this cooking gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Generic item details gump
+const craftGumpID = 4027; // Shared crafting menu frame
+const itemsPerPage = 10; // Items per subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const cookingSkillID = 13; // Index for "cooking" in ItemDetailGump skillNames[]
+const manualMillTarget = true; // If true, player must target mills when grinding wheat
+
+// Mills / ovens / heat sources for AreaItemFunction + StaticInRange checks
+const mills = [
+ 0x188b, 0x1893, 0x1920, 0x1922, 0x192c, 0x192e
];
-function PageX( socket, pUser, pageNum )
-{
- // Pages 1 - 4
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for ( var i = 0; i < myPage[pageNum - 1].length; i++ )
- {
- var index = i % 10;
- if ( index == 0 )
- {
- if ( i > 0 )
- {
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
- }
-
- myGump.AddPage( ( i / 10 ) + 1 );
-
- if ( i > 0 )
- {
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
- }
- }
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
-
- myGump.AddText( 255, 60 + ( index * 20 ), LabelHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ));
-
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + ( 100 * pageNum ) + i );
- }
- myGump.Send( socket );
- myGump.Free();
-}
+const ovens = [
+ 0x0461, 0x046f, 0x092b, 0x093f
+];
-const mills = [
- 0x188b, 0x1893, 0x1920, 0x1922, 0x192c, 0x192e
+const heatSources = [
+ 0x0461, 0x0480, 0x092B, 0x0933, 0x0937, 0x0942, 0x0945, 0x0950, 0x0953,
+ 0x095e, 0x0961, 0x096c, 0x0de3, 0x0de8, 0x0fac
];
+
function FindNearbyMills( pUser, trgItem, pSock )
{
if( !ValidateObject( trgItem ) || !trgItem.isItem )
@@ -76,9 +33,6 @@ function FindNearbyMills( pUser, trgItem, pSock )
return ( mills.indexOf( trgItem.id ) != -1 );
}
-const ovens = [
- 0x461, 0x46F, 0x92B, 0x93F
-];
function FindNearbyOvens( pUser, trgItem, pSock )
{
if( !ValidateObject( trgItem ) || !trgItem.isItem )
@@ -87,10 +41,6 @@ function FindNearbyOvens( pUser, trgItem, pSock )
return ( ovens.indexOf( trgItem.id ) != -1 );
}
-const heatSources = [
- 0x0461, 0x0480, 0x092B, 0x0933, 0x0937, 0x0942, 0x0945, 0x0950, 0x0953,
- 0x095e, 0x0961, 0x096c, 0x0de3, 0x0de8, 0x0fac
-];
function FindNearbyHeatSources( pUser, trgItem, pSock )
{
if( !ValidateObject( trgItem ) || !trgItem.isItem )
@@ -99,291 +49,400 @@ function FindNearbyHeatSources( pUser, trgItem, pSock )
return ( heatSources.indexOf( trgItem.id ) != -1 );
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
-function onTimer( pUser, timerID )
+const craftMapRegistryID = 4038;
+var CookingMap = {};
+
+/** @type { () => boolean } */
+function LoadCookingMap()
{
- if( !ValidateObject( pUser ))
- return;
+ CookingMap = {};
- var socket = pUser.socket;
+ var cookingEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "cooking" );
- switch ( timerID )
+ if( !cookingEntries || !IsCookingArrayValue( cookingEntries ) )
{
- case 1: // Page 1 - Ingredients
- case 2: // Page 2 - Preparation
- case 3: // Page 3 - Baking
- case 4: // Page 4 - Barbecue
- PageX( socket, pUser, timerID );
- break;
+ Console.Warning( "Cooking: Unable to load cooking craft map data." );
+ return false;
+ }
+
+ for( var i = 0; i < cookingEntries.length; i++ )
+ {
+ var entry = cookingEntries[i];
+
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = cookingSkillID;
+
+ CookingMap[entry.makeID] = entry;
}
+
+ Console.Print( "Cooking: Loaded " + cookingEntries.length + " craft map entries.\n" );
+ return true;
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+/** @type { ( value: any ) => boolean } */
+function IsCookingArrayValue( value )
{
- var pUser = pSock.currentChar;
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
- // Don't continue if character is invalid, or worse... dead!
- if( !ValidateObject( pUser ) || pUser.dead )
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
+function PageX( socket, pUser, pageNum )
+{
+ if( !socket || !ValidateObject( pUser ))
return;
- // Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ if( !CookingMap || Object.keys( CookingMap ).length == 0 )
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
- return;
+ if( !LoadCookingMap() )
+ {
+ socket.SysMessage( "Cooking craft map failed to load." );
+ return;
+ }
}
- if( bItem.movable == 3 )
- {
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
- return;
- }
+ var pageItems;
- var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ // No "Last Ten" page here (but we keep the infrastructure if you want it later)
+ if( pageNum == 999 )
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ var lastTenRaw = pUser.GetTempTag( "LastTenCooking" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
- return;
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val );
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
- return;
- }
-
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var timerID = 0;
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in CookingMap )
+ {
+ if( !CookingMap.hasOwnProperty( key ))
+ continue;
- // Check for nearby heatsource
- var nearbyHeatSource = 0;
- var nearbyMill = 0;
- var nearbyOven = 0;
+ var makeID = parseInt( key );
+ var data = CookingMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
- // Make Last Button
- if( pButton == 5000 )
- {
- pButton = pUser.GetTempTag( "MAKELAST" );
- }
+ makeIDs.push( makeID );
+ }
- if( pButton >= 300 && pButton <= 311 )
- {
- // Baking - Requires Oven
- nearbyOven = AreaItemFunction( "FindNearbyOvens", pUser, 2, pSock );
- if( nearbyOven > 0 )
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
+ {
+ var ea = CookingMap[a];
+ var eb = CookingMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering (hooks for future use)
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ var id = makeIDs[k];
+ var data2 = CookingMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )) )
+ pageItems.push( id );
}
- else
+
+ // Fallback: if no items on this page and it's not page 1, go to page 1
+ if( pageItems.length == 0 && pageNum != 1 )
{
- // No dynamic oven found nearby! Look for a static one?
- var staticFound = false;
- for( var i = 0; i < ovens.length; i++ )
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var key2 in CookingMap )
{
- if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, ovens[i] ))
- {
- staticFound = true;
- break;
- }
+ if( !CookingMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = CookingMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
}
- if( !staticFound )
+
+ makeIDs.sort( function( a, b )
{
- pUser.SetTempTag( "prevActionResult", "NOOVEN" );
+ var ea2 = CookingMap[a];
+ var eb2 = CookingMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = CookingMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )) )
+ pageItems.push( id2 );
}
}
}
- else if( pButton >= 400 && pButton <= 405 )
+
+ // Subpage handling (future-proof; currently only 1 subpage per page)
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
{
- // Barbecue - Requires Fire/Generic Heat Source
- nearbyHeatSource = AreaItemFunction( "FindNearbyHeatSources", pUser, 2, pSock );
- if( nearbyHeatSource > 0 )
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var cookGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", cookGump, socket );
+ cookGump.AddPage( 1 );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // use makeID directly as buttonID
+
+ var data5 = CookingMap[makeID];
+
+ if( !data5 )
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ entryText = "[Missing MakeID: " + makeID + "]";
}
else
{
- // No dynamic heat source found nearby! Look for a static one?
- var staticFound = false;
- for( var i = 0; i < heatSources.length; i++ )
+ if( data5.customName )
{
- if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, heatSources[i] ))
- {
- staticFound = true;
- break;
- }
+ entryText = data5.customName;
}
- if( !staticFound )
+ else if( data5.dictID )
{
- pUser.SetTempTag( "prevActionResult", "NOHEATSOURCE" );
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
}
}
+
+ // Craft button uses makeID
+ cookGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ cookGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+
+ // Detail button: 20000 + makeID
+ cookGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
}
- else if( pButton >= 101 && pButton <= 207 )
+
+ // Prev subpage
+ if( subPage > 1 )
{
- // Ingredients and Preparation - no heatsource required
- pUser.SetTempTag( "MAKELAST", pButton );
+ cookGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ cookGump.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
}
- switch ( pButton )
+ // Next subpage
+ if( subPage < totalSubPages )
{
- case 0: // Abort and do nothing
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;
- case 1: // Page 1 - Ingredients
- case 2: // Page 2 - Preparation
- case 3: // Page 3 - Baking
- case 4: // Page 4 - Barbecue
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- // Make Items
- // Page 1 - Ingredients
- case 100: // Sack of Flour
- makeID = 1500; timerID = 1; break;
- case 101: // Dough
- makeID = 1501; timerID = 1; break;
- case 102: // Sweet Dough
- makeID = 1502; timerID = 1; break;
- case 103: // Cake Mix
- makeID = 1503; timerID = 1; break;
- case 104: // Cookie Mix
- makeID = 1504; timerID = 1; break;
- // Page 2 - Preparation
- case 200: // Unbaked Quiche
- makeID = 1550; timerID = 2; break;
- case 201: // Unbaked Meat Pie
- makeID = 1551; timerID = 2; break;
- case 202: // Uncooked Sausage Pizza
- makeID = 1552; timerID = 2; break;
- case 203: // Uncooked Cheese Pizza
- makeID = 1553; timerID = 2; break;
- case 204: // Unbaked Fruit Pie
- makeID = 1554; timerID = 2; break;
- case 205: // Unbaked Peach Cobbler
- makeID = 1555; timerID = 2; break;
- case 206: // Unbaked Applie Pie
- makeID = 1556; timerID = 2; break;
- case 207: // Unbaked Pumpkin Pie
- makeID = 1557; timerID = 2; break;
- // Page 3 - Baking
- case 300: // Bread Loaf
- makeID = 1600; timerID = 3; break;
- case 301: // Pan of Cookies
- makeID = 1601; timerID = 3; break;
- case 302: // Cake
- makeID = 1602; timerID = 3; break;
- case 303: // Muffins
- makeID = 1603; timerID = 3; break;
- case 304: // Baked Quiche
- makeID = 1604; timerID = 3; break;
- case 305: // Baked Meat Pie
- makeID = 1605; timerID = 3; break;
- case 306: // Sausage Pizza
- makeID = 1606; timerID = 3; break;
- case 307: // Cheese Pizza
- makeID = 1607; timerID = 3; break;
- case 308: // Baked Fruit Pie
- makeID = 1608; timerID = 3; break;
- case 309: // Baked Peach Cobbler
- makeID = 1609; timerID = 3; break;
- case 310: // Baked Applie Pie
- makeID = 1610; timerID = 3; break;
- case 311: // Baked Pumpkin Pie
- makeID = 1611; timerID = 3; break;
- // Page 4 - Barbecue
- case 400: // Cooked Bird
- makeID = 1650; timerID = 4; break;
- case 401: // Chicken Leg
- makeID = 1651; timerID = 4; break;
- case 402: // Fish Steak
- makeID = 1652; timerID = 4; break;
- case 403: // Fried Eggs
- makeID = 1653; timerID = 4; break;
- case 404: // Leg of Lamb
- makeID = 1654; timerID = 4; break;
- case 405: // Cut of Ribs
- makeID = 1655; timerID = 4; break;
- // Show Item Details
- case 2100: // Sack of Flour
- itemDetailsID = 1500; break;
- case 2101: // Dough
- itemDetailsID = 1501; break;
- case 2102: // Sweet Dough
- itemDetailsID = 1502; break;
- case 2103: // Cake Mix
- itemDetailsID = 1503; break;
- case 2104: // Cookie Mix
- itemDetailsID = 1504; break;
- case 2200: // Unbaked Quiche
- itemDetailsID = 1550; break;
- case 2201: // Unbaked Meat Pie
- itemDetailsID = 1551; break;
- case 2202: // Uncooked Sausage Pizza
- itemDetailsID = 1552; break;
- case 2203: // Uncooked Cheese Pizza
- itemDetailsID = 1553; break;
- case 2204: // Unbaked Fruit Pie
- itemDetailsID = 1554; break;
- case 2205: // Unbaked Peach Cobbler
- itemDetailsID = 1555; break;
- case 2206: // Unbaked Apple Pie
- itemDetailsID = 1556; break;
- case 2207: // Unbaked Pumpkin Pie
- itemDetailsID = 1557; break;
- case 2300: // Bread Loaf
- itemDetailsID = 1600; break;
- case 2301: // Pan of Cookies
- itemDetailsID = 1601; break;
- case 2302: // Cake
- itemDetailsID = 1602; break;
- case 2303: // Muffins
- itemDetailsID = 1603; break;
- case 2304: // Baked Quiche
- itemDetailsID = 1604; break;
- case 2305: // Baked Meat Pie
- itemDetailsID = 1605; break;
- case 2306: // Sausage Pizza
- itemDetailsID = 1606; break;
- case 2307: // Cheese Pizza
- itemDetailsID = 1607; break;
- case 2308: // Baked Fruit Pie
- itemDetailsID = 1608; break;
- case 2309: // Baked Peach Cobbler
- itemDetailsID = 1609; break;
- case 2310: // Baked Apple Pie
- itemDetailsID = 1610; break;
- case 2311: // Baked Pumpkin Pie
- itemDetailsID = 1611; break;
- case 2400: // Cooked Bird
- itemDetailsID = 1650; break;
- case 2401: // Chicken Leg
- itemDetailsID = 1651; break;
- case 2402: // Fish Steak
- itemDetailsID = 1652; break;
- case 2403: // Fried Eggs
- itemDetailsID = 1653; break;
- case 2404: // Leg of Lamb
- itemDetailsID = 1654; break;
- case 2405: // Cut of Ribs
- itemDetailsID = 1655; break;
- default:
- break;
+ cookGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ cookGump.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
+ }
+
+ cookGump.Send( socket );
+ cookGump.Free();
+}
+
+/** @type { ( pUser: Character, timerID: number ) => void } */
+function onTimer( pUser, timerID )
+{
+ if( !ValidateObject( pUser ))
+ return;
+
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
+
+ if( timerID >= 1 && timerID <= 4 )
+ {
+ PageX( pSocket, pUser, timerID );
}
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
+ }
+}
- if( makeID != 0 )
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
+{
+ if( socket == null )
+ return;
+
+ var pUser = socket.currentChar;
+ if( !ValidateObject( pUser ) || pUser.dead )
+ return;
+
+ // Don't continue if player no longer has access to the cooking tool
+ var tool = socket.tempObj;
+ if( !ValidateObject( tool ) || !pUser.InRange( tool, 3 ))
{
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
+ return;
+ }
+
+ if( tool.movable == 3 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
+ return;
+ }
+
+ var packOwner = GetPackOwner( tool, 0 );
+ if( ValidateObject( packOwner ))
+ {
+ if( packOwner.serial != pUser.serial )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That is in someone else's backpack!
+ return;
+ }
+ }
+ else
+ {
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This must be in your pack to use.
+ return;
+ }
+
+ var gumpID = cookingID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
+ {
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
+ }
+
+ if( pButton >= 9001 && pButton < 10000 )
+ {
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs (1–4: Ingredients, Preparation, Baking, Barbecue)
+ if( pButton >= 1 && pButton <= 4 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
+ return;
+ }
+
+ // Last Ten (if you ever wire it into the main craft gump)
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Cooking", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Cooking" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ var makeID = 0;
+ var timerID = 0;
+
+ // Craft buttons use makeID directly
+ if( CookingMap[pButton] != undefined )
+ {
+ makeID = pButton;
+ var data = CookingMap[makeID];
+ timerID = data.timerID || 1;
+
+ // Era / recipe checks
+ if( !eraOK( data ))
+ {
+ socket.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ pUser.SetTempTag( "MakeLast_Cooking", makeID );
+
+ // Environment checks
var makeItem = false;
+
if( makeID == 1500 )
{
- // Grind wheat to flour
+ // Sack of Flour – wheat grinding
if( manualMillTarget )
{
// Require player to manually target mill
@@ -393,34 +452,38 @@ function onGumpPress( pSock, pButton, gumpData )
{
if( ValidateObject( packItem ) && packItem.id == 0x1ebd )
{
- // Found wheat item in inventory, ask user where to grind it
+ // Found wheat; call its onUseChecked
TriggerEvent( 101, "onUseChecked", pUser, packItem ); // 101 = wheat.js
return;
}
}
+
+ // No wheat found – just fail silently and let DFN/msg handle it
+ return;
}
else
{
- // Look for nearby dynamic mill
- var nearbyMill = AreaItemFunction( "FindNearbyMills", pUser, 2, pSock );
+ // Auto mill lookup (dynamic first, then static)
+ var nearbyMill = AreaItemFunction( "FindNearbyMills", pUser, 2, socket );
if( nearbyMill == 0 )
{
- // No dynamic oven found nearby! Look for a static one?
var staticFound = false;
- for( var i = 0; i < ovens.length; i++ )
+ for( var i = 0; i < mills.length; i++ )
{
- if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, ovens[i] ))
+ if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, mills[i] ))
{
- nearbyMill = 1;
+ staticFound = true;
break;
}
}
+
+ if( staticFound )
+ nearbyMill = 1;
}
if( nearbyMill > 0 )
{
makeItem = true;
- pUser.SetTempTag( "MAKELAST", pButton );
}
else
{
@@ -428,41 +491,191 @@ function onGumpPress( pSock, pButton, gumpData )
}
}
}
- else if( makeID >= 1650 && makeID <= 1655 && nearbyHeatSource > 0 )
+ else if( makeID >= 1600 && makeID <= 1611 )
{
- // Barbecue
- makeItem = true;
+ // Baking – requires oven
+ var nearbyOven = AreaItemFunction( "FindNearbyOvens", pUser, 2, socket );
+ if( nearbyOven > 0 )
+ {
+ makeItem = true;
+ }
+ else
+ {
+ var staticFoundOven = false;
+ for( var o = 0; o < ovens.length; o++ )
+ {
+ if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, ovens[o] ))
+ {
+ staticFoundOven = true;
+ break;
+ }
+ }
+ if( !staticFoundOven )
+ pUser.SetTempTag( "prevActionResult", "NOOVEN" );
+ else
+ makeItem = true;
+ }
}
- else if( makeID >= 1600 && makeID <= 1611 && nearbyOven > 0 )
+ else if( makeID >= 1650 && makeID <= 1655 )
{
- // Baking
- makeItem = true;
+ // Barbecue – requires generic heat source
+ var nearbyHeat = AreaItemFunction( "FindNearbyHeatSources", pUser, 2, socket );
+ if( nearbyHeat > 0 )
+ {
+ makeItem = true;
+ }
+ else
+ {
+ var staticFoundHeat = false;
+ for( var h = 0; h < heatSources.length; h++ )
+ {
+ if( StaticInRange( pUser.x, pUser.y, pUser.worldnumber, 2, heatSources[h] ))
+ {
+ staticFoundHeat = true;
+ break;
+ }
+ }
+ if( !staticFoundHeat )
+ pUser.SetTempTag( "prevActionResult", "NOHEATSOURCE" );
+ else
+ makeItem = true;
+ }
}
- else if( makeID >= 1501 && makeID <= 1557 )
+ else if( ( makeID >= 1501 && makeID <= 1557 ) )
{
- // Ingredients/Baking - no heatsource or mill required
+ // Ingredients & preparations – no heat source or mill required
makeItem = true;
}
if( makeItem )
{
- MakeItem( pSock, pUser, makeID );
+ MakeItem( socket, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
if( GetServerSetting( "ToolUseLimit" ))
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ tool.usesLeft -= 1;
+ if( tool.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ tool.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
}
}
}
- pUser.StartTimer( gumpDelay, timerID, true );
+
+ pUser.StartTimer( gumpDelay, timerID, cookingID );
+ return;
}
- else if( itemDetailsID != 0 )
+
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ var detailMakeID = pButton - 20000;
+ var entry = CookingMap[detailMakeID];
+
+ if( entry )
+ {
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || cookingSkillID );
+
+ // Clear old harvest tags
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old custom harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // Optional harvest dictIDs
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // Optional custom names – plug into your new ItemDetail custom text logic
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
+ }
+}
+
+// Last Ten handling
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenCooking" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
}
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenCooking", newList.join( "," ) );
}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/craftgump.js b/data/js/skill/craft/craftgump.js
index 2e894f588..9674ba742 100644
--- a/data/js/skill/craft/craftgump.js
+++ b/data/js/skill/craft/craftgump.js
@@ -14,38 +14,24 @@ const Tinkering = 4032;
const Cooking = 4034;
const Cartography = 4035;
const Glassblowing = 4036;
+const Masonry = 4037;
// If enabled, players can craft coloured variants of weapons using Blacksmithing skill, though
// unless the craftItems array in blacksmithing.js is updated with specific create entries for the
// coloured weapon variants, they'll just be regular weapons with ore colour applied
const allowColouredWeapons = GetServerSetting( "CraftColouredWeapons" );
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
function CraftingGumpMenu( myGump, socket )
{
var pUser = socket.currentChar;
-
- // Get player's resource counts
- var iron = pUser.ResourceCount( 0x1BF2 );
- var dullcopper = pUser.ResourceCount( 0x1BF2, 0x0973 );
- var shadowiron = pUser.ResourceCount( 0x1BF2, 0x0966 );
- var copper = pUser.ResourceCount( 0x1BF2, 0x07dd );
- var bronze = pUser.ResourceCount( 0x1BF2, 0x06d6 );
- var gold = pUser.ResourceCount( 0x1BF2, 0x08a5 );
- var agapite = pUser.ResourceCount( 0x1BF2, 0x0979 );
- var verite = pUser.ResourceCount( 0x1BF2, 0x089f );
- var valorite = pUser.ResourceCount( 0x1BF2, 0x08ab );
- var logs = pUser.ResourceCount( 0x1BE0 );
- var boards = pUser.ResourceCount( 0x1bd7 );
- var leather = pUser.ResourceCount( 0x1067 );
- var leather1 = pUser.ResourceCount( 0x1068 );
- var leather2 = pUser.ResourceCount( 0x1081 );
- var leather3 = pUser.ResourceCount( 0x1082 );
- var hides = pUser.ResourceCount( 0x1078 );
- var hides1 = pUser.ResourceCount( 0x1079 );
-
var resourcename = 10291;
- var resource = iron;
- var groupList;
+ var resourcename2 = 10291;
+ var resource = 0;
+ var resource2 = 0;
+ var resourceSelection = null;
+ var resourceSelection2 = null;
+ var grouplist = [];
var gumpMenuName = "";
var repair = 51;
var craftingSkillUsed = pUser.GetTempTag( "CRAFT" );
@@ -53,10 +39,11 @@ function CraftingGumpMenu( myGump, socket )
switch( craftingSkillUsed )
{
case 1: // Carpentry
- grouplist = [10601, 10602, 10603, 10604, 10605, 10606, 10607, 10608, 10609, 10610]; //CATEGORIES
- resourcename = 10687;
- resource = ( logs + boards );
- gumpMenuName = 10600;//Carpentry
+ grouplist = [10601, 10602, 10603, 10604, 10605, 10606, 10607, 10608, 10609, 10610];
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "wood", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 10687;
+ resource = resourceSelection ? resourceSelection.amount : 0;
+ gumpMenuName = 10600;
repair = 51;
break;
case 2: // Alchemy
@@ -64,65 +51,28 @@ function CraftingGumpMenu( myGump, socket )
gumpMenuName = 10901;//Alchemy Menu
break;
case 3: // Bowcraft/Fletching
- grouplist = [11202, 11203, 11204]; //CATEGORIES
- gumpMenuName = 11201;//Bowcraft Menu
- resourcename = 10687;
- resource = ( logs + boards );
- //repair = 51;
+ grouplist = [11202, 11203, 11204];
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "wood", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 10687;
+ resource = resourceSelection ? resourceSelection.amount : 0;
+ gumpMenuName = 11201;
break;
case 4: // Tailoring
- grouplist = [11404, 11405, 11406, 11407, 11408, 11410, 11411, 11412]; //CATEGORIES
- gumpMenuName = 11401;//Tailoring Menu
- resourcename = 11402;
- resource = ( leather + leather1 + leather2 + leather3 + hides + hides1 );
- //repair = 51;
+ grouplist = [11404, 11405, 11406, 11407, 11408, 11410, 11411, 11412];
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "tailoring", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 11402;
+ resource = resourceSelection ? resourceSelection.amount : 0;
+ gumpMenuName = 11401;
break;
case 5: // Blacksmithing
- grouplist = [10279, 10280, 10281, 10282, 10283, 10284, 10285] //CATEGORIES
- gumpMenuName = 10188;//Blacksmithing Menu
- switch( pUser.GetTempTag( "ORE" ))
- {
- case 0: // Iron
- resourcename = 10291;
- resource = iron;
- break;
- case 1: // Dull Copper
- resourcename = 10203;
- resource = dullcopper;
- break;
- case 2: // Shadow Iron
- resourcename = 10204;
- resource = shadowiron;
- break;
- case 3: // Copper
- resourcename = 10205;
- resource = copper;
- break;
- case 4: // Bronze
- resourcename = 10206;
- resource = bronze;
- break;
- case 5: // Gold
- resourcename = 10207;
- resource = gold;
- break;
- case 6: // Agapite
- resourcename = 10208;
- resource = agapite;
- break;
- case 7: // Verite
- resourcename = 10209;
- resource = verite;
- break;
- case 8: // Valorite
- resourcename = 10210;
- resource = valorite;
- break;
- default: // Iron
- resourcename = 10291;
- resource = iron;
- break;
- }
+ grouplist = [10279, 10280, 10281, 10282, 10283, 10284, 10285];
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "ore", pUser );
+ resourceSelection2 = TriggerEvent( 4038, "GetCraftResourceSelection", "dragonScales", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 10291;
+ resource = resourceSelection ? resourceSelection.amount : 0;
+ resourcename2 = resourceSelection2 ? resourceSelection2.dictID : 20299;
+ resource2 = resourceSelection2 ? resourceSelection2.amount : 0;
+ gumpMenuName = 10188;
repair = 49;
break;
case 6: // Cooking
@@ -131,6 +81,9 @@ function CraftingGumpMenu( myGump, socket )
break;
case 7: // Tinkering
grouplist = [11991, 11992, 11993, 11994, 11995, 11996, 11997, 11998, 11999]; // CATEGORIES
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "ore", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 10291;
+ resource = resourceSelection ? resourceSelection.amount : 0;
gumpMenuName = 11990; // Tinkering Menu
break;
case 8: // Cartography
@@ -139,8 +92,21 @@ function CraftingGumpMenu( myGump, socket )
break;
case 9: // Glassblowing
grouplist = [13502]; //CATEGORIES
+ // Only show Glass Weapons (page 2) in SA+ era
+ if( coreShardEra >= EraStringToNum( "sa" ))
+ {
+ grouplist.push( 13503 ); // Glass Weapons
+ }
gumpMenuName = 13501;//Cartography Menu
- break;
+ break;
+ case 10: // Masonry
+ grouplist = [14002, 14003, 14004, 14005, 14006, 14007, 14008, 14009, 14010];
+ resourceSelection = TriggerEvent( 4038, "GetCraftResourceSelection", "granite", pUser );
+ resourcename = resourceSelection ? resourceSelection.dictID : 14011;
+ resource = resourceSelection ? resourceSelection.amount : 0;
+ gumpMenuName = 14001;
+ repair = 49;
+ break;
}
myGump.AddPage( 0 );
@@ -249,7 +215,7 @@ function CraftingGumpMenu( myGump, socket )
break;
}
- if( craftingSkillUsed != 2 && craftingSkillUsed != 6 && craftingSkillUsed != 8 )
+ if( craftingSkillUsed != 2 && craftingSkillUsed != 6 && craftingSkillUsed != 8 && craftingSkillUsed != 9 )
{
myGump.AddText( 50, 362, textHue, GetDictionaryEntry( resourcename, socket.language ) + " (" + resource.toString() + ")" );
@@ -261,6 +227,8 @@ function CraftingGumpMenu( myGump, socket )
if( craftingSkillUsed == 5 )
{
+ myGump.AddText( 50, 380, textHue, GetDictionaryEntry( resourcename2, socket.language ) + " (" + resource2.toString() + ")" );
+ myGump.AddButton(15, 380, 4005, 4007, 1, 0, 51); // Material Selection Button
// Blacksmithing
myGump.AddButton(270, 342, 0xfa5, 1, 0, repair); // Repair Button
myGump.AddHTMLGump(305, 345, 150, 18, 0, 0, " " + GetDictionaryEntry( 10212, socket.language ) + "" );// REPAIR ITEM
@@ -346,8 +314,12 @@ function onGumpPress( pSock, pButton, gumpData )
break;
case 9:
TriggerEvent( Glassblowing, "PageX", pSock, pUser, 1 );
- break;
- default:
+ break;
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 1 );
+ }
break;
}
case 2:
@@ -377,8 +349,11 @@ function onGumpPress( pSock, pButton, gumpData )
break;
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 2 );
- break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 2 );
+ }
break;
}
case 3:
@@ -409,7 +384,11 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 3 );
break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 3 );
+ }
break;
}
case 4:
@@ -437,7 +416,11 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 4 );
break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 4 );
+ }
break;
}
case 5:
@@ -459,7 +442,11 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 5 );
break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 5 );
+ }
break;
}
case 6:
@@ -481,7 +468,11 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 6 );
break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 6 );
+ }
break;
}
case 7:
@@ -503,7 +494,11 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 7 );
break;
- default:
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 7 );
+ }
break;
}
case 8:
@@ -519,6 +514,12 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 8 );
break;
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 8 );
+ }
+ break;
}
break;
case 9:
@@ -534,6 +535,12 @@ function onGumpPress( pSock, pButton, gumpData )
case 7:
TriggerEvent( Tinkering, "PageX", pSock, pUser, 9 );
break;
+ case 10:
+ if( pUser.GetTempTag( "Granite" ) >= 0 && pUser.GetTempTag( "Granite" ) <= 8 )
+ {
+ TriggerEvent( Masonry, "PageX", pSock, pUser, 9 );
+ }
+ break;
}
break;
case 10:
diff --git a/data/js/skill/craft/crafting_complete.js b/data/js/skill/craft/crafting_complete.js
index 5f289098a..ce2051755 100644
--- a/data/js/skill/craft/crafting_complete.js
+++ b/data/js/skill/craft/crafting_complete.js
@@ -34,7 +34,16 @@ function onMakeItem( pSock, pCrafter, itemCrafted, craftEntryID )
// Apply special effect based on item crafted
if( ValidateObject( itemCrafted ))
{
- // Jewelry, furniture, potions, maps
+ var craftMapEntry = TriggerEvent( 4038, "GetCraftMapEntryByMakeID", craftEntryID );
+ if( craftMapEntry && craftMapEntry.craftComplete )
+ {
+ if( HandleCraftCompleteType( pSock, pCrafter, itemCrafted, craftEntryID, craftMapEntry.craftComplete ))
+ {
+ return;
+ }
+ }
+
+ // Fallback support for older entries not yet moved into JSON
switch( craftEntryID )
{
case 239: // necklace
@@ -156,6 +165,92 @@ function onMakeItem( pSock, pCrafter, itemCrafted, craftEntryID )
ClearTagsAndScript( pCrafter );
}
+/** @type { ( pSock: Socket, pCrafter: Character, itemCrafted: Item, craftEntryID: number, craftComplete: object ) => boolean } */
+function HandleCraftCompleteType( pSock, pCrafter, itemCrafted, craftEntryID, craftComplete )
+{
+ if( !craftComplete || !craftComplete.type )
+ {
+ return false;
+ }
+
+ switch( craftComplete.type )
+ {
+ case "gemJewelry":
+ ApplyGemJewelryName( pCrafter, itemCrafted );
+ ClearTagsAndScript( pCrafter );
+ return true;
+
+ case "lockableContainer":
+ ApplyCraftedContainerLock( pSock, pCrafter, itemCrafted, craftEntryID );
+ ClearTagsAndScript( pCrafter );
+ return true;
+
+ case "autoIdentifyPotion":
+ ApplyPotionAutoIdentify( itemCrafted );
+ ClearTagsAndScript( pCrafter );
+ return true;
+
+ case "craftedMap":
+ CraftedMapCoords( pSock, itemCrafted );
+ ClearTagsAndScript( pCrafter );
+ return true;
+ }
+
+ return false;
+}
+
+/** @type { ( itemCrafted: Item ) => void } */
+function ApplyPotionAutoIdentify( itemCrafted )
+{
+ itemCrafted.name = itemCrafted.name2;
+ itemCrafted.name2 = "#";
+}
+
+/** @type { ( pSock: Socket, pCrafter: Character, itemCrafted: Item, craftEntryID: number ) => void } */
+function ApplyCraftedContainerLock( pSock, pCrafter, itemCrafted, craftEntryID )
+{
+ var createEntry = CreateEntries[craftEntryID];
+ if( !createEntry )
+ return;
+
+ var skills = createEntry.skills;
+ if( !skills )
+ return;
+
+ for( var i = 0; i < skills.length; i++ )
+ {
+ var skillReq = skills[i];
+ var skillNumber = skillReq[0];
+ var minSkill = skillReq[1];
+
+ if( skillNumber == 11 ) // Carpentry
+ {
+ if( pCrafter.skills.tinkering >= RandomNumber( minSkill / 2, 1000 ))
+ {
+ var newKey = CreateDFNItem( pSock, pCrafter, "0x100E", 1, "ITEM", true );
+ newKey.container = itemCrafted;
+ newKey.PlaceInPack();
+ newKey.name = "key for " + itemCrafted.name;
+
+ newKey.more = itemCrafted.serial;
+ itemCrafted.more = itemCrafted.serial;
+ pSock.SysMessage( GetDictionaryEntry( 12009, pSock.language ));
+ }
+ break;
+ }
+ }
+}
+
+/** @type { ( pCrafter: Character, itemCrafted: Item ) => void } */
+function ApplyGemJewelryName( pCrafter, itemCrafted )
+{
+ var gemName = pCrafter.GetTempTag( "targetedSubResourceName" );
+ if( gemName )
+ {
+ itemCrafted.name = gemName + " " + itemCrafted.name;
+ }
+}
+
function CraftedMapCoords( socket, mapItem )
{
var pUser = socket.currentChar;
@@ -194,7 +289,7 @@ function CraftedMapCoords( socket, mapItem )
mapItem.SetTag( "dimensions", size + "," + size ); // saves information for the map to be reopened
mapItem.SetTag( "boundingbox", ( pUser.x - dist ) + "," + ( pUser.y - dist ) + "," + ( pUser.x + dist ) + "," + ( pUser.y + dist )); // saves information for the map to be reopened
mapItem.SetTag( "Drawn", 1 );
- ClearTagsAndScript(pUser);
+ ClearTagsAndScript( pUser );
}
function ApplyExceptionalArmorBonuses( pCrafter, itemCrafted, coreShardEraValue )
diff --git a/data/js/skill/craft/craftmap_registry.js b/data/js/skill/craft/craftmap_registry.js
new file mode 100644
index 000000000..580f54860
--- /dev/null
+++ b/data/js/skill/craft/craftmap_registry.js
@@ -0,0 +1,508 @@
+///
+// @ts-check
+
+var craftMapRegistry = {};
+var craftMapRegistryLoaded = {};
+var craftMapRegistryLoadError = {};
+var craftResourceMap = null;
+var craftResourceMapLoaded = false;
+var craftResourceMapLoadError = false;
+
+/** @type { () => object|null } */
+function LoadCraftResourceMap()
+{
+ if( craftResourceMapLoaded )
+ return craftResourceMap;
+
+ craftResourceMap = null;
+ craftResourceMapLoaded = false;
+ craftResourceMapLoadError = false;
+
+ var resourceMapFile = new UOXCFile();
+ resourceMapFile.Open( "resourcemap.json", "r", "crafting", true );
+
+ if( resourceMapFile == null || resourceMapFile.Length() < 0 )
+ {
+ Console.Error( "CraftMap system: Unable to open js/jsdata/crafting/resourcemap.json" );
+ craftResourceMapLoadError = true;
+ return null;
+ }
+
+ var fileText = "";
+ while( !resourceMapFile.EOF() )
+ {
+ var rawLine = resourceMapFile.ReadUntil( "\n" );
+ if( rawLine != null && typeof( rawLine ) != "undefined" )
+ {
+ fileText += rawLine;
+ }
+ }
+
+ resourceMapFile.Close();
+ resourceMapFile.Free();
+
+ fileText = SanitizeCraftMapJsonText( fileText );
+
+ try
+ {
+ craftResourceMap = JSON.parse( fileText );
+ }
+ catch( error )
+ {
+ Console.Error( "CraftMap system: Failed to parse resourcemap.json: " + error );
+ craftResourceMapLoadError = true;
+ return null;
+ }
+
+ if( !IsCraftMapArrayValue( craftResourceMap ) )
+ {
+ Console.Error( "CraftMap system: resourcemap.json must contain a JSON array." );
+ craftResourceMapLoadError = true;
+ return null;
+ }
+
+ craftResourceMapLoaded = true;
+ craftResourceMapLoadError = false;
+
+ Console.Print( "CraftMap system: Loaded " + craftResourceMap.length + " resource maps.\n" );
+
+ return craftResourceMap;
+}
+
+/** @type { ( resourceSet: string, pUser: Character ) => object|null } */
+function GetCraftResourceSelection( resourceSet, pUser )
+{
+ var resourceMap = LoadCraftResourceMap();
+
+ if( !resourceMap )
+ return null;
+
+ for( var i = 0; i < resourceMap.length; i++ )
+ {
+ var entry = resourceMap[i];
+
+ if( entry.resourceSet == resourceSet )
+ return BuildCraftResourceSelection( entry, pUser );
+ }
+
+ return null;
+}
+
+/** @type { ( resourceSet: string ) => object|null } */
+function GetCraftResourceList( resourceSet )
+{
+ var resourceMap = LoadCraftResourceMap();
+
+ if( !resourceMap )
+ return null;
+
+ for( var i = 0; i < resourceMap.length; i++ )
+ {
+ if( resourceMap[i].resourceSet == resourceSet )
+ return resourceMap[i];
+ }
+
+ return null;
+}
+
+/** @type { ( resourceEntry: object, pUser: Character ) => object|null } */
+function BuildCraftResourceSelection( resourceEntry, pUser )
+{
+ if( !resourceEntry || !resourceEntry.items || !IsCraftMapArrayValue( resourceEntry.items ) )
+ return null;
+
+ var selectedIndex = resourceEntry.defaultIndex || 0;
+
+ if( resourceEntry.tempTag )
+ {
+ var tempIndex = pUser.GetTempTag( resourceEntry.tempTag );
+ if( tempIndex || tempIndex == 0 )
+ selectedIndex = tempIndex;
+ }
+
+ var selectedResource = null;
+
+ for( var i = 0; i < resourceEntry.items.length; i++ )
+ {
+ var itemEntry = resourceEntry.items[i];
+
+ if( typeof itemEntry.index == "undefined" )
+ {
+ selectedResource = itemEntry;
+ break;
+ }
+
+ if( itemEntry.index == selectedIndex )
+ {
+ selectedResource = itemEntry;
+ break;
+ }
+ }
+
+ if( !selectedResource )
+ selectedResource = resourceEntry.items[0];
+
+ return {
+ dictID: selectedResource.dictID || resourceEntry.dictID,
+ amount: CountCraftResourceAmount( selectedResource, pUser ),
+ resource: selectedResource
+ };
+}
+
+/** @type { ( resourceEntry: object, pUser: Character ) => number } */
+function CountCraftResourceAmount( resourceEntry, pUser )
+{
+ if( !resourceEntry )
+ return 0;
+
+ if( resourceEntry.items && IsCraftMapArrayValue( resourceEntry.items ) )
+ {
+ var totalAmount = 0;
+
+ for( var i = 0; i < resourceEntry.items.length; i++ )
+ {
+ totalAmount += CountCraftResourceAmount( resourceEntry.items[i], pUser );
+ }
+
+ return totalAmount;
+ }
+
+ return pUser.ResourceCount( resourceEntry.itemID, resourceEntry.hue || 0 );
+}
+
+/** @type { () => void } */
+function ReloadCraftResourceMap()
+{
+ craftResourceMap = null;
+ craftResourceMapLoaded = false;
+ craftResourceMapLoadError = false;
+
+ LoadCraftResourceMap();
+}
+
+/** @type { ( craftName: string ) => object|null } */
+function CraftMapRegistry( craftName )
+{
+ craftName = NormalizeCraftMapName( craftName );
+
+ if( craftName == "" )
+ {
+ Console.Warning( "CraftMap system: Missing craft map name." );
+ return null;
+ }
+
+ if( !craftMapRegistryLoaded[craftName] )
+ {
+ LoadCraftMapRegistry( craftName );
+ }
+
+ return craftMapRegistry[craftName] || null;
+}
+
+/** @type { ( makeID: number ) => object|null } */
+function GetCraftMapEntryByMakeID( makeID )
+{
+ makeID = parseInt( makeID, 10 );
+
+ if( isNaN( makeID ))
+ return null;
+
+ var craftNames = [
+ "alchemy",
+ "blacksmithing",
+ "carpentry",
+ "cartography",
+ "cooking",
+ "fletching",
+ "glassblowing",
+ "masonry",
+ "tailoring",
+ "tinkering"
+ ];
+
+ for( var i = 0; i < craftNames.length; i++ )
+ {
+ var craftMap = CraftMapRegistry( craftNames[i] );
+
+ if( !craftMap )
+ continue;
+
+ for( var j = 0; j < craftMap.length; j++ )
+ {
+ var entry = craftMap[j];
+
+ if( !entry )
+ continue;
+
+ if( entry.makeID == makeID )
+ return entry;
+
+ if( entry.oreMake && CraftMapArrayHasValue( entry.oreMake, makeID ))
+ return entry;
+
+ if( entry.graniteMake && CraftMapArrayHasValue( entry.graniteMake, makeID ))
+ return entry;
+ }
+ }
+
+ return null;
+}
+
+/** @type { ( valueList: any, valueToFind: number ) => boolean } */
+function CraftMapArrayHasValue( valueList, valueToFind )
+{
+ if( !IsCraftMapArrayValue( valueList ))
+ return false;
+
+ for( var i = 0; i < valueList.length; i++ )
+ {
+ if( parseInt( valueList[i], 10 ) == valueToFind )
+ return true;
+ }
+
+ return false;
+}
+
+/** @type { ( craftName: string ) => void } */
+function ReloadCraftMapRegistry( craftName )
+{
+ craftName = NormalizeCraftMapName( craftName );
+
+ if( craftName == "" )
+ {
+ craftMapRegistry = {};
+ craftMapRegistryLoaded = {};
+ craftMapRegistryLoadError = {};
+ return;
+ }
+
+ craftMapRegistry[craftName] = null;
+ craftMapRegistryLoaded[craftName] = false;
+ craftMapRegistryLoadError[craftName] = false;
+
+ LoadCraftMapRegistry( craftName );
+}
+
+/** @type { ( craftName: string ) => void } */
+function LoadCraftMapRegistry( craftName )
+{
+ craftName = NormalizeCraftMapName( craftName );
+
+ craftMapRegistry[craftName] = null;
+ craftMapRegistryLoaded[craftName] = false;
+ craftMapRegistryLoadError[craftName] = false;
+
+ var fileName = craftName + ".json";
+
+ var craftMapFile = new UOXCFile();
+ craftMapFile.Open( fileName, "r", "crafting", true );
+
+ if( craftMapFile == null || craftMapFile.Length() < 0 )
+ {
+ Console.Error( "CraftMap system: Unable to open js/jsdata/crafting/" + fileName );
+ craftMapRegistryLoadError[craftName] = true;
+ return;
+ }
+
+ var fileText = "";
+ while( !craftMapFile.EOF() )
+ {
+ var rawLine = craftMapFile.ReadUntil( "\n" );
+ if( rawLine != null && typeof( rawLine ) != "undefined" )
+ {
+ fileText += rawLine;
+ }
+ }
+
+ craftMapFile.Close();
+ craftMapFile.Free();
+
+ fileText = SanitizeCraftMapJsonText( fileText );
+
+ if( fileText == "" )
+ {
+ Console.Error( "CraftMap system: " + fileName + " is empty after sanitizing." );
+ craftMapRegistryLoadError[craftName] = true;
+ return;
+ }
+
+ var parsedCraftMap = null;
+
+ try
+ {
+ parsedCraftMap = JSON.parse( fileText );
+ }
+ catch( error )
+ {
+ Console.Error( "CraftMap system: Failed to parse " + fileName + ": " + error );
+ craftMapRegistryLoadError[craftName] = true;
+ return;
+ }
+
+ if( !IsCraftMapArrayValue( parsedCraftMap ) )
+ {
+ Console.Error( "CraftMap system: " + fileName + " must contain a JSON array." );
+ craftMapRegistryLoadError[craftName] = true;
+ return;
+ }
+
+ craftMapRegistry[craftName] = [];
+
+ for( var i = 0; i < parsedCraftMap.length; i++ )
+ {
+ var entry = parsedCraftMap[i];
+
+ if( !ValidateCraftMapEntry( entry, craftName, i ) )
+ {
+ continue;
+ }
+
+ craftMapRegistry[craftName].push( entry );
+ }
+
+ Console.Print( "CraftMap system: Loaded " + craftMapRegistry[craftName].length + " entries from " + fileName + ".\n" );
+
+ craftMapRegistryLoaded[craftName] = true;
+ craftMapRegistryLoadError[craftName] = false;
+}
+
+/** @type { ( entry: object, craftName: string, entryIndex: number ) => boolean } */
+function ValidateCraftMapEntry( entry, craftName, entryIndex )
+{
+ if( !entry || typeof entry != "object" || IsCraftMapArrayValue( entry ) )
+ {
+ Console.Warning( "CraftMap system: Entry " + entryIndex + " in '" + craftName + "' is not a valid object." );
+ return false;
+ }
+
+ var hasMakeID = (typeof entry.makeID != "undefined" && !isNaN(parseInt(entry.makeID, 10)));
+ var hasButtonID = (typeof entry.buttonID != "undefined" && !isNaN(parseInt(entry.buttonID, 10)));
+ var hasOreMake = (typeof entry.oreMake != "undefined" && IsCraftMapArrayValue(entry.oreMake));
+ var hasGraniteMake = (typeof entry.graniteMake != "undefined" && IsCraftMapArrayValue(entry.graniteMake));
+
+ if (!hasMakeID && !hasButtonID)
+ {
+ Console.Warning("CraftMap system: Entry " + entryIndex + " in '" + craftName + "' is missing valid makeID or buttonID.");
+ return false;
+ }
+
+ if (hasButtonID && !hasMakeID && !hasOreMake && !hasGraniteMake)
+ {
+ Console.Warning("CraftMap system: Entry " + entryIndex + " in '" + craftName + "' has buttonID but no makeID, oreMake, or graniteMake.");
+ return false;
+ }
+
+ if( typeof entry.page == "undefined" || isNaN( parseInt( entry.page, 10 ) ) )
+ {
+ Console.Warning( "CraftMap system: Entry " + entryIndex + " in '" + craftName + "' is missing valid page." );
+ return false;
+ }
+
+ if( typeof entry.timerID == "undefined" || isNaN( parseInt( entry.timerID, 10 ) ) )
+ {
+ entry.timerID = entry.page;
+ }
+
+ if( typeof entry.dictID != "undefined" && isNaN( parseInt( entry.dictID, 10 ) ) )
+ {
+ Console.Warning( "CraftMap system: Entry " + entryIndex + " in '" + craftName + "' has invalid dictID." );
+ return false;
+ }
+
+ if( typeof entry.harvest == "undefined" )
+ {
+ entry.harvest = [];
+ }
+
+ if( !IsCraftMapArrayValue( entry.harvest ) )
+ {
+ Console.Warning( "CraftMap system: Entry " + entryIndex + " in '" + craftName + "' has invalid harvest list." );
+ return false;
+ }
+
+ if( typeof entry.harvestNames != "undefined" && !IsCraftMapArrayValue( entry.harvestNames ) )
+ {
+ Console.Warning( "CraftMap system: Entry " + entryIndex + " in '" + craftName + "' has invalid harvestNames list." );
+ return false;
+ }
+
+ if( hasMakeID )
+ entry.makeID = parseInt( entry.makeID, 10 );
+
+ if( hasButtonID )
+ entry.buttonID = parseInt( entry.buttonID, 10 );
+
+ entry.page = parseInt( entry.page, 10 );
+ entry.timerID = parseInt( entry.timerID, 10 );
+
+ if( typeof entry.dictID != "undefined" )
+ {
+ entry.dictID = parseInt( entry.dictID, 10 );
+ }
+
+ return true;
+}
+
+/** @type { ( craftName: string ) => string } */
+function NormalizeCraftMapName( craftName )
+{
+ if( craftName == null || typeof( craftName ) == "undefined" )
+ {
+ return "";
+ }
+
+ craftName = String( craftName );
+ craftName = craftName.toLowerCase();
+ craftName = craftName.replace( /[^a-z0-9_]/g, "" );
+
+ return craftName;
+}
+
+/** @type { ( value: any ) => boolean } */
+function IsCraftMapArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+/** @type { ( text: string ) => string } */
+function TrimCraftMapString( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ {
+ return "";
+ }
+
+ return text.replace( /^\s+|\s+$/g, "" );
+}
+
+/** @type { ( text: string ) => string } */
+function SanitizeCraftMapJsonText( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ {
+ return "";
+ }
+
+ text = String( text );
+
+ if( text.length > 0 && text.charCodeAt( 0 ) == 65279 )
+ {
+ text = text.substring( 1 );
+ }
+
+ text = text.split( "\r\n" ).join( "\n" );
+ text = text.split( "\r" ).join( "\n" );
+ text = text.split( String.fromCharCode( 160 ) ).join( " " );
+ text = text.split( String.fromCharCode( 255 ) ).join( "" );
+ text = text.split( "\t" ).join( " " );
+
+ text = TrimCraftMapString( text );
+
+ var lastBracket = text.lastIndexOf( "]" );
+ if( lastBracket >= 0 )
+ {
+ text = text.substring( 0, lastBracket + 1 );
+ }
+
+ return TrimCraftMapString( text );
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/crafttool.js b/data/js/skill/craft/crafttool.js
index 99410252a..a2f01ae33 100644
--- a/data/js/skill/craft/crafttool.js
+++ b/data/js/skill/craft/crafttool.js
@@ -1,274 +1,334 @@
///
// @ts-check
-const enableUOX3Craft = 0; // Disable or enable to use old uox3 menus.
-const blacksmithID = 4023; // Use this to tell the gump what script to close.
-const Carpentry = 4025;
-const Alchemy = 4028;
-const Fletching = 4029;
-const Tailoring = 4030;
-const Tinkering = 4032;
-const Cooking = 4034;
-const Cartography = 4035;
-const Glassblowing = 4036;
-
-/** @type { ( user: Character, iUsing: Item ) => boolean } */
-function onUseChecked( pUser, iUsed )
+const enableUOX3Craft = 0; // 0 = new system, 1 = old UOX3 menus
+const BlacksmithingID = 4023;
+const CarpentryID = 4025;
+const AlchemyID = 4028;
+const FletchingID = 4029;
+const TailoringID = 4030;
+const TinkeringID = 4032;
+const CookingID = 4034;
+const CartographyID = 4035;
+const GlassblowingID = 4036;
+const MasonryID = 4037;
+var craftToolMap = null;
+var craftToolMapLoaded = false;
+var craftToolMapLoadError = false;
+
+/** @type { () => object|null } */
+function LoadCraftToolMap()
{
- var socket = pUser.socket;
- var gumpID = Carpentry + 0xffff;
- var gumpID2 = Alchemy + 0xffff;
- var gumpID3 = Fletching + 0xffff;
- var gumpID4 = Tailoring + 0xffff;
- var gumpID5 = blacksmithID + 0xffff;
- var gumpID6 = Cooking + 0xffff;
- var gumpID7 = Tinkering + 0xffff;
- var gumpID8 = Cartography + 0xffff;
-
- if( socket && ValidateObject( iUsed ) && iUsed.isItem )
+ if( craftToolMapLoaded )
+ return craftToolMap;
+
+ craftToolMap = null;
+ craftToolMapLoaded = false;
+ craftToolMapLoadError = false;
+
+ var craftToolFile = new UOXCFile();
+ craftToolFile.Open( "crafttools.json", "r", "crafting", true );
+
+ if( craftToolFile == null || craftToolFile.Length() < 0 )
{
- if( GetServerSetting( "ToolUseLimit" ) && iUsed.usesLeft == 0 )
- {
- // Tool has no uses left
- socket.SysMessage( GetDictionaryEntry( 9262, socket.language )); // This has no more charges.
- return false;
- }
+ Console.Error( "CraftTool system: Unable to open js/jsdata/crafting/crafttools.json" );
+ craftToolMapLoadError = true;
+ return null;
+ }
- if( !pUser.InRange( iUsed, 3 ))
+ var fileText = "";
+ while( !craftToolFile.EOF() )
+ {
+ var rawLine = craftToolFile.ReadUntil( "\n" );
+ if( rawLine != null && typeof( rawLine ) != "undefined" )
{
- socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
- return false;
+ fileText += rawLine;
}
+ }
- if( iUsed.movable == 3 )
- {
- socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
- return false;
- }
+ craftToolFile.Close();
+ craftToolFile.Free();
- var iPackOwner = GetPackOwner( iUsed, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
- {
- if( iPackOwner.serial != pUser.serial ) //And if so does the pack belong to the user?
- {
- socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That resource is in someone else's backpack!
- return false;
- }
- }
- else
- {
- socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This has to be in your backpack before you can use it.
- return false;
- }
+ fileText = SanitizeCraftToolJsonText( fileText );
- socket.tempObj = iUsed;
- var tempPage = pUser.GetTempTag( "page" );
- if( iUsed.id >= 0x1026 && iUsed.id <= 0x1029 || iUsed.id >= 0x102C && iUsed.id <= 0x102F || iUsed.id >= 0x1030 && iUsed.id <= 0x1035 || iUsed.id >= 0x10E4 && iUsed.id <= 0x10E6 )
- {
- // Carpentry
- if( enableUOX3Craft == 1 )
- {
- TriggerEvent( 4006, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID, 0 );
- pUser.SetTempTag( "CRAFT", 1 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- case 8: // Page 8
- case 9: // Page 9
- case 10: // Page 10
- TriggerEvent( Carpentry, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Carpentry, "PageX", socket, pUser, 1 );
- break;
- }
- }
- else if( iUsed.id == 0x0E9B ) // mortar and pestle
- {
- // Alchemy
- if( enableUOX3Craft == 1 )
- {
- TriggerEvent( 4007, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID2, 0 );
- pUser.SetTempTag( "CRAFT", 2 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- TriggerEvent( Alchemy, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Alchemy, "PageX", socket, pUser, 1 );
- break;
- }
- }
- else if( iUsed.id == 0x1022 || iUsed.id == 0x1BD1 || iUsed.id == 0x1BD4 )
- {
- // Bowcraft/Fletching
- if( enableUOX3Craft == 1 )
- {
- TriggerEvent( 4005, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID3, 0 );
- pUser.SetTempTag( "CRAFT", 3 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- TriggerEvent( Fletching, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Fletching, "PageX", socket, pUser, 1 );
- break;
- }
- }
- else if( iUsed.id == 0x0F9D ) // Sewing Kit
- {
- // Tailoring
- if( enableUOX3Craft == 1 )
- {
- TriggerEvent( 4004, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID4, 0 );
- pUser.SetTempTag( "CRAFT", 4 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- case 8: // Page 8
- TriggerEvent( Tailoring, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Tailoring, "PageX", socket, pUser, 1 );
- break;
- }
- }
- else if( iUsed.id == 0x0FBB || iUsed.id == 0x0FBC || iUsed.id == 0x13E3 || iUsed.id == 0x13E4 )
+ try
+ {
+ craftToolMap = JSON.parse( fileText );
+ }
+ catch( error )
+ {
+ Console.Error( "CraftTool system: Failed to parse crafttools.json: " + error );
+ Console.Error( "CraftTool system: crafttools.json length after sanitize: " + fileText.length );
+ Console.Error( "CraftTool system: last char code: " + fileText.charCodeAt( fileText.length - 1 ));
+ craftToolMapLoadError = true;
+ return null;
+ }
+
+ if( !IsCraftToolArrayValue( craftToolMap ))
+ {
+ Console.Error( "CraftTool system: crafttools.json must contain a JSON array." );
+ craftToolMapLoadError = true;
+ return null;
+ }
+
+ craftToolMapLoaded = true;
+ Console.Print( "CraftTool system: Loaded " + craftToolMap.length + " craft tool entries.\n" );
+
+ return craftToolMap;
+}
+
+/** @type { ( pUser: Character, iUsed: Item ) => object|null } */
+function GetCraftToolEntry( pUser, iUsed )
+{
+ var toolMap = LoadCraftToolMap();
+
+ if( !toolMap )
+ return null;
+
+ for( var i = 0; i < toolMap.length; i++ )
+ {
+ var entry = toolMap[i];
+
+ if( CraftToolMatchesEntry( iUsed, entry ))
+ return entry;
+ }
+
+ return null;
+}
+
+/** @type { ( iUsed: Item, entry: object ) => boolean } */
+function CraftToolMatchesEntry( iUsed, entry )
+{
+ if( !entry )
+ return false;
+
+ if( entry.sectionIDs && IsCraftToolArrayValue( entry.sectionIDs ))
+ {
+ for( var i = 0; i < entry.sectionIDs.length; i++ )
{
- // Blacksmithing
- if( enableUOX3Craft == 1 )
- {
+ if( iUsed.sectionID == entry.sectionIDs[i] )
return true;
- }
- socket.CloseGump( gumpID5, 0 );
- pUser.SetTempTag( "CRAFT", 5 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- TriggerEvent( blacksmithID, "PageX", socket, pUser, tempPage );
- break;
- case 8:
- TriggerEvent( blacksmithID, "Page8", socket, pUser );
- break;
- default: TriggerEvent( blacksmithID, "PageX", socket, pUser, 1 );
- break;
- }
}
- else if( iUsed.id == 0x1043 || iUsed.id == 0x097f || iUsed.id == 0x09e2 || iUsed.id == 0x103e )
+ }
+
+ if( entry.toolIDs && IsCraftToolArrayValue( entry.toolIDs ))
+ {
+ for( var j = 0; j < entry.toolIDs.length; j++ )
{
- // Cooking
- if( enableUOX3Craft == 1 )
- {
- //socket.SysMessage( "Old-school crafting gumps have not been implemented for Cooking. Use raw food with heat sources to cook!" );
- TriggerEvent( 104, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID6, 0 );
- pUser.SetTempTag( "CRAFT", 6 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- TriggerEvent( Cooking, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Cooking, "PageX", socket, pUser, 1 );
- break;
- }
+ if( iUsed.id == entry.toolIDs[j] )
+ return true;
}
- else if( iUsed.id == 0x1eb8 || iUsed.id == 0x1eb9 || iUsed.id == 0x1eba || iUsed.id == 0x1ebb || iUsed.id == 0x1ebc ) // Tinker's tools
+ }
+
+ return false;
+}
+
+/** @type { ( value: any ) => boolean } */
+function IsCraftToolArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+/** @type { ( text: string ) => string } */
+function SanitizeCraftToolJsonText( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ return "";
+
+ text = String( text );
+
+ if( text.length > 0 && text.charCodeAt( 0 ) == 65279 )
+ text = text.substring( 1 );
+
+ text = text.split( "\r\n" ).join( "\n" );
+ text = text.split( "\r" ).join( "\n" );
+ text = text.split( String.fromCharCode( 160 ) ).join( " " );
+ text = text.split( String.fromCharCode( 255 ) ).join( "" );
+ text = text.split( "\t" ).join( " " );
+
+ text = TrimCraftToolString( text );
+
+ var lastBracket = text.lastIndexOf( "]" );
+ if( lastBracket >= 0 )
+ text = text.substring( 0, lastBracket + 1 );
+
+ return TrimCraftToolString( text );
+}
+
+/** @type { ( text: string ) => string } */
+function TrimCraftToolString( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ return "";
+
+ return text.replace( /^\s+|\s+$/g, "" );
+}
+
+/** @type { ( specialPages: object ) => object|null } */
+function BuildSpecialPageMap( specialPages )
+{
+ if( !specialPages || !IsCraftToolArrayValue( specialPages ))
+ return null;
+
+ var pageMap = {};
+
+ for( var i = 0; i < specialPages.length; i++ )
+ {
+ var entry = specialPages[i];
+
+ if( entry && typeof entry.page != "undefined" && entry["function"] )
{
- // Tinkering
- if( enableUOX3Craft == 1 )
- {
- TriggerEvent( 4003, "onUseChecked", pUser, iUsed );
- return;
- }
- socket.CloseGump( gumpID7, 0 );
- pUser.SetTempTag( "CRAFT", 7 )
- switch( tempPage )
- {
- case 1: // Page 1
- case 2: // Page 2
- case 3: // Page 3
- case 4: // Page 4
- case 5: // Page 5
- case 6: // Page 6
- case 7: // Page 7
- case 8: // Page 8
- case 9: // Page 9
- TriggerEvent( Tinkering, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Tinkering, "PageX", socket, pUser, 1 );
- break;
- }
+ pageMap[entry.page] = entry["function"];
}
- else if( iUsed.sectionID == "mapmakerspen" )
+ }
+
+ return pageMap;
+}
+
+/**
+ * Ensure the tool is usable: charges > 0, in range, not locked down,
+ * and in the user's own backpack.
+ * @param {Character} pUser
+ * @param {Item} iUsed
+ * @returns {boolean}
+ */
+function checkToolUsable(pUser, iUsed)
+{
+ var socket = pUser.socket;
+ if( !socket || !ValidateObject( iUsed ) || !iUsed.isItem )
+ return false;
+
+ if( GetServerSetting( "ToolUseLimit" ) && iUsed.usesLeft == 0 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 9262, socket.language )); // This has no more charges.
+ return false;
+ }
+
+ if( !pUser.InRange( iUsed, 3 ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
+ return false;
+ }
+
+ if( iUsed.movable == 3 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
+ return false;
+ }
+
+ var iPackOwner = GetPackOwner( iUsed, 0 );
+ if( ValidateObject( iPackOwner ))
+ {
+ if( iPackOwner.serial != pUser.serial )
{
- // Cartography
- socket.CloseGump( gumpID8, 0 );
- pUser.SetTempTag( "CRAFT", 8 );
- switch( tempPage )
- {
- case 1: // Page 1
- TriggerEvent( Cartography, "PageX", socket, pUser, tempPage );
- break;
- default: TriggerEvent( Cartography, "PageX", socket, pUser, 1 );
- break;
- }
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That resource is in someone else's backpack!
+ return false;
}
- else if( iUsed.sectionID == "blowpipe" )
+ }
+ else
+ {
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This has to be in your backpack before you can use it.
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Open a paged crafting gump using a standard "PageX" entry point, with optional
+ * special page handlers (e.g. Page8, Page20).
+ *
+ * @param {Character} pUser
+ * @param {Socket} socket
+ * @param {number} scriptID Script ID of the crafting handler
+ * @param {number} craftIndex Value to store in "CRAFT" temp tag
+ * @param {number} maxPage Highest normal page number (handled by "PageX")
+ * @param {{[page: number]: string}=} specialPages Map: page -> function name
+ */
+function openCraftMenu(pUser, socket, scriptID, craftIndex, maxPage, specialPages)
+{
+ if( !socket )
+ return;
+
+ var gumpID = scriptID + 0xffff;
+ var tempPage = pUser.GetTempTag( "page" );
+
+ socket.CloseGump( gumpID, 0 );
+ pUser.SetTempTag( "CRAFT", craftIndex );
+
+ if( typeof tempPage !== "number" || tempPage < 1 )
+ tempPage = 1;
+
+ // Custom page handlers (e.g. Page8, Page20)
+ if( specialPages && specialPages[tempPage] )
+ {
+ TriggerEvent( scriptID, specialPages[tempPage], socket, pUser );
+ return;
+ }
+
+ // Normal pages handled by PageX, up to maxPage
+ if( tempPage >= 1 && tempPage <= maxPage )
+ {
+ TriggerEvent( scriptID, "PageX", socket, pUser, tempPage );
+ }
+ else
+ {
+ TriggerEvent( scriptID, "PageX", socket, pUser, 1 );
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Main entry
+// ---------------------------------------------------------------------------
+
+/** @type { ( pUser: Character, iUsed: Item ) => boolean } */
+function onUseChecked( pUser, iUsed )
+{
+ var socket = pUser.socket;
+ if( !socket )
+ return false;
+
+ if( !checkToolUsable( pUser, iUsed ))
+ return false;
+
+ // Save tool on socket so skill gumps can reference it
+ socket.tempObj = iUsed;
+
+ var craftToolEntry = GetCraftToolEntry( pUser, iUsed );
+
+ if( !craftToolEntry )
+ return false;
+
+ if( craftToolEntry.requiredTag && pUser.GetTag( craftToolEntry.requiredTag ) == 0 )
+ {
+ if( craftToolEntry.requiredTagMessage )
+ socket.SysMessage( GetDictionaryEntry( craftToolEntry.requiredTagMessage, socket.language ));
+
+ return false;
+ }
+
+ if( enableUOX3Craft == 1 )
+ {
+ if( craftToolEntry.legacyReturnTrue )
+ return true;
+
+ if( craftToolEntry.legacyScriptID )
{
- if( pUser.GetTag( "GlassBlowing" ) == 0 )
- {
- socket.SysMessage( GetDictionaryEntry( 6300, socket.Language ));// You havent learned glassblowing.
- return;
- }
- // Cartography
- socket.CloseGump( gumpID8, 0 );
- pUser.SetTempTag( "CRAFT", 9 );
- switch( tempPage )
- {
- case 1: // Page 1
- TriggerEvent( Glassblowing, "PageX", socket, pUser, tempPage);
- break;
- default: TriggerEvent( Glassblowing, "PageX", socket, pUser, 1);
- break;
- }
+ TriggerEvent( craftToolEntry.legacyScriptID, "onUseChecked", pUser, iUsed );
+ return false;
}
}
+
+ if( craftToolEntry.craft )
+ pUser.SetTempTag( "CUSTOMCRAFT", craftToolEntry.craft );
+
+ openCraftMenu(
+ pUser,
+ socket,
+ craftToolEntry.scriptID,
+ craftToolEntry.craftIndex,
+ craftToolEntry.maxPage,
+ BuildSpecialPageMap( craftToolEntry.specialPages )
+ );
+
return false;
-}
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/customcraft.js b/data/js/skill/craft/customcraft.js
new file mode 100644
index 000000000..8ddb3ac58
--- /dev/null
+++ b/data/js/skill/craft/customcraft.js
@@ -0,0 +1,846 @@
+///
+// @ts-check
+
+var textHue = 0x480;
+var customCraftID = 4040;
+var craftMapRegistryID = 4038;
+var itemDetailsScriptID = 4026;
+var itemsPerPage = 10;
+var displayUnlearnedRecipes = true;
+var coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ) );
+
+var customCraftDefinitions = null;
+var customCraftDefinitionsLoaded = false;
+var customCraftDefinitionsLoadError = false;
+var CustomCraftMap = {};
+
+/** @type { () => object|null } */
+function LoadCustomCraftDefinitions()
+{
+ if( customCraftDefinitionsLoaded )
+ return customCraftDefinitions;
+
+ customCraftDefinitions = null;
+ customCraftDefinitionsLoaded = false;
+ customCraftDefinitionsLoadError = false;
+
+ var customCraftFile = new UOXCFile();
+ customCraftFile.Open( "customcrafts.json", "r", "crafting", true );
+
+ if( customCraftFile == null || customCraftFile.Length() < 0 )
+ {
+ Console.Error( "CustomCraft system: Unable to open js/jsdata/crafting/customcrafts.json" );
+ customCraftDefinitionsLoadError = true;
+ return null;
+ }
+
+ var fileText = "";
+ while( !customCraftFile.EOF() )
+ {
+ var rawLine = customCraftFile.ReadUntil( "\n" );
+ if( rawLine != null && typeof( rawLine ) != "undefined" )
+ {
+ fileText += rawLine;
+ }
+ }
+
+ customCraftFile.Close();
+ customCraftFile.Free();
+
+ fileText = SanitizeCustomCraftJsonText( fileText );
+
+ try
+ {
+ customCraftDefinitions = JSON.parse( fileText );
+ }
+ catch( error )
+ {
+ Console.Error( "CustomCraft system: Failed to parse customcrafts.json: " + error );
+ customCraftDefinitionsLoadError = true;
+ return null;
+ }
+
+ if( !IsCustomCraftArrayValue( customCraftDefinitions ))
+ {
+ Console.Error( "CustomCraft system: customcrafts.json must contain a JSON array." );
+ customCraftDefinitionsLoadError = true;
+ return null;
+ }
+
+ customCraftDefinitionsLoaded = true;
+ Console.Print( "CustomCraft system: Loaded " + customCraftDefinitions.length + " custom craft definitions.\n" );
+
+ return customCraftDefinitions;
+}
+
+/** @type { ( craftName: string ) => object|null } */
+function GetCustomCraftDefinition( craftName )
+{
+ var definitions = LoadCustomCraftDefinitions();
+
+ if( !definitions )
+ return null;
+
+ for( var i = 0; i < definitions.length; i++ )
+ {
+ if( definitions[i] && definitions[i].craft == craftName )
+ return definitions[i];
+ }
+
+ return null;
+}
+
+/** @type { ( pUser: Character ) => object|null } */
+function GetActiveCustomCraftDefinition( pUser )
+{
+ var craftName = pUser.GetTempTag( "CUSTOMCRAFT" );
+
+ if( !craftName )
+ return null;
+
+ return GetCustomCraftDefinition( craftName );
+}
+
+/** @type { ( craftDefinition: object ) => boolean } */
+function LoadCustomCraftMap( craftDefinition )
+{
+ CustomCraftMap = {};
+
+ if( !craftDefinition )
+ return false;
+
+ var mapFile = craftDefinition.mapFile || craftDefinition.craft;
+ var craftEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", mapFile );
+
+ if( !craftEntries || !IsCustomCraftArrayValue( craftEntries ))
+ {
+ Console.Warning( "CustomCraft system: Unable to load custom craft map data for " + mapFile + "." );
+ return false;
+ }
+
+ for( var i = 0; i < craftEntries.length; i++ )
+ {
+ var entry = craftEntries[i];
+
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined && typeof craftDefinition.skillID != "undefined" )
+ entry.skill = craftDefinition.skillID;
+
+ CustomCraftMap[entry.makeID] = entry;
+ }
+
+ Console.Print( "CustomCraft system: Loaded " + craftEntries.length + " entries for " + mapFile + ".\n" );
+ return true;
+}
+
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
+function PageX( socket, pUser, pageNum )
+{
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ var craftDefinition = GetActiveCustomCraftDefinition( pUser );
+
+ if( !craftDefinition )
+ {
+ socket.SysMessage( "Custom craft definition was not found." );
+ return;
+ }
+
+ if( !CustomCraftMap || Object.keys( CustomCraftMap ).length == 0 )
+ {
+ if( !LoadCustomCraftMap( craftDefinition ))
+ {
+ socket.SysMessage( "Custom craft map failed to load." );
+ return;
+ }
+ }
+
+ var pageItems = [];
+
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenCustomCraft_" + craftDefinition.craft ) || "";
+ var split = lastTenRaw.split( "," );
+
+ for( var i = 0; i < split.length; i++ )
+ {
+ var value = parseInt( split[i], 10 );
+ if( !isNaN( value ) && CustomCraftMap[value] )
+ pageItems.push( value );
+ }
+ }
+ else
+ {
+ for( var makeIDStr in CustomCraftMap )
+ {
+ if( !CustomCraftMap.hasOwnProperty( makeIDStr ))
+ continue;
+
+ var makeID = parseInt( makeIDStr, 10 );
+ var data = CustomCraftMap[makeID];
+
+ if( data.page == pageNum && EraOK( data ))
+ {
+ var needsRecipe = data.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe ))
+ pageItems.push( makeID );
+ }
+ }
+
+ pageItems.sort( function( a, b )
+ {
+ var entryA = CustomCraftMap[a];
+ var entryB = CustomCraftMap[b];
+
+ if( entryA && entryB )
+ {
+ if( entryA.sortOrder != undefined && entryB.sortOrder != undefined )
+ return entryA.sortOrder - entryB.sortOrder;
+
+ if( entryA.dictID && entryB.dictID )
+ return entryA.dictID - entryB.dictID;
+ }
+
+ return a - b;
+ });
+ }
+
+ var subPage = pUser.GetTempTag( "subPage" ) || 1;
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+
+ if( subPage < 1 )
+ subPage = 1;
+
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ var customGump = new Gump();
+ BuildCustomCraftFrame( customGump, socket, pUser, craftDefinition, pageNum );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeEntryID = pageItems[j];
+ var entry = CustomCraftMap[makeEntryID];
+ var entryText = GetCustomCraftEntryText( entry, socket );
+
+ customGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, makeEntryID );
+ customGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+ customGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + makeEntryID );
+ }
+
+ if( pageItems.length == 0 )
+ customGump.AddText( 220, 60, textHue, "No items available on this page." );
+
+ if( subPage > 1 )
+ {
+ customGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ customGump.AddHTMLGump( 255, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10101, socket.language ) + "" );
+ }
+
+ if( subPage < totalSubPages )
+ {
+ customGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ customGump.AddHTMLGump( 405, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10100, socket.language ) + "" );
+ }
+
+ customGump.Send( socket );
+ customGump.Free();
+}
+
+/** @type { ( customGump: Gump, socket: Socket, pUser: Character, craftDefinition: object, pageNum: number ) => void } */
+function BuildCustomCraftFrame( customGump, socket, pUser, craftDefinition, pageNum )
+{
+ customGump.AddPage( 0 );
+ customGump.AddBackground( 0, 0, 530, 437, 5054 );
+ customGump.AddTiledGump( 10, 10, 510, 22, 2624 );
+ customGump.AddTiledGump( 10, 292, 150, 45, 2624 );
+ customGump.AddTiledGump( 165, 292, 355, 45, 2624 );
+ customGump.AddTiledGump( 10, 342, 510, 85, 2624 );
+ customGump.AddTiledGump( 10, 37, 200, 250, 2624 );
+ customGump.AddTiledGump( 215, 37, 305, 250, 2624 );
+ customGump.AddCheckerTrans( 10, 10, 510, 417 );
+
+ var menuName = craftDefinition.menuName || craftDefinition.craft || "Custom Craft";
+
+ customGump.AddHTMLGump( 0, 12, 530, 20, 0, 0, "
" + menuName + " " );
+ customGump.AddHTMLGump( 0, 37, 220, 22, 0, 0, " " + GetDictionaryEntry( 10286, socket.language ) + " " );
+ customGump.AddHTMLGump( 230, 37, 280, 22, 0, 0, " " + GetDictionaryEntry( 10287, socket.language ) + " " );
+ customGump.AddHTMLGump( 10, 302, 150, 25, 0, 0, " " + GetDictionaryEntry( 10288, socket.language ) + " " );
+
+ var categories = craftDefinition.categories || [];
+
+ for( var i = 0; i < categories.length; i++ )
+ {
+ var category = categories[i];
+ var categoryPage = category.page || ( i + 1 );
+ var categoryText = category.label || ( "Page " + categoryPage );
+
+ customGump.AddButton( 20, 60 + ( i * 20 ), 4005, 4007, 1, 0, 10000 + categoryPage );
+ customGump.AddText( 55, 60 + ( i * 20 ), textHue, categoryText );
+ }
+
+ if( craftDefinition.showLastTen )
+ {
+ customGump.AddButton( 20, 250, 4005, 4007, 1, 0, 10999 );
+ customGump.AddText( 55, 250, textHue, "Last Ten" );
+ }
+
+ if( craftDefinition.allowRepair )
+ {
+ customGump.AddButton( 20, 345, 4005, 4007, 1, 0, 90001 );
+ customGump.AddText( 55, 345, textHue, GetCustomCraftText( craftDefinition, "repairText", "Repair Item" ) );
+ }
+
+ if( craftDefinition.allowRecycle )
+ {
+ customGump.AddButton( 20, 365, 4005, 4007, 1, 0, 90002 );
+ customGump.AddText( 55, 365, textHue, GetCustomCraftText( craftDefinition, "recycleText", "Recycle Item" ) );
+ }
+}
+
+/** @type { ( entry: object, socket: Socket ) => string } */
+function GetCustomCraftEntryText( entry, socket )
+{
+ if( !entry )
+ return "[Missing Entry]";
+
+ if( entry.customName )
+ return entry.customName;
+
+ if( entry.dictID )
+ {
+ var dictText = GetDictionaryEntry( entry.dictID, socket.language );
+ if( dictText && dictText != "" )
+ return dictText;
+ }
+
+ if( entry.label )
+ return entry.label;
+
+ return "[Unnamed Item: " + entry.makeID + "]";
+}
+
+/** @type { ( pSock: Socket, pButton: number, gumpData: any ) => void } */
+function onGumpPress( pSock, pButton, gumpData )
+{
+ if( !pSock )
+ return;
+
+ var pUser = pSock.currentChar;
+
+ if( !ValidateObject( pUser ))
+ return;
+
+ var craftDefinition = GetActiveCustomCraftDefinition( pUser );
+
+ if( !craftDefinition )
+ return;
+
+ if( !CustomCraftMap || Object.keys( CustomCraftMap ).length == 0 )
+ {
+ if( !LoadCustomCraftMap( craftDefinition ))
+ return;
+ }
+
+ if( pButton == 0 )
+ {
+ pSock.CloseGump( customCraftID + 0xffff, 0 );
+ return;
+ }
+
+ if( craftDefinition.allowRepair && pButton == 90001 )
+ {
+ pSock.CustomTarget( 0, GetCustomCraftText( craftDefinition, "repairTargetText", "Select the item you want to repair." ) );
+ return;
+ }
+
+ if( craftDefinition.allowRecycle && pButton == 90002 )
+ {
+ pSock.CustomTarget( 1, GetCustomCraftText( craftDefinition, "recycleTargetText", "Select the item you want to recycle." ) );
+ return;
+ }
+
+ if( pButton >= 10000 && pButton < 11000 )
+ {
+ if( pButton == 10999 )
+ PageX( pSock, pUser, 999 );
+ else
+ PageX( pSock, pUser, pButton - 10000 );
+
+ return;
+ }
+
+ if( pButton >= 8000 && pButton < 9000 )
+ {
+ pUser.SetTempTag( "subPage", pButton - 8000 );
+ PageX( pSock, pUser, pUser.GetTempTag( "page" ) || 1 );
+ return;
+ }
+
+ if( pButton >= 9000 && pButton < 10000 )
+ {
+ pUser.SetTempTag( "subPage", pButton - 9000 );
+ PageX( pSock, pUser, pUser.GetTempTag( "page" ) || 1 );
+ return;
+ }
+
+ if( pButton >= 20000 )
+ {
+ ShowCustomCraftItemDetails( pSock, pUser, pButton - 20000 );
+ return;
+ }
+
+ var entry = CustomCraftMap[pButton];
+
+ if( !entry )
+ return;
+
+ if( !EraOK( entry ))
+ {
+ pSock.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( entry.recipeID && !HasLearnedRecipe( pUser, entry.recipeID ))
+ {
+ TriggerEvent( 4022, "NeedRecipe", pSock, entry.recipeID );
+ return;
+ }
+
+ pUser.SetTempTag( "MAKELAST", pButton );
+ StoreLastTenCustomCraft( pUser, craftDefinition, pButton );
+
+ pSock.CloseGump( customCraftID + 0xffff, 0 );
+ MakeItem( pSock, pUser, pButton );
+ pUser.StartTimer( 2000, entry.timerID || entry.page || 1, true );
+}
+
+/** @type { ( pSock: Socket, pUser: Character, makeID: number ) => void } */
+function ShowCustomCraftItemDetails( pSock, pUser, makeID )
+{
+ var entry = CustomCraftMap[makeID];
+
+ if( !entry )
+ return;
+
+ pUser.SetTempTag( "ITEMDETAILS", makeID );
+ pUser.SetTempTag( "Skill", entry.skill || 0 );
+ pUser.SetTempTag( "needRecipeID", entry.recipeID || 0 );
+
+ ClearCustomCraftHarvestTags( pUser );
+
+ if( entry.harvest && IsCustomCraftArrayValue( entry.harvest ))
+ {
+ for( var i = 0; i < entry.harvest.length && i < 4; i++ )
+ {
+ var tagName = ( i == 0 ? "Harvest" : "Harvest" + ( i + 1 ));
+ pUser.SetTempTag( tagName, entry.harvest[i] );
+ }
+ }
+
+ if( entry.harvestNames && IsCustomCraftArrayValue( entry.harvestNames ))
+ {
+ for( var j = 0; j < entry.harvestNames.length && j < 4; j++ )
+ {
+ var nameTag = ( j == 0 ? "HarvestName" : "Harvest" + ( j + 1 ) + "Name" );
+ pUser.SetTempTag( nameTag, entry.harvestNames[j] );
+ }
+ }
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+}
+
+/** @type { ( pUser: Character, craftDefinition: object, makeID: number ) => void } */
+function StoreLastTenCustomCraft( pUser, craftDefinition, makeID )
+{
+ if( !craftDefinition || !craftDefinition.showLastTen )
+ return;
+
+ var tagName = "LastTenCustomCraft_" + craftDefinition.craft;
+ var rawValue = pUser.GetTempTag( tagName ) || "";
+ var split = rawValue.split( "," );
+ var newList = [ makeID.toString() ];
+
+ for( var i = 0; i < split.length; i++ )
+ {
+ var value = parseInt( split[i], 10 );
+
+ if( !isNaN( value ) && value != makeID && newList.length < 10 )
+ newList.push( value.toString() );
+ }
+
+ pUser.SetTempTag( tagName, newList.join( "," ) );
+}
+
+/** @type { ( timerObj: Character, timerID: number ) => void } */
+function onTimer( timerObj, timerID )
+{
+ if( !ValidateObject( timerObj ))
+ return;
+
+ var socket = timerObj.socket;
+
+ if( !socket )
+ return;
+
+ PageX( socket, timerObj, timerID );
+}
+
+/** @type { ( entry: object ) => boolean } */
+function EraOK( entry )
+{
+ if( !entry )
+ return false;
+
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+
+ return true;
+}
+
+/** @type { ( pUser: Character, recipeID: number ) => boolean } */
+function HasLearnedRecipe( pUser, recipeID )
+{
+ if( !recipeID || recipeID <= 0 )
+ return true;
+
+ return TriggerEvent( 4022, "NeedRecipe", pUser.socket, recipeID );
+}
+
+/** @type { ( pUser: Character ) => void } */
+function ClearCustomCraftHarvestTags( pUser )
+{
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+}
+
+/** @type { ( value: any ) => boolean } */
+function IsCustomCraftArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+/** @type { ( text: string ) => string } */
+function SanitizeCustomCraftJsonText( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ return "";
+
+ text = String( text );
+
+ if( text.length > 0 && text.charCodeAt( 0 ) == 65279 )
+ text = text.substring( 1 );
+
+ text = text.split( "\r\n" ).join( "\n" );
+ text = text.split( "\r" ).join( "\n" );
+ text = text.split( String.fromCharCode( 160 ) ).join( " " );
+ text = text.split( String.fromCharCode( 255 ) ).join( "" );
+ text = text.split( "\t" ).join( " " );
+
+ text = TrimCustomCraftString( text );
+
+ var lastBracket = text.lastIndexOf( "]" );
+ if( lastBracket >= 0 )
+ text = text.substring( 0, lastBracket + 1 );
+
+ return TrimCustomCraftString( text );
+}
+
+/** @type { ( text: string ) => string } */
+function TrimCustomCraftString( text )
+{
+ if( text == null || typeof( text ) == "undefined" )
+ return "";
+
+ return text.replace( /^\s+|\s+$/g, "" );
+}
+
+/** @type { () => void } */
+function ReloadCustomCraftDefinitions()
+{
+ customCraftDefinitions = null;
+ customCraftDefinitionsLoaded = false;
+ customCraftDefinitionsLoadError = false;
+
+ LoadCustomCraftDefinitions();
+}
+
+/** @type { ( craftDefinition: object, keyName: string, fallbackText: string ) => string } */
+function GetCustomCraftText( craftDefinition, keyName, fallbackText )
+{
+ if( craftDefinition && craftDefinition[keyName] )
+ return craftDefinition[keyName];
+
+ return fallbackText;
+}
+
+/** @type { ( pSock: Socket, pUser: Character, targetObj: Item, craftDefinition: object ) => boolean } */
+function CheckCustomCraftTargetItem( pSock, pUser, targetObj, craftDefinition )
+{
+ if( !ValidateObject( targetObj ) || !targetObj.isItem )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "invalidTargetText", "That is not a valid item." ));
+ return false;
+ }
+
+ var ownerObj = GetPackOwner( targetObj, 0 );
+ if( !ValidateObject( ownerObj ) || ownerObj.serial != pUser.serial )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "mustBeInPackText", "That item must be in your backpack." ));
+ return false;
+ }
+
+ return true;
+}
+
+/** @type { ( pUser: Character, stationIDs: any ) => boolean } */
+function HasNearbyCustomCraftStation( pUser, stationIDs )
+{
+ if( !stationIDs || !IsCustomCraftArrayValue( stationIDs ))
+ return true;
+
+ var nearbyStation = AreaItemFunction( "FindNearbyCustomCraftStation", pUser, 3, stationIDs );
+ return nearbyStation != 0;
+}
+
+/** @type { ( pUser: Character, trgItem: Item, stationIDs: any ) => boolean } */
+function FindNearbyCustomCraftStation( pUser, trgItem, stationIDs )
+{
+ if( !ValidateObject( trgItem ) || !trgItem.isItem )
+ return false;
+
+ for( var i = 0; i < stationIDs.length; i++ )
+ {
+ if( trgItem.id == stationIDs[i] )
+ return true;
+ }
+
+ return false;
+}
+
+/** @type { ( pSock: Socket, pUser: Character, targetObj: Item, craftDefinition: object ) => void } */
+function RepairCustomCraftItem( pSock, pUser, targetObj, craftDefinition )
+{
+ if( !CheckCustomCraftTargetItem( pSock, pUser, targetObj, craftDefinition ))
+ return;
+
+ if( craftDefinition.repairStationIDs && !HasNearbyCustomCraftStation( pUser, craftDefinition.repairStationIDs ))
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairNoStationText", "You need to be near the proper crafting station to repair that." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ if( craftDefinition.repairMaterialType )
+ {
+ var materialType = TriggerEvent( 2506, "GetItemMaterialType", targetObj );
+ if( materialType != craftDefinition.repairMaterialType )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairWrongMaterialText", "That item cannot be repaired with this craft." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+ }
+
+ if( targetObj.maxhp <= 0 )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairInvalidDurabilityText", "That item cannot be repaired." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ if( targetObj.health >= targetObj.maxhp )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairFullText", "That item is already fully repaired." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ var repairSkillID = craftDefinition.repairSkillID;
+ var repairSkillName = craftDefinition.repairSkillName;
+ var repairSkill = 0;
+ var repairSkillCap = 1000;
+
+ if( typeof repairSkillID != "undefined" )
+ {
+ repairSkill = pUser.skills[repairSkillID];
+ repairSkillCap = pUser.skillCaps[repairSkillID];
+ }
+ else if( repairSkillName )
+ {
+ repairSkill = pUser.skills[repairSkillName];
+ repairSkillCap = pUser.skillCaps[repairSkillName];
+ }
+
+ var minSkill = craftDefinition.repairMinSkill || 0;
+ if( repairSkill < minSkill )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairTooDifficultText", "You are not skilled enough to repair that." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ var missingHP = targetObj.maxhp - targetObj.health;
+ var repairDifficulty = Math.floor(( missingHP * 1000 ) / targetObj.maxhp );
+ var minDifficulty = repairDifficulty - 250;
+
+ if( minDifficulty < 0 )
+ minDifficulty = 0;
+
+ var maxDifficulty = repairDifficulty + 250;
+ if( maxDifficulty > repairSkillCap )
+ maxDifficulty = repairSkillCap;
+
+ if( RandomNumber( minDifficulty, 1000 ) < repairSkill )
+ {
+ if( typeof repairSkillID != "undefined" )
+ pUser.CheckSkill( repairSkillID, minDifficulty, maxDifficulty );
+
+ if( GetServerSetting( "ItemRepairDurabilityLoss" ))
+ targetObj.maxhp -= 1;
+
+ targetObj.health = targetObj.maxhp;
+
+ if( craftDefinition.repairSound )
+ pSock.SoundEffect( craftDefinition.repairSound, true );
+
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairSuccessText", "You repair the item." ));
+ }
+ else
+ {
+ var damageLoss = craftDefinition.repairFailureDamage || 1;
+ targetObj.health -= damageLoss;
+
+ if( targetObj.health <= 0 )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairDestroyedText", "The item has been destroyed." ));
+ targetObj.Delete();
+ }
+ else
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "repairFailedText", "You fail to repair the item." ));
+ }
+ }
+
+ RestartCustomCraftGump( pUser, craftDefinition );
+}
+
+/** @type { ( pSock: Socket, pUser: Character, targetObj: Item, craftDefinition: object ) => void } */
+function RecycleCustomCraftItem( pSock, pUser, targetObj, craftDefinition )
+{
+ if( !CheckCustomCraftTargetItem( pSock, pUser, targetObj, craftDefinition ))
+ return;
+
+ if( craftDefinition.recycleStationIDs && !HasNearbyCustomCraftStation( pUser, craftDefinition.recycleStationIDs ))
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "recycleNoStationText", "You need to be near the proper crafting station to recycle that." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ if( craftDefinition.recycleMaterialType )
+ {
+ var materialType = TriggerEvent( 2506, "GetItemMaterialType", targetObj );
+ if( materialType != craftDefinition.recycleMaterialType )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "recycleWrongMaterialText", "That item cannot be recycled with this craft." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+ }
+
+ var recycleResourceID = craftDefinition.recycleResourceID || 0;
+ if( recycleResourceID == 0 )
+ {
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "recycleNoResourceText", "This craft does not define a recycled resource." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+ return;
+ }
+
+ var resourceAmount = craftDefinition.recycleResourceAmount || 1;
+ var resourceHue = craftDefinition.recycleResourceHue || 0;
+
+ if( craftDefinition.recycleUseTargetHue )
+ resourceHue = targetObj.colour;
+
+ if( craftDefinition.recycleFromCreateEntry )
+ {
+ var createEntry = null;
+
+ if( targetObj.entryMadeFrom != null && targetObj.entryMadeFrom != 0 )
+ createEntry = CreateEntries[targetObj.entryMadeFrom];
+
+ if( createEntry && createEntry.resources && createEntry.resources.length > 0 )
+ {
+ resourceAmount = Math.max( 1, Math.floor( createEntry.resources[0][0] / 2 ));
+ }
+ }
+
+ targetObj.Delete();
+
+ var newResource = CreateDFNItem( pSock, pUser, recycleResourceID.toString(), resourceAmount, "ITEM", true, resourceHue );
+
+ if( ValidateObject( newResource ) && craftDefinition.recycleResourceName )
+ newResource.name = craftDefinition.recycleResourceName;
+
+ pSock.SysMessage( GetCustomCraftText( craftDefinition, "recycleSuccessText", "You recycle the item." ));
+ RestartCustomCraftGump( pUser, craftDefinition );
+}
+
+/** @type { ( pUser: Character, craftDefinition: object ) => void } */
+function RestartCustomCraftGump( pUser, craftDefinition )
+{
+ if( !ValidateObject( pUser ))
+ return;
+
+ var timerID = pUser.GetTempTag( "page" ) || 1;
+ var delay = craftDefinition.actionDelay || 200;
+
+ pUser.StartTimer( delay, timerID, true );
+}
+
+/** @type { ( pSock: Socket, pUser: Character, targetObj: any, targetID: number ) => void } */
+function onCallback0( pSock, pUser, targetObj, targetID )
+{
+ var craftDefinition = GetActiveCustomCraftDefinition( pUser );
+
+ if( !craftDefinition || !craftDefinition.allowRepair )
+ return;
+
+ RepairCustomCraftItem( pSock, pUser, targetObj, craftDefinition );
+}
+
+/** @type { ( pSock: Socket, pUser: Character, targetObj: any, targetID: number ) => void } */
+function onCallback1( pSock, pUser, targetObj, targetID )
+{
+ var craftDefinition = GetActiveCustomCraftDefinition( pUser );
+
+ if( !craftDefinition || !craftDefinition.allowRecycle )
+ return;
+
+ RecycleCustomCraftItem( pSock, pUser, targetObj, craftDefinition );
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/fletching.js b/data/js/skill/craft/fletching.js
index d93541437..c12724a50 100644
--- a/data/js/skill/craft/fletching.js
+++ b/data/js/skill/craft/fletching.js
@@ -1,240 +1,531 @@
///
// @ts-check
-const LabelHue = 0x480; // Color of the text.
-const LabelColor = 0x7FFF; // Second Color of text.
-const scriptID = 4029; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
-
-const myPage = [
- // Page 1 - Materials
- [ 11205, 11206, 11207, 11208, 11209 ],
-
- // Page 2 - Ammunition
- [ 11210, 11211, 11212, 11213, 11214, 11215, 11216, 11217 ],
-
- // Page 3 - Weapons
- [ 11218, 11219, 11220 ]
-];
+const textHue = 0x480; // Color of the text.
+const fletchingID = 4029; // Script ID for this fletching gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Generic item details gump
+const craftGumpID = 4027; // Shared crafting menu frame
+const itemsPerPage = 10; // Items per subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const fletchingSkillID = 8; // Index of "bowcraft" in ItemDetailGump skillNames[]
+const craftMapRegistryID = 4038;
+var FletchingMap = {};
+
+/** @type { () => boolean } */
+function LoadFletchingMap()
+{
+ FletchingMap = {};
+
+ var fletchingEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "fletching" );
+
+ if( !fletchingEntries || !IsFletchingArrayValue( fletchingEntries ) )
+ {
+ Console.Warning( "Fletching: Unable to load fletching craft map data." );
+ return false;
+ }
+
+ for( var i = 0; i < fletchingEntries.length; i++ )
+ {
+ var entry = fletchingEntries[i];
+
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = fletchingSkillID;
+
+ FletchingMap[entry.makeID] = entry;
+ }
+
+ Console.Print( "Fletching: Loaded " + fletchingEntries.length + " craft map entries.\n" );
+ return true;
+}
+
+/** @type { ( value: any ) => boolean } */
+function IsFletchingArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+// o--------------------------------------------------------------------------o
+// | PageX() - build a page of fletching items |
+// o--------------------------------------------------------------------------o
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
function PageX( socket, pUser, pageNum )
{
- // Pages 1 - 3
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for ( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ if( !FletchingMap || Object.keys( FletchingMap ).length == 0 )
{
- var index = i % 10;
- if ( index == 0 )
+ if( !LoadFletchingMap() )
+ {
+ socket.SysMessage( "Fletching craft map failed to load." );
+ return;
+ }
+ }
+
+ var pageItems;
+
+ // Last Ten page (only if you wire a tab to 999 later)
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenFletching" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
+ {
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val );
+ }
+ }
+ else
+ {
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in FletchingMap )
+ {
+ if( !FletchingMap.hasOwnProperty( key ))
+ continue;
+
+ var makeID = parseInt( key );
+ var data = FletchingMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
+ {
+ var ea = FletchingMap[a];
+ var eb = FletchingMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = FletchingMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )) )
+ pageItems.push( id );
+ }
+
+ // Fallback: if no items on this page and it's not page 1, go to page 1
+ if( pageItems.length == 0 && pageNum != 1 )
{
- if ( i > 0 )
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var key2 in FletchingMap )
{
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ if( !FletchingMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = FletchingMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
}
- myGump.AddPage( ( i / 10 ) + 1 );
+ makeIDs.sort( function( a, b )
+ {
+ var ea2 = FletchingMap[a];
+ var eb2 = FletchingMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = FletchingMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )) )
+ pageItems.push( id2 );
+ }
+ }
+ }
+
+ // Subpage handling (future-proof; currently only 1 subpage per page)
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var fletchGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", fletchGump, socket );
+ fletchGump.AddPage( 1 );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // use makeID directly as buttonID
+
+ var data5 = FletchingMap[makeID];
- if ( i > 0 )
+ if( !data5 )
+ {
+ entryText = "[Missing MakeID: " + makeID + "]";
+ }
+ else
+ {
+ if( data5.customName )
{
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
+ {
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), LabelHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ) );
+ // Craft button uses makeID
+ fletchGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ fletchGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+
+ // Detail button: 20000 + makeID
+ fletchGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
+ }
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ fletchGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ fletchGump.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
+ }
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + ( 100 * pageNum ) + i );
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ fletchGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ fletchGump.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
}
- myGump.Send( socket );
- myGump.Free();
+
+ fletchGump.Send( socket );
+ fletchGump.Free();
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+/** @type { ( pUser: Character, timerID: number ) => void } */
function onTimer( pUser, timerID )
{
if( !ValidateObject( pUser ))
return;
- var socket = pUser.socket;
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
- switch ( timerID )
+ if( timerID >= 1 && timerID <= 3 )
{
- case 1: // Page 1 - Materials
- case 2: // Page 2 - Ammunition
- case 3: // Page 3 - Weapons
- PageX( socket, pUser, timerID );
- break;
+ PageX( pSocket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
}
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
{
- var pUser = pSock.currentChar;
+ if( socket == null )
+ return;
- // Don't continue if character is invalid, or worse... dead!
+ var pUser = socket.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
// Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ var tool = socket.tempObj;
+ if( !ValidateObject( tool ) || !pUser.InRange( tool, 3 ))
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
return;
}
- if( bItem.movable == 3 )
+ if( tool.movable == 3 )
{
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
return;
}
- var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ var packOwner = GetPackOwner( tool, 0 );
+ if( ValidateObject( packOwner ))
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ if( packOwner.serial != pUser.serial )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That resource is in someone else's backpack!
return;
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This has to be in your backpack before you can use it.
+ return;
+ }
+
+ var gumpID = fletchingID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
+ {
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
+ }
+
+ if( pButton >= 9001 && pButton < 10000 )
+ {
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs (1–3: Materials, Ammunition, Weapons)
+ if( pButton >= 1 && pButton <= 3 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
+ return;
+ }
+
+ // Last Ten (if you wire a tab to this)
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Fletching", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
return;
}
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Fletching" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ var makeID = 0;
var timerID = 0;
- if(( pButton >= 100 && pButton <= 302 ) || pButton == 5000 )
+ // Craft buttons use makeID directly
+ if( FletchingMap[pButton] != undefined )
{
- if( pButton == 5000 )
+ makeID = pButton;
+ var data = FletchingMap[makeID];
+ timerID = data.timerID || 1;
+
+ // Era / recipe checks
+ if( !eraOK( data ))
{
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
+ socket.SysMessage( "That item is not available in this era." );
+ return;
}
- else
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
}
- }
- switch ( pButton )
- {
- case 0: // Abort and do nothing
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;
- case 1: // Page 1 - Materials
- case 2: // Page 2 - Ammunition
- case 3: // Page 3 - Weapons
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- // Make Items
- case 100: // Kindling
- makeID = 190; timerID = 1; break;
- case 101: // Shaft
- makeID = 194; timerID = 1; break;
- case 102: // Five Shafts
- makeID = 195; timerID = 1; break;
- case 103: // Twenty Shafts
- makeID = 196; timerID = 1; break;
- case 104: // Fifty Shafts
- makeID = 197; timerID = 1; break;
- case 200: // Arrow
- makeID = 198; timerID = 2; break;
- case 201: // Five Arrows
- makeID = 199; timerID = 2; break;
- case 202: // Twenty Arrows
- makeID = 200; timerID = 2; break;
- case 203: // Fifty Arrows
- makeID = 201; timerID = 2; break;
- case 204: // Bolt
- makeID = 202; timerID = 2; break;
- case 205: // Five Bolts
- makeID = 203; timerID = 2; break;
- case 206: // Twenty Bolts
- makeID = 204; timerID = 2; break;
- case 207: // Fifty Bolts
- makeID = 205; timerID = 2; break;
- case 300: // Bow
- makeID = 191; timerID = 3; break;
- case 301: // Crossbow
- makeID = 192; timerID = 3; break;
- case 302: // Heavy Crossbow
- makeID = 193; timerID = 3; break;
- // Show Item Details
- case 2100: // Kindling
- itemDetailsID = 190; break;
- case 2101: // Shaft
- itemDetailsID = 194; break;
- case 2102: // Five Shafts
- itemDetailsID = 195; break;
- case 2103: // Twenty Shafts
- itemDetailsID = 196; break;
- case 2104: // Fifty Shafts
- itemDetailsID = 197; break;
- case 2200: // Arrow
- itemDetailsID = 198; break;
- case 2201: // Five Arrows
- itemDetailsID = 199; break;
- case 2202: // Twenty Arrows
- itemDetailsID = 200; break;
- case 2203: // Fifty Arrows
- itemDetailsID = 201; break;
- case 2204: // Bolt
- itemDetailsID = 202; break;
- case 2205: // Five Bolts
- itemDetailsID = 203; break;
- case 2206: // Twenty Bolts
- itemDetailsID = 204; break;
- case 2207: // Fifty Bolts
- itemDetailsID = 205; break;
- case 2300: // Bow
- itemDetailsID = 191; break;
- case 2301: // Crossbow
- itemDetailsID = 192; break;
- case 2302: // Heavy Crossbow
- itemDetailsID = 193; break;
- default:
- break;
+ pUser.SetTempTag( "MakeLast_Fletching", makeID );
+
+ MakeItem( socket, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
+ if( GetServerSetting( "ToolUseLimit" ))
+ {
+ tool.usesLeft -= 1;
+ if( tool.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ {
+ tool.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
+ }
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, fletchingID );
+ return;
}
- if( makeID != 0 )
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
{
- MakeItem( pSock, pUser, makeID );
- if( GetServerSetting( "ToolUseLimit" ))
+ var detailMakeID = pButton - 20000;
+ var entry = FletchingMap[detailMakeID];
+
+ if( entry )
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || fletchingSkillID );
+
+ // Clear old harvest tags
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old custom harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // Optional harvest dictIDs
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // Optional custom names – plugs into your ItemDetail custom harvest name logic
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
}
- }
- pUser.StartTimer( gumpDelay, timerID, true );
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
}
- else if( itemDetailsID != 0 )
+}
+
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenFletching" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
+ }
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
}
+
+ pUser.SetTempTag( "LastTenFletching", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
}
diff --git a/data/js/skill/craft/glassblowing.js b/data/js/skill/craft/glassblowing.js
index 304b41a31..508c369fb 100644
--- a/data/js/skill/craft/glassblowing.js
+++ b/data/js/skill/craft/glassblowing.js
@@ -1,215 +1,523 @@
///
// @ts-check
-const LabelHue = 0x480; // Color of the text.
-const LabelColor = 0x7FFF; // Second Color of text.
-const scriptID = 4036; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
-
-const myPage = [
- // Page 1 - Misc
- [13600, 13601, 13602, 13603, 13604, 13605, 13606, 13607, 13608, 13609, 13610, 13611 ]
-];
+const textHue = 0x480; // Color hue for all text in the crafting gump
+const glassblowingID = 4036; // Script ID used to identify and close this gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Script ID used to show item detail tooltips
+const craftGumpID = 4027; // TriggerEvent ID used to build the crafting gump UI
+const itemsPerPage = 10; // Number of craftable items shown per gump subpage
+const displayUnlearnedRecipes = true; // Show recipes player has not learned
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ) );
+const glassSkillID = 0; // alchmey skill
+const glassHarvestDict = 13504; // sand
-function PageX( socket, pUser, pageNum )
+const craftMapRegistryID = 4038;
+var GlassBlowingMap = {};
+
+/** @type { () => boolean } */
+function LoadGlassBlowingMap()
{
- // Pages 1
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for ( var i = 0; i < myPage[pageNum - 1].length; i++ )
- {
- var index = i % 10;
- if ( index == 0 )
- {
- if ( i > 0 )
- {
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
- }
+ GlassBlowingMap = {};
- myGump.AddPage( ( i / 10 ) + 1 );
+ var glassEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "glassblowing" );
- if ( i > 0 )
- {
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
- }
- }
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
+ if( !glassEntries || !IsGlassBlowingArrayValue( glassEntries ) )
+ {
+ Console.Warning( "Glassblowing: Unable to load glassblowing craft map data." );
+ return false;
+ }
- myGump.AddText( 255, 60 + ( index * 20 ), LabelHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ) );
+ for( var i = 0; i < glassEntries.length; i++ )
+ {
+ var entry = glassEntries[i];
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + ( 100 * pageNum ) + i );
- }
- myGump.Send( socket );
- myGump.Free();
-}
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
-function onTimer( pUser, timerID )
-{
- if( !ValidateObject( pUser ))
- return;
+ if( entry.skill === undefined )
+ entry.skill = glassSkillID;
- var socket = pUser.socket;
+ if( !entry.harvest )
+ entry.harvest = [ glassHarvestDict ];
- switch( timerID )
- {
- case 1: // Page 1 - Maps
- PageX( socket, pUser, timerID );
- break;
+ GlassBlowingMap[entry.makeID] = entry;
}
+
+ Console.Print( "Glassblowing: Loaded " + glassEntries.length + " craft map entries.\n" );
+ return true;
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+/** @type { ( value: any ) => boolean } */
+function IsGlassBlowingArrayValue( value )
{
- var pUser = pSock.currentChar;
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
- // Don't continue if character is invalid, or worse... dead!
- if( !ValidateObject( pUser ) || pUser.dead )
+// If you ever need a specific item to use 2 or more resources, just override its harvest array:
+// GlassBlowingMap[3017].harvest = [ glassHarvestDict, 10016 ]; // two harvest resources
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
+function PageX( socket, pUser, pageNum )
+{
+ if( !socket || !ValidateObject( pUser ))
return;
- // Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ if( !GlassBlowingMap || Object.keys( GlassBlowingMap ).length == 0 )
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
- return;
+ if( !LoadGlassBlowingMap() )
+ {
+ socket.SysMessage( "Glassblowing craft map failed to load." );
+ return;
+ }
}
- if( bItem.movable == 3 )
- {
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
- return;
- }
+ var pageItems;
- var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ // Special "Last Ten" page uses stored makeIDs directly
+ if( pageNum == 999 )
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ var lastTenRaw = pUser.GetTempTag( "LastTenGlassblowing" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
- return;
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val );
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
- return;
+ // Build list of makeIDs for this page from GlassBlowingMap
+ var makeIDs = [];
+ for( var makeIDStr in GlassBlowingMap )
+ {
+ if( !GlassBlowingMap.hasOwnProperty( makeIDStr ))
+ continue;
+
+ var makeID = parseInt( makeIDStr );
+ var data = GlassBlowingMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort numeric so order is stable: 3000,3001,...,3012
+ makeIDs.sort(function(a, b){ return a - b; });
+
+ // Filter by era / recipe in that order
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = GlassBlowingMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )))
+ {
+ pageItems.push( id );
+ }
+ }
+
+ // Fallback if page empty and not page 1
+ if( pageItems.length == 0 && pageNum != 1 )
+ {
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var makeIDStr2 in GlassBlowingMap )
+ {
+ if( !GlassBlowingMap.hasOwnProperty( makeIDStr2 ))
+ continue;
+
+ var makeID2 = parseInt( makeIDStr2 );
+ var data3 = GlassBlowingMap[makeID2];
+ if( !data3 || data3.page != 1 )
+ continue;
+
+ makeIDs.push( makeID2 );
+ }
+ makeIDs.sort(function(a, b){ return a - b; });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = GlassBlowingMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )))
+ {
+ pageItems.push( id2 );
+ }
+ }
+ }
+ }
+
+ // Subpage handling
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+
+ if( subPage < 1 )
+ subPage = 1;
+
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
}
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var timerID = 0;
+ var glassGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", glassGump, socket );
+ glassGump.AddPage( 1 );
- if(( pButton >= 100 && pButton <= 305 ) || pButton == 5000 )
+ for( var j = startIndex; j < endIndex; j++ )
{
- if( pButton == 5000 )
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID;
+
+ var data5 = GlassBlowingMap[makeID];
+
+ if( !data5 )
{
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
+ entryText = "[Missing MakeID: " + makeID + "]";
}
else
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ if( data5.customName )
+ {
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
+ {
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
+ }
}
+
+ // Craft button uses makeID
+ glassGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ glassGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+
+ // Detail button: 20000 + makeID (matches your current onGumpPress)
+ glassGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
}
- switch ( pButton )
+ // Prev subpage
+ if( subPage > 1 )
{
- case 0: // Abort and do nothing
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;
- case 1: // Page 1 - Maps
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- // Make Items
- case 100: // empty bottle
- makeID = 3000; timerID = 1; break;
- case 101: // flask (small)
- makeID = 3001; timerID = 1; break;
- case 102: // flask (medium)
- makeID = 3002; timerID = 1; break;
- case 103: // flask (curved)
- makeID = 3003; timerID = 1; break;
- case 103: // flask (large #1)
- makeID = 3004; timerID = 1; break;
- case 103: // flask (large #2)
- makeID = 3005; timerID = 1; break;
- case 103: // flask (bubbling blue)
- makeID = 3006; timerID = 1; break;
- case 103: // flask (bubbling purple)
- makeID = 3007; timerID = 1; break;
- case 103: // flask (bubbling red)
- makeID = 3008; timerID = 1; break;
- case 103: // empty vials
- makeID = 3009; timerID = 1; break;
- case 103: // full vials
- makeID = 3010; timerID = 1; break;
- case 103: // spinning hourglass
- makeID = 3011; timerID = 1; break;
- // Show Item Details
- case 2100: // empty bottle
- itemDetailsID = 3000; break;
- case 2101: // flask (small)
- itemDetailsID = 3001; break;
- case 2102: // flask (medium)
- itemDetailsID = 3002; break;
- case 2103: // flask (curved)
- itemDetailsID = 3003; break;
- case 2104: // flask (large #1)
- itemDetailsID = 3004; break;
- case 2105: // flask (large #2)
- itemDetailsID = 3005; break;
- case 2106: // flask (bubbling blue)
- itemDetailsID = 3006; break;
- case 2107: // flask (bubbling purple)
- itemDetailsID = 3007; break;
- case 2108: // flask (bubbling red)
- itemDetailsID = 3008; break;
- case 2109: // empty vials
- itemDetailsID = 3009; break;
- case 2110: // full vials
- itemDetailsID = 3010; break;
- case 2111: // spinning hourglass
- itemDetailsID = 3011; break;
- default:
- break;
+ glassGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ glassGump.AddHTMLGump( 255, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10101, socket.language ) + "" );
}
- if( makeID != 0 )
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ glassGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ glassGump.AddHTMLGump( 405, 263, 100, 18, false, false, " " + GetDictionaryEntry( 10100, socket.language ) + "" );
+ }
+
+ glassGump.Send( socket );
+ glassGump.Free();
+}
+
+/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+function onTimer( pUser, timerID )
+{
+ if( !ValidateObject( pUser ))
+ return;
+
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
+
+ if( timerID >= 1 && timerID <= 8 )
+ {
+ PageX( pSocket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
+ }
+}
+
+/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
+{
+ if( socket == null )
+ return;
+
+ var pUser = socket.currentChar;
+
+ if( !ValidateObject( pUser ) || pUser.dead )
+ return;
+
+ var bItem = socket.tempObj;
+ if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ {
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language ));
+ return;
+ }
+
+ if( bItem.movable == 3 )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language ));
+ return;
+ }
+
+ var iPackOwner = GetPackOwner( bItem, 0 );
+ if( ValidateObject( iPackOwner ))
+ {
+ if( iPackOwner.serial != pUser.serial )
+ {
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language ));
+ return;
+ }
+ }
+ else
+ {
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language ));
+ return;
+ }
+
+ var gumpID = glassblowingID + 0xffff;
+
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
+ {
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
+ }
+
+ if( pButton >= 9001 && pButton < 10000 )
+ {
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs
+ if( pButton >= 1 && pButton <= 8 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
+ return;
+ }
+
+ // Last Ten
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Glass", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ var makeID = 0;
+ var timerID = 0;
+
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Glass" );
+ if( last )
+ pButton = last;
+ else
+ return;
+ }
+
+ // Craft buttons use makeID directly
+ if( GlassBlowingMap[pButton] != undefined )
+ {
+ makeID = pButton;
+ var data = GlassBlowingMap[makeID];
+ timerID = data.timerID || 1;
+
+ if( !eraOK( data ))
+ {
+ socket.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ pUser.SetTempTag( "MakeLast_Glass", makeID );
+
+ MakeItem( socket, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
+ if( GetServerSetting( "ToolUseLimit" ))
+ {
+ bItem.usesLeft -= 1;
+ if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ {
+ bItem.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language ));
+ }
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, glassblowingID );
+ return;
+ }
+
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
{
- pUser.AddScriptTrigger(4033); // crafting_complete.js for applying map settings
- MakeItem( pSock, pUser, makeID );
- if( GetServerSetting( "ToolUseLimit" ))
+ var detailMakeID = pButton - 20000;
+ var entry = GlassBlowingMap[detailMakeID];
+
+ if( entry )
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || glassSkillID );
+
+ // Clear old harvest tags to avoid cross-contamination
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // If you later add entry.harvest = [dictID1, dictID2,...], you can push them here
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // OPTIONAL custom names – these override the dictionary string
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
}
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
}
- pUser.StartTimer( gumpDelay, timerID, true );
- }
- else if( itemDetailsID != 0 )
- {
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ return;
}
}
+
+// Last Ten
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenGlassblowing" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
+ }
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenGlassblowing", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/itemdetailgump.js b/data/js/skill/craft/itemdetailgump.js
index 78a460347..04a804705 100644
--- a/data/js/skill/craft/itemdetailgump.js
+++ b/data/js/skill/craft/itemdetailgump.js
@@ -7,1773 +7,184 @@ const Alchemy = 4028;
const Fletching = 4029;
const Tailoring = 4030;
const Tinkering = 4032;
-const scriptID = 4026; // This script
+const itemDetailID = 4026; // This script
const Cooking = 4034;
const Cartography = 4035;
const Glassblowing = 4036;
+const Masonry = 4037;
+const CustomCraft = 4040;
const exceptionalWearablesOnly = true;
function ItemDetailGump( pUser )
{
+ var skillNames = [
+ "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 ",
+ "mysticism ",
+ "imbuing",
+ "throwing "
+ ];
+
var socket = pUser.socket;
var itemGump = new Gump;
var createEntry = null;
- var HARVEST;
+ // Now supports both dict-based and custom harvest names
+ // harvestResource[i] = { id: number, name: string }
+ var harvestResource = [];
var mainSkill;
- switch( pUser.GetTempTag( "ITEMDETAILS" ))
+
+ var detailTag = pUser.GetTempTag( "ITEMDETAILS" );
+ var skillTag = pUser.GetTempTag( "Skill" );
+ var harvestTag = pUser.GetTempTag( "Harvest" );
+ var harvest2Tag = pUser.GetTempTag( "Harvest2" );
+ var harvest3Tag = pUser.GetTempTag( "Harvest3" );
+ var harvest4Tag = pUser.GetTempTag( "Harvest4" );
+
+ // NEW: optional custom harvest names
+ var harvestNameTag = pUser.GetTempTag( "HarvestName" );
+ var harvest2NameTag = pUser.GetTempTag( "Harvest2Name" );
+ var harvest3NameTag = pUser.GetTempTag( "Harvest3Name" );
+ var harvest4NameTag = pUser.GetTempTag( "Harvest4Name" );
+
+ var recipeID = pUser.GetTempTag( "needRecipeID" );
+
+ if (detailTag !== null)
{
- //Start Blacksmith
- case 1:
- createEntry = CreateEntries[1];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 2:
- createEntry = CreateEntries[2];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 3:
- createEntry = CreateEntries[3];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 4:
- createEntry = CreateEntries[4];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 5:
- createEntry = CreateEntries[5];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 6:
- createEntry = CreateEntries[6];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 7:
- createEntry = CreateEntries[7];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 8:
- createEntry = CreateEntries[8];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 9:
- createEntry = CreateEntries[9];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 10:
- createEntry = CreateEntries[10];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 11:
- createEntry = CreateEntries[11];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 12:
- createEntry = CreateEntries[12];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 13:
- createEntry = CreateEntries[13];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 14:
- createEntry = CreateEntries[14];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 15:
- createEntry = CreateEntries[15];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 16:
- createEntry = CreateEntries[16];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 17:
- createEntry = CreateEntries[17];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 18:
- createEntry = CreateEntries[18];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 19:
- createEntry = CreateEntries[19];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 20:
- createEntry = CreateEntries[20];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 21:
- createEntry = CreateEntries[21];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 22:
- createEntry = CreateEntries[22];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 23:
- createEntry = CreateEntries[23];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 24:
- createEntry = CreateEntries[24];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 25:
- createEntry = CreateEntries[25];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 26:
- createEntry = CreateEntries[26];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 27:
- createEntry = CreateEntries[27];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 28:
- createEntry = CreateEntries[28];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 29:
- createEntry = CreateEntries[29];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 30:
- createEntry = CreateEntries[30];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 31:
- createEntry = CreateEntries[31];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 32:
- createEntry = CreateEntries[32];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 33:
- createEntry = CreateEntries[33];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 34:
- createEntry = CreateEntries[34];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 35:
- createEntry = CreateEntries[35];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 36:
- createEntry = CreateEntries[36];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 37:
- createEntry = CreateEntries[37];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 38:
- createEntry = CreateEntries[38];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 39:
- createEntry = CreateEntries[39];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 40:
- createEntry = CreateEntries[40];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 41:
- createEntry = CreateEntries[41];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 42:
- createEntry = CreateEntries[42];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 43:
- createEntry = CreateEntries[43];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 44:
- createEntry = CreateEntries[44];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 45:
- createEntry = CreateEntries[45];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 46:
- createEntry = CreateEntries[46];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 47:
- createEntry = CreateEntries[47];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 48:
- createEntry = CreateEntries[48];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- case 49:
- createEntry = CreateEntries[49];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.blacksmithing );
- break;
- //Start Carpentry
- case 50:
- createEntry = CreateEntries[50];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 51:
- createEntry = CreateEntries[51];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 52:
- createEntry = CreateEntries[52];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 53:
- createEntry = CreateEntries[53];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 54:
- createEntry = CreateEntries[54];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 55:
- createEntry = CreateEntries[55];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 56:
- createEntry = CreateEntries[56];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 57:
- createEntry = CreateEntries[57];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 58:
- createEntry = CreateEntries[58];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 59:
- createEntry = CreateEntries[59];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 60:
- createEntry = CreateEntries[60];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 61:
- createEntry = CreateEntries[61];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 62:
- createEntry = CreateEntries[62];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 63:
- createEntry = CreateEntries[63];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 64:
- createEntry = CreateEntries[64];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 65:
- createEntry = CreateEntries[65];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 66:
- createEntry = CreateEntries[66];
- HARVEST = [10611, 10612];//missing loops
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 67:
- createEntry = CreateEntries[67];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 68:
- createEntry = CreateEntries[68];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 69:
- createEntry = CreateEntries[69];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 70:
- createEntry = CreateEntries[70];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 71:
- createEntry = CreateEntries[71];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 72:
- createEntry = CreateEntries[72];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 73:
- createEntry = CreateEntries[73];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 74:
- createEntry = CreateEntries[74];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 75:
- createEntry = CreateEntries[75];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 76:
- createEntry = CreateEntries[76];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 77:
- createEntry = CreateEntries[77];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 78:
- createEntry = CreateEntries[78];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 79:
- createEntry = CreateEntries[79];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 80:
- createEntry = CreateEntries[80];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 81:
- createEntry = CreateEntries[81];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 82:
- createEntry = CreateEntries[82];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 83:
- createEntry = CreateEntries[83];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 84:
- createEntry = CreateEntries[84];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 85:
- createEntry = CreateEntries[85];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 86:
- createEntry = CreateEntries[86];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 87:
- createEntry = CreateEntries[87];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 88:
- createEntry = CreateEntries[8];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 89:
- createEntry = CreateEntries[89];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 90:
- createEntry = CreateEntries[90];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 91:
- createEntry = CreateEntries[91];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 92:
- createEntry = CreateEntries[92];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 93:
- createEntry = CreateEntries[93];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 94:
- createEntry = CreateEntries[94];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 95:
- createEntry = CreateEntries[95];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 96:
- createEntry = CreateEntries[96];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 97:
- createEntry = CreateEntries[97];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 98:
- createEntry = CreateEntries[98];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 99:
- createEntry = CreateEntries[99];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 100:
- createEntry = CreateEntries[100];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 101:
- createEntry = CreateEntries[101];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 102:
- createEntry = CreateEntries[102];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 103:
- createEntry = CreateEntries[103];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 104:
- createEntry = CreateEntries[104];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 105:
- createEntry = CreateEntries[105];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 106:
- createEntry = CreateEntries[106];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 107:
- createEntry = CreateEntries[107];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 108:
- createEntry = CreateEntries[108];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 109:
- createEntry = CreateEntries[109];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 110:
- pUser.TextMessage( "Case 110!" );
- createEntry = CreateEntries[110];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 111:
- createEntry = CreateEntries[111];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 112:
- createEntry = CreateEntries[112];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 113:
- createEntry = CreateEntries[113];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 114:
- createEntry = CreateEntries[114];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 115:
- createEntry = CreateEntries[115];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 116:
- createEntry = CreateEntries[116];
- HARVEST = [10014, 10016];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 117:
- createEntry = CreateEntries[117];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 118:
- createEntry = CreateEntries[118];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 119:
- createEntry = CreateEntries[119];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 120:
- createEntry = CreateEntries[120];
- HARVEST = [10014, 10015];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 121:
- createEntry = CreateEntries[121];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 122:
- createEntry = CreateEntries[122];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 123:
- createEntry = CreateEntries[123];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- case 124:
- createEntry = CreateEntries[124];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.carpentry );
- break;
- // Start Tailoring
- case 130:
- createEntry = CreateEntries[130];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 131:
- createEntry = CreateEntries[131];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 132:
- createEntry = CreateEntries[132];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 133:
- createEntry = CreateEntries[133];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 134:
- createEntry = CreateEntries[134];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 135:
- createEntry = CreateEntries[135];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 136:
- createEntry = CreateEntries[136];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 137:
- createEntry = CreateEntries[137];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 138:
- createEntry = CreateEntries[138];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 139:
- createEntry = CreateEntries[139];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 140:
- createEntry = CreateEntries[140];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 141:
- createEntry = CreateEntries[141];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 142:
- createEntry = CreateEntries[142];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 143:
- createEntry = CreateEntries[143];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 144:
- createEntry = CreateEntries[144];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 145:
- createEntry = CreateEntries[145];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 146:
- createEntry = CreateEntries[146];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 147:
- createEntry = CreateEntries[147];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 148:
- createEntry = CreateEntries[148];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 149:
- createEntry = CreateEntries[149];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 150:
- createEntry = CreateEntries[150];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 151:
- createEntry = CreateEntries[151];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 152:
- createEntry = CreateEntries[152];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 153:
- createEntry = CreateEntries[153];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 154:
- createEntry = CreateEntries[154];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 155:
- createEntry = CreateEntries[155];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 156:
- createEntry = CreateEntries[156];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 157:
- createEntry = CreateEntries[157];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 158:
- createEntry = CreateEntries[158];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break
- case 159:
- createEntry = CreateEntries[159];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 160:
- createEntry = CreateEntries[160];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 161:
- createEntry = CreateEntries[161];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 162:
- createEntry = CreateEntries[162];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 163:
- createEntry = CreateEntries[163];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 164:
- createEntry = CreateEntries[164];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 165:
- createEntry = CreateEntries[165];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 166:
- createEntry = CreateEntries[166];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 167:
- createEntry = CreateEntries[167];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 168:
- createEntry = CreateEntries[168];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 169:
- createEntry = CreateEntries[169];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 170:
- createEntry = CreateEntries[170];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 171:
- createEntry = CreateEntries[171];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 172:
- createEntry = CreateEntries[172];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 173:
- createEntry = CreateEntries[173];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 174:
- createEntry = CreateEntries[174];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 175:
- createEntry = CreateEntries[175];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 176:
- createEntry = CreateEntries[176];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 177:
- createEntry = CreateEntries[177];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 178:
- createEntry = CreateEntries[178];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 179:
- createEntry = CreateEntries[179];
- HARVEST = [10007];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 180:
- createEntry = CreateEntries[180];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 181:
- createEntry = CreateEntries[181];
- HARVEST = [10007, 10008];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 182:
- createEntry = CreateEntries[182];
- HARVEST = [10007, 10008];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 183:
- createEntry = CreateEntries[184];
- HARVEST = [10007, 10008];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 184:
- createEntry = CreateEntries[184];
- HARVEST = [10007, 10008];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 185:
- createEntry = CreateEntries[185];
- HARVEST = [10007, 10008];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- case 186:
- createEntry = CreateEntries[135];
- HARVEST = [10016];
- mainSkill = parseInt( pUser.skills.tailoring );
- break;
- // Start Fletching
- case 190:
- createEntry = CreateEntries[190];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 190: // Kindling
- createEntry = CreateEntries[190];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 191: // Bow
- createEntry = CreateEntries[191];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 192: // Crossbow
- createEntry = CreateEntries[192];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 193: // Heavy Crossbow
- createEntry = CreateEntries[193];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 194: // Shaft
- createEntry = CreateEntries[194];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 195: // Five Shafts
- createEntry = CreateEntries[195];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 196: // Twenty Shafts
- createEntry = CreateEntries[196];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 197: // Fifty Shafts
- createEntry = CreateEntries[197];
- HARVEST = [10014]; // Boards or Logs
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 198: // Arrow
- createEntry = CreateEntries[198];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 199: // Five Arrows
- createEntry = CreateEntries[199];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 200: // Twenty Arrows
- createEntry = CreateEntries[200];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 201: // Fifty Arrows
- createEntry = CreateEntries[201];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 202: // Bolt
- createEntry = CreateEntries[202];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 203: // Five Bolts
- createEntry = CreateEntries[203];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 204: // Twenty Bolts
- createEntry = CreateEntries[204];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 205: // Fifty Bolts
- createEntry = CreateEntries[205];
- HARVEST = [10029, 10028]; // Shaft, Feather
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 206:
- createEntry = CreateEntries[206];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 207:
- createEntry = CreateEntries[207];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- case 208:
- createEntry = CreateEntries[208];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.bowcraft );
- break;
- // Start Tinkering
- case 274: // Axle
- createEntry = CreateEntries[274];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 273: // Clock Frame
- createEntry = CreateEntries[273];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 270: // Jointing Plane
- createEntry = CreateEntries[270];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 271: // Moulding Plane
- createEntry = CreateEntries[271];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 272: // Smoothing Plane
- createEntry = CreateEntries[272];
- HARVEST = [10014];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 2 - Tools
- case 218: // Dovetail Saw
- createEntry = CreateEntries[218];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 215: // Draw Knife
- createEntry = CreateEntries[215];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 252: // Froe
- createEntry = CreateEntries[252];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 255: // Hammer
- createEntry = CreateEntries[255];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 214: // Hatchet
- createEntry = CreateEntries[214];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 258: // Inshave
- createEntry = CreateEntries[258];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 260: // Lockpick
- createEntry = CreateEntries[260];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 211: // Mortar and Pestle
- createEntry = CreateEntries[211];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 259: // Pick Axe
- createEntry = CreateEntries[259];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 217: // Saw
- createEntry = CreateEntries[217];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 210: // Scissors
- createEntry = CreateEntries[210];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 212: // Scorp
- createEntry = CreateEntries[212];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 216: // Sewing Kit
- createEntry = CreateEntries[216];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 254: // Shovel
- createEntry = CreateEntries[254];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 257: // Sledge Hammer
- createEntry = CreateEntries[257];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 256: // Smith's Hammer
- createEntry = CreateEntries[256];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 253: // Tongs
- createEntry = CreateEntries[253];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 213: // Tool Kit (Tinker's tools)
- createEntry = CreateEntries[213];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 214: // Fletcher's Tools
- createEntry = CreateEntries[284];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 3 - Parts
- case 224: // Barrel Hoops
- createEntry = CreateEntries[224];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 221: // Barrel Tap
- createEntry = CreateEntries[221];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 220: // Clock parts
- createEntry = CreateEntries[220];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 219: // Gears
- createEntry = CreateEntries[219];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 225: // Hinge
- createEntry = CreateEntries[225];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 223: // Sextant parts
- createEntry = CreateEntries[223];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 222: // Springs
- createEntry = CreateEntries[222];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 4 - Utensils
- case 226: // Butcher Knife
- createEntry = CreateEntries[226];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 232: // Cleaver
- createEntry = CreateEntries[232];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 230: // Fork
- createEntry = CreateEntries[230];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 231: // Fork
- createEntry = CreateEntries[231];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 235: // Goblet
- createEntry = CreateEntries[235];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 233: // Knife
- createEntry = CreateEntries[233];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 234: // Knife
- createEntry = CreateEntries[234];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 236: // Pewter Mug
- createEntry = CreateEntries[236];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 229: // Plate
- createEntry = CreateEntries[229];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 237: // Skinning Knife
- createEntry = CreateEntries[237];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 227: // Spoon
- createEntry = CreateEntries[227];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 228: // Spoon
- createEntry = CreateEntries[228];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 5 - Jewelry
- case 243: // Bracelet
- createEntry = CreateEntries[243];
- HARVEST = [10015, 12005];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 241: // Earrings
- createEntry = CreateEntries[241];
- HARVEST = [10015, 12005];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 239: // Necklage (Golden beads)
- createEntry = CreateEntries[239];
- HARVEST = [10015, 12005];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 240: // Necklace (Silver beads)
- createEntry = CreateEntries[240];
- HARVEST = [10015, 12005];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 242: // Necklace (Round)
- createEntry = CreateEntries[242];
- HARVEST = [10015, 12005];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 238: // Weddingband (newbiefied)
- createEntry = CreateEntries[238];
- HARVEST = [10015, 12006];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 6 - Miscellaneous
- case 248: // Globe
- createEntry = CreateEntries[248];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 251: // Heating stand
- createEntry = CreateEntries[251];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 247: // Iron Key
- createEntry = CreateEntries[247];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 244: // Keyring
- createEntry = CreateEntries[244];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 250: // Lantern
- createEntry = CreateEntries[250];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 246: // Scales
- createEntry = CreateEntries[246];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 249: // Spy glass
- createEntry = CreateEntries[249];
- HARVEST = [10015];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 7 - Multi-Component Items
- case 275: // Axle and Gears
- createEntry = CreateEntries[275];
- HARVEST = [11801, 11863];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 276: // Clock
- createEntry = CreateEntries[276];
- HARVEST = [11802, 11862];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 277: // Clock
- createEntry = CreateEntries[277];
- HARVEST = [11802, 11862];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 278: // Clock Parts
- createEntry = CreateEntries[278];
- HARVEST = [11801, 11863];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 279: // Locked Box
- createEntry = CreateEntries[279];
- HARVEST = [10634];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 280: // Locked Chest
- createEntry = CreateEntries[280];
- HARVEST = [10638];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 281: // Potion Keg
- createEntry = CreateEntries[281];
- HARVEST = [10642, 11861, 10612, 10928];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 282: // Sextant
- createEntry = CreateEntries[282];
- HARVEST = [11948];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 283: // Sextant Parts
- createEntry = CreateEntries[283];
- HARVEST = [11801, 11863];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 8 - Candles
- case 245: // Candelabra
- createEntry = CreateEntries[245];
- HARVEST = [10015, 12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 310: // Standing Candelabra
- createEntry = CreateEntries[310];
- HARVEST = [10015, 12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 315: // Regular Candle
- createEntry = CreateEntries[315];
- HARVEST = [10015, 12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 312: // Round Candle
- createEntry = CreateEntries[312];
- HARVEST = [12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 316: // Skull with Candle
- createEntry = CreateEntries[316];
- HARVEST = [12000, 12004];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 314: // Small Candle
- createEntry = CreateEntries[314];
- HARVEST = [12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 311: // Tall Candle
- createEntry = CreateEntries[311];
- HARVEST = [10015, 12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 313: // Thick Candle
- createEntry = CreateEntries[313];
- HARVEST = [12000];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- // Page 9 - Traps
- case 261: // Dart Trap
- createEntry = CreateEntries[261];
- HARVEST = [10015, 12001];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 263: // Explosion Trap
- createEntry = CreateEntries[263];
- HARVEST = [10015, 12003];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- case 262: // Poison Trap
- createEntry = CreateEntries[262];
- HARVEST = [10015, 12002];
- mainSkill = parseInt( pUser.skills.tinkering );
- break;
- //Start Alchemy
- case 290:
- createEntry = CreateEntries[290];
- HARVEST = [10019];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 291:
- createEntry = CreateEntries[291];
- HARVEST = [10019];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 292:
- createEntry = CreateEntries[292];
- HARVEST = [10020];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 293:
- createEntry = CreateEntries[293];
- HARVEST = [10020];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 294:
- createEntry = CreateEntries[294];
- HARVEST = [10020];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 295:
- createEntry = CreateEntries[295];
- HARVEST = [10021];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 296:
- createEntry = CreateEntries[296];
- HARVEST = [10021];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 297:
- createEntry = CreateEntries[297];
- HARVEST = [10021];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 298:
- createEntry = CreateEntries[298];
- HARVEST = [10022];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 299:
- createEntry = CreateEntries[299];
- HARVEST = [10022];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 300:
- createEntry = CreateEntries[300];
- HARVEST = [10022];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 301:
- createEntry = CreateEntries[301];
- HARVEST = [10024];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 302:
- createEntry = CreateEntries[302];
- HARVEST = [10024];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 303:
- createEntry = CreateEntries[303];
- HARVEST = [10024];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 304:
- createEntry = CreateEntries[304];
- HARVEST = [10024];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 305:
- createEntry = CreateEntries[305];
- HARVEST = [10025];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 306:
- createEntry = CreateEntries[306];
- HARVEST = [10025];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 307:
- createEntry = CreateEntries[307];
- HARVEST = [10026];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 308:
- createEntry = CreateEntries[308];
- HARVEST = [10026];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- case 309:
- createEntry = CreateEntries[309];
- HARVEST = [10023];
- mainSkill = parseInt( pUser.skills.alchemy );
- break;
- // Start Cooking
- // Page 1 - Ingredients
- case 1500: // Sack of Flour
- createEntry = CreateEntries[1500];
- HARVEST = [11636];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1501: // Dough
- createEntry = CreateEntries[1501];
- HARVEST = [11637, 11638];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1502: // Sweet Dough
- createEntry = CreateEntries[1502];
- HARVEST = [11607, 11639];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1503: // Cake Mix
- createEntry = CreateEntries[1503];
- HARVEST = [11637, 11608];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1504: // Cookie Mix
- createEntry = CreateEntries[1504];
- HARVEST = [11639, 11608];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- // Page 2 - Preparation
- case 1550: // Unbaked Quiche
- createEntry = CreateEntries[1550];
- HARVEST = [11607, 11640];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1551: // Unbaked Meat Pie
- createEntry = CreateEntries[1551];
- HARVEST = [11607, 11641];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1552: // Uncooked Sausage Pizza
- createEntry = CreateEntries[1552];
- HARVEST = [11607, 11642];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1553: // Uncooked Cheese Pizza
- createEntry = CreateEntries[1553];
- HARVEST = [11607, 11643];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1554: // Unbaked Fruit Pie
- createEntry = CreateEntries[1554];
- HARVEST = [11607, 11644];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1555: // Unbaked Peach Cobbler
- createEntry = CreateEntries[1555];
- HARVEST = [11607, 11645];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1556: // Unbaked Apple Pie
- createEntry = CreateEntries[1556];
- HARVEST = [11607, 11646];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1557: // Unbaked Pumpkin Pie
- createEntry = CreateEntries[1557];
- HARVEST = [11607, 11647];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- // Page 3 - Baking
- case 1600: // Bread Loaf
- createEntry = CreateEntries[1600];
- HARVEST = [11607];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1601: // Pan of Cookies
- createEntry = CreateEntries[1601];
- HARVEST = [11610];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1602: // Cake
- createEntry = CreateEntries[1602];
- HARVEST = [11609];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1603: // Muffins
- createEntry = CreateEntries[1603];
- HARVEST = [11608];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1604: // Baked Quiche
- createEntry = CreateEntries[1604];
- HARVEST = [11611];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1605: // Baked Meat Pie
- createEntry = CreateEntries[1605];
- HARVEST = [11612];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1606: // Sausage Pizza
- createEntry = CreateEntries[1606];
- HARVEST = [11613];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1607: // Cheese Pizza
- createEntry = CreateEntries[1607];
- HARVEST = [11614];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1608: // Baked Fruit Pie
- createEntry = CreateEntries[1608];
- HARVEST = [11615];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1609: // Baked Peach Cobbler
- createEntry = CreateEntries[1609];
- HARVEST = [11616];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1610: // Baked Apple Pie
- createEntry = CreateEntries[1610];
- HARVEST = [11617];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1611: // Baked Pumpkin Pie
- createEntry = CreateEntries[1611];
- HARVEST = [11618];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- // Page 4 - Barbecue
- case 1650: // Cooked Bird
- createEntry = CreateEntries[1650];
- HARVEST = [11648];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1651: // Chicken Leg
- createEntry = CreateEntries[1651];
- HARVEST = [11649];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1652: // Fish Steak
- createEntry = CreateEntries[1652];
- HARVEST = [11650];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1653: // Fried Eggs
- createEntry = CreateEntries[1653];
- HARVEST = [11651];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1654: // Leg of Lamb
- createEntry = CreateEntries[1654];
- HARVEST = [11652];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- case 1655: // Cut of Ribs
- createEntry = CreateEntries[1655];
- HARVEST = [11653];
- mainSkill = parseInt( pUser.skills.cooking );
- break;
- // Cartography
- case 2000: // Local Map
- createEntry = CreateEntries[2000];
- HARVEST = [13004];
- mainSkill = parseInt( pUser.skills.cartography );
- break;
- case 2001: // City Map
- createEntry = CreateEntries[2001];
- HARVEST = [13004];
- mainSkill = parseInt( pUser.skills.cartography );
- break;
- case 2002: // Sea Chart
- createEntry = CreateEntries[2002];
- HARVEST = [13004];
- mainSkill = parseInt( pUser.skills.cartography );
- break;
- case 2003: // World Map
- createEntry = CreateEntries[2003];
- HARVEST = [13004];
- mainSkill = parseInt( pUser.skills.cartography );
- break;
- // Glassblowing
- case 3000: // empty bottle
- createEntry = CreateEntries[3000];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3001: // flask (small)
- createEntry = CreateEntries[3001];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3002: // flask (medium)
- createEntry = CreateEntries[3002];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3003: // flask (curved)
- createEntry = CreateEntries[3003];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3004: // flask (large #1)
- createEntry = CreateEntries[3004];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3005: // flask (large #2)
- createEntry = CreateEntries[3005];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3006: // flask (bubbling blue)
- createEntry = CreateEntries[3006];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3007: // flask (bubbling purple)
- createEntry = CreateEntries[3007];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3008: // flask (bubbling red)
- createEntry = CreateEntries[3008];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3009: // empty vials
- createEntry = CreateEntries[3009];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3010: // full vials
- createEntry = CreateEntries[3010];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- case 3011: // spinning hourglass
- createEntry = CreateEntries[3011];
- HARVEST = [13504];
- mainSkill = parseInt(pUser.skills.alchemy);
- break;
- default:
- break;
+ try
+ {
+ createEntry = CreateEntries[detailTag];
+ }
+ catch (error)
+ {
+ createEntry = null;
+ }
+ }
+
+ if( skillTag >= 0 && skillTag < skillNames.length )
+ {
+ mainSkill = parseInt( pUser.skills[ skillNames[skillTag] ] );
+ }
+ else
+ {// if all fails fallback to alchemy
+ mainSkill = parseInt( pUser.skills.alchemy );
+ }
+
+ // Helper to build harvest entries with both id + optional custom name
+ function makeHarvestObj(idTag, nameTag)
+ {
+ // nothing set at all
+ var hasName = !!(nameTag && nameTag.length);
+ if (idTag === null && !hasName)
+ return null;
+
+ var idNum = 0;
+ if (idTag !== null)
+ {
+ var n = parseInt(idTag, 10);
+ if (!isNaN(n) && n > 0)
+ idNum = n;
+ }
+
+ // if no valid dict id and no name, skip
+ if (idNum === 0 && !hasName)
+ return null;
+
+ return {
+ id: idNum, // 0 = "no dict"
+ name: hasName ? nameTag : ""
+ };
+ }
+
+ // If harvest info is provided, rebuild harvestResource array
+ if( harvestTag !== null || harvest2Tag !== null || harvest3Tag !== null || harvest4Tag !== null
+ || harvestNameTag !== null || harvest2NameTag !== null || harvest3NameTag !== null || harvest4NameTag !== null )
+ {
+ var h1 = makeHarvestObj( harvestTag, harvestNameTag );
+ var h2 = makeHarvestObj( harvest2Tag, harvest2NameTag );
+ var h3 = makeHarvestObj( harvest3Tag, harvest3NameTag );
+ var h4 = makeHarvestObj( harvest4Tag, harvest4NameTag );
+
+ if( h1 ) harvestResource.push( h1 );
+ if( h2 ) harvestResource.push( h2 );
+ if( h3 ) harvestResource.push( h3 );
+ if( h4 ) harvestResource.push( h4 );
}
if( createEntry == null )
{
+ ItemDetailsGump( itemGump, pUser );
+
+ itemGump.AddHTMLGump( 170, 130, 320, 20, false, false, " Missing CreateEntry" );
+ itemGump.AddHTMLGump( 170, 150, 320, 20, false, false, " CreateEntry ID " + detailTag + " was not found." );
+ itemGump.AddHTMLGump( 170, 170, 320, 40, false, false, " Check the makeID in the crafting JSON file. and make sure you add it to create dfn files" );
+
+ itemGump.Send( socket );
+ itemGump.Free();
return;
}
+ // Recipe flags
+ var needsRecipe = ( recipeID > 0 );
+ var hasRecipe = false;
+ if( needsRecipe )
+ {
+ hasRecipe = HasLearnedRecipe( pUser, recipeID );
+ }
+
// Fetch properties of create entry
var createName = createEntry.name; // name of the create entry
var createID = createEntry.id; // section id of item to craft
@@ -1817,7 +228,7 @@ function ItemDetailGump( pUser )
var minSkill = skillReq[1];
var maxSkill = skillReq[2];
- itemGump.AddHTMLGump( 170, 132 + ( i * 20 ), 200, 18, 0, 0, " " + GetDictionaryEntry( 15000 + skills[i][0], socket.language ) + " " );
+ itemGump.AddHTMLGump( 170, 132 + ( i * 20 ), 200, 18, false, false, " " + GetDictionaryEntry( 15000 + skills[i][0], socket.language ) + " " );
itemGump.AddText( 430, 132 + ( i * 20 ), textHue, skills[i][1]/10 );
if( i == 0 )
@@ -1882,9 +293,47 @@ function ItemDetailGump( pUser )
chance += (( pUser.intelligence * ( primaryCraftSkill.intelligence / 10 )) / 10000 );
}
- for( var i = 0; i < HARVEST.length; i++ )
+ var maxHarvest = harvestResource.length;
+ if( resources.length < maxHarvest )
+ maxHarvest = resources.length;
+
+ // MATERIALS list, now supporting custom names
+ for( var i = 0; i < maxHarvest; i++ )
{
- itemGump.AddText( 170, 219 + ( i * 20 ), textHue, GetDictionaryEntry( HARVEST[i], socket.language ));
+ var hObj = harvestResource[i]; // { id, name }
+ var label = "";
+
+ if( hObj )
+ {
+ var dictText = "";
+ if( hObj.id && hObj.id > 0 )
+ dictText = GetDictionaryEntry( hObj.id, socket.language ) || "";
+
+ if( hObj.name && hObj.name.length > 0 )
+ {
+ // Both set: "Custom (Dict)"
+ if( dictText && dictText.length > 0 )
+ label = hObj.name + " (" + dictText + ")";
+ else
+ label = hObj.name;
+ }
+ else
+ {
+ // Only dict
+ label = dictText;
+ }
+
+ // Optional safety fallback if both are empty
+ if( !label || label.length === 0 )
+ {
+ if( hObj.id && hObj.id > 0 )
+ label = "Resource " + hObj.id;
+ else
+ label = "Resource";
+ }
+ }
+
+ itemGump.AddText( 170, 219 + ( i * 20 ), textHue, label );
itemGump.AddText( 430, 219 + ( i * 20 ), textHue, resources[i][0] );
}
@@ -1894,7 +343,9 @@ function ItemDetailGump( pUser )
exceptionalChance = 0;
}
else if( chance > 1.0 )
+ {
chance = 1.0; // Cap chance at 100%
+ }
itemGump.AddText( 430, 80, textHue, ( chance * 100 ).toFixed( 1 ) + "%" ); // Success Chance:
if( !exceptionalWearablesOnly || CheckTileFlag( createID, 22 )) // TF_WEARABLE
@@ -1912,7 +363,24 @@ function ItemDetailGump( pUser )
{
itemGump.AddText( 430, 100, textHue, "-" ); // No chance of exceptional, not a wearable item!
}
- itemGump.Send( pUser );
+
+ if( needsRecipe )
+ {
+ var recipeMsg;
+ if( hasRecipe )
+ {
+ recipeMsg = " You have learned this recipe.";
+ }
+ else
+ {
+ recipeMsg = " You have not learned this recipe.";
+ }
+
+ // OTHER box starts at y=302, you already use 302+20 for the color note (dict 10006),
+ // so 302+40 (342) is a safe line under that.
+ itemGump.AddHTMLGump( 170, 342, 310, 18, false, false, recipeMsg );
+ }
+ itemGump.Send( socket );
itemGump.Free();
}
@@ -1932,18 +400,18 @@ function ItemDetailsGump( itemGump, pUser )
itemGump.AddTiledGump( 165, 302, 355, 80, 2624 );
itemGump.AddTiledGump( 10, 387, 510, 22, 2624 );
itemGump.AddCheckerTrans( 10, 10, 510, 399 );
- itemGump.AddHTMLGump( 170, 40, 150, 20, 0, 0, " " + GetDictionaryEntry( 10000, socket.language ) + " " ); // ITEM
+ itemGump.AddHTMLGump( 170, 40, 150, 20, false, false, " " + GetDictionaryEntry( 10000, socket.language ) + " " ); // ITEM
- itemGump.AddHTMLGump( 10, 217, 150, 22, 0, 0, " " + GetDictionaryEntry( 10001, socket.language ) + " " ); //MATERIALS
- itemGump.AddHTMLGump( 10, 302, 150, 22, 0, 0, " " + GetDictionaryEntry( 10002, socket.language ) + " " ); // OTHER
- itemGump.AddHTMLGump( 170, 80, 250, 18, 0, 0, " " + GetDictionaryEntry( 10003, socket.language ) + "" ); // Success Chance:
+ itemGump.AddHTMLGump( 10, 217, 150, 22, false, false, " " + GetDictionaryEntry( 10001, socket.language ) + " " ); //MATERIALS
+ itemGump.AddHTMLGump( 10, 302, 150, 22, false, false, " " + GetDictionaryEntry( 10002, socket.language ) + " " ); // OTHER
+ itemGump.AddHTMLGump( 170, 80, 250, 18, false, false, " " + GetDictionaryEntry( 10003, socket.language ) + "" ); // Success Chance:
if( GetServerSetting( "RankSystem" ))
{
- itemGump.AddHTMLGump( 170, 100, 250, 18, 0, 0, " " + GetDictionaryEntry( 10004, socket.language ) + "" ); // Exceptional Chance:
+ itemGump.AddHTMLGump( 170, 100, 250, 18, false, false, " " + GetDictionaryEntry( 10004, socket.language ) + "" ); // Exceptional Chance:
}
itemGump.AddButton( 15, 387, 0xfa5, 1, 0, 1 );
- itemGump.AddHTMLGump( 50, 390, 150, 18, 0, 0, " " + GetDictionaryEntry( 10005, socket.language ) + "" ); // BACK
- itemGump.AddHTMLGump( 170, ( 302 + 20 ), 310, 18, 0, 0, " " + GetDictionaryEntry( 10006, socket.language ) + "" ); // * The item retains the color of this material
+ itemGump.AddHTMLGump( 50, 390, 150, 18, false, false, " " + GetDictionaryEntry( 10005, socket.language ) + "" ); // BACK
+ itemGump.AddHTMLGump( 170, ( 302 + 20 ), 310, 18, false, false, " " + GetDictionaryEntry( 10006, socket.language ) + "" ); // * The item retains the color of this material
itemGump.AddText( 500, 219, textHue, "*" );
}
@@ -1957,7 +425,7 @@ function onGumpPress( pSock, pButton, gumpData )
return;
var bItem = pSock.tempObj;
- var gumpID = scriptID + 0xffff;
+ var gumpID = itemDetailID + 0xffff;
switch( pButton )
{
case 0:
@@ -2090,7 +558,7 @@ function onGumpPress( pSock, pButton, gumpData )
break;
}
break;
- case 8: // Cartography
+ case 8: // Cartography
pUser.SetTempTag( "ITEMDETAILS", null );
pSock.CloseGump( gumpID, 0 );
switch( pUser.GetTempTag( "page" ))
@@ -2111,9 +579,48 @@ function onGumpPress( pSock, pButton, gumpData )
TriggerEvent( Glassblowing, "PageX", pSock, pUser, pUser.GetTempTag( "page" ));
break;
default: TriggerEvent( Glassblowing, "PageX", pSock, pUser, 1 );
- break;
- }
- break;
+ }
+ break;
+ case 10: // masonry
+ pUser.SetTempTag( "ITEMDETAILS", null )
+ pSock.CloseGump( gumpID, 0 );
+ switch( pUser.GetTempTag("page" ))
+ {
+ case 1: // Page 1
+ case 2: // Page 2
+ case 3: // Page 3
+ case 4: // Page 4
+ case 5: // Page 5
+ case 6: // Page 6
+ case 7: // Page 7
+ case 8: // Page 8
+ case 9: // Page 9
+ TriggerEvent( Masonry, "PageX", pSock, pUser, pUser.GetTempTag( "page" ));
+ break;
+ default: TriggerEvent( Masonry, "PageX", pSock, pUser, 1 );
+ break;
+ }
+ break;
+ case 100: // Custom Craft
+ pUser.SetTempTag( "ITEMDETAILS", null );
+ pSock.CloseGump( gumpID, 0 );
+ TriggerEvent( CustomCraft, "PageX", pSock, pUser, pUser.GetTempTag( "page" ) || 1 );
+ break;
}
}
}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/masonry.js b/data/js/skill/craft/masonry.js
new file mode 100644
index 000000000..a97b9e7f9
--- /dev/null
+++ b/data/js/skill/craft/masonry.js
@@ -0,0 +1,1106 @@
+///
+// @ts-check
+const textHue = 0x480; // Color of the text.
+const masonryID = 4037; // Script ID used to identify and close this gump
+const gumpDelay = 2000; // Timer for the gump to reappear after crafting.
+const ingotDelay = 200; // Timer for the gump to reappear after selecting an ingot.
+const repairDelay = 200; // Timer for the gump to reappear after repairing an item
+const craftGumpID = 4027;
+const itemDetailsScriptID = 4026;
+
+const itemsPerPage = 10; // Number of craftable items shown per gump subpage
+const displayUnlearnedRecipes = true; // Show recipes player has not learned (if we add any later)
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+
+// If enabled, players can craft coloured variants of weapons, though unless the craftItems array
+// is updated with specific create entries for the coloured weapon variants, they will just be
+// regular weapons with granite colour applied
+const allowColouredWeapons = GetServerSetting( "CraftColouredWeapons" );
+const allowColouredBuildings = false; // Set to true if you want coloured stone walls, stairs, floors
+
+const craftMapRegistryID = 4038;
+var MasonryMap = {};
+var craftItems = [];
+
+/** @type { () => boolean } */
+function LoadMasonryMap()
+{
+ MasonryMap = {};
+ craftItems = [];
+
+ var masonryEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "masonry" );
+
+ if( !masonryEntries || !IsMasonryArrayValue( masonryEntries ) )
+ {
+ Console.Warning( "Masonry: Unable to load masonry craft map data." );
+ return false;
+ }
+
+ for( var i = 0; i < masonryEntries.length; i++ )
+ {
+ var entry = masonryEntries[i];
+
+ if( !entry || typeof entry.buttonID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = 11;
+
+ MasonryMap[entry.buttonID] = entry;
+ }
+
+ Console.Print( "Masonry: Loaded " + masonryEntries.length + " craft map entries.\n" );
+ return true;
+}
+
+/** @type { ( value: any ) => boolean } */
+function IsMasonryArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+
+function PageX( socket, pUser, pageNum )
+{
+ if( !ValidateObject( pUser ))
+ return;
+
+ if( !MasonryMap || Object.keys( MasonryMap ).length == 0 )
+ {
+ if( !LoadMasonryMap() )
+ {
+ socket.SysMessage( "Masonry craft map failed to load." );
+ return;
+ }
+ }
+
+ // Pages 1 - 9: normal crafting pages
+ // Page 999: optional "Last Ten Blacksmith" (if you decide to use it later)
+
+ var subPage = pUser.GetTempTag( "subPage" );
+ var pageItems = [];
+
+ if( pageNum == 999 )
+ {
+ var lastTenRaw = pUser.GetTempTag( "LastTenMasonry" ) || "";
+ var split = lastTenRaw.split( "," );
+ for( var i = 0; i < split.length; i++ )
+ {
+ var val = parseInt( split[i] );
+ if( !isNaN( val ) && MasonryMap[val] )
+ pageItems.push( val ); // here val is the buttonID itself
+ }
+ }
+ else
+ {
+ // Build list of buttonIDs for this page from MasonryMap
+ for( var buttonID in MasonryMap )
+ {
+ var data = MasonryMap[buttonID];
+ if( data.page == pageNum && eraOK( data ))
+ {
+ // If we later add recipes and want to hide unknown ones:
+ var needsRecipe = data.recipeID;
+ var showAll = displayUnlearnedRecipes;
+ if( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe ))
+ {
+ pageItems.push( parseInt( buttonID ) );
+ }
+ }
+ }
+
+ // Sort by buttonID to keep consistent ordering
+ pageItems.sort( function( a, b ){ return a - b; } );
+ }
+
+ if( pageItems.length == 0 )
+ {
+ var emptyGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", emptyGump, socket );
+ emptyGump.AddPage( 1 );
+ emptyGump.AddText( 220, 60, textHue, "No items available on this page." );
+ emptyGump.Send( socket );
+ emptyGump.Free();
+ return;
+ }
+
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var resourceHue = pUser.GetTempTag( "resourceHue" );
+ var blacksmithMenu = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", blacksmithMenu, socket );
+ blacksmithMenu.AddPage( 1 );
+
+ var drawIndex = 0;
+
+ for( var i = startIndex; i < endIndex; i++ )
+ {
+ var buttonID = pageItems[i];
+ var data = MasonryMap[buttonID];
+
+ // Do not show weapons when colored granited ingots selected and colored granited weapons are disabled
+ if( !allowColouredWeapons && resourceHue > 0 && data.page == 6 )
+ continue;
+
+ // Do not show walls when colored granited ingots selected and colored granited walls are disabled
+ if( !allowColouredBuildings && resourceHue > 0 && data.page > 6 )
+ continue;
+
+ var entryText = "";
+ if( data.customName )
+ {
+ entryText = data.customName;
+ }
+ else if( data.dictID )
+ {
+ entryText = GetDictionaryEntry( data.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + buttonID + "]";
+ }
+
+ // Same layout as tailoring: button, text, details button
+ blacksmithMenu.AddButton( 220, 60 + ( drawIndex * 20 ), 4005, 4007, 1, 0, buttonID );
+ blacksmithMenu.AddText( 255, 60 + ( drawIndex * 20 ), textHue, entryText );
+ blacksmithMenu.AddButton( 480, 60 + ( drawIndex * 20 ), 4011, 4012, 1, 0, 2000 + buttonID );
+
+ drawIndex++;
+ }
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ blacksmithMenu.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ blacksmithMenu.AddHTMLGump( 255, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" );
+ }
+
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ blacksmithMenu.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ blacksmithMenu.AddHTMLGump( 405, 263, 100, 18, 0, 0,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" );
+ }
+
+ blacksmithMenu.Send( socket );
+ blacksmithMenu.Free();
+}
+
+function Page20( socket, pUser )
+{
+ //granite Choices
+ var myGump = new Gump;
+ pUser.SetTempTag( "page", 20 );
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
+ var iron = pUser.ResourceCount( 0x1779 );
+ var bronze = pUser.ResourceCount( 0x1779, 0x06d6 );
+ var copper = pUser.ResourceCount( 0x1779, 0x07dd );
+ var agapite = pUser.ResourceCount( 0x1779, 0x0979 );
+ var dullcopper = pUser.ResourceCount( 0x1779, 0x0973 );
+ var gold = pUser.ResourceCount( 0x1779, 0x08a5 );
+ var shadowiron = pUser.ResourceCount( 0x1779, 0x0966 );
+ var valorite = pUser.ResourceCount( 0x1779, 0x08ab );
+ var verite = pUser.ResourceCount( 0x1779, 0x089f );
+ var myPage20 = [
+ GetDictionaryEntry( 14011, socket.language ) + " (" + iron.toString() + ")",
+ GetDictionaryEntry( 14012, socket.language ) + " (" + dullcopper.toString() + ")",
+ GetDictionaryEntry( 14013, socket.language ) + " (" + shadowiron.toString() + ")",
+ GetDictionaryEntry( 14014, socket.language ) + " (" + copper.toString() + ")",
+ GetDictionaryEntry( 14015, socket.language ) + " (" + bronze.toString() + ")",
+ GetDictionaryEntry( 14016, socket.language ) + " (" + gold.toString() + ")",
+ GetDictionaryEntry( 14017, socket.language ) + " (" + agapite.toString() + ")",
+ GetDictionaryEntry( 14018, socket.language ) + " (" + verite.toString() + ")",
+ GetDictionaryEntry( 14019, socket.language ) + " (" + valorite.toString() + ")"
+ ];
+
+ for( var i = 0; i < myPage20.length; i++ )
+ {
+ var index = i % 10;
+ if( index == 0 )
+ {
+ if( i > 0 )
+ {
+ myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
+ myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ }
+
+ myGump.AddPage(( i / 10 ) + 1 );
+
+ if( i > 0 )
+ {
+ myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
+ myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ }
+ }
+
+ myGump.AddButton( 220, 60 + ( index * 20), 4005, 4007, 1, 0, 1000 + i );
+ myGump.AddText( 255, 60 + ( index * 20), textHue, myPage20[i] );
+ }
+ myGump.Send( socket );
+ myGump.Free();
+}
+
+function FindNearbyAnvils( pUser, trgItem, pSock )
+{
+ if( !ValidateObject( trgItem ) || !trgItem.isItem )
+ return false;
+
+ return ( trgItem.id == 0x0faf || trgItem.id == 0x0fb0 || trgItem.id == 0x2dd5 || trgItem.id == 0x2dd6 );
+}
+
+function FindNearbyForges( pUser, trgItem, pSock )
+{
+ if( !ValidateObject( trgItem ) || !trgItem.isItem )
+ return false;
+
+ return (( trgItem.id >= 0x197a && trgItem.id <= 0x19a9 ) || trgItem.id == 0x0Fb1 || trgItem.id == 0x2db0 || trgItem.id == 0x2dd8 );
+}
+
+function SmeltTarget( pSock )
+{
+ pSock.CustomTarget( 1, GetDictionaryEntry( 440, pSock.language )); // What item would you like to smelt?
+}
+
+// Armor and weapons can be smelted back into ingots.
+/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
+function onCallback1( pSock, ourObj )
+{
+ // Smelt item, get ingots in return
+ var mChar = pSock.currentChar;
+
+ if( !ValidateObject( ourObj ) || !ourObj.isItem )
+ {
+ // Targeted object is not an item that can be smelted
+ mChar.SetTempTag( "prevActionResult", "CANTSMELT" );
+ mChar.StartTimer( ingotDelay, 1, true );
+ return;
+ }
+
+ var nearbyAnvil = AreaItemFunction( "FindNearbyAnvils", mChar, 3, pSock );
+ var nearbyForge = AreaItemFunction( "FindNearbyForges", mChar, 3, pSock );
+ if( nearbyForge == 0 || nearbyAnvil == 0)
+ {
+ // No forge nearby
+ mChar.SetTempTag( "prevActionResult", "NOFORGEORANVIL" );
+ mChar.StartTimer( ingotDelay, 1, true );
+ return;
+ }
+
+ var creatorSerial = ourObj.creator;
+ var entryMadeFrom = ourObj.entryMadeFrom;
+ var createEntry;
+ if( entryMadeFrom != null && entryMadeFrom != 0 )
+ {
+ createEntry = CreateEntries[entryMadeFrom];
+ }
+ if( createEntry != null && createEntry.id != ourObj.id )
+ {
+ createEntry = null;
+ }
+
+ var resourceName = "granite";
+ var resourceAmount = 0;
+ var maxResourceAmount = 1;
+ var resourceHue = ourObj.colour;
+
+ if( creatorSerial == -1 || entryMadeFrom == 0 || createEntry == null )
+ {
+ // Not a player-crafted item, return 1 ingot if item is made of metal
+ var materialType = TriggerEvent( 2506, "GetItemMaterialType", ourObj );
+ if( materialType == "metal" )
+ {
+ resourceAmount = 1;
+ }
+ }
+ else
+ {
+ if( createEntry.avgMinSkill > mChar.skills.mining )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "NOSMELTSKILL" );
+ mChar.StartTimer( gumpDelay, 1, true );
+ return;
+ }
+
+ // Loop through resources used to craft item, see how many ingots were used
+ var resourcesUsed = createEntry.resources;
+ for( var i = 0; i < resourcesUsed.length; i++ )
+ {
+ var resource = resourcesUsed[i];
+ var amountNeeded = resource[0];
+ var colorNeeded = resource[1];
+ var resourceIDs = resource[2];
+
+ // Loop through list of resourceIDs that were valid for crafting this item, see if ANY
+ // were a match for the resource we're trying to return
+ for( var j = 0; j <= resourceIDs.length; j++ )
+ {
+ // If both resource needed matches up, and resource color matches up, go for it
+ if( resourceIDs[j] == 0x1bf2 && colorNeeded == resourceHue )
+ {
+ maxResourceAmount = amountNeeded;
+ break;
+ }
+ }
+ }
+
+ if( maxResourceAmount > 1 )
+ {
+ // Calculate amount of resources returned based on player's mining skill, item's wear and tear,
+ // and amount of resources that went into making the item in the first place
+ if ( ourObj.health >= 1 || ourObj.usesLeft >= 1 )
+ {
+ var healthPercentage = 0;
+ if( ourObj.health >= 1 )
+ {
+ healthPercentage = Math.floor( ( ourObj.health * 100) / ourObj.maxhp );
+ }
+
+ var usesPercentage = 0;
+ if( ourObj.usesLeft >= 1 )
+ {
+ usesPercentage = Math.floor( ( ourObj.usesLeft * 100 ) / ourObj.maxUses );
+ }
+
+ var itemPercentage = usesPercentage > 0 ? Math.min( healthPercentage, usesPercentage ) : healthPercentage;
+
+ resourceAmount = Math.floor( ( maxResourceAmount * itemPercentage ) / 100 );
+ }
+
+ // Halve the amount of resources returned
+ resourceAmount = Math.max( Math.floor( resourceAmount / 2 ), 1 );
+
+ // Fetch player's Mining skill
+ var playerSkill = mChar.skills.mining;
+
+ // Based on player's Mining skill, return between 1 to maxResourceAmount
+ resourceAmount = Math.min( Math.max( Math.floor( resourceAmount * ( playerSkill / 1000 )), 1 ), resourceAmount );
+ }
+ else
+ {
+ resourceAmount = 1;
+ }
+ }
+
+ if( resourceAmount == 0 )
+ {
+ // Targeted object is not an item that can be smelted
+ mChar.SetTempTag( "prevActionResult", "CANTSMELT" );
+ mChar.StartTimer( ingotDelay, 1, true );
+ return;
+ }
+
+ if( ourObj.isDyeable )
+ {
+ // Dyeable items should return regular iron ingots
+ resourceHue = 0;
+ }
+
+ switch( resourceHue )
+ {
+ case 0: // Iron granite
+ default:
+ break;
+ case 0x0973: // Dull Copper
+ resourceName = "dull copper granite";
+ break;
+ case 0x0966: // Shadow Iron
+ resourceName = "shadow iron granite";
+ break;
+ case 0x07dd: // Copper
+ resourceName = "copper granite";
+ break;
+ case 0x06d6: // Bronze
+ resourceName = "bronze granite";
+ break;
+ case 0x08a5: // Gold
+ resourceName = "gold granite";
+ break;
+ case 0x0979: // Agapite
+ resourceName = "agapite granite";
+ break;
+ case 0x089f: // Verite
+ resourceName = "verite granite";
+ break;
+ case 0x08ab: // Valorite
+ resourceName = "valorite granite";
+ break;
+ }
+
+ // Delete the melted item
+ ourObj.Delete();
+
+ // Run a generic skill check to give player a chance to increase their mining skill
+ mChar.CheckSkill( 45, 0, mChar.skillCaps.mining );
+
+ var newResource = CreateDFNItem( pSock, mChar, "0x1779", resourceAmount, "ITEM", true, resourceHue );
+ newResource.name = resourceName;
+
+ mChar.SetTempTag( "ingotsFromSmelting", resourceAmount );
+ mChar.SetTempTag( "prevActionResult", "SMELTITEMSUCCESS" );
+ mChar.StartTimer( gumpDelay, 1, true );
+}
+
+function RepairTarget( pSock )
+{
+ pSock.CustomTarget( 2, GetDictionaryEntry( 485, pSock.language )); // What item would you like to repair?
+}
+
+/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
+function onCallback2( pSock, ourObj )
+{
+ // Repair Item
+ var mChar = pSock.currentChar;
+
+ // Don't continue if character is invalid, or worse... dead!
+ if( !ValidateObject( mChar ) || mChar.dead )
+ return;
+
+ var bItem = pSock.tempObj;
+ var anvil = AreaItemFunction( "FindNearbyAnvils", mChar, 3, pSock );
+ var gumpID = masonryID + 0xffff;
+ pSock.tempObj = null;
+
+ if( ValidateObject( mChar ) && mChar.isChar && ValidateObject( bItem ) && bItem.isItem )
+ {
+ if( !ValidateObject( ourObj ) || !ourObj.isItem
+ || TriggerEvent( 2506, "GetItemMaterialType", ourObj ) != "metal"
+ || !CheckTileFlag( ourObj.id, 22 )) // TF_WEARABLE
+ {
+ // Targeted object is not an item that can be repaired
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "CANTREPAIR" );
+ mChar.StartTimer( repairDelay, 1, true );
+ return;
+ }
+
+ if( anvil == 0 )
+ {
+ // No anvil nearby
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "NOANVIL" );
+ mChar.StartTimer( repairDelay, 1, true );
+ return;
+ }
+
+ var itemDurabilityLossEnabled = GetServerSetting( "ItemRepairDurabilityLoss" );
+ var repairID = ourObj.id;
+ var ownerObj = GetPackOwner( ourObj, 0 );
+ if( ownerObj && mChar.serial == ownerObj.serial )
+ {
+ var maxHitpoints = ourObj.maxhp;
+ var currentHitpoints = ourObj.health;
+ if( currentHitpoints < maxHitpoints )
+ {
+ // Get base repair difficulty based on amount of HP missing and max hitpoints
+ var deltaHP = maxHitpoints - currentHitpoints;
+ var repairDifficulty = (( deltaHP / maxHitpoints ) * 1000 );
+ var minDifficulty = repairDifficulty - 250;
+ var skillBonus = 0;
+ var repairSkill = mChar.skills.blacksmithing;
+ if( minDifficulty < 0 )
+ {
+ // If minDifficulty is negative, add the negative value as a bonus to player's skill
+ skillBonus = minDifficulty * -1;
+ minDifficulty = 0;
+ }
+ else if( minDifficulty > repairSkill )
+ {
+ // Player skill below minimum repair difficulty, Too difficult to make the attempt!
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "CANTREPAIR" );
+ mChar.StartTimer( repairDelay, 1, true );
+ return;
+ }
+ var maxDifficulty = Math.min( repairDifficulty + 250, mChar.skillCaps.blacksmithing );
+
+ // Allow repair if random number between min and base difficulty is under player's skill
+ if( RandomNumber( minDifficulty, 1000 ) < ( Math.max( repairSkill + skillBonus, 999 )))
+ {
+ // Give player a chance every now and then to gain skill from repairing
+ if( RandomNumber( 1, 5 ) == 1 )
+ {
+ // Run a skill-check, which might trigger a skill-gain if player passes
+ mChar.CheckSkill( 8, minDifficulty, maxDifficulty ); // Skill 8 = blacksmithing
+ }
+
+ // Reduce object's max durability by 1
+ if( itemDurabilityLossEnabled )
+ {
+ ourObj.maxhp -= 1;
+ }
+
+ // Repair item here
+ ourObj.health = ourObj.maxhp;
+ pSock.SoundEffect( 0x002A, true );
+
+ // Reopen gump after a short delay
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "REPAIRSUCCESS" );
+ mChar.StartTimer( repairDelay, 1, true );
+
+ // GM skill (100.0 skillpoints)
+ // Item with 51 HP max
+ // item with 2 hp left - 99.65% chance to repair
+ // item with 25 hp left - 99.86% chance to repair
+ // item with 40 hp left - 99.9% chance to repair
+
+ // Expert Smith (71.5 skill points)
+ // Item with 51 HP max
+ // item with 2 hp left - 1.45% chance to repair
+ // item with 25 hp left - 61.49% chance to repair
+ // item with 40 hp left - 74.9% chance to repair
+ // item with 48 hp left - 90.6% chance to repair
+
+ // Apprentice Smith (51.5 skill points)
+ // Item with 51 HP max
+ // item with 2 hp left - 0% chance to repair
+ // item with 25 hp left - 34.5% chance to repair
+ // item with 40 hp left - 54.9% chance to repair
+ // item with 48 hp left - 70.6% chance to repair
+ }
+ else
+ {
+ // Failed to repair item - decrease item health!
+ if( repairSkill >= 1000 ) // GM Smith
+ {
+ ourObj.health -= 1;
+ }
+ else if( repairSkill >= 715 ) // Expert Smith
+ {
+ ourObj.health -= 2;
+ }
+ else // Below Expert Smith
+ {
+ ourObj.health -= 3;
+ }
+
+ if( ourObj.health <= 0 )
+ {
+ // Item has been destroyed!
+ pSock.SysMessage( GetDictionaryEntry( 311, pSock.language ).replace(/%s/gi, ourObj.name )); // Your %s has been destroyed.
+ ourObj.Delete();
+ }
+
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "FAILREPAIR" );
+ mChar.StartTimer( repairDelay, 1, true );
+ }
+ }
+ else
+ {
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "FULLREPAIR" );
+ mChar.StartTimer( repairDelay, 1, true );
+ }
+ }
+ else
+ {
+ pSock.tempObj = bItem;
+ pSock.CloseGump( gumpID, 0 );
+ mChar.SetTempTag( "prevActionResult", "CHECKPACK" );
+ mChar.StartTimer( repairDelay, 1, true );
+ }
+ }
+}
+
+/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+function onTimer( pUser, timerID )
+{
+ if( !ValidateObject( pUser ))
+ return;
+
+ var socket = pUser.socket;
+
+ if( timerID >= 1 && timerID <= 9 )
+ {
+ PageX( socket, pUser, timerID ); // Pages 1 - 9
+ }
+ else if( timerID == 20 )
+ {
+ Page20( socket, pUser ); // Ingot selection
+ }
+ else if( timerID == 999 )
+ {
+ PageX( socket, pUser, 999 ); // Last Ten Blacksmith (if used)
+ }
+}
+
+
+/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
+function onGumpPress( pSock, pButton, gumpData )
+{
+ var pUser = pSock.currentChar;
+ var usedMakeLast = false;
+
+ if( !ValidateObject( pUser ) || pUser.dead )
+ return;
+
+ var bItem = pSock.tempObj;
+ if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ {
+ pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ return;
+ }
+
+ if( bItem.movable == 3 )
+ {
+ pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
+ return;
+ }
+
+ var iPackOwner = GetPackOwner( bItem, 0 );
+ if( ValidateObject( iPackOwner ))
+ {
+ if( iPackOwner.serial != pUser.serial )
+ {
+ pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
+ return;
+ }
+ }
+ else
+ {
+ pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack befgranite you can use it.
+ return;
+ }
+
+ var gumpID = masonryID + 0xffff;
+
+ // Close / Exit
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "prevActionResult", null );
+ pUser.SetTempTag( "MAKELAST", null );
+ pSock.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Repair Item
+ if( pButton == 49 )
+ {
+ RepairTarget( pSock );
+ return;
+ }
+
+ // Select Materials (ingots)
+ if( pButton == 50 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ Page20( pSock, pUser );
+ return;
+ }
+
+ // Smelt Item
+ if( pButton == 52 )
+ {
+ SmeltTarget( pSock );
+ return;
+ }
+
+ // Page buttons (1..9)
+ if( pButton >= 1 && pButton <= 9 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ pSock.CloseGump( gumpID, 0 );
+ PageX( pSock, pUser, pButton );
+ return;
+ }
+
+ // Last Ten page (if you wire it into the gump)
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( pSock, pUser, 999 );
+ return;
+ }
+
+ // Subpage navigation (8000 = prev, 9000 = next)
+ if( pButton >= 8000 && pButton < 9000 )
+ {
+ var prevSub = pButton - 8000;
+ var curPage = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", prevSub );
+ PageX( pSock, pUser, curPage );
+ return;
+ }
+
+ if( pButton >= 9000 && pButton < 10000 )
+ {
+ var nextSub = pButton - 9000;
+ var curPage2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", nextSub );
+ PageX( pSock, pUser, curPage2 );
+ return;
+ }
+
+ // Handle "Make Last"
+ if(( pButton >= 100 && pButton <= 998 ) || pButton == 5000 )
+ {
+ if( pButton == 5000 )
+ {
+ pButton = pUser.GetTempTag( "MAKELAST" );
+ usedMakeLast = true;
+ }
+ else
+ {
+ pUser.SetTempTag( "MAKELAST", pButton );
+ }
+ }
+
+ // Item detail buttons (2000 + buttonID)
+ if( pButton >= 2000 && pButton < 3000 )
+ {
+ var detailButtonID = pButton - 2000;
+ var entry = MasonryMap[detailButtonID];
+ if( entry )
+ {
+ // For details we just pass the granite version (granite index 0) to 4026
+ var graniteMakeID = entry.graniteMake[0];
+ if( graniteMakeID > 0 )
+ {
+ // Masonry uses Carpentry skill
+ pUser.SetTempTag( "Skill", entry.skill | 0 );
+
+ // Clear old harvests
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // OPTIONAL custom names – these override the dictionary string
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ pUser.SetTempTag( "ITEMDETAILS", graniteMakeID );
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ }
+ return;
+ }
+
+ // If this is a craft button in our map:
+ if( MasonryMap[pButton] != undefined )
+ {
+ var entry2 = MasonryMap[pButton];
+ var graniteID = pUser.GetTempTag( "Granite" );
+ var resourceHue = pUser.GetTempTag( "resourceHue" );
+
+ // Ensure graniteID within range
+ if( graniteID < 0 || graniteID >= entry2.graniteMake.length )
+ graniteID = 0;
+
+ // Era / recipe gating
+ if( !eraOK( entry2 ))
+ {
+ pSock.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( entry2.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, entry2.recipeID ))
+ {
+ pSock.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ // No colored granited weapons if disabled and using non-iron ingots
+ if( !allowColouredWeapons && resourceHue > 0 && entry2.page > 3 )
+ {
+ pSock.SysMessage( "You cannot use colored granited ingots for weapons on this shard." );
+ return;
+ }
+
+ var makeID = entry2.graniteMake[graniteID];
+ if( !makeID || makeID == 0 )
+ {
+ // Fallback to iron version if for some reason we did not get a specific granite entry
+ makeID = entry2.graniteMake[0];
+ }
+
+ if( !makeID || makeID == 0 )
+ {
+ pSock.SysMessage( "That item is not properly configured." );
+ return;
+ }
+
+ // Runic hammer bonus logic (unchanged from your original)
+ pUser.AddScriptTrigger( 4033 );
+
+ MakeItem( pSock, pUser, makeID, resourceHue );
+
+ // Tool wear
+ var toolUseLimit = GetServerSetting( "ToolUseLimit" );
+ var toolUseBreak = GetServerSetting( "ToolUseBreak" );
+
+ var runicHammer = pUser.FindItemLayer( 0x01 ); // Right Hand
+ if( ValidateObject( runicHammer ) && runicHammer.GetTag( "runicHammer" ) && runicHammer.usesLeft > 0 )
+ {
+ pUser.SetTempTag( "usedRunicHammer", true );
+ pUser.SetTempTag( "runicHammerType", runicHammer.color );
+
+ if( toolUseLimit && runicHammer != bItem )
+ {
+ runicHammer.usesLeft -= 1;
+ if( runicHammer.usesLeft == 0 && toolUseBreak )
+ {
+ runicHammer.Delete();
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language ));
+ }
+ }
+ }
+
+ if( toolUseLimit )
+ {
+ bItem.usesLeft -= 1;
+ if( bItem.usesLeft == 0 && toolUseBreak )
+ {
+ bItem.Delete();
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language ));
+ }
+ }
+
+ // Track in last ten list for blacksmith
+ AddToLastTenmMasonry( pUser, pButton );
+
+ // Reopen page after delay
+ pUser.StartTimer( gumpDelay, entry2.timerID, true );
+ return;
+ }
+
+ // Granite selection buttons (Page20)
+ if( pButton >= 1000 && pButton <= 1008 )
+ {
+ var index = pButton - 1000; // 0..8
+ var newGraniteID = index;
+ var newResourceHue = 0;
+
+ // Optional: use Mining skill gating like ingots
+ var miningSkill = pUser.skills.mining | 0;
+
+ switch( index )
+ {
+ case 0: // Iron
+ newResourceHue = 0;
+ break;
+
+ case 1: // Dull Copper
+ if( miningSkill < 650 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x0973;
+ break;
+
+ case 2: // Shadow Iron
+ if( miningSkill < 700 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x0966;
+ break;
+
+ case 3: // Copper
+ if( miningSkill < 750 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x07dd;
+ break;
+
+ case 4: // Bronze
+ if( miningSkill < 800 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x06d6;
+ break;
+
+ case 5: // Gold
+ if( miningSkill < 850 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x08a5;
+ break;
+
+ case 6: // Agapite
+ if( miningSkill < 900 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x0979;
+ break;
+
+ case 7: // Verite
+ if( miningSkill < 950 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x089f;
+ break;
+
+ case 8: // Valorite
+ if( miningSkill < 990 )
+ {
+ pSock.CloseGump( gumpID, 0 );
+ pUser.StartTimer( ingotDelay, 20, true );
+ pUser.SetTempTag( "prevActionResult", "FAILED" );
+ return;
+ }
+ newResourceHue = 0x08ab;
+ break;
+ }
+
+ // Store selection
+ pUser.SetTempTag( "Granite", newGraniteID );
+ pUser.SetTempTag( "resourceHue", newResourceHue );
+ pUser.SetTempTag( "prevActionResult", null );
+ pUser.SetTempTag( "MAKELAST", null );
+
+ // Close the material select gump
+ pSock.CloseGump( gumpID, 0 );
+
+ // Go back to the last craft page, or default to page 1
+ var curPage = pUser.GetTempTag( "page" );
+ if( !curPage || curPage == 20 )
+ curPage = 1;
+
+ pUser.SetTempTag( "page", curPage );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( pSock, pUser, curPage );
+ return;
+ }
+}
+
+function AddToLastTenmMasonry( pUser, buttonID )
+{
+ var raw = pUser.GetTempTag( "LastTenMasonry" ) || "";
+ var list = raw.split( "," );
+
+ // Remove if already in list
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == buttonID )
+ {
+ list.splice( i, 1 );
+ break;
+ }
+ }
+
+ var newList = [buttonID];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenMasonry", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ // Optional per-entry gates. Strings like "lbr","aos","ml","sa","hs","tol".
+ // If not present, the entry is valid for all eras.
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/recipe_data.js b/data/js/skill/craft/recipe_data.js
index 2cb20aeb2..fde1c2d01 100644
--- a/data/js/skill/craft/recipe_data.js
+++ b/data/js/skill/craft/recipe_data.js
@@ -6,7 +6,7 @@ function AddRecipe( pUser, iUsed, recipeID, recipeSectionID )
// Read Recipe
var myData = ReadRecipeID( pUser );
- for( let i = 0; i < myData.length; i++ )
+ for( var i = 0; i < myData.length; i++ )
{
var myRecipeData = myData[i].split(",");
@@ -35,7 +35,7 @@ function NeedRecipe( pSock, recipeID )
if( myData && myData.length > 0 )
{
- for( let i = 0; i < myData.length; i++ )
+ for( var i = 0; i < myData.length; i++ )
{
var myRecipeData = myData[i].split(",");
diff --git a/data/js/skill/craft/tailoring.js b/data/js/skill/craft/tailoring.js
index 66cf7874f..f29dcf09f 100644
--- a/data/js/skill/craft/tailoring.js
+++ b/data/js/skill/craft/tailoring.js
@@ -1,194 +1,178 @@
///
// @ts-check
-const textHue = 0x480; // Color hue for all text in the crafting gump
-const scriptID = 4030; // Script ID used to identify and close this gump
-const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
-const repairDelay = 200; // Delay (ms) before gump reappears after selecting a resource
-const itemDetailsScriptID = 4026; // Script ID used to show item detail tooltips
-const craftGumpID = 4027; // TriggerEvent ID used to build the crafting gump UI
-const itemsPerPage = 10; // Number of craftable items shown per gump subpage
-const displayUnlearnedRecipes = true; // Show recipes player hasn't learned
-const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
-//////////////////////////////////////////////////////////////////////////////////////////
-// UOX3 Tailoring CraftingMap Configuration
-//
-// Description:
-// This file defines the `CraftingMap` used to populate the tailoring crafting gump in UOX3.
-// Each entry links a craftable item (defined in `create/tailoring.dfn`) to its display name,
-// gump page, and crafting behavior. This setup powers the dynamic UI for tailoring.
-//
-// Data Sources:
-// - `makeID` refers to the unique item ID defined in `create/tailoring.dfn`.
-// - `dictID` refers to a localized string entry defined in `dictionaries/dictionary.eng`.
-// - `customName` is an optional hardcoded string that overrides dictionary text if present.
-// - `recipeID` is an optional ID that locks the item behind a learned recipe.
-//
-// Entry Format:
-// makeID: {
-// dictID: , // (Optional) Used for localized names via dictionary.eng
-// customName: "- ", // (Optional) Overrides dictionary name if present
-// page:
, // Gump page to display item on
-// timerID: , // Timer used to re-show gump after crafting
-// recipeID: // (Optional) Requires the player to learn the recipe before crafting
-// }
-//
-// Display Logic:
-// - If `customName` is present, it is used directly in the crafting gump.
-// - If `dictID` is present (and `customName` is not), `GetDictionaryEntry()` is used.
-// - If both are missing, the gump will show a fallback like `[Unnamed Item: ####]`.
-// - The `page` field determines which category tab the item appears under.
-// - If `recipeID` is present and the player has not learned it, the item will either be:
-// - Hidden entirely (default behavior), or
-// - Displayed but uncraftable, depending on `displayUnlearnedRecipes` setting.
-//
-// Example Entries:
-//
-// // Standard item using dictionary ID for localization
-// 130: { dictID: 11415, page: 1, timerID: 1 },
-//
-// // Custom item with hardcoded display name, no dictionary entry required
-// 999: { customName: "harrys sword", page: 1, timerID: 1 },
-//
-// // Recipe-locked item (only craftable if recipe is learned)
-// 185: { dictID: 11469, page: 8, timerID: 8, recipeID: 185 },
-//
-// // Era Gating
-// 130: { dictID: 11415, page: 1, timerID: 1, minEra: "uo" }, // from UO and up
-// 185: { dictID: 11469, page: 8, timerID: 8, recipeID: 185, minEra: "aos" }, // AoS+
-// 169: { dictID: 11454, page: 6, timerID: 6, maxEra: "lbr" }, // up to LBR
-//
-// Organization:
-// - Items are grouped by `page` value (e.g., Hats, Armor, etc.).
-// - The crafting gump dynamically builds its layout from this CraftingMap.
-// - This system supports localized, custom, hybrid, and recipe-locked crafting menus.
-//
-//////////////////////////////////////////////////////////////////////////////////////////
-
-const CraftingMap = {
- // Hats (Page 1)
- 130: { dictID: 11415, page: 1, timerID: 1 },
- 131: { dictID: 11416, page: 1, timerID: 1 },
- 132: { dictID: 11417, page: 1, timerID: 1 },
- 134: { dictID: 11418, page: 1, timerID: 1 },
- 133: { dictID: 11419, page: 1, timerID: 1 },
- 136: { dictID: 11420, page: 1, timerID: 1 },
- 137: { dictID: 11421, page: 1, timerID: 1 },
- 138: { dictID: 11422, page: 1, timerID: 1 },
- 139: { dictID: 11423, page: 1, timerID: 1 },
- 140: { dictID: 11424, page: 1, timerID: 1 },
- 141: { dictID: 11425, page: 1, timerID: 1 },
- 135: { dictID: 11470, page: 1, timerID: 1 },
-
- // Shirts & Pants (Page 2)
- 142: { dictID: 11426, page: 2, timerID: 2 },
- 143: { dictID: 11427, page: 2, timerID: 2 },
- 144: { dictID: 11428, page: 2, timerID: 2 },
- 145: { dictID: 11429, page: 2, timerID: 2 },
- 146: { dictID: 11430, page: 2, timerID: 2 },
- 147: { dictID: 11431, page: 2, timerID: 2 },
- 148: { dictID: 11432, page: 2, timerID: 2 },
- 149: { dictID: 11433, page: 2, timerID: 2 },
- 150: { dictID: 11434, page: 2, timerID: 2 },
- 151: { dictID: 11435, page: 2, timerID: 2 },
- 180: { dictID: 11436, page: 2, timerID: 2 },
- 152: { dictID: 11437, page: 2, timerID: 2 },
- 153: { dictID: 11438, page: 2, timerID: 2 },
- 154: { dictID: 11439, page: 2, timerID: 2 },
-
- // Misc (Page 3)
- 155: { dictID: 11440, page: 3, timerID: 3 },
- 156: { dictID: 11441, page: 3, timerID: 3 },
- 157: { dictID: 11442, page: 3, timerID: 3 },
- 158: { dictID: 11443, page: 3, timerID: 3 },
-
- // Footwear (Page 4)
- 159: { dictID: 11444, page: 4, timerID: 4 },
- 160: { dictID: 11445, page: 4, timerID: 4 },
- 161: { dictID: 11446, page: 4, timerID: 4 },
- 162: { dictID: 11447, page: 4, timerID: 4 },
-
- // Leather Armor (Page 5)
- 163: { dictID: 11448, page: 5, timerID: 5 },
- 164: { dictID: 11449, page: 5, timerID: 5 },
- 165: { dictID: 11450, page: 5, timerID: 5 },
- 166: { dictID: 11451, page: 5, timerID: 5 },
- 167: { dictID: 11452, page: 5, timerID: 5 },
- 168: { dictID: 11453, page: 5, timerID: 5 },
-
- // Studded Armor (Page 6)
- 169: { dictID: 11454, page: 6, timerID: 6 },
- 170: { dictID: 11455, page: 6, timerID: 6 },
- 171: { dictID: 11456, page: 6, timerID: 6 },
- 172: { dictID: 11457, page: 6, timerID: 6 },
- 173: { dictID: 11458, page: 6, timerID: 6 },
-
- // Female Armor (Page 7)
- 174: { dictID: 11459, page: 7, timerID: 7 },
- 175: { dictID: 11460, page: 7, timerID: 7 },
- 176: { dictID: 11461, page: 7, timerID: 7 },
- 177: { dictID: 11462, page: 7 , timerID: 7},
- 178: { dictID: 11463, page: 7, timerID: 7 },
- 179: { dictID: 11464, page: 7, timerID: 7 },
-
- // Bone Armor (Page 8)
- 181: { dictID: 11465, page: 8, timerID: 8 },
- 182: { dictID: 11466, page: 8, timerID: 8 },
- 183: { dictID: 11467, page: 8, timerID: 8 },
- 184: { dictID: 11468, page: 8, timerID: 8 },
- 185: { dictID: 11469, page: 8, timerID: 8 }
-};
-
-function PageX( socket, pUser, pageNum )
+const textHue = 0x480; // Color hue for all text in the crafting gump
+const tailoringID = 4030; // Script ID used to identify and close this gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const repairDelay = 200; // Delay (ms) before gump reappears after selecting a resource
+const itemDetailsScriptID = 4026; // Script ID used to show item detail tooltips
+const craftGumpID = 4027; // TriggerEvent ID used to build the crafting gump UI
+const itemsPerPage = 10; // Number of craftable items shown per gump subpage
+const displayUnlearnedRecipes = true; // Show recipes player hasn't learned
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const tailoringSkillID = 34; // Tailoring skill index
+
+const craftMapRegistryID = 4038;
+var CraftingMap = {};
+
+/** @type { () => boolean } */
+function LoadTailoringMap()
{
- let subPage = pUser.GetTempTag( "subPage" ) || 1;
+ CraftingMap = {};
+
+ var tailoringEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "tailoring" );
- // Build pages dynamically from CraftingMap
- let myPage = [];
- let dictToMakeID = {}; // local reverse map
+ if( !tailoringEntries || !IsTailoringArrayValue( tailoringEntries ) )
+ {
+ Console.Warning( "Tailoring: Unable to load tailoring craft map data." );
+ return false;
+ }
- for( let makeID in CraftingMap )
+ for( var i = 0; i < tailoringEntries.length; i++ )
{
- let data = CraftingMap[makeID];
- let page = data.page;
- if( !myPage[page - 1] )
- myPage[page - 1] = [];
+ var entry = tailoringEntries[i];
+
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
+
+ if( entry.skill === undefined )
+ entry.skill = tailoringSkillID;
+
+ CraftingMap[entry.makeID] = entry;
+ }
+
+ return true;
+}
- let needsRecipe = data.recipeID;
- let showAll = displayUnlearnedRecipes;
+/** @type { ( value: any ) => boolean } */
+function IsTailoringArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
- if( eraOK( data ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )))
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
+function PageX( socket, pUser, pageNum )
+{
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ if( !CraftingMap || Object.keys( CraftingMap ).length == 0 )
+ {
+ if( !LoadTailoringMap() )
{
- myPage[page - 1].push( data.dictID );
+ socket.SysMessage( "Tailoring craft map failed to load." );
+ return;
}
-
- dictToMakeID[data.dictID] = parseInt( makeID );
}
- let pageItems;
+ var subPage = pUser.GetTempTag( "subPage" ) || 1;
+ var pageItems;
+ // Last Ten page
if( pageNum == 999 )
{
- let lastTenRaw = pUser.GetTag( "LastTenTailoring" ) || "";
- let split = lastTenRaw.split( "," );
+ var lastTenRaw = pUser.GetTempTag( "LastTenTailoring" ) || "";
+ var split = lastTenRaw.split( "," );
pageItems = [];
for( var i = 0; i < split.length; i++ )
{
- let val = parseInt(split[i]);
+ var val = parseInt( split[i] );
if( !isNaN( val ))
- pageItems.push( val );
+ pageItems.push( val ); // makeID
}
}
else
{
- if( pageNum < 1 || pageNum > myPage.length )
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in CraftingMap )
+ {
+ if( !CraftingMap.hasOwnProperty( key ))
+ continue;
+
+ var makeID = parseInt( key );
+ var data = CraftingMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
+ {
+ var ea = CraftingMap[a];
+ var eb = CraftingMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = CraftingMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )) )
+ pageItems.push( id );
+ }
+
+ // Fallback if page empty (and not page 1)
+ if( pageItems.length == 0 && pageNum != 1 )
+ {
pageNum = 1;
- pageItems = myPage[pageNum - 1];
- }
+ makeIDs = [];
+ for( var key2 in CraftingMap )
+ {
+ if( !CraftingMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = CraftingMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
+ }
+
+ makeIDs.sort( function( a, b )
+ {
+ var ea2 = CraftingMap[a];
+ var eb2 = CraftingMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = CraftingMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
- let totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )) )
+ pageItems.push( id2 );
+ }
+ }
+ }
+ // Subpage handling
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
if( subPage < 1 )
subPage = 1;
if( subPage > totalSubPages )
@@ -197,108 +181,104 @@ function PageX( socket, pUser, pageNum )
pUser.SetTempTag( "page", pageNum );
pUser.SetTempTag( "subPage", subPage );
- let startIndex = ( subPage - 1 ) * itemsPerPage;
- let endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
- if( startIndex >= pageItems.length )
+ if( startIndex >= pageItems.length )
{
- subPage = 1;
+ subPage = 1;
startIndex = 0;
- endIndex = Math.min( itemsPerPage, pageItems.length );
+ endIndex = Math.min( itemsPerPage, pageItems.length );
pUser.SetTempTag( "subPage", subPage );
}
- let tailoringMenu = new Gump;
+ var tailoringMenu = new Gump;
TriggerEvent( craftGumpID, "CraftingGumpMenu", tailoringMenu, socket );
tailoringMenu.AddPage( 1 );
- for( let i = startIndex; i < endIndex; i++)
+ for( var j = startIndex; j < endIndex; j++ )
{
- let index = i - startIndex;
- let makeID, entryID, entryText, buttonID;
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // craft button uses makeID directly
+ var data5 = CraftingMap[makeID];
- if( pageNum == 999 )
+ if( !data5 )
{
- makeID = pageItems[i];
+ entryText = "[Missing MakeID: " + makeID + "]";
}
else
{
- entryID = pageItems[i];
- makeID = dictToMakeID[entryID];
+ if( data5.customName )
+ {
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
+ {
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
+ {
+ entryText = "[Unnamed Item: " + makeID + "]";
+ }
}
- let data = CraftingMap[makeID];
-
- if( !data )
- {
- entryText = "[Missing MakeID: " + makeID + "]";
- buttonID = makeID;
- }
- else
- {
- buttonID = makeID;
-
- if( data.customName )
- {
- entryText = data.customName;
- }
- else if( data.dictID )
- {
- entryText = GetDictionaryEntry( data.dictID, socket.language );
- if( !entryText || entryText === "" )
- entryText = "[Missing EntryID: " + data.dictID + "]";
- }
- else
- {
- entryText = "[Unnamed Item: " + makeID + "]";
- }
+ tailoringMenu.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ tailoringMenu.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
+ // Detail button: 20000 + makeID (new system pattern)
+ tailoringMenu.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
}
- tailoringMenu.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
- tailoringMenu.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
- tailoringMenu.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + buttonID );
-}
-
+ // Prev subpage
if( subPage > 1 )
{
tailoringMenu.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
- tailoringMenu.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry(10101, socket.language ) + "" );
+ tailoringMenu.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" );
}
+ // Next subpage
if( subPage < totalSubPages )
{
tailoringMenu.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
- tailoringMenu.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry(10100, socket.language ) + "" );
+ tailoringMenu.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" );
}
tailoringMenu.Send( socket );
tailoringMenu.Free();
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+/** @type { ( pUser: Character, timerID: number ) => void } */
function onTimer( pUser, timerID )
{
if( !ValidateObject( pUser ))
return;
- let socket = pUser.socket;
+ var socket = pUser.socket;
+ if( socket == null )
+ return;
if( timerID >= 1 && timerID <= 8 )
{
- PageX( socket, pUser, timerID ); // Pages 1 - 8
+ PageX( socket, pUser, timerID ); // Pages 1–8
}
- else if ( timerID == 999 )
+ else if( timerID == 999 )
{
- PageX(socket, pUser, 999); // Last Ten
+ PageX( socket, pUser, 999 ); // Last Ten
}
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
function onGumpPress( socket, pButton, gumpData )
{
- var pUser = socket.currentChar;
- var usedMakeLast = false;
+ if( socket == null )
+ return;
+ var pUser = socket.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
@@ -315,39 +295,43 @@ function onGumpPress( socket, pButton, gumpData )
return;
}
- var iPackOwner = GetPackOwner(bItem, 0);
+ var iPackOwner = GetPackOwner( bItem, 0 );
if( ValidateObject( iPackOwner ))
{
if( iPackOwner.serial != pUser.serial )
{
- socket.SysMessage(GetDictionaryEntry( 6032, socket.language ));
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language ));
return;
}
}
else
{
- socket.SysMessage(GetDictionaryEntry( 6022, socket.language ));
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language ));
return;
}
+ var gumpID = tailoringID + 0xffff;
+
+ // Subpage back/forward
if( pButton >= 8001 && pButton < 9000 )
{
- let subPage = pButton - 8000;
- let pageNum = pUser.GetTempTag( "page" ) || 1;
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" ) || 1;
pUser.SetTempTag( "subPage", subPage );
- PageX(socket, pUser, pageNum);
+ PageX( socket, pUser, pageNum );
return;
}
if( pButton >= 9001 && pButton < 10000 )
{
- let subPage = pButton - 9000;
- let pageNum = pUser.GetTempTag( "page" ) || 1;
- pUser.SetTempTag("subPage", subPage);
- PageX( socket, pUser, pageNum );
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" ) || 1;
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
return;
}
+ // Page tabs (1–8)
if( pButton >= 1 && pButton <= 8 )
{
pUser.SetTempTag( "page", pButton );
@@ -356,83 +340,95 @@ function onGumpPress( socket, pButton, gumpData )
return;
}
+ // Last Ten
if( pButton == 11000 )
{
pUser.SetTempTag( "page", 999 );
pUser.SetTempTag( "subPage", 1 );
- PageX(socket, pUser, 999);
+ PageX( socket, pUser, 999 );
return;
}
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Tailoring", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Unravel button
+ if( pButton == 52 )
+ {
+ UnravelTarget( socket );
+ return;
+ }
+
+ var usedMakeLast = false;
var makeID = 0;
var timerID = 0;
- // Handle "Make Last"
- if(( pButton >= 100 && pButton <= 804 ) || pButton == 5000 )
+ // Make Last
+ if( pButton == 5000 )
{
- if( pButton == 5000 )
+ var last = pUser.GetTempTag( "MakeLast_Tailoring" );
+ if( last )
{
- pButton = pUser.GetTempTag( "MAKELAST" );
+ pButton = last;
usedMakeLast = true;
}
else
{
- pUser.SetTempTag( "MAKELAST", pButton );
+ return;
}
}
- // If it's a craft button (found in map)
+ // Craft buttons use makeID directly (if in map)
if( CraftingMap[pButton] != undefined )
{
makeID = pButton;
- timerID = CraftingMap[makeID].timerID || 1;
+ var data = CraftingMap[makeID];
+ timerID = data.timerID || 1;
+
+ // Era / recipe checks
+ if( !eraOK( data ))
+ {
+ socket.SysMessage( "That item is not available in this era." );
+ return;
+ }
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ pUser.SetTempTag( "MakeLast_Tailoring", makeID );
- let materialHue = pUser.GetTempTag( "LastResourceColor" );
+ var materialHue = pUser.GetTempTag( "LastResourceColor" );
+
+ // Cloth items: select cloth (colour) unless Make Last + cached hue
if(( makeID >= 100 && makeID <= 158 ) || makeID == 180 )
{
if( usedMakeLast && materialHue != null )
{
- // Check if recipe required and not known
- let data = CraftingMap[makeID];
- if( data && !eraOK( data ))
- {
- socket.SysMessage( "That item is not available in this era." );
- return;
- }
-
- if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
- {
- socket.SysMessage("You must learn that recipe from a scroll.");
- return;
- }
MakeItem( socket, pUser, makeID, materialHue );
AddToLastTen( pUser, makeID );
- pUser.StartTimer( gumpDelay, timerID, 4030 );
+ pUser.StartTimer( gumpDelay, timerID, tailoringID );
}
else
{
pUser.SetTempTag( "makeID", makeID );
pUser.SetTempTag( "timerID", timerID );
- socket.CustomTarget(1, GetDictionaryEntry( 444, socket.language ));
+ socket.CustomTarget( 1, GetDictionaryEntry( 444, socket.language )); // Select material:
}
}
else
{
- // Check if recipe required and not known
- let data = CraftingMap[makeID];
- if( data && !eraOK( data ))
- {
- socket.SysMessage( "That item is not available in this era." );
- return;
- }
-
- if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
- {
- socket.SysMessage("You must learn that recipe from a scroll.");
- return;
- }
+ // Non-colour-selected tailoring (leather/bone etc.)
MakeItem( socket, pUser, makeID );
AddToLastTen( pUser, makeID );
+
if( GetServerSetting( "ToolUseLimit" ))
{
bItem.usesLeft -= 1;
@@ -442,40 +438,74 @@ function onGumpPress( socket, pButton, gumpData )
socket.SysMessage( GetDictionaryEntry( 10202, socket.language ));
}
}
- pUser.StartTimer( gumpDelay, timerID, 4030 );
+ pUser.StartTimer( gumpDelay, timerID, tailoringID );
}
return;
}
- // If it's a detail button (2000+ button ID pattern)
- if( pButton >= 2000 && pButton <= 3000 )
+ // Detail buttons: 20000 + makeID (new system)
+ if( pButton >= 20000 && pButton < 30000 )
{
- let makeID = pButton - 2000;
- if( CraftingMap[makeID] )
+ var detailMakeID = pButton - 20000;
+ var entry = CraftingMap[detailMakeID];
+ if( entry )
{
- pUser.SetTempTag( "ITEMDETAILS", makeID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
- }
- return;
- }
+ // Which item to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || tailoringSkillID );
+
+ // Clear old harvest tags
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old custom harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // Optional harvest dictIDs (cloth, leather, bone etc.) – add later per item
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
- if( pButton == 0 ) // Exit/Close
- {
- let gumpID = scriptID + 0xffff;
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null );
- socket.CloseGump( gumpID, 0 );
- return;
- }
+ // Optional custom resource names (e.g. "Cloth", "Leather")
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
- if( pButton == 52 ) // Unravel Item
- {
- UnravelTarget( socket );
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
return;
}
}
-/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
+/** @type { ( socket: Socket, ourObj: Character | Item | null ) => void } */
function onCallback1( socket, ourObj )
{
var pUser = socket.currentChar;
@@ -483,58 +513,57 @@ function onCallback1( socket, ourObj )
return;
// Fetch makeID and timerID from temp tag
- var makeID = pUser.GetTempTag( "makeID" );
+ var makeID = pUser.GetTempTag( "makeID" );
var timerID = pUser.GetTempTag( "timerID" );
pUser.SetTempTag( "makeID", null );
pUser.SetTempTag( "timerID", null );
var bItem = socket.tempObj;
- if( ValidateObject( bItem ))
+ if( !ValidateObject( bItem ))
+ return;
+
+ if( ValidateObject( ourObj ) && ourObj.isItem )
{
- if( ValidateObject( ourObj ) && ourObj.isItem )
+ // Make sure targeted item is in player's backpack
+ var iPackOwner = GetPackOwner( ourObj, 0 );
+ if( ValidateObject( iPackOwner ))
{
- // Make sure targeted item is in player's backpack
- var iPackOwner = GetPackOwner( ourObj, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ if( iPackOwner.serial != pUser.serial )
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
- {
- socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That resource is in someone else's backpack!
- return;
- }
- }
- else
- {
- socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This has to be in your backpack before you can use it.
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language ));
return;
}
+ }
+ else
+ {
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language ));
+ return;
+ }
- //Cache cloth color for future "Make Last"
- pUser.SetTempTag( "LastResourceColor", ourObj.colour );
+ // Cache cloth color for Make Last
+ pUser.SetTempTag( "LastResourceColor", ourObj.colour );
- // Check if recipe required and not known
- let data = CraftingMap[makeID];
- if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
- {
- socket.SysMessage("You must learn that recipe from a scroll.");
- return;
- }
+ // Recipe re-check (just in case)
+ var data = CraftingMap[makeID];
+ if( data && data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
- // Pass in the colour of the desired material to use for crafting
- MakeItem( socket, pUser, makeID, ourObj.colour );
- AddToLastTen( pUser, makeID );
- if( GetServerSetting( "ToolUseLimit" ))
+ MakeItem( socket, pUser, makeID, ourObj.colour );
+ AddToLastTen( pUser, makeID );
+
+ if( GetServerSetting( "ToolUseLimit" ))
+ {
+ bItem.usesLeft -= 1;
+ if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
- {
- bItem.Delete();
- socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
- }
+ bItem.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
}
- pUser.StartTimer( gumpDelay, timerID, 4030 );
}
+ pUser.StartTimer( gumpDelay, timerID, tailoringID );
}
}
@@ -544,15 +573,13 @@ function UnravelTarget( socket )
}
// Clothes and leather armor can be unravelled back into cloth and leather
-/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
+/** @type { ( socket: Socket, ourObj: Character | Item | null ) => void } */
function onCallback2( socket, ourObj )
{
- // Unravel item, get cloth/leather in return
var mChar = socket.currentChar;
if( !ValidateObject( ourObj ) || !ourObj.isItem )
{
- // Targeted object is not an item that can be smelted
mChar.SetTempTag( "prevActionResult", "CANTUNRAVEL" );
mChar.StartTimer( repairDelay, 1, true );
return;
@@ -571,46 +598,40 @@ function onCallback2( socket, ourObj )
var resourceColor = ourObj.colour;
var materialType = TriggerEvent( 2506, "GetItemMaterialType", ourObj, 0 );
+ var resourceID = 0;
+
if( creatorSerial == -1 || entryMadeFrom == 0 || createEntry == null )
{
- // Not a player-crafted item, return 1 resource if item is made of cloth/leather
+ // Not player-crafted; 1 resource if cloth/leather
if( materialType == "cloth" || materialType == "leather" )
- {
resourceAmount = 1;
- }
}
else
{
if( createEntry.avgMinSkill > mChar.skills.tailoring )
{
+ var gumpID = tailoringID + 0xffff;
socket.CloseGump( gumpID, 0 );
mChar.SetTempTag( "prevActionResult", "NOUNRAVELSKILL" );
- mChar.StartTimer( gumpDelay, 1, 4030 );
+ mChar.StartTimer( gumpDelay, 1, tailoringID );
return;
}
- // Loop through resources used to craft item, see how many resources were used
- var resourceID = 0;
+ // Loop resources used to craft item
var resourcesUsed = createEntry.resources;
for( var i = 0; i < resourcesUsed.length; i++ )
{
var resource = resourcesUsed[i];
var amountNeeded = resource[0];
- var colorNeeded = resource[1];
- var resourceIDs = resource[2];
+ var colorNeeded = resource[1]; // unused here but kept for completeness
+ var resourceIDs = resource[2];
- // Loop through list of resourceIDs that were valid for crafting this item, see if ANY
- // were a match for the resource we're trying to return
for( var j = 0; j <= resourceIDs.length; j++ )
{
if( !isNaN( parseInt( resourceIDs[j] )))
{
- // If we found some resource that match up to cloth, or leather, go for it
- var resourceType = TriggerEvent( 2506, "GetResourceType", parseInt( resourceIDs[j] ));
- /*mChar.TextMessage( "MaterialType: " + materialType );
- mChar.TextMessage( "ResourceType: " + resourceType );
- mChar.TextMessage( "resourceID: " + resourceIDs[j] );*/
- if( materialType == resourceType )
+ var resType = TriggerEvent( 2506, "GetResourceType", parseInt( resourceIDs[j] ));
+ if( materialType == resType )
{
maxResourceAmount = amountNeeded;
resourceID = resourceIDs[j];
@@ -619,42 +640,26 @@ function onCallback2( socket, ourObj )
}
}
- // We only really care about the first and primary resource....
if( maxResourceAmount > 0 )
break;
}
if( maxResourceAmount > 1 )
{
- // Calculate amount of resources returned based on player's mining skill, item's wear and tear,
- // and amount of resources that went into making the item in the first place
- if( ourObj.health >= 1 || ourObj.usesLeft >= 1 )
- {
- var healthPercentage = 0;
- if( ourObj.health >= 1 )
- {
- healthPercentage = Math.floor( ( ourObj.health * 100 ) / ourObj.maxhp );
- }
+ var healthPercentage = 0;
+ if( ourObj.health >= 1 )
+ healthPercentage = Math.floor( ( ourObj.health * 100 ) / ourObj.maxhp );
- var usesPercentage = 0;
- if( ourObj.usesLeft >= 1 )
- {
- usesPercentage = Math.floor( ( ourObj.usesLeft * 100 ) / ourObj.maxUses );
- }
+ var usesPercentage = 0;
+ if( ourObj.usesLeft >= 1 )
+ usesPercentage = Math.floor( ( ourObj.usesLeft * 100 ) / ourObj.maxUses );
- var itemPercentage = usesPercentage > 0 ? Math.min( healthPercentage, usesPercentage ) : healthPercentage;
+ var itemPercentage = usesPercentage > 0 ? Math.min( healthPercentage, usesPercentage ) : healthPercentage;
- // Reduce amount of resources returned based on state of object's wear and tear
- resourceAmount = Math.floor( ( maxResourceAmount * itemPercentage ) / 100 );
- }
-
- // Halve the amount of resources returned
+ resourceAmount = Math.floor( ( maxResourceAmount * itemPercentage ) / 100 );
resourceAmount = Math.max( Math.floor( resourceAmount / 2 ), 1 );
- // Fetch player's Tailoring skill
var playerSkill = mChar.skills.tailoring;
-
- // Based on player's Tailoring skill, return between 1 to maxResourceAmount
resourceAmount = Math.min( Math.max( Math.floor( resourceAmount * ( playerSkill / 1000 )), 1 ), resourceAmount );
}
else
@@ -665,76 +670,67 @@ function onCallback2( socket, ourObj )
if( resourceAmount == 0 || resourceID == 0 )
{
- // Targeted object is not an item that can be unravelled
mChar.SetTempTag( "prevActionResult", "CANTUNRAVEL" );
- mChar.StartTimer( repairDelay, 1, 4030 );
+ mChar.StartTimer( repairDelay, 1, tailoringID );
return;
}
- // Delete the unravelled item
+ // Delete unravelled item
ourObj.Delete();
- // Run a generic skill check to give player a chance to increase their tailoring skill
- mChar.CheckSkill( 34, 0, mChar.skillCaps.tailoring );
+ // Generic skill check
+ mChar.CheckSkill( tailoringSkillID, 0, mChar.skillCaps.tailoring );
- // Determine the actual resource item to add to player's backpack
- // We'll default to one specific resource per material type
+ // Determine returned resource
var itemToAdd = "";
switch( materialType )
{
- case "cloth":
- itemToAdd = "0x1766"; // cut cloth
- break;
- case "leather":
- itemToAdd = "0x1068"; // cut leather
- break;
- default:
- break;
+ case "cloth": itemToAdd = "0x1766"; break; // cut cloth
+ case "leather": itemToAdd = "0x1068"; break; // cut leather
+ default: break;
}
+
var newResource = CreateDFNItem( socket, mChar, itemToAdd, resourceAmount, "ITEM", true, resourceColor );
mChar.SetTempTag( "resourceFromUnravelling", resourceAmount );
mChar.SetTempTag( "prevActionResult", "UNRAVELSUCCESS" );
- mChar.StartTimer( gumpDelay, 1, 4030 );
+ mChar.StartTimer( gumpDelay, 1, tailoringID );
}
function AddToLastTen( pUser, makeID )
{
- // Append makeID to last ten list
- var raw = pUser.GetTag( "LastTenTailoring" ) || "";
+ var raw = pUser.GetTempTag( "LastTenTailoring" ) || "";
var list = raw.split( "," );
- // Remove if already in list
for( var i = 0; i < list.length; i++ )
{
- if( parseInt( list[i]) == makeID )
+ if( parseInt( list[i] ) == makeID )
{
- list.splice(i, 1);
+ list.splice( i, 1 );
break;
}
}
- // Add to front (no unshift in SpiderMonkey 1.8)
- var newList = [makeID];
- for( var i = 0; i < list.length && newList.length < 10; i++ )
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
{
- var entry = parseInt( list[i] );
+ var entry = parseInt( list[j] );
if( !isNaN( entry ) && entry > 0 )
newList.push( entry );
}
- pUser.SetTag( "LastTenTailoring", newList.join( "," ));
+ pUser.SetTempTag( "LastTenTailoring", newList.join( "," ));
}
function HasLearnedRecipe( pUser, recipeID )
{
var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
- if( !myData || myData.length == 0)
+ if( !myData || myData.length == 0 )
return false;
- for( let i = 0; i < myData.length; i++ )
+ for( var i = 0; i < myData.length; i++ )
{
- let data = myData[i].split( "," );
+ var data = myData[i].split( "," );
if( data[0] == recipeID )
return true;
}
@@ -743,11 +739,9 @@ function HasLearnedRecipe( pUser, recipeID )
function eraOK( entry )
{
- // Optional per-entry gates. Accept either/both. Strings like "lbr","aos","ml","tol".
- // If not present, the entry is valid for all eras.
if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
return false;
if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
return false;
return true;
-}
+}
\ No newline at end of file
diff --git a/data/js/skill/craft/tinkering.js b/data/js/skill/craft/tinkering.js
index dfb4b3623..1af465688 100644
--- a/data/js/skill/craft/tinkering.js
+++ b/data/js/skill/craft/tinkering.js
@@ -1,538 +1,503 @@
///
// @ts-check
-const LabelHue = 0x480; // Color of the text.
-const LabelColor = 0x7FFF; // Second Color of text.
-const scriptID = 4032; // Use this to tell the gump what script to close.
-const gumpDelay = 2000; // Timer for the gump to reapear after crafting.
-const itemDetailsScriptID = 4026;
-const craftGumpID = 4027;
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// The section below is the tables for each page.
-// All you have to do is add the item to your dictionary
-// and then list the dictionary number in the right page and it will
-// add it to the crafting gump.
-///////////////////////////////////////////////////////////////////////////////////////////
+const textHue = 0x480; // Color of the text.
+const tinkeringID = 4032; // Script ID for this tinkering gump
+const gumpDelay = 2000; // Delay (ms) before gump reappears after crafting
+const itemDetailsScriptID = 4026; // Generic item details gump
+const craftGumpID = 4027; // Shared crafting menu frame
+const itemsPerPage = 10; // Items per subpage
+const displayUnlearnedRecipes = true; // For future recipe use
+const coreShardEra = EraStringToNum( GetServerSetting( "CoreShardEra" ));
+const tinkeringSkillID = 37; // Index of "tinkering" in ItemDetailGump.skillNames[]
+
+const craftMapRegistryID = 4038;
+var TinkeringMap = {};
+
+/** @type { () => boolean } */
+function LoadTinkeringMap()
+{
+ TinkeringMap = {};
-const myPage = [
- // Page 1 - Wooden Items
- [ 11801, 11802, 11803, 11804, 11805 ],
+ var tinkeringEntries = TriggerEvent( craftMapRegistryID, "CraftMapRegistry", "tinkering" );
- // Page 2 - Tools
- [ 11820, 11821, 11822, 11823, 11824, 11825, 11826, 11827, 11828, 11829, 11830, 11831, 11832, 11833, 11834, 11835, 11836, 11837, 11838 ],
+ if( !tinkeringEntries )
+ {
+ Console.Warning( "Tinkering: CraftMapRegistry returned null." );
+ return false;
+ }
- // Page 3 - Parts
- [ 11860, 11861, 11862, 11863, 11864, 11865, 11866 ],
+ if( !IsTinkeringArrayValue( tinkeringEntries ))
+ {
+ Console.Warning( "Tinkering: CraftMapRegistry did not return an array." );
+ return false;
+ }
- // Page 4 - Utensils
- [ 11880, 11881, 11882, 11883, 11884, 11885, 11886, 11887, 11888, 11889, 11890, 11891 ],
+ for( var i = 0; i < tinkeringEntries.length; i++ )
+ {
+ var entry = tinkeringEntries[i];
- // Page 5 - Jewelry
- [ 11900, 11901, 11902, 11903, 11904, 11905 ],
+ if( !entry || typeof entry.makeID == "undefined" )
+ continue;
- // Page 6 - Miscellaneuos
- [ 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927 ],
+ if( entry.skill === undefined )
+ entry.skill = tinkeringSkillID;
- // Page 7 - Multi-Component Items
- [ 11940, 11941, 11942, 11943, 11946, 11947, 11948 ],
+ TinkeringMap[entry.makeID] = entry;
+ }
- // Page 8 - Candles
- [ 11960, 11961, 11962, 11963, 11964, 11965, 11966, 11967 ],
+ return true;
+}
- // Page 9 - Traps
- [ 11980, 11981, 11982 ]
-];
+/** @type { ( value: any ) => boolean } */
+function IsTinkeringArrayValue( value )
+{
+ return Object.prototype.toString.call( value ) == "[object Array]";
+}
+// o--------------------------------------------------------------------------o
+// | PageX() - build a page of tinkering items |
+// o--------------------------------------------------------------------------o
+/** @type { ( socket: Socket, pUser: Character, pageNum: number ) => void } */
function PageX( socket, pUser, pageNum )
{
- // Pages 1 - 9
- var myGump = new Gump;
- pUser.SetTempTag( "page", pageNum );
- TriggerEvent( craftGumpID, "CraftingGumpMenu", myGump, socket );
- for ( var i = 0; i < myPage[pageNum - 1].length; i++ )
+ if( !socket || !ValidateObject( pUser ))
+ return;
+
+ if( !TinkeringMap || Object.keys( TinkeringMap ).length == 0 )
+ {
+ if( !LoadTinkeringMap() )
+ {
+ socket.SysMessage( "Tinkering craft map failed to load." );
+ return;
+ }
+ }
+
+ var pageItems;
+
+ // Last Ten page (if you wire a tab to 999 later)
+ if( pageNum == 999 )
{
- var index = i % 10;
- if ( index == 0 )
+ var lastTenRaw = pUser.GetTempTag( "LastTenTinkering" ) || "";
+ var split = lastTenRaw.split( "," );
+ pageItems = [];
+
+ for( var i = 0; i < split.length; i++ )
{
- if ( i > 0 )
+ var val = parseInt( split[i] );
+ if( !isNaN( val ))
+ pageItems.push( val );
+ }
+ }
+ else
+ {
+ // Collect all makeIDs for this page
+ var makeIDs = [];
+ for( var key in TinkeringMap )
+ {
+ if( !TinkeringMap.hasOwnProperty( key ))
+ continue;
+
+ var makeID = parseInt( key );
+ var data = TinkeringMap[makeID];
+ if( !data || data.page != pageNum )
+ continue;
+
+ makeIDs.push( makeID );
+ }
+
+ // Sort by dictID so order matches dictionary sequence
+ makeIDs.sort( function( a, b )
+ {
+ var ea = TinkeringMap[a];
+ var eb = TinkeringMap[b];
+ if( ea && eb )
+ return ( ea.dictID || 0 ) - ( eb.dictID || 0 );
+ return a - b;
+ });
+
+ // Era / recipe filtering
+ pageItems = [];
+ for( var k = 0; k < makeIDs.length; k++ )
+ {
+ var id = makeIDs[k];
+ var data2 = TinkeringMap[id];
+ if( !data2 )
+ continue;
+
+ var needsRecipe = data2.recipeID;
+ var showAll = displayUnlearnedRecipes;
+
+ if( eraOK( data2 ) && ( !needsRecipe || showAll || HasLearnedRecipe( pUser, needsRecipe )) )
+ pageItems.push( id );
+ }
+
+ // Fallback: if no items on this page and it's not page 1, go to page 1
+ if( pageItems.length == 0 && pageNum != 1 )
+ {
+ pageNum = 1;
+
+ makeIDs = [];
+ for( var key2 in TinkeringMap )
{
- myGump.AddButton( 370, 260, 4005, 4007, 0, ( i / 10 ) + 1, 0 );
- myGump.AddHTMLGump( 405, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10100, socket.language ) + "" );// NEXT PAGE
+ if( !TinkeringMap.hasOwnProperty( key2 ))
+ continue;
+
+ var mid2 = parseInt( key2 );
+ var d3 = TinkeringMap[mid2];
+ if( !d3 || d3.page != 1 )
+ continue;
+
+ makeIDs.push( mid2 );
+ }
+
+ makeIDs.sort( function( a, b )
+ {
+ var ea2 = TinkeringMap[a];
+ var eb2 = TinkeringMap[b];
+ if( ea2 && eb2 )
+ return ( ea2.dictID || 0 ) - ( eb2.dictID || 0 );
+ return a - b;
+ });
+
+ pageItems = [];
+ for( var m = 0; m < makeIDs.length; m++ )
+ {
+ var id2 = makeIDs[m];
+ var data4 = TinkeringMap[id2];
+ if( !data4 )
+ continue;
+
+ var needsRecipe2 = data4.recipeID;
+ var showAll2 = displayUnlearnedRecipes;
+
+ if( eraOK( data4 ) && ( !needsRecipe2 || showAll2 || HasLearnedRecipe( pUser, needsRecipe2 )) )
+ pageItems.push( id2 );
}
+ }
+ }
+
+ // Subpage handling (future-proof; currently you effectively have 1 or 2 subpages per category)
+ var subPage = pUser.GetTempTag( "subPage" );
+ var totalSubPages = Math.ceil( pageItems.length / itemsPerPage );
+
+ if( totalSubPages < 1 )
+ totalSubPages = 1;
+ if( subPage < 1 )
+ subPage = 1;
+ if( subPage > totalSubPages )
+ subPage = totalSubPages;
+
+ pUser.SetTempTag( "page", pageNum );
+ pUser.SetTempTag( "subPage", subPage );
+
+ var startIndex = ( subPage - 1 ) * itemsPerPage;
+ var endIndex = Math.min( startIndex + itemsPerPage, pageItems.length );
+
+ if( startIndex >= pageItems.length )
+ {
+ subPage = 1;
+ startIndex = 0;
+ endIndex = Math.min( itemsPerPage, pageItems.length );
+ pUser.SetTempTag( "subPage", subPage );
+ }
+
+ var tinkGump = new Gump;
+ TriggerEvent( craftGumpID, "CraftingGumpMenu", tinkGump, socket );
+ tinkGump.AddPage( 1 );
+
+ for( var j = startIndex; j < endIndex; j++ )
+ {
+ var index = j - startIndex;
+ var makeID = pageItems[j];
+ var entryText;
+ var buttonID = makeID; // use makeID directly as buttonID
- myGump.AddPage( ( i / 10 ) + 1 );
+ var data5 = TinkeringMap[makeID];
- if ( i > 0 )
+ if( !data5 )
+ {
+ entryText = "[Missing MakeID: " + makeID + "]";
+ }
+ else
+ {
+ if( data5.customName )
+ {
+ entryText = data5.customName;
+ }
+ else if( data5.dictID )
+ {
+ entryText = GetDictionaryEntry( data5.dictID, socket.language );
+ if( !entryText || entryText === "" )
+ entryText = "[Missing EntryID: " + data5.dictID + "]";
+ }
+ else
{
- myGump.AddButton( 220, 260, 4014, 4015, 0, i / 10, 0 );
- myGump.AddHTMLGump( 255, 263, 100, 18, 0, 0, " " + GetDictionaryEntry( 10101, socket.language ) + "" );// PREV PAGE
+ entryText = "[Unnamed Item: " + makeID + "]";
}
}
- myGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, ( 100 * pageNum ) + i );
- myGump.AddText( 255, 60 + ( index * 20 ), LabelHue, GetDictionaryEntry( myPage[pageNum - 1][i], socket.language ) );
+ // Craft button
+ tinkGump.AddButton( 220, 60 + ( index * 20 ), 4005, 4007, 1, 0, buttonID );
+ tinkGump.AddText( 255, 60 + ( index * 20 ), textHue, entryText );
- myGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 2000 + ( 100 * pageNum ) + i );
+ // Detail button: 20000 + makeID
+ tinkGump.AddButton( 480, 60 + ( index * 20 ), 4011, 4012, 1, 0, 20000 + buttonID );
}
- myGump.Send( socket );
- myGump.Free();
+
+ // Prev subpage
+ if( subPage > 1 )
+ {
+ tinkGump.AddButton( 220, 260, 4014, 4015, 1, 0, 8000 + ( subPage - 1 ));
+ tinkGump.AddHTMLGump( 255, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10101, socket.language ) + "" ); // PREV PAGE
+ }
+
+ // Next subpage
+ if( subPage < totalSubPages )
+ {
+ tinkGump.AddButton( 370, 260, 4005, 4007, 1, 0, 9000 + ( subPage + 1 ));
+ tinkGump.AddHTMLGump( 405, 263, 100, 18, false, false,
+ " " + GetDictionaryEntry( 10100, socket.language ) + "" ); // NEXT PAGE
+ }
+
+ tinkGump.Send( socket );
+ tinkGump.Free();
}
-/** @type { ( tObject: BaseObject, timerId: number ) => void } */
+/** @type { ( pUser: Character, timerID: number ) => void } */
function onTimer( pUser, timerID )
{
if( !ValidateObject( pUser ))
return;
- var socket = pUser.socket;
+ var pSocket = pUser.socket;
+ if( pSocket == null )
+ return;
- switch ( timerID )
+ if( timerID >= 1 && timerID <= 9 )
{
- case 1: // Page 1 - Wooden Items
- case 2: // Page 2 - Tools
- case 3: // Page 3 - Parts
- case 4: // Page 4 - Utensils
- case 5: // Page 5 - Jewelry
- case 6: // Page 6 - Miscellaneous
- case 7: // Page 7 - Multi-Component Items
- case 8: // Page 8 - Candles
- case 9: // Page 9 - Traps
- PageX( socket, pUser, timerID );
- break;
+ PageX( pSocket, pUser, timerID );
+ }
+ else if( timerID == 999 )
+ {
+ PageX( pSocket, pUser, 999 );
}
}
-/** @type { ( myObj: Socket, pressed: number, gump: GumpData ) => void } */
-function onGumpPress( pSock, pButton, gumpData )
+/** @type { ( socket: Socket, pButton: number, gumpData: GumpData ) => void } */
+function onGumpPress( socket, pButton, gumpData )
{
- var pUser = pSock.currentChar;
+ if( socket == null )
+ return;
- // Don't continue if character is invalid, or worse... dead!
+ var pUser = socket.currentChar;
if( !ValidateObject( pUser ) || pUser.dead )
return;
// Don't continue if player no longer has access to the crafting tool
- var bItem = pSock.tempObj;
- if( !ValidateObject( bItem ) || !pUser.InRange( bItem, 3 ))
+ var tool = socket.tempObj;
+ if( !ValidateObject( tool ) || !pUser.InRange( tool, 3 ))
{
- pSock.SysMessage( GetDictionaryEntry( 461, pSock.language )); // You are too far away.
+ socket.SysMessage( GetDictionaryEntry( 461, socket.language )); // You are too far away.
return;
}
- if( bItem.movable == 3 )
+ if( tool.movable == 3 )
{
- pSock.SysMessage( GetDictionaryEntry( 6031, pSock.language )); // Locked down resources cannot be used!
+ socket.SysMessage( GetDictionaryEntry( 6031, socket.language )); // Locked down resources cannot be used!
return;
}
- var iPackOwner = GetPackOwner( bItem, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
+ var packOwner = GetPackOwner( tool, 0 );
+ if( ValidateObject( packOwner ))
{
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
+ if( packOwner.serial != pUser.serial )
{
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
+ socket.SysMessage( GetDictionaryEntry( 6032, socket.language )); // That resource is in someone else's backpack!
return;
}
}
else
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ socket.SysMessage( GetDictionaryEntry( 6022, socket.language )); // This has to be in your backpack before you can use it.
return;
}
- var gumpID = scriptID + 0xffff;
- var makeID = 0;
- var itemDetailsID = 0;
- var timerID = 0;
+ var gumpID = tinkeringID + 0xffff;
- if(( pButton >= 100 && pButton <= 950 ) || pButton == 5000 )
+ // Subpage back / forward
+ if( pButton >= 8001 && pButton < 9000 )
{
- if( pButton == 5000 )
- {
- // Make Last button
- pButton = pUser.GetTempTag( "MAKELAST" );
- }
- else
- {
- pUser.SetTempTag( "MAKELAST", pButton );
- }
+ var subPage = pButton - 8000;
+ var pageNum = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage );
+ PageX( socket, pUser, pageNum );
+ return;
}
- switch ( pButton )
+ if( pButton >= 9001 && pButton < 10000 )
{
- case 0: // Abort and do nothing
- pUser.SetTempTag( "MAKELAST", null );
- pUser.SetTempTag( "CRAFT", null )
- pSock.CloseGump( gumpID, 0 );
- break;
- case 1: // Page 1 - Wooden Items
- case 2: // Page 2 - Tools
- case 3: // Page 3 - Parts
- case 4: // Page 4 - Utensils
- case 5: // Page 5 - Jewelry
- case 6: // Page 6 - Miscellaneous
- case 7: // Page 7 - Multi-Component Items
- case 8: // Page 8 - Candles
- case 9: // Page 9 - Traps
- pSock.CloseGump( gumpID, 0 );
- PageX( pSock, pUser, pButton );
- break;
- // Make Items
- // Page 1 - Wooden Items
- case 100: // Axle
- makeID = 274; timerID = 1; break;
- case 101: // Clock Frame
- makeID = 273; timerID = 1; break;
- case 102: // Jointing Plane
- makeID = 270; timerID = 1; break;
- case 103: // Moulding Plane
- makeID = 271; timerID = 1; break;
- case 104: // Smoothing Plane
- makeID = 272; timerID = 1; break;
- // Page 2 - Tools
- case 200: // Dovetail Saw
- makeID = 218; timerID = 2; break;
- case 201: // Draw Knife
- makeID = 215; timerID = 2; break;
- case 202: // Froe
- makeID = 252; timerID = 2; break;
- case 203: // Hammer
- makeID = 255; timerID = 2; break;
- case 204: // Hatchet
- makeID = 214; timerID = 2; break;
- case 205: // Inshave
- makeID = 258; timerID = 2; break;
- case 206: // Lockpick
- makeID = 260; timerID = 2; break;
- case 207: // Mortar and Pestle
- makeID = 211; timerID = 2; break;
- case 208: // Pick Axe
- makeID = 259; timerID = 2; break;
- case 209: // Saw
- makeID = 217; timerID = 2; break;
- case 210: // Scissors
- makeID = 210; timerID = 2; break;
- case 211: // Scorp
- makeID = 212; timerID = 2; break;
- case 212: // Sewing Kit
- makeID = 216; timerID = 2; break;
- case 213: // Shovel
- makeID = 254; timerID = 2; break;
- case 214: // Sledge Hammer
- makeID = 257; timerID = 2; break;
- case 215: // Smith's Hammer
- makeID = 256; timerID = 2; break;
- case 216: // Tongs
- makeID = 253; timerID = 2; break;
- case 217: // Tool Kit (Tinker's tools)
- makeID = 213; timerID = 2; break;
- case 218: // Fletcher's Tools
- makeID = 284; timerID = 2; break;
- // Page 3 - Parts
- case 300: // Barrel Hoops
- makeID = 224; timerID = 3; break;
- case 301: // Barrel Tap
- makeID = 221; timerID = 3; break;
- case 302: // Clock parts
- makeID = 220; timerID = 3; break;
- case 303: // Gears
- makeID = 219; timerID = 3; break;
- case 304: // Hinge
- makeID = 225; timerID = 3; break;
- case 305: // Sextant parts
- makeID = 223; timerID = 3; break;
- case 306: // Springs
- makeID = 222; timerID = 3; break;
- // Page 4 - Utensils
- case 400: // Butcher Knife
- makeID = 226; timerID = 4; break;
- case 401: // Cleaver
- makeID = 232; timerID = 4; break;
- case 402: // Fork
- makeID = 230; timerID = 4; break;
- case 403: // Fork
- makeID = 231; timerID = 4; break;
- case 404: // Goblet
- makeID = 235; timerID = 4; break;
- case 405: // Knife
- makeID = 233; timerID = 4; break;
- case 406: // Knife
- makeID = 234; timerID = 4; break;
- case 407: // Pewter Mug
- makeID = 236; timerID = 4; break;
- case 408: // Plate
- makeID = 229; timerID = 4; break;
- case 409: // Skinning Knife
- makeID = 237; timerID = 4; break;
- case 410: // Spoon
- makeID = 227; timerID = 4; break;
- case 411: // Spoon
- makeID = 228; timerID = 4; break;
- // Page 5 - Jewelry
- case 500: // Bracelet
- makeID = 243; timerID = 5; break;
- case 501: // Earrings
- makeID = 241; timerID = 5; break;
- case 502: // Necklage (Golden beads)
- makeID = 239; timerID = 5; break;
- case 503: // Necklace (Silver beads)
- makeID = 240; timerID = 5; break;
- case 504: // Necklace (Round)
- makeID = 242; timerID = 5; break;
- case 505: // Weddingband (newbiefied)
- makeID = 238; timerID = 5; break;
- // Page 6 - Miscellaneous
- case 600: // Candelabra
- makeID = 245; timerID = 6; break;
- case 601: // Globe
- makeID = 248; timerID = 6; break;
- case 602: // Heating stand
- makeID = 251; timerID = 6; break;
- case 603: // Iron Key
- makeID = 247; timerID = 6; break;
- case 604: // Keyring
- makeID = 244; timerID = 6; break;
- case 605: // Lantern
- makeID = 250; timerID = 6; break;
- case 606: // Scales
- makeID = 246; timerID = 6; break;
- case 607: // Spy glass
- makeID = 249; timerID = 6; break;
- // Page 7 - Multi-Component Items
- case 700: // Axle and Gears
- makeID = 275; timerID = 7; break;
- case 701: // Clock
- makeID = 276; timerID = 7; break;
- case 702: // Clock
- makeID = 277; timerID = 7; break;
- case 703: // Clock Parts
- makeID = 278; timerID = 7; break;
- case 704: // Potion Keg
- makeID = 281; timerID = 7; break;
- case 705: // Sextant
- makeID = 282; timerID = 7; break;
- case 706: // Sextant Parts
- makeID = 283; timerID = 7; break;
- // Page 8 - Candles
- case 800: // Candelabra
- makeID = 245; timerID = 8; break;
- case 801: // Standing Candelabra
- makeID = 310; timerID = 8; break;
- case 802: // Regular Candle
- makeID = 315; timerID = 8; break;
- case 803: // Round Candle
- makeID = 312; timerID = 8; break;
- case 804: // Skull with Candle
- makeID = 316; timerID = 8; break;
- case 805: // Small Candle
- makeID = 314; timerID = 8; break;
- case 806: // Tall Candle
- makeID = 311; timerID = 8; break;
- case 807: // Thick Candle
- makeID = 313; timerID = 8; break;
- // Page 9 - Traps
- case 900: // Dart Trap
- makeID = 261; timerID = 9; break;
- case 901: // Explosion Trap
- makeID = 263; timerID = 9; break;
- case 902: // Poison Trap
- makeID = 262; timerID = 9; break;
- // Show Item Details
- case 2100: // Axle
- itemDetailsID = 274; break;
- case 2101: // Clock Frame
- itemDetailsID = 273; break;
- case 2102: // Jointing Plane
- itemDetailsID = 270; break;
- case 2103: // Moulding Plane
- itemDetailsID = 271; break;
- case 2104: // Smoothing Plane
- itemDetailsID = 272; break;
- // Page 2 - Tools
- case 2200: // Dovetail Saw
- itemDetailsID = 218; break;
- case 2201: // Draw Knife
- itemDetailsID = 215; break;
- case 2202: // Froe
- itemDetailsID = 252; break;
- case 2203: // Hammer
- itemDetailsID = 255; break;
- case 2204: // Hatchet
- itemDetailsID = 214; break;
- case 2205: // Inshave
- itemDetailsID = 258; break;
- case 2206: // Lockpick
- itemDetailsID = 260; break;
- case 2207: // Mortar and Pestle
- itemDetailsID = 211; break;
- case 2208: // Pick Axe
- itemDetailsID = 259; break;
- case 2209: // Saw
- itemDetailsID = 217; break;
- case 2210: // Scissors
- itemDetailsID = 210; break;
- case 2211: // Scorp
- itemDetailsID = 212; break;
- case 2212: // Sewing Kit
- itemDetailsID = 216; break;
- case 2213: // Shovel
- itemDetailsID = 254; break;
- case 2214: // Sledge Hammer
- itemDetailsID = 257; break;
- case 2215: // Smith's Hammer
- itemDetailsID = 256; break;
- case 2216: // Tongs
- itemDetailsID = 253; break;
- case 2217: // Tool Kit (Tinker's tools)
- itemDetailsID = 213; break;
- case 2218: // Fletcher's Tools
- itemDetailsID = 284; break;
- // Page 3 - Parts
- case 2300: // Barrel Hoops
- itemDetailsID = 224; break;
- case 2301: // Barrel Tap
- itemDetailsID = 221; break;
- case 2302: // Clock parts
- itemDetailsID = 220; break;
- case 2303: // Gears
- itemDetailsID = 219; break;
- case 2304: // Hinge
- itemDetailsID = 225; break;
- case 2305: // Sextant parts
- itemDetailsID = 223; break;
- case 2306: // Springs
- itemDetailsID = 222; break;
- // Page 4 - Utensils
- case 2400: // Butcher Knife
- itemDetailsID = 226; break;
- case 2401: // Cleaver
- itemDetailsID = 232; break;
- case 2402: // Fork
- itemDetailsID = 230; break;
- case 2403: // Fork
- itemDetailsID = 231; break;
- case 2404: // Goblet
- itemDetailsID = 235; break;
- case 2405: // Knife
- itemDetailsID = 233; break;
- case 2406: // Knife
- itemDetailsID = 234; break;
- case 2407: // Pewter Mug
- itemDetailsID = 236; break;
- case 2408: // Plate
- itemDetailsID = 229; break;
- case 2409: // Skinning Knife
- itemDetailsID = 237; break;
- case 2410: // Spoon
- itemDetailsID = 227; break;
- case 2411: // Spoon
- itemDetailsID = 228; break;
- // Page 5 - Jewelry
- case 2500: // Bracelet
- itemDetailsID = 243; break;
- case 2501: // Earrings
- itemDetailsID = 241; break;
- case 2502: // Necklage (Golden beads)
- itemDetailsID = 239; break;
- case 2503: // Necklace (Silver beads)
- itemDetailsID = 240; break;
- case 2504: // Necklace (Round)
- itemDetailsID = 242; break;
- case 2505: // Weddingband (newbiefied)
- itemDetailsID = 238; break;
- // Page 6 - Miscellaneous
- case 2600: // Candelabra
- itemDetailsID = 245; break;
- case 2601: // Globe
- itemDetailsID = 248; break;
- case 2602: // Heating stand
- itemDetailsID = 251; break;
- case 2603: // Iron Key
- itemDetailsID = 247; break;
- case 2604: // Keyring
- itemDetailsID = 244; break;
- case 2605: // Lantern
- itemDetailsID = 250; break;
- case 2606: // Scales
- itemDetailsID = 246; break;
- case 2607: // Spy glass
- itemDetailsID = 249; break;
- // Page 7 - Multi-Component Items
- case 2700: // Axle and Gears
- itemDetailsID = 275; break;
- case 2701: // Clock
- itemDetailsID = 276; break;
- case 2702: // Clock
- itemDetailsID = 277; break;
- case 2703: // Clock Parts
- itemDetailsID = 278; break;
- case 2704: // Potion Keg
- itemDetailsID = 281; break;
- case 2705: // Sextant
- itemDetailsID = 282; break;
- case 2706: // Sextant Parts
- itemDetailsID = 283; break;
- // Page 8 - Candles
- case 2800: // Candelabra
- itemDetailsID = 245; break;
- case 2801: // Standing Candelabra
- itemDetailsID = 310; break;
- case 2802: // Regular Candle
- itemDetailsID = 315; break;
- case 2803: // Round Candle
- itemDetailsID = 312; break;
- case 2804: // Skull with Candle
- itemDetailsID = 316; break;
- case 2805: // Small Candle
- itemDetailsID = 314; break;
- case 2806: // Tall Candle
- itemDetailsID = 311; break;
- case 2807: // Thick Candle
- itemDetailsID = 313; break;
- // Page 9 - Traps
- case 2900: // Dart Trap
- itemDetailsID = 261; break;
- case 2901: // Explosion Trap
- itemDetailsID = 263; break;
- case 2902: // Poison Trap
- itemDetailsID = 262; break;
- default:
- break;
+ var subPage2 = pButton - 9000;
+ var pageNum2 = pUser.GetTempTag( "page" );
+ pUser.SetTempTag( "subPage", subPage2 );
+ PageX( socket, pUser, pageNum2 );
+ return;
+ }
+
+ // Page tabs (1–9)
+ if( pButton >= 1 && pButton <= 9 )
+ {
+ pUser.SetTempTag( "page", pButton );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, pButton );
+ return;
+ }
+
+ // Last Ten (if you add a tab that sends 11000)
+ if( pButton == 11000 )
+ {
+ pUser.SetTempTag( "page", 999 );
+ pUser.SetTempTag( "subPage", 1 );
+ PageX( socket, pUser, 999 );
+ return;
+ }
+
+ // Close gump
+ if( pButton == 0 )
+ {
+ pUser.SetTempTag( "MakeLast_Tinkering", null );
+ pUser.SetTempTag( "CRAFT", null );
+ socket.CloseGump( gumpID, 0 );
+ return;
+ }
+
+ // Make Last
+ if( pButton == 5000 )
+ {
+ var last = pUser.GetTempTag( "MakeLast_Tinkering" );
+ if( last )
+ pButton = last;
+ else
+ return;
}
- if( makeID != 0 )
+ var makeID = 0;
+ var timerID = 0;
+
+ // Craft buttons use makeID directly
+ if( TinkeringMap[pButton] != undefined )
{
- if(( pButton >= 500 && pButton <= 505 ))
+ makeID = pButton;
+ var data = TinkeringMap[makeID];
+ timerID = data.timerID || 1;
+
+ // Era / recipe checks
+ if( !eraOK( data ))
+ {
+ socket.SysMessage( "That item is not available in this era." );
+ return;
+ }
+
+ if( data.recipeID && !TriggerEvent( 4022, "NeedRecipe", pUser, data.recipeID ))
+ {
+ socket.SysMessage( "You must learn that recipe from a scroll." );
+ return;
+ }
+
+ // Jewelry that needs gem targeting
+ if( data.requiresGemTarget )
{
- // Ask crafter which material to use
pUser.SetTempTag( "makeID", makeID );
pUser.SetTempTag( "timerID", timerID );
- pUser.AddScriptTrigger( 4033 );
- pSock.CustomTarget( 2, GetDictionaryEntry( 12008, pSock.language )); // Select material to use:
+ pUser.AddScriptTrigger( 4033 ); // crafting_complete.js
+ socket.CustomTarget( 2, GetDictionaryEntry( 12008, socket.language )); // Select material to use:
return;
}
- MakeItem( pSock, pUser, makeID );
+ // Normal craft
+ pUser.SetTempTag( "MakeLast_Tinkering", makeID );
+
+ MakeItem( socket, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
if( GetServerSetting( "ToolUseLimit" ))
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
+ tool.usesLeft -= 1;
+ if( tool.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
+ tool.Delete();
+ socket.SysMessage( GetDictionaryEntry( 10202, socket.language )); // You have worn out your tool!
}
- }
- pUser.StartTimer( gumpDelay, timerID, true );
+ }
+
+ pUser.StartTimer( gumpDelay, timerID, tinkeringID );
+ return;
}
- else if( itemDetailsID != 0 )
+
+ // Detail buttons: 20000 + makeID
+ if( pButton >= 20000 && pButton < 30000 )
{
- pUser.SetTempTag( "ITEMDETAILS", itemDetailsID );
- TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ var detailMakeID = pButton - 20000;
+ var entry = TinkeringMap[detailMakeID];
+
+ if( entry )
+ {
+ // Which item details to show
+ pUser.SetTempTag( "ITEMDETAILS", detailMakeID );
+
+ // Skill used
+ pUser.SetTempTag( "Skill", entry.skill || tinkeringSkillID );
+
+ // Clear old harvest tags
+ pUser.SetTempTag( "Harvest", null );
+ pUser.SetTempTag( "Harvest2", null );
+ pUser.SetTempTag( "Harvest3", null );
+ pUser.SetTempTag( "Harvest4", null );
+
+ // Clear old custom harvest names
+ pUser.SetTempTag( "HarvestName", null );
+ pUser.SetTempTag( "Harvest2Name", null );
+ pUser.SetTempTag( "Harvest3Name", null );
+ pUser.SetTempTag( "Harvest4Name", null );
+
+ // Optional harvest dictIDs
+ if( entry.harvest && entry.harvest.length > 0 )
+ {
+ if( entry.harvest.length >= 1 )
+ pUser.SetTempTag( "Harvest", entry.harvest[0] );
+ if( entry.harvest.length >= 2 )
+ pUser.SetTempTag( "Harvest2", entry.harvest[1] );
+ if( entry.harvest.length >= 3 )
+ pUser.SetTempTag( "Harvest3", entry.harvest[2] );
+ if( entry.harvest.length >= 4 )
+ pUser.SetTempTag( "Harvest4", entry.harvest[3] );
+ }
+
+ // Optional custom names – plugs into your ItemDetail custom harvest name logic
+ if( entry.harvestNames && entry.harvestNames.length > 0 )
+ {
+ if( entry.harvestNames.length >= 1 )
+ pUser.SetTempTag( "HarvestName", entry.harvestNames[0] );
+ if( entry.harvestNames.length >= 2 )
+ pUser.SetTempTag( "Harvest2Name", entry.harvestNames[1] );
+ if( entry.harvestNames.length >= 3 )
+ pUser.SetTempTag( "Harvest3Name", entry.harvestNames[2] );
+ if( entry.harvestNames.length >= 4 )
+ pUser.SetTempTag( "Harvest4Name", entry.harvestNames[3] );
+ }
+
+ if( entry.recipeID && entry.recipeID > 0 )
+ pUser.SetTempTag( "needRecipeID", entry.recipeID );
+ else
+ pUser.SetTempTag( "needRecipeID", 0 );
+
+ TriggerEvent( itemDetailsScriptID, "ItemDetailGump", pUser );
+ }
+ return;
}
}
-/** @type { ( tSock: Socket, target: Character | Item | null ) => void } */
+/** @type { ( pSock: Socket, targObj: Character | Item | null ) => void } */
function onCallback2( pSock, targObj )
{
var pUser = pSock.currentChar;
@@ -540,61 +505,109 @@ function onCallback2( pSock, targObj )
return;
// Fetch makeID and timerID from temp tag
- var makeID = pUser.GetTempTag( "makeID" );
+ var makeID = pUser.GetTempTag( "makeID" );
var timerID = pUser.GetTempTag( "timerID" );
pUser.SetTempTag( "makeID", null );
pUser.SetTempTag( "timerID", null );
var bItem = pSock.tempObj; // tool
- if( ValidateObject( bItem ))
+ if( !ValidateObject( bItem ))
+ return;
+
+ if( ValidateObject( targObj ) && targObj.isItem )
{
- if( ValidateObject( targObj ) && targObj.isItem )
+ // Make sure targeted item is in player's backpack
+ var iPackOwner = GetPackOwner( targObj, 0 );
+ if( ValidateObject( iPackOwner ))
{
- // Make sure targeted item is in player's backpack
- var iPackOwner = GetPackOwner( targObj, 0 );
- if( ValidateObject( iPackOwner )) // Is the item in a backpack?
- {
- if( iPackOwner.serial != pUser.serial ) // And if so does the pack belong to the user?
- {
- pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
- return;
- }
- }
- else
+ if( iPackOwner.serial != pUser.serial )
{
- pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ pSock.SysMessage( GetDictionaryEntry( 6032, pSock.language )); // That resource is in someone else's backpack!
return;
}
+ }
+ else
+ {
+ pSock.SysMessage( GetDictionaryEntry( 6022, pSock.language )); // This has to be in your backpack before you can use it.
+ return;
+ }
- if( makeID >= 238 && makeID <= 243 )
+ // Jewelry: verify gem
+ if( makeID >= 238 && makeID <= 243 )
+ {
+ var resourceType = TriggerEvent( 2506, "GetResourceType", targObj.id );
+ if( resourceType != "gems" )
{
- // Jewlery
- var resourceType = TriggerEvent( 2506, "GetResourceType", targObj.id );
- if( resourceType != "gems" )
- {
- pSock.SysMessage( GetDictionaryEntry( 12007, pSock.language )); // That's not a gem resource!
- return;
- }
-
- // Set a temporary tag on character with ID of selected gem
- // We'll check for this ID in the crafting process, and remove it
- // in the onMakeItem event script (crafting_complete.js)
- pUser.SetTempTag( "targetedSubResourceId", targObj.id );
- pUser.SetTempTag( "targetedSubResourceName", targObj.name );
+ pSock.SysMessage( GetDictionaryEntry( 12007, pSock.language )); // That's not a gem resource!
+ return;
}
- MakeItem( pSock, pUser, makeID );
- if( GetServerSetting( "ToolUseLimit" ))
+ // Tags used by crafting_complete.js
+ pUser.SetTempTag( "targetedSubResourceId", targObj.id );
+ pUser.SetTempTag( "targetedSubResourceName", targObj.name );
+ }
+
+ MakeItem( pSock, pUser, makeID );
+ AddToLastTen( pUser, makeID );
+
+ if( GetServerSetting( "ToolUseLimit" ))
+ {
+ bItem.usesLeft -= 1;
+ if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
{
- bItem.usesLeft -= 1;
- if( bItem.usesLeft == 0 && GetServerSetting( "ToolUseBreak" ))
- {
- bItem.Delete();
- pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
- // Play sound effect of tool breaking
- }
+ bItem.Delete();
+ pSock.SysMessage( GetDictionaryEntry( 10202, pSock.language )); // You have worn out your tool!
}
- pUser.StartTimer( gumpDelay, timerID, true );
+ }
+ pUser.StartTimer( gumpDelay, timerID, tinkeringID );
+ }
+}
+
+function AddToLastTen( pUser, makeID )
+{
+ var raw = pUser.GetTempTag( "LastTenTinkering" ) || "";
+ var list = raw.split( "," );
+
+ for( var i = 0; i < list.length; i++ )
+ {
+ if( parseInt( list[i] ) == makeID )
+ {
+ list.splice( i, 1 );
+ break;
}
}
+
+ var newList = [ makeID ];
+ for( var j = 0; j < list.length && newList.length < 10; j++ )
+ {
+ var entry = parseInt( list[j] );
+ if( !isNaN( entry ) && entry > 0 )
+ newList.push( entry );
+ }
+
+ pUser.SetTempTag( "LastTenTinkering", newList.join( "," ) );
+}
+
+function HasLearnedRecipe( pUser, recipeID )
+{
+ var myData = TriggerEvent( 4022, "ReadRecipeID", pUser );
+ if( !myData || myData.length == 0 )
+ return false;
+
+ for( var i = 0; i < myData.length; i++ )
+ {
+ var data = myData[i].split( "," );
+ if( data[0] == recipeID )
+ return true;
+ }
+ return false;
+}
+
+function eraOK( entry )
+{
+ if( entry.minEra && coreShardEra < EraStringToNum( entry.minEra ))
+ return false;
+ if( entry.maxEra && coreShardEra > EraStringToNum( entry.maxEra ))
+ return false;
+ return true;
}
diff --git a/docs/index.html b/docs/index.html
index 287904d4d..343406068 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -6040,6 +6040,456 @@ Rank System
Calculation of rank takes place as part of the crafting process. First, a range of potential ranks is determined based on the values of the MINRANK and MAXRANK tags for an item in the create DFNs. Then, for each skill required to craft the item, the system calculates a temporary rank based on the player's skill, random chance, and a UOX.INI setting (SKILLLEVEL=7 ) which acts as a global modifier to the system to make it easier (low SKILLLEVEL value) to get high rank items, or harder (high SKILLLEVEL value) to get high rank items. A value of 10 makes the difficulty roughly similar to the difficulty of the official UO shards around the LBR era.
When a temporary rank has been calculated for all skills required to craft the item, the average of all these is chosen as the item's final rank, and the modifiers are permanently applied to the item's properties.
+ JSON Crafting Data
+In addition to the older DFN-based create system, UOX3 can use JSON files to control the visible crafting menu data. The JSON files do not replace the actual CreateEntry definitions that decide what item is made, what resources are consumed, and what skills are checked. Instead, they make the crafting menus easier to organize, customize and extend without editing the crafting scripts directly.
+
+The JSON crafting files are stored in:
+js/jsdata/crafting/
+
+
+Crafting JSON Files
+
+
+ alchemy.json - Alchemy menu entries
+ blacksmithing.json - Blacksmithing menu entries
+ carpentry.json - Carpentry menu entries
+ cartography.json - Cartography menu entries
+ cooking.json - Cooking menu entries
+ fletching.json - Bowcraft/Fletching menu entries
+ glassblowing.json - Glassblowing menu entries
+ masonry.json - Masonry menu entries
+ tailoring.json - Tailoring menu entries
+ tinkering.json - Tinkering menu entries
+ resourcemap.json - Shared resource definitions used by crafting menus
+
+
+
+Why Use JSON Crafting Data?
+The JSON crafting system makes crafting easier to maintain. Shard admins can move items between pages, rename menu entries, add custom labels, add era limits, add recipe requirements, define item detail resources and configure some special post-crafting behavior without digging through the crafting scripts.
+
+
+ Move an item to a different crafting page by changing its page value.
+ Change the order of items by changing their position or button mapping in the JSON file.
+ Use dictID for dictionary-based names, or customName for a custom display name.
+ Add recipe locks with recipeID .
+ Add expansion or era limits with minEra and maxEra .
+ Show custom resource names in the item detail gump with harvest and harvestNames .
+ Define special post-craft behavior with craftComplete .
+ Update resource lists, hues, dictionary labels and skill gates through resourcemap.json .
+
+
+
+Basic Craft Entry Example
+
+
{
+ "makeID": 274,
+ "label": "Axle",
+ "dictID": 11801,
+ "page": 1,
+ "timerID": 1,
+ "harvest": [10014]
+}
+
+
This entry adds a craftable item to the menu. The makeID points to the CreateEntry that actually creates the item. The dictID is used for the displayed name, page decides which menu page the item appears on and timerID decides which page reopens after crafting.
+
+
+Craft Entry Fields
+Each crafting JSON file is an array of craft entries. Most entries use only a few fields, but more can be added when needed.
+
+
+Craft Entry Fields
+
+
+
makeID : number
+ The CreateEntry ID used when the player crafts the item. This must match a valid entry from the create system.
+
+
+
+
buttonID : number
+ Optional menu button ID used by crafting skills that need one visible row to map to different makeIDs based on the selected resource. Blacksmithing and Masonry commonly use this style.
+
+
+
+
dictID : number
+ Dictionary entry used for the menu text. This allows the crafting menu to use UOX3 dictionary files for display names.
+
+
+
+
label : text
+ A readable label for admins editing the JSON file. This is useful so the item can be identified without looking up the dictionary ID. It does not have to be the final in-game item name.
+
+
+
+
customName : text
+ Overrides the dictionary text shown in the crafting menu. Use this for custom items that do not have a dictionary entry.
+
+
+
+
page : number
+ The crafting page the entry appears on. Changing this value moves the item to another page in the crafting menu.
+
+
+
+
timerID : number
+ The page/timer value used when the crafting menu reopens after crafting or after a failed attempt.
+
+
+
+
skill : number
+ Optional skill ID override used by the item detail gump and menu logic. If omitted, the crafting script uses the default skill for that craft.
+
+
+
+
harvest : number array
+ Dictionary IDs used to show resource names in the item detail gump. This does not consume resources by itself; the actual resource requirements still come from the CreateEntry.
+
+
+
+
harvestNames : text array
+ Optional custom resource labels for the item detail gump. Use this when a resource name should be clearer than the dictionary entry.
+
+
+
+
recipeID : number
+ Optional recipe requirement. If set, the player must know the recipe before crafting the item.
+
+
+
+
minEra : text
+ Optional minimum shard era required before the entry appears or can be crafted.
+
+
+
+
maxEra : text
+ Optional maximum shard era allowed before the entry is hidden or blocked.
+
+
+
+
requiresGemTarget : true/false
+ Used by Tinkering jewelry entries that require the player to target a gem before crafting.
+
+
+
+
oreMake : number array
+ Used by Blacksmithing entries where one visible menu row can craft different CreateEntries based on the selected ore type.
+
+
+
+
graniteMake : number array
+ Used by Masonry entries where one visible menu row can craft different CreateEntries based on the selected granite type.
+
+
+
+
craftComplete : object
+ Optional post-crafting behavior. This lets the JSON entry request special handling after the item is successfully crafted.
+
+
+
+Era Restrictions
+Craft entries can be limited by shard era. This is useful for keeping newer content hidden on older-era shards.
+
+
+Era Restricted Entry
+
+
{
+ "makeID": 3018,
+ "label": "Glass Sword",
+ "customName": "glass sword",
+ "page": 2,
+ "timerID": 2,
+ "minEra": "sa",
+ "harvest": [13504]
+}
+
+
This entry only appears when the shard era is Stygian Abyss or later.
+
+
+Common era values include:
+t2a , uor , lbr , aos , se , ml , sa , hs , tol
+
+Recipe Locked Crafting
+An entry with a recipeID requires the player to know that recipe before the item can be crafted. This allows custom shards to add rare recipe scrolls or progression-based unlocks without changing the menu script.
+
+
+Recipe Locked Entry
+
+
{
+ "makeID": 3500,
+ "label": "Tall 18th Anniversary Vase",
+ "dictID": 14057,
+ "page": 1,
+ "timerID": 1,
+ "recipeID": 3500,
+ "minEra": "tol",
+ "harvest": [14011]
+}
+
+
This item requires recipe 3500 and only appears in Time of Legends or later.
+
+
+Custom Names
+If an item does not have a dictionary entry, or if a shard wants a different name shown in the menu, use customName .
+
+
+Custom Name Example
+
+
{
+ "makeID": 3012,
+ "label": "Hollow Prism",
+ "customName": "hollow prism",
+ "page": 1,
+ "timerID": 1,
+ "minEra": "ml",
+ "harvest": [13504]
+}
+
+
The menu will display hollow prism instead of looking up a dictionary entry.
+
+
+Item Detail Resources
+The harvest field controls what resource names are shown in the item detail gump. This is for display only. The real resource amounts and item IDs still come from the CreateEntry in the create system.
+
+
+Multiple Resource Display Example
+
+
{
+ "makeID": 281,
+ "label": "Potion Keg",
+ "dictID": 11946,
+ "page": 7,
+ "timerID": 7,
+ "harvest": [10642, 11861, 10612, 10928]
+}
+
+
This lets the item detail gump show multiple needed material labels, such as barrel parts and potion-related materials.
+
+
+
+Custom Resource Name Example
+
+
{
+ "makeID": 308,
+ "label": "Open Keg",
+ "dictID": 10642,
+ "page": 3,
+ "timerID": 3,
+ "harvest": [10611, 10612, 11860],
+ "harvestNames": ["Barrel Staves", "Barrel Lid", "Barrel Hoops"]
+}
+
+
Use harvestNames when the default dictionary label is not clear enough for players or admins.
+
+
+Post-Crafting Behavior
+Some crafted items need special behavior after they are made. The JSON system supports this through the craftComplete object.
+
+
+craftComplete Types
+
+
+
gemJewelry
+ Used for gem-based jewelry. The crafted jewelry is named after the gem used.
+
+
+
+
lockableContainer
+ Used for crafted containers that can receive a lock and key if the crafter has enough Tinkering skill.
+
+
+
+
autoIdentifyPotion
+ Used for alchemy potions. Crafted potions are automatically identified for the crafter.
+
+
+
+
craftedMap
+ Used for crafted maps. The map receives drawn map data based on the crafter's Cartography skill and position.
+
+
+
+
+craftComplete Example
+
+
{
+ "makeID": 243,
+ "label": "Bracelet",
+ "dictID": 11900,
+ "page": 5,
+ "timerID": 5,
+ "requiresGemTarget": true,
+ "harvest": [10015, 12005],
+ "craftComplete": { "type": "gemJewelry" }
+}
+
+
This entry crafts a bracelet, requires a gem target and uses the selected gem name as part of the crafted item's name.
+
+
+Blacksmithing and Masonry Resource Variants
+Some crafting menus have one visible menu row, but the final CreateEntry changes depending on the selected resource. Blacksmithing uses oreMake , while Masonry uses graniteMake .
+
+
+Resource Variant Example
+
+
{
+ "buttonID": 100,
+ "label": "Ringmail Gloves",
+ "dictID": 10289,
+ "page": 1,
+ "timerID": 1,
+ "oreMake": [1, 101, 201, 301, 401, 501, 601, 701, 801],
+ "harvest": [10015]
+}
+
+
The first value is used for iron, the second for dull copper, the third for shadow iron and so on. The resource order comes from resourcemap.json .
+
+
+Resource Map
+The file resourcemap.json defines resource sets used by the crafting menus. It controls the resource display name, item ID, hue, selected resource index and optional skill requirement for resource selection menus.
+
+
+Resource Map Example
+
+
{
+ "resourceSet": "ore",
+ "label": "Ore",
+ "tempTag": "ORE",
+ "defaultIndex": 0,
+ "items": [
+ { "index": 0, "label": "Iron", "dictID": 10291, "itemID": 7154, "hue": 0, "minSkill": 0 },
+ { "index": 1, "label": "Dull Copper", "dictID": 10203, "itemID": 7154, "hue": 2419, "minSkill": 650 },
+ { "index": 2, "label": "Shadow Iron", "dictID": 10204, "itemID": 7154, "hue": 2406, "minSkill": 700 }
+ ]
+}
+
+
This defines selectable ore types for crafting menus. The menu can count the player's matching resources by using the item ID and hue from the selected entry.
+
+
+
+Resource Map Fields
+
+
+
resourceSet : text
+ Unique name for this resource group. Examples: ore , granite , dragonScales , wood , tailoring .
+
+
+
+
label : text
+ Readable label for admins editing the resource map.
+
+
+
+
dictID : number
+ Optional default dictionary entry for the resource set.
+
+
+
+
tempTag : text
+ Temp tag used to remember the player's selected resource. Examples: ORE , Granite , Scale .
+
+
+
+
defaultIndex : number
+ Which resource index is used if the player has not selected anything yet.
+
+
+
+
items : array
+ List of resources in the set. Each item can define index , label , dictID , itemID , hue and minSkill .
+
+
+
+
itemID : number
+ Item ID used when counting the player's resources.
+
+
+
+
hue : number
+ Item hue used when counting the player's resources. Use 0 for normal uncolored resources.
+
+
+
+
minSkill : number
+ Optional skill requirement for selecting this resource. For ores this is usually Mining skill. For scales this can be Blacksmithing skill or left at 0.
+
+
+
+Grouped Resources
+Some resource sets count more than one item ID. For example, wood can count both logs and boards, and tailoring can count leather and hides. These grouped resources do not need an index for every item because the menu adds them together.
+
+
+Grouped Resource Example
+
+
{
+ "resourceSet": "wood",
+ "label": "Wood",
+ "dictID": 10687,
+ "items": [
+ { "label": "Logs", "itemID": 7136, "hue": 0 },
+ { "label": "Boards", "itemID": 7127, "hue": 0 }
+ ]
+}
+
+
This lets the crafting menu show one wood count made from both logs and boards.
+
+
+Adding a New Crafting Entry
+Most new craftables can be added with the following steps:
+
+ Add or confirm the item exists in the CreateEntry system.
+ Add a dictionary entry for the menu label, or decide on a customName .
+ Add a new entry to the correct crafting JSON file.
+ Set makeID , page and timerID .
+ Add harvest entries so the item detail gump shows useful material names.
+ Add optional fields such as recipeID , minEra , maxEra or craftComplete .
+ Reload scripts or restart UOX3, then test the crafting menu and item detail gump.
+
+
+
+Full Custom Item Example
+
+
{
+ "makeID": 5000,
+ "label": "Mythril Gearbox",
+ "customName": "mythril gearbox",
+ "page": 7,
+ "timerID": 7,
+ "recipeID": 5000,
+ "minEra": "ml",
+ "harvest": [10015, 11863],
+ "harvestNames": ["Mythril Ingots", "Gears"]
+}
+
+
This entry adds a custom item to a crafting page, gives it a custom menu name, requires a recipe, limits it to Mondain's Legacy or later and gives clearer resource names in the detail gump.
+
+
+Adding a New Resource
+To add a new selectable resource such as a new ore, granite or scale type, add a new item entry to the correct resource set in resourcemap.json . The new entry should have a unique index, display label, dictionary ID, item ID, hue and skill requirement.
+
+
+New Resource Example
+
+
{
+ "index": 9,
+ "label": "Mythril",
+ "dictID": 25000,
+ "itemID": 7154,
+ "hue": 2200,
+ "minSkill": 1000
+}
+
+
After adding this to the ore resource set, the ore selection menu can show Mythril and count Mythril ingots by item ID and hue.
+
+
+Important Notes
+
+ The JSON files control menu data, not the low-level item creation rules.
+ The actual crafted item, resource consumption, skill checks, sounds and delays still come from the CreateEntry system.
+ makeID , oreMake and graniteMake values must point to valid CreateEntries.
+ label is for admin readability. Use dictID or customName for menu display text.
+ craftComplete only supports behavior types that the crafting complete script knows how to handle.
+ If an entry does not appear, check its page , minEra , maxEra and recipe requirement.
+ If resource counts look wrong, check the itemID and hue in resourcemap.json .
+