diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 628ff2f7bd..cac20da97e 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -182,20 +182,6 @@ function Player:onLook(thing, position, distance) description = string.format("%s, Unique ID: %d", description, uniqueId) end - if thing:isContainer() then - local quickLootCategories = {} - local container = Container(thing.uid) - for categoryId = LOOT_START, LOOT_END do - if container ~= nil then - if container:hasQuickLootCategory(categoryId) then - table.insert(quickLootCategories, categoryId) - end - end - end - - description = string.format("%s, QuickLootCategory: (%s)", description, table.concat(quickLootCategories, ", ")) - end - local itemType = thing:getType() local transformEquipId = itemType:getTransformEquipId() @@ -436,16 +422,6 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, -- Handle move items to the ground if toPosition.x ~= CONTAINER_POSITION then - if item:isContainer() then - local container = Container(item.uid) - for categoryId = LOOT_START, LOOT_END do - if container:hasQuickLootCategory(categoryId) then - container:removeQuickLootCategory(categoryId) - self:setQuickLootBackpack(categoryId, nil) - end - end - end - return true end diff --git a/data/items/items.xml b/data/items/items.xml index f99fd6fbeb..74629370f5 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -427,7 +427,7 @@ - + @@ -954,6 +954,7 @@ + @@ -1295,6 +1296,7 @@ + @@ -1347,14 +1349,14 @@ - + - + @@ -1393,33 +1395,33 @@ - + - + - + - + - + - + @@ -1476,7 +1478,7 @@ - + @@ -1510,28 +1512,28 @@ - + - + - + - + - + @@ -1627,7 +1629,7 @@ - + @@ -1640,12 +1642,12 @@ - + - + @@ -1674,7 +1676,7 @@ - + @@ -1706,37 +1708,37 @@ - + - + - + - + - + - + - + @@ -1759,7 +1761,7 @@ - + @@ -1775,7 +1777,7 @@ - + @@ -1793,7 +1795,7 @@ - + @@ -1844,7 +1846,7 @@ - + @@ -1854,22 +1856,22 @@ - + - + - + - + @@ -1981,25 +1983,25 @@ - + - + - + - + @@ -2023,7 +2025,7 @@ - + @@ -2031,71 +2033,71 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2103,112 +2105,112 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2224,7 +2226,7 @@ - + @@ -2233,7 +2235,7 @@ - + @@ -2241,7 +2243,7 @@ - + @@ -2249,7 +2251,7 @@ - + @@ -2257,7 +2259,7 @@ - + @@ -2265,7 +2267,7 @@ - + @@ -2273,7 +2275,7 @@ - + @@ -2284,12 +2286,12 @@ - + - + @@ -2298,12 +2300,12 @@ - + - - + + @@ -2311,32 +2313,32 @@ - + - + - + - + - + - + @@ -2344,7 +2346,7 @@ - + @@ -2352,19 +2354,19 @@ - + - + - + @@ -2372,7 +2374,7 @@ - + @@ -2380,7 +2382,7 @@ - + @@ -2388,7 +2390,7 @@ - + @@ -2396,7 +2398,7 @@ - + @@ -2404,7 +2406,7 @@ - + @@ -2412,27 +2414,27 @@ - + - + - + - + - + @@ -2440,12 +2442,12 @@ - + - + @@ -2455,7 +2457,7 @@ - + @@ -2464,7 +2466,7 @@ - + @@ -2473,7 +2475,7 @@ - + @@ -2482,7 +2484,7 @@ - + @@ -2491,7 +2493,7 @@ - + @@ -2502,7 +2504,7 @@ - + @@ -2512,7 +2514,7 @@ - + @@ -2523,7 +2525,7 @@ - + @@ -2537,7 +2539,7 @@ - + @@ -2548,7 +2550,7 @@ - + @@ -2556,7 +2558,7 @@ - + @@ -2564,7 +2566,7 @@ - + @@ -2572,7 +2574,7 @@ - + @@ -2582,7 +2584,7 @@ - + @@ -2592,7 +2594,7 @@ - + @@ -2602,7 +2604,7 @@ - + @@ -2610,7 +2612,7 @@ - + @@ -2618,7 +2620,7 @@ - + @@ -2629,7 +2631,7 @@ - + @@ -2645,19 +2647,19 @@ - + - + - + - + @@ -2670,11 +2672,11 @@ - + - + @@ -2684,11 +2686,11 @@ - + - + @@ -2701,25 +2703,25 @@ - + - + - + - + @@ -2729,11 +2731,11 @@ - + - + @@ -2743,7 +2745,7 @@ - + @@ -2754,296 +2756,296 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3065,7 +3067,7 @@ - + @@ -3077,7 +3079,7 @@ - + @@ -3151,14 +3153,14 @@ - + - + @@ -3174,54 +3176,54 @@ - + - + - + - + - + - + - + - + @@ -3238,13 +3240,13 @@ - + - + @@ -3260,6 +3262,7 @@ + @@ -3281,7 +3284,7 @@ - + @@ -3308,7 +3311,7 @@ - + @@ -3317,7 +3320,7 @@ - + @@ -3325,7 +3328,7 @@ - + @@ -3333,21 +3336,21 @@ - + - + - + @@ -3355,7 +3358,7 @@ - + @@ -3363,7 +3366,7 @@ - + @@ -3372,7 +3375,7 @@ - + @@ -3380,7 +3383,7 @@ - + @@ -3388,14 +3391,14 @@ - + - + @@ -3403,13 +3406,13 @@ - + - + @@ -3420,7 +3423,7 @@ - + @@ -3428,7 +3431,7 @@ - + @@ -3439,7 +3442,7 @@ - + @@ -3448,7 +3451,7 @@ - + @@ -3457,14 +3460,14 @@ - + - + @@ -3473,7 +3476,7 @@ - + @@ -3482,20 +3485,20 @@ - + - + - + @@ -3506,7 +3509,7 @@ - + @@ -3515,7 +3518,7 @@ - + @@ -3523,24 +3526,24 @@ - + - + - + - + @@ -3551,7 +3554,7 @@ - + @@ -3559,7 +3562,7 @@ - + @@ -3569,7 +3572,7 @@ - + @@ -3577,7 +3580,7 @@ - + @@ -3585,7 +3588,7 @@ - + @@ -3596,14 +3599,14 @@ - + - + @@ -3611,7 +3614,7 @@ - + @@ -3620,7 +3623,7 @@ - + @@ -3630,7 +3633,7 @@ - + @@ -3640,33 +3643,33 @@ - - + + - + - + - + - + @@ -3678,7 +3681,7 @@ - + @@ -3687,14 +3690,14 @@ - + - + @@ -3704,7 +3707,7 @@ - + @@ -3713,7 +3716,7 @@ - + @@ -3721,7 +3724,7 @@ - + @@ -3729,7 +3732,7 @@ - + @@ -3738,14 +3741,14 @@ - + - + @@ -3754,7 +3757,7 @@ - + @@ -3764,7 +3767,7 @@ - + @@ -3775,7 +3778,7 @@ - + @@ -3784,7 +3787,7 @@ - + @@ -3794,14 +3797,14 @@ - + - + @@ -3811,7 +3814,7 @@ - + @@ -3820,14 +3823,14 @@ - + - + @@ -3835,14 +3838,14 @@ - + - + @@ -3850,10 +3853,10 @@ - + - + @@ -3864,7 +3867,7 @@ - + @@ -3874,7 +3877,7 @@ - + @@ -3884,7 +3887,7 @@ - + @@ -3892,7 +3895,7 @@ - + @@ -3900,7 +3903,7 @@ - + @@ -3910,7 +3913,7 @@ - + @@ -3918,21 +3921,21 @@ - + - + - + @@ -3942,7 +3945,7 @@ - + @@ -3952,7 +3955,7 @@ - + @@ -3961,14 +3964,14 @@ - + - + @@ -3977,7 +3980,7 @@ - + @@ -3986,229 +3989,229 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4216,33 +4219,33 @@ - + - + - + - + - + @@ -4250,14 +4253,14 @@ - + - + @@ -4265,20 +4268,20 @@ - + - + - + @@ -4286,7 +4289,7 @@ - + @@ -4294,14 +4297,14 @@ - + - + @@ -4309,70 +4312,70 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -4380,20 +4383,20 @@ - + - + - + @@ -4401,21 +4404,21 @@ - + - + - + @@ -4423,39 +4426,39 @@ - + - + - + - + - + - + @@ -4463,32 +4466,32 @@ - + - + - + - + - + @@ -4496,61 +4499,61 @@ - + - + - + - + - + - + - + - + - + @@ -4558,7 +4561,7 @@ - + @@ -4568,7 +4571,7 @@ - + @@ -4578,7 +4581,7 @@ - + @@ -4588,7 +4591,7 @@ - + @@ -4598,20 +4601,20 @@ - + - + - + @@ -4619,45 +4622,45 @@ - + - + - + - + - + - + - + - + @@ -4674,11 +4677,11 @@ - + - + @@ -4707,14 +4710,14 @@ - + - + @@ -4762,7 +4765,7 @@ - + @@ -4777,7 +4780,7 @@ - + @@ -4834,7 +4837,7 @@ - + @@ -4849,137 +4852,137 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4987,7 +4990,7 @@ - + @@ -4995,122 +4998,123 @@ - + - + - + - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -5120,15 +5124,15 @@ - + - + - + @@ -5173,18 +5177,18 @@ - + - + - + @@ -5199,7 +5203,7 @@ - + @@ -5231,27 +5235,27 @@ - + - + - + - + - + - + @@ -5259,11 +5263,11 @@ - + - + @@ -5271,31 +5275,31 @@ - + - + - + - + - + - + - + @@ -7755,22 +7759,22 @@ - + - + - + - + @@ -7789,7 +7793,7 @@ - + @@ -7799,14 +7803,14 @@ - + - + @@ -7815,7 +7819,7 @@ - + @@ -7823,14 +7827,14 @@ - + - + @@ -7840,69 +7844,69 @@ - + - + - + - + - + - + - + - + - + - + - - + + @@ -7913,13 +7917,13 @@ - + - + @@ -8569,7 +8573,7 @@ - + @@ -8581,7 +8585,7 @@ - + @@ -8603,7 +8607,7 @@ - + @@ -8671,7 +8675,7 @@ - + @@ -8750,7 +8754,7 @@ - + @@ -8758,7 +8762,7 @@ - + @@ -8830,14 +8834,14 @@ - + - + @@ -9161,14 +9165,14 @@ - + - + @@ -9201,7 +9205,7 @@ - + @@ -9406,7 +9410,7 @@ - + @@ -9516,7 +9520,7 @@ - + @@ -9529,7 +9533,7 @@ - + @@ -9570,7 +9574,7 @@ - + @@ -9627,7 +9631,7 @@ - + @@ -9693,7 +9697,7 @@ - + @@ -9702,11 +9706,11 @@ - + - + @@ -9719,7 +9723,7 @@ - + @@ -9730,7 +9734,7 @@ - + @@ -9738,7 +9742,7 @@ - + @@ -9755,21 +9759,21 @@ - + - + - + - + @@ -9800,135 +9804,135 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -9937,28 +9941,28 @@ - + - + - + - + - + @@ -9967,27 +9971,27 @@ - + - + - + - + - + - + @@ -9996,29 +10000,29 @@ - + - + - + - + - + - + @@ -10026,7 +10030,7 @@ - + @@ -10039,7 +10043,7 @@ - + @@ -10048,7 +10052,7 @@ - + @@ -10102,7 +10106,7 @@ - + @@ -10113,7 +10117,7 @@ - + @@ -10132,7 +10136,7 @@ - + @@ -10148,7 +10152,7 @@ - + @@ -10158,11 +10162,11 @@ - + - + @@ -11015,22 +11019,22 @@ - + - + - + - + @@ -11039,7 +11043,7 @@ - + @@ -11053,30 +11057,30 @@ - + - + - + - + - + - + @@ -11084,19 +11088,19 @@ - + - + - + - + @@ -11162,7 +11166,7 @@ - + @@ -11173,12 +11177,12 @@ - + - + @@ -11343,7 +11347,7 @@ - + @@ -11364,7 +11368,7 @@ - + @@ -11373,7 +11377,7 @@ - + @@ -11788,16 +11792,16 @@ - + - + - + @@ -11810,7 +11814,7 @@ - + @@ -11882,7 +11886,7 @@ - + @@ -11890,6 +11894,7 @@ + @@ -11903,7 +11908,7 @@ - + @@ -11913,24 +11918,26 @@ - + - + + + @@ -11943,6 +11950,7 @@ + @@ -11994,7 +12002,7 @@ - + @@ -12004,7 +12012,7 @@ - + @@ -12013,20 +12021,20 @@ - + - + - + @@ -12039,20 +12047,20 @@ - + - + - - + + - + @@ -12061,19 +12069,19 @@ - + - - + + - + @@ -12105,7 +12113,7 @@ - + @@ -12114,8 +12122,8 @@ - - + + @@ -12146,11 +12154,11 @@ - + - + @@ -12162,7 +12170,7 @@ - + @@ -12179,7 +12187,7 @@ - + @@ -12594,11 +12602,11 @@ - + - + @@ -12772,7 +12780,7 @@ - + @@ -12965,7 +12973,7 @@ - + @@ -13023,7 +13031,7 @@ - + @@ -13033,7 +13041,7 @@ - + @@ -13043,7 +13051,7 @@ - + @@ -13053,7 +13061,7 @@ - + @@ -13062,7 +13070,7 @@ - + @@ -13072,7 +13080,7 @@ - + @@ -13106,7 +13114,7 @@ - + @@ -13114,7 +13122,7 @@ - + @@ -13124,7 +13132,7 @@ - + @@ -13135,7 +13143,7 @@ - + @@ -13144,14 +13152,14 @@ - + - + @@ -13160,7 +13168,7 @@ - + @@ -13170,7 +13178,7 @@ - + @@ -13179,7 +13187,7 @@ - + @@ -13189,7 +13197,7 @@ - + @@ -13198,14 +13206,14 @@ - + - + @@ -13213,7 +13221,7 @@ - + @@ -13223,7 +13231,7 @@ - + @@ -13232,7 +13240,7 @@ - + @@ -13241,45 +13249,45 @@ - + - + - + - + - + - + - + - + - + - + @@ -13287,7 +13295,7 @@ - + @@ -13296,7 +13304,7 @@ - + @@ -13305,7 +13313,7 @@ - + @@ -13314,7 +13322,7 @@ - + @@ -13323,7 +13331,7 @@ - + @@ -13333,7 +13341,7 @@ - + @@ -13342,7 +13350,7 @@ - + @@ -13351,7 +13359,7 @@ - + @@ -13359,7 +13367,7 @@ - + @@ -13367,7 +13375,7 @@ - + @@ -13375,7 +13383,7 @@ - + @@ -13383,7 +13391,7 @@ - + @@ -13392,7 +13400,7 @@ - + @@ -13401,7 +13409,7 @@ - + @@ -13411,7 +13419,7 @@ - + @@ -13420,7 +13428,7 @@ - + @@ -13429,7 +13437,7 @@ - + @@ -13438,7 +13446,7 @@ - + @@ -13447,7 +13455,7 @@ - + @@ -13455,7 +13463,7 @@ - + @@ -13463,7 +13471,7 @@ - + @@ -13471,7 +13479,7 @@ - + @@ -13480,7 +13488,7 @@ - + @@ -13489,7 +13497,7 @@ - + @@ -13497,7 +13505,7 @@ - + @@ -13507,7 +13515,7 @@ - + @@ -13516,7 +13524,7 @@ - + @@ -13525,7 +13533,7 @@ - + @@ -13535,7 +13543,7 @@ - + @@ -13544,7 +13552,7 @@ - + @@ -13552,7 +13560,7 @@ - + @@ -13560,7 +13568,7 @@ - + @@ -13568,7 +13576,7 @@ - + @@ -13578,7 +13586,7 @@ - + @@ -13587,7 +13595,7 @@ - + @@ -13596,7 +13604,7 @@ - + @@ -13604,19 +13612,19 @@ - + - + - + @@ -13628,7 +13636,7 @@ - + @@ -13652,7 +13660,7 @@ - + @@ -13660,7 +13668,7 @@ - + @@ -13668,7 +13676,7 @@ - + @@ -13676,7 +13684,7 @@ - + @@ -13685,7 +13693,7 @@ - + @@ -13694,7 +13702,7 @@ - + @@ -13702,7 +13710,7 @@ - + @@ -13711,7 +13719,7 @@ - + @@ -13719,7 +13727,7 @@ - + @@ -13728,40 +13736,40 @@ - + - + - + - + - + - + @@ -13769,7 +13777,7 @@ - + @@ -13818,7 +13826,7 @@ - + @@ -13855,7 +13863,7 @@ - + @@ -13975,22 +13983,22 @@ - + - + - + - + @@ -13998,12 +14006,12 @@ - + - + @@ -14071,7 +14079,7 @@ - + @@ -14266,14 +14274,14 @@ - + - + @@ -14303,7 +14311,7 @@ - + @@ -14351,19 +14359,19 @@ - + - + - + @@ -14415,7 +14423,7 @@ - + @@ -14427,7 +14435,7 @@ - + @@ -14439,7 +14447,7 @@ - + @@ -14451,7 +14459,7 @@ - + @@ -14463,7 +14471,7 @@ - + @@ -14475,7 +14483,7 @@ - + @@ -14486,7 +14494,7 @@ - + @@ -14498,7 +14506,7 @@ - + @@ -14510,7 +14518,7 @@ - + @@ -14522,7 +14530,7 @@ - + @@ -14534,7 +14542,7 @@ - + @@ -14546,7 +14554,7 @@ - + @@ -14558,7 +14566,7 @@ - + @@ -14570,7 +14578,7 @@ - + @@ -14583,7 +14591,7 @@ - + @@ -14591,19 +14599,19 @@ - + - + - + - + @@ -14611,7 +14619,7 @@ - + @@ -14623,7 +14631,7 @@ - + @@ -14635,7 +14643,7 @@ - + @@ -14647,7 +14655,7 @@ - + @@ -14659,7 +14667,7 @@ - + @@ -14671,7 +14679,7 @@ - + @@ -14684,7 +14692,7 @@ - + @@ -14696,7 +14704,7 @@ - + @@ -14708,7 +14716,7 @@ - + @@ -14720,7 +14728,7 @@ - + @@ -14732,7 +14740,7 @@ - + @@ -14744,7 +14752,7 @@ - + @@ -14756,7 +14764,7 @@ - + @@ -14768,7 +14776,7 @@ - + @@ -14780,7 +14788,7 @@ - + @@ -15038,7 +15046,7 @@ - + @@ -15049,7 +15057,7 @@ - + @@ -15060,7 +15068,7 @@ - + @@ -15110,7 +15118,7 @@ - + @@ -15121,7 +15129,7 @@ - + @@ -15133,7 +15141,7 @@ - + @@ -15145,7 +15153,7 @@ - + @@ -15157,7 +15165,7 @@ - + @@ -15169,7 +15177,7 @@ - + @@ -15181,7 +15189,7 @@ - + @@ -15193,7 +15201,7 @@ - + @@ -15204,7 +15212,7 @@ - + @@ -15216,7 +15224,7 @@ - + @@ -15228,7 +15236,7 @@ - + @@ -15240,7 +15248,7 @@ - + @@ -15252,7 +15260,7 @@ - + @@ -15264,7 +15272,7 @@ - + @@ -15276,7 +15284,7 @@ - + @@ -15288,7 +15296,7 @@ - + @@ -15300,7 +15308,7 @@ - + @@ -15312,7 +15320,7 @@ - + @@ -15324,7 +15332,7 @@ - + @@ -15336,7 +15344,7 @@ - + @@ -15348,7 +15356,7 @@ - + @@ -15360,7 +15368,7 @@ - + @@ -15371,7 +15379,7 @@ - + @@ -15383,7 +15391,7 @@ - + @@ -15395,7 +15403,7 @@ - + @@ -15407,7 +15415,7 @@ - + @@ -15419,7 +15427,7 @@ - + @@ -15431,7 +15439,7 @@ - + @@ -15443,7 +15451,7 @@ - + @@ -15455,7 +15463,7 @@ - + @@ -15467,7 +15475,7 @@ - + @@ -15478,7 +15486,7 @@ - + @@ -15486,7 +15494,7 @@ - + @@ -15494,7 +15502,7 @@ - + @@ -15502,7 +15510,7 @@ - + @@ -15512,7 +15520,7 @@ - + @@ -15522,7 +15530,7 @@ - + @@ -15532,7 +15540,7 @@ - + @@ -15545,14 +15553,14 @@ - + - + @@ -15563,7 +15571,7 @@ - + @@ -15572,7 +15580,7 @@ - + @@ -15580,13 +15588,13 @@ - + - + @@ -15594,7 +15602,7 @@ - + @@ -15604,7 +15612,7 @@ - + @@ -15612,7 +15620,7 @@ - + @@ -15620,7 +15628,7 @@ - + @@ -15628,7 +15636,7 @@ - + @@ -15636,7 +15644,7 @@ - + @@ -15644,7 +15652,7 @@ - + @@ -15660,15 +15668,16 @@ + - + - + @@ -15741,7 +15750,7 @@ - + @@ -15765,7 +15774,7 @@ - + @@ -15786,7 +15795,7 @@ - + @@ -15794,7 +15803,7 @@ - + @@ -15950,7 +15959,7 @@ - + @@ -15986,13 +15995,13 @@ - + - + @@ -16059,16 +16068,16 @@ - + - + - + @@ -16076,26 +16085,26 @@ - + - + - + - + - + @@ -16115,11 +16124,11 @@ - + - + @@ -16198,12 +16207,12 @@ - + - + @@ -16353,14 +16362,14 @@ - + - + @@ -16370,7 +16379,7 @@ - + @@ -16461,7 +16470,7 @@ - + @@ -16469,7 +16478,7 @@ - + @@ -16513,10 +16522,10 @@ - + - + @@ -16589,13 +16598,13 @@ - + - + @@ -16604,41 +16613,41 @@ - + - + - + - + - + - + - + - + @@ -16655,7 +16664,7 @@ - + @@ -16665,7 +16674,7 @@ - + @@ -16675,7 +16684,7 @@ - + @@ -16686,7 +16695,7 @@ - + @@ -16697,7 +16706,7 @@ - + @@ -16707,7 +16716,7 @@ - + @@ -16718,7 +16727,7 @@ - + @@ -16729,7 +16738,7 @@ - + @@ -16738,7 +16747,7 @@ - + @@ -16748,13 +16757,13 @@ - + - + @@ -16772,7 +16781,7 @@ - + @@ -16781,13 +16790,13 @@ - + - + @@ -16799,7 +16808,7 @@ - + @@ -16807,12 +16816,12 @@ - + - + @@ -16820,39 +16829,39 @@ - + - + - + - + - + - + @@ -16860,7 +16869,7 @@ - + @@ -16868,7 +16877,7 @@ - + @@ -16876,7 +16885,7 @@ - + @@ -16884,7 +16893,7 @@ - + @@ -16893,12 +16902,12 @@ - + - + @@ -16911,7 +16920,7 @@ - + @@ -16920,12 +16929,12 @@ - + - + @@ -16933,7 +16942,7 @@ - + @@ -16941,7 +16950,7 @@ - + @@ -16949,7 +16958,7 @@ - + @@ -16960,20 +16969,20 @@ - + - + - + @@ -16981,7 +16990,7 @@ - + @@ -16993,7 +17002,7 @@ - + @@ -17002,7 +17011,7 @@ - + @@ -17011,7 +17020,7 @@ - + @@ -17019,7 +17028,7 @@ - + @@ -17027,21 +17036,21 @@ - + - + - + @@ -17051,7 +17060,7 @@ - + @@ -17060,7 +17069,7 @@ - + @@ -17070,7 +17079,7 @@ - + @@ -17082,7 +17091,7 @@ - + @@ -17091,7 +17100,7 @@ - + @@ -17100,7 +17109,7 @@ - + @@ -17111,7 +17120,7 @@ - + @@ -17120,7 +17129,7 @@ - + @@ -17129,7 +17138,7 @@ - + @@ -17138,7 +17147,7 @@ - + @@ -17147,14 +17156,14 @@ - + - + @@ -17163,7 +17172,7 @@ - + @@ -17172,7 +17181,7 @@ - + @@ -17181,7 +17190,7 @@ - + @@ -17189,7 +17198,7 @@ - + @@ -17198,7 +17207,7 @@ - + @@ -17207,7 +17216,7 @@ - + @@ -17216,7 +17225,7 @@ - + @@ -17225,7 +17234,7 @@ - + @@ -17452,7 +17461,7 @@ - + @@ -17485,7 +17494,7 @@ - + @@ -17506,6 +17515,7 @@ + @@ -17531,11 +17541,11 @@ - + - + @@ -17573,7 +17583,7 @@ - + @@ -17600,7 +17610,7 @@ - + @@ -17647,7 +17657,7 @@ - + @@ -17671,7 +17681,7 @@ - + @@ -17878,9 +17888,11 @@ + + @@ -18017,7 +18029,7 @@ - + @@ -18039,7 +18051,7 @@ - + @@ -18052,30 +18064,30 @@ - + - + - + - + - + - + @@ -18093,7 +18105,7 @@ - + @@ -18111,7 +18123,7 @@ - + @@ -18151,14 +18163,14 @@ - + - + @@ -18166,7 +18178,7 @@ - + @@ -18233,32 +18245,32 @@ - + - + - + - + - + - + @@ -18267,55 +18279,55 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -18571,7 +18583,7 @@ - + @@ -18579,11 +18591,11 @@ - + - + @@ -18596,7 +18608,7 @@ - + @@ -18609,7 +18621,7 @@ - + @@ -18644,12 +18656,12 @@ - + - + @@ -18741,28 +18753,28 @@ - + - + - + - + - + - + @@ -18797,7 +18809,7 @@ - + @@ -18959,7 +18971,7 @@ - + @@ -18974,13 +18986,13 @@ - + - + @@ -19140,7 +19152,7 @@ - + @@ -19412,7 +19424,7 @@ - + @@ -19423,7 +19435,7 @@ - + @@ -19434,7 +19446,7 @@ - + @@ -19444,7 +19456,7 @@ - + @@ -19546,7 +19558,7 @@ - + @@ -19556,14 +19568,14 @@ - + - + @@ -19572,7 +19584,7 @@ - + @@ -19580,14 +19592,14 @@ - + - + @@ -19595,7 +19607,7 @@ - + @@ -19605,7 +19617,7 @@ - + @@ -19613,7 +19625,7 @@ - + @@ -19621,7 +19633,7 @@ - + @@ -19629,7 +19641,7 @@ - + @@ -19637,7 +19649,7 @@ - + @@ -19645,7 +19657,7 @@ - + @@ -19654,7 +19666,7 @@ - + @@ -19662,7 +19674,7 @@ - + @@ -19671,7 +19683,7 @@ - + @@ -19686,7 +19698,7 @@ - + @@ -19694,14 +19706,14 @@ - + - + @@ -19723,13 +19735,13 @@ - + - + @@ -19743,21 +19755,21 @@ - + - + - + @@ -19800,7 +19812,7 @@ - + @@ -19809,7 +19821,7 @@ - + @@ -19929,16 +19941,16 @@ - + - + - + @@ -19952,7 +19964,7 @@ - + @@ -20004,7 +20016,7 @@ - + @@ -20015,7 +20027,7 @@ - + @@ -20043,7 +20055,7 @@ - + @@ -20053,7 +20065,7 @@ - + @@ -20083,12 +20095,12 @@ - + - + @@ -20096,92 +20108,92 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -20189,115 +20201,115 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -20309,7 +20321,7 @@ - + @@ -20554,7 +20566,7 @@ - + @@ -20602,7 +20614,7 @@ - + @@ -20619,7 +20631,7 @@ - + @@ -20628,7 +20640,7 @@ - + @@ -20718,7 +20730,7 @@ - + @@ -20728,52 +20740,52 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -20788,47 +20800,47 @@ - + - + - + - + - + - + - + - + - + - + @@ -20836,95 +20848,95 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -20935,7 +20947,7 @@ - + @@ -20962,7 +20974,7 @@ - + @@ -21025,27 +21037,27 @@ - + - + - + - + - + @@ -21196,14 +21208,14 @@ - + - + @@ -21211,7 +21223,7 @@ - + @@ -21219,7 +21231,7 @@ - + @@ -21227,7 +21239,7 @@ - + @@ -21236,7 +21248,7 @@ - + @@ -21244,7 +21256,7 @@ - + @@ -21253,7 +21265,7 @@ - + @@ -21261,7 +21273,7 @@ - + @@ -21290,11 +21302,11 @@ - + - + @@ -21326,11 +21338,11 @@ - + - + @@ -21338,72 +21350,72 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -21461,7 +21473,7 @@ - + @@ -21469,7 +21481,7 @@ - + @@ -21501,7 +21513,7 @@ - + @@ -21530,11 +21542,11 @@ - + - + @@ -21542,37 +21554,37 @@ - + - + - - + + - + - + - + - + @@ -21605,7 +21617,7 @@ - + @@ -22005,7 +22017,7 @@ - + @@ -22041,74 +22053,74 @@ - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + @@ -22119,7 +22131,7 @@ - + @@ -22127,164 +22139,164 @@ - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + @@ -22307,7 +22319,7 @@ - + @@ -22318,7 +22330,7 @@ - + @@ -22335,14 +22347,14 @@ - + - + @@ -22391,7 +22403,7 @@ - + @@ -22616,12 +22628,12 @@ - + - + @@ -22653,30 +22665,30 @@ - + - + - + - + - + - + @@ -22704,15 +22716,15 @@ - + - + - + @@ -22722,7 +22734,7 @@ - + @@ -22747,16 +22759,16 @@ - + - + - + @@ -22768,7 +22780,7 @@ - + @@ -22778,7 +22790,7 @@ - + @@ -22786,7 +22798,7 @@ - + @@ -22795,7 +22807,7 @@ - + @@ -22803,7 +22815,7 @@ - + @@ -22813,7 +22825,7 @@ - + @@ -22822,14 +22834,14 @@ - + - + @@ -22838,12 +22850,12 @@ - + - + @@ -22864,21 +22876,21 @@ - + - + - + - + @@ -22897,7 +22909,7 @@ - + @@ -23036,7 +23048,7 @@ - + @@ -23069,15 +23081,15 @@ - + - + - + @@ -23119,7 +23131,7 @@ - + @@ -23190,7 +23202,7 @@ - + @@ -23282,7 +23294,7 @@ - + @@ -23340,17 +23352,17 @@ - + - + - + @@ -23359,55 +23371,55 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -23647,7 +23659,7 @@ - + @@ -23677,11 +23689,11 @@ - + - + @@ -23815,7 +23827,7 @@ - + @@ -23825,17 +23837,17 @@ - + - + - + @@ -23844,47 +23856,47 @@ - + - + - + - + - + - + - + - + - + @@ -23933,7 +23945,7 @@ - + @@ -24078,14 +24090,14 @@ - + - + - + @@ -24096,7 +24108,7 @@ - + @@ -24178,7 +24190,7 @@ - + @@ -24192,7 +24204,7 @@ - + @@ -24234,7 +24246,7 @@ - + @@ -24296,7 +24308,7 @@ - + @@ -24305,7 +24317,7 @@ - + @@ -24314,7 +24326,7 @@ - + @@ -24324,7 +24336,7 @@ - + @@ -24338,15 +24350,15 @@ - + - + - + @@ -24356,7 +24368,7 @@ - + @@ -24364,7 +24376,7 @@ - + @@ -24399,38 +24411,38 @@ - + - + - + - + - + - + - + @@ -24643,12 +24655,12 @@ - + - + @@ -24720,7 +24732,7 @@ - + @@ -24781,7 +24793,7 @@ - + @@ -25247,7 +25259,7 @@ - + @@ -25332,7 +25344,7 @@ - + @@ -25341,7 +25353,7 @@ - + @@ -25350,7 +25362,7 @@ - + @@ -25359,7 +25371,7 @@ - + @@ -25369,7 +25381,7 @@ - + @@ -25381,13 +25393,13 @@ - + - + @@ -25395,7 +25407,7 @@ - + @@ -25405,7 +25417,7 @@ - + @@ -25414,7 +25426,7 @@ - + @@ -25424,7 +25436,7 @@ - + @@ -25432,7 +25444,7 @@ - + @@ -25460,48 +25472,48 @@ - + - - + + - + - + - + - + - + - + - + - + @@ -25516,17 +25528,17 @@ - + - + - + @@ -25588,18 +25600,18 @@ - + - + - + @@ -25607,11 +25619,11 @@ - + - + @@ -25638,35 +25650,35 @@ - + - + - + - + - + - + - + - + @@ -25674,14 +25686,14 @@ - + - + @@ -25689,7 +25701,7 @@ - + @@ -25697,7 +25709,7 @@ - + @@ -25705,7 +25717,7 @@ - + @@ -25734,7 +25746,7 @@ - + @@ -25748,7 +25760,7 @@ - + @@ -25786,7 +25798,7 @@ - + @@ -25816,7 +25828,7 @@ - + @@ -25834,7 +25846,7 @@ - + @@ -25845,7 +25857,7 @@ - + @@ -25854,14 +25866,14 @@ - + - + @@ -25870,7 +25882,7 @@ - + @@ -25878,7 +25890,7 @@ - + @@ -25888,7 +25900,7 @@ - + @@ -25904,7 +25916,7 @@ - + @@ -25912,17 +25924,17 @@ - + - + - + @@ -25989,11 +26001,11 @@ - + - + @@ -26038,7 +26050,7 @@ - + @@ -26050,13 +26062,13 @@ - + - + @@ -26593,7 +26605,7 @@ - + @@ -26651,7 +26663,7 @@ - + @@ -26661,7 +26673,7 @@ - + @@ -26858,7 +26870,7 @@ - + @@ -26875,7 +26887,7 @@ - + @@ -26889,19 +26901,19 @@ - + - + - + @@ -26910,7 +26922,7 @@ - + @@ -26919,7 +26931,7 @@ - + @@ -26930,7 +26942,7 @@ - + @@ -26938,7 +26950,7 @@ - + @@ -26949,7 +26961,7 @@ - + @@ -26958,7 +26970,7 @@ - + @@ -26966,7 +26978,7 @@ - + @@ -26974,7 +26986,7 @@ - + @@ -26983,7 +26995,7 @@ - + @@ -26993,7 +27005,7 @@ - + @@ -27005,7 +27017,7 @@ - + @@ -27015,7 +27027,7 @@ - + @@ -27023,7 +27035,7 @@ - + @@ -27032,97 +27044,97 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -27130,7 +27142,7 @@ - + @@ -27140,7 +27152,7 @@ - + @@ -27151,7 +27163,7 @@ - + @@ -27219,7 +27231,7 @@ - + @@ -27228,7 +27240,7 @@ - + @@ -27236,7 +27248,7 @@ - + @@ -27247,7 +27259,7 @@ - + @@ -27256,7 +27268,7 @@ - + @@ -27266,7 +27278,7 @@ - + @@ -27274,7 +27286,7 @@ - + @@ -27303,7 +27315,7 @@ - + @@ -27423,11 +27435,11 @@ - + - + @@ -27512,7 +27524,7 @@ - + @@ -27634,24 +27646,24 @@ - + - + - + - + @@ -28015,31 +28027,31 @@ - + - + - + - + - + - + @@ -28357,14 +28369,14 @@ - + - + @@ -28372,7 +28384,7 @@ - + @@ -28380,38 +28392,38 @@ - + - + - + - + - + - + - + - + @@ -28419,18 +28431,18 @@ - + - + - + - + @@ -28438,77 +28450,77 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -28520,7 +28532,7 @@ - + @@ -30888,38 +30900,38 @@ - + - + - + - + - + - + - + @@ -30933,7 +30945,7 @@ - + @@ -30942,12 +30954,12 @@ - + - + @@ -31264,24 +31276,24 @@ - + - + - + - + - + @@ -31447,11 +31459,11 @@ - + - + @@ -31539,11 +31551,11 @@ - + - + @@ -31640,12 +31652,12 @@ - + - + @@ -31654,7 +31666,7 @@ - + @@ -31784,7 +31796,7 @@ - + @@ -31796,7 +31808,7 @@ - + @@ -31804,7 +31816,7 @@ - + @@ -31812,11 +31824,11 @@ - + - + @@ -31824,7 +31836,7 @@ - + @@ -31836,12 +31848,12 @@ - + - + @@ -31849,17 +31861,17 @@ - + - + - + @@ -31867,7 +31879,7 @@ - + @@ -31876,7 +31888,7 @@ - + @@ -31897,7 +31909,7 @@ - + @@ -32035,7 +32047,7 @@ - + @@ -32091,11 +32103,11 @@ - + - + @@ -32103,7 +32115,7 @@ - + @@ -32111,7 +32123,7 @@ - + @@ -32120,7 +32132,7 @@ - + @@ -32130,7 +32142,7 @@ - + @@ -32138,7 +32150,7 @@ - + @@ -32147,7 +32159,7 @@ - + @@ -32158,7 +32170,7 @@ - + @@ -32166,7 +32178,7 @@ - + @@ -32175,7 +32187,7 @@ - + @@ -32185,7 +32197,7 @@ - + @@ -32193,7 +32205,7 @@ - + @@ -32202,7 +32214,7 @@ - + @@ -32213,7 +32225,7 @@ - + @@ -32221,7 +32233,7 @@ - + @@ -32230,7 +32242,7 @@ - + @@ -32240,7 +32252,7 @@ - + @@ -32248,7 +32260,7 @@ - + @@ -32257,7 +32269,7 @@ - + @@ -32267,7 +32279,7 @@ - + @@ -32277,7 +32289,7 @@ - + @@ -32288,7 +32300,7 @@ - + @@ -32300,7 +32312,7 @@ - + @@ -32310,7 +32322,7 @@ - + @@ -32321,7 +32333,7 @@ - + @@ -32338,7 +32350,7 @@ - + @@ -32349,7 +32361,7 @@ - + @@ -32361,7 +32373,7 @@ - + @@ -32434,7 +32446,7 @@ - + @@ -32625,18 +32637,18 @@ - + - + - + @@ -32661,49 +32673,49 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -32827,7 +32839,7 @@ - + @@ -32849,16 +32861,16 @@ - + - + - + @@ -32874,7 +32886,7 @@ - + @@ -32968,7 +32980,7 @@ - + @@ -33450,7 +33462,7 @@ - + @@ -33548,19 +33560,19 @@ - + - + - + - + @@ -33587,7 +33599,7 @@ - + @@ -33601,7 +33613,7 @@ - + @@ -33609,7 +33621,7 @@ - + @@ -33618,28 +33630,28 @@ - + - + - + - + @@ -33647,21 +33659,21 @@ - + - + - + @@ -33669,7 +33681,7 @@ - + @@ -33677,20 +33689,20 @@ - + - + - + @@ -33699,14 +33711,14 @@ - + - + @@ -33716,7 +33728,7 @@ - + @@ -33726,19 +33738,19 @@ - + - + - + @@ -33778,51 +33790,51 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -33852,14 +33864,14 @@ - + - + @@ -33881,7 +33893,7 @@ - + @@ -33938,27 +33950,27 @@ - + - + - + - + @@ -33996,7 +34008,7 @@ - + @@ -34213,7 +34225,7 @@ - + @@ -34222,7 +34234,7 @@ - + @@ -34346,7 +34358,7 @@ - + @@ -34366,7 +34378,7 @@ - + @@ -34426,11 +34438,11 @@ - + - + @@ -34455,11 +34467,11 @@ - + - + @@ -34668,7 +34680,7 @@ - + @@ -34704,11 +34716,11 @@ - + - + @@ -34718,11 +34730,11 @@ - + - + @@ -34738,11 +34750,11 @@ - + - + @@ -34835,7 +34847,7 @@ - + @@ -34883,11 +34895,11 @@ - + - + @@ -34917,7 +34929,7 @@ - + @@ -35071,7 +35083,7 @@ - + @@ -35193,37 +35205,37 @@ - + - + - + - + - + - + - + @@ -35240,7 +35252,7 @@ - + @@ -35342,20 +35354,20 @@ - + - + - + @@ -35364,7 +35376,7 @@ - + @@ -35372,7 +35384,7 @@ - + @@ -35381,7 +35393,7 @@ - + @@ -35421,15 +35433,15 @@ - + - + - + @@ -35468,7 +35480,7 @@ - + @@ -35480,7 +35492,7 @@ - + @@ -35492,7 +35504,7 @@ - + @@ -35504,7 +35516,7 @@ - + @@ -35512,7 +35524,7 @@ - + @@ -35533,7 +35545,7 @@ - + @@ -35546,7 +35558,7 @@ - + @@ -35558,7 +35570,7 @@ - + @@ -35570,7 +35582,7 @@ - + @@ -35588,7 +35600,7 @@ - + @@ -35604,7 +35616,7 @@ - + @@ -35739,7 +35751,7 @@ - + @@ -35747,7 +35759,7 @@ - + @@ -35758,36 +35770,36 @@ - + - + - + - - + + - + - + - + @@ -35795,22 +35807,22 @@ - + - + - + - + @@ -35818,7 +35830,7 @@ - + @@ -35898,7 +35910,7 @@ - + @@ -35911,7 +35923,7 @@ - + @@ -35920,7 +35932,7 @@ - + @@ -35929,7 +35941,7 @@ - + @@ -35938,7 +35950,7 @@ - + @@ -35947,7 +35959,7 @@ - + @@ -35956,7 +35968,7 @@ - + @@ -35965,7 +35977,7 @@ - + @@ -35974,7 +35986,7 @@ - + @@ -35983,7 +35995,7 @@ - + @@ -35992,7 +36004,7 @@ - + @@ -36001,7 +36013,7 @@ - + @@ -36010,7 +36022,7 @@ - + @@ -36019,7 +36031,7 @@ - + @@ -36028,7 +36040,7 @@ - + @@ -36037,7 +36049,7 @@ - + @@ -36046,7 +36058,7 @@ - + @@ -36055,7 +36067,7 @@ - + @@ -36064,7 +36076,7 @@ - + @@ -36073,7 +36085,7 @@ - + @@ -36082,7 +36094,7 @@ - + @@ -36279,7 +36291,7 @@ - + @@ -36292,14 +36304,14 @@ - + - + @@ -36308,20 +36320,20 @@ - + - + - + - + @@ -36395,7 +36407,7 @@ - + @@ -36403,14 +36415,14 @@ - + - + @@ -36418,13 +36430,13 @@ - + - + @@ -36432,7 +36444,7 @@ - + @@ -36441,7 +36453,7 @@ - + @@ -36455,7 +36467,7 @@ - + @@ -36467,13 +36479,13 @@ - + - + @@ -36481,7 +36493,7 @@ - + @@ -36491,17 +36503,17 @@ - + - + - + @@ -36510,7 +36522,7 @@ - + @@ -36523,7 +36535,7 @@ - + @@ -36667,7 +36679,7 @@ - + @@ -36678,7 +36690,7 @@ - + @@ -36748,7 +36760,7 @@ - + @@ -36757,7 +36769,7 @@ - + @@ -36767,7 +36779,7 @@ - + @@ -36776,7 +36788,7 @@ - + @@ -36786,7 +36798,7 @@ - + @@ -36795,7 +36807,7 @@ - + @@ -36805,7 +36817,7 @@ - + @@ -36817,7 +36829,7 @@ - + @@ -36828,7 +36840,7 @@ - + @@ -36837,7 +36849,7 @@ - + @@ -36877,7 +36889,7 @@ - + @@ -36887,7 +36899,7 @@ - + @@ -36897,7 +36909,7 @@ - + @@ -36906,7 +36918,7 @@ - + @@ -36916,7 +36928,7 @@ - + @@ -36925,7 +36937,7 @@ - + @@ -36935,7 +36947,7 @@ - + @@ -36947,7 +36959,7 @@ - + @@ -36958,7 +36970,7 @@ - + @@ -36967,7 +36979,7 @@ - + @@ -36977,7 +36989,7 @@ - + @@ -36986,7 +36998,7 @@ - + @@ -36996,7 +37008,7 @@ - + @@ -37005,7 +37017,7 @@ - + @@ -37015,7 +37027,7 @@ - + @@ -37024,7 +37036,7 @@ - + @@ -37034,7 +37046,7 @@ - + @@ -37046,7 +37058,7 @@ - + @@ -37057,7 +37069,7 @@ - + @@ -37066,7 +37078,7 @@ - + @@ -37109,17 +37121,17 @@ - + - + - + @@ -37429,7 +37441,7 @@ - + @@ -37441,7 +37453,7 @@ - + @@ -37459,7 +37471,7 @@ - + @@ -37477,7 +37489,7 @@ - + @@ -37502,7 +37514,7 @@ - + @@ -37543,52 +37555,52 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -37596,58 +37608,58 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -37659,7 +37671,7 @@ - + @@ -37671,7 +37683,7 @@ - + @@ -37686,7 +37698,7 @@ - + @@ -37700,7 +37712,7 @@ - + @@ -37708,7 +37720,7 @@ - + @@ -37720,7 +37732,7 @@ - + @@ -37728,7 +37740,7 @@ - + @@ -37743,7 +37755,7 @@ - + @@ -37751,7 +37763,7 @@ - + @@ -37763,7 +37775,7 @@ - + @@ -37786,7 +37798,7 @@ - + @@ -37794,7 +37806,7 @@ - + @@ -37802,13 +37814,13 @@ - + - + @@ -37913,17 +37925,17 @@ - + - + - + @@ -38123,7 +38135,7 @@ - + @@ -38182,7 +38194,7 @@ - + @@ -38232,63 +38244,63 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -38310,38 +38322,38 @@ - + - + - + - + - + - + @@ -38456,7 +38468,7 @@ - + @@ -38471,27 +38483,27 @@ - + - + - + - + - + - + @@ -38501,7 +38513,7 @@ - + @@ -38518,18 +38530,18 @@ - + - + - + @@ -38537,60 +38549,60 @@ - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + @@ -38796,7 +38808,7 @@ - + @@ -39146,13 +39158,13 @@ - + - + @@ -39220,7 +39232,7 @@ - + @@ -39300,48 +39312,48 @@ - + - + - + - + - + - + - + - + - + @@ -39352,14 +39364,14 @@ - + - + @@ -39371,7 +39383,7 @@ - + @@ -39442,7 +39454,7 @@ - + @@ -39450,33 +39462,33 @@ - + - + - + - + - + - + @@ -39493,7 +39505,7 @@ - + @@ -39504,7 +39516,7 @@ - + @@ -39519,7 +39531,7 @@ - + @@ -39530,7 +39542,7 @@ - + @@ -39551,7 +39563,7 @@ - + @@ -39899,420 +39911,420 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -40335,7 +40347,7 @@ - + @@ -40349,126 +40361,126 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -40476,7 +40488,7 @@ - + @@ -40484,7 +40496,7 @@ - + @@ -40492,7 +40504,7 @@ - + @@ -40500,7 +40512,7 @@ - + @@ -40508,7 +40520,7 @@ - + @@ -40516,175 +40528,175 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -40692,7 +40704,7 @@ - + @@ -40700,7 +40712,7 @@ - + @@ -40708,7 +40720,7 @@ - + @@ -40716,7 +40728,7 @@ - + @@ -40724,7 +40736,7 @@ - + @@ -40732,175 +40744,175 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -40908,7 +40920,7 @@ - + @@ -40916,7 +40928,7 @@ - + @@ -40924,7 +40936,7 @@ - + @@ -40932,7 +40944,7 @@ - + @@ -40940,7 +40952,7 @@ - + @@ -40948,49 +40960,49 @@ - + - + - + - + - + - + - + @@ -41035,7 +41047,7 @@ - + @@ -41321,25 +41333,25 @@ - + - + - + - + @@ -41389,7 +41401,7 @@ - + @@ -41399,7 +41411,7 @@ - + @@ -41443,7 +41455,7 @@ - + @@ -41451,7 +41463,7 @@ - + @@ -41460,7 +41472,7 @@ - + @@ -41468,7 +41480,7 @@ - + @@ -41477,7 +41489,7 @@ - + @@ -41485,14 +41497,14 @@ - + - + @@ -41503,7 +41515,7 @@ - + @@ -41514,7 +41526,7 @@ - + @@ -41523,7 +41535,7 @@ - + @@ -41540,11 +41552,11 @@ - + - + @@ -41655,10 +41667,10 @@ - + - + @@ -41668,10 +41680,10 @@ - + - + @@ -41795,7 +41807,7 @@ - + @@ -41807,121 +41819,121 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -41976,7 +41988,7 @@ - + @@ -41987,7 +41999,7 @@ - + @@ -41998,7 +42010,7 @@ - + @@ -42007,7 +42019,7 @@ - + @@ -42018,7 +42030,7 @@ - + @@ -42033,36 +42045,36 @@ - + - + - + - + - + - + @@ -42408,7 +42420,7 @@ - + @@ -42645,8 +42657,8 @@ - - + + @@ -42666,7 +42678,7 @@ - + @@ -42675,7 +42687,7 @@ - + @@ -42686,7 +42698,7 @@ - + @@ -42696,7 +42708,7 @@ - + @@ -42706,7 +42718,7 @@ - + @@ -42719,7 +42731,7 @@ - + @@ -42728,7 +42740,7 @@ - + @@ -42740,7 +42752,7 @@ - + @@ -42749,7 +42761,7 @@ - + @@ -42758,7 +42770,7 @@ - + @@ -42769,7 +42781,7 @@ - + @@ -42781,7 +42793,7 @@ - + @@ -42875,7 +42887,7 @@ - + @@ -43054,17 +43066,17 @@ - + - + - + @@ -43141,26 +43153,26 @@ - + - + - + - + - + @@ -43170,7 +43182,7 @@ - + @@ -43180,7 +43192,7 @@ - + @@ -43191,7 +43203,7 @@ - + @@ -43202,7 +43214,7 @@ - + @@ -43213,7 +43225,7 @@ - + @@ -43246,7 +43258,7 @@ - + @@ -43364,7 +43376,7 @@ - + @@ -43386,15 +43398,15 @@ - + - + - + @@ -43624,7 +43636,7 @@ - + @@ -43668,30 +43680,30 @@ - + - + - + - + - + - + @@ -44350,12 +44362,12 @@ - + - + @@ -44473,7 +44485,7 @@ - + @@ -44507,7 +44519,7 @@ - + @@ -44553,19 +44565,19 @@ - + - + - + - + @@ -44577,7 +44589,7 @@ - + @@ -44590,7 +44602,7 @@ - + @@ -44600,7 +44612,7 @@ - + @@ -44610,7 +44622,7 @@ - + @@ -44620,7 +44632,7 @@ - + @@ -44631,7 +44643,7 @@ - + @@ -44642,7 +44654,7 @@ - + @@ -44651,7 +44663,7 @@ - + @@ -44659,7 +44671,7 @@ - + @@ -44670,7 +44682,7 @@ - + @@ -44679,7 +44691,7 @@ - + @@ -44688,17 +44700,17 @@ - + - + - + - + @@ -44709,7 +44721,7 @@ - + @@ -44724,23 +44736,23 @@ - + - + - + - + - + @@ -44790,7 +44802,7 @@ - + @@ -44800,7 +44812,7 @@ - + @@ -44970,28 +44982,29 @@ - + - + - + - + - + - - + + + @@ -45113,7 +45126,7 @@ - + @@ -45121,7 +45134,7 @@ - + @@ -45129,7 +45142,7 @@ - + @@ -45141,11 +45154,11 @@ - + - + @@ -45372,22 +45385,22 @@ - + - + - + - + @@ -45438,7 +45451,7 @@ - + @@ -45480,7 +45493,7 @@ - + @@ -45494,7 +45507,7 @@ - + @@ -45503,7 +45516,7 @@ - + @@ -45517,7 +45530,7 @@ - + @@ -45614,35 +45627,35 @@ - + - + - + - + - + - + - + - + @@ -45652,25 +45665,25 @@ - - + + - + - + - + @@ -45680,15 +45693,15 @@ - + - + - + @@ -45763,19 +45776,19 @@ - + - + - + - + @@ -45786,37 +45799,37 @@ - + - + - + - + - + - + - + - + @@ -45824,16 +45837,16 @@ - + - + - + @@ -45901,32 +45914,32 @@ - + - + - + - + - + - + - + @@ -45934,11 +45947,11 @@ - + - + @@ -45977,7 +45990,7 @@ - + @@ -46078,7 +46091,7 @@ - + @@ -46090,7 +46103,7 @@ - + @@ -46101,7 +46114,7 @@ - + @@ -46112,7 +46125,7 @@ - + @@ -46121,7 +46134,7 @@ - + @@ -46135,7 +46148,7 @@ - + @@ -46143,7 +46156,7 @@ - + @@ -46154,7 +46167,7 @@ - + @@ -46164,7 +46177,7 @@ - + @@ -46172,7 +46185,7 @@ - + @@ -46182,7 +46195,7 @@ - + @@ -46642,7 +46655,7 @@ - + @@ -46818,7 +46831,7 @@ - + @@ -46840,11 +46853,11 @@ - + - + @@ -46869,15 +46882,15 @@ - + - + - + @@ -46898,7 +46911,7 @@ - + @@ -46936,7 +46949,7 @@ - + @@ -47156,7 +47169,7 @@ - + @@ -47166,59 +47179,59 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -47335,7 +47348,7 @@ - + @@ -47570,7 +47583,7 @@ - + @@ -47579,23 +47592,23 @@ - + - + - + - - + + - + @@ -47619,19 +47632,19 @@ - + - + - + - + @@ -47639,7 +47652,7 @@ - + @@ -47652,7 +47665,7 @@ - + @@ -47662,7 +47675,7 @@ - + @@ -47671,7 +47684,7 @@ - + @@ -47682,7 +47695,7 @@ - + @@ -47695,7 +47708,7 @@ - + @@ -47706,7 +47719,7 @@ - + @@ -47720,35 +47733,35 @@ - + - + - + - + - + - + - + - + @@ -47808,7 +47821,7 @@ - + @@ -47822,7 +47835,7 @@ - + @@ -47833,7 +47846,7 @@ - + @@ -47853,7 +47866,7 @@ - + @@ -47861,21 +47874,21 @@ - + - + - + - + @@ -47884,7 +47897,7 @@ - + @@ -48097,7 +48110,7 @@ - + @@ -48227,7 +48240,7 @@ - + @@ -48249,11 +48262,11 @@ - + - + @@ -48276,7 +48289,7 @@ - + @@ -48429,7 +48442,7 @@ - + @@ -48480,7 +48493,7 @@ - + @@ -48547,7 +48560,7 @@ - + @@ -48559,52 +48572,52 @@ - + - + - + - + - + - + - + - + @@ -48657,7 +48670,7 @@ - + @@ -49012,7 +49025,7 @@ - + @@ -49021,7 +49034,7 @@ - + @@ -49043,7 +49056,7 @@ - + @@ -49127,7 +49140,7 @@ - + @@ -49136,7 +49149,7 @@ - + @@ -49148,7 +49161,7 @@ - + @@ -49156,7 +49169,7 @@ - + @@ -49164,42 +49177,42 @@ - + - + - + - + - + - + - + - + @@ -49207,22 +49220,22 @@ - + - + - + - + @@ -49232,7 +49245,7 @@ - + @@ -49243,7 +49256,7 @@ - + @@ -49287,7 +49300,7 @@ - + @@ -49326,17 +49339,17 @@ - + - + - + @@ -49513,22 +49526,22 @@ - + - + - + - + @@ -49628,259 +49641,613 @@ + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + - + + + + + + + - + - + - + - + + + + + + - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + + + + + + - + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -49900,10 +50267,10 @@ - + - + @@ -49912,17 +50279,18 @@ - + - + + - + @@ -49940,20 +50308,20 @@ + - + - + - + - @@ -49964,56 +50332,177 @@ - + - + + + + - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + + + + + + + + + - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -50022,9 +50511,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -50032,7 +50546,7 @@ - + @@ -50043,7 +50557,7 @@ - + @@ -50075,8 +50589,9 @@ - + + @@ -50088,7 +50603,7 @@ - + @@ -50101,7 +50616,7 @@ - + @@ -50116,7 +50631,7 @@ - + @@ -50129,22 +50644,22 @@ - + - + - + @@ -50157,14 +50672,14 @@ - + - + @@ -50177,7 +50692,7 @@ - + @@ -50189,7 +50704,7 @@ - + @@ -50201,7 +50716,7 @@ - + @@ -50229,7 +50744,7 @@ - + @@ -50239,7 +50754,7 @@ - + @@ -50248,7 +50763,7 @@ - + @@ -50265,7 +50780,7 @@ - + @@ -50275,12 +50790,12 @@ - + - + @@ -50311,119 +50826,129 @@ + + + + + - + + - + - + - + - + - + + - + - + - + - + - - + + + - + - + - + - + - - + + + - + - + - + - + - - + + + - + - + - + - + @@ -50480,7 +51005,7 @@ - + @@ -50492,7 +51017,7 @@ - + @@ -50503,7 +51028,7 @@ - + @@ -50519,12 +51044,12 @@ - + - + @@ -50537,7 +51062,7 @@ - + @@ -50548,12 +51073,12 @@ - + - + @@ -50569,7 +51094,7 @@ - + @@ -50591,6 +51116,19 @@ + + + + + + + + + + + + + @@ -50683,14 +51221,154 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -50714,6 +51392,11 @@ + + + + + @@ -50721,7 +51404,7 @@ - + @@ -50732,18 +51415,24 @@ - + + + + + + + - + @@ -50764,4 +51453,4 @@ - \ No newline at end of file + diff --git a/data/migrations/3.lua b/data/migrations/3.lua index f772d9b389..f67c4210dd 100644 --- a/data/migrations/3.lua +++ b/data/migrations/3.lua @@ -5,6 +5,5 @@ function onUpdateDatabase() ALTER TABLE `prey_slots` ADD `tick` smallint(3) NOT NULL DEFAULT '0'; ]]) - return true end diff --git a/data/migrations/5.lua b/data/migrations/5.lua index 2c7477d680..b80badf395 100644 --- a/data/migrations/5.lua +++ b/data/migrations/5.lua @@ -1,3 +1,5 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + print("> Updating database to version 6 (quickloot)") + db.query("ALTER TABLE `players` ADD `quickloot_fallback` TINYINT DEFAULT 0") + return true end diff --git a/data/migrations/6.lua b/data/migrations/6.lua new file mode 100644 index 0000000000..2c7477d680 --- /dev/null +++ b/data/migrations/6.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false -- true = There are others migrations file | false = this is the last migration file +end diff --git a/data/modules/modules.xml b/data/modules/modules.xml index 341557359b..594a3cb48b 100644 --- a/data/modules/modules.xml +++ b/data/modules/modules.xml @@ -19,11 +19,6 @@ - - - - - diff --git a/data/modules/scripts/quickloot/quickloot.lua b/data/modules/scripts/quickloot/quickloot.lua deleted file mode 100644 index 695c69873d..0000000000 --- a/data/modules/scripts/quickloot/quickloot.lua +++ /dev/null @@ -1,608 +0,0 @@ -QuickLootSystem = { - Developer = "Ticardo (Rick), lBaah, dudantas, gpedro, DudZ", - Version = "1.0", - lastUpdate = "29/03/2020 - 12:00" -} - ---[[ - missing features: - - autowalk if corpse is far (+1sqm) - - better loot messages - - update container to refresh quickLootFlags after save quickloot backpacks -]]-- - -local ClientPackets = { - ManageItemList = 0x91, - SelectBackpack = 0x90, - LootCorpse = 0x8F -} - -local ServerPackets = { - SendBackpack = 0xC0 -} - - -- TODO: check a better storage no -local StorageQuickLoot = { - ItemsToLoot = "831831", - LootMode = 832832, - MainContainerFallback = 833833, -} - -local QUICKLOOT_CATEGORY_ATTRIBUTE = "quickLootCategory" - -local QUICKLOOT_MODE_BLACKLIST = 0 -local QUICKLOOT_MODE_WHITELIST = 1 - -local QuickLootCategory = { - UnassignedLoot = 31, - Gold = 30, - Armors = 1, - Amulets = 2, - Boots = 3, - Containers = 4, - CreatureProducts = 24, - Decoration = 5, - Food = 6, - Helmets = 7, - Legs = 8, - Others = 9, - Potions = 10, - Rings = 11, - Runes = 12, - Shields = 13, - Tools = 14, - Valuables = 15, - WeaponsAmmo = 16, - WeaponsAxe = 17, - WeaponsClubs = 18, - WeaponsDistance = 19 , - WeaponsSwords = 20, - WeaponsWands = 21, - StashRetrieve = 27, -} - -local QuickLootReturn = { - ITEM_LOOTED = { looted = true, id = 1, message = ""}, - ITEM_WITH_NO_CATEGORY = {looted = false, id = 2, message = "Couldn't find the category for this item."}, - PLAYER_HAS_NO_BP_AND_FALLBACK_DISABLED = {looted = false, id = 3, message = "Couldn't find any of the loot containers defined for this item."}, - COULD_NOT_FIND_ANY_BP = {looted = false, id = 4, message = "Couldn't find any of the loot containers defined for this item."}, - NO_FREE_SLOTS_LEFT = {looted = false, id = 5, message = "There is no empty slots left in the loot containers defined."}, - NO_CAPACITY ={looted = false, id = 6, message = "You don't have enough capacity."} -} - - -function onRecvbyte(player, msg, byte) - setupDatabase() - if byte == ClientPackets.ManageItemList then - local lootMode = msg:getByte() - local itemCount = msg:getU16() - local itemList = {} - - if itemCount >= 1 then - local item = nil - for i = 1, itemCount do - item = Game.getItemIdByClientId(msg:getU16()) - if item then - table.insert(itemList, item:getId()) - end - end - end - - player:setQuickLootMode(lootMode) - player:setQuickLootItems(itemList) - player:sendLootBackpacks() - elseif byte == ClientPackets.LootCorpse then - -- TODO add spam protection - local position = msg:getPosition() - local itemId = msg:getU16() - local stackPos = msg:getByte() - - local lootMode = player:getQuickLootMode() - local lootList = player:getQuickLootItems() - local quickLootBackpacks = player:getQuickLootBackpacks() - if position.x == CONTAINER_POSITION then - local container = player:getContainerById(position.y - 64) - if container then - local item = container:getItem(position.z) - if item then - local itemLooted = lootItem(player, quickLootBackpacks, item) - if not itemLooted.looted then - player:sendCancelMessage(itemLooted.message) - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You looted " .. item:getCount().. "x "..item:getName() .. ".") - end - return - end - end - end - - local itemTile = Tile(position) - if not itemTile then - return - end - - if player:getPosition():getDistance(position) > 1 then - return - end - - local thing = itemTile:getThing(stackPos) - if thing and thing:isItem() then - local corpseOwner = thing:getCorpseOwner() - if corpseOwner ~= 0 and not player:canOpenCorpse(corpseOwner) then - player:sendCancelMessage("You are not the owner.") - return - end - - if thing:isContainer() then - local itemsBefore = getContainerItems(thing) - if #itemsBefore == 0 then - player:sendTextMessage(MESSAGE_LOOT, "No loot.") - return - end - - lootContainer(player, quickLootBackpacks, thing, lootMode, lootList) - - local itemsAfter = getContainerItems(thing) - local lootedItems = {} - local notLootedItemsFromList = {} - local notLootedItemsAtAll = {} - for i, k in pairs(itemsBefore) do - local itemStr = k:getCount().. "x "..k:getName() - if not table.contains(itemsAfter, k) then - table.insert(lootedItems, itemStr) - player:updateLootTracker(k) - elseif canLootItem(k.itemid, lootMode, lootList) then - table.insert(notLootedItemsFromList, itemStr) - else - table.insert(notLootedItemsAtAll, itemStr) - end - end - - if #lootedItems > 0 then - player:sendTextMessage(MESSAGE_LOOT, "You looted " .. table.concat(lootedItems, ", ") .. ".") - else - player:sendTextMessage(MESSAGE_LOOT, "You looted none of the dropped items.") - end - - if #notLootedItemsFromList > 0 then - player:sendCancelMessage("Could not loot " .. table.concat(notLootedItemsFromList, ", ") .. ".") - end - elseif thing:isItem() and canLootItem(thing.itemid, lootMode, lootList) then - local itemType = thing:getType() - if itemType:isCorpse() then - return - end - - local itemLooted = lootItem(player, quickLootBackpacks, thing) - if not itemLooted.looted then - player:sendCancelMessage(itemLooted.message) - else - player:sendTextMessage(MESSAGE_LOOT, "You looted " .. item:getCount().. "x "..item:getName() .. ".") - end - end - end - elseif byte == ClientPackets.SelectBackpack then - local action = msg:getByte() - if action == 0 then - local category = msg:getByte() - local containerType = msg:getU16() - local containerSlot = msg:getU16() - local containerPosition = msg:getByte() - local containerId = msg:getU16() - - player:setQuickLootBackpack(category, containerSlot, containerPosition, containerId) - player:sendLootBackpacks() - player:updateQuickLootContainers() - elseif action == 1 then - local category = msg:getByte() - player:setQuickLootBackpack(category, nil) - player:sendLootBackpacks() - player:updateQuickLootContainers() - elseif action == 2 then - local categoryId = msg:getByte() - local quickLootBackpacks = player:getQuickLootBackpacks() - - if quickLootBackpacks[categoryId] then - local category = quickLootBackpacks[categoryId] - local container = getContainerByQuickLootCategory(player, categoryId, category.sid) - if container then - player:sendContainer(container) - end - end - elseif action == 3 then - local bpFallback = player:getQuickLootMainContainerFallback() - if bpFallback ~= nil then - player:setQuickLootMainContainerFallback((bpFallback == 1) and 0 or 1) - else - player:setQuickLootMainContainerFallback(1) - end - player:sendLootBackpacks() - end - - end -end - --- Helpers Methods -function getContainerItems(container) - local rtn = {} - for i = 0, container:getSize() do - local item = container:getItem(i) - if item then - table.insert(rtn, item) - if item:isContainer() then - thisItems = getContainerItems(item) - for i,k in pairs(thisItems) do - table.insert(rtn, k) - end - end - end - end - return rtn -end - -function canLootItem(itemId, lootMode, lootList) - if lootMode == QUICKLOOT_MODE_BLACKLIST then - return not table.contains(lootList, itemId) - end - - if lootMode == QUICKLOOT_MODE_WHITELIST then - return table.contains(lootList, itemId) - end - - return false -end - -function lootContainer(player, backpacks, containerItem, lootMode, lootList) - local container = Container(containerItem.uid) - for i = 0, container:getSize() do - local item = container:getItem(i) - if item then - if item:isContainer() then - lootContainer(player, backpacks, item, lootMode, lootList) - elseif item:isItem() then - if canLootItem(item.itemid, lootMode, lootList) then - if lootItem(player, backpacks, item).looted then - lootContainer(player, backpacks, container, lootMode, lootList) - break - end - end - end - end - end -end - -function lootItem(player, backpacks, item) - if item:getWeight() > player:getFreeCapacity() then - return QuickLootReturn.NO_CAPACITY - end - local itemType = ItemType(item.itemid) - if not itemType or not itemType:getLootCategory() then - return QuickLootReturn.ITEM_WITH_NO_CATEGORY - end - local category = itemType:getLootCategory() - local definedBackpack = backpacks[category] - - -- If there is no bp defined for this category, set UnassignedLoot backpack as the target - if not definedBackpack then - category = QuickLootCategory.UnassignedLoot - definedBackpack = backpacks[category] - end - - local destination = false - - - -- If there is no UnassignedLoot backpack defined or the player don't have the backpack in their inventory, set destination as the mainBp if fallback is enabled - if not definedBackpack or not player:getItemCount(definedBackpack.sid) then - if player:getQuickLootMainContainerFallback() == 1 then - destination = player:getSlotItem(CONST_SLOT_BACKPACK) - else - return QuickLootReturn.PLAYER_HAS_NO_BP_AND_FALLBACK_DISABLED - end - end - - if not destination then - destination = getContainerByQuickLootCategory(player, category, definedBackpack.sid) - end - if not destination then - return QuickLootReturn.COULD_NOT_FIND_ANY_BP - end - - -- check freeSlot (if you dont check, it will add items even being beyond backpack capacity) - if destination:getEmptySlots() == 0 then - -- search for another BP form MainBP like this with FreeSlots - -- if cannot have slot to move, find next container inside selected backpack (or another container == this one in other backpack (recursive performance loss?)) - -- else if it's fully full - -- move to UnassignedLoot (avoid redundancy checks by checking category again here, if it's already UnassignedLoot you can just go to main container) - -- if cannot have slot to move, find next container inside UnassignedLoot backpack - -- else if it's fully full - -- if main container fallback is enabled, move to main cotainer - -- else ignore (and send message to user) - destination = getFirstFreeBPOfType(player:getSlotItem(CONST_SLOT_BACKPACK), destination.itemid) - if not destination and not category == QuickLootCategory.UnassignedLoot then - destination = getFirstFreeBPOfType(player:getSlotItem(CONST_SLOT_BACKPACK), backpacks[QuickLootCategory.UnassignedLoot].sid) - end - if not destination and player:getQuickLootMainContainerFallback() == 1 then - destination = getFirstFreeBPOfType(player:getSlotItem(CONST_SLOT_BACKPACK), player:getSlotItem(CONST_SLOT_BACKPACK).itemid) - end - if not destination then - return QuickLootReturn.NO_FREE_SLOTS_LEFT - end - end - - item:moveTo(destination) - return QuickLootReturn.ITEM_LOOTED -end - -function getFirstFreeBPOfType(rootContainer, bpSID) - if rootContainer.itemid == bpSID and rootContainer:getEmptySlots() > 0 then - return rootContainer - end - - for i = 0, rootContainer:getSize() - 1 do - local item = rootContainer:getItem(i) - if item:isContainer() then - local foundOurBP = getFirstFreeBPOfType(item, bpSID) - if foundOurBP then - return foundOurBP - end - end - end - - return nil -end - -function getContainerBySlot(player, containerSlot, containerIndex) - local item - - local container = player:getContainerById(containerSlot - 64) - if container then - item = container:getItem(containerIndex) - else - item = player:getSlotItem(containerSlot) - end - - return item -end - -function getContainerByQuickLootCategory(player, categoryId, serverId) - local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX) - if inbox then - local itemsCount = inbox:getItemCountById(serverId) - if itemsCount then - local insideInbox = checkContainerCategory(inbox, categoryId) - if insideInbox then - return insideInbox - end - end - end - - local mainBp = player:getSlotItem(CONST_SLOT_BACKPACK) - if mainBp and mainBp:hasQuickLootCategory(categoryId) then - return mainBp - end - - local itemsCount = player:getItemCount(serverId) - if not itemsCount then -- If this player doesn't have any BP like its the set one in the inventory, use mainBP as fallback - return nil - end - - local insideBp = checkContainerCategory(mainBp, categoryId) - if insideBp then - return insideBp - end - - return nil -end - -function checkContainerCategory(containerItem, categoryId) - if not containerItem then - return nil - end - - local container = Container(containerItem.uid) - if container then - if container:hasQuickLootCategory(categoryId) then - return container - end - - for i = 0, container:getSize() - 1 do - local item = container:getItem(i) - - if item:isContainer() then - local nestedContainer = checkContainerCategory(item, categoryId) - if nestedContainer then - return nestedContainer - end - end - end - end - - return nil -end - --- Container Methods -function Container.hasQuickLootCategory(self, categoryId) - return self:getCustomAttribute(string.format("%s%d", QUICKLOOT_CATEGORY_ATTRIBUTE, categoryId)) == 1 -end - -function Container.addQuickLootCategory(self, categoryId) - self:setCustomAttribute(string.format("%s%d", QUICKLOOT_CATEGORY_ATTRIBUTE, categoryId), 1) -end - -function Container.removeQuickLootCategory(self, categoryId) - self:removeCustomAttribute(string.format("%s%d", QUICKLOOT_CATEGORY_ATTRIBUTE, categoryId)) -end - --- Player Methods -function Player.sendLootBackpacks(self) - - local playerId = self:getGuid() - local containers = {} - local count = 0 - local rows = db.storeQuery("SELECT `category_id`, `cid`, `sid` FROM `quickloot_containers` WHERE player_id = " .. playerId) - - if rows then - repeat - local categoryId = result.getNumber(rows, "category_id") - local clientId = result.getNumber(rows, "cid") - local serverId = result.getNumber(rows, "sid") - - if getContainerByQuickLootCategory(self, categoryId, serverId) then - count = count + 1 - containers[categoryId] = clientId - end - until not result.next(rows) - - result.free(rows) - end - - local msg = NetworkMessage() - - msg:addByte(ServerPackets.SendBackpack) - msg:addByte(self:getQuickLootMainContainerFallback() or 1) - msg:addByte(count) - - for categoryId, clientId in pairs(containers) do - msg:addByte(categoryId) - -- if categoryId == QuickLootCategory.UnassignedLoot and not clientId then - --clientId = - -- end - msg:addU16(clientId) - end - - msg:sendToPlayer(self) -end - -function Player.setQuickLootBackpack(self, categoryId, containerSlot, containerPosition, containerId) - local playerId = self:getGuid() - - -- if is just remove instead replace - if not containerSlot then - local oldContainerQuery = db.storeQuery("SELECT `sid` FROM `quickloot_containers` WHERE player_id = " .. playerId .. " and category_id = " .. categoryId) - if oldContainerQuery then - local serverId = result.getNumber(oldContainerQuery, "sid") - local oldContainer = getContainerByQuickLootCategory(self, categoryId, serverId) - if oldContainer then - oldContainer:removeQuickLootCategory(categoryId) - end - end - - result.free(oldContainerQuery) - db.query("DELETE FROM `quickloot_containers` WHERE `player_id` = " .. playerId .. " AND `category_id` = " .. categoryId) - return - end - - -- check if already exists - local query = db.storeQuery("SELECT COUNT(category_id) as c FROM `quickloot_containers` WHERE player_id = " .. playerId .. " AND `category_id` = " .. categoryId) - local count = result.getNumber(query, "c") - result.free(query) - - local container = getContainerBySlot(self, containerSlot, containerPosition) - if not container then - return - end - - if container.itemid == ITEM_GOLD_POUCH and categoryId ~= QuickLootCategory.Gold then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return - end - - -- if exists another value, remove custom attribute - local oldServerId = container.itemid - local oldContainer = getContainerByQuickLootCategory(self, categoryId, oldServerId) - if oldContainer then - oldContainer:removeQuickLootCategory(categoryId) - end - - -- and add custom attribute to new container - local serverId = container.itemid - - if not container:hasQuickLootCategory(categoryId) then - container:addQuickLootCategory(categoryId) - end - - if count == 0 then - db.query("INSERT INTO `quickloot_containers` (`player_id`, `category_id`, `cid`, `sid`) VALUES (" .. playerId .. ", " .. categoryId .. ", " .. containerId .. ", " .. serverId .. ");") - return - end - - db.query('UPDATE `quickloot_containers` SET `cid` = ' .. containerId .. ', `sid` = ' .. serverId.. ' WHERE `player_id` = ' .. playerId .. ' AND `category_id` = ' .. categoryId .. '') -end - -function Player.getQuickLootBackpacks(self) - local playerId = self:getGuid() - local query = db.storeQuery("SELECT `category_id`, `cid`, `sid` FROM `quickloot_containers` WHERE player_id = " .. playerId) - local containers = {} - - if query then - repeat - local categoryId = result.getNumber(query, "category_id") - local clientId = result.getNumber(query, "cid") - local serverId = result.getNumber(query, "sid") - - containers[categoryId] = { - cid = clientId, - sid = serverId, - } - until not result.next(query) - - result.free(query) - end - - return containers -end - -function Player.resetQuickLootItems(self) - self:setQuickLootItems({}) -end - -function Player.getQuickLootMode(self) - return self:getStorageValue(StorageQuickLoot.LootMode) -end - -function Player.getQuickLootMainContainerFallback(self) - return self:getStorageValue(StorageQuickLoot.MainContainerFallback) -end - -function Player.setQuickLootMode(self, check) - self:setStorageValue(StorageQuickLoot.LootMode, check or 0) -end - -function Player.setQuickLootMainContainerFallback(self, check) - self:setStorageValue(StorageQuickLoot.MainContainerFallback, check or 0) -end - -function Player.setQuickLootItems(self, items) - if type(items) ~= "table" then - items = {} - end - - self:setSpecialStorage(StorageQuickLoot.ItemsToLoot, items) -end - -function Player.getQuickLootItems(self) - local value = self:getSpecialStorage(StorageQuickLoot.ItemsToLoot) - if not value then - return {} - end - - return value -end - -function Player.updateQuickLootContainers(self) - -- local quickLootCategories = self:getQuickLootBackpacks() - -- for i = QuickLootCategory.Armors, QuickLootCategory.StashRetrieve do - -- getContainerByQuickLootCategory(self, i, ) - -- end -end - -function setupDatabase() - db.query([[CREATE TABLE IF NOT EXISTS `quickloot_containers` ( - `player_id` INT NOT NULL, - `category_id` INT UNSIGNED NOT NULL, - `cid` INT UNSIGNED NOT NULL, - `sid` INT UNSIGNED NOT NULL, - - CONSTRAINT `fk_quickloot_containers_player_id` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) - )]]) -end diff --git a/schema.sql b/schema.sql index df2ad4861a..e912bb91cb 100644 --- a/schema.sql +++ b/schema.sql @@ -152,6 +152,7 @@ CREATE TABLE IF NOT EXISTS `players` ( `marriage_status` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `marriage_spouse` int(11) NOT NULL DEFAULT '-1', `bonus_rerolls` bigint(21) NOT NULL DEFAULT '0', + `quickloot_fallback` tinyint(1) DEFAULT '0', INDEX `account_id` (`account_id`), INDEX `vocation` (`vocation`), CONSTRAINT `players_pk` PRIMARY KEY (`id`), diff --git a/src/const.h b/src/const.h index cbfb9d40cd..b8203a9531 100644 --- a/src/const.h +++ b/src/const.h @@ -394,39 +394,6 @@ enum Icons_t { ICON_BLEEDING = 1 << 15, }; -enum QuickLootCategory_t : uint8_t { - LOOT_NONE = 0, - LOOT_ARMOR = 1, - LOOT_AMULET = 2, - LOOT_BOOTS = 3, - LOOT_CONTAINER = 4, - LOOT_DECORATION = 5, - LOOT_FOOD = 6, - LOOT_HELMET = 7, - LOOT_LEGS = 8, - LOOT_OTHER = 9, - LOOT_POTION = 10, - LOOT_RING = 11, - LOOT_RUNE = 12, - LOOT_SHIELD = 13, - LOOT_TOOL = 14, - LOOT_VALUABLE = 15, - LOOT_WEAPON_AMMO = 16, - LOOT_WEAPON_AXE = 17, - LOOT_WEAPON_CLUB = 18, - LOOT_WEAPON_DISTANCE = 19, - LOOT_WEAPON_SWORD = 20, - LOOT_WEAPON_WAND = 21, - LOOT_CREATURE_PRODUCT = 24, - LOOT_STASH_RETRIEVE = 27, - LOOT_GOLD = 30, - LOOT_UNASSIGNED = 31, - - LOOT_START = LOOT_ARMOR, - LOOT_END = LOOT_UNASSIGNED - -}; - enum WeaponType_t : uint8_t { WEAPON_NONE, WEAPON_SWORD, diff --git a/src/enums.h b/src/enums.h index 73bfd154ce..57fe092917 100644 --- a/src/enums.h +++ b/src/enums.h @@ -94,6 +94,7 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_SPECIAL = 1 << 23, ITEM_ATTRIBUTE_IMBUINGSLOTS = 1 << 24, ITEM_ATTRIBUTE_OPENCONTAINER = 1 << 25, + ITEM_ATTRIBUTE_QUICKLOOTCONTAINER = 1 << 26, ITEM_ATTRIBUTE_CUSTOM = 1U << 31 }; @@ -533,6 +534,47 @@ enum MapMark_t MAPMARK_GREENSOUTH = 19, }; +enum QuickLootFilter_t +{ + QUICKLOOTFILTER_SKIPPEDLOOT = 0, + QUICKLOOTFILTER_ACCEPTEDLOOT = 1, +}; + +enum ObjectCategory_t +{ + OBJECTCATEGORY_NONE = 0, + OBJECTCATEGORY_ARMORS = 1, + OBJECTCATEGORY_NECKLACES = 2, + OBJECTCATEGORY_BOOTS = 3, + OBJECTCATEGORY_CONTAINERS = 4, + OBJECTCATEGORY_DECORATION = 5, + OBJECTCATEGORY_FOOD = 6, + OBJECTCATEGORY_HELMETS = 7, + OBJECTCATEGORY_LEGS = 8, + OBJECTCATEGORY_OTHERS = 9, + OBJECTCATEGORY_POTIONS = 10, + OBJECTCATEGORY_RINGS = 11, + OBJECTCATEGORY_RUNES = 12, + OBJECTCATEGORY_SHIELDS = 13, + OBJECTCATEGORY_TOOLS = 14, + OBJECTCATEGORY_VALUABLES = 15, + OBJECTCATEGORY_AMMO = 16, + OBJECTCATEGORY_AXES = 17, + OBJECTCATEGORY_CLUBS = 18, + OBJECTCATEGORY_DISTANCEWEAPONS = 19, + OBJECTCATEGORY_SWORDS = 20, + OBJECTCATEGORY_WANDS = 21, + OBJECTCATEGORY_PREMIUMSCROLLS = 22, // not used in quickloot + OBJECTCATEGORY_TIBIACOINS = 23, // not used in quickloot + OBJECTCATEGORY_CREATUREPRODUCTS = 24, + OBJECTCATEGORY_STASHRETRIEVE = 27, + OBJECTCATEGORY_GOLD = 30, + OBJECTCATEGORY_DEFAULT = 31, // unassigned loot + + OBJECTCATEGORY_FIRST = OBJECTCATEGORY_ARMORS, + OBJECTCATEGORY_LAST = OBJECTCATEGORY_DEFAULT, +}; + struct Outfit_t { uint16_t lookType = 0; uint16_t lookTypeEx = 0; diff --git a/src/game.cpp b/src/game.cpp index ae1fd18cee..9467afdc68 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -313,17 +313,18 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index break; } - case STACKPOS_FIND_THING: { - thing = tile->getUseItem(index); - if (!thing) { - thing = tile->getDoorItem(); - } - - if (!thing) { - thing = tile->getTopDownItem(); - } - break; - } + case STACKPOS_FIND_THING: { + thing = tile->getUseItem(index); + if (!thing) { + thing = tile->getDoorItem(); + } + + if (!thing) { + thing = tile->getTopDownItem(); + } + + break; + } default: { thing = nullptr; @@ -1823,6 +1824,316 @@ ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pu return RETURNVALUE_NOTPOSSIBLE; } +void Game::internalQuickLootCorpse(Player* player, Container* corpse) +{ + if (!player || !corpse) { + return; + } + + std::vector itemList; + bool ignoreListItems = (player->quickLootFilter == QUICKLOOTFILTER_SKIPPEDLOOT); + + bool missedAnyGold = false; + bool missedAnyItem = false; + + for (ContainerIterator it = corpse->iterator(); it.hasNext(); it.advance()) { + Item* item = *it; + bool listed = player->isQuickLootListedItem(item); + if ((listed && ignoreListItems) || (!listed && !ignoreListItems)) { + if (item->getWorth() != 0) { + missedAnyGold = true; + } else { + missedAnyItem = true; + } + continue; + } + + itemList.push_back(item); + } + + bool shouldNotifyCapacity = false; + ObjectCategory_t shouldNotifyNotEnoughRoom = OBJECTCATEGORY_NONE; + + uint32_t totalLootedGold = 0; + uint32_t totalLootedItems = 0; + for (Item* item : itemList) { + uint32_t worth = item->getWorth(); + uint16_t baseCount = item->getItemCount(); + ObjectCategory_t category = getObjectCategory(item); + + ReturnValue ret = internalQuickLootItem(player, item, category); + if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { + shouldNotifyCapacity = true; + } else if (ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { + shouldNotifyNotEnoughRoom = category; + } + + bool success = ret == RETURNVALUE_NOERROR; + if (worth != 0) { + missedAnyGold = missedAnyGold || !success; + if (success) { + player->sendLootStats(item); + totalLootedGold += worth; + } else { + // item is not completely moved + totalLootedGold += worth - item->getWorth(); + } + } else { + missedAnyItem = missedAnyItem || !success; + if (success || item->getItemCount() != baseCount) { + totalLootedItems++; + player->sendLootStats(item); + } + } + } + + std::stringstream ss; + if (totalLootedGold != 0 || missedAnyGold || totalLootedItems != 0 || missedAnyItem) { + bool lootedAllGold = totalLootedGold != 0 && !missedAnyGold; + bool lootedAllItems = totalLootedItems != 0 && !missedAnyItem; + if (lootedAllGold) { + if (totalLootedItems != 0 || missedAnyItem) { + ss << "You looted the complete " << totalLootedGold << " gold"; + + if (lootedAllItems) { + ss << " and all dropped items"; + } else if (totalLootedItems != 0) { + ss << ", but you only looted some of the items"; + } else if (missedAnyItem) { + ss << " but none of the dropped items"; + } + } else { + ss << "You looted " << totalLootedGold << " gold"; + } + } else if (lootedAllItems) { + if (totalLootedItems == 1) { + ss << "You looted 1 item"; + } else if (totalLootedGold != 0 || missedAnyGold) { + ss << "You looted all of the dropped items"; + } else { + ss << "You looted all items"; + } + + if (totalLootedGold != 0) { + ss << ", but you only looted " << totalLootedGold << " of the dropped gold"; + } else if (missedAnyGold) { + ss << " but none of the dropped gold"; + } + } else if (totalLootedGold != 0) { + ss << "You only looted " << totalLootedGold << " of the dropped gold"; + if (totalLootedItems != 0) { + ss << " and some of the dropped items"; + } else if (missedAnyItem) { + ss << " but none of the dropped items"; + } + } else if (totalLootedItems != 0) { + ss << "You looted some of the dropped items"; + if (missedAnyGold) { + ss << " but none of the dropped gold"; + } + } else if (missedAnyGold) { + ss << "You looted none of the dropped gold"; + if (missedAnyItem) { + ss << " and none of the items"; + } + } else if (missedAnyItem) { + ss << "You looted none of the dropped items"; + } + } else { + ss << "No loot"; + } + + ss << "."; + player->sendTextMessage(MESSAGE_LOOT, ss.str()); + + if (shouldNotifyCapacity) { + ss.str(std::string()); + ss << "Attention! The loot you are trying to pick up is too heavy for you to carry."; + } else if (shouldNotifyNotEnoughRoom != OBJECTCATEGORY_NONE) { + ss.str(std::string()); + ss << "Attention! The container assigned to category " << getObjectCategoryName(shouldNotifyNotEnoughRoom) << " is full."; + } else { + return; + } + + if (player->lastQuickLootNotification + 15000 < OTSYS_TIME()) { + player->sendTextMessage(MESSAGE_STATUS_WARNING, ss.str()); + } else { + player->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + player->lastQuickLootNotification = OTSYS_TIME(); +} + +ReturnValue Game::internalQuickLootItem(Player* player, Item* item, ObjectCategory_t category /* = OBJECTCATEGORY_DEFAULT*/) +{ + if (!player || !item) { + return RETURNVALUE_NOTPOSSIBLE; + } + + bool fallbackConsumed = false; + uint16_t baseId = 0; + + Container* lootContainer = player->getLootContainer(category); + if (!lootContainer) { + if (player->quickLootFallbackToMainContainer) { + Item* fallbackItem = player->getInventoryItem(CONST_SLOT_BACKPACK); + + if (fallbackItem) { + Container* mainBackpack = fallbackItem->getContainer(); + if (mainBackpack && !fallbackConsumed) { + player->setLootContainer(OBJECTCATEGORY_DEFAULT, mainBackpack); + player->sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK)); + } + } + + lootContainer = fallbackItem ? fallbackItem->getContainer() : nullptr; + fallbackConsumed = true; + } else { + return RETURNVALUE_NOTPOSSIBLE; + } + } else { + baseId = lootContainer->getID(); + } + + if (!lootContainer) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Container* lastSubContainer = nullptr; + uint32_t remainderCount = item->getItemCount(); + ContainerIterator it = lootContainer->iterator(); + + ReturnValue ret; + do { + Item* moveItem = nullptr; + ret = internalMoveItem(item->getParent(), lootContainer, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, 0, player); + if (moveItem) { + remainderCount -= moveItem->getItemCount(); + } + + if (ret != RETURNVALUE_CONTAINERNOTENOUGHROOM) { + break; + } + + // search for a sub container + bool obtainedNewContainer = false; + while (it.hasNext()) { + Item* cur = *it; + Container* subContainer = cur ? cur->getContainer() : nullptr; + it.advance(); + + if (subContainer) { + lastSubContainer = subContainer; + lootContainer = subContainer; + obtainedNewContainer = true; + break; + } + } + + // a hack to fix last empty sub-container + if (!obtainedNewContainer && lastSubContainer && lastSubContainer->size() > 0) { + Item* cur = lastSubContainer->getItemByIndex(lastSubContainer->size() - 1); + Container* subContainer = cur ? cur->getContainer() : nullptr; + + if (subContainer) { + lootContainer = subContainer; + obtainedNewContainer = true; + } + + lastSubContainer = nullptr; + } + + // consumed all sub-container & there is simply no more containers to iterate over. + // check if fallback should be used and if not, then break + bool quickFallback = (player->quickLootFallbackToMainContainer); + bool noFallback = fallbackConsumed || !quickFallback; + if (noFallback && (!lootContainer || !obtainedNewContainer)) { + break; + } else if (!lootContainer || !obtainedNewContainer) { + Item* fallbackItem = player->getInventoryItem(CONST_SLOT_BACKPACK); + if (!fallbackItem || !fallbackItem->getContainer()) { + break; + } + + lootContainer = fallbackItem->getContainer(); + it = lootContainer->iterator(); + + fallbackConsumed = true; + } + } while (remainderCount != 0); + return ret; +} + +ObjectCategory_t Game::getObjectCategory(const Item* item) +{ + ObjectCategory_t category = OBJECTCATEGORY_DEFAULT; + if (!item) { + return OBJECTCATEGORY_NONE; + } + + const ItemType& it = Item::items[item->getID()]; + if (item->getWorth() != 0) { + category = OBJECTCATEGORY_GOLD; + } else if (it.weaponType != WEAPON_NONE) { + switch (it.weaponType) { + case WEAPON_SWORD: + category = OBJECTCATEGORY_SWORDS; + break; + case WEAPON_CLUB: + category = OBJECTCATEGORY_CLUBS; + break; + case WEAPON_AXE: + category = OBJECTCATEGORY_AXES; + break; + case WEAPON_SHIELD: + category = OBJECTCATEGORY_SHIELDS; + break; + case WEAPON_DISTANCE: + category = OBJECTCATEGORY_DISTANCEWEAPONS; + break; + case WEAPON_WAND: + category = OBJECTCATEGORY_WANDS; + break; + case WEAPON_AMMO: + category = OBJECTCATEGORY_AMMO; + break; + default: + break; + } + } else if (it.slotPosition != SLOTP_HAND) { // if it's a weapon/shield should have been parsed earlier + if ((it.slotPosition & SLOTP_HEAD) != 0) { + category = OBJECTCATEGORY_HELMETS; + } else if ((it.slotPosition & SLOTP_NECKLACE) != 0) { + category = OBJECTCATEGORY_NECKLACES; + } else if ((it.slotPosition & SLOTP_BACKPACK) != 0) { + category = OBJECTCATEGORY_CONTAINERS; + } else if ((it.slotPosition & SLOTP_ARMOR) != 0) { + category = OBJECTCATEGORY_ARMORS; + } else if ((it.slotPosition & SLOTP_LEGS) != 0) { + category = OBJECTCATEGORY_LEGS; + } else if ((it.slotPosition & SLOTP_FEET) != 0) { + category = OBJECTCATEGORY_BOOTS; + } else if ((it.slotPosition & SLOTP_RING) != 0) { + category = OBJECTCATEGORY_RINGS; + } + } else if (it.type == ITEM_TYPE_RUNE) { + category = OBJECTCATEGORY_RUNES; + } else if (it.type == ITEM_TYPE_CREATUREPRODUCT) { + category = OBJECTCATEGORY_CREATUREPRODUCTS; + } else if (it.type == ITEM_TYPE_FOOD) { + category = OBJECTCATEGORY_FOOD; + } else if (it.type == ITEM_TYPE_VALUABLE) { + category = OBJECTCATEGORY_VALUABLES; + } else if (it.type == ITEM_TYPE_POTION) { + category = OBJECTCATEGORY_POTIONS; + } else { + category = OBJECTCATEGORY_OTHERS; + } + + return category; +} + Item* searchForItem(Container* container, uint16_t itemId) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { @@ -3287,6 +3598,259 @@ void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance); } +void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos, Item* defaultItem) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerQuickLoot, + this, player->getID(), pos, spriteId, stackPos, defaultItem)); + player->setNextActionTask(task); + return; + } + + if (pos.x != 0xffff) { + if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { + //need to walk to the corpse first before looting it + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(0, std::bind(&Game::playerQuickLoot, + this, player->getID(), pos, spriteId, stackPos, defaultItem)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + + return; + } + } else if (!player->isPremium()) { + player->sendCancelMessage("You must be premium."); + return; + } + + player->setNextActionTask(nullptr); + + Item* item = nullptr; + if (!defaultItem) { + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_FIND_THING); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + item = thing->getItem(); + } else { + item = defaultItem; + } + + if (!item || !item->getParent()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Container* corpse = nullptr; + if (pos.x == 0xffff) { + corpse = item->getParent()->getContainer(); + } else { + corpse = item->getContainer(); + } + + if (!corpse || corpse->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) || corpse->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (!corpse->isRewardCorpse()) { + uint32_t corpseOwner = corpse->getCorpseOwner(); + if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (pos.x == 0xffff) { + uint32_t worth = item->getWorth(); + ObjectCategory_t category = getObjectCategory(item); + ReturnValue ret = internalQuickLootItem(player, item, category); + + std::stringstream ss; + if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { + ss << "Attention! The loot you are trying to pick up is too heavy for you to carry."; + } else if (ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { + ss << "Attention! The container for " << getObjectCategoryName(category) << " is full."; + } else { + if (ret == RETURNVALUE_NOERROR) { + player->sendLootStats(item); + ss << "You looted "; + } else { + ss << "You could not loot "; + } + + if (worth != 0) { + ss << worth << " gold."; + } else { + ss << "1 item."; + } + + player->sendTextMessage(MESSAGE_LOOT, ss.str()); + return; + } + + if (player->lastQuickLootNotification + 15000 < OTSYS_TIME()) { + player->sendTextMessage(MESSAGE_STATUS_WARNING, ss.str()); + } else { + player->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + player->lastQuickLootNotification = OTSYS_TIME(); + } else { + if (corpse->isRewardCorpse()) { + g_actions->useItem(player, pos, 0, corpse, false); + } else { + internalQuickLootCorpse(player, corpse); + } + } + + return; +} + +void Game::playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position& pos, uint16_t spriteId, uint8_t stackPos) +{ + Player* player = getPlayerByID(playerId); + if (!player || pos.x != 0xffff) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Container* container = thing->getContainer(); + if (!container) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (container->getHoldingPlayer() != player) { + player->sendCancelMessage("You must be holding the container to set it as a loot container."); + return; + } + + Container* previousContainer = player->setLootContainer(category, container); + player->sendLootContainers(); + + Cylinder* parent = container->getParent(); + if (parent) { + parent->updateThing(container, container->getID(), container->getItemCount()); + } + + if (previousContainer != nullptr) { + parent = previousContainer->getParent(); + if (parent) { + parent->updateThing(previousContainer, previousContainer->getID(), previousContainer->getItemCount()); + } + } +} + +void Game::playerClearLootContainer(uint32_t playerId, ObjectCategory_t category) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* previousContainer = player->setLootContainer(category, nullptr); + player->sendLootContainers(); + + if (previousContainer != nullptr) { + Cylinder* parent = previousContainer->getParent(); + if (parent) { + parent->updateThing(previousContainer, previousContainer->getID(), previousContainer->getItemCount()); + } + } +} + +void Game::playerOpenLootContainer(uint32_t playerId, ObjectCategory_t category) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getLootContainer(category); + if (!container) { + return; + } + + player->sendContainer(container->getClientID(), container, container->hasParent(), 0); +} + + +void Game::playerSetQuickLootFallback(uint32_t playerId, bool fallback) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->quickLootFallbackToMainContainer = fallback; +} + +void Game::playerQuickLootBlackWhitelist(uint32_t playerId, QuickLootFilter_t filter, std::vector clientIds) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->quickLootFilter = filter; + player->quickLootListClientIds = clientIds; +} + +void Game::playerRequestLockFind(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + std::map itemMap; + uint16_t count = 0; + DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId()); + if (!depotLocker) { + return; + } + + for (Item* locker : depotLocker->getItemList()) { + Container* c = locker->getContainer(); + if (c && c->empty()) { + continue; + } + + if (c) { + for (ContainerIterator it = c->iterator(); it.hasNext(); it.advance()) { + auto itt = itemMap.find((*it)->getID()); + if (itt == itemMap.end()) { + itemMap[(*it)->getID()] = Item::countByType((*it), -1); + count++; + } else { + itemMap[(*it)->getID()] += Item::countByType((*it), -1); + } + } + } + } + + player->sendLockerItems(itemMap, count); + return; +} + void Game::playerCancelAttackAndFollow(uint32_t playerId) { Player* player = getPlayerByID(playerId); diff --git a/src/game.h b/src/game.h index 39fcfb65f3..64a7aa0157 100644 --- a/src/game.h +++ b/src/game.h @@ -319,6 +319,25 @@ class Game bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, bool ghostMode, SpectatorHashSet* spectatorsPtr = nullptr, const Position* pos = nullptr); + /** + * Player wants to loot a corpse + * \param player Player pointer + * \param corpse Container pointer to be looted + */ + void internalQuickLootCorpse(Player* player, Container* corpse); + + /** + * Player wants to loot a single item + * \param player Player pointer + * \param item Item pointer to be looted + * \param category Category of the item + * \returns true if the looting was successful + */ + ReturnValue internalQuickLootItem(Player* player, Item* item, + ObjectCategory_t category = OBJECTCATEGORY_DEFAULT); + + ObjectCategory_t getObjectCategory(const Item* item); + void loadPlayersRecord(); void checkPlayersRecord(); @@ -379,7 +398,7 @@ class Game void playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreCap = false, bool inBackpacks = false); void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, - uint8_t amount, bool ignoreEquipped = false); + uint8_t amount, bool ignoreEquipped = false); void playerCloseShop(uint32_t playerId); void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); void playerCloseTrade(uint32_t playerId); @@ -389,6 +408,16 @@ class Game void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode); void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos); void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + void playerQuickLoot(uint32_t playerId, const Position& pos, + uint16_t spriteId, uint8_t stackPos, Item* defaultItem = nullptr); + void playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, + const Position& pos, uint16_t spriteId, uint8_t stackPos); + void playerClearLootContainer(uint32_t playerId, ObjectCategory_t category);; + void playerOpenLootContainer(uint32_t playerId, ObjectCategory_t category); + void playerSetQuickLootFallback(uint32_t playerId, bool fallback); + void playerQuickLootBlackWhitelist(uint32_t playerId, + QuickLootFilter_t filter, std::vector clientIds); + void playerRequestLockFind(uint32_t playerId); void playerRequestAddVip(uint32_t playerId, const std::string& name); void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); void playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 049d7439dd..339d100462 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -154,7 +154,7 @@ bool IOLoginData::loadPlayerById(Player* player, uint32_t id) { Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings1`, `blessings2`, `blessings3`, `blessings4`, `blessings5`, `blessings6`, `blessings7`, `blessings8`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `prey_stamina_1`, `prey_stamina_2`, `prey_stamina_3`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `skill_critical_hit_chance`, `skill_critical_hit_chance_tries`, `skill_critical_hit_damage`, `skill_critical_hit_damage_tries`, `skill_life_leech_chance`, `skill_life_leech_chance_tries`, `skill_life_leech_amount`, `skill_life_leech_amount_tries`, `skill_mana_leech_chance`, `skill_mana_leech_chance_tries`, `skill_mana_leech_amount`, `skill_mana_leech_amount_tries`, `xpboost_value`, `xpboost_stamina`, `bonus_rerolls` FROM `players` WHERE `id` = " << id; + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings1`, `blessings2`, `blessings3`, `blessings4`, `blessings5`, `blessings6`, `blessings7`, `blessings8`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `prey_stamina_1`, `prey_stamina_2`, `prey_stamina_3`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `skill_critical_hit_chance`, `skill_critical_hit_chance_tries`, `skill_critical_hit_damage`, `skill_critical_hit_damage_tries`, `skill_life_leech_chance`, `skill_life_leech_chance_tries`, `skill_life_leech_amount`, `skill_life_leech_amount_tries`, `skill_mana_leech_chance`, `skill_mana_leech_chance_tries`, `skill_mana_leech_amount`, `skill_mana_leech_amount_tries`, `xpboost_value`, `xpboost_stamina`, `bonus_rerolls`, `quickloot_fallback` FROM `players` WHERE `id` = " << id; return loadPlayer(player, db.storeQuery(query.str())); } @@ -267,7 +267,7 @@ bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) { Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings1`, `blessings2`, `blessings3`, `blessings4`, `blessings5`, `blessings6`, `blessings7`, `blessings8`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `prey_stamina_1`, `prey_stamina_2`, `prey_stamina_3`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `skill_critical_hit_chance`, `skill_critical_hit_chance_tries`, `skill_critical_hit_damage`, `skill_critical_hit_damage_tries`, `skill_life_leech_chance`, `skill_life_leech_chance_tries`, `skill_life_leech_amount`, `skill_life_leech_amount_tries`, `skill_mana_leech_chance`, `skill_mana_leech_chance_tries`, `skill_mana_leech_amount`, `skill_mana_leech_amount_tries`, `xpboost_stamina`, `xpboost_value` FROM `players` WHERE `name` = " << db.escapeString(name); + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings1`, `blessings2`, `blessings3`, `blessings4`, `blessings5`, `blessings6`, `blessings7`, `blessings8`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `prey_stamina_1`, `prey_stamina_2`, `prey_stamina_3`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `skill_critical_hit_chance`, `skill_critical_hit_chance_tries`, `skill_critical_hit_damage`, `skill_critical_hit_damage_tries`, `skill_life_leech_chance`, `skill_life_leech_chance_tries`, `skill_life_leech_amount`, `skill_life_leech_amount_tries`, `skill_mana_leech_chance`, `skill_mana_leech_chance_tries`, `skill_mana_leech_amount`, `skill_mana_leech_amount_tries`, `xpboost_stamina`, `xpboost_value`, `quickloot_fallback` FROM `players` WHERE `name` = " << db.escapeString(name); return loadPlayer(player, db.storeQuery(query.str())); } @@ -308,6 +308,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->setBankBalance(result->getNumber("balance")); + player->quickLootFallbackToMainContainer = result->getNumber("quickloot_fallback"); + player->setSex(static_cast(result->getNumber("sex"))); player->level = std::max(1, result->getNumber("level")); @@ -335,10 +337,10 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->addBlessing(i, result->getNumber(ss.str())); } - unsigned long conditionsSize; - const char* conditions = result->getStream("conditions", conditionsSize); + unsigned long attrSize; + const char* attr = result->getStream("conditions", attrSize); PropStream propStream; - propStream.init(conditions, conditionsSize); + propStream.init(attr, attrSize); Condition* condition = Condition::createCondition(propStream); while (condition) { @@ -517,15 +519,6 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) const std::pair& pair = it->second; Item* item = pair.first; int32_t pid = pair.second; - - Container* itemContainer = item->getContainer(); - if (itemContainer) { - uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); - if (cid > 0) { - openContainersList.emplace_back(std::make_pair(cid, itemContainer)); - } - } - if (pid >= 1 && pid <= 11) { player->internalAddThing(pid, item); } else { @@ -539,6 +532,22 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) container->internalAddThing(item); } } + + Container* itemContainer = item->getContainer(); + if (itemContainer) { + uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace_back(std::make_pair(cid, itemContainer)); + } + if (item->hasAttribute(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER)) { + uint32_t flags = item->getIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { + if (hasBitSet(1 << category, flags)) { + player->setLootContainer((ObjectCategory_t)category, itemContainer, true); + } + } + } + } } } @@ -692,6 +701,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) { + Database& db = Database::getInstance(); + std::ostringstream ss; using ContainerBlock = std::pair; @@ -699,9 +710,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, int32_t runningId = 100; - Database& db = Database::getInstance(); const auto& openContainers = player->getOpenContainers(); - for (const auto& it : itemList) { int32_t pid = it.first; Item* item = it.second; @@ -790,13 +799,13 @@ bool IOLoginData::savePlayer(Player* player) if (player->getHealth() <= 0) { player->changeHealth(1); } - Database& db = Database::getInstance(); std::ostringstream query; query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); DBResult_ptr result = db.storeQuery(query.str()); if (!result) { + std::cout << player->getName() << " 01" << std::endl; return false; } @@ -806,18 +815,6 @@ bool IOLoginData::savePlayer(Player* player) return db.executeQuery(query.str()); } - //serialize conditions - PropWriteStream propWriteStream; - for (Condition* condition : player->conditions) { - if (condition->isPersistent()) { - condition->serialize(propWriteStream); - propWriteStream.write(CONDITIONATTR_END); - } - } - - size_t conditionsSize; - const char* conditions = propWriteStream.getStream(conditionsSize); - //First, an UPDATE query to write the player itself query.str(std::string()); query << "UPDATE `players` SET "; @@ -856,7 +853,19 @@ bool IOLoginData::savePlayer(Player* player) query << "`lastip` = " << player->lastIP << ','; } - query << "`conditions` = " << db.escapeBlob(conditions, conditionsSize) << ','; + //serialize conditions + PropWriteStream propWriteStream; + for (Condition* condition : player->conditions) { + if (condition->isPersistent()) { + condition->serialize(propWriteStream); + propWriteStream.write(CONDITIONATTR_END); + } + } + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + query << "`conditions` = " << db.escapeBlob(attributes, attributesSize) << ','; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { int64_t skullTime = 0; @@ -914,6 +923,7 @@ bool IOLoginData::savePlayer(Player* player) query << "`xpboost_value` = " << player->getStoreXpBoost() << ','; query << "`xpboost_stamina` = " << player->getExpBoostStamina() << ','; query << "`bonus_rerolls` = " << player->getPreyBonusRerolls() << ','; + query << "`quickloot_fallback` = " << (player->quickLootFallbackToMainContainer ? 1 : 0) << ','; if (!player->isOffline()) { query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; @@ -977,6 +987,7 @@ bool IOLoginData::savePlayer(Player* player) //item saving query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); if (!db.executeQuery(query.str())) { + std::cout << player->getName() << " 10" << std::endl; return false; } @@ -991,6 +1002,7 @@ bool IOLoginData::savePlayer(Player* player) } if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { + std::cout << player->getName() << " 11" << std::endl; return false; } diff --git a/src/item.cpp b/src/item.cpp index 86887d6a18..c4355e930f 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -583,8 +583,8 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) } case ATTR_OPENCONTAINER: { - int32_t openContainer; - if (!propStream.read(openContainer)) { + uint8_t openContainer; + if (!propStream.read(openContainer)) { return ATTR_READ_ERROR; } @@ -632,6 +632,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_QUICKLOOTCONTAINER: { + uint32_t flags; + if (!propStream.read(flags)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER, flags); + break; + } + //these should be handled through derived classes //If these are called then something has changed in the items.xml since the map was saved //just read the values @@ -829,7 +839,7 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const if (hasAttribute(ITEM_ATTRIBUTE_OPENCONTAINER)) { propWriteStream.write(ATTR_OPENCONTAINER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)); } if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { @@ -864,6 +874,11 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const entry.second.serialize(propWriteStream); } } + + if (hasAttribute(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER)) { + propWriteStream.write(ATTR_QUICKLOOTCONTAINER); + propWriteStream.write(getQuicklootAttr()); + } } bool Item::hasProperty(ITEMPROPERTY prop) const diff --git a/src/item.h b/src/item.h index c9260b5eed..6f8f6a03f1 100644 --- a/src/item.h +++ b/src/item.h @@ -105,7 +105,9 @@ enum AttrTypes_t { ATTR_SPECIAL = 34, ATTR_IMBUINGSLOTS = 35, ATTR_OPENCONTAINER = 36, - ATTR_CUSTOM_ATTRIBUTES = 37 + ATTR_CUSTOM_ATTRIBUTES = 37, + + ATTR_QUICKLOOTCONTAINER = 38 }; enum Attr_ReadValue { @@ -500,12 +502,22 @@ class ItemAttributes return false; } + const static uint32_t intAttributeTypes = ITEM_ATTRIBUTE_ACTIONID | ITEM_ATTRIBUTE_UNIQUEID | ITEM_ATTRIBUTE_DATE + | ITEM_ATTRIBUTE_WEIGHT | ITEM_ATTRIBUTE_ATTACK | ITEM_ATTRIBUTE_DEFENSE | ITEM_ATTRIBUTE_EXTRADEFENSE + | ITEM_ATTRIBUTE_ARMOR | ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER + | ITEM_ATTRIBUTE_DURATION | ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES + | ITEM_ATTRIBUTE_FLUIDTYPE | ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_IMBUINGSLOTS + | ITEM_ATTRIBUTE_OPENCONTAINER | ITEM_ATTRIBUTE_QUICKLOOTCONTAINER; + + const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER + | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME | ITEM_ATTRIBUTE_SPECIAL; + public: static bool isIntAttrType(itemAttrTypes type) { - return (type & 0x27FFE13) != 0; + return (type & intAttributeTypes) == type; } static bool isStrAttrType(itemAttrTypes type) { - return (type & 0x8001EC) != 0; + return (type & stringAttributeTypes) == type; } inline static bool isCustomAttrType(itemAttrTypes type) { return (type & 0x80000000) != 0; @@ -613,29 +625,6 @@ class Item : virtual public Thing return isLootTrackeable; } - uint32_t getQuickLootFlags() const - { - if (!attributes) { - return 0; - } - - if (!attributes->hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - return 0; - } - - uint32_t flags = 0; - for (uint8_t i = LOOT_START; i < LOOT_END; i++) - { - const ItemAttributes::CustomAttribute* attr = getCustomAttribute("quickLootCategory" + std::to_string(i)); - if (attr != nullptr) { - flags |= (1 << i); - continue; - } - } - - return flags; - } - void removeAttribute(itemAttrTypes type) { if (attributes) { attributes->removeAttribute(type); @@ -855,9 +844,6 @@ class Item : virtual public Thing // Returns the player that is holding this item in his inventory Player* getHoldingPlayer() const; - QuickLootCategory_t getLootCategory() const { - return items[id].quickLootCategory; - } WeaponType_t getWeaponType() const { return items[id].weaponType; } @@ -917,6 +903,12 @@ class Item : virtual public Thing } return items[id].hitChance; } + uint32_t getQuicklootAttr() const { + if (hasAttribute(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER)) { + return getIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + } + return 0; + } uint32_t getWorth() const; LightInfo getLightInfo() const; diff --git a/src/items.cpp b/src/items.cpp index d6cdbb6aa4..c54ea96d4a 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -42,6 +42,45 @@ void Items::clear() nameToItems.clear(); } +using LootTypeNames = std::unordered_map; + +LootTypeNames lootTypeNames = { + {"armor", ITEM_TYPE_ARMOR}, + {"amulet", ITEM_TYPE_AMULET}, + {"boots", ITEM_TYPE_BOOTS}, + {"container", ITEM_TYPE_CONTAINER}, + {"decoration", ITEM_TYPE_DECORATION}, + {"food", ITEM_TYPE_FOOD}, + {"head", ITEM_TYPE_HELMET}, + {"legs", ITEM_TYPE_LEGS}, + {"other", ITEM_TYPE_OTHER}, + {"potion", ITEM_TYPE_POTION}, + {"ring", ITEM_TYPE_RING}, + {"rune", ITEM_TYPE_RUNE}, + {"shield", ITEM_TYPE_SHIELD}, + {"tools", ITEM_TYPE_TOOLS}, + {"valuable", ITEM_TYPE_VALUABLE}, + {"ammo", ITEM_TYPE_AMMO}, + {"axe", ITEM_TYPE_AXE}, + {"club", ITEM_TYPE_CLUB}, + {"distance", ITEM_TYPE_DISTANCE}, + {"sword", ITEM_TYPE_SWORD}, + {"wand", ITEM_TYPE_WAND}, + {"creatureproduct", ITEM_TYPE_CREATUREPRODUCT}, + {"retrieve", ITEM_TYPE_RETRIEVE}, + {"gold", ITEM_TYPE_GOLD}, + {"unassigned", ITEM_TYPE_UNASSIGNED}, +}; + +ItemTypes_t Items::getLootType(const std::string& strValue) +{ + auto lootType = lootTypeNames.find(strValue); + if (lootType != lootTypeNames.end()) { + return lootType->second; + } + return ITEM_TYPE_NONE; +} + bool Items::reload() { clear(); @@ -430,6 +469,14 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) it.type = ITEM_TYPE_RUNE; } else if (tmpStrValue == "supply") { it.type = ITEM_TYPE_SUPPLY; + } else if (tmpStrValue == "creatureproduct") { + it.type = ITEM_TYPE_CREATUREPRODUCT; + } else if (tmpStrValue == "food") { + it.type = ITEM_TYPE_FOOD; + } else if (tmpStrValue == "valuable") { + it.type = ITEM_TYPE_VALUABLE; + } else if (tmpStrValue == "potion") { + it.type = ITEM_TYPE_POTION; } else { std::cout << "[Warning - Items::parseItemNode] Unknown type: " << valueAttribute.as_string() << std::endl; } @@ -552,63 +599,6 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) it.maxTextLen = pugi::cast(valueAttribute.value()); } else if (tmpStrValue == "writeonceitemid") { it.writeOnceItemId = pugi::cast(valueAttribute.value()); - } else if (tmpStrValue == "quicklootcategory") { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - if (tmpStrValue == "none") { - it.quickLootCategory = LOOT_NONE; - } else if (tmpStrValue == "unassigned") { - it.quickLootCategory = LOOT_UNASSIGNED; - } else if (tmpStrValue == "gold") { - it.quickLootCategory = LOOT_GOLD; - } else if (tmpStrValue == "armor") { - it.quickLootCategory = LOOT_ARMOR; - } else if (tmpStrValue == "amulet") { - it.quickLootCategory = LOOT_AMULET; - } else if (tmpStrValue == "boots") { - it.quickLootCategory = LOOT_BOOTS; - } else if (tmpStrValue == "container") { - it.quickLootCategory = LOOT_CONTAINER; - } else if (tmpStrValue == "creatureproduct") { - it.quickLootCategory = LOOT_CREATURE_PRODUCT; - } else if (tmpStrValue == "decoration") { - it.quickLootCategory = LOOT_DECORATION; - } else if (tmpStrValue == "food") { - it.quickLootCategory = LOOT_FOOD; - } else if (tmpStrValue == "helmet") { - it.quickLootCategory = LOOT_HELMET; - } else if (tmpStrValue == "legs") { - it.quickLootCategory = LOOT_LEGS; - } else if (tmpStrValue == "other") { - it.quickLootCategory = LOOT_OTHER; - } else if (tmpStrValue == "potion") { - it.quickLootCategory = LOOT_POTION; - } else if (tmpStrValue == "ring") { - it.quickLootCategory = LOOT_RING; - } else if (tmpStrValue == "rune") { - it.quickLootCategory = LOOT_RUNE; - } else if (tmpStrValue == "shield") { - it.quickLootCategory = LOOT_SHIELD; - } else if (tmpStrValue == "tool") { - it.quickLootCategory = LOOT_TOOL; - } else if (tmpStrValue == "valuable") { - it.quickLootCategory = LOOT_VALUABLE; - } else if (tmpStrValue == "weaponammo") { - it.quickLootCategory = LOOT_WEAPON_AMMO; - } else if (tmpStrValue == "weaponaxe") { - it.quickLootCategory = LOOT_WEAPON_AXE; - } else if (tmpStrValue == "weaponclub") { - it.quickLootCategory = LOOT_WEAPON_CLUB; - } else if (tmpStrValue == "weapondistance") { - it.quickLootCategory = LOOT_WEAPON_DISTANCE; - } else if (tmpStrValue == "weaponsword") { - it.quickLootCategory = LOOT_WEAPON_SWORD; - } else if (tmpStrValue == "weaponwand") { - it.quickLootCategory = LOOT_WEAPON_WAND; - } else if (tmpStrValue == "stashretrieve") { - it.quickLootCategory = LOOT_STASH_RETRIEVE; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown quickLootCategory: " << valueAttribute.as_string() << std::endl; - } } else if (tmpStrValue == "weapontype") { tmpStrValue = asLowerCaseString(valueAttribute.as_string()); if (tmpStrValue == "sword") { @@ -676,6 +666,8 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } else { std::cout << "[Warning - Items::parseItemNode] Unknown effect: " << valueAttribute.as_string() << std::endl; } + } else if (tmpStrValue == "loottype") { + it.type = getLootType(valueAttribute.as_string()); } else if (tmpStrValue == "range") { it.shootRange = pugi::cast(valueAttribute.value()); } else if (tmpStrValue == "stopduration") { diff --git a/src/items.h b/src/items.h index 4a9ad67acb..b3fb12f73f 100644 --- a/src/items.h +++ b/src/items.h @@ -57,6 +57,32 @@ enum ItemTypes_t { ITEM_TYPE_SUPPLY, ITEM_TYPE_REWARDCHEST, ITEM_TYPE_CARPET, + ITEM_TYPE_CREATUREPRODUCT, + ITEM_TYPE_FOOD, + ITEM_TYPE_VALUABLE, + ITEM_TYPE_POTION, + + ITEM_TYPE_ARMOR, + ITEM_TYPE_AMULET, + ITEM_TYPE_BOOTS, + ITEM_TYPE_DECORATION, + ITEM_TYPE_HELMET, + ITEM_TYPE_LEGS, + ITEM_TYPE_OTHER, + ITEM_TYPE_RING, + ITEM_TYPE_SHIELD, + ITEM_TYPE_TOOLS, + ITEM_TYPE_AMMO, + ITEM_TYPE_AXE, + ITEM_TYPE_CLUB, + ITEM_TYPE_DISTANCE, + ITEM_TYPE_SWORD, + ITEM_TYPE_WAND, + + ITEM_TYPE_RETRIEVE, + ITEM_TYPE_GOLD, + ITEM_TYPE_UNASSIGNED, + ITEM_TYPE_LAST, }; @@ -241,7 +267,6 @@ class ItemType MagicEffectClasses magicEffect = CONST_ME_NONE; Direction bedPartnerDir = DIRECTION_NONE; - QuickLootCategory_t quickLootCategory = LOOT_NONE; WeaponType_t weaponType = WEAPON_NONE; Ammo_t ammoType = AMMO_NONE; ShootType_t shootType = CONST_ANI_NONE; @@ -330,6 +355,8 @@ class Items NameMap nameToItems; private: + ItemTypes_t getLootType(const std::string& strValue); + std::map reverseItemMap; std::vector items; InventoryVector inventory; diff --git a/src/luascript.cpp b/src/luascript.cpp index 363d6ace3d..1b94f9ae1f 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1534,20 +1534,46 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_DOORID) registerEnum(ITEM_ATTRIBUTE_SPECIAL) registerEnum(ITEM_ATTRIBUTE_OPENCONTAINER) + registerEnum(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER) registerEnum(ITEM_TYPE_DEPOT) registerEnum(ITEM_TYPE_REWARDCHEST) registerEnum(ITEM_TYPE_MAILBOX) registerEnum(ITEM_TYPE_TRASHHOLDER) - registerEnum(ITEM_TYPE_CONTAINER) registerEnum(ITEM_TYPE_DOOR) registerEnum(ITEM_TYPE_MAGICFIELD) registerEnum(ITEM_TYPE_TELEPORT) registerEnum(ITEM_TYPE_BED) registerEnum(ITEM_TYPE_KEY) - registerEnum(ITEM_TYPE_RUNE) registerEnum(ITEM_TYPE_SUPPLY) + // Quickloot + registerEnum(ITEM_TYPE_ARMOR) + registerEnum(ITEM_TYPE_AMULET) + registerEnum(ITEM_TYPE_BOOTS) + registerEnum(ITEM_TYPE_DECORATION) + registerEnum(ITEM_TYPE_CONTAINER) + registerEnum(ITEM_TYPE_FOOD) + registerEnum(ITEM_TYPE_HELMET) + registerEnum(ITEM_TYPE_LEGS) + registerEnum(ITEM_TYPE_OTHER) + registerEnum(ITEM_TYPE_POTION) + registerEnum(ITEM_TYPE_RING) + registerEnum(ITEM_TYPE_RUNE) + registerEnum(ITEM_TYPE_SHIELD) + registerEnum(ITEM_TYPE_TOOLS) + registerEnum(ITEM_TYPE_VALUABLE) + registerEnum(ITEM_TYPE_AMMO) + registerEnum(ITEM_TYPE_AXE) + registerEnum(ITEM_TYPE_CLUB) + registerEnum(ITEM_TYPE_DISTANCE) + registerEnum(ITEM_TYPE_SWORD) + registerEnum(ITEM_TYPE_WAND) + registerEnum(ITEM_TYPE_CREATUREPRODUCT) + registerEnum(ITEM_TYPE_RETRIEVE) + registerEnum(ITEM_TYPE_GOLD) + registerEnum(ITEM_TYPE_UNASSIGNED) + registerEnum(ITEM_BAG) registerEnum(ITEM_SHOPPING_BAG) registerEnum(ITEM_GOLD_COIN) @@ -1758,34 +1784,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(TILESTATE_FLOORCHANGE_EAST_ALT) registerEnum(TILESTATE_SUPPORTS_HANGABLE) - registerEnum(LOOT_UNASSIGNED) - registerEnum(LOOT_GOLD) - registerEnum(LOOT_ARMOR) - registerEnum(LOOT_AMULET) - registerEnum(LOOT_BOOTS) - registerEnum(LOOT_CONTAINER) - registerEnum(LOOT_CREATURE_PRODUCT) - registerEnum(LOOT_DECORATION) - registerEnum(LOOT_FOOD) - registerEnum(LOOT_HELMET) - registerEnum(LOOT_LEGS) - registerEnum(LOOT_OTHER) - registerEnum(LOOT_POTION) - registerEnum(LOOT_RING) - registerEnum(LOOT_RUNE) - registerEnum(LOOT_SHIELD) - registerEnum(LOOT_TOOL) - registerEnum(LOOT_VALUABLE) - registerEnum(LOOT_WEAPON_AMMO) - registerEnum(LOOT_WEAPON_AXE) - registerEnum(LOOT_WEAPON_CLUB) - registerEnum(LOOT_WEAPON_DISTANCE) - registerEnum(LOOT_WEAPON_SWORD) - registerEnum(LOOT_WEAPON_WAND) - registerEnum(LOOT_STASH_RETRIEVE) - registerEnum(LOOT_START) - registerEnum(LOOT_END) - registerEnum(WEAPON_NONE) registerEnum(WEAPON_SWORD) registerEnum(WEAPON_CLUB) @@ -2435,8 +2433,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity); - registerMethod("Player", "canOpenCorpse", LuaScriptInterface::luaPlayerCanOpenCorpse); - registerMethod("Player", "getKills", LuaScriptInterface::luaPlayerGetKills); registerMethod("Player", "setKills", LuaScriptInterface::luaPlayerSetKills); @@ -2848,7 +2844,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "getExtraDefense", LuaScriptInterface::luaItemTypeGetExtraDefense); registerMethod("ItemType", "getImbuingSlots", LuaScriptInterface::luaItemTypeGetImbuingSlots); registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); - registerMethod("ItemType", "getLootCategory", LuaScriptInterface::luaItemTypeGetLootCategory); registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType); registerMethod("ItemType", "getElementType", LuaScriptInterface::luaItemTypeGetElementType); @@ -8651,24 +8646,6 @@ int LuaScriptInterface::luaPlayerGetFreeCapacity(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerCanOpenCorpse(lua_State* L) -{ - // player:canOpenCorpse(corpseOwner) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - if (player) { - uint32_t corpseOwner = getNumber(L, 2); - pushBoolean(L, player->canOpenCorpse(corpseOwner)); - } - else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerGetKills(lua_State* L) { // player:getKills() @@ -13384,18 +13361,6 @@ int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetLootCategory(lua_State* L) -{ - // itemType:getLootCategory() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->quickLootCategory); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) { // itemType:getWeaponType() diff --git a/src/luascript.h b/src/luascript.h index a419ce8934..64bfc80b11 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -862,8 +862,6 @@ class LuaScriptInterface static int luaPlayerGetCapacity(lua_State* L); static int luaPlayerSetCapacity(lua_State* L); - static int luaPlayerCanOpenCorpse(lua_State* L); - static int luaPlayerGetKills(lua_State* L); static int luaPlayerSetKills(lua_State* L); @@ -1027,6 +1025,10 @@ class LuaScriptInterface static int luaPlayerIsPzLocked(lua_State* L); static int luaPlayerIsOffline(lua_State* L); + static int luaPlayerGetContainers(lua_State* L); + static int luaPlayerSetLootContainer(lua_State* L); + static int luaPlayerGetLootContainer(lua_State* L); + // New Prey static int luaPlayerGetPreyState(lua_State * L); static int luaPlayerGetPreyUnlocked(lua_State * L); @@ -1267,7 +1269,6 @@ class LuaScriptInterface static int luaItemTypeGetExtraDefense(lua_State* L); static int luaItemTypeGetImbuingSlots(lua_State* L); static int luaItemTypeGetArmor(lua_State* L); - static int luaItemTypeGetLootCategory(lua_State* L); static int luaItemTypeGetWeaponType(lua_State* L); static int luaItemTypeGetElementType(lua_State* L); diff --git a/src/player.cpp b/src/player.cpp index 42819353be..c5c99dcf06 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -75,10 +75,15 @@ Player::~Player() it.second->decrementReferenceCounter(); } + for (const auto& it : quickLootContainers) { + it.second->decrementReferenceCounter(); + } + inbox->decrementReferenceCounter(); setWriteItem(nullptr); setEditHouse(nullptr); + logged = false; } bool Player::setVocation(uint16_t vocId) @@ -795,6 +800,94 @@ void Player::onReceiveMail() const } } +Container* Player::setLootContainer(ObjectCategory_t category, Container* container, bool loading /* = false*/) +{ + Container* previousContainer = nullptr; + auto it = quickLootContainers.find(category); + if (it != quickLootContainers.end() && !loading) { + previousContainer = (*it).second; + uint32_t flags = previousContainer->getIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + flags &= ~(1 << category); + if (flags == 0) { + previousContainer->removeAttribute(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + } else { + previousContainer->setIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER, flags); + } + + previousContainer->decrementReferenceCounter(); + quickLootContainers.erase(it); + } + + if (container) { + previousContainer = container; + quickLootContainers[category] = container; + + container->incrementReferenceCounter(); + if (!loading) { + uint32_t flags = container->getIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + container->setIntAttr(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER, flags | static_cast(1 << category)); + } + } + + return previousContainer; +} + +Container* Player::getLootContainer(ObjectCategory_t category) const +{ + if (category != OBJECTCATEGORY_DEFAULT && !isPremium()) { + category = OBJECTCATEGORY_DEFAULT; + } + + auto it = quickLootContainers.find(category); + if (it != quickLootContainers.end()) { + return (*it).second; + } + + if (category != OBJECTCATEGORY_DEFAULT) { + // firstly, fallback to default + return getLootContainer(OBJECTCATEGORY_DEFAULT); + } + + return nullptr; +} + +void Player::checkLootContainers(const Item* item) +{ + if (!item) { + return; + } + + const Container* container = item->getContainer(); + if (!container) { + return; + } + + bool shouldSend = false; + + auto it = quickLootContainers.begin(); + while (it != quickLootContainers.end()) { + Container* lootContainer = (*it).second; + + bool remove = false; + if (item->getHoldingPlayer() != this && (item == lootContainer || container->isHoldingItem(lootContainer))) { + remove = true; + } + + if (remove) { + shouldSend = true; + it = quickLootContainers.erase(it); + lootContainer->decrementReferenceCounter(); + lootContainer->removeAttribute(ITEM_ATTRIBUTE_QUICKLOOTCONTAINER); + } else { + ++it; + } + } + + if (shouldSend) { + sendLootContainers(); + } +} + bool Player::isNearDepotBox() const { const Position& pos = getPosition(); @@ -1142,6 +1235,8 @@ void Player::onRemoveTileItem(const Tile* fromTile, const Position& pos, const I } } } + + checkLootContainers(item); } void Player::onCreatureAppear(Creature* creature, bool isLogin) @@ -1188,7 +1283,7 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) } } - g_game.checkPlayersRecord(); + g_game.checkPlayersRecord(); IOLoginData::updateOnlineStatus(guid, true); } } @@ -1432,6 +1527,8 @@ void Player::onRemoveContainerItem(const Container* container, const Item* item) } } } + + checkLootContainers(item); } void Player::onCloseContainer(const Container* container) @@ -1486,6 +1583,8 @@ void Player::onRemoveInventoryItem(Item* item) } } } + + checkLootContainers(item); } void Player::checkTradeState(const Item* item) @@ -3208,6 +3307,8 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int if (const Item* item = thing->getItem()) { if (const Container* container = item->getContainer()) { + checkLootContainers(container); + if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { autoCloseContainers(container); } else if (container->getTopParent() == this) { diff --git a/src/player.h b/src/player.h index 4bfa9756a2..b4b7ac7072 100644 --- a/src/player.h +++ b/src/player.h @@ -626,6 +626,9 @@ class Player final : public Creature, public Cylinder void onReceiveMail() const; bool isNearDepotBox() const; + Container* setLootContainer(ObjectCategory_t category, Container* container, bool loading = false); + Container* getLootContainer(ObjectCategory_t category) const; + bool canSee(const Position& pos) const override; bool canSeeCreature(const Creature* creature) const override; @@ -968,6 +971,11 @@ class Player final : public Creature, public Cylinder } //inventory + void sendLockerItems(std::map itemMap, uint16_t count) { + if (client) { + client->sendLockerItems(itemMap, count); + } + } void sendCoinBalance() { if (client) { client->sendCoinBalance(); @@ -1022,6 +1030,18 @@ class Player final : public Creature, public Cylinder } } + // Quickloot + void sendLootContainers() { + if (client) { + client->sendLootContainers(); + } + } + void sendLootStats(Item* item) { + if (client) { + client->sendLootStats(item); + } + } + //event methods void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, const Item* newItem, const ItemType& newType) override; @@ -1398,6 +1418,15 @@ class Player final : public Creature, public Cylinder lastMarketInteraction = OTSYS_TIME(); } + bool isQuickLootListedItem(const Item* item) const { + if (!item) { + return false; + } + + auto it = std::find(quickLootListClientIds.begin(), quickLootListClientIds.end(), item->getClientID()); + return it != quickLootListClientIds.end(); + } + bool updateKillTracker(Container* corpse, const std::string& playerName, const Outfit_t creatureOutfit) const { if (client) { @@ -1445,6 +1474,8 @@ class Player final : public Creature, public Cylinder void checkTradeState(const Item* item); bool hasCapacity(const Item* item, uint32_t count) const; + void checkLootContainers(const Item* item); + void gainExperience(uint64_t exp, Creature* source); void addExperience(Creature* source, uint64_t exp, bool sendText = false); void removeExperience(uint64_t exp, bool sendText = false); @@ -1500,6 +1531,9 @@ class Player final : public Creature, public Cylinder std::map rewardMap; + std::map quickLootContainers; + std::vector quickLootListClientIds; + std::vector outfits; GuildWarVector guildWarVector; @@ -1534,6 +1568,7 @@ class Player final : public Creature, public Cylinder int64_t lastPing; int64_t lastPong; int64_t nextAction = 0; + int64_t lastQuickLootNotification = 0; std::vector unjustifiedKills; @@ -1627,7 +1662,8 @@ class Player final : public Creature, public Cylinder tradestate_t tradeState = TRADE_NONE; fightMode_t fightMode = FIGHTMODE_ATTACK; account::AccountType accountType = - account::AccountType::ACCOUNT_TYPE_NORMAL; + account::AccountType::ACCOUNT_TYPE_NORMAL; + QuickLootFilter_t quickLootFilter; bool chaseMode = false; bool secureMode = true; @@ -1638,6 +1674,8 @@ class Player final : public Creature, public Cylinder bool isConnecting = false; bool addAttackSkillPoint = false; bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; + bool quickLootFallbackToMainContainer = false; + bool logged = false; bool scheduledSaleUpdate = false; static uint32_t playerAutoID; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index f186faf193..cc7bcf9a77 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -48,6 +48,65 @@ extern Modules* g_modules; extern Spells* g_spells; extern Imbuements* g_imbuements; +void ProtocolGame::AddItem(NetworkMessage& msg, uint16_t id, uint8_t count) +{ + const ItemType& it = Item::items[id]; + + msg.add(it.clientId); + + if (it.stackable) { + msg.addByte(count); + } else if (it.isSplash() || it.isFluidContainer()) { + msg.addByte(fluidMap[count & 7]); + } else if (it.isContainer() && player->getOperatingSystem() <= CLIENTOS_NEW_WINDOWS) { + msg.addByte(0x00); + } + + if (it.isAnimation) { + msg.addByte(0xFE); // random phase (0xFF for async) + } +} + +void ProtocolGame::AddItem(NetworkMessage& msg, const Item* item) +{ + if (!item) { + return; + } + + const ItemType& it = Item::items[item->getID()]; + + msg.add(it.clientId); + + if (it.stackable) { + msg.addByte(std::min(0xFF, item->getItemCount())); + } else if (it.isSplash() || it.isFluidContainer()) { + msg.addByte(fluidMap[item->getFluidType() & 7]); + } else if (it.isContainer() && player->getOperatingSystem() <= CLIENTOS_NEW_WINDOWS) { + const Container* container = item->getContainer(); + if (container && container->getHoldingPlayer() == player) { + uint32_t lootFlags = 0; + for (auto itt : player->quickLootContainers) { + if (itt.second == container) { + lootFlags |= 1 << itt.first; + } + } + + if (lootFlags != 0) { + msg.addByte(0x01); + msg.add(lootFlags); + } else { + msg.addByte(0x00); + } + } else { + msg.addByte(0x00); + } + } + + if (it.isAnimation) { + msg.addByte(0xFE); // random phase (0xFF for async) + } +} + void ProtocolGame::release() { //dispatcher thread @@ -93,8 +152,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS } if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) - && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER - && g_game.getPlayerByAccount(player->getAccount())) { + && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER + && g_game.getPlayerByAccount(player->getAccount())) { disconnectClient("You may only login with one character\nof your account at the same time."); return; } @@ -455,6 +514,10 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x8C: parseLookAt(msg); break; case 0x8D: parseLookInBattleList(msg); break; case 0x8E: /* join aggression */ break; + case 0x8F: parseQuickLoot(msg); break; + case 0x90: parseLootContainer(msg); break; + case 0x91: parseQuickLootBlackWhitelist(msg); break; + case 0x92: parseRequestLockItems(); break; case 0x96: parseSay(msg); break; case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; case 0x98: parseOpenChannel(msg); break; @@ -915,6 +978,58 @@ void ProtocolGame::parseLookInBattleList(NetworkMessage& msg) addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId); } +void ProtocolGame::parseQuickLoot(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + addGameTask(&Game::playerQuickLoot, player->getID(), pos, spriteId, stackpos, nullptr); +} + +void ProtocolGame::parseLootContainer(NetworkMessage& msg) +{ + uint8_t action = msg.getByte(); + if (action == 0) { + ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + addGameTask(&Game::playerSetLootContainer, player->getID(), category, pos, spriteId, stackpos); + } + else if (action == 1) { + ObjectCategory_t category = (ObjectCategory_t) msg.getByte(); + addGameTask(&Game::playerClearLootContainer, player->getID(), category); + } + else if (action == 2) { + ObjectCategory_t category = (ObjectCategory_t)msg.getByte(); + addGameTask(&Game::playerOpenLootContainer, player->getID(), category); + } + else if (action == 3) { + bool useMainAsFallback = msg.getByte() == 1; + addGameTask(&Game::playerSetQuickLootFallback, player->getID(), useMainAsFallback); + } +} + +void ProtocolGame::parseQuickLootBlackWhitelist(NetworkMessage& msg) +{ + QuickLootFilter_t filter = (QuickLootFilter_t)msg.getByte(); + std::vector listedItems; + + uint16_t size = msg.get(); + listedItems.reserve(size); + + for (int i = 0; i < size; i++) { + listedItems.push_back(msg.get()); + } + + addGameTask(&Game::playerQuickLootBlackWhitelist, player->getID(), filter, listedItems); +} + +void ProtocolGame::parseRequestLockItems() +{ + addGameTask(&Game::playerRequestLockFind, player->getID()); +} + void ProtocolGame::parseSay(NetworkMessage& msg) { std::string receiver; @@ -1766,6 +1881,40 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h writeToOutputBuffer(msg); } +void ProtocolGame::sendLootContainers() +{ + NetworkMessage msg; + msg.addByte(0xC0); + msg.addByte(player->quickLootFallbackToMainContainer ? 1 : 0); + std::map quickLoot; + for (auto it : player->quickLootContainers) { + if (it.second && !it.second->isRemoved()) { + quickLoot[it.first] = it.second; + } + } + msg.addByte(quickLoot.size()); + for (auto it : quickLoot) { + msg.addByte(it.first); + msg.add(it.second->getClientID()); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendLootStats(Item* item) +{ + if (!item) { + return; + } + + NetworkMessage msg; + msg.addByte(0xCF); + AddItem(msg, item); + msg.addString(item->getName()); + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) { NetworkMessage msg; @@ -2004,11 +2153,11 @@ void ProtocolGame::updateCoinBalance() g_dispatcher.addTask( createTask(std::bind([](ProtocolGame* client) { if (client && client->player) { - account::Account account; - account.LoadAccountDB(client->player->getAccount()); - uint32_t coins; - account.GetCoins(&coins); - client->player->coinBalance = coins; + account::Account account; + account.LoadAccountDB(client->player->getAccount()); + uint32_t coins; + account.GetCoins(&coins); + client->player->coinBalance = coins; client->sendCoinBalance(); } }, this)) @@ -2621,9 +2770,9 @@ void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) void ProtocolGame::sendCreatureHealth(const Creature* creature) { - if (creature->isHealthHidden()) { - return; - } + if (creature->isHealthHidden()) { + return; + } NetworkMessage msg; msg.addByte(0x8C); @@ -2862,8 +3011,19 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos } } - sendBasicData(); sendInventoryClientIds(); + Item* slotItem = player->getInventoryItem(CONST_SLOT_BACKPACK); + if (slotItem) { + Container* mainBackpack = slotItem->getContainer(); + Container* hasQuickLootContainer = player->getLootContainer(OBJECTCATEGORY_DEFAULT); + if (mainBackpack && !hasQuickLootContainer) { + player->setLootContainer(OBJECTCATEGORY_DEFAULT, mainBackpack); + sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK)); + } + } + + sendLootContainers(); + sendBasicData(); initPreyData(); player->sendClientCheck(); @@ -3676,50 +3836,6 @@ void ProtocolGame::sendImbuementWindow(Item* item) writeToOutputBuffer(msg); } -void ProtocolGame::AddItem(NetworkMessage& msg, uint16_t id, uint8_t count) -{ - const ItemType& it = Item::items[id]; - - msg.add(it.clientId); - - if (it.stackable) { - msg.addByte(count); - } else if (it.isSplash() || it.isFluidContainer()) { - msg.addByte(fluidMap[count & 7]); - } else if (it.isContainer() && player->getOperatingSystem() <= CLIENTOS_NEW_WINDOWS) { - msg.addByte(0x00); - } - - if (it.isAnimation) { - msg.addByte(0xFE); // random phase (0xFF for async) - } -} - -void ProtocolGame::AddItem(NetworkMessage& msg, const Item* item) -{ - const ItemType& it = Item::items[item->getID()]; - - msg.add(it.clientId); - - if (it.stackable) { - msg.addByte(std::min(0xFF, item->getItemCount())); - } else if (it.isSplash() || it.isFluidContainer()) { - msg.addByte(fluidMap[item->getFluidType() & 7]); - } else if (it.isContainer() && player->getOperatingSystem() <= CLIENTOS_NEW_WINDOWS) { - uint32_t quickLootFlags = item->getQuickLootFlags(); - if (quickLootFlags > 0) { - msg.addByte(2); - msg.add(quickLootFlags); - } else { - msg.addByte(0x00); - } - } - - if (it.isAnimation) { - msg.addByte(0xFE); // random phase (0xFF for async) - } -} - void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) { msg.addByte(0x82); @@ -3970,3 +4086,17 @@ void ProtocolGame::reloadCreature(const Creature* creature) writeToOutputBuffer(msg); } + +void ProtocolGame::sendLockerItems(std::map itemMap, uint16_t count) +{ + NetworkMessage msg; + msg.addByte(0x94); + + msg.add(count); + for (const auto& it : itemMap) { + msg.addItemId(it.first); + msg.add(it.second); + } + + writeToOutputBuffer(msg); +} diff --git a/src/protocolgame.h b/src/protocolgame.h index bab069661d..383212d419 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -73,6 +73,8 @@ class ProtocolGame final : public Protocol void AddItem(NetworkMessage& msg, const Item* item); void AddItem(NetworkMessage& msg, uint16_t id, uint8_t count); + void sendLockerItems(std::map itemMap, uint16_t count); + uint16_t getVersion() const { return version; } @@ -104,6 +106,12 @@ class ProtocolGame final : public Protocol void parseSay(NetworkMessage& msg); void parseLookAt(NetworkMessage& msg); void parseLookInBattleList(NetworkMessage& msg); + + void parseQuickLoot(NetworkMessage& msg); + void parseLootContainer(NetworkMessage& msg); + void parseQuickLootBlackWhitelist(NetworkMessage& msg); + void parseRequestLockItems(); + void parseFightModes(NetworkMessage& msg); void parseAttack(NetworkMessage& msg); void parseFollow(NetworkMessage& msg); @@ -305,6 +313,10 @@ class ProtocolGame final : public Protocol void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); void sendCloseContainer(uint8_t cid); + //quickloot + void sendLootContainers(); + void sendLootStats(Item* item); + //inventory void sendInventoryItem(slots_t slot, const Item* item); void sendInventoryClientIds(); diff --git a/src/tile.cpp b/src/tile.cpp index e538b98508..8134600200 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1652,17 +1652,17 @@ Item* Tile::getDoorItem() const { const TileItemVector* items = getItemList(); if (!items || items->size() == 0) { - return ground; - } - - if (items) { - for (Item* item : *items) { - const ItemType& it = Item::items[item->getID()]; - if (it.isDoor()) { - return item; - } - } - } + return ground; + } + + if (items) { + for (Item* item : *items) { + const ItemType& it = Item::items[item->getID()]; + if (it.isDoor()) { + return item; + } + } + } return nullptr; } diff --git a/src/tile.h b/src/tile.h index abd6b391b1..cc89892eb3 100644 --- a/src/tile.h +++ b/src/tile.h @@ -267,8 +267,8 @@ class Tile : public Cylinder void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; - void internalAddThing(Thing* thing) override final; - void internalAddThing(uint32_t index, Thing* thing) override; + void internalAddThing(Thing* thing) override; + void virtual internalAddThing(uint32_t index, Thing* thing) override; const Position& getPosition() const override final { return tilePos; @@ -279,7 +279,7 @@ class Tile : public Cylinder } Item* getUseItem(int32_t index) const; - Item* getDoorItem() const; + Item* getDoorItem() const; Item* getGround() const { return ground; diff --git a/src/tools.cpp b/src/tools.cpp index 2e5511b109..c7ecde5e4c 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1371,4 +1371,38 @@ bool isCaskItem(uint16_t itemId) return (itemId >= ITEM_HEALTH_CASK_START && itemId <= ITEM_HEALTH_CASK_END) || (itemId >= ITEM_MANA_CASK_START && itemId <= ITEM_MANA_CASK_END) || (itemId >= ITEM_SPIRIT_CASK_START && itemId <= ITEM_SPIRIT_CASK_END); -} \ No newline at end of file +} + +std::string getObjectCategoryName(ObjectCategory_t category) +{ + switch (category) { + case OBJECTCATEGORY_ARMORS: return "Armors"; + case OBJECTCATEGORY_NECKLACES: return "Amulets"; + case OBJECTCATEGORY_BOOTS: return "Boots"; + case OBJECTCATEGORY_CONTAINERS: return "Containers"; + case OBJECTCATEGORY_DECORATION: return "Decoration"; + case OBJECTCATEGORY_FOOD: return "Food"; + case OBJECTCATEGORY_HELMETS: return "Helmets"; + case OBJECTCATEGORY_LEGS: return "Legs"; + case OBJECTCATEGORY_OTHERS: return "Others"; + case OBJECTCATEGORY_POTIONS: return "Potions"; + case OBJECTCATEGORY_RINGS: return "Rings"; + case OBJECTCATEGORY_RUNES: return "Runes"; + case OBJECTCATEGORY_SHIELDS: return "Shields"; + case OBJECTCATEGORY_TOOLS: return "Tools"; + case OBJECTCATEGORY_VALUABLES: return "Valuables"; + case OBJECTCATEGORY_AMMO: return "Weapons: Ammunition"; + case OBJECTCATEGORY_AXES: return "Weapons: Axes"; + case OBJECTCATEGORY_CLUBS: return "Weapons: Clubs"; + case OBJECTCATEGORY_DISTANCEWEAPONS: return "Weapons: Distance"; + case OBJECTCATEGORY_SWORDS: return "Weapons: Swords"; + case OBJECTCATEGORY_WANDS: return "Weapons: Wands"; + case OBJECTCATEGORY_PREMIUMSCROLLS: return "Premium Scrolls"; + case OBJECTCATEGORY_TIBIACOINS: return "Tibia Coins"; + case OBJECTCATEGORY_CREATUREPRODUCTS: return "Creature Products"; + case OBJECTCATEGORY_STASHRETRIEVE: return "Stash Retrieve"; + case OBJECTCATEGORY_GOLD: return "Gold"; + case OBJECTCATEGORY_DEFAULT: return "Unassigned Loot"; + default: return std::string(); + } +} diff --git a/src/tools.h b/src/tools.h index f77d192077..3d83411b66 100644 --- a/src/tools.h +++ b/src/tools.h @@ -100,6 +100,8 @@ NameEval_t validateName(const std::string &name); bool isCaskItem(uint16_t itemId); +std::string getObjectCategoryName(ObjectCategory_t category); + int64_t OTSYS_TIME(); SpellGroup_t stringToSpellGroup(std::string value);